How Meetily Implements Audio Permission Handling Across macOS, Windows, and Linux

Meetily requests platform-native audio permissions through a centralized Rust module before initializing any capture stream, ensuring microphone access on all platforms and additional screen-recording consent on macOS.

The audio permission handling architecture in the Zackriya-Solutions/meetily repository uses a Tauri-based backend to abstract OS-specific security models. The implementation routes all recording requests through a single validation layer that detects the host platform and invokes the appropriate system APIs.

Platform-Specific Permission Architecture

The permission system centers on frontend/src-tauri/src/audio/permissions.rs, which exposes an async ensure_permissions() function. This module acts as a cross-platform abstraction layer that delegates to platform-specific implementations in audio/devices/platform/.

  • macOS: Requires both microphone access and screen-recording permissions to capture system audio.
  • Windows: Relies on WASAPI implicit permissions granted when the user accepts the OS dialog on first access.
  • Linux: Depends on user group membership (audio) and PulseAudio/ALSA socket access rather than interactive prompts.

macOS: Microphone and Screen Recording Permissions

Permission Requirements

On macOS, Meetily must obtain two distinct entitlements before starting a recording session. The microphone permission is requested via the AVAudioSession API, while screen-recording permission is required to capture system audio output using ScreenCaptureKit.

Implementation Details

The Rust module calls a Swift helper script to trigger the macOS security dialog. The frontend/src-tauri/check_screen_permission.swift file uses the AXIsProcessTrustedWithOptions API to prompt the user:

// 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 frontend/src-tauri/src/audio/devices/platform/macos.rs module handles device enumeration after permissions are granted, interfacing with CoreAudio to list available input and virtual audio devices.

Windows: WASAPI Access Control

Windows audio permission handling relies on the Windows Audio Session API (WASAPI). The frontend/src-tauri/src/audio/devices/platform/windows.rs module uses IMMDeviceEnumerator to access capture devices.

Unlike macOS, Windows does not require a dedicated permission API call. Instead, the first attempt to open a capture stream triggers the system consent dialog. If the user denies access, the Rust code surfaces the E_ACCESSDENIED error from WASAPI:

// Conceptual flow in windows.rs
match device.Activate::<IAudioClient>(CLSCTX_ALL, None) {
    Ok(client) => client,
    Err(e) if e.code() == E_ACCESSDENIED => {
        Err("Microphone access denied by Windows privacy settings".into())
    }
    Err(e) => Err(e.into()),
}

Linux: ALSA and PulseAudio Group Requirements

Linux implementations in frontend/src-tauri/src/audio/devices/platform/linux.rs do not use interactive permission dialogs. Instead, the application checks for access to the ALSA device nodes or PulseAudio sockets.

The permission model depends on:

  • Membership in the audio system group for ALSA hardware access.
  • Access to the PulseAudio server socket (typically via user session permissions).

If the user lacks the necessary group membership or socket access, the device open operation fails with a permission error that propagates to the frontend.

Integration with the Recording Pipeline

The permission flow integrates with the UI through the frontend/src/hooks/usePermissionCheck.ts React hook and the frontend/src-tauri/src/audio/recording_commands.rs Tauri command.

When the user initiates a recording, the frontend invokes the start_recording command:

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

const startRecording = async () => {
  try {
    await invoke('start_recording', {
      mic_device_name: 'Built-in Microphone',
      system_device_name: 'BlackHole 2ch',
      meeting_name: 'Team Sync',
    });
  } catch (error) {
    console.error('Permission or device error:', error);
  }
};

The Rust handler in recording_commands.rs immediately calls ensure_permissions():

// frontend/src-tauri/src/audio/recording_commands.rs
#[tauri::command]
pub async fn start_recording(
    mic_device_name: String,
    system_device_name: String,
    meeting_name: String,
) -> Result<(), String> {
    // Validate permissions before allocating resources
    audio::permissions::ensure_permissions().await?;
    
    // Proceed with pipeline initialization...
    audio::pipeline::start(mic_device_name, system_device_name, meeting_name).await
}

The ensure_permissions() function in permissions.rs dispatches to the appropriate platform module:

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

Summary

  • Centralized validation: All recording commands route through permissions.rs, preventing unauthorized audio capture.
  • macOS dual-permission model: Requires explicit microphone and screen-recording consent via Swift helper scripts.
  • Windows implicit model: Leverages WASAPI security dialogs triggered on first device access.
  • Linux group-based model: Relies on audio group membership and PulseAudio socket permissions rather than interactive prompts.
  • Graceful error propagation: Permission denials surface as Tauri command errors, allowing the React frontend to display user-friendly guidance via usePermissionCheck.

Frequently Asked Questions

Does Meetily request audio permissions every time I start a recording?

No. Meetily requests permissions only when the OS indicates they have not been previously granted. On macOS, the system remembers the user's choice after the first approval, while Windows caches the WASAPI consent until the user manually revokes it in Privacy settings. The ensure_permissions() function checks the current authorization status before displaying any dialogs.

Why does Meetily require screen recording permission on macOS?

macOS classifies system audio capture as a screen-recording operation under the kTCCServiceScreenCapture privacy category. To record meeting audio from applications like Zoom or Teams, Meetily must capture the system output device, which requires the same entitlement used for screenshot utilities. The check_screen_permission.swift script explicitly requests this access using AXIsProcessTrustedWithOptions.

How does Meetily handle denied permissions on Windows?

When Windows returns an access-denied error from the WASAPI Activate call, the windows.rs module converts the HRESULT into a descriptive string error. This propagates through recording_commands.rs to the frontend, where the usePermissionCheck hook can detect the failure and prompt the user to enable microphone access in Windows Settings > Privacy > Microphone.

What Linux user group is required for Meetily audio capture?

The user must belong to the audio group to access hardware ALSA devices directly. For PulseAudio installations (most modern distributions), the user typically needs access to the PulseAudio socket in /run/user/$UID/pulse, which is granted automatically to the logged-in user. The linux.rs module attempts to open the default device and returns a permission error if group membership is insufficient.

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 →