Compare commits
4 Commits
master
...
pulseaudio
Author | SHA1 | Date | |
---|---|---|---|
9e4653fbd1 | |||
0f3652a6b2 | |||
6a37ba7f7f | |||
a0529124a6 |
@ -34,6 +34,7 @@ gtk-sys = "0.4.0"
|
||||
lazy_static = "0.2.8"
|
||||
libc = "0.2.31"
|
||||
libnotify = { version = "^1.0.2", optional = true }
|
||||
libpulse-sys = "0.0.0"
|
||||
log = "0.3.8"
|
||||
png = "0.10.0"
|
||||
serde = "1.0.15"
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
use audio::alsa::backend::*;
|
||||
use audio::pulseaudio::*;
|
||||
use audio::frontend::*;
|
||||
use errors::*;
|
||||
use gtk;
|
||||
@ -52,6 +53,19 @@ pub fn new_alsa_appstate() -> AppS<AlsaBackend> {
|
||||
return AppS::new(prefs, audio);
|
||||
}
|
||||
|
||||
/// Create a new application state using the `PABackend`.
|
||||
pub fn new_pa_appstate() -> AppS<PABackend> {
|
||||
let prefs = RefCell::new(unwrap_error!(Prefs::new(), None));
|
||||
|
||||
let card_name = prefs.borrow().device_prefs.card.clone();
|
||||
let chan_name = prefs.borrow().device_prefs.channel.clone();
|
||||
let audio = Rc::new(unwrap_error!(
|
||||
PABackend::new(Some(card_name), Some(chan_name)),
|
||||
None
|
||||
));
|
||||
return AppS::new(prefs, audio);
|
||||
}
|
||||
|
||||
|
||||
impl<T> AppS<T>
|
||||
where
|
||||
|
@ -136,8 +136,20 @@ impl AudioFrontend for AlsaBackend {
|
||||
return Ok(n);
|
||||
}
|
||||
|
||||
fn playable_chan_names(&self) -> Vec<String> {
|
||||
return get_playable_selem_names(&self.acard.borrow().mixer);
|
||||
fn playable_chan_names(&self, cardname: Option<String>) -> Vec<String> {
|
||||
match cardname {
|
||||
Some(name) => {
|
||||
let card = try_r!(get_alsa_card_by_name(name), Vec::default());
|
||||
let mixer = try_r!(get_mixer(&card), Vec::default());
|
||||
|
||||
return get_playable_selem_names(&mixer);
|
||||
},
|
||||
None => self.acard.borrow().playable_chan_names(),
|
||||
}
|
||||
}
|
||||
|
||||
fn playable_card_names(&self) -> Vec<String> {
|
||||
return get_playable_alsa_card_names();
|
||||
}
|
||||
|
||||
fn get_vol(&self) -> Result<f64> {
|
||||
@ -286,29 +298,6 @@ impl AudioFrontend for AlsaBackend {
|
||||
}
|
||||
|
||||
|
||||
/// Invokes the registered handlers.
|
||||
fn invoke_handlers(
|
||||
handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>,
|
||||
signal: AudioSignal,
|
||||
user: AudioUser,
|
||||
) {
|
||||
debug!(
|
||||
"Invoking handlers for signal {:?} by user {:?}",
|
||||
signal,
|
||||
user
|
||||
);
|
||||
if handlers.is_empty() {
|
||||
debug!("No handler found");
|
||||
} else {
|
||||
debug!("Executing handlers")
|
||||
}
|
||||
for handler in handlers {
|
||||
let unboxed = handler.as_ref();
|
||||
unboxed(signal, user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The callback for alsa events that is passed to the alsa subsystem.
|
||||
/// This is the bridge between low-level alsa events and "high-level"
|
||||
/// audio system signals.
|
||||
|
@ -184,6 +184,11 @@ impl AlsaCard {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get playable channel names of the given card.
|
||||
pub fn playable_chan_names(&self) -> Vec<String> {
|
||||
return get_playable_selem_names(&self.mixer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -153,8 +153,11 @@ pub trait AudioFrontend {
|
||||
/// Get the current card name.
|
||||
fn card_name(&self) -> Result<String>;
|
||||
|
||||
/// Get the currently playable card names.
|
||||
fn playable_card_names(&self) -> Vec<String>;
|
||||
|
||||
/// Get the currently playable channel names.
|
||||
fn playable_chan_names(&self) -> Vec<String>;
|
||||
fn playable_chan_names(&self, cardname: Option<String>) -> Vec<String>;
|
||||
|
||||
/// Get the current active channel name.
|
||||
fn chan_name(&self) -> Result<String>;
|
||||
|
@ -2,3 +2,4 @@
|
||||
|
||||
pub mod alsa;
|
||||
pub mod frontend;
|
||||
pub mod pulseaudio;
|
||||
|
562
src/audio/pulseaudio.rs
Normal file
562
src/audio/pulseaudio.rs
Normal file
@ -0,0 +1,562 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
|
||||
//! Pulseaudio backend subsystem.
|
||||
|
||||
use audio::frontend::*;
|
||||
use errors::*;
|
||||
use libc;
|
||||
use libpulse_sys::*;
|
||||
use std::cell::Cell;
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::{CString};
|
||||
use std::ptr;
|
||||
use support::pulseaudio::*;
|
||||
use support::audio::*;
|
||||
|
||||
|
||||
pub const PA_VOLUME_MUTED: i64 = 0x0;
|
||||
pub const PA_VOLUME_NORM: i64 = 0x10000;
|
||||
|
||||
|
||||
|
||||
// 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, only name and description are const
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Sink {
|
||||
pub name: String,
|
||||
pub index: u32,
|
||||
pub description: String,
|
||||
pub channels: u8,
|
||||
}
|
||||
|
||||
impl Sink {
|
||||
pub fn new(sink_desc: Option<String>,
|
||||
chan_name: Option<String>,
|
||||
mainloop: *mut pa_threaded_mainloop,
|
||||
context: *mut pa_context) -> Result<Self> {
|
||||
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(sink);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct PABackend {
|
||||
_cannot_construct: (),
|
||||
m: *mut pa_threaded_mainloop,
|
||||
c: *mut pa_context,
|
||||
pub sink: RefCell<Sink>,
|
||||
pub scroll_step: Cell<u32>,
|
||||
pub handlers: Handlers,
|
||||
}
|
||||
|
||||
|
||||
impl PABackend {
|
||||
pub fn new(sink_desc: Option<String>, chan_name: Option<String>) -> Result<Self> {
|
||||
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 = Sink::new(sink_desc, chan_name, mainloop, context)?;
|
||||
|
||||
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
|
||||
let mut success: bool = false;
|
||||
let data = &mut(mainloop, &mut success);
|
||||
let o = pa_context_subscribe(context,
|
||||
PA_SUBSCRIPTION_MASK_SINK,
|
||||
Some(context_subscribe_cb),
|
||||
data as *mut _ as *mut libc::c_void);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
let handlers = Handlers::new();
|
||||
let cb_box = {
|
||||
let h_ref: &Vec<Box<Fn(AudioSignal, AudioUser)>> = &handlers.borrow();
|
||||
Box::new((mainloop, h_ref as *const Vec<Box<Fn(AudioSignal, AudioUser)>>))
|
||||
};
|
||||
{
|
||||
pa_context_set_subscribe_callback(context,
|
||||
Some(sub_callback),
|
||||
Box::into_raw(cb_box) as *mut libc::c_void);
|
||||
|
||||
}
|
||||
|
||||
|
||||
return Ok(PABackend {
|
||||
_cannot_construct: (),
|
||||
m: mainloop,
|
||||
c: context,
|
||||
sink: RefCell::new(sink),
|
||||
scroll_step: Cell::new(5),
|
||||
handlers,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl AudioFrontend for PABackend {
|
||||
// TODO
|
||||
fn switch_card(
|
||||
&self,
|
||||
card_name: Option<String>,
|
||||
elem_name: Option<String>,
|
||||
user: AudioUser,
|
||||
) -> Result<()> {
|
||||
{
|
||||
let mut ac = self.sink.borrow_mut();
|
||||
*ac = Sink::new(card_name, elem_name, self.m, self.c)?;
|
||||
}
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn get_vol(&self) -> Result<f64> {
|
||||
|
||||
let mut vol: u32 = 0;
|
||||
unsafe {
|
||||
|
||||
pa_threaded_mainloop_lock(self.m);
|
||||
let data = &mut(self, &mut vol);
|
||||
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 as *mut _ as *mut libc::c_void);
|
||||
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_to_percent(vol as i64, (PA_VOLUME_MUTED,
|
||||
PA_VOLUME_NORM))
|
||||
}
|
||||
|
||||
|
||||
fn set_vol(&self, new_vol: f64, user: AudioUser, dir: VolDir, auto_unmute: bool) -> Result<()> {
|
||||
let mut res: Result<()> = Err("No value".into());
|
||||
let new_vol = percent_to_vol(new_vol, (PA_VOLUME_MUTED,
|
||||
PA_VOLUME_NORM), dir)?;
|
||||
unsafe {
|
||||
pa_threaded_mainloop_lock(self.m);
|
||||
let data = &mut(self, &mut res);
|
||||
let sink_name = CString::new(self.sink.borrow().name.clone()).unwrap().into_raw();
|
||||
|
||||
let mut vol_arr: [u32; 32] = [0; 32];
|
||||
for c in 0..(self.sink.borrow().channels) {
|
||||
vol_arr[c as usize] = new_vol as u32;
|
||||
}
|
||||
let mut new_cvol = Struct_pa_cvolume {
|
||||
channels: self.sink.borrow().channels,
|
||||
values: vol_arr,
|
||||
};
|
||||
|
||||
assert!(pa_cvolume_valid(&mut new_cvol as *mut pa_cvolume) != 0, "Invalid cvolume");
|
||||
|
||||
let o = pa_context_set_sink_volume_by_name(self.c,
|
||||
sink_name,
|
||||
&new_cvol as *const pa_cvolume,
|
||||
Some(set_sink_vol),
|
||||
data as *mut _ as *mut libc::c_void);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
fn vol_level(&self) -> VolLevel {
|
||||
let muted = self.get_mute().unwrap_or(false);
|
||||
if muted {
|
||||
return VolLevel::Muted;
|
||||
}
|
||||
let cur_vol = try_r!(self.get_vol(), VolLevel::Muted);
|
||||
match cur_vol {
|
||||
0. => return VolLevel::Off,
|
||||
0.0...33.0 => return VolLevel::Low,
|
||||
0.0...66.0 => return VolLevel::Medium,
|
||||
0.0...100.0 => return VolLevel::High,
|
||||
_ => return VolLevel::Off,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn increase_vol(&self, user: AudioUser, auto_unmute: bool) -> Result<()> {
|
||||
let old_vol = self.get_vol()?;
|
||||
let new_vol = old_vol + (self.scroll_step.get() as f64);
|
||||
|
||||
return self.set_vol(new_vol, user, VolDir::Up, auto_unmute);
|
||||
}
|
||||
|
||||
fn decrease_vol(&self, user: AudioUser, auto_unmute: bool) -> Result<()> {
|
||||
let old_vol = self.get_vol()?;
|
||||
let new_vol = old_vol - (self.scroll_step.get() as f64);
|
||||
|
||||
return self.set_vol(new_vol, user, VolDir::Down, auto_unmute);
|
||||
}
|
||||
|
||||
|
||||
fn has_mute(&self) -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
fn get_mute(&self) -> Result<bool> {
|
||||
let mut mute: bool = false;
|
||||
unsafe {
|
||||
|
||||
pa_threaded_mainloop_lock(self.m);
|
||||
let data = &mut(self, &mut mute);
|
||||
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_mute),
|
||||
data as *mut _ as *mut libc::c_void);
|
||||
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 Ok(mute);
|
||||
}
|
||||
|
||||
|
||||
fn set_mute(&self, mute: bool, user: AudioUser) -> Result<()> {
|
||||
let mut res: Result<()> = Err("No value".into());
|
||||
unsafe {
|
||||
pa_threaded_mainloop_lock(self.m);
|
||||
let data = &mut(self, &mut res);
|
||||
let sink_name = CString::new(self.sink.borrow().name.clone()).unwrap().into_raw();
|
||||
|
||||
let o = pa_context_set_sink_mute_by_name(self.c,
|
||||
sink_name,
|
||||
mute as i32,
|
||||
Some(set_sink_mute),
|
||||
data as *mut _ as *mut libc::c_void);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
fn toggle_mute(&self, user: AudioUser) -> Result<()> {
|
||||
let muted = self.get_mute()?;
|
||||
return self.set_mute(!muted, user);
|
||||
}
|
||||
|
||||
|
||||
// TODO
|
||||
fn connect_handler(&self, cb: Box<Fn(AudioSignal, AudioUser)>) {
|
||||
self.handlers.add_handler(cb);
|
||||
}
|
||||
|
||||
// TODO: name or desc?
|
||||
fn card_name(&self) -> Result<String> {
|
||||
return Ok(self.sink.borrow().description.clone())
|
||||
}
|
||||
|
||||
fn playable_card_names(&self) -> Vec<String> {
|
||||
let sinks = try_r!(get_sinks(self.m, self.c), vec![]);
|
||||
return sinks.iter().map(|s| s.description.clone()).collect();
|
||||
}
|
||||
|
||||
// TODO
|
||||
fn playable_chan_names(&self, cardname: Option<String>) -> Vec<String> {
|
||||
return vec![]
|
||||
}
|
||||
|
||||
// TODO
|
||||
fn chan_name(&self) -> Result<String> {
|
||||
return Ok(String::from("Blah"))
|
||||
}
|
||||
|
||||
fn set_scroll_step(&self, scroll_step: u32) {
|
||||
self.scroll_step.set(scroll_step);
|
||||
}
|
||||
|
||||
fn get_scroll_step(&self) -> u32 {
|
||||
return self.scroll_step.get();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 => {
|
||||
CONTEXT_READY = true;
|
||||
pa_threaded_mainloop_signal(mainloop, 1);
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: Better error handling.
|
||||
unsafe extern "C" fn get_sink_vol(
|
||||
_: *mut pa_context,
|
||||
i: *const pa_sink_info,
|
||||
_: i32,
|
||||
data: *mut libc::c_void) {
|
||||
let (_self, res) = *(data as *mut (*mut PABackend,
|
||||
*mut u32));
|
||||
assert!(!(*_self).m.is_null(), "Mainloop is null");
|
||||
assert!(!res.is_null(), "res is null");
|
||||
|
||||
if i.is_null() {
|
||||
return
|
||||
}
|
||||
|
||||
*res = (*i).volume.values[0];
|
||||
|
||||
pa_threaded_mainloop_signal((*_self).m, 0);
|
||||
}
|
||||
|
||||
// TODO: Better error handling.
|
||||
unsafe extern "C" fn get_sink_mute(
|
||||
_: *mut pa_context,
|
||||
i: *const pa_sink_info,
|
||||
_: i32,
|
||||
data: *mut libc::c_void) {
|
||||
let (_self, res) = *(data as *mut (*mut PABackend,
|
||||
*mut bool));
|
||||
assert!(!(*_self).m.is_null(), "Mainloop is null");
|
||||
assert!(!res.is_null(), "res is null");
|
||||
|
||||
if i.is_null() {
|
||||
return
|
||||
}
|
||||
|
||||
*res = (*i).mute != 0;
|
||||
|
||||
pa_threaded_mainloop_signal((*_self).m, 0);
|
||||
}
|
||||
|
||||
// TODO: Missing error handling.
|
||||
unsafe extern "C" fn set_sink_vol(
|
||||
_: *mut pa_context,
|
||||
success: i32,
|
||||
data: *mut libc::c_void) {
|
||||
let (_self, res) = *(data as *mut (*mut PABackend,
|
||||
*mut Result<()>));
|
||||
assert!(!(*_self).m.is_null(), "Mainloop is null");
|
||||
assert!(!res.is_null(), "res is null");
|
||||
|
||||
if success > 0 {
|
||||
*res = Ok(());
|
||||
} else {
|
||||
*res = Err("Failed to set volume".into());
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_signal((*_self).m, 0);
|
||||
}
|
||||
|
||||
|
||||
// TODO: Missing error handling.
|
||||
// TODO: same as 'set_sink_vol'
|
||||
unsafe extern "C" fn set_sink_mute(
|
||||
_: *mut pa_context,
|
||||
success: i32,
|
||||
data: *mut libc::c_void) {
|
||||
let (_self, res) = *(data as *mut (*mut PABackend,
|
||||
*mut Result<()>));
|
||||
assert!(!(*_self).m.is_null(), "Mainloop is null");
|
||||
assert!(!res.is_null(), "res is null");
|
||||
|
||||
if success > 0 {
|
||||
*res = Ok(());
|
||||
} else {
|
||||
*res = Err("Failed to set volume".into());
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_signal((*_self).m, 0);
|
||||
}
|
||||
|
||||
|
||||
unsafe extern "C" fn context_subscribe_cb(c: *mut pa_context,
|
||||
success: i32,
|
||||
data: *mut libc::c_void) {
|
||||
let (mainloop, res) = *(data as *mut (*mut pa_threaded_mainloop,
|
||||
*mut bool));
|
||||
|
||||
assert!(!mainloop.is_null(), "Mainloop is null");
|
||||
assert!(!res.is_null(), "res is null");
|
||||
|
||||
if success > 0 {
|
||||
*res = true;
|
||||
} else {
|
||||
*res = false;
|
||||
}
|
||||
|
||||
|
||||
pa_threaded_mainloop_signal(mainloop, 0);
|
||||
|
||||
}
|
||||
|
||||
|
||||
unsafe extern "C" fn sub_callback(c: *mut pa_context,
|
||||
t: u32,
|
||||
idx: u32,
|
||||
data: *mut libc::c_void) {
|
||||
|
||||
let (mainloop, p_handlers) = *(data as *mut (*mut pa_threaded_mainloop,
|
||||
*mut Vec<Box<Fn(AudioSignal, AudioUser)>>));
|
||||
|
||||
assert!(!mainloop.is_null(), "Mainloop is null");
|
||||
assert!(!p_handlers.is_null(), "Handlers are null");
|
||||
|
||||
let handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>> = &*p_handlers;
|
||||
|
||||
if (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
|
||||
PA_SUBSCRIPTION_EVENT_SINK {
|
||||
if (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE {
|
||||
// invoke_handlers(
|
||||
// handlers,
|
||||
// AudioSignal::ValuesChanged,
|
||||
// AudioUser::Unknown,
|
||||
// );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
29
src/bin.rs
29
src/bin.rs
@ -1,6 +1,8 @@
|
||||
#![feature(alloc_system)]
|
||||
extern crate alloc_system;
|
||||
extern crate getopts;
|
||||
extern crate libpulse_sys;
|
||||
extern crate libc;
|
||||
|
||||
extern crate pnmixerlib;
|
||||
|
||||
@ -10,9 +12,22 @@ use app_state::*;
|
||||
use getopts::Options;
|
||||
use std::rc::Rc;
|
||||
use std::env;
|
||||
use audio::pulseaudio;
|
||||
use libpulse_sys::*;
|
||||
use std::ffi::{CString, CStr};
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr;
|
||||
use audio::pulseaudio::*;
|
||||
use support::audio::VolDir;
|
||||
|
||||
|
||||
|
||||
|
||||
fn main() {
|
||||
|
||||
|
||||
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
let mut opts = Options::new();
|
||||
@ -59,10 +74,22 @@ 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 before: {:?}", pa.get_vol());
|
||||
// pa.set_vol(80.0, VolDir::Up);
|
||||
// println!("Volume after: {:?}", pa.get_vol());
|
||||
// println!("Mute before: {:?}", pa.get_mute());
|
||||
// println!("PA_VOLUME_NORM: {:?}", PA_VOLUME_NORM);
|
||||
// pa.set_mute(true);
|
||||
// println!("Mute after: {:?}", pa.get_mute());
|
||||
|
||||
// return;
|
||||
|
||||
gtk::init()
|
||||
.unwrap_or_else(|e| panic!("Gtk initialization failed with {}", e));
|
||||
|
||||
let apps = Rc::new(new_alsa_appstate());
|
||||
let apps = Rc::new(new_pa_appstate());
|
||||
|
||||
ui::entry::init(apps);
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
62
src/notif.rs
62
src/notif.rs
@ -144,37 +144,37 @@ where
|
||||
{
|
||||
{
|
||||
/* connect handler */
|
||||
let apps = appstate.clone();
|
||||
appstate.audio.connect_handler(Box::new(move |s, u| {
|
||||
let notif = &apps.notif;
|
||||
if notif.is_none() || !notif.as_ref().unwrap().enabled.get() {
|
||||
return;
|
||||
}
|
||||
let notif = notif.as_ref().unwrap();
|
||||
match (s,
|
||||
u,
|
||||
(notif.from_popup.get(),
|
||||
notif.from_tray.get(),
|
||||
notif.from_external.get(),
|
||||
notif.from_hotkeys.get())) {
|
||||
(AudioSignal::NoCard, _, _) => try_w!(notif.show_text_notif("No sound card", "No playable soundcard found")),
|
||||
(AudioSignal::CardDisconnected, _, _) => try_w!(notif.show_text_notif("Soundcard disconnected", "Soundcard has been disconnected, reloading sound system...")),
|
||||
(AudioSignal::CardError, _, _) => (),
|
||||
(AudioSignal::ValuesChanged,
|
||||
AudioUser::TrayIcon,
|
||||
(_, true, _, _)) => try_w!(notif.show_volume_notif(apps.audio.as_ref())),
|
||||
(AudioSignal::ValuesChanged,
|
||||
AudioUser::Popup,
|
||||
(true, _, _, _)) => try_w!(notif.show_volume_notif(apps.audio.as_ref())),
|
||||
(AudioSignal::ValuesChanged,
|
||||
AudioUser::Unknown,
|
||||
(_, _, true, _)) => try_w!(notif.show_volume_notif(apps.audio.as_ref())),
|
||||
(AudioSignal::ValuesChanged,
|
||||
AudioUser::Hotkeys,
|
||||
(_, _, _, true)) => try_w!(notif.show_volume_notif(apps.audio.as_ref())),
|
||||
_ => (),
|
||||
}
|
||||
}));
|
||||
// let apps = appstate.clone();
|
||||
// appstate.audio.connect_handler(Box::new(move |s, u| {
|
||||
// let notif = &apps.notif;
|
||||
// if notif.is_none() || !notif.as_ref().unwrap().enabled.get() {
|
||||
// return;
|
||||
// }
|
||||
// let notif = notif.as_ref().unwrap();
|
||||
// match (s,
|
||||
// u,
|
||||
// (notif.from_popup.get(),
|
||||
// notif.from_tray.get(),
|
||||
// notif.from_external.get(),
|
||||
// notif.from_hotkeys.get())) {
|
||||
// (AudioSignal::NoCard, _, _) => try_w!(notif.show_text_notif("No sound card", "No playable soundcard found")),
|
||||
// (AudioSignal::CardDisconnected, _, _) => try_w!(notif.show_text_notif("Soundcard disconnected", "Soundcard has been disconnected, reloading sound system...")),
|
||||
// (AudioSignal::CardError, _, _) => (),
|
||||
// (AudioSignal::ValuesChanged,
|
||||
// AudioUser::TrayIcon,
|
||||
// (_, true, _, _)) => try_w!(notif.show_volume_notif(apps.audio.as_ref())),
|
||||
// (AudioSignal::ValuesChanged,
|
||||
// AudioUser::Popup,
|
||||
// (true, _, _, _)) => try_w!(notif.show_volume_notif(apps.audio.as_ref())),
|
||||
// (AudioSignal::ValuesChanged,
|
||||
// AudioUser::Unknown,
|
||||
// (_, _, true, _)) => try_w!(notif.show_volume_notif(apps.audio.as_ref())),
|
||||
// (AudioSignal::ValuesChanged,
|
||||
// AudioUser::Hotkeys,
|
||||
// (_, _, _, true)) => try_w!(notif.show_volume_notif(apps.audio.as_ref())),
|
||||
// _ => (),
|
||||
// }
|
||||
// }));
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
use audio::frontend::*;
|
||||
use errors::*;
|
||||
use prefs::*;
|
||||
use support::alsa::*;
|
||||
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@ -98,16 +98,25 @@ pub fn percent_to_vol(vol: f64, range: (i64, i64), dir: VolDir) -> Result<i64> {
|
||||
}
|
||||
|
||||
|
||||
/// Get all playable card names.
|
||||
pub fn get_playable_card_names() -> Vec<String> {
|
||||
return get_playable_alsa_card_names();
|
||||
}
|
||||
|
||||
|
||||
/// Get all playable channel names.
|
||||
pub fn get_playable_chan_names(card_name: String) -> Vec<String> {
|
||||
let card = try_r!(get_alsa_card_by_name(card_name), Vec::default());
|
||||
let mixer = try_r!(get_mixer(&card), Vec::default());
|
||||
|
||||
return get_playable_selem_names(&mixer);
|
||||
/// Invokes the registered handlers.
|
||||
pub fn invoke_handlers(
|
||||
handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>,
|
||||
signal: AudioSignal,
|
||||
user: AudioUser,
|
||||
) {
|
||||
debug!(
|
||||
"Invoking handlers for signal {:?} by user {:?}",
|
||||
signal,
|
||||
user
|
||||
);
|
||||
if handlers.is_empty() {
|
||||
debug!("No handler found");
|
||||
} else {
|
||||
debug!("Executing handlers")
|
||||
}
|
||||
for handler in handlers {
|
||||
debug!("Handler executing");
|
||||
let unboxed = handler.as_ref();
|
||||
unboxed(signal, user);
|
||||
}
|
||||
}
|
||||
|
@ -9,5 +9,6 @@ pub mod cmd;
|
||||
pub mod gdk_x11;
|
||||
#[macro_use]
|
||||
pub mod glade;
|
||||
pub mod pulseaudio;
|
||||
#[macro_use]
|
||||
pub mod ui;
|
||||
|
84
src/support/pulseaudio.rs
Normal file
84
src/support/pulseaudio.rs
Normal file
@ -0,0 +1,84 @@
|
||||
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<Vec<Sink>> {
|
||||
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<Sink>),
|
||||
*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<Sink>)>(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;
|
||||
|
||||
(*vec).push(Sink {
|
||||
name,
|
||||
index,
|
||||
description,
|
||||
channels,
|
||||
});
|
||||
pa_threaded_mainloop_signal(mainloop, 0);
|
||||
}
|
||||
|
||||
pub fn get_first_sink(mainloop: *mut pa_threaded_mainloop,
|
||||
ctx: *mut pa_context) -> Result<Sink> {
|
||||
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<Sink> {
|
||||
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")?);
|
||||
}
|
@ -63,31 +63,31 @@ where
|
||||
{
|
||||
{
|
||||
/* "global" audio signal handler */
|
||||
let apps = appstate.clone();
|
||||
appstate.audio.connect_handler(
|
||||
Box::new(move |s, u| match (s, u) {
|
||||
(AudioSignal::CardDisconnected, _) => {
|
||||
try_w!(audio_reload(
|
||||
apps.audio.as_ref(),
|
||||
&apps.prefs.borrow(),
|
||||
AudioUser::Unknown,
|
||||
));
|
||||
}
|
||||
(AudioSignal::CardError, _) => {
|
||||
if run_audio_error_dialog(
|
||||
&apps.gui.popup_menu.menu_window,
|
||||
) == (GTK_RESPONSE_YES as i32)
|
||||
{
|
||||
try_w!(audio_reload(
|
||||
apps.audio.as_ref(),
|
||||
&apps.prefs.borrow(),
|
||||
AudioUser::Unknown,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}),
|
||||
);
|
||||
// let apps = appstate.clone();
|
||||
// appstate.audio.connect_handler(
|
||||
// Box::new(move |s, u| match (s, u) {
|
||||
// (AudioSignal::CardDisconnected, _) => {
|
||||
// try_w!(audio_reload(
|
||||
// apps.audio.as_ref(),
|
||||
// &apps.prefs.borrow(),
|
||||
// AudioUser::Unknown,
|
||||
// ));
|
||||
// }
|
||||
// (AudioSignal::CardError, _) => {
|
||||
// if run_audio_error_dialog(
|
||||
// &apps.gui.popup_menu.menu_window,
|
||||
// ) == (GTK_RESPONSE_YES as i32)
|
||||
// {
|
||||
// try_w!(audio_reload(
|
||||
// apps.audio.as_ref(),
|
||||
// &apps.prefs.borrow(),
|
||||
// AudioUser::Unknown,
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
// _ => (),
|
||||
// }),
|
||||
// );
|
||||
|
||||
}
|
||||
|
||||
|
@ -48,16 +48,16 @@ where
|
||||
{
|
||||
/* audio.connect_handler */
|
||||
{
|
||||
let apps = appstate.clone();
|
||||
appstate.audio.connect_handler(Box::new(move |s, u| {
|
||||
/* skip if window is hidden */
|
||||
if !apps.gui.popup_menu.menu.get_visible() {
|
||||
return;
|
||||
}
|
||||
match (s, u) {
|
||||
(_, _) => set_mute_check(&apps),
|
||||
}
|
||||
}));
|
||||
// let apps = appstate.clone();
|
||||
// appstate.audio.connect_handler(Box::new(move |s, u| {
|
||||
// /* skip if window is hidden */
|
||||
// if !apps.gui.popup_menu.menu.get_visible() {
|
||||
// return;
|
||||
// }
|
||||
// match (s, u) {
|
||||
// (_, _) => set_mute_check(&apps),
|
||||
// }
|
||||
// }));
|
||||
|
||||
}
|
||||
|
||||
|
@ -125,31 +125,31 @@ where
|
||||
{
|
||||
/* audio.connect_handler */
|
||||
{
|
||||
let apps = appstate.clone();
|
||||
appstate.audio.connect_handler(Box::new(move |s, u| {
|
||||
/* skip if window is hidden */
|
||||
if !apps.gui.popup_window.popup_window.get_visible() {
|
||||
return;
|
||||
}
|
||||
match (s, u) {
|
||||
/* Update only mute check here
|
||||
* If the user changes the volume through the popup window,
|
||||
* we MUST NOT update the slider value, it's been done already.
|
||||
* It means that, as long as the popup window is visible,
|
||||
* the slider value reflects the value set by user,
|
||||
* and not the real value reported by the audio system.
|
||||
*/
|
||||
(_, AudioUser::Popup) => {
|
||||
apps.gui.popup_window.update_mute_check(
|
||||
apps.audio.as_ref(),
|
||||
);
|
||||
}
|
||||
/* external change, safe to update slider too */
|
||||
(_, _) => {
|
||||
try_w!(apps.gui.popup_window.update(apps.audio.as_ref()));
|
||||
}
|
||||
}
|
||||
}));
|
||||
// let apps = appstate.clone();
|
||||
// appstate.audio.connect_handler(Box::new(move |s, u| {
|
||||
// /* skip if window is hidden */
|
||||
// if !apps.gui.popup_window.popup_window.get_visible() {
|
||||
// return;
|
||||
// }
|
||||
// match (s, u) {
|
||||
// /* Update only mute check here
|
||||
// * If the user changes the volume through the popup window,
|
||||
// * we MUST NOT update the slider value, it's been done already.
|
||||
// * It means that, as long as the popup window is visible,
|
||||
// * the slider value reflects the value set by user,
|
||||
// * and not the real value reported by the audio system.
|
||||
// */
|
||||
// (_, AudioUser::Popup) => {
|
||||
// apps.gui.popup_window.update_mute_check(
|
||||
// apps.audio.as_ref(),
|
||||
// );
|
||||
// }
|
||||
// /* external change, safe to update slider too */
|
||||
// (_, _) => {
|
||||
// try_w!(apps.gui.popup_window.update(apps.audio.as_ref()));
|
||||
// }
|
||||
// }
|
||||
// }));
|
||||
}
|
||||
|
||||
/* mute_check.connect_toggled */
|
||||
|
@ -13,7 +13,6 @@ use prefs::*;
|
||||
use std::ascii::AsciiExt;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use support::audio::*;
|
||||
use ui::hotkey_dialog::HotkeyDialog;
|
||||
|
||||
|
||||
@ -434,21 +433,21 @@ where
|
||||
T: AudioFrontend + 'static,
|
||||
{
|
||||
let apps = appstate.clone();
|
||||
appstate.audio.connect_handler(Box::new(move |s, u| {
|
||||
/* skip if prefs window is not present */
|
||||
if apps.gui.prefs_dialog.borrow().is_none() {
|
||||
return;
|
||||
}
|
||||
// appstate.audio.connect_handler(Box::new(move |s, u| {
|
||||
// /* skip if prefs window is not present */
|
||||
// if apps.gui.prefs_dialog.borrow().is_none() {
|
||||
// return;
|
||||
// }
|
||||
|
||||
match (s, u) {
|
||||
(AudioSignal::CardInitialized, _) => (),
|
||||
(AudioSignal::CardCleanedUp, _) => {
|
||||
fill_card_combo(&apps);
|
||||
fill_chan_combo(&apps, None);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}));
|
||||
// match (s, u) {
|
||||
// (AudioSignal::CardInitialized, _) => (),
|
||||
// (AudioSignal::CardCleanedUp, _) => {
|
||||
// fill_card_combo(&apps);
|
||||
// fill_chan_combo(&apps, None);
|
||||
// }
|
||||
// _ => (),
|
||||
// }
|
||||
// }));
|
||||
}
|
||||
|
||||
|
||||
@ -586,7 +585,7 @@ where
|
||||
/* set card combo */
|
||||
let cur_card_name =
|
||||
try_w!(audio.card_name(), "Can't get current card name!");
|
||||
let available_card_names = get_playable_card_names();
|
||||
let available_card_names = audio.playable_card_names();
|
||||
|
||||
/* set_active_id doesn't work, so save the index */
|
||||
let mut c_index: i32 = -1;
|
||||
@ -613,10 +612,7 @@ where
|
||||
chan_combo.remove_all();
|
||||
|
||||
let audio = &appstate.audio;
|
||||
let available_chan_names = match cardname {
|
||||
Some(name) => get_playable_chan_names(name),
|
||||
None => audio.playable_chan_names(),
|
||||
};
|
||||
let available_chan_names = audio.playable_chan_names(cardname);
|
||||
|
||||
/* set chan combo */
|
||||
let cur_chan_name = try_w!(audio.chan_name());
|
||||
|
@ -422,11 +422,14 @@ where
|
||||
appstate.audio.connect_handler(
|
||||
Box::new(move |s, u| match (s, u) {
|
||||
(_, _) => {
|
||||
apps.gui.tray_icon.update_tooltip(apps.audio.as_ref());
|
||||
try_w!(apps.gui.tray_icon.update_vol_meter(
|
||||
try_w!(apps.audio.get_vol()),
|
||||
apps.audio.vol_level(),
|
||||
));
|
||||
// apps.gui.tray_icon.update_tooltip(apps.audio.as_ref());
|
||||
// try_w!(apps.gui.tray_icon.update_vol_meter(
|
||||
// try_w!(apps.audio.get_vol()),
|
||||
// apps.audio.vol_level(),
|
||||
// ));
|
||||
let vol = apps.audio.get_vol();
|
||||
println!("Vol: {:?}", vol)
|
||||
// println!("Gaga");
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user