diff --git a/src/alsa_card.rs b/src/alsa_card.rs index 1ab2bef46..ee8f669f7 100644 --- a/src/alsa_card.rs +++ b/src/alsa_card.rs @@ -1,3 +1,5 @@ +#![allow(illegal_floating_point_literal_pattern)] + //! Alsa audio subsystem. //! //! This mod mainly defines the `AlsaCard` struct, which is the only data @@ -10,17 +12,21 @@ use alsa::mixer::SelemChannelId::*; use alsa::mixer::{Mixer, Selem, SelemId}; use alsa::poll::PollDescriptors; use alsa_sys; +use audio::*; use errors::*; +use glib; use glib_sys; use libc::c_uint; use libc::pollfd; use libc::size_t; use std::cell::Cell; +use std::cell::RefCell; use std::mem; use std::ptr; use std::rc::Rc; use std::u8; use support_alsa::*; +use support_audio::*; @@ -67,8 +73,6 @@ impl AlsaCard { /// If an elem name is provided, it will be tried. If `None` is provided /// or the given elem name does not exist or is not playable, any other /// playable elem is tried. - /// ## `cb` - /// Callback for the various `AlsaEvent`s. /// /// # Returns /// @@ -122,11 +126,11 @@ impl AlsaCard { let acard = Box::new(AlsaCard { _cannot_construct: (), - card: card, - mixer: mixer, - selem_id: selem_id, + card, + mixer, + selem_id, watch_ids: Cell::new(vec![]), - cb: cb, + cb, }); let watch_ids = AlsaCard::watch_poll_descriptors(vec_pollfd, @@ -136,89 +140,13 @@ impl AlsaCard { return Ok(acard); } - - /// Get the name of the alsa card. - pub fn card_name(&self) -> Result { - return self.card.get_name().from_err(); - } - - - /// Get the name of the channel. - pub fn chan_name(&self) -> Result { - let n = self.selem_id - .get_name() - .map(|y| String::from(y))?; - return Ok(n); - } - - /// Get the `Selem`, looked up by the `SelemId`. - pub fn selem(&self) -> Selem { - return self.mixer.find_selem(&self.selem_id).unwrap(); + fn selem(&self) -> Selem { + let selem_id = &self.selem_id; + let selem = self.mixer.find_selem(selem_id); + return selem.unwrap(); } - - /// Get the current volume. The returned value corresponds to the - /// volume range and might need to be interpreted (such as converting - /// to percentage). This always gets - /// the volume of the `FrontRight` channel, because the seems to be - /// the safest bet. - pub fn get_vol(&self) -> Result { - let selem = self.selem(); - let volume = selem.get_playback_volume(FrontRight); - - return volume.from_err(); - } - - - /// Sets the volume of the current card configuration. - /// ## `new_vol` - /// The volume corresponding to the volume range of the `Selem`. This - /// might need to be translated properly first from other formats - /// (like percentage). - pub fn set_vol(&self, new_vol: i64) -> Result<()> { - let selem = self.selem(); - return selem.set_playback_volume_all(new_vol).from_err(); - } - - - /// Gets the volume range of the currently selected card configuration. - /// - /// # Returns - /// - /// `(min, max)` - pub fn get_volume_range(&self) -> (i64, i64) { - let selem = self.selem(); - return selem.get_playback_volume_range(); - } - - - /// Whether the current card configuration can be muted. - pub fn has_mute(&self) -> bool { - let selem = self.selem(); - return selem.has_playback_switch(); - } - - - /// Get the mute state of the current card configuration. - pub fn get_mute(&self) -> Result { - let selem = self.selem(); - let val = selem.get_playback_switch(FrontRight)?; - return Ok(val == 0); - } - - - /// Set the mute state of the current card configuration. - /// ## `mute` - /// Passing `true` here means the card will be muted. - pub fn set_mute(&self, mute: bool) -> Result<()> { - let selem = self.selem(); - /* true -> mute, false -> unmute */ - let _ = selem.set_playback_switch_all(!mute as i32)?; - return Ok(()); - } - - /// Watch the given alsa card poll descriptors and /// return the corresponding watch IDs for saving /// in the `AlsaCard` struct. @@ -248,7 +176,6 @@ impl AlsaCard { return watch_ids; } - /// Unwatch the given poll descriptors. fn unwatch_poll_descriptors(watch_ids: &Vec) { for watch_id in watch_ids { @@ -269,6 +196,269 @@ impl Drop for AlsaCard { } +/// Alsa implementation of the `AudioFrontend`. +pub struct AlsaBackend { + _cannot_construct: (), + /// The alsa card. + pub acard: RefCell>, + /// Last timestamp of an internal action we triggered, e.g. + /// by setting the volume or the mute state. + pub last_action_timestamp: Rc>, + /// A set of handlers that react to audio signals. We can + /// connect to these. + pub handlers: Handlers, + /// The step at which to increase/decrease the volume. + /// This value is basically from the preferences. + pub scroll_step: Cell, +} + + +impl AlsaBackend { + /// Creates the `AlsaBackend`, containing an `AlsaCard` + /// and additional information. + pub fn new(card_name: Option, + elem_name: Option) + -> Result { + + + let last_action_timestamp = Rc::new(RefCell::new(0)); + let handlers = Handlers::new(); + + let cb = { + let myhandler = handlers.clone(); + let ts = last_action_timestamp.clone(); + Rc::new(move |event| { + on_alsa_event(&mut *ts.borrow_mut(), + &myhandler.borrow(), + event) + }) + }; + + let acard = AlsaCard::new(card_name, elem_name, cb); + + if acard.is_err() { + invoke_handlers(&handlers.borrow(), + AudioSignal::NoCard, + AudioUser::Unknown); + } else { + invoke_handlers(&handlers.borrow(), + AudioSignal::CardInitialized, + AudioUser::Unknown); + } + + let alsa_backend = AlsaBackend { + _cannot_construct: (), + acard: RefCell::new(acard?), + last_action_timestamp: last_action_timestamp.clone(), + handlers, + scroll_step: Cell::new(5), + }; + + return Ok(alsa_backend); + } + + + /// Gets the volume range of the currently selected card configuration. + /// + /// # Returns + /// + /// `(min, max)` + fn get_volume_range(&self) -> (i64, i64) { + let acard = self.acard.borrow(); + let selem = acard.selem(); + return selem.get_playback_volume_range(); + } +} + + +impl AudioFrontend for AlsaBackend { + fn switch_card(&self, + card_name: Option, + elem_name: Option, + user: AudioUser) + -> Result<()> { + debug!("Switching cards"); + debug!("Old card name: {}", self.card_name().unwrap()); + debug!("Old chan name: {}", self.chan_name().unwrap()); + let cb = self.acard + .borrow() + .cb + .clone(); + + { + let mut ac = self.acard.borrow_mut(); + *ac = AlsaCard::new(card_name, elem_name, cb)?; + } + + invoke_handlers(&self.handlers.borrow(), + AudioSignal::CardInitialized, + user); + + return Ok(()); + } + + fn card_name(&self) -> Result { + return self.acard + .borrow() + .card + .get_name() + .from_err(); + } + + fn chan_name(&self) -> Result { + let n = self.acard + .borrow() + .selem_id + .get_name() + .map(|y| String::from(y))?; + return Ok(n); + } + + fn playable_chan_names(&self) -> Vec { + return get_playable_selem_names(&self.acard.borrow().mixer); + } + + fn get_vol(&self) -> Result { + let acard = self.acard.borrow(); + let selem = acard.selem(); + let volume = selem.get_playback_volume(FrontRight)?; + + return vol_to_percent(volume, self.get_volume_range()); + } + + fn set_vol(&self, + new_vol: f64, + user: AudioUser, + dir: VolDir, + auto_unmute: bool) + -> Result<()> { + + { + let mut rc = self.last_action_timestamp.borrow_mut(); + *rc = glib::get_monotonic_time(); + } + + let alsa_vol = percent_to_vol(new_vol, self.get_volume_range(), dir)?; + + /* only invoke handlers etc. if volume did actually change */ + { + let old_alsa_vol = + percent_to_vol(self.get_vol()?, self.get_volume_range(), dir)?; + + if old_alsa_vol == alsa_vol { + return Ok(()); + } + } + + /* auto-unmute */ + if auto_unmute && self.has_mute() && self.get_mute()? { + self.set_mute(false, user)?; + } + + debug!("Setting vol on card {:?} and chan {:?} to {:?} by user {:?}", + self.card_name().unwrap(), + self.chan_name().unwrap(), + new_vol, + user); + + + self.acard + .borrow() + .selem() + .set_playback_volume_all(alsa_vol)?; + + invoke_handlers(&self.handlers.borrow(), + AudioSignal::ValuesChanged, + user); + return Ok(()); + + } + + 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 { + let acard = self.acard.borrow(); + let selem = acard.selem(); + return selem.has_playback_switch(); + } + + fn get_mute(&self) -> Result { + let acard = self.acard.borrow(); + let selem = acard.selem(); + let val = selem.get_playback_switch(FrontRight)?; + return Ok(val == 0); + } + + fn set_mute(&self, mute: bool, user: AudioUser) -> Result<()> { + let mut rc = self.last_action_timestamp.borrow_mut(); + *rc = glib::get_monotonic_time(); + + debug!("Setting mute to {} on card {:?} and chan {:?} by user {:?}", + mute, + self.card_name().unwrap(), + self.chan_name().unwrap(), + user); + + let acard = self.acard.borrow(); + let selem = acard.selem(); + /* true -> mute, false -> unmute */ + let _ = selem.set_playback_switch_all(!mute as i32)?; + + invoke_handlers(&self.handlers.borrow(), + AudioSignal::ValuesChanged, + user); + return Ok(()); + } + + fn toggle_mute(&self, user: AudioUser) -> Result<()> { + let muted = self.get_mute()?; + return self.set_mute(!muted, user); + } + + fn connect_handler(&self, cb: Box) { + self.handlers.add_handler(cb); + } + + 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(); + } +} + + + + + /// The C callback function registered in `watch_poll_descriptors()`. extern "C" fn watch_cb(chan: *mut glib_sys::GIOChannel, cond: glib_sys::GIOCondition, @@ -323,3 +513,61 @@ extern "C" fn watch_cb(chan: *mut glib_sys::GIOChannel, return true as glib_sys::gboolean; } + + +/// Invokes the registered handlers. +fn invoke_handlers(handlers: &Vec>, + 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. +fn on_alsa_event(last_action_timestamp: &mut i64, + handlers: &Vec>, + alsa_event: AlsaEvent) { + let last: i64 = *last_action_timestamp; + + if last != 0 { + let now: i64 = glib::get_monotonic_time(); + let delay: i64 = now - last; + if delay < 1000000 { + return; + } + debug!("Discarding last time stamp, too old"); + *last_action_timestamp = 0; + } + + /* external change */ + match alsa_event { + AlsaEvent::AlsaCardError => { + invoke_handlers(handlers, + self::AudioSignal::CardError, + self::AudioUser::Unknown); + } + AlsaEvent::AlsaCardDiconnected => { + invoke_handlers(handlers, + self::AudioSignal::CardDisconnected, + self::AudioUser::Unknown); + } + AlsaEvent::AlsaCardValuesChanged => { + invoke_handlers(handlers, + self::AudioSignal::ValuesChanged, + self::AudioUser::Unknown); + } + } +} diff --git a/src/app_state.rs b/src/app_state.rs index 21a427537..80c87cb00 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -1,7 +1,8 @@ //! Global application state. -use audio::{Audio, AudioUser}; +use alsa_card::*; +use audio::*; use errors::*; use gtk; use hotkeys::Hotkeys; @@ -18,12 +19,14 @@ use notif::*; // TODO: destructors /// The global application state struct. -pub struct AppS { +pub struct AppS + where T: AudioFrontend +{ _cant_construct: (), /// Mostly static GUI state. pub gui: Gui, /// Audio state. - pub audio: Rc