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

Meetily centralizes audio permission requests through a Rust orchestration layer that delegates to platform-specific APIs, ensuring microphone and screen-recording consent is obtained before any capture begins.

Meetily is an open-source meeting transcription application built with Tauri and React that must navigate distinct audio permission models across operating systems. The application implements a unified permission architecture in frontend/src-tauri/src/audio/permissions.rs that abstracts OS-specific implementations, prompting users appropriately while preventing unauthorized access to input devices.

Cross-Platform Permission Architecture

Meetily’s audio subsystem treats permissions as a prerequisite gate before initializing any capture pipeline. The recording_commands.rs module invokes the central ensure_permissions() function, which selects the appropriate platform implementation via conditional compilation. This design ensures that macOS, Windows, and Linux each trigger their native consent dialogs through a common asynchronous interface.

On macOS, Meetily must obtain two distinct permissions: microphone access via AVAudioSession and screen recording access for system audio capture via ScreenCaptureKit. The macos.rs module requests microphone permissions directly through Rust bindings, while screen recording authorization is handled by the Swift helper script check_screen_permission.swift. This Swift file uses the AXIsProcessTrustedWithOptions API to prompt the user for screen recording access, returning a boolean result that Rust interprets as permission granted or denied.

Windows: WASAPI Access Control

Windows handles audio permissions through the WASAPI (Windows Audio Session API) layer. When Meetily attempts to enumerate capture devices in windows.rs, the IMMDeviceEnumerator interface implicitly triggers the OS permission dialog if the application has not previously been granted microphone access. If the user denies access, the WASAPI call returns an error code that ensure_permissions() surfaces as a structured error to the frontend.

Linux: ALSA and PulseAudio Group Requirements

Linux distributions rely on ALSA or PulseAudio for audio capture, which do not implement graphical permission prompts. Instead, Meetily checks whether the user belongs to the audio group or has sufficient capabilities to open the default PCM device. The linux.rs module attempts to initialize the audio context; failure results in a permission error propagated back to the UI, advising the user to configure group membership or PulseAudio settings.

Centralized Permission Orchestration in Rust

The permissions.rs file acts as the single source of truth for all audio authorization checks. It exposes an async ensure_permissions() function that recording commands must await before initializing the capture pipeline. The function uses #[cfg(target_os = "...")] attributes to compile the correct platform module, which implements the AudioPermissionProvider trait for consistent error handling.

// frontend/src-tauri/src/audio/permissions.rs
pub async fn ensure_permissions() -> Result<(), String> {
    #[cfg(target_os = "macos")]
    {
        macos::request_microphone_permission().await?;
        macos::request_screen_recording_permission().await?;
    }
    #[cfg(target_os = "windows")]
    {
        windows::ensure_wasapi_access().await?;
    }
    #[cfg(target_os = "linux")]
    {
        linux::ensure_alsa_or_pulse_access().await?;
    }
    Ok(())
}

This architecture guarantees that no audio stream starts until the OS confirms access, preventing runtime failures deep in the capture pipeline.

Frontend Integration with React Hooks

The React frontend consumes these Rust commands through the usePermissionCheck hook located in frontend/src/hooks/usePermissionCheck.ts. This hook provides methods to query current permission states and trigger the Rust-side authorization flow. When a user initiates a recording, the hook calls the Tauri start_recording command, which internally invokes ensure_permissions() before proceeding.

// frontend/src/hooks/usePermissionCheck.ts usage example
import { invoke } from '@tauri-apps/api/tauri';
import { usePermissionCheck } from '@/hooks/usePermissionCheck';

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

  if (!hasPermission) {
    await requestPermission();
  }

  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);
  }
};

Platform-Specific Implementation Details

macOS Swift Helper

For screen recording permissions on macOS, Meetily shells out to a dedicated Swift executable:

// 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)

This script returns exit code 0 only when the user has granted screen recording privileges, allowing the Rust backend in permissions.rs to distinguish between denied and authorized states.

Windows and Linux Modules

The windows.rs and linux.rs files in frontend/src-tauri/src/audio/devices/platform/ implement the platform-specific logic. Windows uses COM interfaces to query device state, while Linux attempts to open the default PulseAudio source or ALSA device, treating any access denial as a permission failure that aborts the recording command.

Summary

  • Meetily centralizes audio permission logic in permissions.rs, ensuring all platform-specific checks complete before capture begins.
  • macOS requires separate microphone (AVAudioSession) and screen recording (AXIsProcessTrustedWithOptions) prompts handled via Rust and a Swift helper.
  • Windows relies on WASAPI (IMMDeviceEnumerator) to implicitly request microphone access when devices are enumerated.
  • Linux checks user group membership and device file access via ALSA/PulseAudio, returning explicit errors if permissions are insufficient.
  • The usePermissionCheck React hook bridges the frontend and Rust backend, providing a unified interface for permission state management.

Frequently Asked Questions

How does Meetily handle denied permissions on macOS?

If the user denies either microphone or screen recording access on macOS, the platform functions in macos.rs return an error that propagates through ensure_permissions() to the start_recording command. The usePermissionCheck hook captures this rejection and displays a UI notification directing the user to System Settings.

Does Meetily support headless Linux environments without GUI permission dialogs?

Yes, Meetily supports headless Linux servers because ALSA and PulseAudio do not require interactive permission dialogs. However, the user account running the application must belong to the audio group or have appropriate capabilities to access /dev/snd/* devices.

Why does Meetily use a separate Swift file for macOS screen recording?

Meetily uses check_screen_permission.swift because Rust’s Core Audio bindings cannot directly invoke the AXIsProcessTrustedWithOptions API required for screen recording consent. By compiling and executing this Swift script as a subprocess, Meetily triggers the native macOS transparency consent dialog while maintaining the main application logic in Rust.

How are permission checks integrated with the recording lifecycle?

Permission checks are synchronous prerequisites within the asynchronous recording command. When the frontend calls invoke('start_recording', ...), the handler in recording_commands.rs immediately calls audio::permissions::ensure_permissions().await. Only after this call resolves successfully does the code initialize the audio pipeline, ensuring that no system resources are allocated for capture until the OS confirms the user has granted all necessary entitlements.

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 →