-
-
Notifications
You must be signed in to change notification settings - Fork 591
Enable Win+key combos as toggle hotkeys and intercept in dialog #4434
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,11 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Windows.Input; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using ChefKeys; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using CommunityToolkit.Mvvm.DependencyInjection; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using Flow.Launcher.Infrastructure.Hotkey; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using Flow.Launcher.Infrastructure.DialogJump; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using Flow.Launcher.Infrastructure.UserSettings; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using Flow.Launcher.Plugin; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using Flow.Launcher.ViewModel; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using NHotkey; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| using NHotkey.Wpf; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -16,6 +18,8 @@ internal static class HotKeyMapper | |||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| private static Settings _settings; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| private static MainViewModel _mainViewModel; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| private static Func<int, int, SpecialKeyState, bool> _winComboCallback; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| private static string _winComboHotkeyStr; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| internal static void Initialize() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -82,6 +86,14 @@ internal static void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> | |||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| catch (Exception e) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (hotkey.Win && hotkey.CharKey != Key.None) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| App.API.LogDebug(ClassName, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| $"|HotkeyMapper.SetHotkey|RegisterHotKey failed for {hotkeyStr} ({e.Message}); falling back to global keyboard callback."); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SetWithGlobalCallback(hotkey, action); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+89
to
+95
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard fallback registration failures to avoid uncaught exceptions. If Suggested fix if (hotkey.Win && hotkey.CharKey != Key.None)
{
- App.API.LogDebug(ClassName,
- $"|HotkeyMapper.SetHotkey|RegisterHotKey failed for {hotkeyStr} ({e.Message}); falling back to global keyboard callback.");
- SetWithGlobalCallback(hotkey, action);
- return;
+ try
+ {
+ App.API.LogDebug(ClassName,
+ $"|HotkeyMapper.SetHotkey|RegisterHotKey failed for {hotkeyStr} ({e.Message}); falling back to global keyboard callback.");
+ SetWithGlobalCallback(hotkey, action);
+ return;
+ }
+ catch (Exception fallbackEx)
+ {
+ App.API.LogError(ClassName,
+ $"|HotkeyMapper.SetHotkey|Fallback registration failed for {hotkeyStr}: {fallbackEx.Message} \nStackTrace:{fallbackEx.StackTrace}");
+ string errorMsg = Localize.registerHotkeyFailed(hotkeyStr);
+ string errorMsgTitle = Localize.MessageBoxTitle();
+ App.API.ShowMsgBox(errorMsg, errorMsgTitle);
+ return;
+ }
}Also applies to: 146-147 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| App.API.LogError(ClassName, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| string.Format("|HotkeyMapper.SetHotkey|Error registering hotkey {2}: {0} \nStackTrace:{1}", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| e.Message, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -93,6 +105,47 @@ internal static void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> | |||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| private static void SetWithGlobalCallback(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> action) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (_winComboCallback != null) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| App.API.RemoveGlobalKeyboardCallback(_winComboCallback); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| _winComboCallback = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| _winComboHotkeyStr = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| int expectedVkCode = KeyInterop.VirtualKeyFromKey(hotkey.CharKey); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| bool needCtrl = hotkey.Ctrl; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| bool needAlt = hotkey.Alt; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| bool needShift = hotkey.Shift; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| bool keyCurrentlyDown = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| _winComboCallback = (keyEvent, vkCode, state) => | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| bool isMatch = vkCode == expectedVkCode | ||||||||||||||||||||||||||||||||||||||||||||||||||
| && state.WinPressed | ||||||||||||||||||||||||||||||||||||||||||||||||||
| && state.CtrlPressed == needCtrl | ||||||||||||||||||||||||||||||||||||||||||||||||||
| && state.AltPressed == needAlt | ||||||||||||||||||||||||||||||||||||||||||||||||||
| && state.ShiftPressed == needShift; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isMatch && keyEvent == (int)KeyEvent.WM_KEYDOWN && !keyCurrentlyDown) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| keyCurrentlyDown = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| action?.Invoke(null, null); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isMatch && keyEvent == (int)KeyEvent.WM_KEYUP) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| keyCurrentlyDown = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify the hook emits WM_SYS* events while matcher currently filters on WM_KEY* only.
rg -nP --type=cs -C3 'WM_KEYDOWN|WM_KEYUP|WM_SYSKEYDOWN|WM_SYSKEYUP' \
Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs \
Flow.Launcher/Helper/HotKeyMapper.cs \
Flow.Launcher/HotkeyControlDialog.xaml.csRepository: Flow-Launcher/Flow.Launcher Length of output: 2731 🏁 Script executed: # Get the complete HotKeyMapper method to understand the stuck key scenario
rg -n 'class\s|public\s+(bool|void).*\s+Hook|private.*keyCurrentlyDown' \
Flow.Launcher/Helper/HotKeyMapper.cs -A 20 | head -100Repository: Flow-Launcher/Flow.Launcher Length of output: 858 🏁 Script executed: # Check the full method context around lines 131-140
sed -n '100,150p' Flow.Launcher/Helper/HotKeyMapper.csRepository: Flow-Launcher/Flow.Launcher Length of output: 1826 🏁 Script executed: # Look for any other places where keyCurrentlyDown is accessed/reset
rg -n 'keyCurrentlyDown' Flow.Launcher/Helper/HotKeyMapper.csRepository: Flow-Launcher/Flow.Launcher Length of output: 295 🏁 Script executed: # Check if there are tests or other references that show expected behavior
rg -n 'WinComboCallback|SetWithGlobalCallback|keyCurrentlyDown' \
Flow.Launcher/ -t cs --max-count=20Repository: Flow-Launcher/Flow.Launcher Length of output: 683 🏁 Script executed: # Look for how KeyEvent enum is defined to understand WM_SYS variants
rg -n 'enum KeyEvent|WM_KEYDOWN|WM_SYSKEYDOWN' Flow.Launcher.Infrastructure/ -t cs -B 2 -A 10Repository: Flow-Launcher/Flow.Launcher Length of output: 1549 🏁 Script executed: # Find and display the KeyEvent enum definition
rg -n 'enum KeyEvent' Flow.Launcher.Infrastructure/ -A 20Repository: Flow-Launcher/Flow.Launcher Length of output: 53 🏁 Script executed: # Also check if there are any unit tests for the hotkey functionality
fd -e '.*Test.*\.cs$' | xargs rg -l 'HotKeyMapper|SetWithGlobalCallback' 2>/dev/null || echo "No hotkey tests found"Repository: Flow-Launcher/Flow.Launcher Length of output: 402 🏁 Script executed: # Search more broadly for KeyEvent enum definition
rg -n 'enum KeyEvent' --type cs -iRepository: Flow-Launcher/Flow.Launcher Length of output: 132 🏁 Script executed: # Check in common locations for enums
find . -name '*.cs' -type f | xargs grep -l 'enum KeyEvent' 2>/dev/null | head -5Repository: Flow-Launcher/Flow.Launcher Length of output: 106 🏁 Script executed: # Look at the GlobalHotkey file more carefully to understand KeyEvent usage
sed -n '1,50p' Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.csRepository: Flow-Launcher/Flow.Launcher Length of output: 1976 🏁 Script executed: # Get the KeyEvent enum definition
cat Flow.Launcher.Plugin/KeyEvent.csRepository: Flow-Launcher/Flow.Launcher Length of output: 928 🏁 Script executed: # Verify the complete flow: check HotkeyControlDialog to see how it uses the matcher
cat -n Flow.Launcher/HotkeyControlDialog.xaml.cs | sed -n '50,80p'Repository: Flow-Launcher/Flow.Launcher Length of output: 1590 Include WM_SYS key events and fix stuck key latch in fallback matcher.* The hook emits 🔧 Suggested matcher fix- if (isMatch && keyEvent == (int)KeyEvent.WM_KEYDOWN && !keyCurrentlyDown)
+ if (isMatch
+ && (keyEvent == (int)KeyEvent.WM_KEYDOWN || keyEvent == (int)KeyEvent.WM_SYSKEYDOWN)
+ && !keyCurrentlyDown)
{
keyCurrentlyDown = true;
action?.Invoke(null, null);
return false;
}
- if (isMatch && keyEvent == (int)KeyEvent.WM_KEYUP)
+ if (vkCode == expectedVkCode
+ && (keyEvent == (int)KeyEvent.WM_KEYUP || keyEvent == (int)KeyEvent.WM_SYSKEYUP))
{
keyCurrentlyDown = false;
- return false;
+ return isMatch ? false : true;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| _winComboHotkeyStr = hotkey.ToString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| App.API.RegisterGlobalKeyboardCallback(_winComboCallback); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| internal static void RemoveHotkey(string hotkeyStr) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| try | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -103,6 +156,14 @@ internal static void RemoveHotkey(string hotkeyStr) | |||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if (_winComboCallback != null && hotkeyStr == _winComboHotkeyStr) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| App.API.RemoveGlobalKeyboardCallback(_winComboCallback); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| _winComboCallback = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| _winComboHotkeyStr = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!string.IsNullOrEmpty(hotkeyStr)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| HotkeyManager.Current.Remove(hotkeyStr); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.