Skip to content

Commit 2855c6c

Browse files
authored
Merge pull request #834 from Flow-Launcher/GlobalHotkeyRefactor
[dev] Global hotkey refactor
2 parents 7bf6a7d + f701c0b commit 2855c6c

File tree

9 files changed

+56
-49
lines changed

9 files changed

+56
-49
lines changed

Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
1212
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
1313
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
14+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1415
</PropertyGroup>
1516

1617
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@@ -21,7 +22,6 @@
2122
<DefineConstants>DEBUG;TRACE</DefineConstants>
2223
<ErrorReport>prompt</ErrorReport>
2324
<WarningLevel>4</WarningLevel>
24-
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
2525
<Prefer32Bit>false</Prefer32Bit>
2626
</PropertyGroup>
2727

@@ -32,7 +32,6 @@
3232
<DefineConstants>TRACE</DefineConstants>
3333
<ErrorReport>prompt</ErrorReport>
3434
<WarningLevel>4</WarningLevel>
35-
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
3635
<Prefer32Bit>false</Prefer32Bit>
3736
</PropertyGroup>
3837

Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,28 @@ namespace Flow.Launcher.Infrastructure.Hotkey
99
/// Listens keyboard globally.
1010
/// <remarks>Uses WH_KEYBOARD_LL.</remarks>
1111
/// </summary>
12-
public class GlobalHotkey : IDisposable
12+
public unsafe class GlobalHotkey : IDisposable
1313
{
14-
private static GlobalHotkey instance;
15-
private InterceptKeys.LowLevelKeyboardProc hookedLowLevelKeyboardProc;
16-
private IntPtr hookId = IntPtr.Zero;
14+
private static readonly IntPtr hookId;
15+
16+
17+
1718
public delegate bool KeyboardCallback(KeyEvent keyEvent, int vkCode, SpecialKeyState state);
18-
public event KeyboardCallback hookedKeyboardCallback;
19+
internal static Func<KeyEvent, int, SpecialKeyState, bool> hookedKeyboardCallback;
1920

2021
//Modifier key constants
2122
private const int VK_SHIFT = 0x10;
2223
private const int VK_CONTROL = 0x11;
2324
private const int VK_ALT = 0x12;
2425
private const int VK_WIN = 91;
2526

26-
public static GlobalHotkey Instance
27+
static GlobalHotkey()
2728
{
28-
get
29-
{
30-
if (instance == null)
31-
{
32-
instance = new GlobalHotkey();
33-
}
34-
return instance;
35-
}
36-
}
37-
38-
private GlobalHotkey()
39-
{
40-
// We have to store the LowLevelKeyboardProc, so that it is not garbage collected runtime
41-
hookedLowLevelKeyboardProc = LowLevelKeyboardProc;
4229
// Set the hook
43-
hookId = InterceptKeys.SetHook(hookedLowLevelKeyboardProc);
30+
hookId = InterceptKeys.SetHook(& LowLevelKeyboardProc);
4431
}
4532

46-
public SpecialKeyState CheckModifiers()
33+
public static SpecialKeyState CheckModifiers()
4734
{
4835
SpecialKeyState state = new SpecialKeyState();
4936
if ((InterceptKeys.GetKeyState(VK_SHIFT) & 0x8000) != 0)
@@ -70,8 +57,8 @@ public SpecialKeyState CheckModifiers()
7057
return state;
7158
}
7259

73-
[MethodImpl(MethodImplOptions.NoInlining)]
74-
private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam)
60+
[UnmanagedCallersOnly]
61+
private static IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam)
7562
{
7663
bool continues = true;
7764

@@ -91,17 +78,17 @@ private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam)
9178
{
9279
return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
9380
}
94-
return (IntPtr)1;
81+
return (IntPtr)(-1);
9582
}
9683

97-
~GlobalHotkey()
84+
public void Dispose()
9885
{
99-
Dispose();
86+
InterceptKeys.UnhookWindowsHookEx(hookId);
10087
}
10188

102-
public void Dispose()
89+
~GlobalHotkey()
10390
{
104-
InterceptKeys.UnhookWindowsHookEx(hookId);
91+
Dispose();
10592
}
10693
}
10794
}

Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44

55
namespace Flow.Launcher.Infrastructure.Hotkey
66
{
7-
internal static class InterceptKeys
7+
internal static unsafe class InterceptKeys
88
{
99
public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam);
1010

1111
private const int WH_KEYBOARD_LL = 13;
1212

13-
public static IntPtr SetHook(LowLevelKeyboardProc proc)
13+
public static IntPtr SetHook(delegate* unmanaged<int, UIntPtr, IntPtr, IntPtr> proc)
1414
{
1515
using (Process curProcess = Process.GetCurrentProcess())
1616
using (ProcessModule curModule = curProcess.MainModule)
@@ -20,7 +20,7 @@ public static IntPtr SetHook(LowLevelKeyboardProc proc)
2020
}
2121

2222
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
23-
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
23+
public static extern IntPtr SetWindowsHookEx(int idHook, delegate* unmanaged<int, UIntPtr, IntPtr, IntPtr> lpfn, IntPtr hMod, uint dwThreadId);
2424

2525
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
2626
[return: MarshalAs(UnmanagedType.Bool)]

Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,21 @@ public interface IPublicAPI
119119
/// Fired after global keyboard events
120120
/// if you want to hook something like Ctrl+R, you should use this event
121121
/// </summary>
122+
[Obsolete("Unable to Retrieve correct return value")]
122123
event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent;
124+
125+
/// <summary>
126+
/// Register a callback for Global Keyboard Event
127+
/// </summary>
128+
/// <param name="callback"></param>
129+
public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback);
130+
131+
/// <summary>
132+
/// Remove a callback for Global Keyboard Event
133+
/// </summary>
134+
/// <param name="callback"></param>
135+
public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback);
136+
123137

124138
/// <summary>
125139
/// Fuzzy Search the string with the given query. This is the core search mechanism Flow uses

Flow.Launcher/HotkeyControl.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private void TbHotkey_OnPreviewKeyDown(object sender, KeyEventArgs e)
4545
//when alt is pressed, the real key should be e.SystemKey
4646
Key key = e.Key == Key.System ? e.SystemKey : e.Key;
4747

48-
SpecialKeyState specialKeyState = GlobalHotkey.Instance.CheckModifiers();
48+
SpecialKeyState specialKeyState = GlobalHotkey.CheckModifiers();
4949

5050
var hotkeyModel = new HotkeyModel(
5151
specialKeyState.AltPressed,

Flow.Launcher/PublicAPIInstance.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public PublicAPIInstance(SettingWindowViewModel settingsVM, MainViewModel mainVM
4141
_settingsVM = settingsVM;
4242
_mainVM = mainVM;
4343
_alphabet = alphabet;
44-
GlobalHotkey.Instance.hookedKeyboardCallback += KListener_hookedKeyboardCallback;
44+
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
4545
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
4646
}
4747

@@ -204,25 +204,35 @@ public void OpenDirectory(string DirectoryPath, string FileName = null)
204204
Arguments = FileName is null ?
205205
explorerInfo.DirectoryArgument.Replace("%d", DirectoryPath) :
206206
explorerInfo.FileArgument.Replace("%d", DirectoryPath).Replace("%f",
207-
Path.IsPathRooted(FileName) ? FileName : Path.Combine(DirectoryPath, FileName))
207+
Path.IsPathRooted(FileName) ? FileName : Path.Combine(DirectoryPath, FileName))
208208
};
209209
explorer.Start();
210210
}
211211

212212
public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent;
213213

214+
private readonly List<Func<int, int, SpecialKeyState, bool>> _globalKeyboardHandlers = new();
215+
216+
public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) => _globalKeyboardHandlers.Add(callback);
217+
public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) => _globalKeyboardHandlers.Remove(callback);
218+
214219
#endregion
215220

216221
#region Private Methods
217222

218223
private bool KListener_hookedKeyboardCallback(KeyEvent keyevent, int vkcode, SpecialKeyState state)
219224
{
225+
var continueHook = true;
220226
if (GlobalKeyboardEvent != null)
221227
{
222-
return GlobalKeyboardEvent((int)keyevent, vkcode, state);
228+
continueHook = GlobalKeyboardEvent((int)keyevent, vkcode, state);
229+
}
230+
foreach (var x in _globalKeyboardHandlers)
231+
{
232+
continueHook &= x((int)keyevent, vkcode, state);
223233
}
224234

225-
return true;
235+
return continueHook;
226236
}
227237

228238
#endregion

Flow.Launcher/ViewModel/MainViewModel.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ private void InitializeKeyCommands()
208208
{
209209
bool hideWindow = result.Action != null && result.Action(new ActionContext
210210
{
211-
SpecialKeyState = GlobalHotkey.Instance.CheckModifiers()
211+
SpecialKeyState = GlobalHotkey.CheckModifiers()
212212
});
213213

214214
if (hideWindow)
@@ -244,8 +244,8 @@ private void InitializeKeyCommands()
244244
autoCompleteText = SelectedResults.SelectedItem.QuerySuggestionText;
245245
}
246246

247-
var SpecialKeyState = GlobalHotkey.Instance.CheckModifiers();
248-
if (SpecialKeyState.ShiftPressed)
247+
var specialKeyState = GlobalHotkey.CheckModifiers();
248+
if (specialKeyState.ShiftPressed)
249249
{
250250
autoCompleteText = result.SubTitle;
251251
}

Plugins/Flow.Launcher.Plugin.Shell/Main.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ public void Init(PluginInitContext context)
312312
{
313313
this.context = context;
314314
_settings = context.API.LoadSettingJsonStorage<Settings>();
315-
context.API.GlobalKeyboardEvent += API_GlobalKeyboardEvent;
315+
context.API.RegisterGlobalKeyboardCallback(API_GlobalKeyboardEvent);
316316
}
317317

318318
bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state)
@@ -337,12 +337,9 @@ bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state)
337337

338338
private void OnWinRPressed()
339339
{
340-
context.API.ChangeQuery($"{context.CurrentPluginMetadata.ActionKeywords[0]}{Plugin.Query.TermSeparator}");
341-
342340
// show the main window and set focus to the query box
343-
Window mainWindow = Application.Current.MainWindow;
344-
mainWindow.Show();
345-
mainWindow.Focus();
341+
context.API.ShowMainWindow();
342+
context.API.ChangeQuery($"{context.CurrentPluginMetadata.ActionKeywords[0]}{Plugin.Query.TermSeparator}");
346343
}
347344

348345
public Control CreateSettingPanel()

Plugins/Flow.Launcher.Plugin.Shell/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"Name": "Shell",
55
"Description": "Provide executing commands from Flow Launcher",
66
"Author": "qianlifeng",
7-
"Version": "1.4.6",
7+
"Version": "1.4.7",
88
"Language": "csharp",
99
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
1010
"ExecuteFileName": "Flow.Launcher.Plugin.Shell.dll",

0 commit comments

Comments
 (0)