PermissionFlow

repository·main·Indexed 43 days ago

https://github.com/jaywcjlove/permissionflow

A macOS and iOS library for simplifying privacy permission requests and management. It provides intuitive UI guidance via floating panels for drag-based authorization in macOS System Settings, real-time status detection using official Apple APIs, and SystemSettingsKit for creating strongly typed deep links to System Settings pages.

Tokens
19.5K
Snippets
48
Records
68
Agent score
54%

What's inside PermissionFlow

  1. Overview of PermissionFlow

    main

    PermissionFlow

    PermissionFlow is a macOS-specific library designed to guide users through the permission authorization process. It provides two main capabilities:

    1. Permission Guidance: For supported drag-based authorization pages in macOS, it opens the correct System Settings privacy pane and displays a floating panel. This panel follows the System Settings window and allows users to drag the current application bundle directly into the permission list.
    2. SystemSettingsKit: A utility for creating strongly typed deep links into various System Settings pages and subsections on macOS (with partial support for iOS).

    Key Features

    • Automatic Navigation: Automatically opens the specific System Settings privacy pane required for a permission.
    • Floating Guidance Panel:
      • Animates from the trigger position to the System Settings window.
      • Tracks the System Settings window as it moves.
      • Provides the current app as a native drag source.
      • Automatically closes when System Settings is closed.
      • Supports adaptive height based on content.
    • Real-time Status: UI components (buttons) provide visual feedback on permission status (e.g., green checkmark for granted, blue arrow for not granted).
    • Intelligent Detection: Uses official Apple APIs to check permission status accurately without triggering unnecessary system prompts.
  2. Important considerations for PermissionFlow

    main

    When using PermissionFlow, keep the following technical behaviors and constraints in mind:

    • Floating Helper Windows: These only appear on permission pages that support drag-and-drop authorization.
    • Permission Status Detection: The library uses official Apple APIs (CGPreflightListenEventAccess, CGPreflightScreenCaptureAccess, AXIsProcessTrusted) to check status accurately without triggering system prompts.
    • Status Refreshing: Permission status is automatically refreshed when the application returns to the foreground or when the permission button appears on the screen.
    • System Behavior: Specific behaviors of System Settings are controlled by macOS and may vary slightly between different system versions.
    • Security Model: This library does not bypass the macOS security model; it is designed to facilitate a smoother user experience during the system authorization process.
    • Window Tracking: If Accessibility permissions are available, the library prioritizes using AX (Accessibility) for precise window tracking. Window Server queries are used for startup and as a fallback. AX results undergo type validation before conversion; if an exception occurs, it returns nil or skips the current frame.
  3. Supported Permission Panes and Status Detection

    main

    PermissionFlow supports various privacy panes. Most use a floating drag-and-drop workflow, while others (like .camera or .microphone) trigger the system prompt and open System Settings directly.

    Supported Panes

    • .accessibility: Status detection supported.
    • .fullDiskAccess: Status detection supported.
    • .inputMonitoring: Status detection supported (via extension).
    • .screenRecording: Status detection supported (via extension).
    • .camera: Status detection supported (via extension); no floating panel.
    • .microphone: Status detection supported; no floating panel.
    • .calendars: Status detection supported; no floating panel; requires Info.plist.
    • .reminders: Status detection supported; no floating panel; requires Info.plist.
    • .bluetooth: Status detection supported (via extension).
    • .mediaAppleMusic: Status detection supported (via extension).
    • .appManagement: Status detection not available.
    • .developerTools: Status detection not available.

    PermissionFlowButton Status States

    PermissionFlowButton automatically displays:

    • ✅ Granted: Green checkmark + "Granted"
    • ➡️ Not Granted: Blue arrow + "Grant"
    • 🔄 Checking: Clock icon + "Checking..."
    • ❓ Unknown: Blue arrow + "Open" (for unsupported detection)
  4. Key features of PermissionFlow

    main

    PermissionFlow provides several automated UI and logic features to improve permission granting:

    • Real-time Permission Status: Buttons automatically update their visual state (e.g., a green checkmark for authorized, a blue arrow for unauthorized).
    • Automated Navigation: Automatically opens the relevant System Settings privacy page.
    • Floating Guidance Window:
      • Animates from the click location to the vicinity of the System Settings window.
      • Follows the System Settings window as it moves.
      • Displays the current application within the window as a native drag source.
      • Automatically closes when System Settings is closed.
      • Features adaptive height based on content.
    • Singleton Window Management: Ensures only one active floating window exists globally.
    • Smart Permission Detection: Uses official Apple APIs to check permission status accurately without triggering system prompts.
  5. Important considerations for using PermissionFlow

    main

    When integrating PermissionFlow into your macOS application, keep the following technical behaviors and constraints in mind:

    • Floating Helper Windows: These only appear on permission panes that support drag-and-drop authorization.
    • Permission Status Checking: The library uses official Apple APIs (CGPreflightListenEventAccess, CGPreflightScreenCaptureAccess, AXIsProcessTrusted) to check status accurately without triggering system prompts.
    • Automatic Refresh: Permission states are automatically refreshed when the application returns to the foreground or when the permission button appears on the screen.
    • System Behavior: Specific behaviors of System Settings are controlled by macOS; minor differences may exist between different macOS versions.
    • Security Model: This package does not bypass the macOS security model; it is designed to facilitate a smoother user experience during the system authorization process.
    • Window Tracking: When Accessibility permissions are available, the package prioritizes using Accessibility (AX) for precise window tracking, using Window Server queries as a startup and fallback mechanism. AX results undergo type validation before conversion; invalid types return nil or skip the current frame.
  6. Manage permission states with PermissionFlowStatusStore

    main

    To track and react to permission changes across your SwiftUI views, use PermissionFlowStatusStore.

    1. Inject into the environment: Create an instance of PermissionFlowStatusStore in your @main App struct and inject it using .environmentObject().
    2. Access in views: Use @EnvironmentObject to access the store. You can check the state of a specific pane using permissionStatusStore.state(for: .paneName) and manually trigger a refresh with permissionStatusStore.refresh(.paneName).
    3. Initialization: By default, it tracks all cases in PermissionFlowPane.allCases. You can limit the scope by passing specific panes to the initializer: PermissionFlowStatusStore(panes: [.accessibility, .fullDiskAccess]).

    Note: PermissionFlowStatusStore is a state container. For optional permissions (like .camera or .screenRecording), you must still call the corresponding register() method (e.g., PermissionFlowInputMonitoringStatus.register()) for the store to receive updates.

    // 1. Setup in App Entry
    @main
    struct MyApp: App {
        @StateObject private var permissionStatusStore = PermissionFlowStatusStore()
    
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .environmentObject(permissionStatusStore)
            }
        }
    }
    
    // 2. Usage in Subview
    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: "已授權"
            case .notGranted: "未授權"
            case .unknown: "未知"
            case .checking: "檢查中"
            }
        }
    }
  7. How PermissionFlow Authorization Works

    main

    PermissionFlow guides users through the macOS permission process using a floating panel and System Settings integration.

    Workflow Lifecycle

    1. Request: Your app requests a specific permission pane (e.g., .camera).
    2. Navigation: PermissionFlow opens the corresponding System Settings page.
    3. Floating Panel: If the pane supports app-list style authorization, a floating panel appears.
    4. Animation: The panel animates from the user's click location toward the System Settings window.
    5. Tracking: The panel tracks the System Settings window position using Accessibility (AX) attributes (falling back to Window Server frame lookup if AX is unavailable).
    6. Authorization: The user drags their app bundle into the permission list within the floating panel.

    Key Implementation Details

    • Status Detection: Uses official Apple APIs (CGPreflightListenEventAccess, CGPreflightScreenCaptureAccess, AXIsProcessTrusted) to check status without triggering system prompts.
    • Automatic Refresh: Permission status is automatically refreshed when the app becomes active or when permission buttons appear on screen.
    • Localization: Use .environment(\.locale, ...) with PermissionFlowButton or set localeIdentifier on the controller. Note: Always use PermissionFlowResources.packageBundle (or your app's bundle) instead of Bundle.module to avoid crashes in packaged apps.
  8. Understand the PermissionFlow workflow

    main

    The PermissionFlow library automates the process of guiding users to grant permissions via a drag-and-drop interface:

    1. Request: The host application requests a specific permission page.
    2. Open: PermissionFlow automatically opens the corresponding System Settings page.
    3. Floating UI: If the permission page supports drag-and-drop authorization, a floating auxiliary window appears.
    4. Animation: The floating window animates from the click location toward the System Settings window.
    5. Tracking: The floating window continuously follows the System Settings window as it moves (using AX attributes with safety checks to prevent crashes).
    6. Authorization: The user drags the current app icon from the floating window into the system authorization list.
  9. Configure PermissionFlow library products in your target

    main

    After adding the package, you must add the specific products to your app target.

    • Core UI: Use PermissionFlow for floating authorization guidance.
    • Deep Linking: Use SystemSettingsKit for arbitrary System Settings pages.
    • Status Detection: Use PermissionFlowExtendedStatus for a bundle of optional permissions, or individual products like PermissionFlowCameraStatus for specific needs.
    // Basic setup with core products
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "PermissionFlow", package: "PermissionFlow"),
            .product(name: "SystemSettingsKit", package: "PermissionFlow")
        ]
    )
    
    // Setup with extended status detection (e.g., Camera, Bluetooth, etc.)
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "PermissionFlow", package: "PermissionFlow"),
            .product(name: "PermissionFlowExtendedStatus", package: "PermissionFlow")
        ]
    )
  10. Enable optional permission state detection

    main

    By default, only a subset of permissions (like .accessibility, .fullDiskAccess, .microphone, .calendars, and .reminders) are detectable. To detect states for optional permissions such as .bluetooth, .inputMonitoring, .mediaAppleMusic, or .screenRecording, you must register the corresponding extended status product at app launch using PermissionFlowExtendedStatus.register().

    import PermissionFlowExtendedStatus
    import SwiftUI
    
    @main
    struct MyApp: App {
        init() {
            PermissionFlowExtendedStatus.register()
        }
    
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
  11. How to handle localization in PermissionFlow

    main

    To ensure correct localization and prevent crashes in your installation environment, follow these rules:

    1. For PermissionFlowButton: Use the .environment(\.locale, ...) modifier to set the locale.
    2. For Manual Controllers: Use localeIdentifier or the setLocaleIdentifier(_:) method.
    3. Resource Access: Always use PermissionFlowResources.packageBundle (or bundle) for host code. Do not use Bundle.module, as this can cause crashes in certain installation environments. Always provide default text/copy to ensure stability.
    // For PermissionFlowButton
    PermissionFlowButton()
        .environment(\.locale, Locale(identifier: "zh-Hant-TW"))
    
    // For manual controllers
    controller.setLocaleIdentifier("zh-Hant-TW")
    
    // Correct way to access resources to avoid crashes
    let bundle = PermissionFlowResources.packageBundle
  12. Requirements for running PermissionFlow

    main

    To use PermissionFlow, your environment and project must meet the following requirements:

    • Operating System: macOS 13+
    • Toolchain: Swift 6 Package Toolchain
    • App Type: The host application must be a macOS application built with SwiftUI and AppKit.