#![forbid(unsafe_code)]
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DeviceId(DeviceIdRepr);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
enum DeviceIdRepr {
Wasapi(String),
MediaFoundation(String),
DxgiOutput(String),
}
impl DeviceId {
#[must_use]
pub fn from_wasapi_endpoint_id(id: impl Into<String>) -> Self {
Self(DeviceIdRepr::Wasapi(id.into()))
}
#[must_use]
pub fn from_media_foundation_symbolic_link(link: impl Into<String>) -> Self {
Self(DeviceIdRepr::MediaFoundation(link.into()))
}
#[must_use]
pub fn from_dxgi_output_device_name(name: impl Into<String>) -> Self {
Self(DeviceIdRepr::DxgiOutput(name.into()))
}
#[must_use]
pub fn as_wasapi_endpoint_id(&self) -> Option<&str> {
match &self.0 {
DeviceIdRepr::Wasapi(id) => Some(id),
DeviceIdRepr::MediaFoundation(_) | DeviceIdRepr::DxgiOutput(_) => None,
}
}
#[must_use]
pub fn as_media_foundation_symbolic_link(&self) -> Option<&str> {
match &self.0 {
DeviceIdRepr::MediaFoundation(link) => Some(link),
DeviceIdRepr::Wasapi(_) | DeviceIdRepr::DxgiOutput(_) => None,
}
}
#[must_use]
pub fn as_dxgi_output_device_name(&self) -> Option<&str> {
match &self.0 {
DeviceIdRepr::DxgiOutput(name) => Some(name),
DeviceIdRepr::Wasapi(_) | DeviceIdRepr::MediaFoundation(_) => None,
}
}
}
impl std::fmt::Display for DeviceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
DeviceIdRepr::Wasapi(id) => write!(f, "wasapi:{id}"),
DeviceIdRepr::MediaFoundation(link) => write!(f, "mf-symlink:{link}"),
DeviceIdRepr::DxgiOutput(name) => write!(f, "dxgi-output:{name}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[error("unrecognized device id tag prefix: {0:?}")]
pub struct ParseDeviceIdError(String);
impl std::str::FromStr for DeviceId {
type Err = ParseDeviceIdError;
#[allow(
clippy::option_if_let_else,
reason = "a chained if-let over 3 mutually exclusive tag prefixes reads clearer than nested map_or_else closures"
)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(rest) = s.strip_prefix("wasapi:") {
Ok(Self::from_wasapi_endpoint_id(rest))
} else if let Some(rest) = s.strip_prefix("mf-symlink:") {
Ok(Self::from_media_foundation_symbolic_link(rest))
} else if let Some(rest) = s.strip_prefix("dxgi-output:") {
Ok(Self::from_dxgi_output_device_name(rest))
} else {
Err(ParseDeviceIdError(s.to_owned()))
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum Select {
#[default]
Default,
Id(DeviceId),
NameContains(String),
}
#[cfg(test)]
#[path = "device_id_tests.rs"]
mod tests;