diff --git a/openless-all/app/src-tauri/src/coordinator/capsule.rs b/openless-all/app/src-tauri/src/coordinator/capsule.rs index 9df2a57c..488ad645 100644 --- a/openless-all/app/src-tauri/src/coordinator/capsule.rs +++ b/openless-all/app/src-tauri/src/coordinator/capsule.rs @@ -29,6 +29,9 @@ pub(crate) fn capsule_show_strategy_for_platform() -> CapsuleShowStrategy { static CAPSULE_NO_ACTIVATE_FALLBACK_WARNED: AtomicBool = AtomicBool::new(false); static CAPSULE_SUPPRESSED_BY_TOGGLE_LOGGED: AtomicBool = AtomicBool::new(false); static CAPSULE_FIRST_SHOW_LOGGED: AtomicBool = AtomicBool::new(false); +// issue #631:上一次应用到胶囊窗口的点击穿透值。初始 false 与窗口创建时一致 +// (tauri.conf.json 未设 ignore),按变化去重,避免录音中 ~30Hz 电平帧重复调系统 API。 +static CAPSULE_IGNORE_CURSOR_APPLIED: AtomicBool = AtomicBool::new(false); // #470 诊断 v2:capsule webview 句柄取不到时的一次性门,区分「窗口压根没创建」(A0)。 static CAPSULE_WINDOW_MISSING_LOGGED: AtomicBool = AtomicBool::new(false); @@ -69,6 +72,18 @@ pub(crate) fn show_capsule_window_for_recording( } } +/// issue #631:该状态下胶囊窗口是否应忽略鼠标事件(点击穿透到下层应用)。 +/// 胶囊窗口 220×110 远大于可见 pill,贴近输入框时透明区域会吃掉用户点击并激活 +/// OpenLess(误触弹出主界面)。终态(Done/Cancelled/Error)与 Idle 没有可交互 +/// 按钮——包括 2s toast 停留和离场动画期间——让点击穿透;录音/转写/润色态有 +/// ✕/✓ 按钮,保持可点。 +pub(crate) fn capsule_ignore_cursor_for_state(state: CapsuleState) -> bool { + !matches!( + state, + CapsuleState::Recording | CapsuleState::Transcribing | CapsuleState::Polishing + ) +} + /// 终止态(Done / Cancelled / Error)后延迟 N ms 把胶囊改回 Idle,让浮窗自动消失。 /// 用户点 ✕ / ✓ / 中途出错 / 按 Esc 都走这里,统一 2 秒。 pub(crate) const CAPSULE_AUTO_HIDE_DELAY_MS: u64 = 2000; @@ -242,6 +257,13 @@ pub(crate) fn emit_capsule( #[cfg(not(target_os = "linux"))] { + // issue #631:终态/空闲让胶囊点击穿透,录音完成后用户点击贴近的输入框 + // 不再误触胶囊激活 OpenLess。状态变化时才真正调系统 API。 + let ignore_cursor = capsule_ignore_cursor_for_state(state); + if CAPSULE_IGNORE_CURSOR_APPLIED.swap(ignore_cursor, Ordering::SeqCst) != ignore_cursor { + let _ = window.set_ignore_cursor_events(ignore_cursor); + } + // 三平台统一:Done / Cancelled / Error 状态保留 ~1.5s toast // (schedule_capsule_idle 之后会回 Idle 隐藏)。 // Windows 上 linger 的真实问题(截图选中 / 死区 / 拖拽卡顿)由 #140 加的 diff --git a/openless-all/app/src-tauri/src/coordinator/tests.rs b/openless-all/app/src-tauri/src/coordinator/tests.rs index 51e01556..1050e984 100644 --- a/openless-all/app/src-tauri/src/coordinator/tests.rs +++ b/openless-all/app/src-tauri/src/coordinator/tests.rs @@ -810,6 +810,19 @@ fn window_hotkey_fallback_is_disabled_when_no_explicit_fallback_is_advertised() ); } +#[test] +fn capsule_ignore_cursor_only_in_non_interactive_states() { + // issue #631:有 ✕/✓ 按钮的三个状态必须可点;终态/空闲(含 toast 停留与 + // 离场动画期间)点击穿透,避免误触激活 OpenLess 弹出主界面。 + assert!(!capsule_ignore_cursor_for_state(CapsuleState::Recording)); + assert!(!capsule_ignore_cursor_for_state(CapsuleState::Transcribing)); + assert!(!capsule_ignore_cursor_for_state(CapsuleState::Polishing)); + assert!(capsule_ignore_cursor_for_state(CapsuleState::Done)); + assert!(capsule_ignore_cursor_for_state(CapsuleState::Cancelled)); + assert!(capsule_ignore_cursor_for_state(CapsuleState::Error)); + assert!(capsule_ignore_cursor_for_state(CapsuleState::Idle)); +} + #[test] fn capsule_show_strategy_matches_platform_activation_contract() { // 平台列表必须与 capsule_show_strategy_for_platform 的 cfg 完全一致: