Files
Genarrative/apps/desktop-shell/src-tauri/src/shell/file_drop.rs
T
kdletters dbfe743046 收口桌面壳拖拽图片诊断日志
桌面壳拖拽图片 payload 失败只记录固定事件标签

扩展桌面壳配置门禁禁止输出 payload 错误详情

补充宿主壳方案和共享决策中的拖拽图片诊断边界
2026-06-21 03:01:56 +08:00

170 lines
5.7 KiB
Rust

use crate::host_bridge::file_payloads::{import_image_file_payload, import_image_mime_type};
use crate::shell::events::host_bridge_event_script;
use crate::shell::lifecycle::log_desktop_host_event_result;
use serde_json::Value;
use std::path::PathBuf;
use tauri::{DragDropEvent, WebviewWindow, WindowEvent};
fn first_valid_desktop_image_drop_payload(
paths: &[PathBuf],
position: (i32, i32),
) -> Option<Value> {
paths.iter().find_map(|path| {
if !path.is_file() || import_image_mime_type(path).is_none() {
return None;
}
match import_image_file_payload(path.clone(), "dropped", Some(position)) {
Ok(payload) => Some(payload),
Err(_error) => {
log_desktop_image_drop_payload_failure();
None
}
}
})
}
fn log_desktop_image_drop_payload_failure() -> bool {
eprintln!("desktop host event failed for file.imageDropped.payload");
false
}
fn normalize_desktop_drop_position(x: f64, y: f64) -> (i32, i32) {
(x.round().max(0.0) as i32, y.round().max(0.0) as i32)
}
fn emit_desktop_image_drop_event(
window: &WebviewWindow,
paths: &[PathBuf],
position: (i32, i32),
) -> tauri::Result<()> {
let Some(payload) = first_valid_desktop_image_drop_payload(paths, position) else {
return Ok(());
};
let script =
host_bridge_event_script("file.imageDropped", payload).map_err(tauri::Error::Json)?;
window.eval(script)
}
pub(crate) fn register_desktop_file_drop_events(window: &WebviewWindow) {
let drop_window = window.clone();
window.on_window_event(move |event| {
if let WindowEvent::DragDrop(DragDropEvent::Drop { paths, position }) = event {
let drop_position = normalize_desktop_drop_position(position.x, position.y);
log_desktop_host_event_result(
"file.imageDropped",
emit_desktop_image_drop_event(&drop_window, paths, drop_position),
);
}
});
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
fn png_bytes() -> Vec<u8> {
vec![0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a, 0, 0, 0, 0]
}
#[test]
fn image_drop_skips_disguised_image_before_valid_image() {
let invalid_path = std::env::temp_dir().join(format!(
"genarrative-desktop-drop-invalid-{}.png",
std::process::id()
));
let valid_path = std::env::temp_dir().join(format!(
"genarrative-desktop-drop-valid-{}.png",
std::process::id()
));
fs::write(&invalid_path, b"text").expect("write invalid drop image");
fs::write(&valid_path, png_bytes()).expect("write valid drop image");
let payload = first_valid_desktop_image_drop_payload(
&[invalid_path.clone(), valid_path.clone()],
(7, 9),
)
.expect("valid image drop payload");
assert_eq!(
payload["fileName"],
valid_path.file_name().unwrap().to_str().unwrap()
);
assert_eq!(payload["position"], serde_json::json!({ "x": 7, "y": 9 }));
fs::remove_file(invalid_path).expect("remove invalid drop image");
fs::remove_file(valid_path).expect("remove valid drop image");
}
#[test]
fn image_drop_logs_invalid_candidate_then_uses_next_valid_image() {
let disguised_path = std::env::temp_dir().join(format!(
"genarrative-desktop-drop-disguised-{}.png",
std::process::id()
));
let valid_path = std::env::temp_dir().join(format!(
"genarrative-desktop-drop-valid-after-error-{}.png",
std::process::id()
));
fs::write(&disguised_path, b"text").expect("write disguised drop image");
fs::write(&valid_path, png_bytes()).expect("write valid drop image");
let payload = first_valid_desktop_image_drop_payload(
&[disguised_path.clone(), valid_path.clone()],
(11, 13),
)
.expect("valid image drop payload after invalid candidate");
assert_eq!(
payload["fileName"],
valid_path.file_name().unwrap().to_str().unwrap()
);
assert_eq!(payload["position"], serde_json::json!({ "x": 11, "y": 13 }));
fs::remove_file(disguised_path).expect("remove disguised drop image");
fs::remove_file(valid_path).expect("remove valid drop image");
}
#[test]
fn image_drop_payload_failure_reports_failure() {
assert!(!log_desktop_image_drop_payload_failure());
}
#[test]
fn image_drop_payload_failure_logs_stable_label_only() {
assert!(!log_desktop_image_drop_payload_failure());
}
#[test]
fn image_drop_without_valid_image_does_not_emit_payload() {
let text_path = std::env::temp_dir().join(format!(
"genarrative-desktop-drop-text-{}.txt",
std::process::id()
));
let directory_path = std::env::temp_dir().join(format!(
"genarrative-desktop-drop-directory-{}",
std::process::id()
));
fs::write(&text_path, b"not an image").expect("write text drop file");
fs::create_dir_all(&directory_path).expect("create drop directory");
let payload = first_valid_desktop_image_drop_payload(
&[directory_path.clone(), text_path.clone()],
(3, 5),
);
assert_eq!(payload, None);
fs::remove_file(text_path).expect("remove text drop file");
fs::remove_dir(directory_path).expect("remove drop directory");
}
#[test]
fn desktop_drop_position_is_rounded_and_clamped_to_window_bounds() {
assert_eq!(normalize_desktop_drop_position(12.4, 24.6), (12, 25));
assert_eq!(normalize_desktop_drop_position(-4.2, -0.6), (0, 0));
}
}