From 7f5d80ff68a22538ecfec4d5a73e93e23a12e0be Mon Sep 17 00:00:00 2001 From: scoped Date: Sun, 22 Feb 2026 12:33:55 -0500 Subject: [PATCH] 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 ---