From 6a37ba7f7f1b71a5e78a1f9bb4fb6513d8cf844c Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Wed, 4 Oct 2017 00:26:49 +0200 Subject: [PATCH] Blubb --- src/audio/pulseaudio.rs | 271 ++++++++++++++++++++++++++++++++++++++ src/bin.rs | 70 ++-------- src/errors.rs | 4 + src/lib.rs | 3 +- src/support/audio.rs | 2 + src/support/mod.rs | 1 + src/support/pulseaudio.rs | 86 ++++++++++++ 7 files changed, 376 insertions(+), 61 deletions(-) create mode 100644 src/support/pulseaudio.rs diff --git a/src/audio/pulseaudio.rs b/src/audio/pulseaudio.rs index e69de29bb..27f2acc02 100644 --- a/src/audio/pulseaudio.rs +++ b/src/audio/pulseaudio.rs @@ -0,0 +1,271 @@ +//! Pulseaudio backend subsystem. + +use audio::frontend::*; +use errors::*; +use libc; +use libpulse_sys::*; +use std::cell::RefCell; +use std::ffi::{CString, CStr}; +use std::mem; +use std::os::raw::c_char; +use std::ptr; +use support::pulseaudio::*; +use support::audio::*; + + +// TODO: get info based on index, not descr. +// +// TODO: how to hook pulseaudio events? port change? +// TODO: how to handle channels + + +// TODO: update when sink changes +#[derive(Clone, Debug)] +pub struct Sink { + pub name: String, + pub index: u32, + pub description: String, + pub channels: u8, + pub base_vol: u32, +} + + +pub struct PABackend { + _cannot_construct: (), + m: *mut pa_threaded_mainloop, + c: *mut pa_context, + pub sink: RefCell, +} + + +impl PABackend { + pub fn new(sink_desc: Option) -> Result { + unsafe { + let mainloop: *mut pa_threaded_mainloop = pa_threaded_mainloop_new(); + + ensure!(!mainloop.is_null(), "Main loop is null"); + + let api: *mut pa_mainloop_api = pa_threaded_mainloop_get_api(mainloop); + + let context_name = CString::new("pnmixer-rs").unwrap(); + let context: *mut pa_context = pa_context_new(api, + context_name.as_ptr()); + + if context.is_null() { + pa_threaded_mainloop_free(mainloop); + bail!("Couldn't create context"); + } + + pa_context_set_state_callback(context, + Some(context_state_cb), + mainloop as *mut libc::c_void); + // TODO: don't spawn new daemon + let cret = pa_context_connect(context, + ptr::null_mut(), + 0, + ptr::null_mut()); + + if cret < 0 { + pa_context_unref(context); + pa_threaded_mainloop_free(mainloop); + bail!("Couldn't connect to daemon"); + } + + let mret = pa_threaded_mainloop_start(mainloop); + + if mret < 0 { + pa_context_unref(context); + pa_threaded_mainloop_free(mainloop); + bail!("Couldn't start main loop"); + } + + pa_threaded_mainloop_lock(mainloop); + while !CONTEXT_READY { + pa_threaded_mainloop_wait(mainloop); + } + pa_threaded_mainloop_accept(mainloop); + pa_threaded_mainloop_unlock(mainloop); + CONTEXT_READY = false; + + let sink = { + match sink_desc.as_ref().map(|s| s.as_str()) { + Some("(default)") => get_first_sink(mainloop, context)?, + Some(sd) => { + let mysink = get_sink_by_desc(mainloop, context, sd); + match mysink { + Ok(s) => s, + Err(_) => { + warn!("Could not find sink with name {}, trying others", sd); + get_first_sink(mainloop, context)? + } + } + + } + None => get_first_sink(mainloop, context)? + } + + }; + + return Ok(PABackend { + _cannot_construct: (), + m: mainloop, + c: context, + sink: RefCell::new(sink), + }) + } + } + + pub fn get_vol(&self) -> Result { + + let mut vol: Result = Err("No value".into()); + unsafe { + + pa_threaded_mainloop_lock(self.m); + let _data = &mut(self.m, &mut vol); + let data = mem::transmute::<&mut(*mut pa_threaded_mainloop, + &mut Result), + *mut libc::c_void>(_data); + let sink_name = CString::new(self.sink.borrow().name.clone()).unwrap().into_raw(); + let o = pa_context_get_sink_info_by_name(self.c, + sink_name, + Some(get_sink_vol), + data); + if o.is_null() { + pa_threaded_mainloop_unlock(self.m); + bail!("Failed to initialize PA operation!"); + } + + while pa_operation_get_state(o) == PA_OPERATION_RUNNING { + pa_threaded_mainloop_wait(self.m); + } + pa_operation_unref(o); + pa_threaded_mainloop_unlock(self.m); + + let _ = CString::from_raw(sink_name); + } + return vol; + } + + pub fn set_vol(&self, new_vol: f64, dir: VolDir) -> Result<()> { + let mut res: Result<()> = Err("No value".into()); + unsafe { + // pa_threaded_mainloop_lock(self.m); + let _data = &mut(self, &mut res); + let data = mem::transmute::<&mut(&PABackend, + &mut Result<()>), + *mut libc::c_void>(_data); + let sink_name = CString::new(self.sink.borrow().name.clone()).unwrap().into_raw(); + + let new_vol = percent_to_vol(new_vol, + (0, self.sink.borrow().base_vol as i64), + dir)?; + let mut vol_arr: [u32; 32] = [0; 32]; + for c in 0..(self.sink.borrow().channels - 1) { + vol_arr[c as usize] = new_vol as u32; + } + let new_cvol = Struct_pa_cvolume { + channels: self.sink.borrow().channels, + values: vol_arr, + + }; + + let o = pa_context_set_sink_volume_by_name(self.c, + sink_name, + &new_cvol as *const pa_cvolume, + Some(set_sink_vol), + data); + + if o.is_null() { + pa_threaded_mainloop_unlock(self.m); + bail!("Failed to initialize PA operation!"); + } + + while pa_operation_get_state(o) == PA_OPERATION_RUNNING { + pa_threaded_mainloop_wait(self.m); + } + pa_operation_unref(o); + pa_threaded_mainloop_unlock(self.m); + } + + return res; + } +} + + +impl Drop for PABackend { + fn drop(&mut self) { + unsafe { + debug!("Stopping PA main loop"); + pa_threaded_mainloop_stop(self.m); + debug!("Freeing PA context"); + pa_context_unref(self.c); + debug!("Freeing main loop"); + pa_threaded_mainloop_free(self.m); + } + } +} + + + + + +static mut CONTEXT_READY: bool = false; + +// TODO: proper error handling +unsafe extern "C" fn context_state_cb( + ctx: *mut pa_context, data: *mut libc::c_void) { + + let mainloop: *mut pa_threaded_mainloop = data as *mut pa_threaded_mainloop; + let state = pa_context_get_state(ctx); + + match state { + PA_CONTEXT_READY => { + println!("Context ready"); + CONTEXT_READY = true; + pa_threaded_mainloop_signal(mainloop, 1); + }, + _ => () + } +} + + +// TODO: Better error handling. +unsafe extern "C" fn get_sink_vol( + ctx: *mut pa_context, + i: *const pa_sink_info, + eol: i32, + data: *mut libc::c_void) { + let &mut(mainloop, res) = mem::transmute::<*mut libc::c_void, + &mut(*mut pa_threaded_mainloop, + *mut Result)>(data); + assert!(!mainloop.is_null(), "Mainloop is null"); + + if i.is_null() { + return + } + + *res = vol_to_percent((*i).volume.values[0] as i64, + (0, (*i).base_volume as i64)); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +// TODO: Missing error handling. +unsafe extern "C" fn set_sink_vol( + ctx: *mut pa_context, + success: i32, + data: *mut libc::c_void) { + let &mut(mainloop, res) = mem::transmute::<*mut libc::c_void, + &mut(*mut pa_threaded_mainloop, + *mut Result<()>)>(data); + assert!(!mainloop.is_null(), "Mainloop is null"); + + if success > 0 { + *res = Ok(()); + } else { + *res = Err("Failed to set volume".into()); + } + + pa_threaded_mainloop_signal(mainloop, 0); +} + diff --git a/src/bin.rs b/src/bin.rs index 16622cfae..11691f122 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -17,72 +17,14 @@ use libpulse_sys::*; use std::ffi::{CString, CStr}; use std::os::raw::c_char; use std::ptr; +use audio::pulseaudio::*; +use support::audio::VolDir; -static mut CONTEXT_READY: bool = false; - - -unsafe extern "C" fn context_state_cb( - ctx: *mut pa_context, data: *mut libc::c_void) { - - let mainloop: *mut pa_threaded_mainloop = data as *mut pa_threaded_mainloop; - let state = pa_context_get_state(ctx); - - match state { - PA_CONTEXT_READY => { - CONTEXT_READY = true; - pa_threaded_mainloop_signal(mainloop, 1); - }, - _ => () - } - -} fn main() { - unsafe { - let mainloop: *mut pa_threaded_mainloop = pa_threaded_mainloop_new(); - if mainloop.is_null() { - panic!("Oh no"); - } - let api: *mut pa_mainloop_api = pa_threaded_mainloop_get_api(mainloop); - - let context_name = CString::new("pnmixer-rs").unwrap(); - let context: *mut pa_context = pa_context_new(api, - context_name.as_ptr()); - - if context.is_null() { - panic!("Oh no"); - } - - pa_context_set_state_callback(context, - Some(context_state_cb), - mainloop as *mut libc::c_void); - let ret = pa_context_connect(context, - std::ptr::null_mut(), - 0, - std::ptr::null_mut()); - - if ret < 0 { - panic!("Oh no"); - } - - let ret = pa_threaded_mainloop_start(mainloop); - - if ret < 0 { - panic!("Oh no"); - } - - pa_threaded_mainloop_lock(mainloop); - while !CONTEXT_READY { - pa_threaded_mainloop_wait(mainloop); - } - pa_threaded_mainloop_accept(mainloop); - pa_threaded_mainloop_unlock(mainloop); - CONTEXT_READY = false; - - } let args: Vec = env::args().collect(); @@ -131,6 +73,14 @@ fn main() { .unwrap_or_else(|e|{panic!("Logger initialization failed with {}",e)}); + let pa = PABackend::new(Some(String::from("Built-in Audio Analog Stereo"))).unwrap(); + println!("Sink: {:?}", pa.sink); + println!("Volume: {:?}", pa.get_vol()); + pa.set_vol(80.0, VolDir::Up).unwrap(); + println!("Volume: {:?}", pa.get_vol()); + + return; + gtk::init() .unwrap_or_else(|e| panic!("Gtk initialization failed with {}", e)); diff --git a/src/errors.rs b/src/errors.rs index 2e7311dec..9834c2c48 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -23,6 +23,10 @@ error_chain! { description("User hit cancel") display("User hit cancel: {}", t) } + PulseAudioError(t: String) { + description("Error in pulseaudio call") + display("Error in pulseaudio call: {}", t) + } } } diff --git a/src/lib.rs b/src/lib.rs index 8829cff2e..1b17d59eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,14 +55,15 @@ extern crate gio; extern crate glib; extern crate glib_sys; extern crate gobject_sys; -pub extern crate gtk; extern crate gtk_sys; extern crate libc; +extern crate libpulse_sys; extern crate png; extern crate w_result; extern crate which; extern crate x11; extern crate xdg; +pub extern crate gtk; #[cfg(feature = "notify")] pub extern crate libnotify; diff --git a/src/support/audio.rs b/src/support/audio.rs index 93d99c6b3..82fef9d8c 100644 --- a/src/support/audio.rs +++ b/src/support/audio.rs @@ -9,6 +9,8 @@ use audio::frontend::*; use errors::*; use prefs::*; + +// TODO: rm alsa use support::alsa::*; diff --git a/src/support/mod.rs b/src/support/mod.rs index f4e4ee122..6aad015a1 100644 --- a/src/support/mod.rs +++ b/src/support/mod.rs @@ -9,5 +9,6 @@ pub mod cmd; pub mod gdk_x11; #[macro_use] pub mod glade; +pub mod pulseaudio; #[macro_use] pub mod ui; diff --git a/src/support/pulseaudio.rs b/src/support/pulseaudio.rs new file mode 100644 index 000000000..d9cd0569f --- /dev/null +++ b/src/support/pulseaudio.rs @@ -0,0 +1,86 @@ +use audio::pulseaudio::Sink; +use errors::*; +use libc; +use libpulse_sys::*; +use std::ffi::CStr; +use std::mem; + + +pub fn get_sinks(mainloop: *mut pa_threaded_mainloop, + ctx: *mut pa_context) -> Result> { + unsafe { + let mut v = vec![]; + { + pa_threaded_mainloop_lock(mainloop); + let _data = &mut(mainloop, &mut v); + let data = mem::transmute::<&mut(*mut pa_threaded_mainloop, + &mut Vec), + *mut libc::c_void>(_data); + let o = pa_context_get_sink_info_list(ctx, + Some(get_all_sinks), + data); + if o.is_null() { + pa_threaded_mainloop_unlock(mainloop); + bail!("Failed to initialize PA operation!"); + } + + while pa_operation_get_state(o) == PA_OPERATION_RUNNING { + pa_threaded_mainloop_wait(mainloop); + } + pa_operation_unref(o); + pa_threaded_mainloop_unlock(mainloop); + } + + return Ok(v); + } +} + +unsafe extern "C" fn get_all_sinks( + ctx: *mut pa_context, + i: *const pa_sink_info, + eol: i32, + data: *mut libc::c_void) { + let &mut(mainloop, vec) = mem::transmute::<*mut libc::c_void, + &mut(*mut pa_threaded_mainloop, + *mut Vec)>(data); + assert!(!mainloop.is_null(), "Mainloop is null"); + + if i.is_null() { + return + } + let name = CStr::from_ptr((*i).name).to_str().unwrap().to_owned(); + let index = (*i).index; + let description = CStr::from_ptr((*i).description).to_str().unwrap().to_owned(); + let channels = (*i).channel_map.channels; + let base_vol = (*i).base_volume; + + (*vec).push(Sink { + name, + index, + description, + channels, + base_vol, + }); + pa_threaded_mainloop_signal(mainloop, 0); +} + +pub fn get_first_sink(mainloop: *mut pa_threaded_mainloop, + ctx: *mut pa_context) -> Result { + let sinks = get_sinks(mainloop, ctx)?; + ensure!(!sinks.is_empty(), "No sinks found!"); + + return Ok(sinks[0].clone()); +} + +// TODO: Could be done directly via PA API. +pub fn get_sink_by_desc(mainloop: *mut pa_threaded_mainloop, + ctx: *mut pa_context, + desc: &str) -> Result { + let sinks = get_sinks(mainloop, ctx)?; + ensure!(!sinks.is_empty(), "No sinks found!"); + + return Ok(sinks.into_iter() + .find(|s| s.description == desc) + .map(|s| s.clone()) + .ok_or("No sink found with desc")?); +}