From fb3455af88205b92330e9da3d38daff5955e453c Mon Sep 17 00:00:00 2001 From: Cooper-X-Oak Date: Sat, 2 May 2026 01:32:42 +0800 Subject: [PATCH 1/2] =?UTF-8?q?docs(windows):=20=E5=8D=A0=E4=BD=8D?= =?UTF-8?q?=E6=89=BF=E6=8E=A5=20QA=20hotkey=20=E8=B7=A8=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E5=BD=92=E4=B8=80=E4=BF=AE=E5=A4=8D=20(#148)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From a758cfa998f86b81cc091048e2c1f33250f4159b Mon Sep 17 00:00:00 2001 From: Cooper-X-Oak Date: Sat, 2 May 2026 06:01:56 +0800 Subject: [PATCH 2/2] fix(qa): normalize cmd modifier to ctrl on Windows --- openless-all/app/src-tauri/src/qa_hotkey.rs | 33 ++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/openless-all/app/src-tauri/src/qa_hotkey.rs b/openless-all/app/src-tauri/src/qa_hotkey.rs index d3186cb8..4740a012 100644 --- a/openless-all/app/src-tauri/src/qa_hotkey.rs +++ b/openless-all/app/src-tauri/src/qa_hotkey.rs @@ -173,7 +173,7 @@ fn forward_loop( fn parse_binding(binding: &QaHotkeyBinding) -> Result { let mut mods = Modifiers::empty(); for raw in &binding.modifiers { - let tag = raw.trim().to_ascii_lowercase(); + let tag = normalize_modifier_tag(raw); let bit = match tag.as_str() { "cmd" | "command" | "super" | "meta" | "win" => Modifiers::SUPER, "ctrl" | "control" => Modifiers::CONTROL, @@ -187,6 +187,17 @@ fn parse_binding(binding: &QaHotkeyBinding) -> Result { Ok(HotKey::new(Some(mods), code)) } +fn normalize_modifier_tag(raw: &str) -> String { + let tag = raw.trim().to_ascii_lowercase(); + #[cfg(target_os = "windows")] + { + if matches!(tag.as_str(), "cmd" | "command") { + return "ctrl".to_string(); + } + } + tag +} + /// 把用户配置的主键字符串解析成 keyboard_types::Code。 /// 支持单字符(字母 / 数字 / 符号)+ 常见命名键(F1..F12 / Enter / Tab / Escape / Space)。 fn parse_primary(raw: &str) -> Result { @@ -339,4 +350,24 @@ mod tests { Err(QaHotkeyError::UnsupportedKey(_)) )); } + + #[test] + fn cmd_modifier_normalizes_per_platform() { + let binding = QaHotkeyBinding { + primary: ";".into(), + modifiers: vec!["cmd".into(), "shift".into()], + }; + let parsed = parse_binding(&binding).expect("binding parses"); + + #[cfg(target_os = "windows")] + { + assert!(parsed.mods.contains(Modifiers::CONTROL)); + assert!(!parsed.mods.contains(Modifiers::SUPER)); + } + + #[cfg(not(target_os = "windows"))] + { + assert!(parsed.mods.contains(Modifiers::SUPER)); + } + } }