diff --git a/Keybinding.Test/ModifierAliasNormalizationTests.cs b/Keybinding.Test/ModifierAliasNormalizationTests.cs
index bdd46f0..449fd09 100644
--- a/Keybinding.Test/ModifierAliasNormalizationTests.cs
+++ b/Keybinding.Test/ModifierAliasNormalizationTests.cs
@@ -66,6 +66,44 @@ public void EveryConstructionPath_NormalizesModifierAliases(string alias, string
}
}
+ [TestMethod]
+ [DataRow("Esc", "Escape")]
+ [DataRow("Return", "Enter")]
+ [DataRow("Del", "Delete")]
+ [DataRow("Ins", "Insert")]
+ [DataRow("PgUp", "PageUp")]
+ [DataRow("PgDn", "PageDown")]
+ [DataRow("Up", "ArrowUp")]
+ [DataRow("UpArrow", "ArrowUp")]
+ [DataRow("Down", "ArrowDown")]
+ [DataRow("DownArrow", "ArrowDown")]
+ [DataRow("Left", "ArrowLeft")]
+ [DataRow("LeftArrow", "ArrowLeft")]
+ [DataRow("Right", "ArrowRight")]
+ [DataRow("RightArrow", "ArrowRight")]
+ [DataRow("Option", "Alt")]
+ [DataRow("Super", "Meta")]
+ [DataRow("D0", "0")]
+ [DataRow("D1", "1")]
+ [DataRow("D2", "2")]
+ [DataRow("D3", "3")]
+ [DataRow("D4", "4")]
+ [DataRow("D5", "5")]
+ [DataRow("D6", "6")]
+ [DataRow("D7", "7")]
+ [DataRow("D8", "8")]
+ [DataRow("D9", "9")]
+ public void ChordParse_KeyAliasesMatchCanonicalName(string alias, string canonical)
+ {
+ ArgumentNullException.ThrowIfNull(alias);
+ Chord expected = Chord.Parse($"Ctrl+{canonical}");
+ Chord actual = Chord.Parse($"Ctrl+{alias}");
+
+ Assert.AreEqual(expected, actual);
+ Assert.AreEqual(expected.GetHashCode(), actual.GetHashCode());
+ Assert.AreEqual(new Note(canonical), new Note(NoteName.Create(alias.ToUpperInvariant())));
+ }
+
[TestMethod]
[DataRow("control")]
[DataRow("Control")]
@@ -125,4 +163,20 @@ public async Task StoredProfileWithAliasSpelling_MatchesAfterReload()
Assert.AreEqual(Chord.Parse("Ctrl+S"), profile.GetChord("save"));
Assert.AreEqual(Chord.Parse("Meta+F"), profile.GetChord("find"));
}
+
+ [TestMethod]
+ public async Task StoredProfileWithKeyAlias_ExecutesCanonicalChordAfterLoad()
+ {
+ string json = """
+ [{"id":"p","name":"Profile","chords":{"save":{"notes":["CTRL","ESC"]}}}]
+ """;
+ await File.WriteAllTextAsync(Path.Combine(_testDataDirectory, Constants.Files.ProfilesFileName), json).ConfigureAwait(false);
+
+ using KeybindingManager manager = new(_testDataDirectory);
+ await manager.InitializeAsync().ConfigureAwait(false);
+ manager.Commands.RegisterCommand(new Command("save", "Save"));
+ manager.Profiles.SetActiveProfile("p");
+
+ Assert.AreEqual("save", manager.Keybindings.ExecuteChord(Chord.Parse("Ctrl+Escape")));
+ }
}
diff --git a/Keybinding/Models/MusicalTypes.cs b/Keybinding/Models/MusicalTypes.cs
index 16e514c..0ceac48 100644
--- a/Keybinding/Models/MusicalTypes.cs
+++ b/Keybinding/Models/MusicalTypes.cs
@@ -59,17 +59,36 @@ private static string ValidateKey(string key) =>
: key;
///
- /// Maps a modifier alias to its canonical key name, so that every way of building a note
+ /// Maps key aliases to canonical key names, so that every way of building a note
/// (parsing, constructing directly, or loading a stored profile) compares and hashes the same.
///
/// An uppercase key name
/// The canonical key name
- private static string CanonicalizeKey(string key) => key switch
+ private static string CanonicalizeKey(string key)
{
- "CONTROL" => "CTRL",
- "WIN" or "WINDOWS" or "CMD" or "COMMAND" => "META",
- _ => key
- };
+ if (key.Length == 2 && key[0] == 'D' && key[1] is >= '0' and <= '9')
+ {
+ return key[1..];
+ }
+
+ return key switch
+ {
+ "CONTROL" => "CTRL",
+ "OPTION" => "ALT",
+ "WIN" or "WINDOWS" or "CMD" or "COMMAND" or "SUPER" => "META",
+ "ESC" => nameof(SpecialKeys.Escape).ToUpperInvariant(),
+ "RETURN" => nameof(SpecialKeys.Enter).ToUpperInvariant(),
+ "DEL" => nameof(SpecialKeys.Delete).ToUpperInvariant(),
+ "INS" => nameof(SpecialKeys.Insert).ToUpperInvariant(),
+ "PGUP" => nameof(SpecialKeys.PageUp).ToUpperInvariant(),
+ "PGDN" => nameof(SpecialKeys.PageDown).ToUpperInvariant(),
+ "UP" or "UPARROW" => nameof(SpecialKeys.ArrowUp).ToUpperInvariant(),
+ "DOWN" or "DOWNARROW" => nameof(SpecialKeys.ArrowDown).ToUpperInvariant(),
+ "LEFT" or "LEFTARROW" => nameof(SpecialKeys.ArrowLeft).ToUpperInvariant(),
+ "RIGHT" or "RIGHTARROW" => nameof(SpecialKeys.ArrowRight).ToUpperInvariant(),
+ _ => key
+ };
+ }
///
/// Gets the key that this note represents
@@ -309,7 +328,7 @@ public static Chord Parse(string value)
foreach (string part in parts)
{
- // The Note constructor normalizes modifier aliases such as "Control" and "Cmd"
+ // The Note constructor normalizes key aliases such as "Control" and "Esc"
notes.Add(new Note(part));
}