Skip to content

OneMore hot keys silently stop working until a OneMore ribbon item is clicked #2199

@stevencohn

Description

@stevencohn

Root Cause: TOCTOU Race in WinEventProc

File: OneMore\Helpers\Hotkeys\HotkeyManager.cs, line 185

The hook is registered with WINEVENT_OUTOFCONTEXT:

Native.SetWinEventHook(
Native.EVENT_SYSTEM_FOREGROUND, // 3
Native.EVENT_SYSTEM_MINIMIZEEND, // 23
IntPtr.Zero, evDelegate,
0, 0, Native.WINEVENT_OUTOFCONTEXT | Native.WINEVENT_SKIPOWNTHREAD);

WINEVENT_OUTOFCONTEXT means the callback is posted asynchronously to the MessageWindow thread's message queue. By the time the callback executes, the foreground window may have already changed from what triggered the event.

Inside WinEventProc (line 185), the check is:

Native.GetWindowThreadProcessId(Native.GetForegroundWindow(), out var pid);

This queries the foreground window at callback execution time, not at event generation time. This creates a TOCTOU race:

Failure scenario (most common)

  1. User is in OneNote → hotkeys registered (registered = true)
  2. A OneMore dialog opens in dllhost.exe (or a notification popup appears) → focus leaves OneNote
  3. EVENT_SYSTEM_FOREGROUND fires and is queued for our thread
  4. User quickly dismisses the popup/dialog → focus returns to OneNote
  5. Another EVENT_SYSTEM_FOREGROUND fires and is queued
  6. Our thread processes event 3: GetForegroundWindow() now returns OneNote (user already returned) → pid == oneNotePID, registered == true → no action (missed the unregister)
  7. Our thread processes event 5: same → no action
  8. Net result: looks fine so far ✓

BUT in a different ordering (common with minimize/restore or rapid window switching):

  1. OneNote focus, registered=true
  2. Minimize OneNote → EVENT_SYSTEM_MINIMIZESTART queued
  3. Restore OneNote via taskbar → EVENT_SYSTEM_MINIMIZEEND queued
  4. Callback for MINIMIZESTART: GetForegroundWindow() = taskbar → pid != oneNotePID → unregister, registered=false ✓
  5. Callback for MINIMIZEEND: GetForegroundWindow() at this moment might be taskbar/desktop (transition still in progress) → pid != oneNotePID → no re-registration → stuck unregistered
  6. No subsequent event fires. User is in OneNote, but hotkeys are dead.

The same race applies when a notification popup (Teams, email toast) briefly steals and then auto-releases focus — the EVENT_SYSTEM_FOREGROUND for returning to OneNote may be processed after something else moves to the foreground.

Why ribbon command fixes it

When the user opens a ribbon dropdown or invokes any command that shows a WinForms dialog:

  • The menu popup or dialog takes focus
  • When it closes, EVENT_SYSTEM_FOREGROUND fires for the OneNo
  • This particular event is processed quickly (clean transition, nothing else competing)
  • GetForegroundWindow() returns OneNote at callback time → pid == oneNotePID, registered == false → re-registration succeeds

Fix

Use the hwnd parameter passed directly to WinEventProc instead of calling GetForegroundWindow(). Per MSDN, for EVENT_SYSTEM_FOREGROUND, hwnd is the window gaining focus — it carries the state at event generation time, not callback execution time, eliminating the race.

For EVENT_SYSTEM_MINIMIZEEND, hwnd is the window being restored (OneNote). Using hwnd.PID always equals oneNotePID, correctly triggering re-registration whenever OneNote is restored.

For EVENT_SYSTEM_MINIMIZESTART, hwnd is also the OneNote window being minimized — its PID == oneNotePID. The existing logic would treat this as "OneNote is foreground → re-register," which is wrong. Instead, handle MINIMIZESTART as an explicit unregister when the minimizing window belongs to OneNote.

Metadata

Metadata

Assignees

Labels

enhancementGood ol' fashioned programmin! Not user-requestedreleasedAvailable in a released installer

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions