# How OpenSuperWhisper Manages Microphone Permissions and Device Selection

> Discover how OpenSuperWhisper handles microphone permissions and device selection via two Swift services: PermissionsManager for system authorization and MicrophoneService for hardware management.

- Repository: [Starmel/OpenSuperWhisper](https://github.com/Starmel/OpenSuperWhisper)
- Tags: how-to-guide
- Published: 2026-07-07

---

**OpenSuperWhisper delegates microphone access control to two specialized Swift services: `PermissionsManager` coordinates macOS TCC authorization and system preference routing, while `MicrophoneService` handles hardware discovery, persistence, and runtime device selection.**

OpenSuperWhisper is a macOS transcription application that requires precise audio input handling for real-time speech recognition. The codebase implements a clean separation between permission logic and hardware management using Apple's `AVCaptureDevice` APIs. This architecture ensures non-blocking permission checks while maintaining responsive device selection across hardware changes.

## Architecture Overview

The application separates concerns into two distinct components located in [`OpenSuperWhisper/PermissionsManager.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/PermissionsManager.swift) and [`OpenSuperWhisper/MicrophoneService.swift`](https://github.com/Starmel/OpenSuperWhisper/blob/main/OpenSuperWhisper/MicrophoneService.swift).

### PermissionsManager

This class centralizes all microphone authorization logic using Apple's privacy-sensitive TCC (Transparency, Consent, and Control) frameworks. It executes all permission checks on a dedicated `checkQueue` to prevent main thread blocking while providing SwiftUI-friendly observable state through `@Published` properties.

### MicrophoneService

This singleton service manages the complete lifecycle of audio input devices. It discovers available hardware through `AVCaptureDevice.DiscoverySession`, filters out virtual aggregate devices like `CADefaultDeviceAggregate`, and persists user selections via `AppPreferences.shared.selectedMicrophoneData`. The service emits `microphoneDidChange` notifications to alert the transcription engine when the active device changes.

## Permission Management Flow

The permission system operates through a four-stage process that balances user experience with system security requirements.

1. **Background Initialization**: When `PermissionsManager` initializes, it calls `checkAllPermissions()` on a private utility queue (`checkQueue`) to read the current authorization status without freezing the UI.

2. **Status Verification**: The system reads `AVCaptureDevice.authorizationStatus(for: .audio)` to determine if the state is `.authorized`, `.denied`, or `.notDetermined`.

3. **Request Handling**: The `requestMicrophonePermissionOrOpenSystemPreferences()` method handles two scenarios:
   - If status is `.notDetermined`, it calls `AVCaptureDevice.requestAccess(for: .audio)` to present the native permission dialog.
   - If access was previously denied, it opens the Security & Privacy pane using the `x‑apple.systempreferences:com.apple.preference.security?Privacy_Microphone` URL scheme.

4. **UI Synchronization**: The `@Published var isMicrophonePermissionGranted` property publishes state changes that SwiftUI views consume through `@StateObject` bindings, ensuring the interface reflects authorization status immediately.

## Device Discovery and Selection

Audio hardware management follows a resilient workflow that adapts to device connectivity changes in real-time.

1. **Service Startup**: `MicrophoneService.shared` initializes by calling `loadSavedMicrophone()` to restore the previously selected device from JSON-encoded preferences.

2. **Hardware Discovery**: The `refreshAvailableMicrophones()` method creates an `AVCaptureDevice.DiscoverySession` targeting `.microphone`, `.external`, and `.builtInMicrophone` device types. It wraps each `AVCaptureDevice` into a lightweight `AudioDevice` struct containing UID, name, manufacturer, and built-in status flags.

3. **Selection Logic**: The `getDefaultMicrophone()` helper prioritizes built-in microphones, falling back to the first available device if no built-in option exists. When users explicitly select a device, `selectMicrophone(_:)` updates the active reference, persists the choice, and posts the `microphoneDidChange` notification.

4. **Runtime Validation**: The `updateCurrentMicrophone()` method continuously verifies that the selected device remains connected. If the hardware disconnects, the service silently falls back to the default microphone, ensuring transcription continuity.

5. **Device Classification**: Helper methods like `isBuiltInDevice(_:)` and `isBluetoothMicrophone(_:)` categorize hardware for UI labeling and routing logic.

## Implementation Examples

The following code demonstrates how SwiftUI views interact with these services.

Checking and requesting permissions:

```swift
@StateObject private var permissions = PermissionsManager()

Button("Enable Mic") {
    permissions.requestMicrophonePermissionOrOpenSystemPreferences()
}
.disabled(permissions.isMicrophonePermissionGranted)

```

Listing and selecting available microphones:

```swift
@ObservedObject private var micService = MicrophoneService.shared

List(micService.availableMicrophones) { device in
    HStack {
        Text(device.displayName)
        if micService.getActiveMicrophone()?.id == device.id {
            Image(systemName: "checkmark")
        }
    }
    .onTapGesture {
        micService.selectMicrophone(device)
    }
}

```

Accessing the active capture device for the transcription engine:

```swift
if let avDevice = MicrophoneService.shared.getAVCaptureDevice() {
    // Pass `avDevice` to WhisperEngine.startCapture(...)
}

```

## Summary

- **Separate Concerns**: OpenSuperWhisper isolates permission logic in `PermissionsManager` and hardware management in `MicrophoneService`.
- **Non-Blocking Checks**: All TCC authorization checks run on a dedicated `checkQueue` to maintain UI responsiveness.
- **Smart Fallbacks**: The device selection system automatically falls back to default hardware when the selected microphone disconnects.
- **Deep Linking**: Denied permissions trigger direct navigation to System Preferences via the `x‑apple.systempreferences` URL scheme.
- **Persistent State**: User microphone selections survive app restarts through JSON-encoded preferences in `AppPreferences.shared.selectedMicrophoneData`.

## Frequently Asked Questions

### How does OpenSuperWhisper handle denied microphone permissions?

When `AVCaptureDevice.authorizationStatus` returns `.denied`, the `requestMicrophonePermissionOrOpenSystemPreferences()` method opens the Security & Privacy pane directly using the `x‑apple.systempreferences:com.apple.preference.security?Privacy_Microphone` URL scheme. This deep-links users to the exact location where they can manually grant microphone access without navigating system menus.

### What happens when the selected microphone disconnects during use?

The `updateCurrentMicrophone()` method in `MicrophoneService` continuously monitors device connectivity. If the active device disappears, the service automatically falls back to the default microphone (prioritizing built-in hardware) and updates the `currentMicrophone` reference, ensuring transcription continues uninterrupted.

### How does the app remember which microphone I selected?

User preferences persist through `AppPreferences.shared.selectedMicrophoneData`, which JSON-encodes the `AudioDevice` struct containing the device's unique UID. During initialization, `loadSavedMicrophone()` restores this selection and validates it against currently connected hardware via `refreshAvailableMicrophones()`.

### Why does OpenSuperWhisper use a separate queue for permission checks?

All Transparency, Consent, and Control (TCC) checks run on a dedicated `checkQueue` (DispatchQueue) to prevent blocking the main thread during authorization status reads. This ensures the SwiftUI interface remains responsive while `PermissionsManager` polls for permission updates, particularly when the app returns from the background.