dbfe743046
桌面壳拖拽图片 payload 失败只记录固定事件标签 扩展桌面壳配置门禁禁止输出 payload 错误详情 补充宿主壳方案和共享决策中的拖拽图片诊断边界
170 lines
5.7 KiB
Rust
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));
|
|
}
|
|
}
|