Skip to content

Handle Accessibility permission and keep cmdX running when its menu bar icon is hidden (macOS 26) - #22

Open
rubnogueira wants to merge 1 commit into
YONN2222:mainfrom
rubnogueira:fix/accessibility-permission-and-menubar-hide
Open

Handle Accessibility permission and keep cmdX running when its menu bar icon is hidden (macOS 26)#22
rubnogueira wants to merge 1 commit into
YONN2222:mainfrom
rubnogueira:fix/accessibility-permission-and-menubar-hide

Conversation

@rubnogueira

@rubnogueira rubnogueira commented Sep 1, 2026

Copy link
Copy Markdown

Closes #21
Closes #14

What this does

Makes the key interceptor robust to the two things that were silently breaking ⌘X/⌘V, and surfaces Accessibility permission in the UI.

Accessibility permission

  • On launch, AppDelegate checks AXIsProcessTrusted() and shows the system prompt when access is missing.
  • A lightweight poll keeps the status live and auto-starts the interceptor the moment access is granted — no relaunch needed.
  • The popover now shows a live status card (granted / needed) with a Grant Access… button.
  • KeyInterceptor publishes hasAccessibilityPermission; start() guards on it instead of failing silently.

Survive the menu bar icon being hidden (macOS 26 / Tahoe)

  • The app's lifetime was anchored to the single SwiftUI MenuBarExtra scene, so hiding/removing the menu bar item on Tahoe tore the scene down and terminated the whole process.
  • Replaced it with a classic AppKit NSStatusItem owned by AppDelegate, an empty Settings scene, and applicationShouldTerminateAfterLastWindowClosed == false. The icon still shows (and respects the OS show/hide setting), but the app now runs as a background agent whose lifetime no longer depends on the menu bar item.
  • This also delivers the "Hide in menu bar" feature requested in Hide in menu bar #14: because the app keeps running with no menu bar item, users can hide the ⌘ symbol via macOS 26's menu bar settings and cmdX continues to work.

Keep the event tap alive

  • Prevent App Nap via a held ProcessInfo.beginActivity token (still allows idle system sleep) — otherwise the throttled callback times out and the tap gets disabled when there's no visible UI.
  • Re-enable the tap when the system disables it (tapDisabledByTimeout / tapDisabledByUserInput), instead of leaving it dead.

Misc

  • Updated the update notification text, which told users to open the app in the menu bar to install updates.

Testing

  • Built with Xcode 26.
  • Verified: launches and prompts for Accessibility when missing; popover shows the correct live status; interception starts automatically on grant; ⌘X/⌘V keep working after the menu bar icon is hidden.

Notes for reviewers

  • This consolidates the two previous NSApplicationDelegate classes (AppDelegateWrapper and the unused AppDelegate) into one.
  • ContentView is now hosted in the NSStatusItem popover rather than a MenuBarExtra.

…ar icon is hidden

The key interceptor requires Accessibility permission and its lifetime was
tied to the SwiftUI MenuBarExtra scene, so ⌘X/⌘V silently stopped working
when permission was missing/revoked and the app quit outright when its menu
bar icon was hidden on macOS 26 (Tahoe).

Accessibility permission:
- Check AXIsProcessTrusted() on launch and prompt when access is missing.
- Poll so the status stays live and the interceptor auto-starts the moment
  access is granted (no relaunch).
- Show a live permission status card in the popover with a Grant Access button.
- KeyInterceptor publishes hasAccessibilityPermission; start() guards on it
  instead of failing silently.

Survive the menu bar icon being hidden (macOS 26):
- Replace the single MenuBarExtra scene with a classic AppKit NSStatusItem
  owned by AppDelegate, an empty Settings scene, and
  applicationShouldTerminateAfterLastWindowClosed == false, so the app runs as
  a background agent whose lifetime no longer depends on the menu bar item.

Keep the event tap alive:
- Prevent App Nap with a held ProcessInfo activity token (idle sleep allowed).
- Re-enable the tap when the system disables it (timeout / user input).

Also update the update notification text, which referenced opening the menu
bar to install updates.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant