Skip to content

Walk users through Screen Recording and Accessibility permissions - #10

Merged
salemhilal merged 8 commits into
mainfrom
fix/accessibility-permission
Oct 5, 2026
Merged

salemhilal merged 8 commits into
mainfrom
fix/accessibility-permission

Conversation

@salemhilal

Copy link
Copy Markdown
Owner

Fixes 1.5.0's silent capture failure: ScreenHint needs Accessibility for its capture event tap but never got users to grant it. When the tap couldn't be created, New Hint called AXIsProcessTrustedWithOptions(prompt:) and returned. macOS shows that prompt at most once, and in the sandboxed App Store build it doesn't appear at all, so nothing happened.

This is the base of a stack: this PR → #8 (copy feedback) → #9 (tour).

Permissions window

  • A row per permission (Screen Recording, Accessibility), each showing what it's for.
  • Not yet requested: "Request Access" makes the system request. That's the prompt for Screen Recording, and for Accessibility the call that also adds ScreenHint to the list.
  • Requested but still missing: "Open System Settings…" opens the right pane, with a line naming it for the running macOS (Accessibility is "Device Control and Data Access" on macOS 27; Screen Recording is "Screen & System Audio Recording" on 15+). ScreenHint is listed there because it has asked. A request flips straight to this state because there's no way to tell whether a prompt actually appeared.
  • Granted: shows "Allowed". Rows refresh every second, and when the app becomes active, so granting in System Settings updates the window.

Where it shows

  • At launch when anything is missing, in place of the bare Screen Recording request at launch.
  • When a capture is blocked by a missing permission, in place of the old Screen Recording alert and the silent Accessibility failure.
  • As a section in Settings.

Also

  • The app delegate's print() calls are now os.Logger.
  • Accessibility counts as granted if AXIsProcessTrusted() is true or the event tap can be created, since the former can read false when the tap works (e.g. under Xcode).

Testing

  • PermissionsTests (5 tests) covers request-then-open-Settings, persisting "requested" across launches, granted states, and live refresh, with the system calls stubbed so no prompts appear. All 34 unit tests pass.
  • Verified that x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility still opens the right pane on macOS 27.
  • Not yet checked by eye: the window's layout and the real request flow. The Mac was locked during testing.

🤖 Generated with Claude Code

ScreenHint 1.5.0 needs Accessibility for its capture event tap, but
never got it: when the tap couldn't be created, choosing New Hint just
called AXIsProcessTrustedWithOptions(prompt:) and returned. macOS shows
that prompt at most once, and in the sandboxed App Store build it
doesn't appear at all, so capture silently did nothing.

Add a Permissions window with a row per permission. A permission that's
never been asked for gets the system request; one that's been asked for
and is still missing gets an "Open System Settings…" button for the
right pane (named for the running macOS), where ScreenHint is listed
because it has asked. Granted permissions show as Allowed, and the rows
refresh live while the window is open.

The window shows at launch when anything is missing (in place of the
bare Screen Recording request at launch) and whenever a capture is
blocked by a missing permission (in place of the old alert and the
silent failure). It's also a section in Settings. The request logic is
unit-tested with the system calls stubbed out.

Also switch the app delegate's print() calls to os.Logger.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
salemhilal and others added 7 commits October 4, 2026 19:24
…for Accessibility

- Rows are titled with the pane's name on the running macOS (Device
  Control and Data Access on 27, Screen & System Audio Recording on 15+).
- The action sits in a fixed-width column and the "where to find it"
  line shows whenever a permission is missing, so pressing a button no
  longer reflows the row and pushes the window's buttons out of view.
  The title clears the window's close button.
- Accessibility is requested with CGRequestPostEventAccess() as well as
  AXIsProcessTrustedWithOptions(), and "Open System Settings…" asks
  again before opening the pane, so ScreenHint is listed there to turn
  on. (A pending privacy prompt for another permission holds this back
  until it's answered.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each permission is now a card with its status (Allowed / Not allowed) on
the title line, and, when it's missing, where to find it in System
Settings with the button beside it. The text always gets the card's full
width, so it reads the same in Settings, the Permissions window, and the
tour.

Settings gets a "Settings" title in place of the empty space at the top,
and the same 480pt content width as the tour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…polish

From a review of the stack:
- A tap created earlier (e.g. pre-warmed at launch) outlives a revoked
  Accessibility grant but never receives events, so turning Accessibility
  off while ScreenHint ran left it showing "Allowed", and New Hint put up
  an overlay that ignored the mouse. When AXIsProcessTrusted() reads false,
  the tap is now recreated before being trusted or used.
- The Accessibility prompt never appears for ScreenHint, so its first
  click goes straight to System Settings (requesting first, so ScreenHint
  is listed) instead of only relabeling the button.
- Status labels tint only the symbol; colored text was too low-contrast.
- The rows refresh once a second only while something is still missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
macOS doesn't announce permission changes, so with the once-a-second
check limited to while something was missing, revoking a permission
(in System Settings or with tccutil) left the cards saying "Allowed"
until ScreenHint came to the front. Check the whole time they're on
screen; once everything's granted it's just two quick system calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e prompts

- CGRequestScreenCaptureAccess() only asks the system once per launch,
  so pressing the Screen Recording button again (e.g. after a reset)
  registered nothing and ScreenHint was missing from the pane. Also ask
  ScreenCaptureKit for shareable content, which checks with the system
  every time.
- The Permissions window is an ordinary window instead of floating, so
  it doesn't sit on top of the system's permission prompts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Accessibility card created a fresh event tap every second to see whether
access was on. AXIsProcessTrusted() reads the switch in System Settings
directly, and picks up changes right away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cards

- Don't open the Permissions window over the tour on first launch.
- Settings, About and the tour are ordinary windows, so the Screen Recording
  prompt isn't hidden under Settings' permission cards.
- Drop a doc comment left behind by a deleted function, and say what the
  Accessibility request can actually show (the "(Events)" prompt).
- Hide the cards' decorative symbols from VoiceOver, and say which permission
  each button is for.
- Clean up the throwaway defaults domains the permission tests make.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@salemhilal
salemhilal merged commit 1378946 into main Oct 5, 2026
1 check passed
@salemhilal
salemhilal deleted the fix/accessibility-permission branch October 5, 2026 12:32
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