fix(desktop): 识别浮层浮在其他 App 全屏之上——改 Accessory 激活策略
ci / server (push) Failing after 13s
ci / design-system (push) Failing after 12s

标准 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 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-12 20:05:34 +08:00
parent f8d3e0d1e4
commit 161c7fa0e8
2 changed files with 52 additions and 15 deletions
+46 -15
View File
@@ -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) {
+6
View File
@@ -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());