Skip to content

feat(permissions): native drag-to-grant permission flow#3898

Merged
louis030195 merged 12 commits into
screenpipe:mainfrom
divanshu-go:feat/permission-flow-panel
Jun 9, 2026
Merged

feat(permissions): native drag-to-grant permission flow#3898
louis030195 merged 12 commits into
screenpipe:mainfrom
divanshu-go:feat/permission-flow-panel

Conversation

@divanshu-go

@divanshu-go divanshu-go commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Description

Instead of just opening System Settings and hoping the user figures it out, screenpipe now shows a floating panel with the app icon the user can drag directly into the settings list. This covers accessibility, screen recording, and input monitoring. Calendar and microphone still use the native system prompt.

On top of the drag panel, the capture pipeline now gates on TCC status at startup — VisionManager and UI recording no longer start until the required permissions are granted, which prevents macOS from showing its own raw TCC padlock dialogs before onboarding runs.

What changed

  • New permission-flow.ts — wraps the native macOS drag panel plugin and all the screenpipe-specific controller logic in one file

  • All permission buttons, banners, onboarding, and recovery screens now go through the new flow

  • New Tauri command set_window_always_on_top_native — drops screenpipe below System Settings during the grant so it doesn't block the UI

  • Startup capture gate (capture_session.rs): VisionManager skips start when screen recording is not yet granted; UI recording skips start when accessibility is not yet granted. Both emit a permission_needed event instead. spawn_screenpipe is called again from onboarding after the user grants access.

  • permission_needed event (screenpipe-events, engine_events, use-permission-monitor): new event emitted by the Rust capture layer when capture is blocked. Flows Rust → engine_events → frontend. The frontend hook shows the recovery window when received outside onboarding/recovery paths. Suppressed during active onboarding so it doesn't interfere with the normal flow.

  • Accessibility included in recovery "all ok" check (permission-recovery/page.tsx): the recovery page no longer restarts screenpipe until screen recording, microphone, AND accessibility are all granted on macOS.

  • Tauri-specific screen recording probe (check_screen_recording_tauri): release builds on macOS 15+ use preflight-only — Apple changed CGWindowListCreateImage to return the app's own windows without Screen Recording permission (false positive). Debug builds use the full preflight || capture_probe chain since devs already have permissions and benefit from the false-negative protection.

  • CLI prompt_permissions: start_ui_recording gains a prompt_permissions bool. CLI passes true so native TCC dialogs appear as expected in a terminal. Tauri passes cfg!(debug_assertions) — release builds defer to the drag panel, dev builds use the native dialog as fallback since dragflow doesn't work in dev mode.

Edge cases fixed

Panel wouldn't close when app was already in the settings list.
macOS rejects duplicate drag-drops, so the plugin never got a "drop accepted" signal. We now clear the TCC entry before showing the panel so the drag always lands clean.

Panel stayed open after user manually toggled the permission.
Two issues here. While System Settings is open, the screenpipe window is occluded and polling was paused. Also, the watcher only started after the drag finished, so mid-drag toggles were missed entirely. Fixed by polling through occlusion and starting the watcher before the drag begins.

Panel showed on screenpipe windows, not just System Settings.
The native plugin tracks the host app and shows the panel whenever screenpipe is frontmost. Fixed by calling stopCurrentFlow() the moment screenpipe regains focus.

System Settings hidden behind screenpipe — screenpipe runs always-on-top, which blocked System Settings from appearing in front. Fixed by temporarily dropping the window level when opening settings and restoring it when screenpipe refocuses.

Native TCC padlock dialog appearing before onboarding on macOS 15+.
SCShareableContent::current() (called inside VisionManager startup) triggers Apple's native TCC dialog if screen recording hasn't been granted yet — even before the user reaches the permission step. Fixed by gating VisionManager and UI recording on TCC status before any ScreenCaptureKit API is touched.

CGWindowListCreateImage false positive on macOS 15+ Tauri.
Apple changed this API in Sequoia to return the app's own windows even without Screen Recording permission, making our 1×1 capture probe always return non-NULL. Fixed by splitting into check_screen_recording() (CLI — always uses full probe chain) and check_screen_recording_tauri() (Tauri release on macOS 15+ — preflight-only; dev builds still use full chain).

Test plan

  • App not in list → drag panel → drag in → closes ✓
  • App in list but disabled → drag panel → TCC reset → drag in → closes ✓
  • App in list but disabled → manually toggle → closes within 1.5s ✓
  • Switch back to screenpipe mid-flow → panel hides ✓
  • Switch to unrelated app → panel hides ✓
  • Calendar / microphone → native prompt, no drag panel ✓
  • Fresh install, no permissions → app starts without native TCC dialog appearing before onboarding ✓
  • Revoke screen recording mid-session → permission_needed fires → recovery window appears ✓
  • On onboarding path when permission_needed fires → recovery window does NOT appear ✓
  • Recovery page: all three (screen recording + mic + accessibility on macOS) must be granted before restart triggers ✓
  • CLI: native TCC dialogs for accessibility + input monitoring appear as expected ✓
  • macOS 15 release build: screen recording check uses preflight-only, no false positive from own windows ✓
  • macOS 14/13: full probe chain still used, no regression ✓

@divanshu-go divanshu-go force-pushed the feat/permission-flow-panel branch from 398b436 to 8dd5dd7 Compare June 7, 2026 18:52
@louis030195

Copy link
Copy Markdown
Collaborator

@divanshu-go can you share video of the onboarding or when user remove screen permission while using the app?

@louis030195 louis030195 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

apps/screenpipe-app-tauri/lib/utils/permission-flow.ts:123 — wrap in try/catch? on win/linux this will throw and bypass fallback
@divanshu-go nice! did you test on windows/linux? since the plugin is mac-only, we should make sure it doesnt crash on other platforms

generated by the screenpipe pr-review pipe (https://screenpi.pe), not written by a human — reply and tag @louis030195 if it got something wrong.

@divanshu-go divanshu-go force-pushed the feat/permission-flow-panel branch from 02c25b7 to b247714 Compare June 8, 2026 18:36

@louis030195 louis030195 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

wow this is a masterclass of a pr @divanshu-go . super thorough test plan and edge cases handling. lgtm !

generated by the screenpipe pr-review pipe (https://screenpi.pe), not written by a human — reply and tag @louis030195 if it got something wrong.

- Hide drag panel when screenpipe becomes frontmost: call
  stopCurrentFlow() in the onFocusChanged handler so the panel is only
  visible in system settings, not alongside screenpipe's own windows

- Poll even when document.hidden: added pollWhenHidden option to
  watchAuthorizationStatus and pass it true from watchUntilGrantedAndClose
  so the 1.5s interval keeps running while system settings occludes the
  screenpipe window — detects manual toggle grants without waiting for
  focus to return

- Immediate post-drag grant check: after startFlow resolves, call
  authorizationState() immediately to detect grants on apps already in
  the settings list that were re-enabled via drag (native plugin skips
  its own close signal in this case)

- Start watcher before startFlow: watchUntilGrantedAndClose now runs
  before await startFlow so a manual toggle while the drag panel is
  still open is caught by the 1.5s poller

- Force-close flow resource on grant: stopActiveFlow now calls both
  stopCurrentFlow() (soft) and flow.close() (destroys Swift controller)
  ensuring the panel disappears even when the plugin's drag state
  machine is stuck

- Reset stale TCC entry before drag: calls resetPermission before
  showing the drag panel so macOS does not reject the drop as a
  duplicate when screenpipe is already in the settings list but disabled
…vent

- Gate VisionManager start on screen recording permission to prevent
  SCShareableContent from triggering native TCC dialog before onboarding
- Gate UI recording start on accessibility permission; skip with warning
  when not yet granted
- Add permission_needed event (Rust → engine_events → frontend) so the
  recovery window can surface when capture is blocked at startup
- Suppress permission_needed during active onboarding in handle_needed
- Add prompt_permissions param to start_ui_recording: CLI passes true
  (native TCC dialogs expected), Tauri passes cfg!(debug_assertions)
  so dev builds get the native dialog fallback when dragflow unavailable
- Fix check_screen_recording_tauri: debug builds use full probe chain on
  macOS 15+ (capture_probe safe in dev), release skips it to avoid the
  false-positive from own-window compositor change in Sequoia
- Add com.apple.security.automation.apple-events entitlement so the
  Automation TCC prompt surfaces for browser URL capture
…macOS target

- Reintroduced the tauri-plugin-permission-flow for macOS, ensuring it is only initialized on that platform.
- Removed the global initialization from the main application setup to streamline platform-specific configurations.
@divanshu-go divanshu-go force-pushed the feat/permission-flow-panel branch 2 times, most recently from 5ab1bae to e7d35d4 Compare June 9, 2026 17:07
@divanshu-go divanshu-go force-pushed the feat/permission-flow-panel branch from e7d35d4 to a2f11ce Compare June 9, 2026 17:15
tauri-plugin-permission-flow failed to compile on Windows/Linux because the
non-macOS stub handle wasn't generic over the runtime nor a `tauri::Resource`,
and the macOS-only `permission_flow` error variants weren't cfg-gated. Point
the dependency at the fixed fork commit so non-macOS builds succeed.
@divanshu-go

divanshu-go commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

@louis030195

Onboarding

result1780942224.mp4

Permission Recovery

result1781036366.mp4

@louis030195 louis030195 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

great work! @divanshu-go windows/linux builds passing now is amazing. ill merge when CI is green

generated by the screenpipe pr-review pipe (https://screenpi.pe), not written by a human — reply and tag @louis030195 if it got something wrong.

@louis030195 louis030195 merged commit e63e248 into screenpipe:main Jun 9, 2026
12 checks passed
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