From cce67d27c7fe342212efff8f3a9cbae25fc2c21b Mon Sep 17 00:00:00 2001 From: ATARYS-LAB <342661122+ATARYS-LAB@users.noreply.github.com> Date: Tue, 3 Nov 2026 18:45:14 +0100 Subject: [PATCH] Shortcuts: outside macOS Ctrl and Cmd are one key when checking who has a shortcut One Ctrl press runs a Ctrl and a Cmd shortcut alike on Windows and Linux, so two commands could hold Ctrl+K and Cmd+K and only the first would ever run. same() now compares them as one key there; macOS keeps Control apart. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XkJ8y75DE7yLZ7utxSRDeP --- crates/ui-egui/src/keys.rs | 40 +++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/crates/ui-egui/src/keys.rs b/crates/ui-egui/src/keys.rs index 8f7252c49..2fd7419bd 100644 --- a/crates/ui-egui/src/keys.rs +++ b/crates/ui-egui/src/keys.rs @@ -30,8 +30,17 @@ const FIXED: &[&str] = &["copyclip", "cutclip", "pasteclip", "copybase", "pasteb /// The dialog name in `UiState::dialog`. pub const DIALOG: &str = "shortcuts"; +/// Whether two shortcuts are one key press. Outside macOS Ctrl is the command key, and one Ctrl +/// press runs a Ctrl and a Cmd shortcut alike, so they are compared as Cmd. fn same(a: &KeyboardShortcut, b: &KeyboardShortcut) -> bool { - a.logical_key == b.logical_key && a.modifiers == b.modifiers + let pressed = |m: Modifiers| { + if !cfg!(target_os = "macos") && (m.ctrl || m.command || m.mac_cmd) { + Modifiers { ctrl: false, command: true, mac_cmd: false, ..m } + } else { + m + } + }; + a.logical_key == b.logical_key && pressed(a.modifiers) == pressed(b.modifiers) } /// A registry shortcut string ("Cmd+Shift+G", "Alt+4", "F3") as a shortcut; `None` when empty or @@ -521,16 +530,20 @@ mod tests { fn control_and_command_control_shortcuts_keep_their_control() { let mut user = UserKeys::new(); assert_eq!(assign(&mut user, "line", "Ctrl+K"), Ok(vec![])); - assert_eq!(assign(&mut user, "circle", "Cmd+Ctrl+K"), Ok(vec![])); - assert_eq!(user.get("line").map(String::as_str), Some("Ctrl+K")); + // Outside macOS both are one Ctrl press, so Circle takes it from Line. + let lost = if cfg!(target_os = "macos") { vec![] } else { vec!["line".to_string()] }; + assert_eq!(assign(&mut user, "circle", "Cmd+Ctrl+K"), Ok(lost)); assert_eq!(user.get("circle").map(String::as_str), Some("Cmd+Ctrl+K")); let eff = effective(&user); let of = |id: &str| eff.iter().find(|(_, i)| i == id).map(|(s, _)| s.modifiers); - assert_eq!(of("line"), Some(Modifiers::CTRL)); assert_eq!(of("circle"), Some(Modifiers { ctrl: true, command: true, mac_cmd: true, ..Modifiers::NONE })); - // Command+Control+K is tried before Control+K, which it holds too. - let at = |id: &str| eff.iter().position(|(_, i)| i == id); - assert!(at("circle") < at("line")); + if cfg!(target_os = "macos") { + assert_eq!(user.get("line").map(String::as_str), Some("Ctrl+K")); + assert_eq!(of("line"), Some(Modifiers::CTRL)); + // Command+Control+K is tried before Control+K, which it holds too. + let at = |id: &str| eff.iter().position(|(_, i)| i == id); + assert!(at("circle") < at("line")); + } } #[test] @@ -549,6 +562,19 @@ mod tests { assert_eq!(assign(&mut user, "line", "Cmd+8"), Ok(vec!["ui.toggle.quickcalc".to_string()])); } + /// On Windows and Linux one Ctrl press runs a Ctrl and a Cmd shortcut alike, so they are one + /// key: giving it takes it from the command that had it, and preferences keep it once. + #[test] + #[cfg(not(target_os = "macos"))] + fn ctrl_and_cmd_are_one_key_outside_macos() { + let mut user = UserKeys::new(); + assert_eq!(assign(&mut user, "line", "Ctrl+P"), Ok(vec!["plot".to_string()])); + assert_eq!(runs(&user, "Cmd+P").as_deref(), Some("line")); + assert_eq!(assign(&mut user, "circle", "Cmd+P"), Ok(vec!["line".to_string()])); + let user = from_prefs(&json!({"circle": "Cmd+K", "line": "Ctrl+K"})); + assert_eq!(user.values().filter(|k| !k.is_empty()).count(), 1, "{user:?}"); + } + #[test] fn a_new_shortcut_replaces_the_commands_own_and_clear_removes_it() { let mut user = UserKeys::new();