PermissionFlow

2.1.0

A macOS library for guiding users through permission setup with System Settings deeplinks and drag-to-authorize support.
jaywcjlove/PermissionFlow

What's New

Using my app is also a way to support me:
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

中文InstallationPublic APISystem 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:

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
  • 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:

.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:

.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:

.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 8 privacy panes that support the floating drag-and-drop authorization workflow:

  • .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
  • .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
  • 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.

Quick Start

SwiftUI button

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:

import PermissionFlowExtendedStatus
import SwiftUI

@main
struct MyApp: App {
    init() {
        PermissionFlowExtendedStatus.register()
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Manual status display

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:

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.

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.

PermissionFlowButton(
    title: "Open Screen Recording",
    pane: .screenRecording,
    suggestedAppURLs: [Bundle.main.bundleURL],
    configuration: .init()
)

PermissionFlow.makeController

Creates a reusable controller:

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:

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:

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.

URL format

x-apple.systempreferences:<pane-identifier>
x-apple.systempreferences:<pane-identifier>?<anchor>

Examples:

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

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:)
  • .bluetooth
  • .loginItems
  • .privacy(anchor:)

Privacy anchors

For Privacy & Security subsections, use:

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 floating 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.
  • .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:

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

Configuration

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.

Description

  • Swift Tools 6.2.0
View More Packages from this Author

Dependencies

  • None
Last updated: Tue May 05 2026 21:19:10 GMT-0900 (Hawaii-Aleutian Daylight Time)