use std::{
cell::Cell,
ffi::CString,
os::raw::c_void,
ptr,
sync::{Arc, mpsc},
};
use crate::pipeline::Pipeline;
use crate::state::PwEvent;
use super::filter::{FilterHandle, create_eq_filter};
use super::props::Props;
pub(crate) struct NullSinkListenerData {
pub(crate) tx: mpsc::Sender<PwEvent>,
pub(crate) core_raw: *mut pipewire_sys::pw_core,
pub(crate) pipeline: Arc<Pipeline>,
pub(crate) filter_cell_ptr: *mut Cell<Option<FilterHandle>>,
pub(crate) null_sink_id_cell_ptr: *mut Cell<Option<u32>>,
pub(crate) filter_created: Cell<bool>,
}
pub(crate) struct NullSinkHandle {
pub(crate) proxy: *mut pipewire_sys::pw_proxy,
pub(crate) listener_ptr: *mut libspa_sys::spa_hook,
pub(crate) events_ptr: *mut pipewire_sys::pw_proxy_events,
pub(crate) data_ptr: *mut NullSinkListenerData,
}
impl NullSinkHandle {
pub(crate) unsafe fn destroy(self) {
unsafe {
if !self.proxy.is_null() {
pipewire_sys::pw_proxy_destroy(self.proxy);
}
if !self.data_ptr.is_null() {
drop(Box::from_raw(self.data_ptr));
}
if !self.events_ptr.is_null() {
drop(Box::from_raw(self.events_ptr));
}
if !self.listener_ptr.is_null() {
drop(Box::from_raw(self.listener_ptr));
}
}
}
}
pub(crate) unsafe extern "C" fn bound_cb(data: *mut c_void, global_id: u32) {
unsafe {
let nd = &*data.cast::<NullSinkListenerData>();
(*nd.null_sink_id_cell_ptr).set(Some(global_id));
let _ = nd.tx.send(PwEvent::NullSinkCreated {
module_id: global_id,
});
if !nd.filter_created.get() {
nd.filter_created.set(true);
let handle = create_eq_filter(nd.core_raw, &nd.pipeline, &nd.tx, Some(global_id));
if let Some(h) = handle {
(*nd.filter_cell_ptr).set(Some(h));
}
}
}
}
pub(crate) fn create_null_sink(
core_raw: *mut pipewire_sys::pw_core,
tx: &mpsc::Sender<PwEvent>,
) -> Option<NullSinkHandle> {
let props = Props::new("factory.name", "support.null-audio-sink");
props.set("media.class", "Audio/Sink");
props.set("node.name", "eqtui");
props.set("node.description", "eqtui (Virtual Sink)");
props.set("audio.position", "FL,FR");
props.set("monitor.channel-volumes", "false");
props.set("monitor.passthrough", "true");
props.set("priority.session", "0");
props.set("node.passive", "true");
props.set("node.virtual", "true");
let factory_cstr = CString::new("adapter").expect("factory name should not contain null bytes");
let type_cstr =
CString::new("PipeWire:Interface:Node").expect("type string should not contain null bytes");
let iface = core_raw.cast::<libspa_sys::spa_interface>();
let methods = unsafe { (*iface).cb.funcs.cast::<pipewire_sys::pw_core_methods>() };
let Some(create_fn) = (unsafe { (*methods).create_object }) else {
let _ = tx.send(PwEvent::NullSinkError(
"core create_object method not available".into(),
));
return None;
};
let proxy_ptr = unsafe {
create_fn(
(*iface).cb.data,
factory_cstr.as_ptr(),
type_cstr.as_ptr(),
pipewire_sys::PW_VERSION_NODE,
props.into_raw().cast::<libspa_sys::spa_dict>(),
0,
)
};
if proxy_ptr.is_null() {
let _ = tx.send(PwEvent::NullSinkError(
"pw_core_create_object for null-audio-sink returned NULL".into(),
));
return None;
}
let proxy = proxy_ptr.cast::<pipewire_sys::pw_proxy>();
Some(NullSinkHandle {
proxy,
listener_ptr: ptr::null_mut(),
events_ptr: ptr::null_mut(),
data_ptr: ptr::null_mut(),
})
}