use std::ffi::CString;
use enum_primitive_derive::Primitive;
use crate::protocol::{port_info::*, *};
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Primitive)]
pub enum SourceState {
Running = 0,
Idle = 1,
#[default]
Suspended = 2,
}
bitflags! {
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub struct SourceFlags: 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 DYNAMIC_LATENCY = 0x0040;
const FLAT_VOLUME = 0x0080;
}
}
#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct SourceInfo {
pub index: u32,
pub name: CString,
pub description: Option<CString>,
pub props: Props,
pub state: SourceState,
pub sample_spec: SampleSpec,
pub channel_map: ChannelMap,
pub owner_module_index: Option<u32>,
pub cvolume: ChannelVolume,
pub base_volume: Volume,
pub volume_steps: Option<u32>,
pub muted: bool,
pub monitor_of_sink_index: Option<u32>,
pub monitor_of_sink_name: Option<CString>,
pub flags: SourceFlags,
pub actual_latency: u64,
pub configured_latency: u64,
pub driver: Option<CString>,
pub card_index: Option<u32>,
pub ports: Vec<PortInfo>,
pub active_port: usize,
pub formats: Vec<FormatInfo>,
}
impl TagStructRead for SourceInfo {
fn read(ts: &mut TagStructReader<'_>, protocol_version: u16) -> Result<Self, ProtocolError> {
let mut source = SourceInfo {
index: ts
.read_index()?
.ok_or_else(|| ProtocolError::Invalid("invalid source 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_of_sink_index: ts.read_index()?,
monitor_of_sink_name: ts.read_string()?,
actual_latency: ts.read_usec()?,
driver: ts.read_string()?,
flags: SourceFlags::from_bits_truncate(ts.read_u32()?),
..Default::default()
};
if protocol_version >= 13 {
source.props = ts.read()?;
source.configured_latency = ts.read_usec()?;
}
if protocol_version >= 15 {
source.base_volume = ts.read()?;
source.state = ts.read_enum()?;
let steps = ts.read_u32()?;
source.volume_steps = if steps == 0 { None } else { Some(steps) };
source.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)
};
source.ports.push(PortInfo {
name: name.unwrap_or_default().to_owned(),
port_type,
description,
dir: PortDirection::Output,
priority,
available,
availability_group,
});
}
let active_port_name = ts.read_string()?;
if let Some(port) = active_port_name {
source.active_port = source
.ports
.iter()
.position(|p| port.to_bytes() == p.name.as_bytes())
.unwrap_or(0);
}
}
if protocol_version >= 21 {
for _ in 0..ts.read_u8()? {
source.formats.push(ts.read()?);
}
}
Ok(source)
}
}
impl TagStructWrite for SourceInfo {
fn write(
&self,
w: &mut TagStructWriter<'_>,
protocol_version: u16,
) -> Result<(), ProtocolError> {
w.write_index(Some(self.index))?;
w.write_string(Some(&self.name))?;
w.write_string(self.description.as_ref())?;
w.write(self.sample_spec)?;
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_of_sink_index)?;
w.write_string(self.monitor_of_sink_name.as_ref())?;
w.write_usec(self.actual_latency)?;
w.write_string(self.driver.as_ref())?;
w.write_u32(self.flags.bits())?;
if protocol_version >= 13 {
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(0))?;
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)?;
}
}
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(())
}
}
impl CommandReply for SourceInfo {}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct GetSourceInfo {
pub index: Option<u32>,
pub name: Option<CString>,
}
impl TagStructRead for GetSourceInfo {
fn read(ts: &mut TagStructReader<'_>, _protocol_version: u16) -> Result<Self, ProtocolError> {
Ok(Self {
index: ts.read_index()?,
name: ts.read_string()?,
})
}
}
impl TagStructWrite for GetSourceInfo {
fn write(
&self,
w: &mut TagStructWriter<'_>,
_protocol_version: u16,
) -> Result<(), ProtocolError> {
w.write_index(self.index)?;
w.write_string(self.name.as_ref())?;
Ok(())
}
}
pub type SourceInfoList = Vec<SourceInfo>;
impl CommandReply for SourceInfoList {}
impl TagStructRead for SourceInfoList {
fn read(ts: &mut TagStructReader<'_>, _protocol_version: u16) -> Result<Self, ProtocolError> {
let mut sources = Vec::new();
while ts.has_data_left()? {
sources.push(ts.read()?);
}
Ok(sources)
}
}
impl TagStructWrite for SourceInfoList {
fn write(
&self,
w: &mut TagStructWriter<'_>,
_protocol_version: u16,
) -> Result<(), ProtocolError> {
for info in self {
w.write(info)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use serde::test_util::test_serde;
use super::*;
#[test]
fn source_info_serde() -> anyhow::Result<()> {
let source = SourceInfo {
index: 0,
name: CString::new("test").unwrap(),
..Default::default()
};
test_serde(&source)
}
}
#[cfg(test)]
#[cfg(feature = "_integration-tests")]
mod integration_tests {
use super::*;
use crate::{
integration_test_util::connect_and_init,
protocol::{read_reply_message, write_command_message},
};
use pretty_assertions::assert_eq;
#[test]
fn list_sources() -> Result<(), Box<dyn std::error::Error>> {
let (mut sock, protocol_version) = connect_and_init()?;
write_command_message(
sock.get_mut(),
0,
&Command::GetSourceInfoList,
protocol_version,
)?;
let (seq, info_list) = read_reply_message::<SourceInfoList>(&mut sock, protocol_version)?;
assert_eq!(seq, 0);
assert!(!info_list.is_empty());
write_command_message(
sock.get_mut(),
1,
&Command::GetSourceInfo(GetSourceInfo {
index: Some(info_list[0].index),
..Default::default()
}),
protocol_version,
)?;
let (seq, info) = read_reply_message::<SourceInfo>(&mut sock, protocol_version)?;
assert_eq!(seq, 1);
assert_eq!(info, info_list[0]);
Ok(())
}
}