Merge pull request #834 from Flow-Launcher/GlobalHotkeyRefactor

[dev] Global hotkey refactor
This commit is contained in:
Jeremy Wu 2021-12-08 20:56:03 +11:00 committed by GitHub
commit 2855c6c317
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 57 additions and 50 deletions

View file

@ -11,6 +11,7 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -21,7 +22,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
@ -32,7 +32,6 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>

View file

@ -9,13 +9,14 @@ namespace Flow.Launcher.Infrastructure.Hotkey
/// Listens keyboard globally.
/// <remarks>Uses WH_KEYBOARD_LL.</remarks>
/// </summary>
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<KeyEvent, int, SpecialKeyState, bool> 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();
}
}
}

View file

@ -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<int, UIntPtr, IntPtr, IntPtr> 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<int, UIntPtr, IntPtr, IntPtr> lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]

View file

@ -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
/// </summary>
[Obsolete("Unable to Retrieve correct return value")]
event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent;
/// <summary>
/// Register a callback for Global Keyboard Event
/// </summary>
/// <param name="callback"></param>
public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback);
/// <summary>
/// Remove a callback for Global Keyboard Event
/// </summary>
/// <param name="callback"></param>
public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback);
/// <summary>
/// Fuzzy Search the string with the given query. This is the core search mechanism Flow uses

View file

@ -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,

View file

@ -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<Func<int, int, SpecialKeyState, bool>> _globalKeyboardHandlers = new();
public void RegisterGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> callback) => _globalKeyboardHandlers.Add(callback);
public void RemoveGlobalKeyboardCallback(Func<int, int, SpecialKeyState, bool> 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

View file

@ -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;
}

View file

@ -312,7 +312,7 @@ namespace Flow.Launcher.Plugin.Shell
{
this.context = context;
_settings = context.API.LoadSettingJsonStorage<Settings>();
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()

View file

@ -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",