[Shortcut Guide] Restore long-press Windows key activation#48583
[Shortcut Guide] Restore long-press Windows key activation#48583niels9001 wants to merge 5 commits into
Conversation
Brings back the pre-V2 activation mode where users could open Shortcut Guide by holding the Windows key (default 900ms) instead of pressing a customized shortcut. The two modes are mutually exclusive, matching the v0.99 behavior. New settings: - use_legacy_press_win_key_behavior (bool, default false) - press_time (int ms, default 900) The C++ module overrides keep_track_of_pressed_win_key() and milliseconds_win_key_must_be_pressed(); when legacy mode is on it also returns nullopt from GetHotkeyEx() so the runner doesn't register the custom shortcut. The runner already handles dynamic re-registration via CentralizedKeyboardHook::AddPressedKeyAction and UpdateHotkeyEx(). The Settings page exposes a 'Activation method' ComboBox; selecting 'Press and hold the Windows key' hides the shortcut control, shows a press-duration NumberBox, and surfaces the existing warning InfoBar about possible side-effects. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Two runner-side bugs caused the long-press Win activation to misfire: 1. UpdateHotkeyEx() did not clear pre-existing pressed-key descriptors before re-adding them. Because the runner calls UpdateHotkeyEx() on every settings update -- and from several general-settings paths without first calling update_hotkeys() -- the LWIN/RWIN descriptors would accumulate. PressedKeyTimerProc iterates all descriptors that share a timer id, so one Win-hold ended up firing OnHotkeyEx() N times (N launches, then "Another instance is running" errors). Fix: introduce ClearModulePressedKeyActions() and call it at the top of the win-key branch in UpdateHotkeyEx(); also gate on is_enabled() to match the regular hotkey path. ClearModuleHotkeys is refactored to reuse the new helper and now kills any in-flight timers. 2. PressedKeyTimerProc ignored the action's return value, so even when the action wanted Start Menu suppression we never sent the dummy keystroke. The result: the user's Win-key release activated the Start Menu directly over the freshly-shown Shortcut Guide window. Fix: honor the bool return -- when true, send the same WM_KEYUP dummy event KeyboardHookProc uses for regular hotkeys. The Shortcut Guide action lambda now returns true. A break is also added after the matching action so a single timer id fires its action exactly once even if duplicate descriptors slip through. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
The dummy 0xFF keystroke that prevents the Start Menu from opening after a long-press action was being sent at timer-fire time -- ~press_time ms before the actual Win release. By the time the user released Win, the Windows shell had already forgotten the injection and Start Menu would still pop up. Match the v0.99 ShortcutGuide pattern: record a pending chord-break when the timer fires, then inject the dummy keystroke inside the WH_KEYBOARD_LL hook when the matching Win-up arrives, before CallNextHookEx propagates it to the shell. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Make the toggle path explicit in the trace log so it's obvious when an alternating press is dismissing a held-over Debug-build instance vs failing to launch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
WinUI3's dispatcher loop does not auto-exit when the last window closes (unlike WPF/WinForms). Without an explicit Application.Exit() call, Application.Start in Program.cs never returns and SG.exe lingers after its window is closed. The runner's module then sees IsProcessActive() == true on the next OnHotkeyEx, takes the toggle/dismiss path, and TerminateProcess's the zombie instead of launching a new window. Net result: every other hotkey press appears to do nothing. Call Microsoft.UI.Xaml.Application.Current.Exit() in the MainWindow.Closed handler so the process actually terminates after the window goes away. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@check-spelling-bot Report🔴 Please reviewSee the 📂 files view, the 📜action log, 👼 SARIF report, or 📝 job summary for details.
See ❌ Event descriptions for more information. Some files were automatically ignored 🙈These sample patterns would exclude them: You should consider adding them to: File matching is via Perl regular expressions. To check these files, more of their words need to be in the dictionary than not. You can use To update file exclusions, you could run the following commands... in a clone of the git@github.com:niels9001/PowerToys.git repository curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c/apply.pl' |
perl - 'https://github.com/microsoft/PowerToys/actions/runs/27473829785/attempts/1' &&
git commit -m 'Update check-spelling metadata'Forbidden patterns 🙅 (1)In order to address this, you could change the content to not match the forbidden patterns (comments before forbidden patterns may help explain why they're forbidden), add patterns for acceptable instances, or adjust the forbidden patterns themselves. These forbidden patterns matched content: Should be
|
Summary of the Pull Request
Restores the pre-V2 long-press Windows key activation for Shortcut Guide. Users can now choose between the customized keyboard shortcut (default Win + Shift + /) or holding the Windows key for a configurable duration (default 900 ms). The two modes are mutually exclusive, matching the v0.99 behavior.
Closes #48491
PR Checklist
Detailed Description of the Pull Request / Additional comments
New settings (settings.json)
use_legacy_press_win_key_behaviorfalsepress_time900When
use_legacy_press_win_key_behavioris true:std::nulloptfromGetHotkeyEx()so the runner does not register the customized shortcut.truefromkeep_track_of_pressed_win_key()and the configured ms frommilliseconds_win_key_must_be_pressed(), which causespowertoy_module.cpp::UpdateHotkeyExto register a press-action onVK_LWIN/VK_RWINviaCentralizedKeyboardHook::AddPressedKeyAction.set_config->UpdateHotkeyEximmediately, so no restart is needed.Simplifications vs v0.99
v0.99 had two press-time settings (
press_timeandpress_time_for_taskbar_icon_shortcuts). V2 shows the taskbar indicator inline whenever the selected section has a<TASKBAR1-9>marker, so the dual-stage timing is no longer meaningful. This PR ships onepress_timesetting.Backwards compat
use_legacy_press_win_key_behavior/press_timevalues.Files changed
ShortcutGuideProperties.csUseLegacyPressWinKeyBehaviorandPressTime(default 900 ms)ShortcutGuideModuleInterface/dllmain.cppkeep_track_of_pressed_win_key/milliseconds_win_key_must_be_pressed; gatesGetHotkeyExShortcutGuideViewModel.csNotifyPropertyChanged(which both raises change and saves+sends IPC)ShortcutGuidePage.xamlActivation methodComboBox; the existing shortcut card and the new press-duration NumberBox toggle visibility; warning InfoBarResources.reswShortcutGuide_PressTime.Header/.DescriptionSettings.UI.UnitTests/ViewModelTests/ShortcutGuide.csValidation Steps Performed
Settings.UI.Library,ShortcutGuideModuleInterface,PowerToys.Settings,Settings.UI.UnitTestsall build clean (exit 0, empty errors logs) on arm64 / Debug.Settings.UI.UnitTests-> all 10 ShortcutGuide tests pass, including the 3 new ones.Custom shortcutre-registers the shortcut.