How Meetily Handles Audio Permissions on macOS, Windows, and Linux

Meetily automatically triggers platform-specific permission requests before any audio capture begins, using a centralized Rust module that orchestrates microphone and screen-recording consent across all supported operating systems.

Meetily is an open-source AI meeting assistant built with Tauri. Before it can record meetings, it must obtain system-level audio permissions—requirements vary significantly by platform. This article examines how Meetily's permission architecture works, tracing the flow from JavaScript invocation through Rust platform abstractions to native OS APIs.

Permission Architecture Overview

Meetily's permission system follows a layered design that keeps platform complexity isolated while ensuring consistent behavior:

The permission flow executes automatically when any recording command is invoked, preventing audio capture until all required consents are granted.

macOS: Microphone and Screen Recording Permissions

macOS requires two distinct permissions for full meeting capture: microphone access and screen recording (for system audio). Meetily handles these sequentially in permissions.rs, calling native Swift code for the screen-recording check.

Microphone Permission

The macOS platform module uses AVAudioSession to request microphone access. This triggers the standard macOS permission dialog with the application's NSMicrophoneUsageDescription string.

Screen Recording Permission

System audio capture requires ScreenCaptureKit entitlement, which needs explicit user approval. Meetily delegates this to a Swift helper:

// frontend/src-tauri/check_screen_permission.swift
import Cocoa

let options = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as String: true] as CFDictionary
let trusted = AXIsProcessTrustedWithOptions(options)
exit(trusted ? 0 : 1)

The Rust caller runs this script and parses the exit code to determine whether recording can proceed.

macOS Implementation Files

File Purpose
frontend/src-tauri/src/audio/devices/platform/macos.rs Device enumeration and native API calls
frontend/src-tauri/check_screen_permission.swift Screen-recording permission prompt

Windows: WASAPI Access and Device Permissions

Windows handles audio permissions through the WASAPI (Windows Audio Session API) framework. Unlike macOS, Windows typically does not show a permission dialog for microphone access—access is granted implicitly when the application first opens an audio stream.

Meetily's Windows implementation in audio/devices/platform/windows.rs uses IMMDeviceEnumerator to discover and activate capture devices. If the OS denies access (for example, due to privacy settings or corporate policy), the error propagates back to the frontend:

// Conceptual flow in windows.rs
let device = device_enumerator
    .GetDefaultAudioEndpoint(eCapture, eConsole, &device)
    .map_err(|e| format!("WASAPI access denied: {}", e))?;

The frontend receives this error through the Tauri command layer and displays an actionable message to the user.

Linux: ALSA/PulseAudio Group Membership

Linux audio permissions are implicit rather than interactive. Meetily attempts to open the default ALSA or PulseAudio device; success depends on the user belonging to the audio group and having appropriate udev rules.

The audio/devices/platform/linux.rs module tries PulseAudio first, falling back to ALSA if unavailable. Permission failures surface as device-open errors:

// Simplified from linux.rs
pub async fn ensure_alsa_or_pulse_access() -> Result<(), String> {
    match pulse::connect() {
        Ok(_) => Ok(()),
        Err(_) => alsa::open_default()
            .map_err(|e| format!("Audio access failed: {}. Ensure user is in 'audio' group.", e)),
    }
}

No OS dialog appears—administrators must configure access through system groups and permissions.

Frontend Integration: The Permission Hook

Meetily's React frontend uses a custom hook to coordinate permission state with the Rust backend. The usePermissionCheck.ts hook provides:

  • hasPermission: Boolean indicating current consent status
  • requestPermission(): Triggers the platform-specific flow
  • Error state propagation for UI feedback
// Frontend usage example
import { invoke } from '@tauri-apps/api/tauri';
import { usePermissionCheck } from '@/hooks/usePermissionCheck';

const MeetingRecorder = () => {
  const { hasPermission, requestPermission } = usePermissionCheck();

  const startRecording = async () => {
    if (!hasPermission) {
      const granted = await requestPermission();
      if (!granted) return; // User denied
    }

    try {
      await invoke('start_recording', {
        mic_device_name: 'Built-in Microphone',
        system_device_name: 'BlackHole 2ch',
        meeting_name: 'Team Sync',
      });
    } catch (e) {
      console.error('Recording failed:', e);
    }
  };

  return <button onClick={startRecording}>Start Recording</button>;
};

The hook internally calls Tauri commands that delegate to permissions.rs, ensuring consistent behavior across all entry points.

Complete Permission Flow

When a user clicks "Start Recording," this sequence executes:

  1. JavaScript invokes start_recording command via Tauri API
  2. recording_commands.rs receives the call and invokes permissions::ensure_permissions()
  3. permissions.rs selects platform module based on cfg! macros
  4. Platform module executes native permission requests:
    • macOS: request_microphone_permission() → Swift screen-recording check
    • Windows: ensure_wasapi_access() → Verify device activation succeeds
    • Linux: ensure_alsa_or_pulse_access() → Test device open
  5. Result propagation: Success allows pipeline initialization; failure returns descriptive error
  6. Frontend displays error or proceeds to recording UI

This flow guarantees fail-fast behavior—permissions are verified before any audio pipeline resources are allocated.

Key Implementation Files

File Path Role
[frontend/src-tauri/src/audio/permissions.rs](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/permissions.rs) Central permission orchestrator with platform dispatch
[frontend/src-tauri/check_screen_permission.swift](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/check_screen_permission.swift) macOS screen-recording prompt via AXIsProcessTrustedWithOptions
[frontend/src-tauri/src/audio/devices/platform/macos.rs](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/platform/macos.rs) macOS audio device access and AVAudioSession integration
[frontend/src-tauri/src/audio/devices/platform/windows.rs](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/platform/windows.rs) Windows WASAPI device enumeration and access validation
[frontend/src-tauri/src/audio/devices/platform/linux.rs](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/devices/platform/linux.rs) Linux ALSA/PulseAudio device access with group validation
[frontend/src/hooks/usePermissionCheck.ts](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/hooks/usePermissionCheck.ts) React hook for permission state and user-initiated requests
[frontend/src-tauri/src/audio/recording_commands.rs](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/recording_commands.rs) Tauri command handlers that enforce permission checks

Design Patterns and Best Practices

Meetily's permission handling demonstrates several robust patterns:

  • Platform abstraction trait: Common interface across macOS, Windows, and Linux implementations
  • Early validation: Permissions checked before resource allocation prevents partial initialization states
  • User-transparent flow: Permission requests emerge from normal app interaction, not preemptive prompts
  • Clear error messages: Platform-specific failures include actionable guidance (e.g., "Ensure user is in 'audio' group")

The architecture also simplifies testing: platform modules can be mocked, and the permission layer supports conditional compilation for different target environments.

Summary

  • Centralized control: permissions.rs gates all audio capture through ensure_permissions()
  • macOS complexity: Requires both microphone and screen-recording permissions, with Swift helper for the latter
  • Windows simplicity: Implicit WASAPI access, with graceful handling of enterprise policy restrictions
  • Linux system dependency: Relies on group membership and udev configuration rather than interactive prompts
  • Frontend coordination: usePermissionCheck.ts hook bridges Rust permission state with React UI state
  • Fail-fast safety: No audio pipeline initializes until all platform requirements are satisfied

Frequently Asked Questions

How does Meetily request microphone permission on macOS?

Meetily's macOS platform module uses AVAudioSession to trigger the standard system permission dialog. This occurs in audio/devices/platform/macos.rs when request_microphone_permission() is called from the central permissions.rs orchestrator. The user sees the application-specific usage description defined in Info.plist.

Why does macOS require screen recording permission for meeting audio?

macOS routes system audio (application sounds, not microphone input) through ScreenCaptureKit. Meetily therefore needs kTCCServiceScreenCapture approval to capture remote participants' voices in video calls. The check_screen_permission.swift script uses AXIsProcessTrustedWithOptions to prompt the user if this permission is not yet granted.

What happens if Windows WASAPI access is blocked by group policy?

If Windows enterprise policies block audio capture, the windows.rs platform module receives an E_ACCESSDENIED or similar HRESULT from IMMDeviceEnumerator. This error propagates through recording_commands.rs to the frontend, where usePermissionCheck displays: "WASAPI access denied. Contact your system administrator." The recording does not start.

Can Meetily work on Linux without root access?

Yes, provided the user belongs to the audio group and has read/write access to ALSA devices or PulseAudio socket. The linux.rs module attempts PulseAudio first (which often works for standard desktop sessions), falling back to direct ALSA access. No elevation is required if permissions are properly configured.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →