An open API service indexing awesome lists of open source software.

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.

Awesome Lists containing this project

README

          


Using my app is also a way to support me:


Zipora: Zip/RAR/7Z Unarchiver
Scap: Screenshot & Markup Edit
Screen Test
Deskmark
Keyzer
Vidwall Hub
VidCrop
Vidwall
Mousio Hint
Mousio
Musicer
Audioer
FileSentinel
FocusCursor
Videoer
KeyClicker
DayBar
Iconed
Menuist
Quick RSS
Quick RSS
Web Serve
Copybook Generator
DevTutor for SwiftUI
RegexMate
Time Passage
Iconize Folder
Textsound Saver
Create Custom Symbols
DevHub
Resume Revise
Palette Genius
Symbol Scribe


[中文](./README.zh.md) • [Installation](#installation) • [Public API](#public-api) • [System Settings URL Scheme](#system-settings-url-scheme)


PermissionFlow
===

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.