cpvc 0.6.0

A simple crate + wrapper for controlling system audio cross platform
Documentation
// #[cfg(not(target_os="linux"))]
#[cfg(target_os="linux")]

mod device {

    use std::sync::{Arc, Mutex};
    use crate::{debug_eprintln, debug_println, device::DeviceTrait, error::Error, pulseaudio};

    pub struct PulseAudioDevice {
        dev_str: String,
    }

    impl DeviceTrait for PulseAudioDevice {

        // Name is device description (e.g. "Dummy Output")
        fn from_name(name: String) -> Result<Self, Error> {
            let devices = pulseaudio::get_device_identifiers()?;

            for (id, names) in devices {
                if name == names {
                    return Ok(PulseAudioDevice {
                        dev_str: id,
                    });
                }
            }
            
            Err(Error::DeviceNotFound)

        }

        // UID is device name (e.g. "auto_null")
        fn from_uid(id: String) -> Result<Self, Error> {
            let devices = pulseaudio::get_device_identifiers()?;

            for (dev_str, _name) in devices {
                if id == dev_str {
                    return Ok(PulseAudioDevice {
                        dev_str,
                    });
                }
            }
            
            Err(Error::DeviceNotFound)
        }


        fn get_name(&self) -> Result<String, Error> {
            pulseaudio::get_device_name(self.dev_str.clone())
        }


        fn get_vol(&self) -> Result<f32, Error> {
            let mut vol = 0.0;
            let volume: Arc<Mutex<f32>> = Arc::new(Mutex::new(vol));
            let clone = Arc::clone(&volume);
            let dev_str = self.dev_str.clone();
            let (mut mainloop, context) = pulseaudio::acquire_mainloop_and_context();

            let changed = Arc::new(Mutex::new(false));
            let changed_clone = changed.clone();

            let error = Arc::new(Mutex::new(None));
            let err_clone = error.clone();

            let op = context.introspect().get_sink_info_list( move |info | {
                    match info {
                        libpulse_binding::callbacks::ListResult::Item(device) => {
                            if let Some(name) = &device.name && name.to_string() == dev_str{
                                if device.mute {
                                    *clone.lock().unwrap() = 0.0;
                                } else {
                                    let mut vol_str = device.volume.avg().print().trim().to_string();
                                    vol_str.remove(vol_str.len() - 1);
                                    match vol_str.parse::<u8>() {
                                        Ok(vol) => {
                                            *clone.lock().unwrap() = vol as f32 / 100.0;
                                        },
                                        Err(err) => {
                                            err_clone.lock().unwrap().replace(Error::VolumeCaptureFailed(format!("Failed to parse volume string {}", err))); 
                                            debug_eprintln(&format!("Failed to parse volume string {}", err));
                                        }
                                    }
                                }
                                *changed_clone.lock().unwrap() = true;
                            }
                        },
                        libpulse_binding::callbacks::ListResult::End => {
                            debug_println("Devices finished")
                        },
                        libpulse_binding::callbacks::ListResult::Error => {
                            err_clone.lock().unwrap().replace(Error::VolumeCaptureFailed(format!("ListResult Access 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));

            vol = *volume.lock().unwrap();
            if let Some(error) = error.lock().unwrap().take() {
                Err(error)
            } else if *changed.lock().unwrap() {
                Ok(vol)
            } else {
                Err(Error::VolumeCaptureFailed(format!("Failed to detect device")))
            }
        }

        fn set_vol(&self, value: f32) -> Result<(), Error> {
            let mut success = None;
            let vol_channels = Arc::new(Mutex::new(None));
            let clone = Arc::clone(&vol_channels);
            let dev_str = self.dev_str.clone();

            let changed = Arc::new(Mutex::new(false));
            let changed_clone = changed.clone();

            let error = Arc::new(Mutex::new(None));
            let err_clone = error.clone();

            let (mut mainloop, context) = pulseaudio::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(name) = &device.name && name.to_string() == dev_str {
                                use libpulse_binding::volume::{Volume};
                                use libpulse_sys::volume::PA_VOLUME_NORM;
                                let vol = Volume((value * PA_VOLUME_NORM as f32) as u32);
                                let mut channel_vols = device.volume;
                                channel_vols.set(device.sample_spec.channels, vol.into());
                                clone.lock().unwrap().replace((device.index, channel_vols));
                                *changed_clone.lock().unwrap() = true;
                            }
                        },
                        libpulse_binding::callbacks::ListResult::End => {
                            debug_println("channel volume aquired");
                        },
                        libpulse_binding::callbacks::ListResult::Error => {
                            err_clone.lock().unwrap().replace(Error::VolumeCaptureFailed(format!("ListResult Access Error"))); 
                            debug_eprintln("error gathering device information");    
                        },
                    }
                });

            
            while op.get_state() == libpulse_binding::operation::State::Running {
                mainloop.iterate(false);
            }

            if let Some((index, volume)) = vol_channels.lock().unwrap().take() {
                let vol_runner;
                if value == 0.0 {
                    vol_runner = context.introspect().set_sink_mute_by_index(index, true, None);
                } else {
                    vol_runner = context.introspect().set_sink_volume_by_index(index, &volume, None);
                }             
                while vol_runner.get_state() == libpulse_binding::operation::State::Running {
                    mainloop.iterate(false);
                    success = Some(true);
                }
            } else {
                success = Some(false);
            }

            mainloop.quit(libpulse_binding::def::Retval(0));

            if let Some(error) = error.lock().unwrap().take() {
                Err(error)
            } else if !*changed.lock().unwrap() {
                Err(Error::VolumeCaptureFailed(format!("Failed to detect device")))
            } else {
                match success {
                    Some(val) => {
                        if val {
                            Ok(())
                        } else {
                            Err(Error::VolumeCaptureFailed(format!("Failed to adjust device volume")))
                        }
                    }
                    None => {
                        Err(Error::VolumeCaptureFailed(format!("Failed to adjust device volume")))
                    }
                }
            }
        }

        fn get_mute(&self) -> Result<bool, Error> {
            let mute;
            let mute_status = Arc::new(Mutex::new(0));
            let clone = Arc::clone(&mute_status);
            let dev_str = self.dev_str.clone();

            let (mut mainloop, context) = pulseaudio::acquire_mainloop_and_context();

            let changed = Arc::new(Mutex::new(false));
            let changed_clone = changed.clone();

            let error = Arc::new(Mutex::new(None));
            let err_clone = error.clone();

            let op = context.introspect().get_sink_info_list( move |info | {
                    match info {
                        libpulse_binding::callbacks::ListResult::Item(device) => {
                            
                            if let Some(name) = &device.name && name.to_string() == dev_str {
                                if device.mute {
                                    *clone.lock().unwrap() = 1;
                                }
                                *changed_clone.lock().unwrap() = true;
                            }
                        },
                        libpulse_binding::callbacks::ListResult::End => {
                            debug_println("Devices finished")
                        },
                        libpulse_binding::callbacks::ListResult::Error => {
                            err_clone.lock().unwrap().replace(Error::VolumeCaptureFailed(format!("ListResult Access 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));

            mute = *mute_status.lock().unwrap();
            if let Some(error) = error.lock().unwrap().take() {
                Err(error)
            } else if !*changed.lock().unwrap() {
                Err(Error::VolumeCaptureFailed(format!("Failed to detect device")))
            } else {
                match mute {
                    1 => {
                        Ok(true)
                    }
                    _ => {
                        Ok(false)
                    }
                }
            }
        }

        fn set_mute(&self, mute: bool) -> Result<(), Error> {
            let mut status = false;
            let dev_index = Arc::new(Mutex::new(None));
            let dev_str = self.dev_str.clone();
            let clone = Arc::clone(&dev_index);

            let (mut mainloop, context) = pulseaudio::acquire_mainloop_and_context();

            let changed = Arc::new(Mutex::new(false));
            let changed_clone = changed.clone();

            let error = Arc::new(Mutex::new(None));
            let err_clone = error.clone();

            let op = context.introspect().get_sink_info_list( move |info | {
                    match info {
                        libpulse_binding::callbacks::ListResult::Item(device) => {
                            if let Some(name) = &device.name && name.to_string() == dev_str{
                                clone.lock().unwrap().replace(device.index);
                                *changed_clone.lock().unwrap() = true;
                            }
                        },
                        libpulse_binding::callbacks::ListResult::End => {
                            debug_println("channel volume aquired");
                        },
                        libpulse_binding::callbacks::ListResult::Error => {
                            err_clone.lock().unwrap().replace(Error::VolumeCaptureFailed(format!("ListResult Access Error"))); 
                            debug_eprintln("error gathering device information");    
                        },
                    }
                });

            
            while op.get_state() == libpulse_binding::operation::State::Running {
                mainloop.iterate(false);
            }


            if let Some(index) = dev_index.lock().unwrap().take() {
                let mute_runner;
                mute_runner = context.introspect().set_sink_mute_by_index(index, mute, None);        
                while mute_runner.get_state() == libpulse_binding::operation::State::Running {
                    mainloop.iterate(false);
                    status = true;
                }
            } else {
                status = false;
            }
            mainloop.quit(libpulse_binding::def::Retval(0));
            if let Some(error) = error.lock().unwrap().take() {
                Err(error)
            } else if !*changed.lock().unwrap() {
                Err(Error::VolumeCaptureFailed(format!("Failed to detect device")))
            } else {
                match status {
                    true => {
                        Ok(())
                    }
                    false => {
                        Err(Error::VolumeCaptureFailed(format!("Failed to adjust device volume")))
                    }
                }
            }
        } 
    }

    impl PulseAudioDevice {
        pub fn get_device_str(&self) -> String {
            self.dev_str.clone()
        }
    }
    
}

#[cfg(not(target_os="linux"))]
// #[cfg(target_os="linux")]

mod device {

    use std::sync::{Arc, Mutex};
    use crate::{debug_eprintln, debug_println, device::DeviceTrait, error::Error, pulseaudio};

    pub struct PulseAudioDevice {
        dev_str: String,
    }

    impl DeviceTrait for PulseAudioDevice {}

    impl PulseAudioDevice {
        pub fn get_device_str(&self) -> String {
            String::from("CPVC: Platform Unsupported")
        }
    }
    
}

pub(crate) use device::*;