fix(hotkey): 补齐 Windows 主窗口前台热键#90
Merged
Merged
Conversation
Windows can deliver modifier-only key events to the focused WebView without the low-level hook seeing the same edge, so the main window now forwards only hotkey-relevant DOM key events to the coordinator. Both native hook and window-forwarded events share the same coordinator edge gate so a working hook does not double-trigger toggle sessions. Constraint: Issue Open-Less#85 requires OpenLess foreground Right Control to reuse existing pressed/released/cancel semantics without changing global hook behavior Rejected: Start dictation directly in the frontend | would duplicate coordinator state-machine semantics Rejected: Keep a separate window-held flag | review found it can double-trigger toggle mode when both DOM and low-level hook report the same physical press Confidence: medium Scope-risk: moderate Tested: cargo check --manifest-path openless-all/app/src-tauri/Cargo.toml Tested: cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml window_key_matcher_mirrors_windows_trigger_aliases Tested: npm run build Tested: git diff --check Not-tested: Physical Windows foreground Right Control on this Linux host
Reviewer's GuideAdds a Windows-foreground hotkey fallback path by forwarding specific key events from the Tauri frontend to the Rust coordinator, which matches them against the configured hotkey trigger and reuses the existing press/release/cancel logic while deduplicating edges and preserving the legacy global hook path. Sequence diagram for Windows foreground hotkey handlingsequenceDiagram
actor User
participant Window as TauriWindow
participant App as App_tsx
participant IPC as ipc_ts
participant Tauri as TauriCommands
participant Coord as Coordinator
participant Inner as InnerState
User->>Window: Focus OpenLess main window
User->>Window: Press RightControl
Window->>App: keydown event (KeyboardEvent)
App->>App: isWindowHotkeyCandidate(event)
alt hotkey candidate
App->>IPC: handleWindowHotkeyEvent("keydown", key, code, repeat)
IPC->>Tauri: invoke handle_window_hotkey_event
Tauri->>Coord: handle_window_hotkey_event(event_type, key, code, repeat)
Coord->>Inner: handle_window_hotkey_event(inner, event_type, key, code, repeat)
alt event_type == keydown and key == Escape
Inner->>Inner: cancel_session
else Windows target
Inner->>Inner: window_key_matches_trigger(trigger, key, code)
alt matches trigger and repeat == false
Inner->>Inner: handle_pressed_edge
Inner->>Inner: hotkey_trigger_held.swap(true)
alt was_held == false
Inner->>Inner: handle_pressed
end
end
end
else non hotkey candidate
App-->>Window: Ignore event
end
User->>Window: Release RightControl
Window->>App: keyup event (KeyboardEvent)
App->>App: isWindowHotkeyCandidate(event)
alt hotkey candidate
App->>IPC: handleWindowHotkeyEvent("keyup", key, code, repeat)
IPC->>Tauri: invoke handle_window_hotkey_event
Tauri->>Coord: handle_window_hotkey_event(event_type, key, code, repeat)
Coord->>Inner: handle_window_hotkey_event(inner, event_type, key, code, repeat)
alt event_type == keyup and matches trigger
Inner->>Inner: handle_released_edge
Inner->>Inner: hotkey_trigger_held.swap(false)
alt was_held == true
Inner->>Inner: handle_released
end
end
else non hotkey candidate
App-->>Window: Ignore event
end
Updated class diagram for Coordinator hotkey handlingclassDiagram
class Inner {
+Mutex<Option<Recorder>> recorder
+Mutex<Option<HotkeyMonitor>> hotkey
+Mutex<HotkeyStatus> hotkey_status
+AtomicBool hotkey_trigger_held
}
class Coordinator {
+inner: Arc<Inner>
+new() Coordinator
+cancel_dictation() void
+handle_window_hotkey_event(event_type: String, key: String, code: String, repeat: bool) Result~(), String~
}
class CommandsRs {
+handle_window_hotkey_event(coord: CoordinatorState, event_type: String, key: String, code: String, repeat: bool) Result~(), String~
}
class App_tsx {
+useEffect_osGate()
+useEffect_windowHotkeyForwarder()
+isWindowHotkeyCandidate(event: KeyboardEvent) bool
}
class Ipc_ts {
+handleWindowHotkeyEvent(eventType: string, key: string, code: string, repeat: boolean) Promise~void~
}
class HotkeyTrigger {
<<enum>>
RightControl
LeftControl
RightOption
RightAlt
LeftOption
RightCommand
Fn
}
class WindowKeyMatcher {
+window_key_matches_trigger(trigger: HotkeyTrigger, key: &str, code: &str) bool
}
class HotkeyEdges {
+handle_pressed_edge(inner: &Arc<Inner>) void
+handle_released_edge(inner: &Arc<Inner>) void
+handle_pressed(inner: &Arc<Inner>) void
+handle_released(inner: &Arc<Inner>) void
+handle_window_hotkey_event(inner: &Arc<Inner>, event_type: String, key: String, code: String, repeat: bool) Result~(), String~
}
Coordinator --> Inner
CommandsRs --> Coordinator
Ipc_ts --> CommandsRs
App_tsx --> Ipc_ts
HotkeyEdges --> Inner
WindowKeyMatcher --> HotkeyTrigger
HotkeyEdges --> WindowKeyMatcher
HotkeyEdges --> HotkeyTrigger
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The
handleWindowHotkeyEventIPC useseventTypeas the key on the frontend but the Tauri command expects anevent_typeargument, so the payload field name should be aligned on one side or the other to ensure Tauri correctly binds the parameter.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `handleWindowHotkeyEvent` IPC uses `eventType` as the key on the frontend but the Tauri command expects an `event_type` argument, so the payload field name should be aligned on one side or the other to ensure Tauri correctly binds the parameter.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
摘要
Fixes #85。
本 PR 修复 Windows 上 OpenLess 主窗口处于前台时,默认右 Ctrl 热键不触发的问题。
此前 Windows 全局热键在外部窗口前台时可以正常进入
WH_KEYBOARD_LL路径,但当焦点位于 OpenLess 主窗口自身时,UI/DOM 能收到ControlRight,后端全局 hook 路径却没有对应热键事件,导致默认rightControl无法触发按住说话。本次改动在 Windows 主窗口前台时增加一条窗口键盘事件兜底路径:前端只转发热键相关按键,后端根据当前
hotkey.trigger匹配event.key/event.code,命中后复用既有handle_pressed/handle_released/cancel_session语义。全局 hook 路径保持不变。修复 / 新增 / 改进
Windows 主窗口前台时监听
keydown/keyup。前端仅转发热键相关按键,避免把普通输入键盘事件发送到后端:
新增前端 IPC 方法:
handleWindowHotkeyEvent新增后端 Tauri command,用于接收窗口前台热键事件。
后端按当前
hotkey.trigger匹配窗口事件中的event.key/event.code。命中当前热键后复用既有 coordinator 路径:
handle_pressedhandle_releasedcancel_session增加 RightCtrl 等侧键匹配测试,覆盖侧键专用 code 匹配行为。
注册新的 Tauri command。
兼容
不包含:
WH_KEYBOARD_LLhook 路径。对现有用户 / 本地环境 / 构建流程的影响:
event.code保留左右键差异,例如ControlRight。测试计划
命令:
cargo check --manifest-path openless-all/app/src-tauri/Cargo.toml结果:通过,仅有既有 warnings
证据路径:本地检查输出
命令:
cargo test --manifest-path openless-all/app/src-tauri/Cargo.toml window_key_matcher_uses_side_specific_modifier_codes结果:通过
证据路径:本地测试输出
命令:
npm run build结果:通过
证据路径:本地构建输出
命令:
git diff --check结果:通过
证据路径:本地命令输出
主要改动文件
openless-all/app/src/App.tsxopenless-all/app/src/lib/ipc.tsopenless-all/app/src-tauri/src/commands.rsopenless-all/app/src-tauri/src/coordinator.rsopenless-all/app/src-tauri/src/lib.rs备注
本 PR 只补齐 OpenLess 主窗口前台时的 Windows 热键兜底路径。外部窗口前台时仍依赖现有全局 hook,不改变原有系统级热键行为。
Summary by Sourcery
Add a Windows foreground window hotkey path so the main OpenLess window can react to the configured trigger without relying solely on the global keyboard hook.
Bug Fixes:
Enhancements:
Tests: