diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index e01bc1efd..40c2cb956 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -11,6 +11,7 @@ false false false + true @@ -21,7 +22,6 @@ DEBUG;TRACE prompt 4 - true false @@ -32,7 +32,6 @@ TRACE prompt 4 - false false diff --git a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs index e92a93c12..a09185696 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs @@ -9,13 +9,14 @@ namespace Flow.Launcher.Infrastructure.Hotkey /// Listens keyboard globally. /// Uses WH_KEYBOARD_LL. /// - public class GlobalHotkey : IDisposable + public unsafe class GlobalHotkey : IDisposable { - private static GlobalHotkey instance; - private InterceptKeys.LowLevelKeyboardProc hookedLowLevelKeyboardProc; - private IntPtr hookId = IntPtr.Zero; + private static readonly IntPtr hookId; + + + public delegate bool KeyboardCallback(KeyEvent keyEvent, int vkCode, SpecialKeyState state); - public event KeyboardCallback hookedKeyboardCallback; + internal static Func hookedKeyboardCallback; //Modifier key constants private const int VK_SHIFT = 0x10; @@ -23,27 +24,13 @@ namespace Flow.Launcher.Infrastructure.Hotkey private const int VK_ALT = 0x12; private const int VK_WIN = 91; - public static GlobalHotkey Instance + static GlobalHotkey() { - get - { - if (instance == null) - { - instance = new GlobalHotkey(); - } - return instance; - } - } - - private GlobalHotkey() - { - // We have to store the LowLevelKeyboardProc, so that it is not garbage collected runtime - hookedLowLevelKeyboardProc = LowLevelKeyboardProc; // Set the hook - hookId = InterceptKeys.SetHook(hookedLowLevelKeyboardProc); + hookId = InterceptKeys.SetHook(& LowLevelKeyboardProc); } - public SpecialKeyState CheckModifiers() + public static SpecialKeyState CheckModifiers() { SpecialKeyState state = new SpecialKeyState(); if ((InterceptKeys.GetKeyState(VK_SHIFT) & 0x8000) != 0) @@ -70,8 +57,8 @@ namespace Flow.Launcher.Infrastructure.Hotkey return state; } - [MethodImpl(MethodImplOptions.NoInlining)] - private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam) + [UnmanagedCallersOnly] + private static IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam) { bool continues = true; @@ -91,17 +78,17 @@ namespace Flow.Launcher.Infrastructure.Hotkey { return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam); } - return (IntPtr)1; - } - - ~GlobalHotkey() - { - Dispose(); + return (IntPtr)(-1); } public void Dispose() { InterceptKeys.UnhookWindowsHookEx(hookId); } + + ~GlobalHotkey() + { + Dispose(); + } } } \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs b/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs index c45e685f3..d33bac34c 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs @@ -4,13 +4,13 @@ using System.Runtime.InteropServices; namespace Flow.Launcher.Infrastructure.Hotkey { - internal static class InterceptKeys + internal static unsafe class InterceptKeys { public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam); private const int WH_KEYBOARD_LL = 13; - public static IntPtr SetHook(LowLevelKeyboardProc proc) + public static IntPtr SetHook(delegate* unmanaged proc) { using (Process curProcess = Process.GetCurrentProcess()) using (ProcessModule curModule = curProcess.MainModule) @@ -20,7 +20,7 @@ namespace Flow.Launcher.Infrastructure.Hotkey } [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); + public static extern IntPtr SetWindowsHookEx(int idHook, delegate* unmanaged lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 0b59371f8..d1444d5db 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -119,7 +119,21 @@ namespace Flow.Launcher.Plugin /// Fired after global keyboard events /// if you want to hook something like Ctrl+R, you should use this event /// + [Obsolete("Unable to Retrieve correct return value")] event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; + + /// + /// Register a callback for Global Keyboard Event + /// + /// + public void RegisterGlobalKeyboardCallback(Func callback); + + /// + /// Remove a callback for Global Keyboard Event + /// + /// + public void RemoveGlobalKeyboardCallback(Func callback); + /// /// Fuzzy Search the string with the given query. This is the core search mechanism Flow uses diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 9ba7c4368..bc437d862 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -45,7 +45,7 @@ namespace Flow.Launcher //when alt is pressed, the real key should be e.SystemKey Key key = e.Key == Key.System ? e.SystemKey : e.Key; - SpecialKeyState specialKeyState = GlobalHotkey.Instance.CheckModifiers(); + SpecialKeyState specialKeyState = GlobalHotkey.CheckModifiers(); var hotkeyModel = new HotkeyModel( specialKeyState.AltPressed, diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index eb62733b7..6713929d6 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -41,7 +41,7 @@ namespace Flow.Launcher _settingsVM = settingsVM; _mainVM = mainVM; _alphabet = alphabet; - GlobalHotkey.Instance.hookedKeyboardCallback += KListener_hookedKeyboardCallback; + GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback; WebRequest.RegisterPrefix("data", new DataWebRequestFactory()); } @@ -204,25 +204,35 @@ namespace Flow.Launcher Arguments = FileName is null ? explorerInfo.DirectoryArgument.Replace("%d", DirectoryPath) : explorerInfo.FileArgument.Replace("%d", DirectoryPath).Replace("%f", - Path.IsPathRooted(FileName) ? FileName : Path.Combine(DirectoryPath, FileName)) + Path.IsPathRooted(FileName) ? FileName : Path.Combine(DirectoryPath, FileName)) }; explorer.Start(); } public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent; + private readonly List> _globalKeyboardHandlers = new(); + + public void RegisterGlobalKeyboardCallback(Func callback) => _globalKeyboardHandlers.Add(callback); + public void RemoveGlobalKeyboardCallback(Func callback) => _globalKeyboardHandlers.Remove(callback); + #endregion #region Private Methods private bool KListener_hookedKeyboardCallback(KeyEvent keyevent, int vkcode, SpecialKeyState state) { + var continueHook = true; if (GlobalKeyboardEvent != null) { - return GlobalKeyboardEvent((int)keyevent, vkcode, state); + continueHook = GlobalKeyboardEvent((int)keyevent, vkcode, state); + } + foreach (var x in _globalKeyboardHandlers) + { + continueHook &= x((int)keyevent, vkcode, state); } - return true; + return continueHook; } #endregion diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 187ba63ec..37052cca6 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -208,7 +208,7 @@ namespace Flow.Launcher.ViewModel { bool hideWindow = result.Action != null && result.Action(new ActionContext { - SpecialKeyState = GlobalHotkey.Instance.CheckModifiers() + SpecialKeyState = GlobalHotkey.CheckModifiers() }); if (hideWindow) @@ -244,8 +244,8 @@ namespace Flow.Launcher.ViewModel autoCompleteText = SelectedResults.SelectedItem.QuerySuggestionText; } - var SpecialKeyState = GlobalHotkey.Instance.CheckModifiers(); - if (SpecialKeyState.ShiftPressed) + var specialKeyState = GlobalHotkey.CheckModifiers(); + if (specialKeyState.ShiftPressed) { autoCompleteText = result.SubTitle; } diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index 5bf82cb10..0c539015e 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -312,7 +312,7 @@ namespace Flow.Launcher.Plugin.Shell { this.context = context; _settings = context.API.LoadSettingJsonStorage(); - context.API.GlobalKeyboardEvent += API_GlobalKeyboardEvent; + context.API.RegisterGlobalKeyboardCallback(API_GlobalKeyboardEvent); } bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state) @@ -337,12 +337,9 @@ namespace Flow.Launcher.Plugin.Shell private void OnWinRPressed() { - context.API.ChangeQuery($"{context.CurrentPluginMetadata.ActionKeywords[0]}{Plugin.Query.TermSeparator}"); - // show the main window and set focus to the query box - Window mainWindow = Application.Current.MainWindow; - mainWindow.Show(); - mainWindow.Focus(); + context.API.ShowMainWindow(); + context.API.ChangeQuery($"{context.CurrentPluginMetadata.ActionKeywords[0]}{Plugin.Query.TermSeparator}"); } public Control CreateSettingPanel() diff --git a/Plugins/Flow.Launcher.Plugin.Shell/plugin.json b/Plugins/Flow.Launcher.Plugin.Shell/plugin.json index b71dba2da..2cfcbca2e 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Shell/plugin.json @@ -4,7 +4,7 @@ "Name": "Shell", "Description": "Provide executing commands from Flow Launcher", "Author": "qianlifeng", - "Version": "1.4.6", + "Version": "1.4.7", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Shell.dll",