diff options
Diffstat (limited to 'alsa')
| -rw-r--r-- | alsa/Cargo.toml | 18 | ||||
| -rw-r--r-- | alsa/src/communication.rs | 68 | ||||
| -rw-r--r-- | alsa/src/communication/pipe_chan.rs | 172 | ||||
| -rw-r--r-- | alsa/src/config.rs | 104 | ||||
| -rw-r--r-- | alsa/src/lib.rs | 37 | ||||
| -rw-r--r-- | alsa/src/runnable.rs | 397 |
6 files changed, 796 insertions, 0 deletions
diff --git a/alsa/Cargo.toml b/alsa/Cargo.toml new file mode 100644 index 0000000..d70a07e --- /dev/null +++ b/alsa/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "swaystatus-alsa" +version = "0.1.0" +authors = ["Andreas Grois <andi@grois.info>"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +swaystatus-plugin = { path = '../swaystatus-plugin', version = '*'} +formatable-float = { path = '../formatable-float', version = '*'} +serde = { version = "1.0", features = ["derive"] } +erased-serde = "0.3" +libc = "0.2.152" +errno = "0.3.8" + +[lib] +crate-type = ["cdylib"] diff --git a/alsa/src/communication.rs b/alsa/src/communication.rs new file mode 100644 index 0000000..3caf4cf --- /dev/null +++ b/alsa/src/communication.rs @@ -0,0 +1,68 @@ +use swaystatus_plugin::*; + +use self::pipe_chan::{Sender, Receiver, SendError, ReceiveError, create_pipe_chan}; + +pub(crate) mod pipe_chan; + +#[repr(C)] +pub enum MessagesFromMain { + Quit, + Refresh +} + +pub(crate) struct MessagesFromMainReceiver{ + receiver : Receiver +} + +pub(crate) struct MessagesFromMainSender{ + sender: Sender +} + +pub(crate) fn make_sender_for_main() -> Result<(MessagesFromMainSender, MessagesFromMainReceiver),()>{ + create_pipe_chan().map(|(sender,receiver)| (MessagesFromMainSender{sender}, MessagesFromMainReceiver{receiver})) +} + +impl MessagesFromMainSender { + pub(crate) fn send(&self, message : MessagesFromMain) -> Result<(), SendError>{ + self.sender.send_byte(match message { + MessagesFromMain::Quit => 0, + MessagesFromMain::Refresh => 1, + }) + } +} + +impl MessagesFromMainReceiver { + pub(crate) fn receive(&self) -> Result<Option<MessagesFromMain>, ReceiveError> { + self.receiver.read_byte().map(|o| o.map(|b| match b { + 0 => MessagesFromMain::Quit, + 1 => MessagesFromMain::Refresh, + _ => unreachable!() + })) + } + pub(crate) fn file_handle(&self) -> &pipe_chan::FileHandle{ + self.receiver.file_handle() + } +} + +pub struct SenderForMain { + sender : MessagesFromMainSender, +} + +impl<'p> SenderForMain { + pub fn new(sender : MessagesFromMainSender) -> Self { + Self { sender } + } + + fn send(&self, message : MessagesFromMain) -> Result<(), PluginCommunicationError> { + self.sender.send(message).map_err(|_| PluginCommunicationError) + } +} + +impl MsgMainToModule for SenderForMain { + fn send_quit(&self) -> Result<(),PluginCommunicationError> { + self.send(MessagesFromMain::Quit) + } + fn send_refresh(&self) -> Result<(),PluginCommunicationError> { + self.send(MessagesFromMain::Refresh) + } +} diff --git a/alsa/src/communication/pipe_chan.rs b/alsa/src/communication/pipe_chan.rs new file mode 100644 index 0000000..5f0f9ec --- /dev/null +++ b/alsa/src/communication/pipe_chan.rs @@ -0,0 +1,172 @@ +use std::{ffi::{c_int, c_void}, fmt::Display, error::Error}; +use libc::{read, close, pipe2, O_NONBLOCK, EINTR, EAGAIN, EWOULDBLOCK, EPIPE }; +use errno::{errno, set_errno, Errno}; //Why isn't this in libc?!? + +/// Sends byte data to the corresponding receiver. +pub(crate) struct Sender { + handle : FileHandle, +} +/// Receives byte data from the corresponding sender. +pub(crate) struct Receiver { + handle : FileHandle, +} + +impl Receiver { + pub(crate) fn read_byte(&self) -> Result<Option<u8>, ReceiveError> { + set_errno(Errno(0)); + let mut buf : u8 = 0; + let status = unsafe {read(self.handle.get_raw(),&mut buf as *mut u8 as *mut c_void, 1)}; + let e = errno(); + if status > 0 { + Ok(Some(buf)) + } else if status == 0 && e == Errno(0) { + Err(ReceiveError::SenderHasHungUp) + } else if e.0 == EINTR { + self.read_byte() //got interrupted by a signal, try again. + } else if e.0 == EAGAIN || e.0 == EWOULDBLOCK { + Ok(None) //nothing to receive + } else { + Err(ReceiveError::UnknownError) + } + } + pub(crate) fn file_handle(&self) -> &FileHandle { + &self.handle + } +} + +impl Sender { + pub(crate) fn send_byte(&self, byte : u8) -> Result<(), SendError> { + set_errno(Errno(0)); + let status = unsafe {libc::write(self.handle.get_raw(), &byte as *const u8 as *const c_void, 1)}; + let e = errno(); + if status > 0 { + Ok(()) + } else if e.0 == EINTR { + self.send_byte(byte) //interrupted, retry + } else if e.0 == EPIPE { + Err(SendError::ReceiverHasHungUp) + } else if e.0 == EAGAIN { + Err(SendError::ChannelFullWouldBlock) + } else { + Err(SendError::UnknownError) + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum SendError{ + ReceiverHasHungUp, + ChannelFullWouldBlock, + UnknownError +} + +impl Display for SendError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SendError::ReceiverHasHungUp => write!(f, "Write failed, Receiver has closed their end of the pipe."), + SendError::ChannelFullWouldBlock => write!(f, "Write failed, the pipe is clogged."), + SendError::UnknownError => write!(f, "Write failed for unknown reasons. Probably a bug."), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ReceiveError { + SenderHasHungUp, + UnknownError, +} + +impl Display for ReceiveError{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ReceiveError::SenderHasHungUp => write!(f, "Read failed, Sender has closed their end of the pipe."), + ReceiveError::UnknownError => write!(f, "Read failed for unknown reasons. Probably a bug."), + } + + } +} + +impl Error for ReceiveError {} + +pub(crate) fn create_pipe_chan() -> Result<(Sender, Receiver),()> { + let mut handles : [c_int;2] = [0;2]; + let result = unsafe { pipe2(handles.as_mut_ptr(), O_NONBLOCK) }; + if result == -1 { + Err(()) + } else { + Ok(( + Sender{handle : FileHandle{raw : handles[1]}}, + Receiver{handle : FileHandle{ raw : handles[0]}} + )) + } +} + +pub(crate) struct FileHandle { + raw : c_int, +} + +impl FileHandle { + pub(crate) fn get_raw(&self) -> c_int { + self.raw + } +} + +impl Drop for FileHandle { + fn drop(&mut self) { + unsafe { + close(self.raw); + } + } +} + +#[cfg(test)] +mod pipe_chan_test { + use libc::fcntl; + + use super::*; + + #[test] + fn simple_send_read(){ + let (send, recv) = create_pipe_chan().unwrap(); + assert_eq!(recv.read_byte(), Ok(None)); + send.send_byte(5).unwrap(); + send.send_byte(27).unwrap(); + assert_eq!(recv.read_byte(), Ok(Some(5))); + assert_eq!(recv.read_byte(), Ok(Some(27))); + assert_eq!(recv.read_byte(), Ok(None)); + } + + #[test] + fn simple_drop_sender(){ + let (send, recv) = create_pipe_chan().unwrap(); + assert_eq!(recv.read_byte(), Ok(None)); + send.send_byte(5).unwrap(); + send.send_byte(27).unwrap(); + drop(send); + assert_eq!(recv.read_byte(), Ok(Some(5))); + assert_eq!(recv.read_byte(), Ok(Some(27))); + assert_eq!(recv.read_byte(), Err(ReceiveError::SenderHasHungUp)); + } + + #[test] + fn simple_drop_receiver(){ + let (send, recv) = create_pipe_chan().unwrap(); + assert_eq!(recv.read_byte(), Ok(None)); + send.send_byte(5).unwrap(); + drop(recv); + assert_eq!(send.send_byte(27), Err(SendError::ReceiverHasHungUp)); + } + + #[test] + fn overfill_sender(){ + let (send, recv) = create_pipe_chan().unwrap(); + assert_eq!(recv.read_byte(), Ok(None)); + + let capacity = unsafe {fcntl(send.handle.get_raw(), libc::F_GETPIPE_SZ)}; + for _ in 0..capacity { + assert!(send.send_byte(3).is_ok()); + } + assert_eq!(send.send_byte(3), Err(SendError::ChannelFullWouldBlock)); + + } +}
\ No newline at end of file diff --git a/alsa/src/config.rs b/alsa/src/config.rs new file mode 100644 index 0000000..93bcab9 --- /dev/null +++ b/alsa/src/config.rs @@ -0,0 +1,104 @@ +use std::ffi::CString; + +use formatable_float::{FormatableFloatValue, KeyBackingTypeMetadata, FormattingError}; +use serde::{Serialize, Deserialize}; +use swaystatus_plugin::*; + +use crate::{runnable::AlsaVolumeRunnable, communication::{SenderForMain, make_sender_for_main}}; + +#[derive(Serialize, Deserialize)] +pub struct AlsaVolumeConfig{ + pub(crate) device : CString, + pub(crate) element : CString, + pub(crate) abstraction : SElemAbstraction, + sorting: FieldSorting, + mute: FormatableMute, + volume: FormatableFloatValue<VolumeKeyVolume>, +} + +#[derive(Serialize, Deserialize, Clone, Copy)] +pub(crate) enum SElemAbstraction{ + None, + Basic, +} + + +#[derive(Serialize, Deserialize)] +enum FieldSorting { + MuteVolume, + VolumeMute, +} + +impl AlsaVolumeConfig { + pub(crate) fn format_volume(&self, volume : f32, mute : bool) -> Result<String,FormattingError> { + let formatted_mute = self.mute.format_mute(mute).unwrap_or(String::new()); + let join_strings = |v : String,m : String| match self.sorting { + FieldSorting::MuteVolume => m + &v, + FieldSorting::VolumeMute => v + &m, + }; + match self.volume.format_float(volume) + { + Ok(v) => Ok(join_strings(v.unwrap_or_default(), formatted_mute)), + Err(FormattingError::EmptyMap { numeric_fallback }) => Err(FormattingError::EmptyMap { numeric_fallback: join_strings(numeric_fallback, formatted_mute) }), + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(tag = "Format")] +enum FormatableMute { + Off, + Symbol { + #[serde(rename = "Label")] + label : String, + #[serde(rename = "MuteSymbol")] + mute_symbol : String, + #[serde(rename = "UnmuteSymbol")] + unmute_symbol : String + } +} + +impl FormatableMute { + fn format_mute(&self, mute : bool) -> Option<String> { + match self { + FormatableMute::Off => { None } + FormatableMute::Symbol{ label, mute_symbol, unmute_symbol} => { Some(format!("{}{}", label, { if mute { mute_symbol } else { unmute_symbol }}))} + } + } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +struct VolumeKeyVolume; +impl KeyBackingTypeMetadata for VolumeKeyVolume{ + type BackingType = u8; + const MIN : Self::BackingType = 0; + const MAX : Self::BackingType = 100; + const FLOAT_MIN : f32 = 0.0; + const FLOAT_MAX : f32 = 1.0; +} + + +impl SwayStatusModuleInstance for AlsaVolumeConfig { + fn make_runnable<'p>(&'p self, to_main : Box<dyn MsgModuleToMain + 'p>) -> (Box<dyn SwayStatusModuleRunnable + 'p>, Box<dyn MsgMainToModule + 'p>) { + if let Ok((s,r)) = make_sender_for_main() { + (Box::new(AlsaVolumeRunnable::new(to_main, r, self)), Box::new(SenderForMain::new(s))) + } else { + to_main.send_update(Err(PluginError::ShowInsteadOfText("Pipe creation failed. Call your plumber.".to_owned()))) + .expect("Tried to send an error to main, but main is not listening any more."); + panic!("Pipe creation failed. Call your plumber.") + } + } +} + +impl Default for AlsaVolumeConfig { + fn default() -> Self { + Self { + device: CString::new("default").unwrap(), + element: CString::new("Master").unwrap(), + abstraction : SElemAbstraction::None, + volume: FormatableFloatValue::Numeric { label: " ".into(), digits: 0 }, + mute: FormatableMute::Symbol { label : String::new(), mute_symbol : String::from("🔇"), unmute_symbol : String::from("🔊") }, + sorting: FieldSorting::MuteVolume, + } + } +}
\ No newline at end of file diff --git a/alsa/src/lib.rs b/alsa/src/lib.rs new file mode 100644 index 0000000..8b10036 --- /dev/null +++ b/alsa/src/lib.rs @@ -0,0 +1,37 @@ +use swaystatus_plugin::*; + +mod config; +mod communication; +mod runnable; + +use config::AlsaVolumeConfig; + +pub struct AlsaVolumePlugin; +impl SwayStatusModule for AlsaVolumePlugin { + fn get_name(&self) -> &str { + "AlsaVolume" + } + fn deserialize_config<'de, 'p>(&'p self, deserializer : &mut (dyn erased_serde::Deserializer + 'de)) -> Result<Box<dyn SwayStatusModuleInstance + 'p>,erased_serde::Error> { + erased_serde::deserialize::<AlsaVolumeConfig>(deserializer) + .map(|c| Box::new(c) as Box<dyn SwayStatusModuleInstance>) + } + fn get_default_config<'p>(&'p self) -> Box<dyn SwayStatusModuleInstance + 'p> { + Box::new(config::AlsaVolumeConfig::default()) + } + fn print_help(&self) { + println!( +r#"Swaystatus Alsa Volume plugin. + +This is a volume display for ALSA. Currently quite limited, but hey, you're free to extend it. You must set the device and element name in config. +Blanace is not supported at the moment, just volume of a single element."# + ); + } +} + +impl AlsaVolumePlugin { + fn new() -> Self { + Self + } +} + +declare_swaystatus_module!(AlsaVolumePlugin, AlsaVolumePlugin::new); diff --git a/alsa/src/runnable.rs b/alsa/src/runnable.rs new file mode 100644 index 0000000..4835436 --- /dev/null +++ b/alsa/src/runnable.rs @@ -0,0 +1,397 @@ +use std::{cell::RefCell, fmt::Display, error::Error, ffi::CStr}; + +use formatable_float::FormattingError; +use libc::{c_int, c_char, c_uint, c_void, c_long, c_ushort, c_short}; +use swaystatus_plugin::*; + +use crate::{communication::MessagesFromMainReceiver, config::SElemAbstraction}; + +use super::config::AlsaVolumeConfig; + +pub struct AlsaVolumeRunnable<'r>{ + to_main : Box<dyn MsgModuleToMain + 'r>, + from_main : MessagesFromMainReceiver, + config : &'r AlsaVolumeConfig, +} + +impl<'r> AlsaVolumeRunnable<'r> { + pub fn new(to_main : Box<dyn MsgModuleToMain + 'r>, from_main : MessagesFromMainReceiver, config : &'r AlsaVolumeConfig) -> Self { + Self { to_main, from_main, config } + } + fn send_error_to_main<E>(&self, err : E) where E : std::error::Error { + self.to_main.send_update(Err(PluginError::ShowInsteadOfText(String::from("Error")))).expect("Tried to tell main thread that an error occured. Main thread isn't listening any more."); + self.to_main.send_update(Err(PluginError::PrintToStdErr(err.to_string()))).expect("Tried to tell main thread that an error occured. Main thread isn't listening any more."); + } + fn run_internal(&self) -> Result<(), AlsaVolumeError>{ + //Using C callbacks in Rust is a minefield. + //However, we can take the easy way out here, namely we only care about a single element, so we can just make a single data field ;-) + //It still needs to be in a Cell though. + let elem_scratch_space = RefCell::new(None); + let mixer_scratch_space = MixerScratchSpace{ + elem_name: &self.config.element, + elem_scratch: &elem_scratch_space, + }; + let mixer = open_mixer(0)?; + register_selem(mixer.handle, &self.config.device, self.config.abstraction)?; + unsafe { snd_mixer_set_callback(mixer.handle, Some(Self::mixer_callback)) }; + unsafe { snd_mixer_set_callback_private(mixer.handle, &mixer_scratch_space as *const MixerScratchSpace as *const c_void)}; + load_mixer(mixer.handle)?; + + let mut should_update_main_even_if_unchanged = true; + loop { + + let descriptor_count = unsafe{snd_mixer_poll_descriptors_count(mixer.handle)}; + if descriptor_count < 0 { + return Err(AlsaVolumeError::FailedToGetPollDescriptors); + } + let mut descriptors : Vec<libc::pollfd> = std::iter::once(libc::pollfd{ + fd: self.from_main.file_handle().get_raw(), + events: libc::POLLIN, + revents: 0 + }) + .chain(std::iter::repeat( + libc::pollfd{ + fd: 0, + events: 0, + revents: 0, + } + )) + .take(descriptor_count as usize + 1) + .collect(); + let descriptor_count = if descriptor_count > 0 { unsafe {snd_mixer_poll_descriptors(mixer.handle, &mut descriptors[1] , descriptor_count as c_uint)} } else { 0 }; + if descriptor_count < 0 { + return Err(AlsaVolumeError::FailedToGetPollDescriptors); + } + let n = unsafe {libc::poll(descriptors.as_mut_ptr(), descriptors.len() as u64, -1)}; + if n < 0 && n != libc::EINTR { + return Err(AlsaVolumeError::UnexpectedPollError); + } + //first check if there's any data on our pipe from main. + loop { + match self.from_main.receive(){ + Ok(Some(message)) => match message{ + crate::communication::MessagesFromMain::Quit => { return Ok(())}, + crate::communication::MessagesFromMain::Refresh => { should_update_main_even_if_unchanged = true; }, + }, + Ok(None) => break, //main has nothing more to say. + Err(e) => match e { + crate::communication::pipe_chan::ReceiveError::SenderHasHungUp => { return Err(AlsaVolumeError::MainHungUpWithoutQuit) }, + crate::communication::pipe_chan::ReceiveError::UnknownError => { return Err(AlsaVolumeError::ErrorInPluginCommunication) }, + }, + } + } + let old_values = mixer_scratch_space.elem_scratch.borrow().clone(); + let anything_new_from_alsa = n > (if descriptors[0].revents != 0 { 1 } else { 0 }); + if anything_new_from_alsa { + let mut revents = 0; + let worked = unsafe {snd_mixer_poll_descriptors_revents(mixer.handle, &descriptors[1], descriptor_count as c_uint,&mut revents) }; + if worked < 0{ + return Err(AlsaVolumeError::UnexpectedPollError); + } + if (revents as c_short) & (libc::POLLERR | libc::POLLNVAL) != 0 { + return Err(AlsaVolumeError::DeviceRemoved); + } + if (revents as c_short) & libc::POLLIN != 0 { + let handling_worked = unsafe {snd_mixer_handle_events(mixer.handle)}; + if handling_worked < 0 { + return Err(AlsaVolumeError::EventHandlingError); + } + } + } + let new_values = mixer_scratch_space.elem_scratch.borrow().clone(); + if new_values != old_values || should_update_main_even_if_unchanged { + self.send_updated_values_to_main(new_values).expect("Tried to update main thread, but it seems to be gone?"); + } + should_update_main_even_if_unchanged = false; + } + } + + fn send_updated_values_to_main(&self, volume : Option<ElemVolumeInfo>) -> Result<(),PluginCommunicationError> { + match volume{ + Some(volume) => { + let formatted_volume = self.config.format_volume(volume.volume, volume.mute); + match formatted_volume { + Ok(msg) => { self.to_main.send_update(Ok(msg)) } + Err(e) => { + let full_message = e.to_string(); + match e { + FormattingError::EmptyMap{ numeric_fallback } => { + self.to_main.send_update(Err(PluginError::ShowInsteadOfText(numeric_fallback)))?; + self.to_main.send_update(Err(PluginError::PrintToStdErr(full_message))) + } + } + } + } + }, + None => { + self.to_main.send_update(Err(PluginError::ShowInsteadOfText("Unknown".into()))) + }, + } + } + + extern "C" fn mixer_callback(mixer : SndMixerHandle, flags : c_uint, element : SndMixerElemHandle) -> c_int { + if flags & (1<<2) != 0 { //SND_CTL_EVENT_MASK_ADD + //check if the newly added element is the one we are looking for. + let scratch : &MixerScratchSpace = unsafe{&*(snd_mixer_get_callback_private(mixer) as *const MixerScratchSpace)}; + let elem_name = unsafe { CStr::from_ptr(snd_mixer_selem_get_name(element)) }; + if elem_name == scratch.elem_name { + unsafe {snd_mixer_elem_set_callback(element, Some(Self::element_callback))}; + unsafe {snd_mixer_elem_set_callback_private(element,scratch.elem_scratch as *const ElemScratchSpace as *const c_void)}; + 0 + } else { + 0 + } + } else { + 0 + } + } + + extern "C" fn element_callback(element : SndMixerElemHandle, flags : c_uint) -> c_int { + //okay, we hit the right element, sooo + if flags == (!0) { //SND_CTL_EVENT_MASK_REMOVE + 0 + } else { //could check further to exclude more spurious wake-ups, but for now... + //we don't do any magic here. Just sum up all channel's values and call it a day. + let (count, volume_sum) = ALL_CHANNELS.iter() + .filter_map(|channel| get_volume_for_channel(element, *channel)) + .fold((0,0), |(c, ov), v| (c+1, ov + v)); + let average = if count == 0 { None } else { Some(volume_sum / count)}; + let range = get_volume_range(element); + let normalized = average.zip(range).map(|(average, range)| ((average - range.0)*100000) / (range.1 - range.0)); + let scratch = unsafe{&*(snd_mixer_elem_get_callback_private(element) as *const ElemScratchSpace)}; + let has_mute = unsafe{ snd_mixer_selem_has_playback_switch(element) != 0}; + let is_mute = if has_mute { + !ALL_CHANNELS.iter().any(|channel| get_switch_for_channel(element, *channel)) + } else { false }; + *scratch.borrow_mut() = normalized.map(|volume| ElemVolumeInfo{volume : volume as f32 / 100000.0, mute : is_mute}); + 0 + } + } +} + +#[derive(Clone, Debug, PartialEq)] +struct ElemVolumeInfo{ + volume : f32, + mute : bool +} + +type ElemScratchSpace = RefCell<Option<ElemVolumeInfo>>; + +struct MixerScratchSpace<'a>{ + elem_name : &'a CStr, + elem_scratch : &'a ElemScratchSpace, +} + +impl<'r> SwayStatusModuleRunnable for AlsaVolumeRunnable<'r> { + fn run(&self) { + match self.run_internal(){ + Ok(()) => {}, + Err(e) => self.send_error_to_main(e), + } + } +} + +#[derive(Clone, Copy, Debug)] +enum AlsaVolumeError{ + FailedToOpenMixer, + FailedToLoadElements, + FailedToGetPollDescriptors, + UnexpectedPollError, + ErrorInPluginCommunication, + MainHungUpWithoutQuit, + DeviceRemoved, + EventHandlingError, +} + +impl Display for AlsaVolumeError{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AlsaVolumeError::FailedToOpenMixer => write!(f, "Failed to open Mixer"), + AlsaVolumeError::FailedToLoadElements => write!(f, "Failed to load Mixer elements"), + AlsaVolumeError::FailedToGetPollDescriptors => write!(f, "Failed to get poll descriptors"), + AlsaVolumeError::UnexpectedPollError => write!(f, "Polling for updates failed for an unhandled reason. Debug."), + AlsaVolumeError::ErrorInPluginCommunication => write!(f, "Something went wrong with the pipe from main thread. Debug."), + AlsaVolumeError::MainHungUpWithoutQuit => write!(f, "Main thread ended communication without saying goodbye. Debug."), + AlsaVolumeError::DeviceRemoved => write!(f, "Device removed. Unsupported for now."), + AlsaVolumeError::EventHandlingError => write!(f, "Failure while handling mixer events. Debug."), + } + } +} +impl Error for AlsaVolumeError{} + +struct MixerHandleScopeGuard{ + handle : SndMixerHandle +} +impl Drop for MixerHandleScopeGuard{ + fn drop(&mut self) { + if !self.handle.is_null() { + unsafe {snd_mixer_close(self.handle)}; + } + } +} + +fn open_mixer(mode : c_int) -> Result<MixerHandleScopeGuard,AlsaVolumeError>{ + let mut handle : SndMixerHandle = std::ptr::null(); + let error_code = unsafe {snd_mixer_open(&mut handle, mode)}; + if error_code == 0 { + Ok(MixerHandleScopeGuard { handle }) + } else { + Err(AlsaVolumeError::FailedToOpenMixer) + } +} + +fn load_mixer(mixer : SndMixerHandle) -> Result<(), AlsaVolumeError>{ + let error_code = unsafe { snd_mixer_load(mixer)}; + if error_code == 0 { + Ok(()) + } else { + Err(AlsaVolumeError::FailedToLoadElements) + } +} + +fn register_selem(mixer : SndMixerHandle, device : &CStr, abstraction : SElemAbstraction) -> Result<(), AlsaVolumeError>{ + let options = SndMixerSelemRegopt{ + ver: 1, + abstraction: match abstraction { + SElemAbstraction::None => SndMixerSelemRegoptAbstract::None, + SElemAbstraction::Basic => SndMixerSelemRegoptAbstract::Basic, + }, + device: device.as_ptr(), + playback_pcm: std::ptr::null(), + capture_pcm: std::ptr::null(), + }; + let error_code = unsafe {snd_mixer_selem_register(mixer, &options, std::ptr::null_mut())}; + if error_code == 0 { + Ok(()) + } else { + Err(AlsaVolumeError::FailedToOpenMixer) + } +} + +fn get_volume_for_channel(element : SndMixerElemHandle, channel : SndMixerSelemChannelIdT) -> Option<c_long>{ + if unsafe {snd_mixer_selem_has_playback_channel(element, channel) > 0} { + let mut value : c_long = 0; + if unsafe { snd_mixer_selem_get_playback_volume(element, channel, &mut value) == 0}{ + Some(value) + } else { + None + } + } else { + None + } +} + +fn get_switch_for_channel(element : SndMixerElemHandle, channel : SndMixerSelemChannelIdT) -> bool { + let mut switch = 0; + let worked = unsafe { snd_mixer_selem_get_playback_switch(element, channel, &mut switch)}; + worked == 0 && switch != 0 +} + +fn get_volume_range(element : SndMixerElemHandle) -> Option<(c_long, c_long)>{ + let mut min = 0; + let mut max = 0; + if unsafe { snd_mixer_selem_get_playback_volume_range(element, &mut min, &mut max) == 0 } { + Some((min, max)) + } else { + None + } +} + +#[repr(C)] struct SndMixerT { _private: [u8; 0]} +#[repr(C)] struct SndPcmT { _private: [u8; 0]} +#[repr(C)] struct SndMixerClassT { _private: [u8; 0]} +#[repr(C)] struct SndMixerElemT { _private: [u8; 0]} + +type SndMixerHandle = *const SndMixerT; +type SndMixerElemHandle = *const SndMixerElemT; + +#[repr(C)] enum SndMixerSelemRegoptAbstract { + None, + Basic, +} +#[repr(C)] struct SndMixerSelemRegopt { + ver : c_int, + abstraction : SndMixerSelemRegoptAbstract, + device : *const c_char, + playback_pcm : *const SndPcmT, + capture_pcm : *const SndPcmT, +} + +#[derive(Clone,Copy)] +#[repr(C)] enum SndMixerSelemChannelIdT { + FrontLeft, + FrontRight, + RearLeft, + RearRight, + FrontCenter, + Woofer, + SideLeft, + SideRight, + RearCenter, +} + +static ALL_CHANNELS : [SndMixerSelemChannelIdT;9] = [ + SndMixerSelemChannelIdT::FrontLeft, + SndMixerSelemChannelIdT::FrontRight, + SndMixerSelemChannelIdT::RearLeft, + SndMixerSelemChannelIdT::RearRight, + SndMixerSelemChannelIdT::FrontCenter, + SndMixerSelemChannelIdT::Woofer, + SndMixerSelemChannelIdT::SideLeft, + SndMixerSelemChannelIdT::SideRight, + SndMixerSelemChannelIdT::RearCenter, +]; + + +type SndMixerCallbackT = extern "C" fn(SndMixerHandle, c_uint, SndMixerElemHandle) -> c_int; +type SndMixerElemCallbackT = extern "C" fn(SndMixerElemHandle, c_uint) -> c_int; +#[link(name = "asound")] +extern "C" { + //int snd_mixer_open ( snd_mixer_t ** mixerp, int mode ) + fn snd_mixer_open(mixer : *mut SndMixerHandle, mode : c_int) -> c_int; + //int snd_mixer_close ( snd_mixer_t * mixer ) + fn snd_mixer_close(mixer : SndMixerHandle) -> c_int; + //int snd_mixer_selem_register(snd_mixer_t *mixer, struct snd_mixer_selem_regopt *options, snd_mixer_class_t **classp); + fn snd_mixer_selem_register(mixer : SndMixerHandle, options: *const SndMixerSelemRegopt, class: *mut *const SndMixerClassT) -> c_int; + //void snd_mixer_set_callback ( snd_mixer_t * obj, snd_mixer_callback_t val ) + fn snd_mixer_set_callback(mixer : SndMixerHandle, callback : Option<SndMixerCallbackT>); + //void snd_mixer_set_callback_private ( snd_mixer_t * mixer, void * val ) + fn snd_mixer_set_callback_private(mixer : SndMixerHandle, value : *const c_void); //the *const is a lie, but one that we need for Stacked Borrows sanity. + //void* snd_mixer_get_callback_private ( const snd_mixer_t * mixer ) + fn snd_mixer_get_callback_private(mixer : SndMixerHandle) -> *const c_void; //the *const is a lie, but one that we need for Stacked Borrows sanity. + + //int snd_mixer_load ( snd_mixer_t * mixer ) + fn snd_mixer_load(mixer : SndMixerHandle) -> c_int; + + //const char* snd_mixer_selem_get_name ( snd_mixer_elem_t * elem ) + fn snd_mixer_selem_get_name(element: SndMixerElemHandle) -> *const c_char; + //void snd_mixer_elem_set_callback ( snd_mixer_elem_t * mixer, snd_mixer_elem_callback_t val ) + fn snd_mixer_elem_set_callback(element : SndMixerElemHandle, callback : Option<SndMixerElemCallbackT>); + //void snd_mixer_elem_set_callback_private ( snd_mixer_elem_t * mixer, void * val ) + fn snd_mixer_elem_set_callback_private(element : SndMixerElemHandle, value : *const c_void); + //void* snd_mixer_elem_get_callback_private ( const snd_mixer_elem_t * mixer ) + fn snd_mixer_elem_get_callback_private(element : SndMixerElemHandle) -> *const c_void; + + //int snd_mixer_selem_has_playback_channel ( snd_mixer_elem_t * elem, snd_mixer_selem_channel_id_t channel ) + fn snd_mixer_selem_has_playback_channel(element : SndMixerElemHandle, channel : SndMixerSelemChannelIdT) -> c_int; + //int snd_mixer_selem_get_playback_volume ( snd_mixer_elem_t * elem, snd_mixer_selem_channel_id_t channel, long * value ) + fn snd_mixer_selem_get_playback_volume(element : SndMixerElemHandle, channel : SndMixerSelemChannelIdT, value : *mut c_long) -> c_int; + //int snd_mixer_selem_get_playback_volume_range ( snd_mixer_elem_t * elem, long * min, long * max ) + fn snd_mixer_selem_get_playback_volume_range(element : SndMixerElemHandle, min: *mut c_long, max : *mut c_long) -> c_int; + + //int snd_mixer_selem_has_playback_switch ( snd_mixer_elem_t * elem ) + fn snd_mixer_selem_has_playback_switch(element : SndMixerElemHandle) -> c_int; + //int snd_mixer_selem_get_playback_switch ( snd_mixer_elem_t * elem, snd_mixer_selem_channel_id_t channel, int * value ) + fn snd_mixer_selem_get_playback_switch(element : SndMixerElemHandle, channel : SndMixerSelemChannelIdT, value : *mut c_int) -> c_int; + + //int snd_mixer_poll_descriptors_count ( snd_mixer_t * mixer ) + fn snd_mixer_poll_descriptors_count(mixer : SndMixerHandle) -> c_int; + //int snd_mixer_poll_descriptors ( snd_mixer_t * mixer, struct pollfd * pfds, unsigned int space ) + fn snd_mixer_poll_descriptors(mixer : SndMixerHandle, fds : *mut libc::pollfd, space : c_uint) -> c_int; + + //int snd_mixer_poll_descriptors_revents ( snd_mixer_t * mixer, struct pollfd * pfds, unsigned int nfds, unsigned short * revents ) + fn snd_mixer_poll_descriptors_revents(mixer : SndMixerHandle, fds : *const libc::pollfd, nfds : c_uint, revents : *mut c_ushort) -> c_int; + + //int snd_mixer_handle_events ( snd_mixer_t * mixer ) + fn snd_mixer_handle_events(mixer : SndMixerHandle) -> c_int; +}
\ No newline at end of file |
