using System; using System.Collections.Generic; using System.Windows.Input; namespace ytLive.Services; /// /// A modifier+key chord (e.g. Ctrl+F10, Alt+F1, F5). /// Stored in the Settings table as "modifiers+vk" (both uint, decimal). /// public sealed record HotkeyChord(uint Modifiers, uint VirtualKey) { public const uint MOD_ALT = 0x0001; public const uint MOD_CONTROL = 0x0002; public const uint MOD_SHIFT = 0x0004; public const uint MOD_WIN = 0x0008; private static readonly Dictionary ModifierDisplay = new() { [MOD_CONTROL] = "Ctrl", [MOD_ALT] = "Alt", [MOD_SHIFT] = "Shift", [MOD_WIN] = "Win", }; /// Storage format: "modifiers+vk" (both decimal uint). public override string ToString() => $"{Modifiers}+{VirtualKey}"; /// Parse from storage format ("modifiers+vk"). public static HotkeyChord ParseStorage(string s) { var parts = s.Split('+'); if (parts.Length != 2 || !uint.TryParse(parts[0], out var mod) || !uint.TryParse(parts[1], out var vk)) throw new FormatException($"Invalid hotkey storage format: {s}"); return new HotkeyChord(mod, vk); } /// Build from WPF Key + ModifierKeys (used during key capture). public static HotkeyChord FromWpf(Key key, ModifierKeys modifiers) { uint vk = (uint)KeyInterop.VirtualKeyFromKey(key); uint mod = 0; if (modifiers.HasFlag(ModifierKeys.Control)) mod |= MOD_CONTROL; if (modifiers.HasFlag(ModifierKeys.Alt)) mod |= MOD_ALT; if (modifiers.HasFlag(ModifierKeys.Shift)) mod |= MOD_SHIFT; if (modifiers.HasFlag(ModifierKeys.Windows)) mod |= MOD_WIN; return new HotkeyChord(mod, vk); } /// Human-readable display string: "Ctrl+F10", "Alt+F1", "F5". public string ToDisplayString() { var name = GetKeyName(VirtualKey); if (Modifiers == 0) return name; var sb = new System.Text.StringBuilder(); if ((Modifiers & MOD_CONTROL) != 0) { sb.Append("Ctrl"); sb.Append('+'); } if ((Modifiers & MOD_ALT) != 0) { sb.Append("Alt"); sb.Append('+'); } if ((Modifiers & MOD_SHIFT) != 0) { sb.Append("Shift"); sb.Append('+'); } if ((Modifiers & MOD_WIN) != 0) { sb.Append("Win"); sb.Append('+'); } sb.Append(name); return sb.ToString(); } /// Human-readable display string for a binding that may be null (unbound). public static string DisplayString(HotkeyChord? chord) => chord?.ToDisplayString() ?? "None"; private static string GetKeyName(uint vk) => KeyNames.TryGetValue(vk, out var name) ? name : $"VK 0x{vk:X2}"; private static readonly Dictionary KeyNames = new() { // Letters A-Z (0x41-0x5A) [0x41]="A",[0x42]="B",[0x43]="C",[0x44]="D",[0x45]="E", [0x46]="F",[0x47]="G",[0x48]="H",[0x49]="I",[0x4A]="J", [0x4B]="K",[0x4C]="L",[0x4D]="M",[0x4E]="N",[0x4F]="O", [0x50]="P",[0x51]="Q",[0x52]="R",[0x53]="S",[0x54]="T", [0x55]="U",[0x56]="V",[0x57]="W",[0x58]="X",[0x59]="Y", [0x5A]="Z", // Digits 0-9 (0x30-0x39) [0x30]="0",[0x31]="1",[0x32]="2",[0x33]="3",[0x34]="4", [0x35]="5",[0x36]="6",[0x37]="7",[0x38]="8",[0x39]="9", // Function keys F1-F24 (0x70-0x87) [0x70]="F1",[0x71]="F2",[0x72]="F3",[0x73]="F4",[0x74]="F5", [0x75]="F6",[0x76]="F7",[0x77]="F8",[0x78]="F9",[0x79]="F10", [0x7A]="F11",[0x7B]="F12",[0x7C]="F13",[0x7D]="F14", [0x7E]="F15",[0x7F]="F16",[0x80]="F17",[0x81]="F18", [0x82]="F19",[0x83]="F20",[0x84]="F21",[0x85]="F22", [0x86]="F23",[0x87]="F24", // Navigation [0x08]="Bksp",[0x09]="Tab",[0x0D]="Enter",[0x13]="Pause", [0x14]="CapsLock",[0x1B]="Esc",[0x20]="Space",[0x21]="PgUp", [0x22]="PgDn",[0x23]="End",[0x24]="Home",[0x25]="←", [0x26]="↑",[0x27]="→",[0x28]="↓",[0x2C]="PrintScreen", [0x2D]="Insert",[0x2E]="Delete", // Numpad [0x60]="Num0",[0x61]="Num1",[0x62]="Num2",[0x63]="Num3", [0x64]="Num4",[0x65]="Num5",[0x66]="Num6",[0x67]="Num7", [0x68]="Num8",[0x69]="Num9",[0x6A]="*",[0x6B]="+", [0x6C]=".",[0x6D]="-",[0x6E]=".",[0x6F]="/", [0x90]="NumLock",[0x91]="ScrollLock", // Punctuation / symbols [0xBA]=";",[0xBB]="=",[0xBC]=",",[0xBD]="-",[0xBE]=".", [0xBF]="/",[0xC0]="`",[0xDB]="[",[0xDC]="\\", [0xDD]="]",[0xDE]="'", // Media / browser / apps [0xA0]="LShift",[0xA1]="RShift", [0xA2]="LCtrl",[0xA3]="RCtrl", [0xA4]="LAlt",[0xA5]="RAlt", [0xAD]="Mute",[0xAE]="VolDown",[0xAF]="VolUp", [0xB0]="Next",[0xB1]="Prev",[0xB3]="Stop",[0xB5]="Play", [0xE2]="OEM_102", }; }