From 47bdbd15ebe86e2e3e268401be5ce5f1bc311e76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=9F=8F=E9=9D=92?= Date: Tue, 16 Jun 2026 12:33:03 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(hotkey):=205=20=E4=B8=AA=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=20setter=20=E6=8E=A5=E5=85=A5=20Less=20Computer=20?= =?UTF-8?q?=E9=94=AE=E7=A2=B0=E6=92=9E=E6=A0=A1=E9=AA=8C=EF=BC=8C=E6=B6=88?= =?UTF-8?q?=E9=99=A4=E4=B8=8E=E9=9B=86=E4=B8=AD=E5=BC=8F=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=E7=9A=84=E4=B8=8D=E4=B8=80=E8=87=B4=20(#684)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 集中式 reject_hotkey_collisions 已校验 coding_agent_voice_hotkey,但 set_dictation/translation/switch_style/open_app_hotkey(hotkeys.rs)与 set_qa_hotkey(qa.rs)这 5 个单键 setter 全部漏掉 less_computer 校验—— 用户可经单键面板把任一动作键设成与 Less Computer 键相同而不被拒,随后 完整保存又被 reject_hotkey_collisions 拒绝,两条路径不一致。 把早已存在但未接线的 reject_*_less_computer_hotkey_overlap 接进这 5 个 setter(与现有 pairwise 校验同模式);reject_qa_less_computer_hotkey_overlap 提升为 pub(crate) 供 qa.rs 调用。附 reject_hotkey_collisions 碰撞矩阵单测。 --- .../app/src-tauri/src/commands/hotkeys.rs | 68 ++++++++++++++++++- openless-all/app/src-tauri/src/commands/qa.rs | 3 + 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/openless-all/app/src-tauri/src/commands/hotkeys.rs b/openless-all/app/src-tauri/src/commands/hotkeys.rs index 2e4f712f..890c89cc 100644 --- a/openless-all/app/src-tauri/src/commands/hotkeys.rs +++ b/openless-all/app/src-tauri/src/commands/hotkeys.rs @@ -23,6 +23,9 @@ pub fn set_dictation_hotkey( if let Some(open_app) = prefs.open_app_hotkey.as_ref() { reject_dictation_open_app_hotkey_overlap(&binding, open_app)?; } + if let Some(less_computer) = prefs.coding_agent_voice_hotkey.as_ref() { + reject_dictation_less_computer_hotkey_overlap(&binding, less_computer)?; + } prefs.dictation_hotkey = binding; sync_dictation_hotkey_legacy_fields(&mut prefs); coord.prefs().set(prefs).map_err(|e| e.to_string())?; @@ -48,6 +51,9 @@ pub fn set_translation_hotkey( if let Some(open_app) = previous.open_app_hotkey.as_ref() { reject_translation_open_app_hotkey_overlap(&binding, open_app)?; } + if let Some(less_computer) = previous.coding_agent_voice_hotkey.as_ref() { + reject_translation_less_computer_hotkey_overlap(&binding, less_computer)?; + } let mut prefs = previous.clone(); prefs.translation_hotkey = binding; coord.prefs().set(prefs).map_err(|e| e.to_string())?; @@ -82,6 +88,9 @@ pub fn set_switch_style_hotkey( if let Some(open_app) = prefs.open_app_hotkey.as_ref() { reject_switch_style_open_app_hotkey_overlap(binding, open_app)?; } + if let Some(less_computer) = prefs.coding_agent_voice_hotkey.as_ref() { + reject_less_computer_switch_style_hotkey_overlap(less_computer, binding)?; + } } prefs.switch_style_hotkey = binding; coord.prefs().set(prefs).map_err(|e| e.to_string())?; @@ -109,6 +118,9 @@ pub fn set_open_app_hotkey( if let Some(switch_style) = prefs.switch_style_hotkey.as_ref() { reject_switch_style_open_app_hotkey_overlap(switch_style, binding)?; } + if let Some(less_computer) = prefs.coding_agent_voice_hotkey.as_ref() { + reject_less_computer_open_app_hotkey_overlap(less_computer, binding)?; + } } prefs.open_app_hotkey = binding; coord.prefs().set(prefs).map_err(|e| e.to_string())?; @@ -316,7 +328,7 @@ pub(crate) fn reject_qa_open_app_hotkey_overlap( reject_hotkey_overlap(qa, open_app, "打开应用快捷键不能和 QA 快捷键相同") } -fn reject_qa_less_computer_hotkey_overlap( +pub(crate) fn reject_qa_less_computer_hotkey_overlap( qa: &ShortcutBinding, less_computer: &ShortcutBinding, ) -> Result<(), String> { @@ -406,3 +418,57 @@ fn shortcut_bindings_overlap(left: &ShortcutBinding, right: &ShortcutBinding) -> } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn key(primary: &str) -> ShortcutBinding { + ShortcutBinding { + primary: primary.into(), + modifiers: vec![], + } + } + + /// 锁定碰撞矩阵:每个动作键与 Less Computer 键相同都必须被 reject_hotkey_collisions + /// 拒绝。5 个快捷 setter(set_dictation/translation/switch_style/open_app/qa_hotkey) + /// 此前漏校验 coding_agent_voice_hotkey,已接入对应的 less_computer 校验。 + #[test] + fn each_action_hotkey_collides_with_less_computer() { + let lc = key("LeftControl"); + let mut prefs = UserPreferences { + dictation_hotkey: key("A"), + translation_hotkey: key("B"), + qa_hotkey: Some(key("C")), + switch_style_hotkey: Some(key("D")), + open_app_hotkey: Some(key("E")), + coding_agent_voice_hotkey: Some(lc.clone()), + ..Default::default() + }; + // 基线全不同 → 通过。 + assert!(reject_hotkey_collisions(&prefs).is_ok()); + + prefs.dictation_hotkey = lc.clone(); + assert!(reject_hotkey_collisions(&prefs).is_err()); + prefs.dictation_hotkey = key("A"); + + prefs.translation_hotkey = lc.clone(); + assert!(reject_hotkey_collisions(&prefs).is_err()); + prefs.translation_hotkey = key("B"); + + prefs.qa_hotkey = Some(lc.clone()); + assert!(reject_hotkey_collisions(&prefs).is_err()); + prefs.qa_hotkey = Some(key("C")); + + prefs.switch_style_hotkey = Some(lc.clone()); + assert!(reject_hotkey_collisions(&prefs).is_err()); + prefs.switch_style_hotkey = Some(key("D")); + + prefs.open_app_hotkey = Some(lc.clone()); + assert!(reject_hotkey_collisions(&prefs).is_err()); + prefs.open_app_hotkey = Some(key("E")); + + // 复位后再次全不同 → 通过。 + assert!(reject_hotkey_collisions(&prefs).is_ok()); + } +} diff --git a/openless-all/app/src-tauri/src/commands/qa.rs b/openless-all/app/src-tauri/src/commands/qa.rs index 779f8787..33f92254 100644 --- a/openless-all/app/src-tauri/src/commands/qa.rs +++ b/openless-all/app/src-tauri/src/commands/qa.rs @@ -29,6 +29,9 @@ pub fn set_qa_hotkey( if let Some(open_app) = prefs.open_app_hotkey.as_ref() { reject_qa_open_app_hotkey_overlap(binding, open_app)?; } + if let Some(less_computer) = prefs.coding_agent_voice_hotkey.as_ref() { + reject_qa_less_computer_hotkey_overlap(binding, less_computer)?; + } } prefs.qa_hotkey = binding; coord.prefs().set(prefs).map_err(|e| e.to_string())?; From 2de1775f3d006d172f00cb57c8bd0502f0103fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=9F=8F=E9=9D=92?= Date: Tue, 16 Jun 2026 12:36:33 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(hotkey):=20set=5Fcombo=5Fhotkey=20?= =?UTF-8?q?=E4=B9=9F=E6=8E=A5=E5=85=A5=20Less=20Computer=20=E7=A2=B0?= =?UTF-8?q?=E6=92=9E=E6=A0=A1=E9=AA=8C=20(#684)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 组合听写键 setter 同样把组合键写进 dictation_hotkey 却漏校验 less_computer, 补上 reject_dictation_less_computer_hotkey_overlap,与其余 5 个 setter 一致。 --- openless-all/app/src-tauri/src/commands/hotkeys.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openless-all/app/src-tauri/src/commands/hotkeys.rs b/openless-all/app/src-tauri/src/commands/hotkeys.rs index 890c89cc..b22d875e 100644 --- a/openless-all/app/src-tauri/src/commands/hotkeys.rs +++ b/openless-all/app/src-tauri/src/commands/hotkeys.rs @@ -168,6 +168,9 @@ pub fn set_combo_hotkey(coord: CoordinatorState<'_>, binding: ComboBinding) -> R if let Some(open_app) = prefs.open_app_hotkey.as_ref() { reject_dictation_open_app_hotkey_overlap(&shortcut, open_app)?; } + if let Some(less_computer) = prefs.coding_agent_voice_hotkey.as_ref() { + reject_dictation_less_computer_hotkey_overlap(&shortcut, less_computer)?; + } prefs.custom_combo_hotkey = Some(binding); prefs.dictation_hotkey = shortcut; sync_dictation_hotkey_legacy_fields(&mut prefs);