aboutsummaryrefslogtreecommitdiff
path: root/alsa
diff options
context:
space:
mode:
Diffstat (limited to 'alsa')
-rw-r--r--alsa/Cargo.toml18
-rw-r--r--alsa/src/communication.rs68
-rw-r--r--alsa/src/communication/pipe_chan.rs172
-rw-r--r--alsa/src/config.rs104
-rw-r--r--alsa/src/lib.rs37
-rw-r--r--alsa/src/runnable.rs397
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