Skip to content

fix: recover CGEventTap after system events — fixes #167 system-wide input degradation#169

Merged
pablopunk merged 1 commit into
mainfrom
fractal-Swift-Shift-https-github-com-pablopunk-swiftshift-issues-167-foll-62dcab
Jun 19, 2026
Merged

fix: recover CGEventTap after system events — fixes #167 system-wide input degradation#169
pablopunk merged 1 commit into
mainfrom
fractal-Swift-Shift-https-github-com-pablopunk-swiftshift-issues-167-foll-62dcab

Conversation

@pablopunk

@pablopunk pablopunk commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Problem

SwiftShift's mouse-click shortcuts (Command+click for Move, Command+right-click for Resize) use a CGEventTap to intercept mouse events. macOS silently disables event taps on sleep/wake, display changes, session lock, and slow callbacks — but SwiftShift had no recovery mechanism. A dead tap causes system-wide input lag because CGEventTaps are synchronous (WindowServer pauses all input until the callback returns).

Closes #167.

Root Cause

The reporter uses Command+click for Move and Command+right-click for Resize. Each shortcut press creates CGEventSupervisor subscriptions which create a CGEventTap. The tap dies silently on system events, and without recovery the tap stays dead — causing the system-wide input delay. Restarting the app works because it creates a fresh tap.

Fixes

  1. CGEventSupervisor vendored & fixed — proper Mach port teardown (run loop source removal + CFMachPortInvalidate), auto-reenable tap on disabled-by-timeout/user-input events
  2. System event recovery — observers for , , trigger full rebuild of all input hooks
  3. **** — fixes stale flag after so mouse-only chords recover properly
  4. Active shortcut state cleared on recovery — prevents stale modifier-key state from blocking future activations
  5. 60s health check timer — catches tap death from Secure Input (password prompts, sudo) via periodic

Summary by CodeRabbit

  • Bug Fixes
    • Resolved system-wide mouse input degradation (Issue 167) caused by the event tap being silently disabled during sleep/wake, display changes, session lock, and related conditions.
    • Added stronger automatic recovery that re-enables event handling and rebuilds input hooks when tap health monitoring detects failure.
  • Documentation
    • Updated the included fix documentation for the vendored event-supervision component.

@codesandbox

codesandbox Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@vercel

vercel Bot commented Jun 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
swiftshift-app Ready Ready Preview, Comment Jun 19, 2026 11:09pm

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

CGEventSupervisor is vendored from its remote GitHub dependency into Swift Shift/packages/CGEventSupervisor/ as a local Swift package. The vendored copy adds proper Mach port teardown (CFRunLoopRemoveSource, CFMachPortInvalidate) and re-enables the event tap upon receiving tapDisabledByTimeout or tapDisabledByUserInput events. The Xcode project's packageReferences entry is switched from remote to local. ShortcutsManager gains system-event observers (sleep/wake, session activation, screen parameter changes) that trigger rebuildAllInputHooks(), which cancels all CGEventSupervisor subscriptions and rebuilds them. MouseChordActionManager adds a 60-second repeating healthCheckTimer that calls forceRebuild() to recover from silently disabled taps.

Possibly related PRs

  • pablopunk/SwiftShift#83: Modifies ShortcutsManager with shortcut and mouse-subscription teardown/cleanup logic that aligns with this PR's rebuildAllInputHooks rebuild flow.
  • pablopunk/SwiftShift#159: Introduces or modifies MouseChordActionManager chord-based behavior that this PR extends with forceRebuild() and healthCheckTimer.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: fixing CGEventTap recovery after system events, with direct reference to the issue (#167) addressing the system-wide input degradation problem.
Linked Issues check ✅ Passed The PR fully addresses issue #167 requirements: vendoring CGEventSupervisor with Mach port teardown and auto-reenable, adding system event recovery observers, implementing periodic health checks, and clearing stale state to eliminate recurring input degradation.
Out of Scope Changes check ✅ Passed All changes are directly scoped to resolving issue #167: vendoring CGEventSupervisor, adding recovery mechanisms, fixing state management, and implementing health checks. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pablopunk

Copy link
Copy Markdown
Owner Author

@codex pls review this PR, take into account we're trying to fix a performance issue that scales up with usage only when the mouse buttons options are enabled

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@FIXES.md`:
- Around line 43-44: The documentation in FIXES.md currently implies that
`activeShortcuts` is cleared as part of
`MouseChordActionManager.forceRebuild()`, but it is actually cleared in
`ShortcutsManager.rebuildAllInputHooks()`. Revise the wording to explicitly
attribute the clearing of `activeShortcuts` and the stopping of in-progress
tracking to `ShortcutsManager.rebuildAllInputHooks()` to remove the ambiguity
about which component owns this responsibility.

In `@Swift` Shift/packages/CGEventSupervisor/README.md:
- Around line 3-9: The CGEventSupervisor vendored package declares MIT License
in its README but lacks a corresponding LICENSE file in the Swift
Shift/packages/CGEventSupervisor/ directory, and the upstream source also lacks
explicit license documentation. Resolve this compliance issue by either creating
a LICENSE file in the CGEventSupervisor package directory containing the full
MIT license text, or update the README to accurately reflect the actual
licensing status after confirming with the upstream author. Do not claim a
license without proper documentation to back it up.

In `@Swift`
Shift/packages/CGEventSupervisor/Sources/CGEventSupervisor/CGEventSupervisor+Setup.swift:
- Around line 58-61: The eventMask computed property's reduce closure performs
an unchecked shift operation (1 << $1.rawValue) that can overflow if
CGEventType.rawValue exceeds safe bit widths. Add bounds checking in the reduce
closure to filter out invalid event types before applying the bitwise operation.
Guard against rawValues that would cause shift overflow by validating they fall
within a safe range (typically 0 to 63 for 64-bit operations) before performing
the shift and OR operation.

In `@Swift`
Shift/packages/CGEventSupervisor/Sources/CGEventSupervisor/CGEventSupervisor+Subscribers.swift:
- Around line 28-35: The needsSetup check in the subscriber update logic only
detects when events are added because it compares against the old
subscribedEvents before updating the __nsEventSubscribers record. When an
existing subscriber narrows its event set (subscribes to fewer events), the tap
is not rebuilt and continues listening to stale events. Fix this by computing
needsSetup after updating the __nsEventSubscribers with the new events, so it
can detect both additive changes and when the overall set of subscribed events
decreases, triggering a tap rebuild when necessary. Apply the same fix to the
duplicate logic at lines 51-58.

In `@Swift` Shift/src/Manager/ShortcutsManager.swift:
- Around line 820-825: The startHealthCheckTimer method's timer closure
currently forces a rebuild every 60 seconds whenever isSubscribed is true, which
can interrupt an in-progress chord gesture. Add an additional guard condition in
the timer closure (after the existing guard that checks isSubscribed) to check
whether a chord gesture is currently active before calling forceRebuild(),
ensuring that rebuilds are skipped if a chord gesture is in progress.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b5a9836-b071-4dc3-8a0d-d38165ba77a3

📥 Commits

Reviewing files that changed from the base of the PR and between 8ae3a14 and bad9402.

📒 Files selected for processing (13)
  • .gitignore
  • FIXES.md
  • Swift Shift.xcodeproj/project.pbxproj
  • Swift Shift/packages/CGEventSupervisor/Package.swift
  • Swift Shift/packages/CGEventSupervisor/README.md
  • Swift Shift/packages/CGEventSupervisor/Sources/CGEventSupervisor/CGEventSupervisor+Callback.swift
  • Swift Shift/packages/CGEventSupervisor/Sources/CGEventSupervisor/CGEventSupervisor+Setup.swift
  • Swift Shift/packages/CGEventSupervisor/Sources/CGEventSupervisor/CGEventSupervisor+Subscribers.swift
  • Swift Shift/packages/CGEventSupervisor/Sources/CGEventSupervisor/CGEventSupervisor+Types.swift
  • Swift Shift/packages/CGEventSupervisor/Sources/CGEventSupervisor/CGEventSupervisor.swift
  • Swift Shift/packages/CGEventSupervisor/Sources/CGEventSupervisor/Extensions/CGEvent.swift
  • Swift Shift/packages/CGEventSupervisor/Sources/CGEventSupervisor/Extensions/NSEvent.swift
  • Swift Shift/src/Manager/ShortcutsManager.swift

Comment thread FIXES.md Outdated
Comment thread Swift Shift/packages/CGEventSupervisor/README.md
Comment thread Swift Shift/src/Manager/ShortcutsManager.swift

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bad94028f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Swift Shift/src/Manager/ShortcutsManager.swift
@pablopunk pablopunk force-pushed the fractal-Swift-Shift-https-github-com-pablopunk-swiftshift-issues-167-foll-62dcab branch 2 times, most recently from fc1e7e3 to 7918f58 Compare June 19, 2026 23:06
@pablopunk

Copy link
Copy Markdown
Owner Author

CodeRabbit review follow-up:

FIXES.md ambiguity — fixed: clarified that ShortcutsManager.rebuildAllInputHooks() (not forceRebuild()) owns the activeShortcuts clearing.

LICENSE file — added MIT LICENSE file to the vendored package.

Health check timer mid-gesture — added guard for activeAction == nil before calling forceRebuild().

eventMask shift overflow — skipped. This is pre-existing upstream code in CGEventSupervisor, not introduced by this PR. CGEventType raw values are well under 64 (largest is ~30), so overflow is not practically reachable.

Subscriber needsSetup ordering — skipped. Also pre-existing upstream code. The scenario (narrowing an existing subscriber's events) never occurs in SwiftShift's usage — we either subscribe to a fixed set or cancel entirely.

Fixes #167 — system-wide input degradation after 2-3 days
caused by CGEventTap silently dying on sleep/wake,
display changes, and session lock.

- Vendor CGEventSupervisor as local package with:
  - Proper Mach port teardown (run loop source + CFMachPortInvalidate)
  - Auto-reenable tap on kCGEventTapDisabledByTimeout/UserInput
- Add system event recovery observers (didWake, sessionDidBecomeActive, didChangeScreenParameters)
- Add forceRebuild() to fix stale isSubscribed flag after cancelAll()
- Clear active shortcut state during recovery
- Add 60s health check timer for Secure Input recovery
@pablopunk pablopunk force-pushed the fractal-Swift-Shift-https-github-com-pablopunk-swiftshift-issues-167-foll-62dcab branch from 7918f58 to 32f9518 Compare June 19, 2026 23:08
@pablopunk pablopunk merged commit 74f7c3d into main Jun 19, 2026
5 of 6 checks passed
@pablopunk pablopunk deleted the fractal-Swift-Shift-https-github-com-pablopunk-swiftshift-issues-167-foll-62dcab branch June 19, 2026 23:13
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.

Window manager starts becoming less and less responsive after a few days

1 participant