use crate::event::Event;
use crate::state::State;
use crate::types::{
AudioChangeSource, AudioEndpoint, AudioEndpoints, AudioFeatures, AudioLink, StreamKind,
};
use crate::wire::audio_sub as sub;
use super::handlers::{stream_source, takes_command};
use super::Rx;
mod entry {
pub(super) const ENDPOINT: u8 = 1;
pub(super) const ADDRESS: u8 = 2;
pub(super) const ROUTE_IN: u8 = 3;
pub(super) const PARENT: u8 = 5;
}
const LINKS_AT: usize = 20;
const ENTRY_BASE: usize = 36;
const ENTRY_SIZE: usize = 16;
const ENTRY_SIZE_LEGACY: usize = 24;
fn entry_at(index: usize, stride: usize) -> usize {
ENTRY_BASE + index * stride
}
fn layout(rx: &Rx<'_>, count: usize) -> Option<(usize, Option<usize>)> {
let len = rx.frame.payload().len();
let end = entry_at(count, ENTRY_SIZE);
if len == end {
return Some((ENTRY_SIZE, None));
}
if let Some(links) = rx.frame.u16(end) {
if len == end + 4 + usize::from(links) * LINK_ENTRY {
return Some((ENTRY_SIZE, Some(end)));
}
}
(len == entry_at(count, ENTRY_SIZE_LEGACY)).then_some((ENTRY_SIZE_LEGACY, None))
}
fn change_source(rx: &Rx<'_>) -> Option<AudioChangeSource> {
let f = &rx.frame;
if f.payload().len() < SELECT_INPUT_SIZE {
let unit = f.uid(4)?;
return Some(AudioChangeSource {
source_uid: unit,
source_id: u16::try_from(f.u32(24)?).ok()?,
target_uid: unit,
target_id: f.u16(20)?,
});
}
Some(AudioChangeSource {
source_uid: f.uid(36)?,
source_id: f.u16(54)?,
target_uid: f.uid(20)?,
target_id: f.u16(52)?,
})
}
const SELECT_INPUT_SIZE: usize = 56;
fn endpoints(rx: &Rx<'_>, stride: usize) -> AudioEndpoints {
let f = &rx.frame;
let mut eps = AudioEndpoints::default();
let Some(count) = f.u16(28) else {
return eps;
};
for index in 0..usize::from(count) {
let base = entry_at(index, stride);
let Some(id) = f.u8(base) else {
break;
};
if f.u8(base + 1) == Some(entry::ENDPOINT) {
eps.add(AudioEndpoint {
id,
features: AudioFeatures::from_bits(f.u32(base + 8).unwrap_or(0)),
..AudioEndpoint::default()
});
eps.set_status(id, AudioFeatures::from_bits(f.u32(base + 12).unwrap_or(0)));
}
}
for index in 0..usize::from(count) {
let base = entry_at(index, stride);
let Some(id) = f.u8(base) else {
break;
};
if eps.get(id).is_none() {
continue;
}
match f.u8(base + 1) {
Some(entry::ADDRESS) => {
if let Some(bytes) = f.bytes_from(base + 8).filter(|b| b.len() >= 6) {
let address = stream_source(StreamKind::Audio, bytes, 0);
if let Some(ep) = eps.get_mut(id) {
ep.address = Some(address);
}
}
}
Some(entry::PARENT) => {
if let Some(parent) = f.u8(base + 8) {
if let Some(ep) = eps.get_mut(id) {
ep.parent = Some(parent);
}
if let Some(ep) = eps.get_mut(parent) {
ep.children.push(id);
}
}
}
Some(entry::ROUTE_IN) => {
if let (Some(available), Some(routed)) = (f.u32(base + 8), f.u32(base + 12)) {
if let Some(ep) = eps.get_mut(id) {
ep.inputs_available = Some(available);
ep.inputs_routed = Some(routed);
}
}
}
_ => {}
}
}
eps
}
const LINK_ENTRY: usize = 20;
fn links(rx: &Rx<'_>, idx: usize) -> Option<Vec<AudioLink>> {
let f = &rx.frame;
let count = usize::from(f.u16(idx)?);
if f.payload().len() < idx + 4 + count * LINK_ENTRY {
return None;
}
let mut out = Vec::new();
for index in 0..count {
let base = 4 + idx + index * LINK_ENTRY;
let Some(endpoint) = f.u8(base) else {
break;
};
let linked_device = f.uid(base + 4).unwrap_or(crate::wire::DeviceUid::ZERO);
if linked_device.is_zero() {
continue;
}
out.push(AudioLink {
endpoint,
linked_endpoint: f.u8(base + 1).unwrap_or(0),
linked_device,
});
}
Some(out)
}
pub(super) fn audio(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(op) = rx.frame.u16(0) else {
return;
};
let sender = rx.sender();
let target = rx.uid_or_zero(4);
let reports = matches!(op, sub::FEATURES | sub::LINKS);
if reports && target != sender || !reports && !takes_command(state, sender, target) {
return;
}
match op {
sub::FEATURES => {
let count = usize::from(rx.frame.u16(28).unwrap_or(0));
let Some((stride, links_at)) = layout(rx, count) else {
return;
};
let eps = endpoints(rx, stride);
let links = links_at.and_then(|at| links(rx, at));
if let Some(device) = state.device_mut(sender) {
if count > 0 {
device.audio_legacy = Some(stride == ENTRY_SIZE_LEGACY);
}
device.set_audio_endpoints(eps, ev);
if let Some(links) = links {
device.apply_audio_links(&links);
}
}
}
sub::LINKS => {
let Some(links) = links(rx, LINKS_AT) else {
return;
};
if let Some(device) = state.device_mut(sender) {
device.apply_audio_links(&links);
}
}
sub::SELECT_INPUT => {
let Some(change) = change_source(rx) else {
return;
};
if let Some(device) = state.device_mut(target) {
device.set_audio_select_input(change, ev);
}
}
sub::MUTE | sub::TRIGGER | sub::VOLUME | sub::LOCK => {
let (Some(endpoint), Some(value)) = (rx.frame.u16(20), rx.frame.u32(24)) else {
return;
};
if state.device(target).is_none() {
return;
}
let device = target;
ev.push(match op {
sub::MUTE => Event::AudioEndpointMute {
device,
endpoint,
muted: value != 0,
},
sub::TRIGGER => Event::AudioEndpointTrigger {
device,
endpoint,
active: value != 0,
},
sub::LOCK => Event::AudioEndpointLock {
device,
endpoint,
locked: value != 0,
},
_ => Event::AudioEndpointVolume {
device,
endpoint,
volume: value,
},
});
}
_ => {}
}
}