kael 0.2.0

GPU-accelerated native UI framework for Rust — build desktop apps with Metal, DirectX, and Vulkan rendering
Documentation
use crate::PermissionStatus;
use crate::media_capture::{
    CaptureBackend, CaptureConfig, CaptureDeviceInfo, CaptureDeviceKind, CaptureSession,
    CaptureSessionState, DeviceEnumerator, FrameCallback,
};
use anyhow::{Result, anyhow};
use std::sync::atomic::{AtomicU64, Ordering};

pub struct LinuxMicrophoneBackend;

impl LinuxMicrophoneBackend {
    pub fn new() -> Self {
        Self
    }
}

impl DeviceEnumerator for LinuxMicrophoneBackend {
    fn devices(&self, kind: CaptureDeviceKind) -> Result<Vec<CaptureDeviceInfo>> {
        match kind {
            CaptureDeviceKind::Microphone => Ok(vec![CaptureDeviceInfo {
                id: "mic-0".to_string(),
                name: "Default Microphone".to_string(),
                kind: CaptureDeviceKind::Microphone,
                is_available: true,
            }]),
            _ => Ok(vec![]),
        }
    }
}

impl CaptureBackend for LinuxMicrophoneBackend {
    fn create_session(&self, config: &CaptureConfig) -> Result<Box<dyn CaptureSession>> {
        match config.kind {
            CaptureDeviceKind::Microphone => {
                Ok(Box::new(LinuxMicrophoneSession::new(config.clone())))
            }
            _ => Err(anyhow!(
                "LinuxMicrophoneBackend does not support {:?}",
                config.kind
            )),
        }
    }
}

struct LinuxMicrophoneSession {
    config: CaptureConfig,
    state: CaptureSessionState,
    dropped: AtomicU64,
    latency_ms: AtomicU64,
    callback: Option<FrameCallback>,
}

impl LinuxMicrophoneSession {
    fn new(config: CaptureConfig) -> Self {
        Self {
            config,
            state: CaptureSessionState::Idle,
            dropped: AtomicU64::new(0),
            latency_ms: AtomicU64::new(0),
            callback: None,
        }
    }
}

impl CaptureSession for LinuxMicrophoneSession {
    fn start(&mut self, config: CaptureConfig, callback: FrameCallback) -> Result<()> {
        self.config = config;
        self.state = CaptureSessionState::Starting;
        self.callback = Some(callback);
        Err(anyhow!(
            "PulseAudio/PipeWire microphone capture requires runtime initialization"
        ))
    }

    fn pause(&mut self) -> Result<()> {
        self.state = CaptureSessionState::Paused;
        Ok(())
    }

    fn resume(&mut self) -> Result<()> {
        self.state = CaptureSessionState::Running;
        Ok(())
    }

    fn stop(&mut self) -> Result<()> {
        self.state = CaptureSessionState::Stopped;
        self.callback = None;
        Ok(())
    }

    fn state(&self) -> CaptureSessionState {
        self.state
    }

    fn dropped_frame_count(&self) -> u64 {
        self.dropped.load(Ordering::Relaxed)
    }

    fn latency_ms(&self) -> u64 {
        self.latency_ms.load(Ordering::Relaxed)
    }
}

fn pipewire_permission_status() -> Option<PermissionStatus> {
    let output = std::process::Command::new("dbus-send")
        .args([
            "--session",
            "--dest=org.freedesktop.impl.portal.PermissionStore",
            "--type=method_call",
            "--print-reply",
            "/org/freedesktop/impl/portal/PermissionStore",
            "org.freedesktop.impl.portal.PermissionStore.Lookup",
            "string:devices",
            "string:microphone",
        ])
        .output()
        .ok()?;

    if !output.status.success() {
        return None;
    }

    let stdout = std::str::from_utf8(&output.stdout).ok()?;

    if stdout.contains("\"no\"") {
        Some(PermissionStatus::Denied)
    } else if stdout.contains("\"yes\"") {
        Some(PermissionStatus::Granted)
    } else {
        Some(PermissionStatus::NotDetermined)
    }
}

pub fn microphone_status() -> PermissionStatus {
    if let Some(status) = pipewire_permission_status() {
        return status;
    }

    PermissionStatus::Granted
}

#[allow(dead_code)]
pub fn request_microphone_permission(callback: Box<dyn FnOnce(bool) + Send>) {
    let status = microphone_status();
    match status {
        PermissionStatus::Granted => callback(true),
        PermissionStatus::Denied | PermissionStatus::Restricted => callback(false),
        PermissionStatus::NotDetermined => callback(true),
    }
}