Skip to content

[PowerAccent] Fix injection hygiene and reset state on focus loss#48572

Open
crutkas wants to merge 3 commits into
microsoft:mainfrom
crutkas:user/crutkas/fix-poweraccent-injection-hygiene
Open

[PowerAccent] Fix injection hygiene and reset state on focus loss#48572
crutkas wants to merge 3 commits into
microsoft:mainfrom
crutkas:user/crutkas/fix-poweraccent-injection-hygiene

Conversation

@crutkas

@crutkas crutkas commented Jun 13, 2026

Copy link
Copy Markdown
Member

Summary

Fixes PowerAccent injection hygiene and a stuck accent-menu state when the selector loses focus.

What this changes

  • Inject arrow keys with the extended-key flag. Replaces SendKeys.SendWait("{LEFT}"/"{RIGHT}") with WindowsFunctions.SendArrowKey(VK_LEFT/VK_RIGHT), which sets KEYEVENTF_EXTENDEDKEY on both the down and up events. Without it the synthesized arrows carry the non-extended scan code and some apps treat them as NumLock-dependent numpad navigation instead of the arrow cluster.
  • Tag injected keys so the centralized hook ignores them (dwExtraInfo = 0x110, mirroring CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG) so PowerAccent's own injections can't re-trigger other shortcuts.
  • Reset keyboard state on focus loss. New ForceReset() (added to the KeyboardListener WinRT type/IDL) is wired to the selector's Deactivated event, so the accent menu can't be left stuck if focus is taken mid-selection.
  • Make the listener's state flags std::atomic. They are written from the low-level hook thread (OnKeyDown/OnKeyUp) and from the UI/Dispatcher thread (ForceReset). A mutex is intentionally avoided: the OnKeyDown/OnKeyUp callbacks Dispatcher.Invoke onto the UI thread where ForceReset runs, so a lock held across them would deadlock.

Testing

  • Builds Release x64 (PowerAccent.Core + PowerAccentKeyboardService) clean against current main. StyleCop passes.

This is one of a small set of related "stuck key" hardening fixes; each stands alone.

crutkas and others added 3 commits June 12, 2026 17:29
- Filter LLKHF_INJECTED events in LL hook to prevent cross-module triggers
- Tag all SendInput output with dwExtraInfo for cross-hook identification
- Add ForceReset() for focus-loss cleanup
- Replace SendKeys.SendWait with tagged SendInput for arrow keys

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selector_Deactivated now calls ForceReset, so losing focus mid-accent clears
any half-pressed state instead of leaving the toolbar or shift modifiers stuck.

OnKeyDown/OnKeyUp run on the low-level keyboard hook thread while ForceReset
runs on the UI thread. The PowerAccent.cs callbacks Dispatcher.Invoke
synchronously onto that UI thread, so holding a lock across the hook callbacks
would deadlock against ForceReset. The shared flags and letterPressed are
therefore std::atomic rather than mutex-guarded - race-free and lock-free on
the hot hook path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PowerAccent injects VK_LEFT / VK_RIGHT to move the caret while selecting an
accent. Without KEYEVENTF_EXTENDEDKEY the synthesized arrow keys carry the
non-extended scan code, which some apps interpret as numpad navigation
(affected by NumLock) rather than the dedicated arrow cluster. Set the
extended-key flag on both the key-down and key-up INPUTs, matching how
WinForms SendKeys synthesizes these keys.

Also resolve two StyleCop errors in code this change set introduced, so the
project builds clean: rename the POWERTOYS_INJECTED_TAG constant to
PascalCase PowerToysInjectedTag (SA1310) and add the required blank line
after the backspace send-failure check (SA1513).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, 👼 SARIF report, or 📝 job summary for details.

❌ Errors and Warnings Count
⚠️ binary-file 1
⚠️ duplicate-pattern 2
❌ forbidden-pattern 1
⚠️ large-file 1

See ❌ Event descriptions for more information.

Some files were automatically ignored 🙈

These sample patterns would exclude them:

^src/modules/ZoomIt/ZoomIt/rnnoise/rnnoise_data_little\.c$
^src/modules/ZoomIt/ZoomIt/selfie_segmentation\.onnx$

You should consider adding them to:

.github/actions/spell-check/excludes.txt

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 patterns.txt to exclude portions, add items to the dictionary (e.g. by adding them to allow.txt), or fix typos.

To update file exclusions, you could run the following commands

... in a clone of the git@github.com:crutkas/autoUpgradeAttempt.git repository
on the user/crutkas/fix-poweraccent-injection-hygiene branch (ℹ️ how do I use this?):

curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c/apply.pl' |
perl - 'https://github.com/microsoft/PowerToys/actions/runs/27451220763/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 a
\san (?=(?:[b-dfgjklpqtvwz]|h(?!onou?r|our|s[lv]|tml|ttp|ref)|n(?!ginx|grok|pm)|r(?!c)|s(?!s[ho]|vg))[a-z]|x(?!\b|[-\d]|ml))
If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant