From 742c1d70fea12bc387c8bbc197f56d6d94f93b76 Mon Sep 17 00:00:00 2001 From: scoped Date: Sun, 22 Feb 2026 12:14:41 -0500 Subject: [PATCH 01/10] Refactor GlobalHotkeyService to support dynamic hotkey registration and update. Introduce HotkeyModifiers and HotkeyVirtualKey properties in SettingsService for user-defined hotkeys. --- ClipForge/GlobalHotkeyService.cs | 38 ++++--- ClipForge/HotkeyHelper.cs | 175 +++++++++++++++++++++++++++++++ ClipForge/SettingsService.cs | 7 +- 3 files changed, 204 insertions(+), 16 deletions(-) create mode 100644 ClipForge/HotkeyHelper.cs diff --git a/ClipForge/GlobalHotkeyService.cs b/ClipForge/GlobalHotkeyService.cs index d786b51..551cb5e 100644 --- a/ClipForge/GlobalHotkeyService.cs +++ b/ClipForge/GlobalHotkeyService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.InteropServices; using Microsoft.UI.Xaml; @@ -6,39 +6,49 @@ namespace ClipForge { public class GlobalHotkeyService { - // These two lines talk directly to Windows to register/unregister hotkeys [DllImport("user32.dll")] private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); [DllImport("user32.dll")] private static extern bool UnregisterHotKey(IntPtr hWnd, int id); - // This is the ID we'll use to identify our clip hotkey private const int HOTKEY_CLIP = 1; + private const uint MOD_NOREPEAT = 0x4000; // don't fire on key repeat - // Alt key modifier - private const uint MOD_ALT = 0x0001; - - // F9 key - private const uint VK_F9 = 0x78; - - // This is the "event" that fires when the hotkey is pressed public event Action? ClipRequested; private IntPtr _hwnd; + private uint _modifiers; + private uint _vk; - public void Initialize(IntPtr hwnd) + /// Register the clip hotkey. Use settings values (e.g. HotkeyModifiers, HotkeyVirtualKey). + public void Initialize(IntPtr hwnd, uint modifiers, uint vk) { _hwnd = hwnd; - RegisterHotKey(_hwnd, HOTKEY_CLIP, MOD_ALT, VK_F9); + _modifiers = modifiers; + _vk = vk; + TryRegister(); + } + + /// Update the hotkey at runtime (e.g. after user remaps). Unregisters old and registers new. + public bool UpdateHotkey(uint modifiers, uint vk) + { + UnregisterHotKey(_hwnd, HOTKEY_CLIP); + _modifiers = modifiers; + _vk = vk; + return TryRegister(); + } + + private bool TryRegister() + { + uint mod = _modifiers | MOD_NOREPEAT; + return RegisterHotKey(_hwnd, HOTKEY_CLIP, mod, _vk); } public void ProcessHotkey(int id) { if (id == HOTKEY_CLIP) - { ClipRequested?.Invoke(); - } } public void Cleanup() diff --git a/ClipForge/HotkeyHelper.cs b/ClipForge/HotkeyHelper.cs new file mode 100644 index 0000000..f1f50fa --- /dev/null +++ b/ClipForge/HotkeyHelper.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using Windows.System; + +namespace ClipForge +{ + /// Converts between hotkey modifier/vk and display strings. + public static class HotkeyHelper + { + public const uint MOD_ALT = 0x0001; + public const uint MOD_CONTROL = 0x0002; + public const uint MOD_SHIFT = 0x0004; + public const uint MOD_WIN = 0x0008; + public const uint MOD_NOREPEAT = 0x4000; + + private static readonly Dictionary KeyNames = new() + { + { VirtualKey.LeftButton, "LMB" }, + { VirtualKey.RightButton, "RMB" }, + { VirtualKey.Cancel, "Cancel" }, + { VirtualKey.Back, "Backspace" }, + { VirtualKey.Tab, "Tab" }, + { VirtualKey.Clear, "Clear" }, + { VirtualKey.Enter, "Enter" }, + { VirtualKey.Shift, "Shift" }, + { VirtualKey.Control, "Ctrl" }, + { VirtualKey.Menu, "Alt" }, + { VirtualKey.Pause, "Pause" }, + { VirtualKey.CapitalLock, "Caps Lock" }, + { VirtualKey.Kana, "Kana" }, + { VirtualKey.Hangul, "Hangul" }, + { VirtualKey.Junja, "Junja" }, + { VirtualKey.Final, "Final" }, + { VirtualKey.Hanja, "Hanja" }, + { VirtualKey.Kanji, "Kanji" }, + { VirtualKey.Escape, "Escape" }, + { VirtualKey.Convert, "Convert" }, + { VirtualKey.NonConvert, "NonConvert" }, + { VirtualKey.Accept, "Accept" }, + { VirtualKey.ModeChange, "ModeChange" }, + { VirtualKey.Space, "Space" }, + { VirtualKey.PageUp, "PgUp" }, + { VirtualKey.PageDown, "PgDn" }, + { VirtualKey.End, "End" }, + { VirtualKey.Home, "Home" }, + { VirtualKey.Left, "Left" }, + { VirtualKey.Up, "Up" }, + { VirtualKey.Right, "Right" }, + { VirtualKey.Down, "Down" }, + { VirtualKey.Select, "Select" }, + { VirtualKey.Print, "Print" }, + { VirtualKey.Execute, "Execute" }, + { VirtualKey.Snapshot, "Print Screen" }, + { VirtualKey.Insert, "Insert" }, + { VirtualKey.Delete, "Delete" }, + { VirtualKey.Help, "Help" }, + { VirtualKey.Number0, "0" }, + { VirtualKey.Number1, "1" }, + { VirtualKey.Number2, "2" }, + { VirtualKey.Number3, "3" }, + { VirtualKey.Number4, "4" }, + { VirtualKey.Number5, "5" }, + { VirtualKey.Number6, "6" }, + { VirtualKey.Number7, "7" }, + { VirtualKey.Number8, "8" }, + { VirtualKey.Number9, "9" }, + { VirtualKey.A, "A" }, + { VirtualKey.B, "B" }, + { VirtualKey.C, "C" }, + { VirtualKey.D, "D" }, + { VirtualKey.E, "E" }, + { VirtualKey.F, "F" }, + { VirtualKey.G, "G" }, + { VirtualKey.H, "H" }, + { VirtualKey.I, "I" }, + { VirtualKey.J, "J" }, + { VirtualKey.K, "K" }, + { VirtualKey.L, "L" }, + { VirtualKey.M, "M" }, + { VirtualKey.N, "N" }, + { VirtualKey.O, "O" }, + { VirtualKey.P, "P" }, + { VirtualKey.Q, "Q" }, + { VirtualKey.R, "R" }, + { VirtualKey.S, "S" }, + { VirtualKey.T, "T" }, + { VirtualKey.U, "U" }, + { VirtualKey.V, "V" }, + { VirtualKey.W, "W" }, + { VirtualKey.X, "X" }, + { VirtualKey.Y, "Y" }, + { VirtualKey.Z, "Z" }, + { VirtualKey.LeftWindows, "Win" }, + { VirtualKey.RightWindows, "Win" }, + { VirtualKey.Application, "App" }, + { VirtualKey.Sleep, "Sleep" }, + { VirtualKey.NumberPad0, "Num 0" }, + { VirtualKey.NumberPad1, "Num 1" }, + { VirtualKey.NumberPad2, "Num 2" }, + { VirtualKey.NumberPad3, "Num 3" }, + { VirtualKey.NumberPad4, "Num 4" }, + { VirtualKey.NumberPad5, "Num 5" }, + { VirtualKey.NumberPad6, "Num 6" }, + { VirtualKey.NumberPad7, "Num 7" }, + { VirtualKey.NumberPad8, "Num 8" }, + { VirtualKey.NumberPad9, "Num 9" }, + { VirtualKey.Multiply, "Num *" }, + { VirtualKey.Add, "Num +" }, + { VirtualKey.Separator, "Num ," }, + { VirtualKey.Subtract, "Num -" }, + { VirtualKey.Decimal, "Num ." }, + { VirtualKey.Divide, "Num /" }, + { VirtualKey.F1, "F1" }, + { VirtualKey.F2, "F2" }, + { VirtualKey.F3, "F3" }, + { VirtualKey.F4, "F4" }, + { VirtualKey.F5, "F5" }, + { VirtualKey.F6, "F6" }, + { VirtualKey.F7, "F7" }, + { VirtualKey.F8, "F8" }, + { VirtualKey.F9, "F9" }, + { VirtualKey.F10, "F10" }, + { VirtualKey.F11, "F11" }, + { VirtualKey.F12, "F12" }, + { VirtualKey.F13, "F13" }, + { VirtualKey.F14, "F14" }, + { VirtualKey.F15, "F15" }, + { VirtualKey.F16, "F16" }, + { VirtualKey.F17, "F17" }, + { VirtualKey.F18, "F18" }, + { VirtualKey.F19, "F19" }, + { VirtualKey.F20, "F20" }, + { VirtualKey.F21, "F21" }, + { VirtualKey.F22, "F22" }, + { VirtualKey.F23, "F23" }, + { VirtualKey.F24, "F24" }, + { VirtualKey.NumberKeyLock, "NumLock" }, + { VirtualKey.Scroll, "Scroll Lock" }, + { VirtualKey.LeftShift, "Shift" }, + { VirtualKey.RightShift, "Shift" }, + { VirtualKey.LeftControl, "Ctrl" }, + { VirtualKey.RightControl, "Ctrl" }, + { VirtualKey.LeftMenu, "Alt" }, + { VirtualKey.RightMenu, "Alt" }, + }; + + /// Format modifier flags + virtual key as display string (e.g. "Alt + PgUp"). + public static string ToDisplayString(uint modifiers, uint vk) + { + var parts = new List(); + if ((modifiers & MOD_WIN) != 0) parts.Add("Win"); + if ((modifiers & MOD_CONTROL) != 0) parts.Add("Ctrl"); + if ((modifiers & MOD_ALT) != 0) parts.Add("Alt"); + if ((modifiers & MOD_SHIFT) != 0) parts.Add("Shift"); + var keyName = GetKeyName((VirtualKey)vk); + if (!string.IsNullOrEmpty(keyName)) + parts.Add(keyName); + return parts.Count > 0 ? string.Join(" + ", parts) : "None"; + } + + public static string GetKeyName(VirtualKey key) + { + return KeyNames.TryGetValue(key, out var name) ? name : key.ToString(); + } + + /// True if the key is typically used only as a modifier (don't use as sole key). + public static bool IsModifierKey(VirtualKey key) + { + return key == VirtualKey.LeftWindows || key == VirtualKey.RightWindows + || key == VirtualKey.Control || key == VirtualKey.LeftControl || key == VirtualKey.RightControl + || key == VirtualKey.Menu || key == VirtualKey.LeftMenu || key == VirtualKey.RightMenu + || key == VirtualKey.Shift || key == VirtualKey.LeftShift || key == VirtualKey.RightShift; + } + } +} diff --git a/ClipForge/SettingsService.cs b/ClipForge/SettingsService.cs index 929eb3c..080aeea 100644 --- a/ClipForge/SettingsService.cs +++ b/ClipForge/SettingsService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Text.Json; @@ -9,7 +9,10 @@ namespace ClipForge public int ClipLengthSeconds { get; set; } = 30; public int VideoQuality { get; set; } = 70; public int Framerate { get; set; } = 60; - public string HotkeyDisplay { get; set; } = "Alt + F9"; + /// Windows modifier flags: Alt=1, Ctrl=2, Shift=4, Win=8. + public int HotkeyModifiers { get; set; } = 1; // MOD_ALT + /// Windows virtual key code (e.g. VK_F9=0x78, VK_PRIOR=0x21 for PgUp). + public int HotkeyVirtualKey { get; set; } = 0x78; // VK_F9 } public class SettingsService -- 2.49.1 From 52a2515e2dfb94d2400717be394fa239d7d9c19a Mon Sep 17 00:00:00 2001 From: scoped Date: Sun, 22 Feb 2026 12:14:57 -0500 Subject: [PATCH 02/10] Enhance GlobalHotkeyService to allow for dynamic hotkey updates and improve user experience with customizable HotkeyModifiers and HotkeyVirtualKey settings. --- ClipForge/ClipForge.csproj | 4 ++-- ClipForge/MainWindow.xaml | 18 ++++++++++-------- ClipForge/MainWindow.xaml.cs | 15 ++++++++++++++- ClipForge/Package.appxmanifest | 2 +- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/ClipForge/ClipForge.csproj b/ClipForge/ClipForge.csproj index af21841..41618fa 100644 --- a/ClipForge/ClipForge.csproj +++ b/ClipForge/ClipForge.csproj @@ -18,12 +18,12 @@ 4A55954F2A73A9D620442C7DFBFC7C95A71D8D24 SHA256 True - C:\Users\Blade\Desktop\Clipforge Packaged\V1\ + C:\Users\Blade\Desktop\Clipforge Packaged\V0.1\ False True Auto x64 - J:\Projects\ClipForge\ClipForge\bin\x64\Release\net8.0-windows10.0.19041.0 + C:\Users\Blade\Desktop\Clipforge Packaged 0 diff --git a/ClipForge/MainWindow.xaml b/ClipForge/MainWindow.xaml index 3e3959c..cf2712b 100644 --- a/ClipForge/MainWindow.xaml +++ b/ClipForge/MainWindow.xaml @@ -371,22 +371,24 @@ FontWeight="SemiBold" VerticalAlignment="Center"/> - - - + - - + diff --git a/ClipForge/MainWindow.xaml.cs b/ClipForge/MainWindow.xaml.cs index 4320aad..510dacf 100644 --- a/ClipForge/MainWindow.xaml.cs +++ b/ClipForge/MainWindow.xaml.cs @@ -1,10 +1,12 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media.Animation; using Microsoft.Win32; using System; using System.Linq; using System.Runtime.InteropServices; +using Windows.System; using WinRT.Interop; namespace ClipForge @@ -16,6 +18,7 @@ namespace ClipForge private ClipLibraryService _clipLibrary; private SettingsService _settingsService; private ThumbnailService _thumbnailService; + private bool _isRecordingHotkey; private delegate IntPtr WinProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); private WinProc _newWndProc; @@ -41,6 +44,13 @@ namespace ClipForge private static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + [DllImport("user32.dll")] + private static extern short GetAsyncKeyState(int vk); + + private const int VK_SHIFT = 0x10; + private const int VK_CONTROL = 0x11; + private const int VK_MENU = 0x12; // Alt + private const int VK_LWIN = 0x5B; private const int GWLP_WNDPROC = -4; private const uint WM_HOTKEY = 0x0312; private const int GWL_EXSTYLE = -20; @@ -78,7 +88,10 @@ namespace ClipForge _hotkeyService = new GlobalHotkeyService(); _hotkeyService.ClipRequested += OnClipRequested; - _hotkeyService.Initialize(hwnd); + var mod = _settingsService.Settings.HotkeyModifiers; + var vk = _settingsService.Settings.HotkeyVirtualKey; + if (mod == 0 && vk == 0) { mod = 1; vk = 0x78; } + _hotkeyService.Initialize(hwnd, (uint)mod, (uint)vk); ClipGrid.ItemsSource = _clipLibrary.Clips; UpdateClipCount(); diff --git a/ClipForge/Package.appxmanifest b/ClipForge/Package.appxmanifest index ca70092..c877d90 100644 --- a/ClipForge/Package.appxmanifest +++ b/ClipForge/Package.appxmanifest @@ -11,7 +11,7 @@ + Version="0.1.0.0" /> -- 2.49.1 From 6aa49c0b07d8c4f12ce7c7ee41196d83ca23c3f1 Mon Sep 17 00:00:00 2001 From: scoped Date: Sun, 22 Feb 2026 12:15:50 -0500 Subject: [PATCH 03/10] Add hotkey recording functionality in MainWindow. Users can now set custom hotkeys through a dedicated button, with real-time feedback on key presses and modifier keys. Updates to settings are reflected immediately. --- ClipForge/MainWindow.xaml.cs | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/ClipForge/MainWindow.xaml.cs b/ClipForge/MainWindow.xaml.cs index 510dacf..6e57291 100644 --- a/ClipForge/MainWindow.xaml.cs +++ b/ClipForge/MainWindow.xaml.cs @@ -182,6 +182,8 @@ namespace ClipForge QualityLabel.Text = $"{s.VideoQuality}%"; FramerateCombo.SelectedIndex = s.Framerate == 30 ? 0 : 1; StartupToggle.IsOn = IsStartupEnabled(); + if (HotkeyRecorderText != null) + HotkeyRecorderText.Text = HotkeyHelper.ToDisplayString((uint)s.HotkeyModifiers, (uint)s.HotkeyVirtualKey); } // --- STARTUP WITH WINDOWS --- @@ -248,6 +250,42 @@ namespace ClipForge _ = ShowToastAsync("✅ Settings saved!"); } + // --- CUSTOM HOTKEY --- + private void HotkeyRecorderButton_Click(object sender, RoutedEventArgs e) + { + if (_isRecordingHotkey) return; + _isRecordingHotkey = true; + HotkeyRecorderText.Text = "Press any key..."; + this.KeyDown += OnHotkeyCaptureKeyDown; + } + + private void OnHotkeyCaptureKeyDown(object sender, KeyRoutedEventArgs e) + { + if (!_isRecordingHotkey) return; + e.Handled = true; + + var key = e.Key; + if (HotkeyHelper.IsModifierKey(key)) + return; // wait for a non-modifier key + + uint mod = 0; + if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) mod |= HotkeyHelper.MOD_CONTROL; + if ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0) mod |= HotkeyHelper.MOD_ALT; + if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0) mod |= HotkeyHelper.MOD_SHIFT; + if ((GetAsyncKeyState(VK_LWIN) & 0x8000) != 0) mod |= HotkeyHelper.MOD_WIN; + + uint vk = (uint)key; + _settingsService.Settings.HotkeyModifiers = (int)mod; + _settingsService.Settings.HotkeyVirtualKey = (int)vk; + + var ok = _hotkeyService.UpdateHotkey(mod, vk); + var display = HotkeyHelper.ToDisplayString(mod, vk); + HotkeyRecorderText.Text = ok ? display : display + " (in use?)"; + + this.KeyDown -= OnHotkeyCaptureKeyDown; + _isRecordingHotkey = false; + } + // --- NAV --- private void NavClips_Click(object sender, RoutedEventArgs e) { -- 2.49.1 From 49028026155ac65130301ff0887fbe62db5b8038 Mon Sep 17 00:00:00 2001 From: scoped Date: Sun, 22 Feb 2026 12:17:03 -0500 Subject: [PATCH 04/10] Update MainWindow.xaml to include a name and focus properties for the root grid, enhancing accessibility and usability. --- ClipForge/MainWindow.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ClipForge/MainWindow.xaml b/ClipForge/MainWindow.xaml index cf2712b..3e4165a 100644 --- a/ClipForge/MainWindow.xaml +++ b/ClipForge/MainWindow.xaml @@ -13,7 +13,7 @@ - + -- 2.49.1 From 9b73bdbb247ca6c6c1f53c6a8442dd866ec67ba4 Mon Sep 17 00:00:00 2001 From: scoped Date: Sun, 22 Feb 2026 12:21:27 -0500 Subject: [PATCH 05/10] Refactor MainWindow.xaml to streamline the hotkey button definition by consolidating properties and removing unnecessary elements, improving code clarity. --- ClipForge/MainWindow.xaml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/ClipForge/MainWindow.xaml b/ClipForge/MainWindow.xaml index 3e4165a..e50bd9b 100644 --- a/ClipForge/MainWindow.xaml +++ b/ClipForge/MainWindow.xaml @@ -13,7 +13,7 @@ - + @@ -381,14 +381,12 @@ BorderThickness="0" CornerRadius="6" Padding="16,10" - MinWidth="140"> - - + MinWidth="140" + FontFamily="Consolas" + FontSize="14" + FontWeight="Bold" + Foreground="#E8FF47" + Content="Alt + F9"/> -- 2.49.1 From ae744f1077ef7d7ce8f7bf84eba94846ee41c871 Mon Sep 17 00:00:00 2001 From: scoped Date: Sun, 22 Feb 2026 12:21:38 -0500 Subject: [PATCH 06/10] Update MainWindow.xaml.cs to replace HotkeyRecorderText with HotkeyRecorderButton for displaying hotkey information, enhancing user interaction during hotkey recording. Adjustments made to ensure proper focus and event handling for the button. --- ClipForge/MainWindow.xaml.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ClipForge/MainWindow.xaml.cs b/ClipForge/MainWindow.xaml.cs index 6e57291..e9cde1e 100644 --- a/ClipForge/MainWindow.xaml.cs +++ b/ClipForge/MainWindow.xaml.cs @@ -182,8 +182,8 @@ namespace ClipForge QualityLabel.Text = $"{s.VideoQuality}%"; FramerateCombo.SelectedIndex = s.Framerate == 30 ? 0 : 1; StartupToggle.IsOn = IsStartupEnabled(); - if (HotkeyRecorderText != null) - HotkeyRecorderText.Text = HotkeyHelper.ToDisplayString((uint)s.HotkeyModifiers, (uint)s.HotkeyVirtualKey); + if (HotkeyRecorderButton != null) + HotkeyRecorderButton.Content = HotkeyHelper.ToDisplayString((uint)s.HotkeyModifiers, (uint)s.HotkeyVirtualKey); } // --- STARTUP WITH WINDOWS --- @@ -255,8 +255,9 @@ namespace ClipForge { if (_isRecordingHotkey) return; _isRecordingHotkey = true; - HotkeyRecorderText.Text = "Press any key..."; - this.KeyDown += OnHotkeyCaptureKeyDown; + HotkeyRecorderButton.Content = "Press any key..."; + HotkeyRecorderButton.Focus(FocusState.Programmatic); + HotkeyRecorderButton.KeyDown += OnHotkeyCaptureKeyDown; } private void OnHotkeyCaptureKeyDown(object sender, KeyRoutedEventArgs e) @@ -280,9 +281,9 @@ namespace ClipForge var ok = _hotkeyService.UpdateHotkey(mod, vk); var display = HotkeyHelper.ToDisplayString(mod, vk); - HotkeyRecorderText.Text = ok ? display : display + " (in use?)"; + HotkeyRecorderButton.Content = ok ? display : display + " (in use?)"; - this.KeyDown -= OnHotkeyCaptureKeyDown; + HotkeyRecorderButton.KeyDown -= OnHotkeyCaptureKeyDown; _isRecordingHotkey = false; } -- 2.49.1 From 7f5d80ff68a22538ecfec4d5a73e93e23a12e0be Mon Sep 17 00:00:00 2001 From: scoped Date: Sun, 22 Feb 2026 12:33:55 -0500 Subject: [PATCH 07/10] Enhance hotkey recording functionality in MainWindow.xaml.cs by adding escape key support to cancel recording and improving error handling. Introduce a delayed registration mechanism to prevent immediate re-entry of hotkeys, ensuring stability during hotkey updates. --- ClipForge/MainWindow.xaml.cs | 76 +++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/ClipForge/MainWindow.xaml.cs b/ClipForge/MainWindow.xaml.cs index e9cde1e..5d18733 100644 --- a/ClipForge/MainWindow.xaml.cs +++ b/ClipForge/MainWindow.xaml.cs @@ -255,7 +255,7 @@ namespace ClipForge { if (_isRecordingHotkey) return; _isRecordingHotkey = true; - HotkeyRecorderButton.Content = "Press any key..."; + HotkeyRecorderButton.Content = "Press any key... (Esc to cancel)"; HotkeyRecorderButton.Focus(FocusState.Programmatic); HotkeyRecorderButton.KeyDown += OnHotkeyCaptureKeyDown; } @@ -265,26 +265,72 @@ namespace ClipForge if (!_isRecordingHotkey) return; e.Handled = true; - var key = e.Key; - if (HotkeyHelper.IsModifierKey(key)) - return; // wait for a non-modifier key + try + { + var key = e.Key; + // Escape cancels without changing the hotkey + if (key == VirtualKey.Escape) + { + StopRecordingHotkey(restoreDisplay: true); + return; + } + if (HotkeyHelper.IsModifierKey(key)) + return; // wait for a non-modifier key + if (key == VirtualKey.None) + return; - uint mod = 0; - if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) mod |= HotkeyHelper.MOD_CONTROL; - if ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0) mod |= HotkeyHelper.MOD_ALT; - if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0) mod |= HotkeyHelper.MOD_SHIFT; - if ((GetAsyncKeyState(VK_LWIN) & 0x8000) != 0) mod |= HotkeyHelper.MOD_WIN; + uint mod = 0; + if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) mod |= HotkeyHelper.MOD_CONTROL; + if ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0) mod |= HotkeyHelper.MOD_ALT; + if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0) mod |= HotkeyHelper.MOD_SHIFT; + if ((GetAsyncKeyState(VK_LWIN) & 0x8000) != 0) mod |= HotkeyHelper.MOD_WIN; - uint vk = (uint)key; - _settingsService.Settings.HotkeyModifiers = (int)mod; - _settingsService.Settings.HotkeyVirtualKey = (int)vk; + uint vk = (uint)key; + _settingsService.Settings.HotkeyModifiers = (int)mod; + _settingsService.Settings.HotkeyVirtualKey = (int)vk; - var ok = _hotkeyService.UpdateHotkey(mod, vk); - var display = HotkeyHelper.ToDisplayString(mod, vk); - HotkeyRecorderButton.Content = ok ? display : display + " (in use?)"; + var display = HotkeyHelper.ToDisplayString(mod, vk); + StopRecordingHotkey(restoreDisplay: false); + HotkeyRecorderButton.Content = display; + // Defer registration so the key is released first; otherwise the new hotkey can fire + // immediately and re-enter (e.g. OnClipRequested) and crash. + _ = DelayedHotkeyRegister(mod, vk, display); + } + catch (Exception ex) + { + StopRecordingHotkey(restoreDisplay: true); + _ = ShowToastAsync("⚠ " + ex.Message); + } + } + + private void StopRecordingHotkey(bool restoreDisplay) + { HotkeyRecorderButton.KeyDown -= OnHotkeyCaptureKeyDown; _isRecordingHotkey = false; + if (restoreDisplay) + { + var s = _settingsService.Settings; + HotkeyRecorderButton.Content = HotkeyHelper.ToDisplayString((uint)s.HotkeyModifiers, (uint)s.HotkeyVirtualKey); + } + } + + private async System.Threading.Tasks.Task DelayedHotkeyRegister(uint mod, uint vk, string display) + { + await System.Threading.Tasks.Task.Delay(300); + App.MainQueue?.TryEnqueue(() => + { + try + { + var ok = _hotkeyService.UpdateHotkey(mod, vk); + HotkeyRecorderButton.Content = ok ? display : display + " (in use?)"; + } + catch (Exception ex) + { + HotkeyRecorderButton.Content = display; + _ = ShowToastAsync("⚠ " + ex.Message); + } + }); } // --- NAV --- -- 2.49.1 From fcd70b699d46fbecdd678a3188fe102014ae03d4 Mon Sep 17 00:00:00 2001 From: scoped Date: Sun, 22 Feb 2026 12:36:49 -0500 Subject: [PATCH 08/10] Refactor hotkey handling in MainWindow.xaml.cs to improve user experience by implementing a visual indicator for active hotkeys and enhancing the feedback mechanism during recording. Adjustments made to ensure better responsiveness and clarity in user interactions. --- ClipForge/HotkeyHelper.cs | 266 ++++++++++++++++++++------------------ 1 file changed, 137 insertions(+), 129 deletions(-) diff --git a/ClipForge/HotkeyHelper.cs b/ClipForge/HotkeyHelper.cs index f1f50fa..8b88a08 100644 --- a/ClipForge/HotkeyHelper.cs +++ b/ClipForge/HotkeyHelper.cs @@ -13,136 +13,144 @@ namespace ClipForge public const uint MOD_WIN = 0x0008; public const uint MOD_NOREPEAT = 0x4000; - private static readonly Dictionary KeyNames = new() + // Built in static ctor with TryAdd so duplicate VirtualKey enum values (e.g. Kana/Hangul = 0x15) don't throw. + private static readonly Dictionary KeyNames = new(); + + static HotkeyHelper() { - { VirtualKey.LeftButton, "LMB" }, - { VirtualKey.RightButton, "RMB" }, - { VirtualKey.Cancel, "Cancel" }, - { VirtualKey.Back, "Backspace" }, - { VirtualKey.Tab, "Tab" }, - { VirtualKey.Clear, "Clear" }, - { VirtualKey.Enter, "Enter" }, - { VirtualKey.Shift, "Shift" }, - { VirtualKey.Control, "Ctrl" }, - { VirtualKey.Menu, "Alt" }, - { VirtualKey.Pause, "Pause" }, - { VirtualKey.CapitalLock, "Caps Lock" }, - { VirtualKey.Kana, "Kana" }, - { VirtualKey.Hangul, "Hangul" }, - { VirtualKey.Junja, "Junja" }, - { VirtualKey.Final, "Final" }, - { VirtualKey.Hanja, "Hanja" }, - { VirtualKey.Kanji, "Kanji" }, - { VirtualKey.Escape, "Escape" }, - { VirtualKey.Convert, "Convert" }, - { VirtualKey.NonConvert, "NonConvert" }, - { VirtualKey.Accept, "Accept" }, - { VirtualKey.ModeChange, "ModeChange" }, - { VirtualKey.Space, "Space" }, - { VirtualKey.PageUp, "PgUp" }, - { VirtualKey.PageDown, "PgDn" }, - { VirtualKey.End, "End" }, - { VirtualKey.Home, "Home" }, - { VirtualKey.Left, "Left" }, - { VirtualKey.Up, "Up" }, - { VirtualKey.Right, "Right" }, - { VirtualKey.Down, "Down" }, - { VirtualKey.Select, "Select" }, - { VirtualKey.Print, "Print" }, - { VirtualKey.Execute, "Execute" }, - { VirtualKey.Snapshot, "Print Screen" }, - { VirtualKey.Insert, "Insert" }, - { VirtualKey.Delete, "Delete" }, - { VirtualKey.Help, "Help" }, - { VirtualKey.Number0, "0" }, - { VirtualKey.Number1, "1" }, - { VirtualKey.Number2, "2" }, - { VirtualKey.Number3, "3" }, - { VirtualKey.Number4, "4" }, - { VirtualKey.Number5, "5" }, - { VirtualKey.Number6, "6" }, - { VirtualKey.Number7, "7" }, - { VirtualKey.Number8, "8" }, - { VirtualKey.Number9, "9" }, - { VirtualKey.A, "A" }, - { VirtualKey.B, "B" }, - { VirtualKey.C, "C" }, - { VirtualKey.D, "D" }, - { VirtualKey.E, "E" }, - { VirtualKey.F, "F" }, - { VirtualKey.G, "G" }, - { VirtualKey.H, "H" }, - { VirtualKey.I, "I" }, - { VirtualKey.J, "J" }, - { VirtualKey.K, "K" }, - { VirtualKey.L, "L" }, - { VirtualKey.M, "M" }, - { VirtualKey.N, "N" }, - { VirtualKey.O, "O" }, - { VirtualKey.P, "P" }, - { VirtualKey.Q, "Q" }, - { VirtualKey.R, "R" }, - { VirtualKey.S, "S" }, - { VirtualKey.T, "T" }, - { VirtualKey.U, "U" }, - { VirtualKey.V, "V" }, - { VirtualKey.W, "W" }, - { VirtualKey.X, "X" }, - { VirtualKey.Y, "Y" }, - { VirtualKey.Z, "Z" }, - { VirtualKey.LeftWindows, "Win" }, - { VirtualKey.RightWindows, "Win" }, - { VirtualKey.Application, "App" }, - { VirtualKey.Sleep, "Sleep" }, - { VirtualKey.NumberPad0, "Num 0" }, - { VirtualKey.NumberPad1, "Num 1" }, - { VirtualKey.NumberPad2, "Num 2" }, - { VirtualKey.NumberPad3, "Num 3" }, - { VirtualKey.NumberPad4, "Num 4" }, - { VirtualKey.NumberPad5, "Num 5" }, - { VirtualKey.NumberPad6, "Num 6" }, - { VirtualKey.NumberPad7, "Num 7" }, - { VirtualKey.NumberPad8, "Num 8" }, - { VirtualKey.NumberPad9, "Num 9" }, - { VirtualKey.Multiply, "Num *" }, - { VirtualKey.Add, "Num +" }, - { VirtualKey.Separator, "Num ," }, - { VirtualKey.Subtract, "Num -" }, - { VirtualKey.Decimal, "Num ." }, - { VirtualKey.Divide, "Num /" }, - { VirtualKey.F1, "F1" }, - { VirtualKey.F2, "F2" }, - { VirtualKey.F3, "F3" }, - { VirtualKey.F4, "F4" }, - { VirtualKey.F5, "F5" }, - { VirtualKey.F6, "F6" }, - { VirtualKey.F7, "F7" }, - { VirtualKey.F8, "F8" }, - { VirtualKey.F9, "F9" }, - { VirtualKey.F10, "F10" }, - { VirtualKey.F11, "F11" }, - { VirtualKey.F12, "F12" }, - { VirtualKey.F13, "F13" }, - { VirtualKey.F14, "F14" }, - { VirtualKey.F15, "F15" }, - { VirtualKey.F16, "F16" }, - { VirtualKey.F17, "F17" }, - { VirtualKey.F18, "F18" }, - { VirtualKey.F19, "F19" }, - { VirtualKey.F20, "F20" }, - { VirtualKey.F21, "F21" }, - { VirtualKey.F22, "F22" }, - { VirtualKey.F23, "F23" }, - { VirtualKey.F24, "F24" }, - { VirtualKey.NumberKeyLock, "NumLock" }, - { VirtualKey.Scroll, "Scroll Lock" }, - { VirtualKey.LeftShift, "Shift" }, - { VirtualKey.RightShift, "Shift" }, - { VirtualKey.LeftControl, "Ctrl" }, - { VirtualKey.RightControl, "Ctrl" }, - { VirtualKey.LeftMenu, "Alt" }, - { VirtualKey.RightMenu, "Alt" }, - }; + TryAdd(VirtualKey.LeftButton, "LMB"); + TryAdd(VirtualKey.RightButton, "RMB"); + TryAdd(VirtualKey.Cancel, "Cancel"); + TryAdd(VirtualKey.Back, "Backspace"); + TryAdd(VirtualKey.Tab, "Tab"); + TryAdd(VirtualKey.Clear, "Clear"); + TryAdd(VirtualKey.Enter, "Enter"); + TryAdd(VirtualKey.Shift, "Shift"); + TryAdd(VirtualKey.Control, "Ctrl"); + TryAdd(VirtualKey.Menu, "Alt"); + TryAdd(VirtualKey.Pause, "Pause"); + TryAdd(VirtualKey.CapitalLock, "Caps Lock"); + TryAdd(VirtualKey.Kana, "Kana"); + TryAdd(VirtualKey.Hangul, "Hangul"); // same value as Kana on some SDKs; TryAdd skips duplicate + TryAdd(VirtualKey.Junja, "Junja"); + TryAdd(VirtualKey.Final, "Final"); + TryAdd(VirtualKey.Hanja, "Hanja"); + TryAdd(VirtualKey.Kanji, "Kanji"); + TryAdd(VirtualKey.Escape, "Escape"); + TryAdd(VirtualKey.Convert, "Convert"); + TryAdd(VirtualKey.NonConvert, "NonConvert"); + TryAdd(VirtualKey.Accept, "Accept"); + TryAdd(VirtualKey.ModeChange, "ModeChange"); + TryAdd(VirtualKey.Space, "Space"); + TryAdd(VirtualKey.PageUp, "PgUp"); + TryAdd(VirtualKey.PageDown, "PgDn"); + TryAdd(VirtualKey.End, "End"); + TryAdd(VirtualKey.Home, "Home"); + TryAdd(VirtualKey.Left, "Left"); + TryAdd(VirtualKey.Up, "Up"); + TryAdd(VirtualKey.Right, "Right"); + TryAdd(VirtualKey.Down, "Down"); + TryAdd(VirtualKey.Select, "Select"); + TryAdd(VirtualKey.Print, "Print"); + TryAdd(VirtualKey.Execute, "Execute"); + TryAdd(VirtualKey.Snapshot, "Print Screen"); + TryAdd(VirtualKey.Insert, "Insert"); + TryAdd(VirtualKey.Delete, "Delete"); + TryAdd(VirtualKey.Help, "Help"); + TryAdd(VirtualKey.Number0, "0"); + TryAdd(VirtualKey.Number1, "1"); + TryAdd(VirtualKey.Number2, "2"); + TryAdd(VirtualKey.Number3, "3"); + TryAdd(VirtualKey.Number4, "4"); + TryAdd(VirtualKey.Number5, "5"); + TryAdd(VirtualKey.Number6, "6"); + TryAdd(VirtualKey.Number7, "7"); + TryAdd(VirtualKey.Number8, "8"); + TryAdd(VirtualKey.Number9, "9"); + TryAdd(VirtualKey.A, "A"); + TryAdd(VirtualKey.B, "B"); + TryAdd(VirtualKey.C, "C"); + TryAdd(VirtualKey.D, "D"); + TryAdd(VirtualKey.E, "E"); + TryAdd(VirtualKey.F, "F"); + TryAdd(VirtualKey.G, "G"); + TryAdd(VirtualKey.H, "H"); + TryAdd(VirtualKey.I, "I"); + TryAdd(VirtualKey.J, "J"); + TryAdd(VirtualKey.K, "K"); + TryAdd(VirtualKey.L, "L"); + TryAdd(VirtualKey.M, "M"); + TryAdd(VirtualKey.N, "N"); + TryAdd(VirtualKey.O, "O"); + TryAdd(VirtualKey.P, "P"); + TryAdd(VirtualKey.Q, "Q"); + TryAdd(VirtualKey.R, "R"); + TryAdd(VirtualKey.S, "S"); + TryAdd(VirtualKey.T, "T"); + TryAdd(VirtualKey.U, "U"); + TryAdd(VirtualKey.V, "V"); + TryAdd(VirtualKey.W, "W"); + TryAdd(VirtualKey.X, "X"); + TryAdd(VirtualKey.Y, "Y"); + TryAdd(VirtualKey.Z, "Z"); + TryAdd(VirtualKey.LeftWindows, "Win"); + TryAdd(VirtualKey.RightWindows, "Win"); + TryAdd(VirtualKey.Application, "App"); + TryAdd(VirtualKey.Sleep, "Sleep"); + TryAdd(VirtualKey.NumberPad0, "Num 0"); + TryAdd(VirtualKey.NumberPad1, "Num 1"); + TryAdd(VirtualKey.NumberPad2, "Num 2"); + TryAdd(VirtualKey.NumberPad3, "Num 3"); + TryAdd(VirtualKey.NumberPad4, "Num 4"); + TryAdd(VirtualKey.NumberPad5, "Num 5"); + TryAdd(VirtualKey.NumberPad6, "Num 6"); + TryAdd(VirtualKey.NumberPad7, "Num 7"); + TryAdd(VirtualKey.NumberPad8, "Num 8"); + TryAdd(VirtualKey.NumberPad9, "Num 9"); + TryAdd(VirtualKey.Multiply, "Num *"); + TryAdd(VirtualKey.Add, "Num +"); + TryAdd(VirtualKey.Separator, "Num ,"); + TryAdd(VirtualKey.Subtract, "Num -"); + TryAdd(VirtualKey.Decimal, "Num ."); + TryAdd(VirtualKey.Divide, "Num /"); + TryAdd(VirtualKey.F1, "F1"); + TryAdd(VirtualKey.F2, "F2"); + TryAdd(VirtualKey.F3, "F3"); + TryAdd(VirtualKey.F4, "F4"); + TryAdd(VirtualKey.F5, "F5"); + TryAdd(VirtualKey.F6, "F6"); + TryAdd(VirtualKey.F7, "F7"); + TryAdd(VirtualKey.F8, "F8"); + TryAdd(VirtualKey.F9, "F9"); + TryAdd(VirtualKey.F10, "F10"); + TryAdd(VirtualKey.F11, "F11"); + TryAdd(VirtualKey.F12, "F12"); + TryAdd(VirtualKey.F13, "F13"); + TryAdd(VirtualKey.F14, "F14"); + TryAdd(VirtualKey.F15, "F15"); + TryAdd(VirtualKey.F16, "F16"); + TryAdd(VirtualKey.F17, "F17"); + TryAdd(VirtualKey.F18, "F18"); + TryAdd(VirtualKey.F19, "F19"); + TryAdd(VirtualKey.F20, "F20"); + TryAdd(VirtualKey.F21, "F21"); + TryAdd(VirtualKey.F22, "F22"); + TryAdd(VirtualKey.F23, "F23"); + TryAdd(VirtualKey.F24, "F24"); + TryAdd(VirtualKey.NumberKeyLock, "NumLock"); + TryAdd(VirtualKey.Scroll, "Scroll Lock"); + TryAdd(VirtualKey.LeftShift, "Shift"); + TryAdd(VirtualKey.RightShift, "Shift"); + TryAdd(VirtualKey.LeftControl, "Ctrl"); + TryAdd(VirtualKey.RightControl, "Ctrl"); + TryAdd(VirtualKey.LeftMenu, "Alt"); + TryAdd(VirtualKey.RightMenu, "Alt"); + } + + private static void TryAdd(VirtualKey key, string name) + { + KeyNames.TryAdd(key, name); + } /// Format modifier flags + virtual key as display string (e.g. "Alt + PgUp"). public static string ToDisplayString(uint modifiers, uint vk) -- 2.49.1 From fa5bc81bd6022e466f25f8027692c7a5364e36c5 Mon Sep 17 00:00:00 2001 From: scoped Date: Sun, 22 Feb 2026 12:41:51 -0500 Subject: [PATCH 09/10] Implement visual style updates for navigation buttons in MainWindow.xaml.cs to indicate active selections. This change enhances user experience by providing clear feedback on the currently selected page. --- ClipForge/MainWindow.xaml.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ClipForge/MainWindow.xaml.cs b/ClipForge/MainWindow.xaml.cs index 5d18733..4a82457 100644 --- a/ClipForge/MainWindow.xaml.cs +++ b/ClipForge/MainWindow.xaml.cs @@ -338,12 +338,16 @@ namespace ClipForge { ClipsPage.Visibility = Visibility.Visible; SettingsPage.Visibility = Visibility.Collapsed; + NavClips.Style = (Microsoft.UI.Xaml.Style)Application.Current.Resources["ClipForgeNavButtonSelectedStyle"]; + NavSettings.Style = (Microsoft.UI.Xaml.Style)Application.Current.Resources["ClipForgeNavButtonStyle"]; } private void NavSettings_Click(object sender, RoutedEventArgs e) { ClipsPage.Visibility = Visibility.Collapsed; SettingsPage.Visibility = Visibility.Visible; + NavSettings.Style = (Microsoft.UI.Xaml.Style)Application.Current.Resources["ClipForgeNavButtonSelectedStyle"]; + NavClips.Style = (Microsoft.UI.Xaml.Style)Application.Current.Resources["ClipForgeNavButtonStyle"]; } private void RecordButton_Click(object sender, RoutedEventArgs e) -- 2.49.1 From 86b4e10a83b21231246ce55ec3a5aaed27d509ad Mon Sep 17 00:00:00 2001 From: scoped Date: Sun, 22 Feb 2026 12:42:09 -0500 Subject: [PATCH 10/10] Refactor MainWindow.xaml.cs to improve the visual feedback for active selections in navigation buttons, enhancing user experience. This update includes style adjustments to better indicate the currently selected page. --- ClipForge/App.xaml | 39 +++++++- ClipForge/MainWindow.xaml | 196 ++++++++++++++++++++++++-------------- 2 files changed, 160 insertions(+), 75 deletions(-) diff --git a/ClipForge/App.xaml b/ClipForge/App.xaml index b3f8d56..d741abd 100644 --- a/ClipForge/App.xaml +++ b/ClipForge/App.xaml @@ -8,9 +8,44 @@ - - + + + + + + + + + + + + + + + + + + + diff --git a/ClipForge/MainWindow.xaml b/ClipForge/MainWindow.xaml index e50bd9b..bf013f1 100644 --- a/ClipForge/MainWindow.xaml +++ b/ClipForge/MainWindow.xaml @@ -13,56 +13,71 @@ - + - + - + - - - + + + + + + + + + - + @@ -91,7 +112,7 @@ - + @@ -102,37 +123,44 @@ - + + FontSize="26" + FontWeight="Bold" + Foreground="{StaticResource ClipForgeTextPrimaryBrush}"/> + Margin="0,0,0,4"/>