Skip to content

Gate switcher event monitor pump on hardware event counters - #1408

Closed
ProspectOre wants to merge 1 commit into
steipete:mainfrom
ProspectOre:fix/switcher-monitor-event-gated
Closed

Gate switcher event monitor pump on hardware event counters#1408
ProspectOre wants to merge 1 commit into
steipete:mainfrom
ProspectOre:fix/switcher-monitor-event-gated

Conversation

@ProspectOre

Copy link
Copy Markdown
Contributor

Summary

Gate the merged provider switcher's event-monitor pump on hardware event counters, so it runs once per actual click or key press instead of once per menu-tracking run-loop cycle. This fixes the felt input freezes / dropped clicks during long merged-menu sessions reported in #1399, isolated by controlled bisection on reproducing hardware.

Context

ProviderSwitcherShortcutEventMonitor (the only working delivery mechanism for switcher clicks and Command-number/arrow shortcuts during tracking — see the #1260 closure evidence) installs a beforeSources run-loop observer for the tracking mode. The observer fired on every cycle of the menu-tracking loop, and each NSApp.nextEvent(until: .distantPast, ...) peek re-enters the run loop. Under continuous pointer movement this multiplies run-loop passes and starves the tracking session's own input processing: clicks drop, the desktop beachballs, and the process profiles healthy throughout — the signature in #1399, #1387, #1364.

Isolation evidence (same machine, same ~2-minute manual interaction protocol per round, WindowServer log stream + 1ms sample captures)

Build Felt beachballs / dropped input WindowServer buffer-clear events
Stock current main yes 5 (single burst at ~50s of tracking)
Experiment: all in-tracking churn suppressed no 0
Experiment: churn suppressed, monitor alone re-enabled yes 1
Stock + this fix no — switcher clicks and ⌘1/⌘2/⌘3 and ←/→ all verified working 7 (single burst; see honesty notes)

Control: 120s holding an Apple menu-bar menu open with continuous cursor motion produced zero buffer-clear events, ruling out generic macOS tracking behavior. Full protocol and logs posted on #1399.

Change

  • EventCounterGate: before peeking, compare CGEventSource.counterForEventType(.combinedSessionState, ...) for exactly the three event types the monitor handles (leftMouseDown, leftMouseUp, keyDown). Unchanged counters → no pump. Identical delivery semantics: any matching hardware event changes a counter, so the very next observer fire pumps.
  • A pure-userspace 8ms time bound caps the counter queries themselves regardless of how hot the tracking loop spins.

Validation

  • swift test --filter 'StatusMenuSwitcherClickTests|StatusMenuPersistentRefreshTests|StatusItemControllerShutdownTests' — 25 tests pass.
  • make check — 0 violations; git diff --check clean.
  • Live manual verification on reproducing hardware (table above): no beachballs, switcher clicks work, ⌘-number and arrow shortcuts work during tracking.

Honesty / scope notes

ProviderSwitcherShortcutEventMonitor's run-loop observer fired on every
cycle of the menu-tracking loop, and each nextEvent peek re-enters the
run loop. During continuous pointer movement over the merged dropdown
this starves the tracking session's own input processing - dropped
clicks and system-wide beachball reports (steipete#1399) - while the process
profiles healthy. The pump now runs only when a leftMouseDown,
leftMouseUp, or keyDown hardware counter has actually changed, bounded
by a pure-userspace 8ms time check, reducing it from once per run-loop
cycle to once per click or key press with identical delivery semantics.
@ProspectOre

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 10, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@steipete

Copy link
Copy Markdown
Owner

Superseded by #1397, landed in eda747ba6d4293a831398b8dadd2e2189793a7f6.

The landed implementation gates the same NSApp.nextEvent pump on session HID counters, and additionally handles delayed AppKit queue delivery, bursts represented by one counter snapshot, unhandled queued events, keyUp, and uncounted key autorepeat. It includes focused gate tests plus packaged-menu keyboard/stress proof.

Thanks @ProspectOre for independently isolating the same hotspot and documenting the WindowServer evidence.

@steipete steipete closed this Jun 10, 2026
@ProspectOre
ProspectOre deleted the fix/switcher-monitor-event-gated branch June 15, 2026 07:26
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.

2 participants