[PowerToys Run] Use global HotKey instead of low level keyboard hook (#13114)

* [PowerToys Run] Register global HotKey

Using low level keyboard hooks caused focus issues when invoking
PowerToys Run. Using a global HotKey solves this issue.

* Properly unregister hotkey on dispose

* fix spellchecker errors
This commit is contained in:
Jaime Bernardo 2021-09-08 18:39:51 +01:00 committed by GitHub
parent 2c58bdbfb2
commit 5963294b04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 189 additions and 50 deletions

View File

@ -652,6 +652,7 @@ FOF
FOFX
FOLDERID
folderpath
FORCEMINIMIZE
FORCEOFFLINE
foreach
formatetc
@ -1891,7 +1892,9 @@ SHOWDEFAULT
SHOWELEVATIONPROMPT
SHOWMAXIMIZED
SHOWMINIMIZED
SHOWMINNOACTIVE
SHOWNA
SHOWNOACTIVATE
SHOWNORMAL
SHOWWINDOW
shtypes

View File

@ -310,9 +310,13 @@ public:
enable();
}
/* Before we used the central keyboard hook to trigger PowerToys Run.
* Now, PowerToys Run uses a global hotkey so that it can get focus.
* This means we can't return true so that the hotkey can propagate to PowerToys Run.
Logger::trace("Set POWER_LAUNCHER_SHARED_EVENT");
SetEvent(m_hEvent);
return true;
*/
}
return false;

View File

@ -138,7 +138,6 @@ namespace PowerLauncher
bootTime.Stop();
Log.Info(textToLog.ToString(), GetType());
_mainVM.RegisterHotkey();
PowerToysTelemetry.Log.WriteEvent(new LauncherBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds });
// [Conditional("RELEASE")]

View File

@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Win32.SafeHandles;
using Wox.Plugin.Logger;
@ -17,32 +16,11 @@ namespace PowerLauncher.Helper
{
public static class EnvironmentHelper
{
private const string EnvironmentChangeType = "Environment";
private const string Username = "USERNAME";
private const string ProcessorArchitecture = "PROCESSOR_ARCHITECTURE";
private const string Path = "PATH";
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1801:Review unused parameters", Justification = "Params are required for delegate signature requirements.")]
public static IntPtr ProcessWindowMessages(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
{
switch ((WM)msg)
{
case WM.SETTINGCHANGE:
string changeType = Marshal.PtrToStringUni(lparam);
if (changeType == EnvironmentChangeType)
{
Log.Info("Reload environment", typeof(EnvironmentHelper));
UpdateEnvironment();
handled = true;
}
break;
}
return IntPtr.Zero;
}
private static void UpdateEnvironment()
internal static void UpdateEnvironment()
{
// Username and process architecture are set by the machine vars, this
// may lead to incorrect values so save off the current values to restore.

View File

@ -31,6 +31,18 @@ namespace PowerLauncher.Helper
[DllImport("user32.dll")]
internal static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
[DllImport("user32")]
internal static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32")]
internal static extern bool UnregisterHotKey(IntPtr hWnd, int id);
[DllImport("user32.dll")]
internal static extern IntPtr SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", SetLastError = true)]
internal static extern int GetWindowLong(IntPtr hWnd, int nIndex);
@ -86,6 +98,34 @@ namespace PowerLauncher.Helper
}
}
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey
internal enum HOTKEY_MODIFIERS : uint
{
ALT = 0x0001,
CONTROL = 0x0002,
SHIFT = 0x0004,
WIN = 0x0008,
NOREPEAT = 0x4000,
CHECK_FLAGS = 0x000F, // modifiers to compare between keys.
}
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
internal enum SW : int
{
HIDE = 0x0000,
SHOWNORMAL = 0x0001,
SHOWMINIMIZED = 0x0002,
SHOWMAXIMIZED = 0x0003,
SHOWNOACTIVATE = 0x0004,
SHOW = 0x0005,
MINIMIZE = 0x0006,
SHOWMINNOACTIVE = 0x0007,
SHOWNA = 0x0008,
RESTORE = 0x0009,
SHOWDEFAULT = 0x000A,
FORCEMINIMIZE = 0x000B,
}
internal enum WM
{
NULL = 0x0000,
@ -185,6 +225,8 @@ namespace PowerLauncher.Helper
NCMOUSELEAVE = 0x02A2,
HOTKEY = 0x0312,
DWMCOMPOSITIONCHANGED = 0x031E,
DWMNCRENDERINGCHANGED = 0x031F,
DWMCOLORIZATIONCOLORCHANGED = 0x0320,

View File

@ -19,6 +19,7 @@
SourceInitialized="OnSourceInitialized"
Loaded="OnLoaded"
Closing="OnClosing"
Closed="OnClosed"
Background="Transparent"
LocationChanged="OnLocationChanged"
Deactivated="OnDeactivated"

View File

@ -5,6 +5,7 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
@ -96,10 +97,39 @@ namespace PowerLauncher
Activate();
}
private const string EnvironmentChangeType = "Environment";
#pragma warning disable CA1801 // Review unused parameters
public IntPtr ProcessWindowMessages(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
#pragma warning restore CA1801 // Review unused parameters
{
switch ((WM)msg)
{
case WM.SETTINGCHANGE:
string changeType = Marshal.PtrToStringUni(lparam);
if (changeType == EnvironmentChangeType)
{
Log.Info("Reload environment", typeof(EnvironmentHelper));
EnvironmentHelper.UpdateEnvironment();
handled = true;
}
break;
case WM.HOTKEY:
handled = _viewModel.ProcessHotKeyMessages(wparam, lparam);
break;
}
return IntPtr.Zero;
}
private void OnSourceInitialized(object sender, EventArgs e)
{
_hwndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
_hwndSource.AddHook(EnvironmentHelper.ProcessWindowMessages);
_hwndSource.AddHook(ProcessWindowMessages);
// Call RegisterHotKey only after a window handle can be used, so that a global hotkey can be registered.
_viewModel.RegisterHotkey(_hwndSource.Handle);
}
private void OnLoaded(object sender, RoutedEventArgs e)
@ -489,5 +519,11 @@ namespace PowerLauncher
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void OnClosed(object sender, EventArgs e)
{
_hwndSource.RemoveHook(ProcessWindowMessages);
_hwndSource = null;
}
}
}

View File

@ -50,6 +50,12 @@ namespace PowerLauncher.ViewModel
private bool _saved;
private ushort _hotkeyHandle;
private const int _globalHotKeyId = 0x0001;
private IntPtr _globalHotKeyHwnd;
private uint _globalHotKeyVK;
private uint _globalHotKeyFSModifiers;
private bool _usingGlobalHotKey;
internal HotkeyManager HotkeyManager { get; private set; }
public MainViewModel(PowerToysRunSettings settings)
@ -75,39 +81,44 @@ namespace PowerLauncher.ViewModel
RegisterResultsUpdatedEvent();
}
public void RegisterHotkey()
public void RegisterHotkey(IntPtr hwnd)
{
Log.Info("RegisterHotkey()", GetType());
if (_settings != null && _settings.UsePowerToysRunnerKeyboardHook)
_settings.PropertyChanged += (s, e) =>
{
NativeEventWaiter.WaitForEventLoop(Constants.PowerLauncherSharedEvent(), OnHotkey);
_hotkeyHandle = 0;
}
else
{
HotkeyManager = new HotkeyManager();
_settings.PropertyChanged += (s, e) =>
if (e.PropertyName == nameof(PowerToysRunSettings.Hotkey))
{
if (e.PropertyName == nameof(PowerToysRunSettings.Hotkey))
Application.Current.Dispatcher.Invoke(() =>
{
Application.Current.Dispatcher.Invoke(() =>
if (!string.IsNullOrEmpty(_settings.PreviousHotkey))
{
if (!string.IsNullOrEmpty(_settings.PreviousHotkey))
if (_usingGlobalHotKey)
{
HotkeyManager.UnregisterHotkey(_hotkeyHandle);
NativeMethods.UnregisterHotKey(_globalHotKeyHwnd, _globalHotKeyId);
_usingGlobalHotKey = false;
Log.Info("Unregistering previous global hotkey", GetType());
}
if (!string.IsNullOrEmpty(_settings.Hotkey))
if (_hotkeyHandle != 0)
{
SetHotkey(_settings.Hotkey, OnHotkey);
HotkeyManager?.UnregisterHotkey(_hotkeyHandle);
_hotkeyHandle = 0;
Log.Info("Unregistering previous low level key handler", GetType());
}
});
}
};
}
SetHotkey(_settings.Hotkey, OnHotkey);
SetCustomPluginHotkey();
}
if (!string.IsNullOrEmpty(_settings.Hotkey))
{
SetHotkey(hwnd, _settings.Hotkey, OnHotkey);
}
});
}
};
SetHotkey(hwnd, _settings.Hotkey, OnHotkey);
// TODO: Custom plugin hotkeys.
// SetCustomPluginHotkey();
}
private void RegisterResultsUpdatedEvent()
@ -672,15 +683,35 @@ namespace PowerLauncher.ViewModel
return selected;
}
private void SetHotkey(string hotkeyStr, HotkeyCallback action)
#pragma warning disable CA1801 // Review unused parameters
internal bool ProcessHotKeyMessages(IntPtr wparam, IntPtr lparam)
#pragma warning restore CA1801 // Review unused parameters
{
var hotkey = new HotkeyModel(hotkeyStr);
SetHotkey(hotkey, action);
if (wparam.ToInt32() == _globalHotKeyId)
{
OnHotkey();
return true;
}
return false;
}
private void SetHotkey(HotkeyModel hotkeyModel, HotkeyCallback action)
private static uint VKModifiersFromHotKey(Hotkey hotkey)
{
return (uint)(HOTKEY_MODIFIERS.NOREPEAT | (hotkey.Alt ? HOTKEY_MODIFIERS.ALT : 0) | (hotkey.Ctrl ? HOTKEY_MODIFIERS.CONTROL : 0) | (hotkey.Shift ? HOTKEY_MODIFIERS.SHIFT : 0) | (hotkey.Win ? HOTKEY_MODIFIERS.WIN : 0));
}
private void SetHotkey(IntPtr hwnd, string hotkeyStr, HotkeyCallback action)
{
var hotkey = new HotkeyModel(hotkeyStr);
SetHotkey(hwnd, hotkey, action);
}
private void SetHotkey(IntPtr hwnd, HotkeyModel hotkeyModel, HotkeyCallback action)
{
Log.Info("Set HotKey()", GetType());
string hotkeyStr = hotkeyModel.ToString();
try
{
Hotkey hotkey = new Hotkey
@ -692,6 +723,39 @@ namespace PowerLauncher.ViewModel
Key = (byte)KeyInterop.VirtualKeyFromKey(hotkeyModel.CharKey),
};
if (_usingGlobalHotKey)
{
NativeMethods.UnregisterHotKey(_globalHotKeyHwnd, _globalHotKeyId);
_usingGlobalHotKey = false;
Log.Info("Unregistering previous global hotkey", GetType());
}
if (_hotkeyHandle != 0)
{
HotkeyManager?.UnregisterHotkey(_hotkeyHandle);
_hotkeyHandle = 0;
Log.Info("Unregistering previous low level key handler", GetType());
}
_globalHotKeyVK = hotkey.Key;
_globalHotKeyFSModifiers = VKModifiersFromHotKey(hotkey);
if (NativeMethods.RegisterHotKey(hwnd, _globalHotKeyId, _globalHotKeyFSModifiers, _globalHotKeyVK))
{
// Using global hotkey registered through the native RegisterHotKey method.
_globalHotKeyHwnd = hwnd;
_usingGlobalHotKey = true;
Log.Info("Registered global hotkey", GetType());
return;
}
Log.Warn("Registering global shortcut failed. Will use low-level keyboard hook instead.", GetType());
// Using fallback low-level keyboard hook through HotkeyManager.
if (HotkeyManager == null)
{
HotkeyManager = new HotkeyManager();
}
_hotkeyHandle = HotkeyManager.RegisterHotkey(hotkey, action);
}
#pragma warning disable CA1031 // Do not catch general exception types
@ -721,6 +785,11 @@ namespace PowerLauncher.ViewModel
return false;
}
/* TODO: Custom Hotkeys for Plugins. Commented since this is an incomplete feature.
* This needs:
* - Support for use with global shortcut.
* - Support for use with the fallback Shortcut Manager.
* - Support for use through the runner centralized keyboard hooks.
private void SetCustomPluginHotkey()
{
if (_settings.CustomPluginHotkeys == null)
@ -742,6 +811,7 @@ namespace PowerLauncher.ViewModel
});
}
}
*/
private void OnHotkey()
{
@ -966,6 +1036,12 @@ namespace PowerLauncher.ViewModel
{
if (disposing)
{
if (_usingGlobalHotKey)
{
NativeMethods.UnregisterHotKey(_globalHotKeyHwnd, _globalHotKeyId);
_usingGlobalHotKey = false;
}
if (_hotkeyHandle != 0)
{
HotkeyManager?.UnregisterHotkey(_hotkeyHandle);