audiofox 1.0.1

Cross platform audio capture made easy!
// SPDX-License-Identifier: GPL-2.0-or-later WITH Classpath-exception-2.0
// Audiofox, the audio capturer!
// Copyright (c) 2026 foxberries

use std::rc::Rc;
use std::sync::mpsc;

use pw::context::ContextRc;
use pw::core::CoreRc;
use pw::main_loop::MainLoopRc;

use crate::common::result::Result;
use crate::platform::linux::command::{CmdListener, Response, StreamStates};
use crate::platform::linux::manager::Command;
use crate::platform::linux::registry::RegState;

/// Entry point for all functionality on PipeWire, which starts and runs the [`MainLoop`](MainLoopRc).
pub fn pw_thread(sender: &mpsc::Sender<Response>, receiver: pw::channel::Receiver<Command>) {
    if let Err(e) = || -> Result<()> {
        // Initialize the main four objects
        let mainloop = MainLoopRc::new(None)?;
        let context = ContextRc::new(&mainloop, None)?;
        let core = context.connect_rc(None)?;
        let registry = core.get_registry()?;

        // Initialize the main two states
        let reg_state = Rc::new(RegState::default());
        let streams = Rc::new(StreamStates::default());

        // Initialize the main two listeners
        let listener = Rc::new(CmdListener::new(&mainloop, &core, &reg_state, sender, &streams));
        let _reg_listener = reg_state.monitor(&registry, &streams, sender, &listener);

        // Attach the command listener to the command receiver
        let _attached_receiver = receiver.attach(mainloop.loop_(), listener.recv());

        // Complete init and return ready
        drain_queue(&mainloop, &core);
        send!(sender, Response::Ready)?;

        // Run until the manager directs to stop
        mainloop.run();

        Ok(())
    }() {
        // If init fails, report the error
        send_logged!(sender, Response::Error(e));
    }
}

/// Allows all pending events to complete before proceeding. This fully initializes the [`RegState`]
/// before returning the `Ready` response, so [`list()`](crate::AudioManager::list) can be called
/// immediately after [`start()`](crate::start).
///
/// # Panics
/// If `core.sync(0)` returns a synchronous success rather than an async success, `libspa` will panic.
///
/// See [`into_async_result`](pw::spa::utils::result::SpaResult::into_async_result) for details.
fn drain_queue(mainloop: &MainLoopRc, core: &CoreRc) {
    let _core_listener = core
        .add_listener_local()
        .done({
            let mainloop = mainloop.clone();
            move |_id, _seq| {
                mainloop.quit();
            }
        })
        .register();
    core.sync(0).expect("Core sync should never fail");
    mainloop.run();
}