From 27dbeadcfd1431e5efab8343c008fb1e76fa744f Mon Sep 17 00:00:00 2001 From: Cooper-X-Oak Date: Sat, 2 May 2026 00:22:39 +0800 Subject: [PATCH] fix: make capsule click-through outside recording --- .../app/scripts/windows-ui-config.test.mjs | 50 ++++++++++++++++--- openless-all/app/src-tauri/src/coordinator.rs | 9 ++++ 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/openless-all/app/scripts/windows-ui-config.test.mjs b/openless-all/app/scripts/windows-ui-config.test.mjs index ad98ca64..caaf4c6b 100644 --- a/openless-all/app/scripts/windows-ui-config.test.mjs +++ b/openless-all/app/scripts/windows-ui-config.test.mjs @@ -6,12 +6,48 @@ function assertEqual(actual, expected, name) { } } -const raw = await readFile(new URL('../src-tauri/tauri.conf.json', import.meta.url), 'utf-8'); -const config = JSON.parse(raw); -const mainWindow = config.app.windows.find((window) => window.label === 'main'); - -if (!mainWindow) { - throw new Error('main window config missing'); +function assertMatch(source, pattern, name) { + if (!pattern.test(source)) { + throw new Error(`${name}: pattern ${pattern} not found`); + } } -assertEqual(mainWindow.decorations, false, 'windows main window should use only custom titlebar'); +const raw = await readFile(new URL('../src-tauri/tauri.conf.json', import.meta.url), 'utf-8'); +const config = JSON.parse(raw); +const capsuleWindow = config.app.windows.find((window) => window.label === 'capsule'); +const libRs = await readFile(new URL('../src-tauri/src/lib.rs', import.meta.url), 'utf-8'); +const coordinatorRs = await readFile(new URL('../src-tauri/src/coordinator.rs', import.meta.url), 'utf-8'); + +if (!capsuleWindow) { + throw new Error('capsule window config missing'); +} + +assertEqual(capsuleWindow.width, 220, 'windows capsule config keeps translation-capable width baseline'); +assertEqual(capsuleWindow.height, 110, 'windows capsule config keeps translation-capable height baseline'); +assertEqual(capsuleWindow.transparent, true, 'capsule window should keep transparent visuals'); +assertEqual(capsuleWindow.alwaysOnTop, true, 'capsule window should stay above the focused app while recording'); +assertMatch( + libRs, + /#\[cfg\(target_os = "windows"\)\][\s\S]*?\(196\.0, height\)/, + 'windows runtime capsule width should collapse to the visible pill', +); +assertMatch( + libRs, + /let height = if translation_active \{ 110\.0 \} else \{ 52\.0 \};/, + 'windows runtime capsule height should shrink outside translation mode', +); +assertMatch( + libRs, + /window\.set_size\(LogicalSize::new\(cap_w, cap_h\)\)\?/, + 'capsule positioning should resync runtime size with the computed layout', +); +assertMatch( + coordinatorRs, + /let accepts_cursor_events = matches!\(state, CapsuleState::Recording\);/, + 'windows capsule should only accept clicks while actively recording', +); +assertMatch( + coordinatorRs, + /window\.set_ignore_cursor_events\(!accepts_cursor_events\)/, + 'windows capsule should pass clicks through in non-recording states', +); diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index cca83683..4c0ed086 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -2456,6 +2456,15 @@ fn emit_capsule( if let Some(window) = app.get_webview_window("capsule") { let visible = !matches!(state, CapsuleState::Idle); maybe_position_capsule_bottom_center(inner, &window, payload.translation); + #[cfg(target_os = "windows")] + { + // The capsule is always-on-top on Windows. Once recording stops, it must + // stop intercepting clicks meant for the app underneath. + let accepts_cursor_events = matches!(state, CapsuleState::Recording); + if let Err(e) = window.set_ignore_cursor_events(!accepts_cursor_events) { + log::warn!("[capsule] set_ignore_cursor_events failed: {e}"); + } + } if show_capsule && visible { if cfg!(target_os = "windows") { if !show_capsule_window_no_activate() {