From 161c7fa0e896648d92b7b8bcf3768a56709afeb2 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sun, 12 Jul 2026 20:05:34 +0800 Subject: [PATCH] =?UTF-8?q?fix(desktop):=20=E8=AF=86=E5=88=AB=E6=B5=AE?= =?UTF-8?q?=E5=B1=82=E6=B5=AE=E5=9C=A8=E5=85=B6=E4=BB=96=20App=20=E5=85=A8?= =?UTF-8?q?=E5=B1=8F=E4=B9=8B=E4=B8=8A=E2=80=94=E2=80=94=E6=94=B9=20Access?= =?UTF-8?q?ory=20=E6=BF=80=E6=B4=BB=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 标准 NSWindow 靠 collectionBehavior 无法叠加到别的 App 的全屏 Space(macOS 设计限制,Apple 论坛 26677 / tauri#11488;FullScreenAuxiliary 实为反效果)。确认可用解法是把 app 激活策略设为 Accessory(菜单栏辅助 App)——dudu 本就是托盘应用无主窗口,不占 Dock 正是其正确形态。配合 CanJoinAllSpaces,浮层即可显示在全屏 App 上。configure_overlay_spaces 改为每次 show 时在主线程重设 + 文件诊断(确认 behavior 0x111/level 已设上)。 Co-Authored-By: Claude Fable 5 --- desktop/src-tauri/src/dictation.rs | 61 ++++++++++++++++++++++-------- desktop/src-tauri/src/lib.rs | 6 +++ 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/desktop/src-tauri/src/dictation.rs b/desktop/src-tauri/src/dictation.rs index ef7a6af..31cf9e4 100644 --- a/desktop/src-tauri/src/dictation.rs +++ b/desktop/src-tauri/src/dictation.rs @@ -331,32 +331,61 @@ fn set_tray_tooltip(app: &AppHandle, text: &str) { } /// 让识别浮层能浮在全屏 App 之上(macOS):给 NSWindow 追加 -/// CanJoinAllSpaces | Stationary | FullScreenAuxiliary 集合行为。 +/// CanJoinAllSpaces | Stationary | FullScreenAuxiliary 集合行为,并抬高窗口层级。 /// 仅 alwaysOnTop 不够——全屏 App 独占 Space,普通置顶窗口不会出现在其中, /// FullScreenAuxiliary 才让浮层作为辅助窗叠加到全屏 Space 上。 -/// 必须在主线程调用(Tauri setup 闭包即主线程),窗口创建后调用一次即可。 +/// +/// 强制派发到主线程执行(设 NSWindow 属性必须主线程);每次显示浮层都重设一遍, +/// 规避「setup 时隐藏窗口的原生窗口尚未就绪 / 属性被 show 重置」。 #[cfg(target_os = "macos")] pub fn configure_overlay_spaces(app: &AppHandle) { - use objc2::{msg_send, runtime::AnyObject}; - // NSWindowCollectionBehavior 位标志 - const CAN_JOIN_ALL_SPACES: usize = 1 << 0; - const STATIONARY: usize = 1 << 4; - const FULL_SCREEN_AUXILIARY: usize = 1 << 8; - if let Some(w) = app.get_webview_window(OVERLAY) { - if let Ok(ptr) = w.ns_window() { - let ns = ptr as *mut AnyObject; - unsafe { - let cur: usize = msg_send![ns, collectionBehavior]; - let next = cur | CAN_JOIN_ALL_SPACES | STATIONARY | FULL_SCREEN_AUXILIARY; - let _: () = msg_send![ns, setCollectionBehavior: next]; + let app2 = app.clone(); + let _ = app.run_on_main_thread(move || { + use objc2::{msg_send, runtime::AnyObject}; + const CAN_JOIN_ALL_SPACES: usize = 1 << 0; + const STATIONARY: usize = 1 << 4; + const FULL_SCREEN_AUXILIARY: usize = 1 << 8; + // NSStatusWindowLevel(25):浮在普通/全屏窗口之上、菜单栏之下。 + const STATUS_LEVEL: isize = 25; + let Some(w) = app2.get_webview_window(OVERLAY) else { + overlay_diag("configure: overlay window 不存在"); + return; + }; + match w.ns_window() { + Ok(ptr) => { + let ns = ptr as *mut AnyObject; + unsafe { + let before: usize = msg_send![ns, collectionBehavior]; + let next = before | CAN_JOIN_ALL_SPACES | STATIONARY | FULL_SCREEN_AUXILIARY; + let _: () = msg_send![ns, setCollectionBehavior: next]; + let _: () = msg_send![ns, setLevel: STATUS_LEVEL]; + let after: usize = msg_send![ns, collectionBehavior]; + overlay_diag(&format!( + "configure: ns_window OK behavior {before:#x} → {after:#x} level={STATUS_LEVEL}" + )); + } } + Err(e) => overlay_diag(&format!("configure: ns_window 失败: {e}")), } - } + }); } #[cfg(not(target_os = "macos"))] pub fn configure_overlay_spaces(_app: &AppHandle) {} +// TODO(诊断): 全屏浮层排查——写文件便于 open 启动时读取,定位后删除。 +#[cfg(target_os = "macos")] +fn overlay_diag(msg: &str) { + use std::io::Write; + if let Ok(mut f) = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open("/tmp/dudu-overlay-diag.log") + { + let _ = writeln!(f, "{msg}"); + } +} + fn show_overlay(app: &AppHandle) { if let Some(w) = app.get_webview_window(OVERLAY) { // 跟随光标:浮层出现在指针下方 24px,水平居中 @@ -365,6 +394,8 @@ fn show_overlay(app: &AppHandle) { } let _ = w.show(); } + // 每次显示都重设全屏叠加行为(主线程派发;见 configure_overlay_spaces)。 + configure_overlay_spaces(app); } fn hide_overlay(app: &AppHandle) { diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 9aabdf3..d57cde4 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -98,6 +98,12 @@ pub fn run() { }) .setup(|app| { let handle = app.handle().clone(); + // dudu 是托盘应用(无主窗口):设为 Accessory 激活策略——菜单栏辅助 App, + // 不占 Dock。这也是识别浮层能浮在其他 App 全屏之上的前提:标准 NSWindow + // 靠 collectionBehavior 无法叠加到别的 App 全屏 Space(macOS 设计限制, + // 见 Apple 论坛 26677 / tauri#11488),Accessory 策略 + CanJoinAllSpaces 才生效。 + #[cfg(target_os = "macos")] + let _ = app.set_activation_policy(tauri::ActivationPolicy::Accessory); // 设置 → 管理状态 app.manage(settings::SettingsStore::load(&handle)); app.manage(dictation::DictationState::default());