From 1dfcf7f3026c5d362dac8a1aeb4919b098c47e84 Mon Sep 17 00:00:00 2001 From: H-Chris233 Date: Fri, 1 May 2026 23:33:08 +0800 Subject: [PATCH] Avoid relayouting capsule on every audio tick The capsule window now tracks its last applied geometry and only resizes/repositions when the monitor or translation state actually changes. That keeps steady-state level updates from churning the native window manager while preserving the current placement behavior when geometry really does change. Constraint: Keep the fix minimal and avoid introducing a broader window-management abstraction. Rejected: Leave the unconditional relayout in place | it adds avoidable compositor churn during recording. Confidence: high Scope-risk: narrow Directive: Any future capsule geometry change should update the cached geometry key, not reintroduce unconditional relayout. Tested: cargo test --manifest-path src-tauri/Cargo.toml --lib -- --nocapture Not-tested: Live compositor/jitter behavior on Windows/macOS/Linux. --- openless-all/app/src-tauri/src/coordinator.rs | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index 999e1831..12a32ee5 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -111,6 +111,9 @@ struct Inner { qa_hotkey: Mutex>, /// QA 单独的 session 状态,与 dictation 的 SessionPhase 不冲突。 qa_state: Mutex, + /// 最近一次应用到 capsule 窗口的几何状态。避免录音 level tick 反复触发 + /// resize / reposition。 + capsule_layout: Mutex>, /// QA 用的 ASR 句柄(始终是 Volcengine 流式)。 qa_asr: Mutex>>, /// QA 用的 Recorder 句柄。 @@ -181,6 +184,7 @@ impl Coordinator { translation_modifier_seen: AtomicBool::new(false), qa_hotkey: Mutex::new(None), qa_state: Mutex::new(QaSessionState::default()), + capsule_layout: Mutex::new(None), qa_asr: Mutex::new(None), qa_recorder: Mutex::new(None), }), @@ -2451,7 +2455,7 @@ fn emit_capsule( let show_capsule = inner.prefs.get().show_capsule; if let Some(window) = app.get_webview_window("capsule") { let visible = !matches!(state, CapsuleState::Idle); - let _ = crate::position_capsule_bottom_center(&window, payload.translation); + maybe_position_capsule_bottom_center(inner, &window, payload.translation); if show_capsule && visible { if cfg!(target_os = "windows") { if !show_capsule_window_no_activate() { @@ -2472,6 +2476,41 @@ fn emit_capsule( let _ = app.emit_to("capsule", "capsule:state", payload); } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct CapsuleLayoutState { + translation_active: bool, + monitor_x: i32, + monitor_y: i32, + monitor_width: u32, + monitor_height: u32, + scale_bits: u64, +} + +fn maybe_position_capsule_bottom_center( + inner: &Arc, + window: &tauri::WebviewWindow, + translation_active: bool, +) { + let Some(monitor) = window.current_monitor().ok().flatten() else { + return; + }; + let next = CapsuleLayoutState { + translation_active, + monitor_x: monitor.position().x, + monitor_y: monitor.position().y, + monitor_width: monitor.size().width, + monitor_height: monitor.size().height, + scale_bits: monitor.scale_factor().to_bits(), + }; + let mut last = inner.capsule_layout.lock(); + if last.as_ref() == Some(&next) { + return; + } + *last = Some(next); + drop(last); + let _ = crate::position_capsule_bottom_center(window, translation_active); +} + // ─────────────────────────── audio bridge ─────────────────────────── struct DeferredAsrBridge {