{"id":50801772,"url":"https://github.com/jaywcjlove/PermissionFlow","last_synced_at":"2026-06-30T03:00:36.204Z","repository":{"id":352229339,"uuid":"1213047906","full_name":"jaywcjlove/PermissionFlow","owner":"jaywcjlove","description":"A macOS library for guiding users through permission setup with System Settings deeplinks and drag-to-authorize support.","archived":false,"fork":false,"pushed_at":"2026-05-26T17:21:05.000Z","size":120,"stargazers_count":290,"open_issues_count":5,"forks_count":14,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-05-27T05:38:18.178Z","etag":null,"topics":["accessibility","accessibility-permission","appkit","jaywcjlove","macos","permission-manager","permissions","sandbox","sandbox-permissions","swift","swift-package"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jaywcjlove.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["jaywcjlove"]}},"created_at":"2026-04-17T01:54:17.000Z","updated_at":"2026-05-27T04:19:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jaywcjlove/PermissionFlow","commit_stats":null,"previous_names":["jaywcjlove/sandboxdrop","jaywcjlove/permissionflow"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/jaywcjlove/PermissionFlow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaywcjlove%2FPermissionFlow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaywcjlove%2FPermissionFlow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaywcjlove%2FPermissionFlow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaywcjlove%2FPermissionFlow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jaywcjlove","download_url":"https://codeload.github.com/jaywcjlove/PermissionFlow/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaywcjlove%2FPermissionFlow/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34950330,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-30T02:00:05.919Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["accessibility","accessibility-permission","appkit","jaywcjlove","macos","permission-manager","permissions","sandbox","sandbox-permissions","swift","swift-package"],"created_at":"2026-06-12T21:00:28.531Z","updated_at":"2026-06-30T03:00:36.198Z","avatar_url":"https://github.com/jaywcjlove.png","language":"Swift","funding_links":["https://github.com/sponsors/jaywcjlove"],"categories":["Security"],"sub_categories":[],"readme":"\u003cdiv align=\"left\"\u003e\n  \u003csup\u003eUsing \u003ca href=\"https://wangchujiang.com/#/app\" target=\"_blank\"\u003emy app\u003c/a\u003e is also a way to \u003ca href=\"https://wangchujiang.com/#/sponsor\" target=\"_blank\"\u003esupport\u003c/a\u003e me:\u003c/sup\u003e\n  \u003cbr\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6766860898\" title=\"Zipora: Zip/RAR/7Z Unarchiver\"\u003e\u003cimg alt=\"Zipora: Zip/RAR/7Z Unarchiver\" height=\"52\" src=\"https://wangchujiang.com/appicon/zipora.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6758053530\" title=\"Scap: Screenshot \u0026 Markup Edit for macOS\"\u003e\u003cimg alt=\"Scap: Screenshot \u0026 Markup Edit\" height=\"52\" src=\"https://wangchujiang.com/appicon/scap.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6757317079\" title=\"Screen Test for macOS\"\u003e\u003cimg alt=\"Screen Test\" height=\"52\" src=\"https://wangchujiang.com/appicon/screen-test.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6755948110\" title=\"Deskmark for macOS\"\u003e\u003cimg alt=\"Deskmark\" height=\"52\" src=\"https://wangchujiang.com/appicon/deskmark.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6500434773\" title=\"Keyzer for macOS\"\u003e\u003cimg alt=\"Keyzer\" height=\"52\" src=\"https://wangchujiang.com/appicon/keyzer.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://github.com/jaywcjlove/vidwall-hub\" title=\"Vidwall Hub for macOS\"\u003e\u003cimg alt=\"Vidwall Hub\" height=\"52\" src=\"https://wangchujiang.com/appicon/vidwall-hub.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6752624705\" title=\"VidCrop for macOS\"\u003e\u003cimg alt=\"VidCrop\" height=\"52\" src=\"https://wangchujiang.com/appicon/vidcrop.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6747587746\" title=\"Vidwall for macOS\"\u003e\u003cimg alt=\"Vidwall\" height=\"52\" src=\"https://wangchujiang.com/appicon/vidwall.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://wangchujiang.com/mousio-hint/\" title=\"Mousio Hint for macOS\"\u003e\u003cimg alt=\"Mousio Hint\" height=\"52\" src=\"https://wangchujiang.com/appicon/mousio-hint.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6746747327\" title=\"Mousio for macOS\"\u003e\u003cimg alt=\"Mousio\" height=\"52\" src=\"https://wangchujiang.com/appicon/mousio.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6745227444\" title=\"Musicer for macOS\"\u003e\u003cimg alt=\"Musicer\" height=\"52\" src=\"https://wangchujiang.com/appicon/musicer.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6743841447\" title=\"Audioer for macOS\"\u003e\u003cimg alt=\"Audioer\" height=\"52\" src=\"https://wangchujiang.com/appicon/audioer.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6744690194\" title=\"FileSentinel for macOS\"\u003e\u003cimg alt=\"FileSentinel\" height=\"52\" src=\"https://wangchujiang.com/appicon/file-sentinel.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6743495172\" title=\"FocusCursor for macOS\"\u003e\u003cimg alt=\"FocusCursor\" height=\"52\" src=\"https://wangchujiang.com/appicon/focus-cursor.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6742680573\" title=\"Videoer for macOS\"\u003e\u003cimg alt=\"Videoer\" height=\"52\" src=\"https://wangchujiang.com/appicon/videoer.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6740425504\" title=\"KeyClicker for macOS\"\u003e\u003cimg alt=\"KeyClicker\" height=\"52\" src=\"https://wangchujiang.com/appicon/key-clicker.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6739052447\" title=\"DayBar for macOS\"\u003e\u003cimg alt=\"DayBar\" height=\"52\" src=\"https://wangchujiang.com/appicon/daybar.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6739444407\" title=\"Iconed for macOS\"\u003e\u003cimg alt=\"Iconed\" height=\"52\" src=\"https://wangchujiang.com/appicon/iconed.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6737160756\" title=\"Menuist for macOS\"\u003e\u003cimg alt=\"Menuist\" height=\"52\" src=\"https://wangchujiang.com/appicon/rightmenu-master.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6723903021\" title=\"Paste Quick for macOS\"\u003e\u003cimg alt=\"Quick RSS\" height=\"52\" src=\"https://wangchujiang.com/appicon/paste-quick.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6670696072\u0026platform=mac\" title=\"Quick RSS for macOS/iOS\"\u003e\u003cimg alt=\"Quick RSS\" height=\"52\" src=\"https://wangchujiang.com/appicon/quick-rss.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6670167443\" title=\"Web Serve for macOS\"\u003e\u003cimg alt=\"Web Serve\" height=\"52\" src=\"https://wangchujiang.com/appicon/web-serve.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6503953628\u0026platform=mac\" title=\"Copybook Generator for macOS/iOS\"\u003e\u003cimg alt=\"Copybook Generator\" height=\"52\" src=\"https://wangchujiang.com/appicon/copybook-generator.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6471227008\u0026platform=mac\" title=\"DevTutor for macOS/iOS\"\u003e\u003cimg alt=\"DevTutor for SwiftUI\" height=\"52\" src=\"https://wangchujiang.com/appicon/devtutor.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6479819388\u0026platform=mac\" title=\"RegexMate for macOS/iOS\"\u003e\u003cimg alt=\"RegexMate\" height=\"52\" src=\"https://wangchujiang.com/appicon/regex-mate.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6479194014\u0026platform=mac\" title=\"Time Passage for macOS/iOS\"\u003e\u003cimg alt=\"Time Passage\" height=\"52\" src=\"https://wangchujiang.com/appicon/time-passage.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6478772538\" title=\"IconizeFolder for macOS\"\u003e\u003cimg alt=\"Iconize Folder\" height=\"52\" src=\"https://wangchujiang.com/appicon/iconize-folder.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6478511402\u0026platform=mac\" title=\"Textsound Saver for macOS/iOS\"\u003e\u003cimg alt=\"Textsound Saver\" height=\"52\" src=\"https://wangchujiang.com/appicon/textsound-saver.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6476924627\" title=\"Create Custom Symbols for macOS\"\u003e\u003cimg alt=\"Create Custom Symbols\" height=\"52\" src=\"https://wangchujiang.com/appicon/create-custom-symbols.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6476452351\" title=\"DevHub for macOS\"\u003e\u003cimg alt=\"DevHub\" height=\"52\" src=\"https://wangchujiang.com/appicon/devhub.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6476400184\" title=\"Resume Revise for macOS\"\u003e\u003cimg alt=\"Resume Revise\" height=\"52\" src=\"https://wangchujiang.com/appicon/resume-revise.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6472593276\" title=\"Palette Genius for macOS\"\u003e\u003cimg alt=\"Palette Genius\" height=\"52\" src=\"https://wangchujiang.com/appicon/palette-genius.png\"\u003e\u003c/a\u003e\n  \u003ca target=\"_blank\" href=\"https://jaywcjlove.github.io/maslink/?id=6470879005\" title=\"Symbol Scribe for macOS\"\u003e\u003cimg alt=\"Symbol Scribe\" height=\"52\" src=\"https://wangchujiang.com/appicon/symbol-scribe.png\"\u003e\u003c/a\u003e\n\u003c/div\u003e\n\u003chr\u003e\n\n[中文](./README.zh.md) • [Installation](#installation) • [Public API](#public-api) • [System Settings URL Scheme](#system-settings-url-scheme)\n\n\u003chr\u003e\n\nPermissionFlow\n===\n\n\u003cimg alt=\"PermissionFlow\" src=\"https://github.com/user-attachments/assets/9af78ef4-be7b-48b2-8651-3b8c42e0a9c8\" /\u003e\n\n`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.\n\n- `PermissionFlow`: macOS-only floating guidance for drag-based privacy authorization\n- `SystemSettingsKit`: typed Settings deeplinks for macOS, with partial iOS support\n\nIt 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.\n\n\n## Features\n\n- **Real-time permission status display**: Buttons automatically show whether permissions are granted with visual feedback (green checkmark for granted, blue arrow for not granted)\n- Opens the target `System Settings` privacy pane automatically\n- Animates the floating panel from the click position to the `System Settings` window\n- Follows the `System Settings` window while it moves\n- Shows the current app as a native drag source\n- Keeps only one active floating panel at a time\n- Closes the floating panel automatically when `System Settings` closes\n- Supports adaptive floating panel height based on content\n- **Intelligent permission detection**: Uses official Apple APIs for accurate permission status checking without triggering system prompts\n\n## Requirements\n\n- macOS 13+\n- Swift 6 package toolchain\n- SwiftUI + AppKit host application\n\n## Installation\n\nAdd the package to your app:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/jaywcjlove/PermissionFlow.git\", from: \"1.0.0\")\n]\n```\n\nThe 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.\n\nThis package now exposes these library products:\n\n- `PermissionFlow`: floating authorization guidance for supported privacy panes on macOS\n- `SystemSettingsKit`: reusable deeplink API for arbitrary System Settings pages\n- `PermissionFlowStatusStore`: injectable SwiftUI environment status store for reading permission state from any view\n- `PermissionFlowExtendedStatus`: one-stop optional status detection for `.bluetooth`, `.inputMonitoring`, `.mediaAppleMusic`, and `.screenRecording`\n- `PermissionFlowBluetoothStatus`: optional status detection for `.bluetooth`\n- `PermissionFlowMediaStatus`: optional status detection for `.mediaAppleMusic`\n- `PermissionFlowInputMonitoringStatus`: optional status detection for `.inputMonitoring`\n- `PermissionFlowScreenRecordingStatus`: optional status detection for `.screenRecording`\n\nThen add the product you need to your target:\n\n```swift\n.target(\n    name: \"YourApp\",\n    dependencies: [\n        .product(name: \"PermissionFlow\", package: \"PermissionFlow\"),\n        .product(name: \"SystemSettingsKit\", package: \"PermissionFlow\")\n    ]\n)\n```\n\nIf you want status detection for `.bluetooth`, `.inputMonitoring`, `.mediaAppleMusic`, and `.screenRecording`, add the optional extension product as well:\n\n```swift\n.target(\n    name: \"YourApp\",\n    dependencies: [\n        .product(name: \"PermissionFlow\", package: \"PermissionFlow\"),\n        .product(name: \"PermissionFlowExtendedStatus\", package: \"PermissionFlow\")\n    ]\n)\n```\n\nYou can also depend on only the specific extension products you need:\n\n```swift\n.product(name: \"PermissionFlowBluetoothStatus\", package: \"PermissionFlow\")\n.product(name: \"PermissionFlowMediaStatus\", package: \"PermissionFlow\")\n.product(name: \"PermissionFlowInputMonitoringStatus\", package: \"PermissionFlow\")\n.product(name: \"PermissionFlowScreenRecordingStatus\", package: \"PermissionFlow\")\n```\n\nWhy this split matters:\n\n- Apps that only use `PermissionFlow` keep the original core integration and do not need to link optional status-detection modules by default.\n- This reduces unnecessary compile-time and link-time dependencies such as `CoreBluetooth`, `MusicKit`, and `Carbon` when those permission states are not needed.\n- 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.\n\nPlatform support:\n\n- `PermissionFlow`: `macOS 13+`\n- `SystemSettingsKit`: `macOS 13+`, `iOS 16+`\n\n`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.\n\n## Supported Permission Panes\n\n`PermissionFlow` covers these privacy panes. Most use the floating drag-and-drop authorization workflow; `.microphone` uses the system microphone prompt instead.\n\n- `.accessibility`: Opens `Privacy \u0026 Security \u003e Accessibility`. ✅ **Status Detection Supported**\n- `.fullDiskAccess`: Opens `Privacy \u0026 Security \u003e Full Disk Access`. ✅ **Status Detection Supported**\n- `.inputMonitoring`: Opens `Privacy \u0026 Security \u003e Input Monitoring`. ✅ **Status Detection Supported**\n- `.screenRecording`: Opens `Privacy \u0026 Security \u003e Screen Recording`. ✅ **Status Detection Supported**\n- `.microphone`: Requests microphone authorization and opens `Privacy \u0026 Security \u003e Microphone` when settings access is needed. ✅ **Status Detection Supported**\n- `.bluetooth`: Opens `Privacy \u0026 Security \u003e Bluetooth`. ✅ **Supports status detection**\n- `.mediaAppleMusic`: Opens `Privacy \u0026 Security \u003e Media \u0026 Apple Music`. ✅ **Supports status detection**\n- `.appManagement`: Opens `Privacy \u0026 Security \u003e App Management`. ⚠️ Status detection not available\n- `.developerTools`: Opens `Privacy \u0026 Security \u003e Developer Tools`. ⚠️ Status detection not available\n\n**Permission Status Display**: For supported permissions, `PermissionFlowButton` automatically displays the current authorization status:\n- ✅ **Granted**: Green checkmark icon with \"Granted\" text\n- ➡️ **Not Granted**: Blue arrow icon with \"Grant\" text  \n- Built into `PermissionFlow`: `.accessibility`, `.fullDiskAccess`, `.microphone`\n- Available through optional status extensions: `.bluetooth`, `.inputMonitoring`, `.mediaAppleMusic`, `.screenRecording`\n- 🔄 **Checking**: Clock icon with \"Checking...\" text\n- ❓ **Unknown**: Blue arrow icon with \"Open\" text (for unsupported detection)\n\nFor every other `System Settings` page or privacy subsection, use `SystemSettingsKit`.\n\n## Info.plist Privacy Descriptions\n\nPermissions 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 \u0026 Capabilities \u003e App Sandbox**.\n\n### Microphone\n\nUse this when requesting `.microphone` or calling Apple's microphone authorization APIs.\n\n```xml\n\u003ckey\u003eNSMicrophoneUsageDescription\u003c/key\u003e\n\u003cstring\u003eThis app needs microphone access for audio recording.\u003c/string\u003e\n```\n\nFor sandboxed macOS apps, turn on `Audio Input`, or add:\n\n```xml\n\u003ckey\u003ecom.apple.security.device.audio-input\u003c/key\u003e\n\u003ctrue/\u003e\n```\n\n### Camera\n\nUse this when requesting camera access.\n\n```xml\n\u003ckey\u003eNSCameraUsageDescription\u003c/key\u003e\n\u003cstring\u003eThis app needs camera access for video capture.\u003c/string\u003e\n```\n\nFor sandboxed macOS apps, turn on `Camera`, or add:\n\n```xml\n\u003ckey\u003ecom.apple.security.device.camera\u003c/key\u003e\n\u003ctrue/\u003e\n```\n\n### Apple Events\n\nUse this when your app sends Apple Events, such as automating or controlling another app.\n\n```xml\n\u003ckey\u003eNSAppleEventsUsageDescription\u003c/key\u003e\n\u003cstring\u003eThis app needs to control other apps for authorization guidance.\u003c/string\u003e\n```\n\nFor sandboxed macOS apps, turn on `Apple Events`, or add:\n\n```xml\n\u003ckey\u003ecom.apple.security.automation.apple-events\u003c/key\u003e\n\u003ctrue/\u003e\n```\n\n## Quick Start\n\n### SwiftUI button\n\n```swift\nimport PermissionFlow\nimport SwiftUI\n\nstruct ContentView: View {\n    var body: some View {\n        PermissionFlowButton(\n            title: \"Grant Accessibility\",\n            pane: .accessibility,\n            suggestedAppURLs: [Bundle.main.bundleURL]\n        )\n    }\n}\n```\n\n### Enable optional status detection\n\nTo enable status detection for `.bluetooth`, `.inputMonitoring`, `.mediaAppleMusic`, and `.screenRecording`, add the optional extension products and register them once at app startup:\n\n```swift\nimport PermissionFlowExtendedStatus\nimport SwiftUI\n\n@main\nstruct MyApp: App {\n    init() {\n        PermissionFlowExtendedStatus.register()\n    }\n\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n        }\n    }\n}\n```\n\n### Inject a status store at app startup\n\nIf you want to read permission state from any SwiftUI view, add the `PermissionFlowStatusStore` product:\n\n```swift\n.product(name: \"PermissionFlow\", package: \"PermissionFlow\"),\n.product(name: \"PermissionFlowStatusStore\", package: \"PermissionFlow\")\n```\n\nThen create and inject the store at the app entry point:\n\n```swift\nimport PermissionFlow\nimport PermissionFlowStatusStore\nimport SwiftUI\n\n@main\nstruct MyApp: App {\n    @StateObject private var permissionStatusStore = PermissionFlowStatusStore()\n\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n                .environmentObject(permissionStatusStore)\n        }\n    }\n}\n```\n\nRead it from any child view:\n\n```swift\nimport PermissionFlow\nimport PermissionFlowStatusStore\nimport SwiftUI\n\nstruct PermissionBadge: View {\n    @EnvironmentObject private var permissionStatusStore: PermissionFlowStatusStore\n\n    var body: some View {\n        Text(title(for: permissionStatusStore.state(for: .accessibility)))\n            .onAppear {\n                permissionStatusStore.refresh(.accessibility)\n            }\n    }\n\n    private func title(for state: PermissionAuthorizationState) -\u003e String {\n        switch state {\n        case .granted:\n            \"Granted\"\n        case .notGranted:\n            \"Not Granted\"\n        case .unknown:\n            \"Unknown\"\n        case .checking:\n            \"Checking\"\n        }\n    }\n}\n```\n\n`PermissionFlowStatusStore` tracks `PermissionFlowPane.allCases` by default and refreshes automatically when the app becomes active again. You can also track only selected panes:\n\n```swift\n@StateObject private var permissionStatusStore = PermissionFlowStatusStore(\n    panes: [.accessibility, .fullDiskAccess, .screenRecording]\n)\n```\n\n`PermissionFlowStatusStore` does not decide whether a pane is detectable by itself; it reads the providers currently registered in `PermissionStatusRegistry`. Current support is:\n\n| Pane | Detectable by default | Requires extra registration | Not reliably detectable |\n| --- | --- | --- | --- |\n| `.accessibility` | ✅ |  |  |\n| `.fullDiskAccess` | ✅ |  |  |\n| `.microphone` | ✅ |  |  |\n| `.bluetooth` |  | ✅ |  |\n| `.inputMonitoring` |  | ✅ |  |\n| `.mediaAppleMusic` |  | ✅ |  |\n| `.screenRecording` |  | ✅ |  |\n| `.appManagement` |  |  | ✅ |\n| `.developerTools` |  |  | ✅ |\n\nFor panes that are not reliably detectable, `state(for:)` usually returns `.unknown`.\n\nNote: `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:\n\n```swift\nimport PermissionFlowInputMonitoringStatus\nimport PermissionFlowStatusStore\nimport SwiftUI\n\n@main\nstruct MyApp: App {\n    @StateObject private var permissionStatusStore: PermissionFlowStatusStore\n\n    init() {\n        PermissionFlowInputMonitoringStatus.register()\n        _permissionStatusStore = StateObject(\n            wrappedValue: PermissionFlowStatusStore()\n        )\n    }\n\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n                .environmentObject(permissionStatusStore)\n        }\n    }\n}\n```\n\nTo enable all optional status providers at once, register `PermissionFlowExtendedStatus`:\n\n```swift\nimport PermissionFlowExtendedStatus\nimport PermissionFlowStatusStore\nimport SwiftUI\n\n@main\nstruct MyApp: App {\n    @StateObject private var permissionStatusStore: PermissionFlowStatusStore\n\n    init() {\n        PermissionFlowExtendedStatus.register()\n        _permissionStatusStore = StateObject(\n            wrappedValue: PermissionFlowStatusStore()\n        )\n    }\n\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n                .environmentObject(permissionStatusStore)\n        }\n    }\n}\n```\n\n### Manual status display\n\n```swift\nimport AppKit\nimport PermissionFlow\nimport SwiftUI\n\nstruct ManualPermissionButton: View {\n    @StateObject private var controller = PermissionFlow.makeController()\n    @State private var authorizationState: PermissionAuthorizationState = .checking\n\n    let didBecomeActive = NotificationCenter.default.publisher(\n        for: NSApplication.didBecomeActiveNotification\n    )\n\n    var body: some View {\n        Button {\n            controller.authorize(\n                pane: .accessibility,\n                suggestedAppURLs: [Bundle.main.bundleURL],\n                sourceFrameInScreen: clickSourceFrameInScreen()\n            )\n        } label: {\n            Label {\n                Text(title(for: authorizationState))\n            } icon: {\n                let icon = PermissionFlowButtonState\n                    .make(from: authorizationState).systemImage\n                Image(systemName: icon)\n            }\n        }\n        .onAppear(perform: refreshStatus)\n        .onReceive(didBecomeActive) { _ in\n            refreshStatus()\n        }\n    }\n\n    private func refreshStatus() {\n        let provider = PermissionStatusRegistry.provider(for: .accessibility)\n        authorizationState = provider.authorizationState()\n    }\n\n    private func title(for state: PermissionAuthorizationState) -\u003e String {\n        switch state {\n        case .granted:\n            \"Granted\"\n        case .notGranted:\n            \"Grant\"\n        case .unknown:\n            \"Open\"\n        case .checking:\n            \"Checking...\"\n        }\n    }\n\n    private func clickSourceFrameInScreen() -\u003e CGRect {\n        let mouse = NSEvent.mouseLocation\n        return CGRect(x: mouse.x - 16, y: mouse.y - 16, width: 32, height: 32)\n    }\n}\n```\n\n### Manual controller usage\n\nUse `PermissionFlowController` when you want to control the flow yourself:\n\n```swift\nimport PermissionFlow\nimport SwiftUI\n\n@MainActor\nfinal class PermissionViewModel: ObservableObject {\n    private let controller = PermissionFlow.makeController()\n\n    func requestFullDiskAccess() {\n        controller.authorize(\n            pane: .fullDiskAccess,\n            suggestedAppURLs: [Bundle.main.bundleURL]\n        )\n    }\n}\n```\n\n### Keep the launch animation\n\nIf 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.\n\nIf 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.\n\n```swift\nimport AppKit\nimport PermissionFlow\n\n@MainActor\nfinal class PermissionViewModel: ObservableObject {\n    private let controller = PermissionFlow.makeController()\n\n    func requestAccessibility() {\n        let mouseLocation = NSEvent.mouseLocation\n        let sourceFrame = CGRect(\n            x: mouseLocation.x - 16,\n            y: mouseLocation.y - 16,\n            width: 32,\n            height: 32\n        )\n\n        controller.authorize(\n            pane: .accessibility,\n            suggestedAppURLs: [Bundle.main.bundleURL],\n            sourceFrameInScreen: sourceFrame\n        )\n    }\n}\n```\n\n## Public API\n\n### `PermissionFlowButton`\n\nConvenience SwiftUI button for launching a permission flow.\n\n```swift\nPermissionFlowButton(\n    title: \"Open Screen Recording\",\n    pane: .screenRecording,\n    suggestedAppURLs: [Bundle.main.bundleURL],\n    configuration: .init()\n)\n\nPermissionFlowButton(\n    pane: .screenRecording,\n    suggestedAppURLs: [Bundle.main.bundleURL],\n    configuration: .init()\n) { buttonState in\n    Label(\"Open Screen Recording\", systemImage: buttonState.systemImage)\n        .foregroundStyle(buttonState.isGranted ? .green : .primary)\n}\n```\n\n### `PermissionFlow.makeController`\n\nCreates a reusable controller:\n\n```swift\nlet controller = PermissionFlow.makeController(\n    configuration: .init(\n        requiredAppURLs: [Bundle.main.bundleURL],\n        promptForAccessibilityTrust: false\n    )\n)\n```\n\n### `PermissionFlowController`\n\nMain entry points:\n\n- `authorize(pane:suggestedAppURLs:sourceFrameInScreen:)`\n- `showPanel()`\n- `closePanel()`\n- `resetDroppedApps()`\n- `registerDroppedApp(_:)`\n\n### `SystemSettings.open`\n\nOpen any System Settings page directly from a pane identifier and optional anchor:\n\n```swift\nimport SystemSettingsKit\n\nSystemSettings.open(\n    paneIdentifier: \"com.apple.Wallpaper-Settings.extension\"\n)\n\nSystemSettings.open(\n    paneIdentifier: \"com.apple.settings.PrivacySecurity.extension\",\n    anchor: \"Privacy_Advertising\"\n)\n```\n\nYou can also use `SystemSettingsDestination`:\n\n```swift\nimport SystemSettingsKit\n\nSystemSettings.open(.wallpaper)\nSystemSettings.open(.privacy(anchor: .privacyAllFiles))\nSystemSettings.open(.displays(anchor: .resolutionSection))\n```\n\n## System Settings URL Scheme\n\n`SystemSettingsKit` exposes a lightweight API for opening arbitrary System Settings panes using the `x-apple.systempreferences:` URL scheme.\n\nThe 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).\n\n### URL format\n\n```text\nx-apple.systempreferences:\u003cpane-identifier\u003e\nx-apple.systempreferences:\u003cpane-identifier\u003e?\u003canchor\u003e\n```\n\nExamples:\n\n```text\nx-apple.systempreferences:com.apple.Wallpaper-Settings.extension\nx-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_Advertising\nx-apple.systempreferences:com.apple.Wallpaper-Settings.extension?ScreenSaver\n```\n\n### Package type\n\n```swift\npublic struct SystemSettingsDestination {\n    public let paneIdentifier: String\n    public let anchor: String?\n    public var url: URL { get }\n}\n```\n\n### Convenience destinations\n\nThe package includes a few common helpers:\n\n- `.wallpaper`\n- `.displays`\n- `.displays(anchor:)`\n- `.accessibility`\n- `.accessibility(anchor:)`\n- `.bluetooth`\n- `.loginItems`\n- `.loginItems(anchor:)`\n- `.loginItems(extensionPointIdentifier:)`\n- `.wifi`\n- `.wifi(anchor:)`\n- `.vpn`\n- `.vpn(anchor:)`\n- `.privacy(anchor:)`\n\n### Privacy anchors\n\nFor Privacy \u0026 Security subsections, use:\n\n```swift\nSystemSettings.open(.privacy(anchor: .privacyAllFiles))\nSystemSettings.open(.privacy(anchor: .privacyAdvertising))\nSystemSettings.open(.privacy(anchor: .privacyAccessibility))\nSystemSettings.open(.privacy(anchor: .security))\n```\n\nThe existing `PermissionFlowPane` type continues to handle the privacy pages used by the authorization workflow.\n\n- `.appManagement`: Opens `Privacy \u0026 Security \u003e App Management`.\n- `.accessibility`: Opens `Privacy \u0026 Security \u003e Accessibility`.\n- `.bluetooth`: Opens `Privacy \u0026 Security \u003e Bluetooth`.\n- `.developerTools`: Opens `Privacy \u0026 Security \u003e Developer Tools`.\n- `.fullDiskAccess`: Opens `Privacy \u0026 Security \u003e Full Disk Access`.\n- `.inputMonitoring`: Opens `Privacy \u0026 Security \u003e Input Monitoring`.\n- `.mediaAppleMusic`: Opens `Privacy \u0026 Security \u003e Media \u0026 Apple Music`.\n- `.microphone`: Requests microphone authorization and opens `Privacy \u0026 Security \u003e Microphone` when settings access is needed.\n- `.screenRecording`: Opens `Privacy \u0026 Security \u003e Screen Recording`.\n\nAvailable typed privacy anchors and their destinations:\n\n- `.advanced`: `Privacy \u0026 Security \u003e Advanced`\n- `.fileVault`: `Privacy \u0026 Security \u003e FileVault`\n- `.locationAccessReport`: `Privacy \u0026 Security \u003e Location Access Report`\n- `.lockdownMode`: `Privacy \u0026 Security \u003e Lockdown Mode`\n- `.privacyAccessibility`: `Privacy \u0026 Security \u003e Accessibility`\n- `.privacyAdvertising`: `Privacy \u0026 Security \u003e Advertising`\n- `.privacyAllFiles`: `Privacy \u0026 Security \u003e Full Disk Access`\n- `.privacyAnalytics`: `Privacy \u0026 Security \u003e Analytics \u0026 Improvements`\n- `.privacyAppBundles`: `Privacy \u0026 Security \u003e App Management`\n- `.privacyAudioCapture`: `Privacy \u0026 Security \u003e Audio Capture`\n- `.privacyAutomation`: `Privacy \u0026 Security \u003e Automation`\n- `.privacyBluetooth`: `Privacy \u0026 Security \u003e Bluetooth`\n- `.privacyCalendars`: `Privacy \u0026 Security \u003e Calendars`\n- `.privacyCamera`: `Privacy \u0026 Security \u003e Camera`\n- `.privacyContacts`: `Privacy \u0026 Security \u003e Contacts`\n- `.privacyDevTools`: `Privacy \u0026 Security \u003e Developer Tools`\n- `.privacyFilesAndFolders`: `Privacy \u0026 Security \u003e Files \u0026 Folders`\n- `.privacyFocus`: `Privacy \u0026 Security \u003e Focus`\n- `.privacyHomeKit`: `Privacy \u0026 Security \u003e Home`\n- `.privacyListenEvent`: `Privacy \u0026 Security \u003e Input Monitoring`\n- `.privacyLocationServices`: `Privacy \u0026 Security \u003e Location Services`\n- `.privacyMedia`: `Privacy \u0026 Security \u003e Media \u0026 Apple Music`\n- `.privacyMicrophone`: `Privacy \u0026 Security \u003e Microphone`\n- `.privacyMotion`: `Privacy \u0026 Security \u003e Motion \u0026 Fitness`\n- `.privacyNudityDetection`: `Privacy \u0026 Security \u003e Sensitive Content Warning`\n- `.privacyPasskeyAccess`: `Privacy \u0026 Security \u003e Passkey Access`\n- `.privacyPhotos`: `Privacy \u0026 Security \u003e Photos`\n- `.privacyReminders`: `Privacy \u0026 Security \u003e Reminders`\n- `.privacyRemoteDesktop`: `Privacy \u0026 Security \u003e Remote Desktop`\n- `.privacyScreenCapture`: `Privacy \u0026 Security \u003e Screen Recording`\n- `.privacySpeechRecognition`: `Privacy \u0026 Security \u003e Speech Recognition`\n- `.privacySystemServices`: `Privacy \u0026 Security \u003e System Services`\n- `.security`: `Privacy \u0026 Security \u003e Security`\n- `.securityImprovements`: `Privacy \u0026 Security \u003e Security Improvements`\n\n### Displays anchors\n\nDisplays now has a typed helper instead of raw string anchors:\n\n```swift\nSystemSettings.open(.displays)\nSystemSettings.open(.displays(anchor: .arrangementSection))\nSystemSettings.open(.displays(anchor: .resolutionSection))\nSystemSettings.open(.displays(anchor: .nightShiftSection))\n```\n\nAvailable display anchors and their destinations:\n\n- `.advancedSection`: `Displays \u003e Advanced`\n- `.ambienceSection`: `Displays \u003e Ambience`\n- `.arrangementSection`: `Displays \u003e Arrangement`\n- `.characteristicSection`: `Displays \u003e Display Characteristics`\n- `.displaysSection`: `Displays \u003e Displays`\n- `.miscellaneousSection`: `Displays \u003e Miscellaneous`\n- `.nightShiftSection`: `Displays \u003e Night Shift`\n- `.profileSection`: `Displays \u003e Color Profile`\n- `.resolutionSection`: `Displays \u003e Resolution`\n- `.sidecarSection`: `Displays \u003e Sidecar`\n\n### Login Items anchors\n\nLogin Items supports typed subsection anchors:\n\n```swift\nSystemSettings.open(.loginItems)\nSystemSettings.open(.loginItems(anchor: .extensionItems))\nSystemSettings.open(.loginItems(extensionPointIdentifier: .quickLookPreview))\nSystemSettings.open(.loginItems(extensionPointIdentifier: .shareServices))\n```\n\nAvailable login item anchors and extension point helpers:\n\n- `.extensionItems`: `Login Items \u003e Extension Items`\n- `.shareServices`: `extensionPointIdentifier=com.apple.share-services`\n- `.actions`: `extensionPointIdentifier=com.apple.ui-services`\n- `.photoEditing`: `extensionPointIdentifier=com.apple.photo-editing`\n- `.spotlightImporter`: `extensionPointIdentifier=com.apple.spotlight.import`\n- `.quickLookPreview`: `Login Items \u0026 Extensions \u003e Extensions \u003e Quick Look`, using `extensionPointIdentifier=com.apple.quicklook.preview`\n- `.fileProvider`: `extensionPointIdentifier=com.apple.fileprovider-nonui`\n- `.finderQuickActions`: `extensionPointIdentifier=com.apple.finder-quick-actions`\n- `.touchBarQuickActions`: `extensionPointIdentifier=com.apple.touchbar-quick-actions`\n- `.legacyDockTiles`: `extensionPointIdentifier=com.apple.extensionkit.legacy-plugins.docktiles`\n- `.legacySpotlightImporter`: `extensionPointIdentifier=com.apple.extensionkit.legacy-plugins.spotlight-importer`\n\n### Wi-Fi anchors\n\nWi-Fi supports typed subsection anchors:\n\n```swift\nSystemSettings.open(.wifi)\nSystemSettings.open(.wifi(anchor: .generalMain))\nSystemSettings.open(.wifi(anchor: .generalJoin))\nSystemSettings.open(.wifi(anchor: .generalDetails))\nSystemSettings.open(.wifi(anchor: .advanced))\n```\n\nAvailable Wi-Fi anchors and their destinations:\n\n- `.advanced`: `Wi-Fi \u003e Advanced`\n- `.generalDetails`: `Wi-Fi \u003e Details`\n- `.generalJoin`: `Wi-Fi \u003e Join`\n- `.generalMain`: `Wi-Fi \u003e Main`\n\n### VPN anchors\n\nVPN supports typed subsection anchors:\n\n```swift\nSystemSettings.open(.vpn)\nSystemSettings.open(.vpn(anchor: .vpn))\nSystemSettings.open(.vpn(anchor: .vpnOnDemand))\n```\n\nAvailable VPN anchors and their destinations:\n\n- `.vpn`: `VPN \u003e VPN`\n- `.vpnOnDemand`: `VPN \u003e VPN on Demand`\n\n### Accessibility anchors\n\nAccessibility has a typed helper for common sections and a raw string fallback for detailed control-level anchors:\n\n```swift\nSystemSettings.open(.accessibility)\nSystemSettings.open(.accessibility(anchor: .display))\nSystemSettings.open(.accessibility(anchor: .voiceOver))\nSystemSettings.open(.accessibility(anchor: \"AX_ZOOM_MAX_FACTOR\"))\n```\n\nAvailable common accessibility anchors:\n\n- `.display`: `Accessibility \u003e Display`\n- `.text`: `Accessibility \u003e Text`\n- `.pointer`: `Accessibility \u003e Pointer`\n- `.mouseAndTrackpad`: `Accessibility \u003e Mouse \u0026 Trackpad`\n- `.headphones`: `Accessibility \u003e Headphones`\n- `.voiceOver`: `Accessibility \u003e VoiceOver`\n- `.zoom`: `Accessibility \u003e Zoom`\n- `.displayFilters`: `Accessibility \u003e Display Filters`\n- `.backgroundSounds`: `Accessibility \u003e Background Sounds`\n- `.spokenContent`: `Accessibility \u003e Spoken Content`\n- `.captions`: `Accessibility \u003e Captions`\n- `.audio`: `Accessibility \u003e Audio`\n- `.descriptions`: `Accessibility \u003e Audio Descriptions`\n- `.keyboard`: `Accessibility \u003e Keyboard`\n- `.fullKeyboardAccess`: `Accessibility \u003e Full Keyboard Access`\n- `.stickyKeys`: `Accessibility \u003e Sticky Keys`\n- `.slowKeys`: `Accessibility \u003e Slow Keys`\n- `.virtualKeyboard`: `Accessibility \u003e Accessibility Keyboard`\n- `.voiceControl`: `Accessibility \u003e Voice Control`\n- `.switchControl`: `Accessibility \u003e Switch Control`\n- `.alternateMouseButtons`: `Accessibility \u003e Alternate Mouse Buttons`\n- `.headMouse`: `Accessibility \u003e Head Pointer`\n- `.mouseKeys`: `Accessibility \u003e Mouse Keys`\n- `.hoverText`: `Accessibility \u003e Hover Text`\n- `.hoverTyping`: `Accessibility \u003e Hover Typing`\n- `.liveSpeech`: `Accessibility \u003e Live Speech`\n- `.personalVoice`: `Accessibility \u003e Personal Voice`\n- `.siri`: `Accessibility \u003e Siri`\n- `.shortcut`: `Accessibility \u003e Accessibility Shortcut`\n\n## Configuration\n\n```swift\nlet configuration = PermissionFlowConfiguration(\n    requiredAppURLs: [Bundle.main.bundleURL],\n    promptForAccessibilityTrust: false\n)\n```\n\n### Notes\n\n- `requiredAppURLs` preloads apps into the panel\n- `promptForAccessibilityTrust` controls whether AX trust is actively prompted\n\n## How It Works\n\n1. Your app requests a permission pane.\n2. `PermissionFlow` opens the matching `System Settings` page.\n3. If that pane supports drag-based authorization, a floating panel appears.\n4. The panel animates from the click location to the `System Settings` window.\n5. The panel tracks the `System Settings` window position.\n6. The user drags the current `.app` bundle into the permission list.\n\n## Example\n\nThe repository includes an `Example` macOS app that demonstrates all supported permission flows.\n\n## Notes and Limitations\n\n- The floating helper is only shown for panes that support app-list style authorization.\n- **Permission status detection**: Uses official Apple APIs (`CGPreflightListenEventAccess`, `CGPreflightScreenCaptureAccess`, `AXIsProcessTrusted`) for accurate status checking without triggering system prompts.\n- **Status refresh**: Permission status is automatically refreshed when the app becomes active and when buttons appear on screen.\n- `System Settings` behavior is controlled by macOS and may vary slightly by OS version.\n- AX-based window tracking is used when available. Window Server frame lookup is used as fallback and bootstrap.\n- The package does not bypass macOS security. It only guides the user through the system UI.\n\n## License\n\nLicensed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaywcjlove%2FPermissionFlow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaywcjlove%2FPermissionFlow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaywcjlove%2FPermissionFlow/lists"}