mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-08-29 10:09:43 +02:00
fix(shortcutguide): separate hotkey and Win hold activation (#50000)
## Summary of the Pull Request Fixes a regression where `Win+Shift+/` was treated as a Windows-key hold because both activation paths signaled the same event. Regular activation now opens the full Shortcut Guide independently of the **Hold Windows key** setting and remains visible after Win is released. ## PR Checklist - [x] Closes: #49990 - [ ] **Communication:** Core contributor review is still required - [x] **Tests:** Added/updated and all pass - [x] **Localization:** N/A - no end-user-facing strings changed - [x] **Dev docs:** N/A - no developer-facing contract or workflow changed ## Detailed Description of the Pull Request / Additional comments - Keeps regular activation on `OnHotkeyEx()` and routes Win-only holds through the existing `on_hotkey(size_t)` module seam with a reserved ID. - Adds a dedicated named event for Win-key hold activation while preserving the existing regular trigger event and module ABI layout. - Removes `GetAsyncKeyState` trigger-source inference from Shortcut Guide UI. - Tracks the source and visible surface explicitly so Win release closes only hold-owned UI: - regular hotkey always opens the full guide and is unaffected by hold settings; - **Off** ignores Win-only holds; - **Taskbar indicators** closes on Win release; - **Open Shortcut Guide** follows the configured close-on-release value. - Handles both left and right Windows keys. - Clears stale held-key registrations and pending timers before re-registering them. - Uses `MOD_NOREPEAT` for centralized activation hotkeys so a held chord cannot repeatedly toggle the overlay. - Normalizes `MOD_NOREPEAT` before centralized action lookup, validates queued hold activations against the current Win-key state, and transfers hold-owned full-guide surfaces to regular-hotkey ownership. - Adds a pure activation policy and a data-driven unit-test matrix. Touched areas: - `src/runner/` - source-specific dispatch, held-key registration cleanup, and repeat suppression. - `src/common/interop/` - additive hold-event constant and WinRT projection. - `src/modules/ShortcutGuide/ShortcutGuideModuleInterface/` - dedicated hold event signaling and hold-setting guard. - `src/modules/ShortcutGuide/ShortcutGuide.Ui/` - explicit activation routing and source-aware release behavior. - `src/modules/ShortcutGuide/ShortcutGuide.UnitTests/` - activation and release policy coverage. **Risks and mitigations** - The existing regular event and settings JSON remain unchanged. - No virtual method or data member was added to `PowertoyModuleIface`; the existing `on_hotkey(size_t)` method is reused. - Duplicate hold callbacks are prevented during settings refresh, and duplicate hold events are UI no-ops. - No new telemetry or user-content logging was added. ## Validation Steps Performed - Built the full x64 Release solution and confirmed the complete payload starts without missing-module dialogs or Runner startup errors. - After rebasing onto current `main`, reran `tools\build\build-essentials.cmd -Platform x64 -Configuration Release` and built the affected interop, Runner, module-interface, index-generator, UI, and unit-test projects; all error logs were empty. - Ran the x64 Release `ShortcutGuide.UnitTests.dll` with `vstest.console.exe`: **43/43 passed**, including all 15 activation-policy cases. - After addressing review feedback, rebuilt the x64 Release Runner, Shortcut Guide UI, and unit-test projects; all builds passed and the unit tests remained **43/43**. - Signaled a queued hold after Win was released and confirmed no overlay opened. Then exercised hold-owned full-guide to regular-hotkey ownership transfer and confirmed both activations resolved to `ShowFullGuide` and the guide remained visible after Win release. - Held an injected `Win+Shift+/` chord for 1.4 seconds over Notepad: the full guide opened, remained visible after Win release, and logs recorded two regular activations (open/close) with **zero** hold activations. - Triggered the dedicated hold event over Notepad in both hold modes: - **Taskbar indicators:** the 2048x1104 overlay was visible and `WS_EX_TOPMOST`. - **Open Shortcut Guide:** the full guide and taskbar indicators were visible in the same topmost overlay. Manual verification matrix for a preview build: 1. Set **Hold Windows key** to Off, Taskbar indicators, and Open Shortcut Guide. 2. In each mode, press and release `Win+Shift+/`; confirm one full panel remains visible. 3. Hold LWin and RWin separately; confirm only the selected hold behavior runs. 4. For Open Shortcut Guide, verify close-on-release enabled and disabled. 5. Verify a second regular activation toggles the full panel closed. For physical Win-hold checks, run PowerToys at the same or higher integrity level as the foreground app. `RegisterHotKey` activation can work across an elevation mismatch while Runner's low-level hold hook cannot observe the key. Closes #49990 --------- Copilot-Session: 8271bded-18e8-474e-8e3b-addd71f67f50
This commit is contained in:
@@ -167,6 +167,10 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
{
|
||||
return CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT;
|
||||
}
|
||||
hstring Constants::ShortcutGuideWinKeyHoldEvent()
|
||||
{
|
||||
return CommonSharedConstants::SHORTCUT_GUIDE_WIN_KEY_HOLD_EVENT;
|
||||
}
|
||||
hstring Constants::RegistryPreviewTriggerEvent()
|
||||
{
|
||||
return CommonSharedConstants::REGISTRY_PREVIEW_TRIGGER_EVENT;
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
static hstring TerminatePeekEvent();
|
||||
static hstring PowerAccentExitEvent();
|
||||
static hstring ShortcutGuideTriggerEvent();
|
||||
static hstring ShortcutGuideWinKeyHoldEvent();
|
||||
static hstring RegistryPreviewTriggerEvent();
|
||||
static hstring GcodePreviewResizeEvent();
|
||||
static hstring BgcodePreviewResizeEvent();
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace PowerToys
|
||||
static String TerminatePeekEvent();
|
||||
static String PowerAccentExitEvent();
|
||||
static String ShortcutGuideTriggerEvent();
|
||||
static String ShortcutGuideWinKeyHoldEvent();
|
||||
static String RegistryPreviewTriggerEvent();
|
||||
static String MeasureToolTriggerEvent();
|
||||
static String GcodePreviewResizeEvent();
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace CommonSharedConstants
|
||||
const wchar_t TERMINATE_COLOR_PICKER_SHARED_EVENT[] = L"Local\\TerminateColorPickerEvent-3d676258-c4d5-424e-a87a-4be22020e813";
|
||||
|
||||
const wchar_t SHORTCUT_GUIDE_TRIGGER_EVENT[] = L"Local\\ShortcutGuide-TriggerEvent-d4275ad3-2531-4d19-9252-c0becbd9b496";
|
||||
const wchar_t SHORTCUT_GUIDE_WIN_KEY_HOLD_EVENT[] = L"Local\\ShortcutGuide-WinKeyHoldEvent-b5eb7614-d1c4-49d7-9813-ab277aabdd80";
|
||||
|
||||
const wchar_t SHORTCUT_GUIDE_EXIT_EVENT[] = L"Local\\ShortcutGuide-ExitEvent-35697cdd-a3d2-47d6-a246-34efcc73eac0";
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace ShortcutGuide.Helpers;
|
||||
|
||||
public enum ShortcutGuideActivationSource
|
||||
{
|
||||
None,
|
||||
RegularHotkey,
|
||||
WindowsKeyHold,
|
||||
}
|
||||
|
||||
public enum ShortcutGuideOverlaySurface
|
||||
{
|
||||
Hidden,
|
||||
TaskbarIndicators,
|
||||
FullGuide,
|
||||
}
|
||||
|
||||
public enum ShortcutGuideActivationAction
|
||||
{
|
||||
None,
|
||||
ShowTaskbarIndicators,
|
||||
ShowFullGuide,
|
||||
Close,
|
||||
}
|
||||
|
||||
public static class ShortcutGuideActivationPolicy
|
||||
{
|
||||
public static ShortcutGuideActivationAction GetActivationAction(
|
||||
ShortcutGuideActivationSource activationSource,
|
||||
bool isOverlayVisible,
|
||||
ShortcutGuideActivationSource activeSource,
|
||||
ShortcutGuideOverlaySurface activeSurface,
|
||||
ShortcutGuideWindowsKeyAction windowsKeyAction)
|
||||
{
|
||||
if (!isOverlayVisible)
|
||||
{
|
||||
activeSource = ShortcutGuideActivationSource.None;
|
||||
activeSurface = ShortcutGuideOverlaySurface.Hidden;
|
||||
}
|
||||
|
||||
if (activationSource == ShortcutGuideActivationSource.RegularHotkey)
|
||||
{
|
||||
if (activeSource == ShortcutGuideActivationSource.WindowsKeyHold ||
|
||||
activeSurface == ShortcutGuideOverlaySurface.TaskbarIndicators ||
|
||||
activeSurface == ShortcutGuideOverlaySurface.Hidden)
|
||||
{
|
||||
return ShortcutGuideActivationAction.ShowFullGuide;
|
||||
}
|
||||
|
||||
return ShortcutGuideActivationAction.Close;
|
||||
}
|
||||
|
||||
if (activationSource != ShortcutGuideActivationSource.WindowsKeyHold ||
|
||||
windowsKeyAction == ShortcutGuideWindowsKeyAction.Off ||
|
||||
isOverlayVisible ||
|
||||
activeSurface != ShortcutGuideOverlaySurface.Hidden)
|
||||
{
|
||||
return ShortcutGuideActivationAction.None;
|
||||
}
|
||||
|
||||
return windowsKeyAction == ShortcutGuideWindowsKeyAction.OpenShortcutGuide
|
||||
? ShortcutGuideActivationAction.ShowFullGuide
|
||||
: ShortcutGuideActivationAction.ShowTaskbarIndicators;
|
||||
}
|
||||
|
||||
public static bool ShouldCloseOnWindowsKeyRelease(
|
||||
ShortcutGuideActivationSource activeSource,
|
||||
ShortcutGuideOverlaySurface activeSurface,
|
||||
bool closeFullGuideOnRelease)
|
||||
{
|
||||
if (activeSource != ShortcutGuideActivationSource.WindowsKeyHold)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return activeSurface == ShortcutGuideOverlaySurface.TaskbarIndicators ||
|
||||
(activeSurface == ShortcutGuideOverlaySurface.FullGuide && closeFullGuideOnRelease);
|
||||
}
|
||||
}
|
||||
@@ -42,9 +42,12 @@ namespace ShortcutGuide
|
||||
|
||||
internal static string CurrentAppName { get; set; } = string.Empty;
|
||||
|
||||
private EventWaitHandle? _launchedEvent;
|
||||
|
||||
private Thread? _listenForLaunchedEventThread;
|
||||
private readonly SemaphoreSlim _activationGate = new(1, 1);
|
||||
private EventWaitHandle? _regularHotkeyEvent;
|
||||
private EventWaitHandle? _winKeyHoldEvent;
|
||||
private Thread? _listenForActivationEventsThread;
|
||||
private int _activeSource = (int)ShortcutGuideActivationSource.None;
|
||||
private int _activeSurface = (int)ShortcutGuideOverlaySurface.Hidden;
|
||||
|
||||
private static readonly UIntPtr _ignoreKeyEventFlag = 0x5557;
|
||||
|
||||
@@ -67,6 +70,7 @@ namespace ShortcutGuide
|
||||
{
|
||||
this.LoadData();
|
||||
OverlayWindow = new OverlayWindow();
|
||||
OverlayWindow.ClosingStarted += (_, _) => ResetActivationState();
|
||||
OverlayWindow.Activate();
|
||||
OverlayWindow.AppWindow.Hide();
|
||||
OverlayWindow.Closed += (_, _) =>
|
||||
@@ -83,22 +87,15 @@ namespace ShortcutGuide
|
||||
Current.Exit();
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
_launchedEvent = EventWaitHandle.OpenExisting(Constants.ShortcutGuideTriggerEvent());
|
||||
Logger.LogInfo($"Opened Shortcut Guide trigger event '{Constants.ShortcutGuideTriggerEvent()}'.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to open existing event '{Constants.ShortcutGuideTriggerEvent()}': {ex.Message}");
|
||||
}
|
||||
_regularHotkeyEvent = TryOpenActivationEvent(Constants.ShortcutGuideTriggerEvent());
|
||||
_winKeyHoldEvent = TryOpenActivationEvent(Constants.ShortcutGuideWinKeyHoldEvent());
|
||||
|
||||
_listenForLaunchedEventThread = new Thread(ListenForLaunchedEvents)
|
||||
_listenForActivationEventsThread = new Thread(ListenForActivationEvents)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "ShortcutGuide-ShowEventListener",
|
||||
Name = "ShortcutGuide-ActivationEventListener",
|
||||
};
|
||||
_listenForLaunchedEventThread.Start();
|
||||
_listenForActivationEventsThread.Start();
|
||||
_winKeyUpKeyboardHook = new HotkeySettingsControlHook(
|
||||
(int key) =>
|
||||
{
|
||||
@@ -106,26 +103,30 @@ namespace ShortcutGuide
|
||||
},
|
||||
(int key) =>
|
||||
{
|
||||
if (OverlayWindow.AppWindow.IsVisible)
|
||||
if (key is not 0x5B and not 0x5C)
|
||||
{
|
||||
if (ShortcutGuideProperties.WindowsKeyAction.Value != ((int)ShortcutGuideWindowsKeyAction.OpenShortcutGuide) || ShortcutGuideProperties.CloseOnWindowsKeyRelease.Value)
|
||||
return;
|
||||
}
|
||||
|
||||
var activeSource = (ShortcutGuideActivationSource)Volatile.Read(ref _activeSource);
|
||||
var activeSurface = (ShortcutGuideOverlaySurface)Volatile.Read(ref _activeSurface);
|
||||
bool isOverlayVisible = OverlayWindow.AppWindow.IsVisible;
|
||||
if (isOverlayVisible || activeSource == ShortcutGuideActivationSource.WindowsKeyHold)
|
||||
{
|
||||
if (ShortcutGuideActivationPolicy.ShouldCloseOnWindowsKeyRelease(
|
||||
activeSource,
|
||||
activeSurface,
|
||||
ShortcutGuideProperties.CloseOnWindowsKeyRelease.Value))
|
||||
{
|
||||
OverlayWindow.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
OverlayWindow.CloseAnimated();
|
||||
});
|
||||
OverlayWindow.DispatcherQueue.TryEnqueue(CloseOverlay);
|
||||
}
|
||||
|
||||
NativeMethods.SendInput(1, [new() { Type = 1, Data = new() { Keyboard = new NativeMethods.KEYBDINPUT { WVk = 0xFF, DwFlags = 0x2 } } }], Marshal.SizeOf<NativeMethods.INPUT>());
|
||||
SendSingleKeyboardInput((short)key, 0x2); // key up
|
||||
}
|
||||
else
|
||||
{
|
||||
SendSingleKeyboardInput((short)key, 0x2); // key up
|
||||
}
|
||||
},
|
||||
() => true,
|
||||
(int key, nuint specialFlags) => key == 91 && specialFlags != _ignoreKeyEventFlag);
|
||||
() => OverlayWindow.AppWindow.IsVisible || (ShortcutGuideActivationSource)Volatile.Read(ref _activeSource) == ShortcutGuideActivationSource.WindowsKeyHold,
|
||||
(int key, nuint specialFlags) => (key is 0x5B or 0x5C) && specialFlags != _ignoreKeyEventFlag);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -142,6 +143,8 @@ namespace ShortcutGuide
|
||||
{
|
||||
0xA5 => true, // VK_RMENU (Right Alt - AltGr)
|
||||
0xA3 => true, // VK_RCONTROL
|
||||
0x5B => true, // VK_LWIN
|
||||
0x5C => true, // VK_RWIN
|
||||
0x2D => true, // VK_INSERT
|
||||
0x2E => true, // VK_DELETE
|
||||
0x23 => true, // VK_END
|
||||
@@ -179,73 +182,35 @@ namespace ShortcutGuide
|
||||
NativeMethods.SendInput(1, inputs, Marshal.SizeOf<NativeMethods.INPUT>());
|
||||
}
|
||||
|
||||
private void ListenForLaunchedEvents()
|
||||
private void ListenForActivationEvents()
|
||||
{
|
||||
if (_launchedEvent == null)
|
||||
List<(WaitHandle Handle, ShortcutGuideActivationSource Source)> activationEvents = [];
|
||||
if (_regularHotkeyEvent != null)
|
||||
{
|
||||
activationEvents.Add((_regularHotkeyEvent, ShortcutGuideActivationSource.RegularHotkey));
|
||||
}
|
||||
|
||||
if (_winKeyHoldEvent != null)
|
||||
{
|
||||
activationEvents.Add((_winKeyHoldEvent, ShortcutGuideActivationSource.WindowsKeyHold));
|
||||
}
|
||||
|
||||
if (activationEvents.Count == 0)
|
||||
{
|
||||
Logger.LogError("Failed to open any Shortcut Guide activation trigger events.");
|
||||
return;
|
||||
}
|
||||
|
||||
var handles = new WaitHandle[] { _launchedEvent };
|
||||
WaitHandle[] handles = activationEvents.ConvertAll(item => item.Handle).ToArray();
|
||||
try
|
||||
{
|
||||
Logger.LogInfo("Shortcut Guide show-event listener started.");
|
||||
Logger.LogInfo("Shortcut Guide activation-event listener started.");
|
||||
while (true)
|
||||
{
|
||||
var index = WaitHandle.WaitAny(handles);
|
||||
if (index == 0)
|
||||
{
|
||||
Logger.LogInfo("Shortcut Guide trigger event signaled.");
|
||||
OverlayWindow.DispatcherQueue.TryEnqueue(async () =>
|
||||
{
|
||||
// VK_LWIN long-press shows only the taskbar pane.
|
||||
// Use the Win32 key state directly: WPF's
|
||||
// System.Windows.Input.Keyboard is not initialized
|
||||
// on the WinUI UI thread.
|
||||
const int VK_LWIN = 0x5B;
|
||||
const int VK_RWIN = 0x5C;
|
||||
bool winKeyDown = ((NativeMethods.GetAsyncKeyState(VK_LWIN) & 0x8000) != 0) ||
|
||||
((NativeMethods.GetAsyncKeyState(VK_RWIN) & 0x8000) != 0);
|
||||
if (winKeyDown && ShortcutGuideProperties.WindowsKeyAction.Value == (int)ShortcutGuideWindowsKeyAction.Off)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (winKeyDown && ShortcutGuideProperties.WindowsKeyAction.Value == (int)ShortcutGuideWindowsKeyAction.TaskbarIndicators)
|
||||
{
|
||||
if (OverlayWindow.AppWindow.IsVisible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OverlayWindow.MainPaneControl.Visibility = Visibility.Collapsed;
|
||||
OverlayWindow.ShowOverlay();
|
||||
OverlayWindow.UpdateTaskbarPaneLayout();
|
||||
OverlayWindow.TaskbarPaneControl.Visibility = Visibility.Visible;
|
||||
return;
|
||||
}
|
||||
|
||||
if (OverlayWindow.AppWindow.IsVisible)
|
||||
{
|
||||
OverlayWindow.CloseAnimated();
|
||||
OverlayWindow.MainPaneControl.Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
Program.ForegroundWindowHandle = NativeMethods.GetForegroundWindow();
|
||||
OverlayWindow.MainPaneControl.Visibility = Visibility.Collapsed;
|
||||
OverlayWindow.ShowOverlay();
|
||||
await OverlayWindow.MainPaneControl.Open();
|
||||
OverlayWindow.UpdateTaskbarPaneLayout();
|
||||
OverlayWindow.MainPaneControl.Visibility = Visibility.Visible;
|
||||
OverlayWindow.MainPaneControl.FocusSearch();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
int eventIndex = WaitHandle.WaitAny(handles);
|
||||
var activationSource = activationEvents[eventIndex].Source;
|
||||
Logger.LogInfo($"Shortcut Guide trigger event signaled by {activationSource}.");
|
||||
OverlayWindow.DispatcherQueue.TryEnqueue(() => _ = HandleActivationAsync(activationSource));
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
@@ -256,6 +221,147 @@ namespace ShortcutGuide
|
||||
}
|
||||
}
|
||||
|
||||
private static EventWaitHandle? TryOpenActivationEvent(string eventName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activationEvent = EventWaitHandle.OpenExisting(eventName);
|
||||
Logger.LogInfo($"Opened Shortcut Guide trigger event '{eventName}'.");
|
||||
return activationEvent;
|
||||
}
|
||||
catch (WaitHandleCannotBeOpenedException ex)
|
||||
{
|
||||
Logger.LogError($"Failed to open Shortcut Guide trigger event '{eventName}': {ex.Message}");
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
Logger.LogError($"Failed to open Shortcut Guide trigger event '{eventName}': {ex.Message}");
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.LogError($"Failed to open Shortcut Guide trigger event '{eventName}': {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task HandleActivationAsync(ShortcutGuideActivationSource activationSource)
|
||||
{
|
||||
await _activationGate.WaitAsync();
|
||||
try
|
||||
{
|
||||
bool isOverlayVisible = OverlayWindow.AppWindow.IsVisible;
|
||||
var activeSource = (ShortcutGuideActivationSource)Volatile.Read(ref _activeSource);
|
||||
var activeSurface = (ShortcutGuideOverlaySurface)Volatile.Read(ref _activeSurface);
|
||||
var windowsKeyAction = (ShortcutGuideWindowsKeyAction)ShortcutGuideProperties.WindowsKeyAction.Value;
|
||||
var action = ShortcutGuideActivationPolicy.GetActivationAction(
|
||||
activationSource,
|
||||
isOverlayVisible,
|
||||
activeSource,
|
||||
activeSurface,
|
||||
windowsKeyAction);
|
||||
|
||||
Logger.LogInfo($"Shortcut Guide activation action: {action}.");
|
||||
switch (action)
|
||||
{
|
||||
case ShortcutGuideActivationAction.ShowTaskbarIndicators:
|
||||
if (!TryBeginActivation(activationSource, ShortcutGuideOverlaySurface.TaskbarIndicators))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Program.ForegroundWindowHandle = NativeMethods.GetForegroundWindow();
|
||||
OverlayWindow.MainPaneControl.Visibility = Visibility.Collapsed;
|
||||
OverlayWindow.ShowOverlay();
|
||||
OverlayWindow.UpdateTaskbarPaneLayout();
|
||||
OverlayWindow.TaskbarPaneControl.Visibility = Visibility.Visible;
|
||||
break;
|
||||
|
||||
case ShortcutGuideActivationAction.ShowFullGuide:
|
||||
bool isFullGuideVisible = isOverlayVisible && activeSurface == ShortcutGuideOverlaySurface.FullGuide;
|
||||
if (!TryBeginActivation(activationSource, ShortcutGuideOverlaySurface.FullGuide))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (isFullGuideVisible)
|
||||
{
|
||||
OverlayWindow.MainPaneControl.Visibility = Visibility.Visible;
|
||||
OverlayWindow.MainPaneControl.FocusSearch();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isOverlayVisible)
|
||||
{
|
||||
Program.ForegroundWindowHandle = NativeMethods.GetForegroundWindow();
|
||||
}
|
||||
|
||||
OverlayWindow.MainPaneControl.Visibility = Visibility.Collapsed;
|
||||
OverlayWindow.ShowOverlay();
|
||||
await OverlayWindow.MainPaneControl.Open();
|
||||
if ((ShortcutGuideActivationSource)Volatile.Read(ref _activeSource) != activationSource ||
|
||||
(ShortcutGuideOverlaySurface)Volatile.Read(ref _activeSurface) != ShortcutGuideOverlaySurface.FullGuide)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OverlayWindow.UpdateTaskbarPaneLayout();
|
||||
OverlayWindow.MainPaneControl.Visibility = Visibility.Visible;
|
||||
OverlayWindow.MainPaneControl.FocusSearch();
|
||||
break;
|
||||
|
||||
case ShortcutGuideActivationAction.Close:
|
||||
CloseOverlay();
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to handle Shortcut Guide activation.", ex);
|
||||
CloseOverlay();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_activationGate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CloseOverlay()
|
||||
{
|
||||
OverlayWindow.CloseAnimated();
|
||||
}
|
||||
|
||||
private void SetActivationState(ShortcutGuideActivationSource source, ShortcutGuideOverlaySurface surface)
|
||||
{
|
||||
Volatile.Write(ref _activeSource, (int)source);
|
||||
Volatile.Write(ref _activeSurface, (int)surface);
|
||||
}
|
||||
|
||||
private void ResetActivationState()
|
||||
{
|
||||
SetActivationState(ShortcutGuideActivationSource.None, ShortcutGuideOverlaySurface.Hidden);
|
||||
}
|
||||
|
||||
private bool TryBeginActivation(ShortcutGuideActivationSource source, ShortcutGuideOverlaySurface surface)
|
||||
{
|
||||
SetActivationState(source, surface);
|
||||
if (source != ShortcutGuideActivationSource.WindowsKeyHold || IsWindowsKeyPressed())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ResetActivationState();
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsWindowsKeyPressed()
|
||||
{
|
||||
const int VirtualKeyLeftWindows = 0x5B;
|
||||
const int VirtualKeyRightWindows = 0x5C;
|
||||
return (NativeMethods.GetAsyncKeyState(VirtualKeyLeftWindows) & 0x8000) != 0 ||
|
||||
(NativeMethods.GetAsyncKeyState(VirtualKeyRightWindows) & 0x8000) != 0;
|
||||
}
|
||||
|
||||
private void LoadData()
|
||||
{
|
||||
SettingsUtils settingsUtils = SettingsUtils.Default;
|
||||
@@ -319,19 +425,20 @@ namespace ShortcutGuide
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_launchedEvent?.Dispose();
|
||||
_regularHotkeyEvent?.Dispose();
|
||||
_winKeyHoldEvent?.Dispose();
|
||||
|
||||
if (_listenForLaunchedEventThread == null)
|
||||
if (_listenForActivationEventsThread == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!_listenForLaunchedEventThread.Join(TimeSpan.FromMilliseconds(250)))
|
||||
if (!_listenForActivationEventsThread.Join(TimeSpan.FromMilliseconds(250)))
|
||||
{
|
||||
_listenForLaunchedEventThread.Interrupt();
|
||||
_listenForLaunchedEventThread.Join(TimeSpan.FromMilliseconds(250));
|
||||
_listenForActivationEventsThread.Interrupt();
|
||||
_listenForActivationEventsThread.Join(TimeSpan.FromMilliseconds(250));
|
||||
}
|
||||
}
|
||||
catch (ThreadInterruptedException)
|
||||
@@ -341,7 +448,7 @@ namespace ShortcutGuide
|
||||
{
|
||||
}
|
||||
|
||||
_listenForLaunchedEventThread = null;
|
||||
_listenForActivationEventsThread = null;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ namespace ShortcutGuide
|
||||
|
||||
internal string CloseType => _closeType;
|
||||
|
||||
internal event EventHandler? ClosingStarted;
|
||||
|
||||
public MainPaneControl MainPaneControl => this.MainPane;
|
||||
|
||||
internal TaskbarPaneControl TaskbarPaneControl => this.TaskbarPane;
|
||||
@@ -308,6 +310,7 @@ namespace ShortcutGuide
|
||||
}
|
||||
|
||||
_isClosing = true;
|
||||
ClosingStarted?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
// Collapse both pseudo-windows so their Implicit.HideAnimations play
|
||||
this.MainPane.Visibility = Visibility.Collapsed;
|
||||
@@ -375,6 +378,7 @@ namespace ShortcutGuide
|
||||
/// </summary>
|
||||
public void ShowOverlay()
|
||||
{
|
||||
_closeTimer?.Stop();
|
||||
_isClosing = false;
|
||||
|
||||
RepositionToCursorMonitor();
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using ShortcutGuide.Helpers;
|
||||
|
||||
namespace ShortcutGuide.UnitTests.ActivationTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class ShortcutGuideActivationPolicyTests
|
||||
{
|
||||
[TestMethod]
|
||||
[DataRow(ShortcutGuideWindowsKeyAction.Off, ShortcutGuideActivationAction.None)]
|
||||
[DataRow(ShortcutGuideWindowsKeyAction.TaskbarIndicators, ShortcutGuideActivationAction.ShowTaskbarIndicators)]
|
||||
[DataRow(ShortcutGuideWindowsKeyAction.OpenShortcutGuide, ShortcutGuideActivationAction.ShowFullGuide)]
|
||||
public void GetActivationAction_WindowsKeyHold_UsesConfiguredAction(
|
||||
ShortcutGuideWindowsKeyAction windowsKeyAction,
|
||||
ShortcutGuideActivationAction expected)
|
||||
{
|
||||
var action = ShortcutGuideActivationPolicy.GetActivationAction(
|
||||
ShortcutGuideActivationSource.WindowsKeyHold,
|
||||
isOverlayVisible: false,
|
||||
ShortcutGuideActivationSource.None,
|
||||
ShortcutGuideOverlaySurface.Hidden,
|
||||
windowsKeyAction);
|
||||
|
||||
Assert.AreEqual(expected, action);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(ShortcutGuideWindowsKeyAction.Off)]
|
||||
[DataRow(ShortcutGuideWindowsKeyAction.TaskbarIndicators)]
|
||||
[DataRow(ShortcutGuideWindowsKeyAction.OpenShortcutGuide)]
|
||||
public void GetActivationAction_RegularHotkey_AlwaysOpensFullGuide(ShortcutGuideWindowsKeyAction windowsKeyAction)
|
||||
{
|
||||
var action = ShortcutGuideActivationPolicy.GetActivationAction(
|
||||
ShortcutGuideActivationSource.RegularHotkey,
|
||||
isOverlayVisible: false,
|
||||
ShortcutGuideActivationSource.None,
|
||||
ShortcutGuideOverlaySurface.Hidden,
|
||||
windowsKeyAction);
|
||||
|
||||
Assert.AreEqual(ShortcutGuideActivationAction.ShowFullGuide, action);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetActivationAction_RegularHotkey_PromotesHoldIndicators()
|
||||
{
|
||||
var action = ShortcutGuideActivationPolicy.GetActivationAction(
|
||||
ShortcutGuideActivationSource.RegularHotkey,
|
||||
isOverlayVisible: true,
|
||||
ShortcutGuideActivationSource.WindowsKeyHold,
|
||||
ShortcutGuideOverlaySurface.TaskbarIndicators,
|
||||
ShortcutGuideWindowsKeyAction.TaskbarIndicators);
|
||||
|
||||
Assert.AreEqual(ShortcutGuideActivationAction.ShowFullGuide, action);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetActivationAction_RegularHotkey_TakesOwnershipOfHoldGuide()
|
||||
{
|
||||
var action = ShortcutGuideActivationPolicy.GetActivationAction(
|
||||
ShortcutGuideActivationSource.RegularHotkey,
|
||||
isOverlayVisible: true,
|
||||
ShortcutGuideActivationSource.WindowsKeyHold,
|
||||
ShortcutGuideOverlaySurface.FullGuide,
|
||||
ShortcutGuideWindowsKeyAction.OpenShortcutGuide);
|
||||
|
||||
Assert.AreEqual(ShortcutGuideActivationAction.ShowFullGuide, action);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetActivationAction_RegularHotkey_ClosesRegularGuide()
|
||||
{
|
||||
var action = ShortcutGuideActivationPolicy.GetActivationAction(
|
||||
ShortcutGuideActivationSource.RegularHotkey,
|
||||
isOverlayVisible: true,
|
||||
ShortcutGuideActivationSource.RegularHotkey,
|
||||
ShortcutGuideOverlaySurface.FullGuide,
|
||||
ShortcutGuideWindowsKeyAction.TaskbarIndicators);
|
||||
|
||||
Assert.AreEqual(ShortcutGuideActivationAction.Close, action);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetActivationAction_HoldWhileRegularGuideVisible_DoesNothing()
|
||||
{
|
||||
var action = ShortcutGuideActivationPolicy.GetActivationAction(
|
||||
ShortcutGuideActivationSource.WindowsKeyHold,
|
||||
isOverlayVisible: true,
|
||||
ShortcutGuideActivationSource.RegularHotkey,
|
||||
ShortcutGuideOverlaySurface.FullGuide,
|
||||
ShortcutGuideWindowsKeyAction.OpenShortcutGuide);
|
||||
|
||||
Assert.AreEqual(ShortcutGuideActivationAction.None, action);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(ShortcutGuideActivationSource.RegularHotkey, ShortcutGuideOverlaySurface.FullGuide, true, false)]
|
||||
[DataRow(ShortcutGuideActivationSource.RegularHotkey, ShortcutGuideOverlaySurface.FullGuide, false, false)]
|
||||
[DataRow(ShortcutGuideActivationSource.WindowsKeyHold, ShortcutGuideOverlaySurface.TaskbarIndicators, false, true)]
|
||||
[DataRow(ShortcutGuideActivationSource.WindowsKeyHold, ShortcutGuideOverlaySurface.FullGuide, true, true)]
|
||||
[DataRow(ShortcutGuideActivationSource.WindowsKeyHold, ShortcutGuideOverlaySurface.FullGuide, false, false)]
|
||||
public void ShouldCloseOnWindowsKeyRelease_UsesActivationOwnership(
|
||||
ShortcutGuideActivationSource activeSource,
|
||||
ShortcutGuideOverlaySurface activeSurface,
|
||||
bool closeFullGuideOnRelease,
|
||||
bool expected)
|
||||
{
|
||||
Assert.AreEqual(
|
||||
expected,
|
||||
ShortcutGuideActivationPolicy.ShouldCloseOnWindowsKeyRelease(activeSource, activeSurface, closeFullGuideOnRelease));
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,12 @@ public:
|
||||
Logger::warn(L"Failed to create {} event. {}", CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT, get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
|
||||
winKeyHoldEvent = CreateEvent(nullptr, false, false, CommonSharedConstants::SHORTCUT_GUIDE_WIN_KEY_HOLD_EVENT);
|
||||
if (!winKeyHoldEvent)
|
||||
{
|
||||
Logger::warn(L"Failed to create {} event. {}", CommonSharedConstants::SHORTCUT_GUIDE_WIN_KEY_HOLD_EVENT, get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
|
||||
InitSettings();
|
||||
}
|
||||
|
||||
@@ -132,6 +138,10 @@ public:
|
||||
{
|
||||
CloseHandle(triggerEvent);
|
||||
}
|
||||
if (winKeyHoldEvent)
|
||||
{
|
||||
CloseHandle(winKeyHoldEvent);
|
||||
}
|
||||
|
||||
delete this;
|
||||
}
|
||||
@@ -144,18 +154,17 @@ public:
|
||||
|
||||
virtual void OnHotkeyEx() override
|
||||
{
|
||||
Logger::trace("OnHotkeyEx()");
|
||||
if (!_enabled)
|
||||
SignalEvent(triggerEvent, CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT, L"regular hotkey");
|
||||
}
|
||||
|
||||
virtual bool on_hotkey(size_t hotkeyId) override
|
||||
{
|
||||
if (hotkeyId == PowertoyModuleIface::WIN_KEY_HOLD_HOTKEY_ID && m_windowsKeyAction != WindowsKeyAction::Off)
|
||||
{
|
||||
return;
|
||||
SignalEvent(winKeyHoldEvent, CommonSharedConstants::SHORTCUT_GUIDE_WIN_KEY_HOLD_EVENT, L"Windows key hold");
|
||||
}
|
||||
|
||||
if (!IsProcessActive())
|
||||
{
|
||||
StartProcess();
|
||||
}
|
||||
|
||||
SetEvent(triggerEvent);
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void send_settings_telemetry() override
|
||||
@@ -170,6 +179,13 @@ public:
|
||||
virtual UINT milliseconds_win_key_must_be_pressed() override { return m_millisecondsWinKeyPressTimeForGlobalWindowsShortcuts; }
|
||||
|
||||
private:
|
||||
enum class WindowsKeyAction
|
||||
{
|
||||
Off = 0,
|
||||
TaskbarIndicators = 1,
|
||||
OpenShortcutGuide = 2,
|
||||
};
|
||||
|
||||
std::wstring app_name;
|
||||
//contains the non localized key of the powertoy
|
||||
std::wstring app_key;
|
||||
@@ -186,7 +202,28 @@ private:
|
||||
UINT m_millisecondsWinKeyPressTimeForTaskbarIconShortcuts = DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_TASKBAR_ICON_SHORTCUTS;
|
||||
|
||||
HANDLE triggerEvent;
|
||||
HANDLE winKeyHoldEvent;
|
||||
HANDLE exitEvent;
|
||||
WindowsKeyAction m_windowsKeyAction = WindowsKeyAction::TaskbarIndicators;
|
||||
|
||||
void SignalEvent(HANDLE eventHandle, const wchar_t* eventName, const wchar_t* activationSource)
|
||||
{
|
||||
Logger::trace(L"Shortcut Guide was invoked by {}", activationSource);
|
||||
if (!_enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsProcessActive() && !StartProcess())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SetEvent(eventHandle))
|
||||
{
|
||||
Logger::error(L"Failed to signal {}. {}", eventName, get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
bool StartProcess(std::wstring args = L"")
|
||||
{
|
||||
@@ -199,6 +236,10 @@ private:
|
||||
{
|
||||
ResetEvent(triggerEvent);
|
||||
}
|
||||
if (winKeyHoldEvent)
|
||||
{
|
||||
ResetEvent(winKeyHoldEvent);
|
||||
}
|
||||
|
||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
std::wstring executable_args = L"";
|
||||
@@ -302,7 +343,7 @@ private:
|
||||
Logger::warn("Failed to initialize Shortcut Guide start shortcut");
|
||||
}
|
||||
|
||||
try
|
||||
try
|
||||
{
|
||||
auto propertiesObject = settingsObject.GetNamedObject(L"properties");
|
||||
if (propertiesObject.HasKey(L"press_time"))
|
||||
@@ -324,7 +365,34 @@ try
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...) { /* Keep defaults */ }
|
||||
catch (...)
|
||||
{ /* Keep defaults */
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto propertiesObject = settingsObject.GetNamedObject(L"properties");
|
||||
if (propertiesObject.HasKey(L"win_key_action"))
|
||||
{
|
||||
const auto value = static_cast<int>(propertiesObject.GetNamedObject(L"win_key_action").GetNamedNumber(L"value"));
|
||||
switch (value)
|
||||
{
|
||||
case static_cast<int>(WindowsKeyAction::Off):
|
||||
m_windowsKeyAction = WindowsKeyAction::Off;
|
||||
break;
|
||||
case static_cast<int>(WindowsKeyAction::OpenShortcutGuide):
|
||||
m_windowsKeyAction = WindowsKeyAction::OpenShortcutGuide;
|
||||
break;
|
||||
case static_cast<int>(WindowsKeyAction::TaskbarIndicators):
|
||||
default:
|
||||
m_windowsKeyAction = WindowsKeyAction::TaskbarIndicators;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{ /* Keep defaults */
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -135,6 +135,9 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reserved ID used when the legacy Windows-key hold path invokes on_hotkey.
|
||||
static constexpr size_t WIN_KEY_HOLD_HOTKEY_ID = static_cast<size_t>(-1);
|
||||
|
||||
/* These are for enabling the legacy behavior of showing the shortcut guide after pressing the win key.
|
||||
* keep_track_of_pressed_win_key returns true if the module wants to keep track of the win key being pressed.
|
||||
* milliseconds_win_key_must_be_pressed returns the number of milliseconds the win key should be pressed before triggering the module.
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace CentralizedHotkeys
|
||||
ids[shortcut] = nextId++;
|
||||
}
|
||||
|
||||
if (!RegisterHotKey(runnerWindow, ids[shortcut], shortcut.modifiersMask, shortcut.vkCode))
|
||||
if (!RegisterHotKey(runnerWindow, ids[shortcut], shortcut.modifiersMask | MOD_NOREPEAT, shortcut.vkCode))
|
||||
{
|
||||
Logger::warn(L"Failed to add {} shortcut. {}", ToWstring(shortcut), get_last_error_or_default(GetLastError()));
|
||||
return false;
|
||||
@@ -97,11 +97,13 @@ namespace CentralizedHotkeys
|
||||
|
||||
void PopulateHotkey(Shortcut shortcut)
|
||||
{
|
||||
if (!actions.empty())
|
||||
shortcut.modifiersMask &= static_cast<WORD>(~MOD_NOREPEAT);
|
||||
const auto actionIt = actions.find(shortcut);
|
||||
if (actionIt != actions.end() && !actionIt->second.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
actions[shortcut].begin()->action(shortcut.modifiersMask, shortcut.vkCode);
|
||||
actionIt->second.begin()->action(shortcut.modifiersMask, shortcut.vkCode);
|
||||
}
|
||||
catch(std::exception& ex)
|
||||
{
|
||||
|
||||
@@ -212,6 +212,46 @@ namespace CentralizedKeyboardHook
|
||||
pressedKeyDescriptors.insert({ .virtualKey = vk, .moduleName = moduleName, .action = std::move(action), .idTimer = timerId, .millisecondsToPress = milliseconds });
|
||||
}
|
||||
|
||||
void ClearPressedKeyActions(const std::wstring& moduleName) noexcept
|
||||
{
|
||||
Logger::trace(L"UnRegister pressed key action for {}", moduleName);
|
||||
std::unique_lock lock{ pressedKeyMutex };
|
||||
const DWORD trackedKey = vkCodePressed.load();
|
||||
bool removedTrackedKey = false;
|
||||
auto it = pressedKeyDescriptors.begin();
|
||||
while (it != pressedKeyDescriptors.end())
|
||||
{
|
||||
if (it->moduleName == moduleName)
|
||||
{
|
||||
removedTrackedKey |= it->virtualKey == trackedKey;
|
||||
if (it->idTimer != 0)
|
||||
{
|
||||
KillTimer(runnerWindow, it->idTimer);
|
||||
}
|
||||
|
||||
it = pressedKeyDescriptors.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (pressedKeyDescriptors.empty())
|
||||
{
|
||||
vkCodePressed = VK_DISABLED;
|
||||
}
|
||||
else if (removedTrackedKey)
|
||||
{
|
||||
PressedKeyDescriptor trackedKeyDescriptor{ .virtualKey = trackedKey };
|
||||
const auto [first, last] = pressedKeyDescriptors.equal_range(trackedKeyDescriptor);
|
||||
if (first == last)
|
||||
{
|
||||
vkCodePressed = VK_DISABLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClearModuleHotkeys(const std::wstring& moduleName) noexcept
|
||||
{
|
||||
Logger::trace(L"UnRegister hotkey action for {}", moduleName);
|
||||
@@ -230,21 +270,7 @@ namespace CentralizedKeyboardHook
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
std::unique_lock lock{ pressedKeyMutex };
|
||||
auto it = pressedKeyDescriptors.begin();
|
||||
while (it != pressedKeyDescriptors.end())
|
||||
{
|
||||
if (it->moduleName == moduleName)
|
||||
{
|
||||
it = pressedKeyDescriptors.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
ClearPressedKeyActions(moduleName);
|
||||
}
|
||||
|
||||
void Start() noexcept
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace CentralizedKeyboardHook
|
||||
void Stop() noexcept;
|
||||
void SetHotkeyAction(const std::wstring& moduleName, const Hotkey& hotkey, std::function<bool()>&& action) noexcept;
|
||||
void AddPressedKeyAction(const std::wstring& moduleName, const DWORD vk, const UINT milliseconds, std::function<bool()>&& action) noexcept;
|
||||
void ClearPressedKeyActions(const std::wstring& moduleName) noexcept;
|
||||
void ClearModuleHotkeys(const std::wstring& moduleName) noexcept;
|
||||
void RegisterWindow(HWND hwnd) noexcept;
|
||||
};
|
||||
|
||||
@@ -79,6 +79,7 @@ void PowertoyModule::update_hotkeys()
|
||||
void PowertoyModule::UpdateHotkeyEx()
|
||||
{
|
||||
CentralizedHotkeys::UnregisterHotkeysForModule(pt_module->get_key());
|
||||
CentralizedKeyboardHook::ClearPressedKeyActions(pt_module->get_key());
|
||||
|
||||
auto container = pt_module->GetHotkeyEx();
|
||||
if (container.has_value() && pt_module->is_enabled())
|
||||
@@ -102,11 +103,15 @@ void PowertoyModule::UpdateHotkeyEx()
|
||||
// Just for enabling the shortcut guide legacy behavior of pressing the Windows Key.
|
||||
// This is not the sort of behavior we'd like to have generalized on other modules.
|
||||
// But this was a way to bring back the long windows key behavior that the community wanted back while maintaining the separate process.
|
||||
if (pt_module->keep_track_of_pressed_win_key())
|
||||
if (pt_module->is_enabled() && pt_module->keep_track_of_pressed_win_key())
|
||||
{
|
||||
auto modulePtr = pt_module.get();
|
||||
auto action = [modulePtr] {
|
||||
modulePtr->OnHotkeyEx();
|
||||
if (modulePtr->is_enabled())
|
||||
{
|
||||
return modulePtr->on_hotkey(PowertoyModuleIface::WIN_KEY_HOLD_HOTKEY_ID);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
CentralizedKeyboardHook::AddPressedKeyAction(pt_module->get_key(), VK_LWIN, pt_module->milliseconds_win_key_must_be_pressed(), action);
|
||||
|
||||
Reference in New Issue
Block a user