https://github.com/jaywcjlove/PermissionFlow
A macOS library for guiding users through permission setup with System Settings deeplinks and drag-to-authorize support.
https://github.com/jaywcjlove/PermissionFlow
accessibility accessibility-permission appkit jaywcjlove macos permission-manager permissions sandbox sandbox-permissions swift swift-package
Last synced: 1 day ago
JSON representation
A macOS library for guiding users through permission setup with System Settings deeplinks and drag-to-authorize support.
- Host: GitHub
- URL: https://github.com/jaywcjlove/PermissionFlow
- Owner: jaywcjlove
- License: mit
- Created: 2026-04-17T01:54:17.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-05-26T17:21:05.000Z (about 1 month ago)
- Last Synced: 2026-05-27T05:38:18.178Z (about 1 month ago)
- Topics: accessibility, accessibility-permission, appkit, jaywcjlove, macos, permission-manager, permissions, sandbox, sandbox-permissions, swift, swift-package
- Language: Swift
- Homepage:
- Size: 117 KB
- Stars: 290
- Watchers: 2
- Forks: 14
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
- awesome-swift - PermissionFlow - Swift Package for macOS privacy authorization guidance and system settings deeplinks. (Security)
README
[中文](./README.zh.md) • [Installation](#installation) • [Public API](#public-api) • [System Settings URL Scheme](#system-settings-url-scheme)
PermissionFlow
===

`PermissionFlow` is a macOS permission-guidance library that opens the target `System Settings` privacy pane and, for supported drag-based authorization pages, shows a floating panel that follows the System Settings window and lets users drag the current `.app` into the permission list. It also includes `SystemSettingsKit` for strongly typed deeplinks into `System Settings` pages and subsections.
- `PermissionFlow`: macOS-only floating guidance for drag-based privacy authorization
- `SystemSettingsKit`: typed Settings deeplinks for macOS, with partial iOS support
It opens the correct privacy page automatically and, for panes that support drag-based authorization, shows a floating helper panel that follows the `System Settings` window and lets the user drag the current `.app` bundle into the permission list.
## Features
- **Real-time permission status display**: Buttons automatically show whether permissions are granted with visual feedback (green checkmark for granted, blue arrow for not granted)
- Opens the target `System Settings` privacy pane automatically
- Animates the floating panel from the click position to the `System Settings` window
- Follows the `System Settings` window while it moves
- Shows the current app as a native drag source
- Keeps only one active floating panel at a time
- Closes the floating panel automatically when `System Settings` closes
- Supports adaptive floating panel height based on content
- **Intelligent permission detection**: Uses official Apple APIs for accurate permission status checking without triggering system prompts
## Requirements
- macOS 13+
- Swift 6 package toolchain
- SwiftUI + AppKit host application
## Installation
Add the package to your app:
```swift
dependencies: [
.package(url: "https://github.com/jaywcjlove/PermissionFlow.git", from: "1.0.0")
]
```
The package URL and installation entry stay the same as before. What changed is the product layout: permission status detection for some panes is now split into optional extensions instead of being linked by default.
This package now exposes these library products:
- `PermissionFlow`: floating authorization guidance for supported privacy panes on macOS
- `SystemSettingsKit`: reusable deeplink API for arbitrary System Settings pages
- `PermissionFlowStatusStore`: injectable SwiftUI environment status store for reading permission state from any view
- `PermissionFlowExtendedStatus`: one-stop optional status detection for `.bluetooth`, `.inputMonitoring`, `.mediaAppleMusic`, and `.screenRecording`
- `PermissionFlowBluetoothStatus`: optional status detection for `.bluetooth`
- `PermissionFlowMediaStatus`: optional status detection for `.mediaAppleMusic`
- `PermissionFlowInputMonitoringStatus`: optional status detection for `.inputMonitoring`
- `PermissionFlowScreenRecordingStatus`: optional status detection for `.screenRecording`
Then add the product you need to your target:
```swift
.target(
name: "YourApp",
dependencies: [
.product(name: "PermissionFlow", package: "PermissionFlow"),
.product(name: "SystemSettingsKit", package: "PermissionFlow")
]
)
```
If you want status detection for `.bluetooth`, `.inputMonitoring`, `.mediaAppleMusic`, and `.screenRecording`, add the optional extension product as well:
```swift
.target(
name: "YourApp",
dependencies: [
.product(name: "PermissionFlow", package: "PermissionFlow"),
.product(name: "PermissionFlowExtendedStatus", package: "PermissionFlow")
]
)
```
You can also depend on only the specific extension products you need:
```swift
.product(name: "PermissionFlowBluetoothStatus", package: "PermissionFlow")
.product(name: "PermissionFlowMediaStatus", package: "PermissionFlow")
.product(name: "PermissionFlowInputMonitoringStatus", package: "PermissionFlow")
.product(name: "PermissionFlowScreenRecordingStatus", package: "PermissionFlow")
```
Why this split matters:
- Apps that only use `PermissionFlow` keep the original core integration and do not need to link optional status-detection modules by default.
- This reduces unnecessary compile-time and link-time dependencies such as `CoreBluetooth`, `MusicKit`, and `Carbon` when those permission states are not needed.
- In practice, this usually keeps the final app product cleaner and can reduce the amount of optional code that ends up linked into your binary.
Platform support:
- `PermissionFlow`: `macOS 13+`
- `SystemSettingsKit`: `macOS 13+`, `iOS 16+`
`SystemSettingsKit` is intentionally partial on iOS. The macOS deeplink-based pane and anchor APIs remain macOS-only, while iOS only exposes destinations that are publicly supported by UIKit, such as the current app's Settings page.
## Supported Permission Panes
`PermissionFlow` covers these privacy panes. Most use the floating drag-and-drop authorization workflow; `.microphone` uses the system microphone prompt instead.
- `.accessibility`: Opens `Privacy & Security > Accessibility`. ✅ **Status Detection Supported**
- `.fullDiskAccess`: Opens `Privacy & Security > Full Disk Access`. ✅ **Status Detection Supported**
- `.inputMonitoring`: Opens `Privacy & Security > Input Monitoring`. ✅ **Status Detection Supported**
- `.screenRecording`: Opens `Privacy & Security > Screen Recording`. ✅ **Status Detection Supported**
- `.microphone`: Requests microphone authorization and opens `Privacy & Security > Microphone` when settings access is needed. ✅ **Status Detection Supported**
- `.bluetooth`: Opens `Privacy & Security > Bluetooth`. ✅ **Supports status detection**
- `.mediaAppleMusic`: Opens `Privacy & Security > Media & Apple Music`. ✅ **Supports status detection**
- `.appManagement`: Opens `Privacy & Security > App Management`. ⚠️ Status detection not available
- `.developerTools`: Opens `Privacy & Security > Developer Tools`. ⚠️ Status detection not available
**Permission Status Display**: For supported permissions, `PermissionFlowButton` automatically displays the current authorization status:
- ✅ **Granted**: Green checkmark icon with "Granted" text
- ➡️ **Not Granted**: Blue arrow icon with "Grant" text
- Built into `PermissionFlow`: `.accessibility`, `.fullDiskAccess`, `.microphone`
- Available through optional status extensions: `.bluetooth`, `.inputMonitoring`, `.mediaAppleMusic`, `.screenRecording`
- 🔄 **Checking**: Clock icon with "Checking..." text
- ❓ **Unknown**: Blue arrow icon with "Open" text (for unsupported detection)
For every other `System Settings` page or privacy subsection, use `SystemSettingsKit`.
## Info.plist Privacy Descriptions
Permissions that trigger Apple's system privacy prompt must include the matching usage description in the host app's `Info.plist`. If the host macOS app uses App Sandbox, also enable the matching entitlement in **Signing & Capabilities > App Sandbox**.
### Microphone
Use this when requesting `.microphone` or calling Apple's microphone authorization APIs.
```xml
NSMicrophoneUsageDescription
This app needs microphone access for audio recording.
```
For sandboxed macOS apps, turn on `Audio Input`, or add:
```xml
com.apple.security.device.audio-input
```
### Camera
Use this when requesting camera access.
```xml
NSCameraUsageDescription
This app needs camera access for video capture.
```
For sandboxed macOS apps, turn on `Camera`, or add:
```xml
com.apple.security.device.camera
```
### Apple Events
Use this when your app sends Apple Events, such as automating or controlling another app.
```xml
NSAppleEventsUsageDescription
This app needs to control other apps for authorization guidance.
```
For sandboxed macOS apps, turn on `Apple Events`, or add:
```xml
com.apple.security.automation.apple-events
```
## Quick Start
### SwiftUI button
```swift
import PermissionFlow
import SwiftUI
struct ContentView: View {
var body: some View {
PermissionFlowButton(
title: "Grant Accessibility",
pane: .accessibility,
suggestedAppURLs: [Bundle.main.bundleURL]
)
}
}
```
### Enable optional status detection
To enable status detection for `.bluetooth`, `.inputMonitoring`, `.mediaAppleMusic`, and `.screenRecording`, add the optional extension products and register them once at app startup:
```swift
import PermissionFlowExtendedStatus
import SwiftUI
@main
struct MyApp: App {
init() {
PermissionFlowExtendedStatus.register()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
```
### Inject a status store at app startup
If you want to read permission state from any SwiftUI view, add the `PermissionFlowStatusStore` product:
```swift
.product(name: "PermissionFlow", package: "PermissionFlow"),
.product(name: "PermissionFlowStatusStore", package: "PermissionFlow")
```
Then create and inject the store at the app entry point:
```swift
import PermissionFlow
import PermissionFlowStatusStore
import SwiftUI
@main
struct MyApp: App {
@StateObject private var permissionStatusStore = PermissionFlowStatusStore()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(permissionStatusStore)
}
}
}
```
Read it from any child view:
```swift
import PermissionFlow
import PermissionFlowStatusStore
import SwiftUI
struct PermissionBadge: View {
@EnvironmentObject private var permissionStatusStore: PermissionFlowStatusStore
var body: some View {
Text(title(for: permissionStatusStore.state(for: .accessibility)))
.onAppear {
permissionStatusStore.refresh(.accessibility)
}
}
private func title(for state: PermissionAuthorizationState) -> String {
switch state {
case .granted:
"Granted"
case .notGranted:
"Not Granted"
case .unknown:
"Unknown"
case .checking:
"Checking"
}
}
}
```
`PermissionFlowStatusStore` tracks `PermissionFlowPane.allCases` by default and refreshes automatically when the app becomes active again. You can also track only selected panes:
```swift
@StateObject private var permissionStatusStore = PermissionFlowStatusStore(
panes: [.accessibility, .fullDiskAccess, .screenRecording]
)
```
`PermissionFlowStatusStore` does not decide whether a pane is detectable by itself; it reads the providers currently registered in `PermissionStatusRegistry`. Current support is:
| Pane | Detectable by default | Requires extra registration | Not reliably detectable |
| --- | --- | --- | --- |
| `.accessibility` | ✅ | | |
| `.fullDiskAccess` | ✅ | | |
| `.microphone` | ✅ | | |
| `.bluetooth` | | ✅ | |
| `.inputMonitoring` | | ✅ | |
| `.mediaAppleMusic` | | ✅ | |
| `.screenRecording` | | ✅ | |
| `.appManagement` | | | ✅ |
| `.developerTools` | | | ✅ |
For panes that are not reliably detectable, `state(for:)` usually returns `.unknown`.
Note: `PermissionFlowStatusStore` is only the state container. Optional panes such as `.inputMonitoring`, `.screenRecording`, `.bluetooth`, and `.mediaAppleMusic` still need their status providers registered first. In other words, `register()` and `PermissionFlowStatusStore` are two separate steps:
```swift
import PermissionFlowInputMonitoringStatus
import PermissionFlowStatusStore
import SwiftUI
@main
struct MyApp: App {
@StateObject private var permissionStatusStore: PermissionFlowStatusStore
init() {
PermissionFlowInputMonitoringStatus.register()
_permissionStatusStore = StateObject(
wrappedValue: PermissionFlowStatusStore()
)
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(permissionStatusStore)
}
}
}
```
To enable all optional status providers at once, register `PermissionFlowExtendedStatus`:
```swift
import PermissionFlowExtendedStatus
import PermissionFlowStatusStore
import SwiftUI
@main
struct MyApp: App {
@StateObject private var permissionStatusStore: PermissionFlowStatusStore
init() {
PermissionFlowExtendedStatus.register()
_permissionStatusStore = StateObject(
wrappedValue: PermissionFlowStatusStore()
)
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(permissionStatusStore)
}
}
}
```
### Manual status display
```swift
import AppKit
import PermissionFlow
import SwiftUI
struct ManualPermissionButton: View {
@StateObject private var controller = PermissionFlow.makeController()
@State private var authorizationState: PermissionAuthorizationState = .checking
let didBecomeActive = NotificationCenter.default.publisher(
for: NSApplication.didBecomeActiveNotification
)
var body: some View {
Button {
controller.authorize(
pane: .accessibility,
suggestedAppURLs: [Bundle.main.bundleURL],
sourceFrameInScreen: clickSourceFrameInScreen()
)
} label: {
Label {
Text(title(for: authorizationState))
} icon: {
let icon = PermissionFlowButtonState
.make(from: authorizationState).systemImage
Image(systemName: icon)
}
}
.onAppear(perform: refreshStatus)
.onReceive(didBecomeActive) { _ in
refreshStatus()
}
}
private func refreshStatus() {
let provider = PermissionStatusRegistry.provider(for: .accessibility)
authorizationState = provider.authorizationState()
}
private func title(for state: PermissionAuthorizationState) -> String {
switch state {
case .granted:
"Granted"
case .notGranted:
"Grant"
case .unknown:
"Open"
case .checking:
"Checking..."
}
}
private func clickSourceFrameInScreen() -> CGRect {
let mouse = NSEvent.mouseLocation
return CGRect(x: mouse.x - 16, y: mouse.y - 16, width: 32, height: 32)
}
}
```
### Manual controller usage
Use `PermissionFlowController` when you want to control the flow yourself:
```swift
import PermissionFlow
import SwiftUI
@MainActor
final class PermissionViewModel: ObservableObject {
private let controller = PermissionFlow.makeController()
func requestFullDiskAccess() {
controller.authorize(
pane: .fullDiskAccess,
suggestedAppURLs: [Bundle.main.bundleURL]
)
}
}
```
### Keep the launch animation
If you use `PermissionFlowButton`, the package captures the click position for you and the floating panel will animate from the button click to the `System Settings` window automatically.
If you call `PermissionFlowController.authorize(...)` manually, pass the click source frame yourself. Otherwise the panel will still appear, but it will skip the launch animation and jump directly to the target position.
```swift
import AppKit
import PermissionFlow
@MainActor
final class PermissionViewModel: ObservableObject {
private let controller = PermissionFlow.makeController()
func requestAccessibility() {
let mouseLocation = NSEvent.mouseLocation
let sourceFrame = CGRect(
x: mouseLocation.x - 16,
y: mouseLocation.y - 16,
width: 32,
height: 32
)
controller.authorize(
pane: .accessibility,
suggestedAppURLs: [Bundle.main.bundleURL],
sourceFrameInScreen: sourceFrame
)
}
}
```
## Public API
### `PermissionFlowButton`
Convenience SwiftUI button for launching a permission flow.
```swift
PermissionFlowButton(
title: "Open Screen Recording",
pane: .screenRecording,
suggestedAppURLs: [Bundle.main.bundleURL],
configuration: .init()
)
PermissionFlowButton(
pane: .screenRecording,
suggestedAppURLs: [Bundle.main.bundleURL],
configuration: .init()
) { buttonState in
Label("Open Screen Recording", systemImage: buttonState.systemImage)
.foregroundStyle(buttonState.isGranted ? .green : .primary)
}
```
### `PermissionFlow.makeController`
Creates a reusable controller:
```swift
let controller = PermissionFlow.makeController(
configuration: .init(
requiredAppURLs: [Bundle.main.bundleURL],
promptForAccessibilityTrust: false
)
)
```
### `PermissionFlowController`
Main entry points:
- `authorize(pane:suggestedAppURLs:sourceFrameInScreen:)`
- `showPanel()`
- `closePanel()`
- `resetDroppedApps()`
- `registerDroppedApp(_:)`
### `SystemSettings.open`
Open any System Settings page directly from a pane identifier and optional anchor:
```swift
import SystemSettingsKit
SystemSettings.open(
paneIdentifier: "com.apple.Wallpaper-Settings.extension"
)
SystemSettings.open(
paneIdentifier: "com.apple.settings.PrivacySecurity.extension",
anchor: "Privacy_Advertising"
)
```
You can also use `SystemSettingsDestination`:
```swift
import SystemSettingsKit
SystemSettings.open(.wallpaper)
SystemSettings.open(.privacy(anchor: .privacyAllFiles))
SystemSettings.open(.displays(anchor: .resolutionSection))
```
## System Settings URL Scheme
`SystemSettingsKit` exposes a lightweight API for opening arbitrary System Settings panes using the `x-apple.systempreferences:` URL scheme.
The behavior and examples are based on the identifiers and deeplink notes collected in [SystemSettings-URLs-macOS](https://github.com/jaywcjlove/SystemSettings-URLs-macOS/blob/main/README.md).
### URL format
```text
x-apple.systempreferences:
x-apple.systempreferences:?
```
Examples:
```text
x-apple.systempreferences:com.apple.Wallpaper-Settings.extension
x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Advertising
x-apple.systempreferences:com.apple.Wallpaper-Settings.extension?ScreenSaver
```
### Package type
```swift
public struct SystemSettingsDestination {
public let paneIdentifier: String
public let anchor: String?
public var url: URL { get }
}
```
### Convenience destinations
The package includes a few common helpers:
- `.wallpaper`
- `.displays`
- `.displays(anchor:)`
- `.accessibility`
- `.accessibility(anchor:)`
- `.bluetooth`
- `.loginItems`
- `.loginItems(anchor:)`
- `.loginItems(extensionPointIdentifier:)`
- `.wifi`
- `.wifi(anchor:)`
- `.vpn`
- `.vpn(anchor:)`
- `.privacy(anchor:)`
### Privacy anchors
For Privacy & Security subsections, use:
```swift
SystemSettings.open(.privacy(anchor: .privacyAllFiles))
SystemSettings.open(.privacy(anchor: .privacyAdvertising))
SystemSettings.open(.privacy(anchor: .privacyAccessibility))
SystemSettings.open(.privacy(anchor: .security))
```
The existing `PermissionFlowPane` type continues to handle the privacy pages used by the authorization workflow.
- `.appManagement`: Opens `Privacy & Security > App Management`.
- `.accessibility`: Opens `Privacy & Security > Accessibility`.
- `.bluetooth`: Opens `Privacy & Security > Bluetooth`.
- `.developerTools`: Opens `Privacy & Security > Developer Tools`.
- `.fullDiskAccess`: Opens `Privacy & Security > Full Disk Access`.
- `.inputMonitoring`: Opens `Privacy & Security > Input Monitoring`.
- `.mediaAppleMusic`: Opens `Privacy & Security > Media & Apple Music`.
- `.microphone`: Requests microphone authorization and opens `Privacy & Security > Microphone` when settings access is needed.
- `.screenRecording`: Opens `Privacy & Security > Screen Recording`.
Available typed privacy anchors and their destinations:
- `.advanced`: `Privacy & Security > Advanced`
- `.fileVault`: `Privacy & Security > FileVault`
- `.locationAccessReport`: `Privacy & Security > Location Access Report`
- `.lockdownMode`: `Privacy & Security > Lockdown Mode`
- `.privacyAccessibility`: `Privacy & Security > Accessibility`
- `.privacyAdvertising`: `Privacy & Security > Advertising`
- `.privacyAllFiles`: `Privacy & Security > Full Disk Access`
- `.privacyAnalytics`: `Privacy & Security > Analytics & Improvements`
- `.privacyAppBundles`: `Privacy & Security > App Management`
- `.privacyAudioCapture`: `Privacy & Security > Audio Capture`
- `.privacyAutomation`: `Privacy & Security > Automation`
- `.privacyBluetooth`: `Privacy & Security > Bluetooth`
- `.privacyCalendars`: `Privacy & Security > Calendars`
- `.privacyCamera`: `Privacy & Security > Camera`
- `.privacyContacts`: `Privacy & Security > Contacts`
- `.privacyDevTools`: `Privacy & Security > Developer Tools`
- `.privacyFilesAndFolders`: `Privacy & Security > Files & Folders`
- `.privacyFocus`: `Privacy & Security > Focus`
- `.privacyHomeKit`: `Privacy & Security > Home`
- `.privacyListenEvent`: `Privacy & Security > Input Monitoring`
- `.privacyLocationServices`: `Privacy & Security > Location Services`
- `.privacyMedia`: `Privacy & Security > Media & Apple Music`
- `.privacyMicrophone`: `Privacy & Security > Microphone`
- `.privacyMotion`: `Privacy & Security > Motion & Fitness`
- `.privacyNudityDetection`: `Privacy & Security > Sensitive Content Warning`
- `.privacyPasskeyAccess`: `Privacy & Security > Passkey Access`
- `.privacyPhotos`: `Privacy & Security > Photos`
- `.privacyReminders`: `Privacy & Security > Reminders`
- `.privacyRemoteDesktop`: `Privacy & Security > Remote Desktop`
- `.privacyScreenCapture`: `Privacy & Security > Screen Recording`
- `.privacySpeechRecognition`: `Privacy & Security > Speech Recognition`
- `.privacySystemServices`: `Privacy & Security > System Services`
- `.security`: `Privacy & Security > Security`
- `.securityImprovements`: `Privacy & Security > Security Improvements`
### Displays anchors
Displays now has a typed helper instead of raw string anchors:
```swift
SystemSettings.open(.displays)
SystemSettings.open(.displays(anchor: .arrangementSection))
SystemSettings.open(.displays(anchor: .resolutionSection))
SystemSettings.open(.displays(anchor: .nightShiftSection))
```
Available display anchors and their destinations:
- `.advancedSection`: `Displays > Advanced`
- `.ambienceSection`: `Displays > Ambience`
- `.arrangementSection`: `Displays > Arrangement`
- `.characteristicSection`: `Displays > Display Characteristics`
- `.displaysSection`: `Displays > Displays`
- `.miscellaneousSection`: `Displays > Miscellaneous`
- `.nightShiftSection`: `Displays > Night Shift`
- `.profileSection`: `Displays > Color Profile`
- `.resolutionSection`: `Displays > Resolution`
- `.sidecarSection`: `Displays > Sidecar`
### Login Items anchors
Login Items supports typed subsection anchors:
```swift
SystemSettings.open(.loginItems)
SystemSettings.open(.loginItems(anchor: .extensionItems))
SystemSettings.open(.loginItems(extensionPointIdentifier: .quickLookPreview))
SystemSettings.open(.loginItems(extensionPointIdentifier: .shareServices))
```
Available login item anchors and extension point helpers:
- `.extensionItems`: `Login Items > Extension Items`
- `.shareServices`: `extensionPointIdentifier=com.apple.share-services`
- `.actions`: `extensionPointIdentifier=com.apple.ui-services`
- `.photoEditing`: `extensionPointIdentifier=com.apple.photo-editing`
- `.spotlightImporter`: `extensionPointIdentifier=com.apple.spotlight.import`
- `.quickLookPreview`: `Login Items & Extensions > Extensions > Quick Look`, using `extensionPointIdentifier=com.apple.quicklook.preview`
- `.fileProvider`: `extensionPointIdentifier=com.apple.fileprovider-nonui`
- `.finderQuickActions`: `extensionPointIdentifier=com.apple.finder-quick-actions`
- `.touchBarQuickActions`: `extensionPointIdentifier=com.apple.touchbar-quick-actions`
- `.legacyDockTiles`: `extensionPointIdentifier=com.apple.extensionkit.legacy-plugins.docktiles`
- `.legacySpotlightImporter`: `extensionPointIdentifier=com.apple.extensionkit.legacy-plugins.spotlight-importer`
### Wi-Fi anchors
Wi-Fi supports typed subsection anchors:
```swift
SystemSettings.open(.wifi)
SystemSettings.open(.wifi(anchor: .generalMain))
SystemSettings.open(.wifi(anchor: .generalJoin))
SystemSettings.open(.wifi(anchor: .generalDetails))
SystemSettings.open(.wifi(anchor: .advanced))
```
Available Wi-Fi anchors and their destinations:
- `.advanced`: `Wi-Fi > Advanced`
- `.generalDetails`: `Wi-Fi > Details`
- `.generalJoin`: `Wi-Fi > Join`
- `.generalMain`: `Wi-Fi > Main`
### VPN anchors
VPN supports typed subsection anchors:
```swift
SystemSettings.open(.vpn)
SystemSettings.open(.vpn(anchor: .vpn))
SystemSettings.open(.vpn(anchor: .vpnOnDemand))
```
Available VPN anchors and their destinations:
- `.vpn`: `VPN > VPN`
- `.vpnOnDemand`: `VPN > VPN on Demand`
### Accessibility anchors
Accessibility has a typed helper for common sections and a raw string fallback for detailed control-level anchors:
```swift
SystemSettings.open(.accessibility)
SystemSettings.open(.accessibility(anchor: .display))
SystemSettings.open(.accessibility(anchor: .voiceOver))
SystemSettings.open(.accessibility(anchor: "AX_ZOOM_MAX_FACTOR"))
```
Available common accessibility anchors:
- `.display`: `Accessibility > Display`
- `.text`: `Accessibility > Text`
- `.pointer`: `Accessibility > Pointer`
- `.mouseAndTrackpad`: `Accessibility > Mouse & Trackpad`
- `.headphones`: `Accessibility > Headphones`
- `.voiceOver`: `Accessibility > VoiceOver`
- `.zoom`: `Accessibility > Zoom`
- `.displayFilters`: `Accessibility > Display Filters`
- `.backgroundSounds`: `Accessibility > Background Sounds`
- `.spokenContent`: `Accessibility > Spoken Content`
- `.captions`: `Accessibility > Captions`
- `.audio`: `Accessibility > Audio`
- `.descriptions`: `Accessibility > Audio Descriptions`
- `.keyboard`: `Accessibility > Keyboard`
- `.fullKeyboardAccess`: `Accessibility > Full Keyboard Access`
- `.stickyKeys`: `Accessibility > Sticky Keys`
- `.slowKeys`: `Accessibility > Slow Keys`
- `.virtualKeyboard`: `Accessibility > Accessibility Keyboard`
- `.voiceControl`: `Accessibility > Voice Control`
- `.switchControl`: `Accessibility > Switch Control`
- `.alternateMouseButtons`: `Accessibility > Alternate Mouse Buttons`
- `.headMouse`: `Accessibility > Head Pointer`
- `.mouseKeys`: `Accessibility > Mouse Keys`
- `.hoverText`: `Accessibility > Hover Text`
- `.hoverTyping`: `Accessibility > Hover Typing`
- `.liveSpeech`: `Accessibility > Live Speech`
- `.personalVoice`: `Accessibility > Personal Voice`
- `.siri`: `Accessibility > Siri`
- `.shortcut`: `Accessibility > Accessibility Shortcut`
## Configuration
```swift
let configuration = PermissionFlowConfiguration(
requiredAppURLs: [Bundle.main.bundleURL],
promptForAccessibilityTrust: false
)
```
### Notes
- `requiredAppURLs` preloads apps into the panel
- `promptForAccessibilityTrust` controls whether AX trust is actively prompted
## How It Works
1. Your app requests a permission pane.
2. `PermissionFlow` opens the matching `System Settings` page.
3. If that pane supports drag-based authorization, a floating panel appears.
4. The panel animates from the click location to the `System Settings` window.
5. The panel tracks the `System Settings` window position.
6. The user drags the current `.app` bundle into the permission list.
## Example
The repository includes an `Example` macOS app that demonstrates all supported permission flows.
## Notes and Limitations
- The floating helper is only shown for panes that support app-list style authorization.
- **Permission status detection**: Uses official Apple APIs (`CGPreflightListenEventAccess`, `CGPreflightScreenCaptureAccess`, `AXIsProcessTrusted`) for accurate status checking without triggering system prompts.
- **Status refresh**: Permission status is automatically refreshed when the app becomes active and when buttons appear on screen.
- `System Settings` behavior is controlled by macOS and may vary slightly by OS version.
- AX-based window tracking is used when available. Window Server frame lookup is used as fallback and bootstrap.
- The package does not bypass macOS security. It only guides the user through the system UI.
## License
Licensed under the MIT License.