audiofox 1.0.3

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

use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt::{Debug, Display};
use core::ops::Deref;

/// One or more [`AudioSource`] received by the [`list()`](crate::AudioManager::list) command.
#[derive(Default, Clone, Eq, PartialEq, Hash)]
#[must_use]
#[repr(transparent)]
pub struct AudioSources(Box<[AudioSource]>);

impl Deref for AudioSources {
    type Target = [AudioSource];

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Debug for AudioSources {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        for (i, source) in self.0.iter().enumerate() {
            if i > 0 {
                write!(f, ", ")?;
            }
            write!(f, "{source:?}")?;
        }
        Ok(())
    }
}

impl Display for AudioSources {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        for (i, source) in self.0.iter().enumerate() {
            if i > 0 {
                write!(f, ", ")?;
            }
            write!(f, "{source}")?;
        }
        Ok(())
    }
}

impl FromIterator<AudioSource> for AudioSources {
    fn from_iter<T: IntoIterator<Item = AudioSource>>(iter: T) -> Self {
        Self(iter.into_iter().collect())
    }
}

impl From<&[AudioSource]> for AudioSources {
    fn from(slice: &[AudioSource]) -> Self {
        Self(slice.to_owned().into_boxed_slice())
    }
}

impl From<Box<[AudioSource]>> for AudioSources {
    fn from(boxed: Box<[AudioSource]>) -> Self {
        Self(boxed)
    }
}

impl From<Vec<AudioSource>> for AudioSources {
    fn from(vec: Vec<AudioSource>) -> Self {
        Self(vec.into_boxed_slice())
    }
}

impl<'a> IntoIterator for &'a AudioSources {
    type Item = &'a AudioSource;
    type IntoIter = core::slice::Iter<'a, AudioSource>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.iter()
    }
}

impl IntoIterator for AudioSources {
    type Item = AudioSource;
    type IntoIter = alloc::vec::IntoIter<AudioSource>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

/// A valid source of audio that Audiofox can link to.
///
/// To be registered as an `AudioSource`, the source must be detectable by the system, and have
/// exactly two output channels.
///
/// # Invariants
/// An `AudioSource` is only valid for use in [`link()`](crate::AudioManager::link) if it is received
/// by [`list()`](crate::AudioManager::list). Creating your own `AudioSource` that is at any point
/// passed to `link()` is unsupported with no behavioral guarantees.
///
/// # Platforms
/// Each platform has certain additional criteria for an `AudioSource`:
/// - Windows: The source must not represent a system process; i.e., its PID is not `0`.
/// - macOS: The source must represent a regular application that appears in the Dock; i.e., it has
///   an `NSApplicationActivationPolicy` of `Regular`. It therefore cannot represent a system daemon
///   or background process.
/// - Linux: The source must have a `media.class` of `Streams/Output/Audio`, and its output channels
///   must be labeled `FL` and `FR`. This is the default for nodes of this class.
///
/// These criteria may change in future development.
///
/// ## Note for Linux
/// Conceptually, an `AudioSource` may be thought of as an application, but on Linux, it may be more
/// accurately considered the union of all valid nodes for a given [`SourceName`].
#[derive(Default, Clone, PartialEq, Eq, Hash)]
#[must_use]
pub struct AudioSource {
    pub id: SourceId,
    pub name: SourceName,
}

impl Debug for AudioSource {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "[{}] {}", self.id, self.name)
    }
}

impl Display for AudioSource {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "{}", self.name)
    }
}

/// The ID of the [`AudioSource`].
///
/// # Platforms
/// On each platform, this value represents:
/// - Windows, macOS: The PID of the target application.
/// - Linux: The `object.id` of each node represented by the [`SourceName`] field.
///
/// These representations may change in future development.
#[derive(Default, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct SourceId(Box<[u32]>);

impl AsRef<[u32]> for SourceId {
    fn as_ref(&self) -> &[u32] {
        &self.0
    }
}

impl Deref for SourceId {
    type Target = [u32];
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Display for SourceId {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        for (i, id) in self.0.iter().enumerate() {
            if i > 0 {
                write!(f, ", ")?;
            }
            write!(f, "{id}")?;
        }
        Ok(())
    }
}

impl From<u32> for SourceId {
    fn from(value: u32) -> Self {
        Self(Box::new([value]))
    }
}

impl From<&[u32]> for SourceId {
    fn from(values: &[u32]) -> Self {
        Self(Box::from(Vec::from(values)))
    }
}

impl From<Vec<u32>> for SourceId {
    fn from(value: Vec<u32>) -> Self {
        Self(value.into_boxed_slice())
    }
}

/// The name of the [`AudioSource`].
///
/// # Platforms
/// On each platform, this value represents:
/// - Windows, macOS: The localized display name of the target application, with a fallback to
///   its process name.
/// - Linux: The `node.name` property of the target nodes.
///
/// These representations may change in future development.
#[derive(Default, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct SourceName(Box<str>);
impl AsRef<str> for SourceName {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

impl Deref for SourceName {
    type Target = str;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Display for SourceName {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<&str> for SourceName {
    fn from(value: &str) -> Self {
        Self(value.into())
    }
}

impl From<Rc<str>> for SourceName {
    fn from(value: Rc<str>) -> Self {
        Self(value.as_ref().into())
    }
}

impl From<String> for SourceName {
    fn from(value: String) -> Self {
        Self(value.into_boxed_str())
    }
}