use std::ffi::CString;
use super::CommandReply;
use crate::protocol::{port_info::*, *};
use bitflags::bitflags;
use enum_primitive_derive::Primitive;
bitflags! {
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub struct SinkFlags: u32 {
const HW_VOLUME_CTRL = 0x0001;
const LATENCY = 0x0002;
const HARDWARE = 0x0004;
const NETWORK = 0x0008;
const HW_MUTE_CTRL = 0x0010;
const DECIBEL_VOLUME = 0x0020;
const FLAT_VOLUME = 0x0040;
const DYNAMIC_LATENCY = 0x0080;
const SET_FORMATS = 0x0100;
}
}
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Primitive)]
pub enum SinkState {
Running = 0,
Idle = 1,
#[default]
Suspended = 2,
}
#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct SinkInfo {
pub index: u32,
pub name: CString,
pub description: Option<CString>,
pub props: Props,
pub state: SinkState,
pub sample_spec: SampleSpec,
pub channel_map: ChannelMap,
pub owner_module_index: Option<u32>,
pub cvolume: ChannelVolume,
pub muted: bool,
pub monitor_source_index: Option<u32>,
pub monitor_source_name: Option<CString>,
pub flags: SinkFlags,
pub actual_latency: u64,
pub configured_latency: u64,
pub driver: Option<CString>,
pub base_volume: Volume,
pub volume_steps: Option<u32>,
pub card_index: Option<u32>,
pub ports: Vec<PortInfo>,
pub active_port: usize,
pub formats: Vec<FormatInfo>,
}
impl SinkInfo {
pub fn new_dummy(index: u32) -> Self {
Self {
index,
name: CString::new("Dummy Sink").unwrap(),
props: Props::new(),
state: SinkState::Idle,
sample_spec: SampleSpec {
format: SampleFormat::S16Le,
channels: 2,
sample_rate: 48000,
},
channel_map: ChannelMap::stereo(),
cvolume: ChannelVolume::norm(2),
muted: false,
flags: SinkFlags::empty(),
ports: vec![PortInfo {
name: CString::new("Stereo Output").unwrap(),
port_type: PortType::Unknown,
description: None,
dir: PortDirection::Input,
priority: 0,
available: PortAvailable::Yes,
availability_group: None,
}],
active_port: 0,
formats: vec![FormatInfo::new(FormatEncoding::Pcm)],
..Default::default()
}
}
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct GetSinkInfo {
pub index: Option<u32>,
pub name: Option<CString>,
}
impl TagStructRead for GetSinkInfo {
fn read(ts: &mut TagStructReader<'_>, _protocol_version: u16) -> Result<Self, ProtocolError> {
Ok(Self {
index: ts.read_index()?,
name: ts.read_string()?,
})
}
}
impl TagStructWrite for GetSinkInfo {
fn write(
&self,
w: &mut TagStructWriter<'_>,
_protocol_version: u16,
) -> Result<(), ProtocolError> {
w.write_index(self.index)?;
w.write_string(self.name.as_ref())?;
Ok(())
}
}
impl CommandReply for SinkInfo {}
impl TagStructRead for SinkInfo {
fn read(
ts: &mut TagStructReader<'_>,
protocol_version: u16,
) -> Result<SinkInfo, ProtocolError> {
let mut sink = SinkInfo {
index: ts
.read_index()?
.ok_or_else(|| ProtocolError::Invalid("invalid sink index".into()))?,
name: ts.read_string_non_null()?,
description: ts.read_string()?,
sample_spec: ts.read()?,
channel_map: ts.read()?,
owner_module_index: ts.read_index()?,
cvolume: ts.read()?,
muted: ts.read_bool()?,
monitor_source_index: ts.read_index()?,
monitor_source_name: ts.read_string()?,
actual_latency: ts.read_usec()?,
driver: ts.read_string()?,
flags: SinkFlags::from_bits_truncate(ts.read_u32()?),
props: ts.read()?,
configured_latency: ts.read_usec()?,
..Default::default()
};
if protocol_version >= 15 {
sink.base_volume = ts.read()?;
sink.state = ts.read_enum()?;
sink.volume_steps = match ts.read_u32()? {
0 => None,
n => Some(n),
};
sink.card_index = ts.read_index()?;
}
if protocol_version >= 16 {
for _ in 0..ts.read_u32()? {
let name = ts
.read_string()?
.ok_or(ProtocolError::Invalid("empty port name".into()));
let description = ts.read_string()?;
let priority = ts.read_u32()?;
let available = if protocol_version >= 24 {
ts.read_enum()?
} else {
PortAvailable::Unknown
};
let (availability_group, port_type) = if protocol_version >= 34 {
(ts.read_string()?, ts.read_enum()?)
} else {
(None, PortType::Unknown)
};
sink.ports.push(PortInfo {
name: name.unwrap_or_default().to_owned(),
description,
dir: PortDirection::Input,
priority,
available,
availability_group,
port_type,
});
}
let active_port_name = ts.read_string()?;
if let Some(port) = active_port_name {
sink.active_port = sink
.ports
.iter()
.position(|p| port.to_bytes() == p.name.as_bytes())
.unwrap_or(0);
}
}
if protocol_version >= 21 {
for _ in 0..ts.read_u8()? {
sink.formats.push(ts.read()?);
}
}
Ok(sink)
}
}
impl TagStructWrite for SinkInfo {
fn write(
&self,
w: &mut TagStructWriter<'_>,
protocol_version: u16,
) -> Result<(), ProtocolError> {
w.write_u32(self.index)?;
w.write_string(Some(&self.name))?;
w.write_string(self.description.as_ref())?;
w.write(self.sample_spec.protocol_downgrade(protocol_version))?;
w.write(self.channel_map)?;
w.write_index(self.owner_module_index)?;
w.write(self.cvolume)?;
w.write_bool(self.muted)?;
w.write_index(self.monitor_source_index)?; w.write_string(self.monitor_source_name.as_ref())?;
w.write_usec(self.actual_latency)?;
w.write_string(self.driver.as_ref())?;
w.write_u32(self.flags.bits())?;
w.write(&self.props)?;
w.write_usec(self.configured_latency)?;
if protocol_version >= 15 {
w.write(self.base_volume)?;
w.write_u32(self.state as u32)?;
w.write_u32(self.volume_steps.unwrap_or_default())?;
w.write_index(self.card_index)?;
}
if protocol_version >= 16 {
w.write_u32(self.ports.len() as u32)?;
for port in &self.ports {
w.write_string(Some(&port.name))?;
w.write_string(port.description.as_ref())?;
w.write_u32(port.priority)?;
if protocol_version >= 24 {
w.write_u32(port.available as u32)?;
}
if protocol_version >= 34 {
w.write_string(port.availability_group.as_ref())?;
w.write_u32(port.port_type as u32)?;
}
}
let active_port_name = if self.active_port < self.ports.len() {
Some(&self.ports[self.active_port].name)
} else {
None
};
w.write_string(active_port_name)?;
}
if protocol_version >= 21 {
w.write_u8(self.formats.len() as u8)?;
for format in &self.formats {
w.write(format)?;
}
}
Ok(())
}
}
pub type SinkInfoList = Vec<SinkInfo>;
impl CommandReply for SinkInfoList {}
impl TagStructRead for SinkInfoList {
fn read(ts: &mut TagStructReader<'_>, _protocol_version: u16) -> Result<Self, ProtocolError> {
let mut sinks = Vec::new();
while ts.has_data_left()? {
sinks.push(ts.read()?);
}
Ok(sinks)
}
}
impl TagStructWrite for SinkInfoList {
fn write(
&self,
w: &mut TagStructWriter<'_>,
_protocol_version: u16,
) -> Result<(), ProtocolError> {
for sink in self {
w.write(sink)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::ffi::CString;
use super::*;
use crate::protocol::serde::test_util::test_serde;
#[test]
fn sink_info_list_serde() {
let mut props1 = Props::new();
props1.set(Prop::DeviceString, CString::new("foo").unwrap());
let mut props2 = Props::new();
props2.set(Prop::ApplicationName, CString::new("bar").unwrap());
let sinks = vec![
SinkInfo {
index: 0,
name: CString::new("sink0").unwrap(),
props: props1,
sample_spec: SampleSpec {
format: SampleFormat::S16Le,
channels: 2,
sample_rate: 44100,
},
..Default::default()
},
SinkInfo {
index: 1,
name: CString::new("sink1").unwrap(),
props: props2,
sample_spec: SampleSpec {
format: SampleFormat::S16Le,
channels: 2,
sample_rate: 44100,
},
..Default::default()
},
];
test_serde(&sinks).expect("SinkInfoList roundtrip")
}
}
#[cfg(test)]
#[cfg(feature = "_integration-tests")]
mod integration_tests {
use super::*;
use crate::integration_test_util::*;
#[test]
fn list_sinks() -> Result<(), Box<dyn std::error::Error>> {
let (mut sock, protocol_version) = connect_and_init()?;
write_command_message(
sock.get_mut(),
0,
&Command::GetSinkInfoList,
protocol_version,
)?;
let (_, info_list) = read_reply_message::<SinkInfoList>(&mut sock, protocol_version)?;
assert!(!info_list.is_empty());
Ok(())
}
}