feat(macos): first-run permissions window - #735
Conversation
A fresh macOS install could not reach Screen Recording: the app only raised the prompt for a status Chromium never reports (`not-determined`), so the first Record click went straight to an English 'Open System Settings' dialog for an app that was not even in the list yet. The microphone was also requested on every launch, before anything used it. A permissions window now opens at launch while Screen Recording is missing, and from the app and tray menus. It lists Screen & system audio (required), Accessibility (recommended, for the cursor), and the microphone and camera (optional), each with its live status and the one action that can move it: raise macOS' prompt the first time, open the right System Settings pane after that. - Screen Recording is read from a helper spawned per read (`--screen-access-status`): the app's own read is cached for the life of the process and never sees a grant made while it runs. - 'Never asked' and 'refused' are the same bool on macOS, so the app keeps its own note of the prompts it raised (permissions.json). - When the grant exists but this process cannot use it yet, the window offers the relaunch. - The Record button and the cursor check open the window instead of the two hard-coded English dialogs; the renderer's 6 s retry loop and the launch-time microphone request are gone. Strings in all 15 locales.
System Settings offers 'Quit & Reopen' the moment Screen Recording is turned on, and the window tells the user it comes back from that. It did not: at launch it only opened while Screen Recording was missing, so the relaunch left Accessibility, the microphone and the camera behind, and broke the window's own promise. The window now also opens at launch when the onboarding was started (its prompt raised) and not finished. It counts as finished once it is closed with Screen Recording granted. Users who held the grant before the window existed never raised a prompt through it, so an update does not show it to them.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (49)
💤 Files with no reviewable changes (3)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughThe change adds a macOS permissions manager, a dedicated permissions window, and a renderer interface for checking and requesting Screen Recording, Accessibility, microphone, and camera permissions. It also adds a fresh-process Screen Recording probe, updates app entry points, and adds translations. ChangesmacOS Permissions Onboarding
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant PermissionsWindow
participant Preload
participant PermissionsIPC
participant MacPermissions
participant ScreenAccessProbe
PermissionsWindow->>Preload: permissions.get()
Preload->>PermissionsIPC: permissions:get
PermissionsIPC->>MacPermissions: read()
MacPermissions->>ScreenAccessProbe: readMacScreenCaptureAccess()
ScreenAccessProbe-->>MacPermissions: Screen Recording result
MacPermissions-->>PermissionsIPC: permissions snapshot
PermissionsIPC-->>Preload: permissions snapshot
Preload-->>PermissionsWindow: permissions snapshot
Suggested reviewers: Merge Risk: ⚪ Minimal · up to The change adds a macOS permissions onboarding window and moves the Screen Recording and Accessibility prompts into it. No concrete defect was found in the permission reads, the IPC flow, or the launch behavior. The helper used to read Screen Recording status is packaged where the app looks for it. The change looks ready to merge, with normal testing on older macOS versions. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 36.11% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 36 functions across 15 files. (31 skipped: 31 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
…lert needs
From macOS 15 every ScreenCaptureKit app outside Apple's picker gets an
alert asking whether it may bypass the system private window picker,
again from time to time. The window's line about it ('macOS will
occasionally ask you to confirm this access') was too vague for anyone
to connect it with that alert when it arrived.
The line now describes the alert, says to click Allow, and says it
comes back. It stays an explanation rather than a row: replayd shows the
alert on its own schedule whatever the last answer was, and its approval
is unreadable, so there is nothing to trigger or to show a status for.
On macOS an owned message box is a sheet, and AppKit dims the whole owning window behind it. The HUD is a ~907x696 transparent window around a ~60px bar, so every dialog it owned greyed a large invisible area of the desktop. On macOS the transparent overlays (HUD, source selector, countdown) no longer own their message boxes: an unowned alert is app-modal and sits above their floating level. Windows and Linux keep the owner, where an unowned dialog opens behind the always-on-top HUD. The two permission dialogs this first covered in ipc/handlers.ts are gone since #735 replaced them with the permissions window; what remains are main.ts's dialogs (About, updates, diagnostics), all of which fall back to the HUD as their owner.
On macOS an owned message box is a sheet, and AppKit dims the whole owning window behind it. The HUD is a ~907x696 transparent window around a ~60px bar, so every dialog it owned greyed a large invisible area of the desktop. On macOS the transparent overlays (HUD, source selector, countdown) no longer own their message boxes: an unowned alert is app-modal and sits above their floating level. Windows and Linux keep the owner, where an unowned dialog opens behind the always-on-top HUD. The two permission dialogs this first covered in ipc/handlers.ts are gone since #735 replaced them with the permissions window; what remains are main.ts's dialogs (About, updates, diagnostics), all of which fall back to the HUD as their owner.
On macOS an owned message box is a sheet, and AppKit dims the whole owning window behind it. The HUD is a ~907x696 transparent window around a ~60px bar, so every dialog it owned greyed a large invisible area of the desktop. On macOS the transparent overlays (HUD, source selector, countdown) no longer own their message boxes: an unowned alert is app-modal and sits above their floating level. Windows and Linux keep the owner, where an unowned dialog opens behind the always-on-top HUD. Backport to release/v1.13.0: #735 (permissions window) is not on this branch, so the two permission dialogs in ipc/handlers.ts still exist here and still own the HUD. They are routed through showMessageBoxOver too. (cherry picked from commit 1b1705e)
Why
On a fresh Mac, OpenScreen could not get Screen Recording through its own UI:
not-determined, which Chromium never reports:getMediaAccessStatus("screen")isCGPreflightScreenCaptureAccess(), a bool. So the first Record click went straight to an English "Open System Settings" dialog, and OpenScreen did not even appear in the list there yet. Credit to @heyitsR1 for this diagnosis in fix(macos): raise the Screen Recording prompt on first run #302.This PR replaces all of that with one permissions window. It supersedes #302.
What the user sees
At launch, while Screen Recording is missing, a window lists:
Each row shows its live status and the one action that can change it:
"Get started" unlocks once Screen Recording is granted. If the grant exists but this process cannot use it yet, the window offers Restart OpenScreen. After a relaunch mid-onboarding (our button, or System Settings' own "Quit & Reopen"), the window comes back to finish the remaining rows. It does not reappear once it has been closed with the grant in hand, and it never appears for users who already held the grant.
The window is also reachable from the app menu and the tray ("Permissions…"). The Record button and the editable-cursor check open it instead of the two hard-coded English dialogs.
How
electron/permissions/macPermissions.tsholds the pure logic (statuses, requests, when to show at launch), with injected dependencies and unit tests.openscreen-screencapturekit-helper --screen-access-status, from fix(macos): raise the Screen Recording prompt on first run #302). A fresh process has no stale cache.userData/permissions.json), as Kap and Cap do.desktopCapturer), so TCC files the grant under the app bundle.com.apple.preference.security?Privacy_*form, which opens the right pane from macOS 13 to 26.replaydshows it on its own schedule whatever the last answer was (so the app cannot raise it on demand), and its approval store is unreadable (so there is no status to show). Removing it for good would takeSCContentSharingPicker, which is being studied for the follow-up.Removed: the launch-time microphone request, the renderer's 6 s retry loop (
openSourceSelectorFlow), therequest-screen-accesschannel, and both English dialogs.Strings: all 15 locales.
Verified
Packaged, Developer-ID-signed build on macOS 26.5, fresh profile, TCC reset beforehand:
vitest(full suite),tsc,biome,docs:check,i18n-checkNot verified on macOS 13, 14 or 15. The per-version behaviour (deep links, prompt wording, the 15+ reminder) relies on Apple's documentation and public reports.
Follow-up
A second PR will let the ScreenCaptureKit helper list sources for the picker. A grant then works immediately, and the Restart step disappears except as a fallback.
Summary by CodeRabbit