From d301102d49f2d1efa678c0c67d3eb95782775548 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 23:29:06 +0000 Subject: [PATCH] Fold browser and Silk.NET letter and digit key names into the plain key [patch] Browsers send KeyA-KeyZ and Digit0-Digit9 (KeyboardEvent.code) and Silk.NET sends Number0-Number9, so `ControlLeft+KeyS` parsed as `Ctrl+KEYS` and never matched a stored `Ctrl+S`. CanonicalizeKey now folds these next to the WinForms D0-D9 rule. Numpad and keypad names stay distinct. Fixes ktsu-dev/Keybinding#218 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW26R7a4LaEL6NnFvBq --- .../ModifierAliasNormalizationTests.cs | 43 +++++++++++++++++++ Keybinding/Models/MusicalTypes.cs | 15 +++++++ 2 files changed, 58 insertions(+) diff --git a/Keybinding.Test/ModifierAliasNormalizationTests.cs b/Keybinding.Test/ModifierAliasNormalizationTests.cs index 449fd09..2f1c3aa 100644 --- a/Keybinding.Test/ModifierAliasNormalizationTests.cs +++ b/Keybinding.Test/ModifierAliasNormalizationTests.cs @@ -93,6 +93,16 @@ public void EveryConstructionPath_NormalizesModifierAliases(string alias, string [DataRow("D7", "7")] [DataRow("D8", "8")] [DataRow("D9", "9")] + // Browser KeyboardEvent.code and Silk.NET Key names for letters and digits (ktsu-dev/Keybinding#218) + [DataRow("KeyA", "A")] + [DataRow("KeyS", "S")] + [DataRow("KeyZ", "Z")] + [DataRow("Digit0", "0")] + [DataRow("Digit1", "1")] + [DataRow("Digit9", "9")] + [DataRow("Number0", "0")] + [DataRow("Number1", "1")] + [DataRow("Number9", "9")] public void ChordParse_KeyAliasesMatchCanonicalName(string alias, string canonical) { ArgumentNullException.ThrowIfNull(alias); @@ -104,6 +114,39 @@ public void ChordParse_KeyAliasesMatchCanonicalName(string alias, string canonic Assert.AreEqual(new Note(canonical), new Note(NoteName.Create(alias.ToUpperInvariant()))); } + [TestMethod] + [DataRow("ControlLeft+KeyS", "Ctrl+S")] + [DataRow("ControlLeft+Digit1", "Ctrl+1")] + [DataRow("ControlLeft+Number1", "Ctrl+1")] + [DataRow("ShiftLeft+MetaLeft+KeyK", "Shift+Meta+K")] + public void ChordParse_BrowserAndSilkKeyNames_DisplayAsCanonicalChord(string framework, string canonical) + { + Chord chord = Chord.Parse(framework); + + Assert.AreEqual(Chord.Parse(canonical), chord); + Assert.AreEqual(canonical, chord.ToString()); + } + + [TestMethod] + [DataRow("Numpad1")] + [DataRow("NumPad1")] + [DataRow("Keypad1")] + [DataRow("Key")] + [DataRow("Key1")] + [DataRow("KeyAB")] + [DataRow("Digit")] + [DataRow("DigitA")] + [DataRow("Digit10")] + [DataRow("Number")] + [DataRow("NumberA")] + public void NoteConstructor_OtherKeyNamesStayDistinct(string key) + { + ArgumentNullException.ThrowIfNull(key); + Note note = new(key); + + Assert.AreEqual(key.ToUpperInvariant(), note.Key.ToString()); + } + [TestMethod] [DataRow("control")] [DataRow("Control")] diff --git a/Keybinding/Models/MusicalTypes.cs b/Keybinding/Models/MusicalTypes.cs index acbe933..57db5f1 100644 --- a/Keybinding/Models/MusicalTypes.cs +++ b/Keybinding/Models/MusicalTypes.cs @@ -71,11 +71,26 @@ private static string ValidateKey(string key) => /// The canonical key name private static string CanonicalizeKey(string key) { + // Letter and main-row digit keys as UI frameworks name them: WinForms/WPF D0-D9, browser + // KeyboardEvent.code KeyA-KeyZ and Digit0-Digit9, and Silk.NET Key.Number0-Number9. + // Numpad keys (NumPad0-9, Numpad0-9, Keypad0-9) stay distinct, as an app may bind them separately. if (key.Length == 2 && key[0] == 'D' && key[1] is >= '0' and <= '9') { return key[1..]; } + if (key.Length == 4 && key.StartsWith("KEY", StringComparison.Ordinal) && key[3] is >= 'A' and <= 'Z') + { + return key[3..]; + } + + if (((key.Length == 6 && key.StartsWith("DIGIT", StringComparison.Ordinal)) + || (key.Length == 7 && key.StartsWith("NUMBER", StringComparison.Ordinal))) + && key[^1] is >= '0' and <= '9') + { + return key[^1..]; + } + return key switch { // Modifiers, including the left and right variants and the names UI frameworks give them