pub mod device;
#[cfg(target_os="linux")]
pub mod pulseaudio {
use libpulse_binding::{
context::{Context, introspect::SinkInfo},
callbacks::ListResult,
mainloop::standard::Mainloop,
proplist::Proplist
};
use std::sync::{Arc, Mutex};
use crate::{VolumeControl, debug_eprintln, debug_println, device::DeviceTrait, error::Error, pulseaudio::device::PulseAudioDevice};
pub fn get_device_identifiers() -> Result<Vec<(String, String)>, Error> {
let mut devices: Vec<(String, String)> = Vec::new();
let device_list = Arc::new(Mutex::new(Vec::new()));
let mut mainloop = Mainloop::new().expect("Failed to create mainloop");
let proplist = Proplist::new().unwrap();
let mut context = Context::new_with_proplist(&mainloop, "CPVC", &proplist)
.expect("Failed to create connection context");
context.connect(None, libpulse_binding::context::FlagSet::NOFLAGS, None)
.expect("Failed to connect context");
loop {
match context.get_state() {
libpulse_binding::context::State::Ready => break,
libpulse_binding::context::State::Failed | libpulse_binding::context::State::Terminated => {
panic!("Context failed or terminated");
}
_ => {
mainloop.iterate(false);
}
}
}
let clone = Arc::clone(&device_list);
let error = Arc::new(Mutex::new(None));
let error_clone = error.clone();
let op = context.introspect().get_sink_info_list(move |info: ListResult<&SinkInfo> | {
match info {
libpulse_binding::callbacks::ListResult::Item(device) => {
if let Some(description) = device.description.as_ref() && let Some(name) = device.name.as_ref() {
if let Ok(mut lock) = clone.lock() {
lock.push((name.to_string(), description.to_string()));
} else {
error_clone.lock().unwrap().replace(Some(Error::DeviceAccessFailed(format!("Failed to unlock device list on device {}", name))));
}
} else {
error_clone.lock().unwrap().replace(Some(Error::DeviceAccessFailed(format!("Failed to access device description"))));
}
},
libpulse_binding::callbacks::ListResult::End => {
debug_println("Devices finished");
},
libpulse_binding::callbacks::ListResult::Error => {
debug_eprintln("error gathering device information");
},
}
});
while op.get_state() == libpulse_binding::operation::State::Running {
mainloop.iterate(false);
}
mainloop.quit(libpulse_binding::def::Retval(0));
if let Ok(list) = &mut device_list.lock() {
devices.append(list);
} else {
return Err(Error::DeviceEnumerationFailed(format!("Failed to lock onto device list")));
}
Ok(devices)
}
pub(super) fn acquire_mainloop_and_context() -> (Mainloop, Context) {
let mut mainloop = Mainloop::new().expect("Failed to create mainloop");
let proplist = Proplist::new().unwrap();
let mut context = Context::new_with_proplist(&mainloop, "CPVC", &proplist)
.expect("Failed to create connection context");
context.connect(None, libpulse_binding::context::FlagSet::NOFLAGS, None)
.expect("Failed to connect context");
loop {
match context.get_state() {
libpulse_binding::context::State::Ready => break,
libpulse_binding::context::State::Failed | libpulse_binding::context::State::Terminated => {
panic!("Context failed or terminated");
}
_ => {
mainloop.iterate(false);
}
}
}
(mainloop, context)
}
pub fn get_default_output_dev() -> Result<PulseAudioDevice, Error> {
let default_dev = Arc::new(Mutex::new(String::new()));
let clone = Arc::clone(&default_dev);
let (mut mainloop, context) = acquire_mainloop_and_context();
let op = context.introspect().get_sink_info_list( move |info | {
match info {
libpulse_binding::callbacks::ListResult::Item(device) => {
if let Some(_) = device.active_port {
*clone.lock().unwrap() = device.description.as_ref().unwrap().to_string();
}
},
libpulse_binding::callbacks::ListResult::End => {
debug_println("Devices finished")
},
libpulse_binding::callbacks::ListResult::Error => {
debug_eprintln("error gathering device information");
},
}
});
while op.get_state() == libpulse_binding::operation::State::Running {
mainloop.iterate(false);
}
mainloop.quit(libpulse_binding::def::Retval(0));
let device_name = default_dev.lock().unwrap().clone();
PulseAudioDevice::from_name(device_name)
}
pub fn get_device_id(name: String) -> Result<String, Error> {
let devices = get_device_identifiers()?;
for (dev_str, names) in devices {
if names == name {
return Ok(dev_str);
}
}
Err(Error::DeviceNotFound)
}
pub fn get_device_name(id: String) -> Result<String, Error> {
let devices = get_device_identifiers()?;
for (dev_str, name) in devices {
if id == dev_str {
return Ok(name);
}
}
Err(Error::DeviceNotFound)
}
pub fn get_sound_devices() -> Result<Vec<String>, Error> {
Ok(get_device_identifiers()?.into_iter().map(|(_id, name)| name).collect())
}
pub fn get_vol() -> Result<f32, Error> {
let default_dev = get_default_output_dev()?;
default_dev.get_vol()
}
pub fn set_vol(value: f32) -> Result<(), Error> {
let default_dev = get_default_output_dev()?;
default_dev.set_vol(value)
}
pub fn get_mute() -> Result<bool, Error> {
let default_dev = get_default_output_dev()?;
default_dev.get_mute()
}
pub fn set_mute(state: bool) -> Result<(), Error> {
let default_dev = get_default_output_dev()?;
default_dev.set_mute(state)
}
}
#[cfg(not(target_os="linux"))]
pub mod pulseaudio {
use crate::{error::Error, pulseaudio::device::PulseAudioDevice};
pub fn get_device_identifiers() -> Result<Vec<(String, String)>, Error> {
Err(Error::PlatformUnsupported)
}
pub fn get_default_output_dev() -> Result<PulseAudioDevice, Error> {
Err(Error::PlatformUnsupported)
}
pub fn get_device_id(name: String) -> Result<String, Error> {
Err(Error::PlatformUnsupported)
}
pub fn get_device_name(id: String) -> Result<String, Error> {
Err(Error::PlatformUnsupported)
}
pub fn get_sound_devices() -> Result<Vec<String>, Error> {
Err(Error::PlatformUnsupported)
}
pub fn get_vol() -> Result<f32, Error> {
Err(Error::PlatformUnsupported)
}
pub fn set_vol(value: f32) -> Result<(), Error> {
Err(Error::PlatformUnsupported)
}
pub fn get_mute() -> Result<bool, Error> {
Err(Error::PlatformUnsupported)
}
pub fn set_mute(state: bool) -> Result<(), Error> {
Err(Error::PlatformUnsupported)
}
}
pub(crate) use pulseaudio::*;