Make Audio subsystem a trait 'AudioFrontend'

Fixes #10
This commit is contained in:
Julian Ospald 2017-07-18 17:20:17 +02:00
parent a9145628a8
commit 6251a65764
No known key found for this signature in database
GPG Key ID: 511B62C09D50CD28
13 changed files with 573 additions and 546 deletions

View File

@ -1,3 +1,5 @@
#![allow(illegal_floating_point_literal_pattern)]
//! Alsa audio subsystem. //! Alsa audio subsystem.
//! //!
//! This mod mainly defines the `AlsaCard` struct, which is the only data //! 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::mixer::{Mixer, Selem, SelemId};
use alsa::poll::PollDescriptors; use alsa::poll::PollDescriptors;
use alsa_sys; use alsa_sys;
use audio::*;
use errors::*; use errors::*;
use glib;
use glib_sys; use glib_sys;
use libc::c_uint; use libc::c_uint;
use libc::pollfd; use libc::pollfd;
use libc::size_t; use libc::size_t;
use std::cell::Cell; use std::cell::Cell;
use std::cell::RefCell;
use std::mem; use std::mem;
use std::ptr; use std::ptr;
use std::rc::Rc; use std::rc::Rc;
use std::u8; use std::u8;
use support_alsa::*; 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 /// 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 /// or the given elem name does not exist or is not playable, any other
/// playable elem is tried. /// playable elem is tried.
/// ## `cb`
/// Callback for the various `AlsaEvent`s.
/// ///
/// # Returns /// # Returns
/// ///
@ -122,11 +126,11 @@ impl AlsaCard {
let acard = Box::new(AlsaCard { let acard = Box::new(AlsaCard {
_cannot_construct: (), _cannot_construct: (),
card: card, card,
mixer: mixer, mixer,
selem_id: selem_id, selem_id,
watch_ids: Cell::new(vec![]), watch_ids: Cell::new(vec![]),
cb: cb, cb,
}); });
let watch_ids = AlsaCard::watch_poll_descriptors(vec_pollfd, let watch_ids = AlsaCard::watch_poll_descriptors(vec_pollfd,
@ -136,89 +140,13 @@ impl AlsaCard {
return Ok(acard); return Ok(acard);
} }
/// Get the name of the alsa card.
pub fn card_name(&self) -> Result<String> {
return self.card.get_name().from_err();
}
/// Get the name of the channel.
pub fn chan_name(&self) -> Result<String> {
let n = self.selem_id
.get_name()
.map(|y| String::from(y))?;
return Ok(n);
}
/// Get the `Selem`, looked up by the `SelemId`. /// Get the `Selem`, looked up by the `SelemId`.
pub fn selem(&self) -> Selem { fn selem(&self) -> Selem {
return self.mixer.find_selem(&self.selem_id).unwrap(); 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<i64> {
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<bool> {
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 /// Watch the given alsa card poll descriptors and
/// return the corresponding watch IDs for saving /// return the corresponding watch IDs for saving
/// in the `AlsaCard` struct. /// in the `AlsaCard` struct.
@ -248,7 +176,6 @@ impl AlsaCard {
return watch_ids; return watch_ids;
} }
/// Unwatch the given poll descriptors. /// Unwatch the given poll descriptors.
fn unwatch_poll_descriptors(watch_ids: &Vec<u32>) { fn unwatch_poll_descriptors(watch_ids: &Vec<u32>) {
for watch_id in watch_ids { 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<Box<AlsaCard>>,
/// Last timestamp of an internal action we triggered, e.g.
/// by setting the volume or the mute state.
pub last_action_timestamp: Rc<RefCell<i64>>,
/// 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<u32>,
}
impl AlsaBackend {
/// Creates the `AlsaBackend`, containing an `AlsaCard`
/// and additional information.
pub fn new(card_name: Option<String>,
elem_name: Option<String>)
-> Result<AlsaBackend> {
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<String>,
elem_name: Option<String>,
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<String> {
return self.acard
.borrow()
.card
.get_name()
.from_err();
}
fn chan_name(&self) -> Result<String> {
let n = self.acard
.borrow()
.selem_id
.get_name()
.map(|y| String::from(y))?;
return Ok(n);
}
fn playable_chan_names(&self) -> Vec<String> {
return get_playable_selem_names(&self.acard.borrow().mixer);
}
fn get_vol(&self) -> Result<f64> {
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<bool> {
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<Fn(AudioSignal, AudioUser)>) {
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()`. /// The C callback function registered in `watch_poll_descriptors()`.
extern "C" fn watch_cb(chan: *mut glib_sys::GIOChannel, extern "C" fn watch_cb(chan: *mut glib_sys::GIOChannel,
cond: glib_sys::GIOCondition, cond: glib_sys::GIOCondition,
@ -323,3 +513,61 @@ extern "C" fn watch_cb(chan: *mut glib_sys::GIOChannel,
return true as glib_sys::gboolean; return true as glib_sys::gboolean;
} }
/// 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.
fn on_alsa_event(last_action_timestamp: &mut i64,
handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>,
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);
}
}
}

View File

@ -1,7 +1,8 @@
//! Global application state. //! Global application state.
use audio::{Audio, AudioUser}; use alsa_card::*;
use audio::*;
use errors::*; use errors::*;
use gtk; use gtk;
use hotkeys::Hotkeys; use hotkeys::Hotkeys;
@ -18,12 +19,14 @@ use notif::*;
// TODO: destructors // TODO: destructors
/// The global application state struct. /// The global application state struct.
pub struct AppS { pub struct AppS<T>
where T: AudioFrontend
{
_cant_construct: (), _cant_construct: (),
/// Mostly static GUI state. /// Mostly static GUI state.
pub gui: Gui, pub gui: Gui,
/// Audio state. /// Audio state.
pub audio: Rc<Audio>, pub audio: Rc<T>,
/// Preferences state. /// Preferences state.
pub prefs: RefCell<Prefs>, pub prefs: RefCell<Prefs>,
#[cfg(feature = "notify")] #[cfg(feature = "notify")]
@ -31,39 +34,50 @@ pub struct AppS {
/// is set to `None`. /// is set to `None`.
pub notif: Option<Notif>, pub notif: Option<Notif>,
/// Hotkey state. /// Hotkey state.
pub hotkeys: RefCell<Box<Hotkeys>>, // Gets an Rc to Audio. pub hotkeys: RefCell<Box<Hotkeys<T>>>, // Gets an Rc to Audio.
} }
impl AppS { /// Create a new application state using the `AlsaBackend`.
pub fn new_alsa_appstate() -> AppS<AlsaBackend> {
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!(AlsaBackend::new(Some(card_name),
Some(chan_name)),
None));
return AppS::new(prefs, audio);
}
impl<T> AppS<T>
where T: AudioFrontend
{
/// Create an application state instance. There should really only be one. /// Create an application state instance. There should really only be one.
pub fn new() -> AppS { pub fn new(prefs: RefCell<Prefs>, audio: Rc<T>) -> Self {
let builder_popup_window = let builder_popup_window =
gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"),
"/data/ui/popup-window.glade"))); "/data/ui/popup-window.glade")));
let builder_popup_menu = let builder_popup_menu =
gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"),
"/data/ui/popup-menu.glade"))); "/data/ui/popup-menu.glade")));
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();
// TODO: better error handling // TODO: better error handling
#[cfg(feature = "notify")] #[cfg(feature = "notify")]
let notif = result_warn!(Notif::new(&prefs.borrow()), None).ok(); let notif = result_warn!(Notif::new(&prefs.borrow()), None).ok();
let audio = Rc::new(unwrap_error!(Audio::new(Some(card_name),
Some(chan_name)), let hotkeys = unwrap_error!(wresult_warn!(
None)); Hotkeys::new(&prefs.borrow(),
let hotkeys = unwrap_error!(wresult_warn!(Hotkeys::new(&prefs.borrow(), audio.clone()), None),
audio.clone()), None),
None); None);
let gui = let gui =
@ -72,7 +86,7 @@ impl AppS {
return AppS { return AppS {
_cant_construct: (), _cant_construct: (),
gui, gui,
audio: audio, audio,
prefs, prefs,
#[cfg(feature = "notify")] #[cfg(feature = "notify")]
notif, notif,
@ -87,14 +101,14 @@ impl AppS {
pub fn update_tray_icon(&self) -> Result<()> { pub fn update_tray_icon(&self) -> Result<()> {
debug!("Update tray icon!"); debug!("Update tray icon!");
return self.gui.tray_icon.update_all(&self.prefs.borrow(), return self.gui.tray_icon.update_all(&self.prefs.borrow(),
&self.audio, self.audio.as_ref(),
None); None);
} }
/// Update the Popup Window state. /// Update the Popup Window state.
pub fn update_popup_window(&self) -> Result<()> { pub fn update_popup_window(&self) -> Result<()> {
debug!("Update PopupWindow!"); debug!("Update PopupWindow!");
return self.gui.popup_window.update(&self.audio); return self.gui.popup_window.update(self.audio.as_ref());
} }
#[cfg(feature = "notify")] #[cfg(feature = "notify")]
@ -112,7 +126,7 @@ impl AppS {
/// Update the audio state. /// Update the audio state.
pub fn update_audio(&self, user: AudioUser) -> Result<()> { pub fn update_audio(&self, user: AudioUser) -> Result<()> {
return audio_reload(&self.audio, &self.prefs.borrow(), user); return audio_reload(self.audio.as_ref(), &self.prefs.borrow(), user);
} }
/// Update the config file. /// Update the config file.

View File

@ -1,5 +1,4 @@
#![allow(missing_docs)] // enums #![allow(missing_docs)] // enums
#![allow(illegal_floating_point_literal_pattern)]
//! High-level audio subsystem. //! High-level audio subsystem.
//! //!
@ -9,15 +8,11 @@
//! of the underlying audio implementation, may it be alsa or whatever. //! of the underlying audio implementation, may it be alsa or whatever.
use alsa_card::*;
use errors::*; use errors::*;
use glib;
use std::cell::Cell;
use std::cell::Ref; use std::cell::Ref;
use std::cell::RefCell; use std::cell::RefCell;
use std::f64; use std::f64;
use std::rc::Rc; use std::rc::Rc;
use support_alsa::*;
use support_audio::*; use support_audio::*;
@ -65,162 +60,39 @@ pub struct Handlers {
impl Handlers { impl Handlers {
fn new() -> Handlers { pub fn new() -> Handlers {
return Handlers { inner: Rc::new(RefCell::new(vec![])) }; return Handlers { inner: Rc::new(RefCell::new(vec![])) };
} }
fn borrow(&self) -> Ref<Vec<Box<Fn(AudioSignal, AudioUser)>>> { pub fn borrow(&self) -> Ref<Vec<Box<Fn(AudioSignal, AudioUser)>>> {
return self.inner.borrow(); return self.inner.borrow();
} }
fn add_handler(&self, cb: Box<Fn(AudioSignal, AudioUser)>) { pub fn add_handler(&self, cb: Box<Fn(AudioSignal, AudioUser)>) {
self.inner.borrow_mut().push(cb); self.inner.borrow_mut().push(cb);
} }
} }
/// High-level Audio struct, which could theoretically be backend // TODO: explain more, specify details that need to be implemented
/// agnostic. pub trait AudioFrontend {
pub struct Audio { /// Switches the current card. Must invoke handlers.
_cannot_construct: (),
/// The alsa card.
pub acard: RefCell<Box<AlsaCard>>,
/// Last timestamp of an internal action we triggered, e.g.
/// by setting the volume or the mute state.
pub last_action_timestamp: Rc<RefCell<i64>>,
/// 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<u32>,
}
impl Audio {
/// Create a new Audio instance. This tries very hard to get
/// a working configuration from the backend.
/// ## `card_name`
/// If a card name is provided, it will be tried. If `None` is provided
/// or the given card name does not exist or is not playable, any other
/// playable card is tried.
/// ## `elem_name`
/// 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.
///
/// # Returns
///
/// `Ok(Audio)` on success, `Err(error)` otherwise.
pub fn new(card_name: Option<String>,
elem_name: Option<String>)
-> Result<Audio> {
let handlers = Handlers::new();
let last_action_timestamp = Rc::new(RefCell::new(0));
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);
/* additionally dispatch signals */
if acard.is_err() {
invoke_handlers(&handlers.borrow(),
AudioSignal::NoCard,
AudioUser::Unknown);
} else {
invoke_handlers(&handlers.borrow(),
AudioSignal::CardInitialized,
AudioUser::Unknown);
}
let audio = Audio {
_cannot_construct: (),
acard: RefCell::new(acard?),
last_action_timestamp: last_action_timestamp.clone(),
handlers: handlers.clone(),
scroll_step: Cell::new(5),
};
return Ok(audio);
}
/// Switches the current alsa card. Behaves the same way in regards to
/// `card_name` and `elem_name` as the `Audio::new()` method.
/// ## `user` /// ## `user`
/// Where the card switch originates from. /// Where the card switch originates from.
pub fn switch_acard(&self, fn switch_card(&self,
card_name: Option<String>, card_name: Option<String>,
elem_name: Option<String>, elem_name: Option<String>,
user: AudioUser) user: AudioUser)
-> Result<()> { -> Result<()>;
debug!("Switching cards");
debug!("Old card name: {}",
self.acard
.borrow()
.card_name()
.unwrap());
debug!("Old chan name: {}",
self.acard
.borrow()
.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(), /// Current volume. Between 0 and 100.
AudioSignal::CardInitialized, /// This always gets the volume of the `FrontRight` channel, because the
user); /// seems to be the safest bet.
fn get_vol(&self) -> Result<f64>;
return Ok(()); /// Set the current volume. Must invoke handlers.
}
/// Current volume.
pub fn vol(&self) -> Result<f64> {
let alsa_vol = self.acard
.borrow()
.get_vol()?;
return vol_to_percent(alsa_vol, self.acard.borrow().get_volume_range());
}
/// Current volume level, nicely usable for e.g. selecting from a set
/// of images.
pub fn vol_level(&self) -> VolLevel {
let muted = self.get_mute().unwrap_or(false);
if muted {
return VolLevel::Muted;
}
let cur_vol = try_r!(self.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,
}
}
/// Set the current volume.
/// ## `new_vol` /// ## `new_vol`
/// Set the volume to this value. /// Set the volume to this value.
/// ## `user` /// ## `user`
@ -230,219 +102,60 @@ impl Audio {
/// or increase. This helps with rounding problems. /// or increase. This helps with rounding problems.
/// ## `auto_unmute` /// ## `auto_unmute`
/// Whether to automatically unmute if the volume changes. /// Whether to automatically unmute if the volume changes.
pub fn set_vol(&self, fn set_vol(&self,
new_vol: f64, new_vol: f64,
user: AudioUser, user: AudioUser,
dir: VolDir, dir: VolDir,
auto_unmute: bool) auto_unmute: bool)
-> Result<()> { -> Result<()>;
{
let mut rc = self.last_action_timestamp.borrow_mut();
*rc = glib::get_monotonic_time();
}
let alsa_vol = percent_to_vol(new_vol,
self.acard.borrow().get_volume_range(),
dir)?;
/* only invoke handlers etc. if volume did actually change */
{
let old_alsa_vol =
percent_to_vol(self.vol()?,
self.acard.borrow().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.acard
.borrow()
.card_name()
.unwrap(),
self.acard
.borrow()
.chan_name()
.unwrap(),
new_vol,
user);
self.acard
.borrow()
.set_vol(alsa_vol)?;
invoke_handlers(&self.handlers.borrow(),
AudioSignal::ValuesChanged,
user);
return Ok(());
}
/// Current volume level, nicely usable for e.g. selecting from a set
/// of images.
fn vol_level(&self) -> VolLevel;
/// Increase the volume. The step to increasy by is taken from /// Increase the volume. The step to increasy by is taken from
/// `self.scroll_step`. /// `self.scroll_step`.
/// ## `user` /// ## `user`
/// Where the card switch originates from. /// Where the card switch originates from.
pub fn increase_vol(&self, fn increase_vol(&self, user: AudioUser, auto_unmute: bool) -> Result<()>;
user: AudioUser,
auto_unmute: bool)
-> Result<()> {
let old_vol = self.vol()?;
let new_vol = old_vol + (self.scroll_step.get() as f64);
return self.set_vol(new_vol, user, VolDir::Up, auto_unmute);
}
/// Decrease the volume. The step to decrease by is taken from /// Decrease the volume. The step to decrease by is taken from
/// `self.scroll_step`. /// `self.scroll_step`.
/// ## `user` /// ## `user`
/// Where the card switch originates from. /// Where the card switch originates from.
pub fn decrease_vol(&self, fn decrease_vol(&self, user: AudioUser, auto_unmute: bool) -> Result<()>;
user: AudioUser,
auto_unmute: bool)
-> Result<()> {
let old_vol = self.vol()?;
let new_vol = old_vol - (self.scroll_step.get() as f64);
return self.set_vol(new_vol, user, VolDir::Down, auto_unmute);
}
/// Whether the current audio configuration can be muted. /// Whether the current audio configuration can be muted.
pub fn has_mute(&self) -> bool { fn has_mute(&self) -> bool;
return self.acard.borrow().has_mute();
}
/// Get the mute state of the current audio configuration. /// Get the mute state of the current audio configuration.
pub fn get_mute(&self) -> Result<bool> { fn get_mute(&self) -> Result<bool>;
return self.acard.borrow().get_mute();
}
/// Set the mute state of the current audio configuration. /// Set the mute state of the current audio configuration.
pub fn set_mute(&self, mute: bool, user: AudioUser) -> Result<()> { /// Must invoke handlers.
let mut rc = self.last_action_timestamp.borrow_mut(); fn set_mute(&self, mute: bool, user: AudioUser) -> Result<()>;
*rc = glib::get_monotonic_time();
debug!("Setting mute to {} on card {:?} and chan {:?} by user {:?}",
mute,
self.acard
.borrow()
.card_name()
.unwrap(),
self.acard
.borrow()
.chan_name()
.unwrap(),
user);
self.acard
.borrow()
.set_mute(mute)?;
invoke_handlers(&self.handlers.borrow(),
AudioSignal::ValuesChanged,
user);
return Ok(());
}
/// Toggle the mute state of the current audio configuration. /// Toggle the mute state of the current audio configuration.
pub fn toggle_mute(&self, user: AudioUser) -> Result<()> { fn toggle_mute(&self, user: AudioUser) -> Result<()>;
let muted = self.get_mute()?;
return self.set_mute(!muted, user);
}
/// Connect a signal handler to the audio subsystem. This can /// Connect a signal handler to the audio subsystem. This can
/// be done from anywhere, e.g. in the UI code to react to /// be done from anywhere, e.g. in the UI code to react to
/// certain signals. Multiple handlers for the same signals are fine, /// certain signals. Multiple handlers for the same signals are fine,
/// they will be executed in order. /// they will be executed in order.
pub fn connect_handler(&self, cb: Box<Fn(AudioSignal, AudioUser)>) { fn connect_handler(&self, cb: Box<Fn(AudioSignal, AudioUser)>);
self.handlers.add_handler(cb);
}
/// Get the current card name. /// Get the current card name.
pub fn card_name(&self) -> Result<String> { fn card_name(&self) -> Result<String>;
return self.acard.borrow().card_name();
}
/// Get the currently playable channel names. /// Get the currently playable channel names.
pub fn playable_chan_names(&self) -> Vec<String> { fn playable_chan_names(&self) -> Vec<String>;
return get_playable_selem_names(&self.acard.borrow().mixer);
}
/// Get the current active channel name. /// Get the current active channel name.
pub fn chan_name(&self) -> Result<String> { fn chan_name(&self) -> Result<String>;
return self.acard.borrow().chan_name();
} /// Set the scroll step.
} fn set_scroll_step(&self, scroll_step: u32);
/// Get the scroll step.
/// Invokes the registered handlers. fn get_scroll_step(&self) -> u32;
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.
fn on_alsa_event(last_action_timestamp: &mut i64,
handlers: &Vec<Box<Fn(AudioSignal, AudioUser)>>,
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);
}
}
} }

View File

@ -18,7 +18,7 @@ fn main() {
.init(Some("pnmixer=debug".to_string())) .init(Some("pnmixer=debug".to_string()))
.unwrap_or_else(|e| panic!("Logger initialization failed with {}", e)); .unwrap_or_else(|e| panic!("Logger initialization failed with {}", e));
let apps = Rc::new(AppS::new()); let apps = Rc::new(new_alsa_appstate());
ui_entry::init(apps); ui_entry::init(apps);

View File

@ -5,7 +5,7 @@
//! before they can be interpreted by Gtk/Gdk. //! before they can be interpreted by Gtk/Gdk.
use audio::{Audio, AudioUser}; use audio::*;
use errors::*; use errors::*;
use errors; use errors;
use gdk; use gdk;
@ -23,22 +23,26 @@ use x11;
/// The possible Hotkeys for manipulating the volume. /// The possible Hotkeys for manipulating the volume.
pub struct Hotkeys { pub struct Hotkeys<T>
where T: AudioFrontend
{
enabled: bool, enabled: bool,
mute_key: Option<Hotkey>, mute_key: Option<Hotkey>,
up_key: Option<Hotkey>, up_key: Option<Hotkey>,
down_key: Option<Hotkey>, down_key: Option<Hotkey>,
// need this to access audio in 'key_filter' // need this to access audio in 'key_filter'
audio: Rc<Audio>, audio: Rc<T>,
auto_unmute: bool, auto_unmute: bool,
} }
impl Hotkeys { impl<T> Hotkeys<T>
where T: AudioFrontend
{
/// Creates the hotkeys subsystem and binds the hotkeys. /// Creates the hotkeys subsystem and binds the hotkeys.
pub fn new(prefs: &Prefs, pub fn new(prefs: &Prefs,
audio: Rc<Audio>) audio: Rc<T>)
-> WResult<Box<Hotkeys>, errors::Error, errors::Error> { -> WResult<Box<Hotkeys<T>>, errors::Error, errors::Error> {
debug!("Creating hotkeys control"); debug!("Creating hotkeys control");
let mut hotkeys = let mut hotkeys =
Box::new(Hotkeys { Box::new(Hotkeys {
@ -55,10 +59,10 @@ impl Hotkeys {
/* bind hotkeys */ /* bind hotkeys */
let data_ptr = let data_ptr =
unsafe { unsafe {
mem::transmute::<&Hotkeys, mem::transmute::<&Hotkeys<T>,
glib_sys::gpointer>(hotkeys.as_ref()) glib_sys::gpointer>(hotkeys.as_ref())
}; };
hotkeys_add_filter(Some(key_filter), data_ptr); hotkeys_add_filter(Some(key_filter::<T>), data_ptr);
return WOk(hotkeys, warn); return WOk(hotkeys, warn);
} }
@ -159,8 +163,8 @@ impl Hotkeys {
} }
let data_ptr = let data_ptr =
unsafe { mem::transmute::<&Hotkeys, glib_sys::gpointer>(self) }; unsafe { mem::transmute::<&Hotkeys<T>, glib_sys::gpointer>(self) };
hotkeys_add_filter(Some(key_filter), data_ptr); hotkeys_add_filter(Some(key_filter::<T>), data_ptr);
} }
/// Unbind hotkeys manually. Should be paired with a `bind()` call. /// Unbind hotkeys manually. Should be paired with a `bind()` call.
@ -186,22 +190,25 @@ impl Hotkeys {
} }
let data_ptr = let data_ptr =
unsafe { mem::transmute::<&Hotkeys, glib_sys::gpointer>(self) }; unsafe { mem::transmute::<&Hotkeys<T>, glib_sys::gpointer>(self) };
hotkeys_remove_filter(Some(key_filter), data_ptr); hotkeys_remove_filter(Some(key_filter::<T>), data_ptr);
} }
} }
impl Drop for Hotkeys { impl<T> Drop for Hotkeys<T>
where T: AudioFrontend
{
fn drop(&mut self) { fn drop(&mut self) {
debug!("Freeing hotkeys"); debug!("Freeing hotkeys");
self.mute_key = None; self.mute_key = None;
self.up_key = None; self.up_key = None;
self.down_key = None; self.down_key = None;
let data_ptr = let data_ptr = unsafe {
unsafe { mem::transmute::<&mut Hotkeys, glib_sys::gpointer>(self) }; mem::transmute::<&mut Hotkeys<T>, glib_sys::gpointer>(self)
};
hotkeys_remove_filter(Some(key_filter), data_ptr) hotkeys_remove_filter(Some(key_filter::<T>), data_ptr)
} }
} }
@ -243,14 +250,16 @@ fn hotkeys_remove_filter(function: gdk_sys::GdkFilterFunc,
/// This function is called before Gtk/Gdk can respond /// This function is called before Gtk/Gdk can respond
/// to any(!) window event and handles pressed hotkeys. /// to any(!) window event and handles pressed hotkeys.
extern "C" fn key_filter(gdk_xevent: *mut gdk_sys::GdkXEvent, extern "C" fn key_filter<T>(gdk_xevent: *mut gdk_sys::GdkXEvent,
_: *mut gdk_sys::GdkEvent, _: *mut gdk_sys::GdkEvent,
data: glib_sys::gpointer) data: glib_sys::gpointer)
-> gdk_sys::GdkFilterReturn { -> gdk_sys::GdkFilterReturn
where T: AudioFrontend
{
let xevent = gdk_xevent as *mut x11::xlib::XKeyEvent; let xevent = gdk_xevent as *mut x11::xlib::XKeyEvent;
let hotkeys: &Hotkeys = let hotkeys: &Hotkeys<T> =
unsafe { mem::transmute::<glib_sys::gpointer, &Hotkeys>(data) }; unsafe { mem::transmute::<glib_sys::gpointer, &Hotkeys<T>>(data) };
let mute_key = &hotkeys.mute_key; let mute_key = &hotkeys.mute_key;
let up_key = &hotkeys.up_key; let up_key = &hotkeys.up_key;
let down_key = &hotkeys.down_key; let down_key = &hotkeys.down_key;

View File

@ -97,4 +97,3 @@ pub mod ui_tray_icon;
#[cfg(feature = "notify")] #[cfg(feature = "notify")]
pub mod notif; pub mod notif;

View File

@ -73,8 +73,10 @@ impl Notif {
} }
/// Shows a volume notification, e.g. for volume or mute state change. /// Shows a volume notification, e.g. for volume or mute state change.
pub fn show_volume_notif(&self, audio: &Audio) -> Result<()> { pub fn show_volume_notif<T>(&self, audio: &T) -> Result<()>
let vol = audio.vol()?; where T: AudioFrontend
{
let vol = audio.get_vol()?;
let vol_level = audio.vol_level(); let vol_level = audio.vol_level();
let icon = { let icon = {
@ -92,12 +94,8 @@ impl Notif {
VolLevel::Muted => String::from("Volume muted"), VolLevel::Muted => String::from("Volume muted"),
_ => { _ => {
format!("{} ({})\nVolume: {}", format!("{} ({})\nVolume: {}",
audio.acard audio.card_name()?,
.borrow() audio.chan_name()?,
.card_name()?,
audio.acard
.borrow()
.chan_name()?,
vol as i32) vol as i32)
} }
} }
@ -133,7 +131,9 @@ impl Drop for Notif {
/// Initialize the notification subsystem. /// Initialize the notification subsystem.
pub fn init_notify(appstate: Rc<AppS>) { pub fn init_notify<T>(appstate: Rc<AppS<T>>)
where T: AudioFrontend + 'static
{
{ {
/* connect handler */ /* connect handler */
let apps = appstate.clone(); let apps = appstate.clone();
@ -154,16 +154,16 @@ pub fn init_notify(appstate: Rc<AppS>) {
(AudioSignal::CardError, _, _) => (), (AudioSignal::CardError, _, _) => (),
(AudioSignal::ValuesChanged, (AudioSignal::ValuesChanged,
AudioUser::TrayIcon, AudioUser::TrayIcon,
(_, true, _, _)) => try_w!(notif.show_volume_notif(&apps.audio)), (_, true, _, _)) => try_w!(notif.show_volume_notif(apps.audio.as_ref())),
(AudioSignal::ValuesChanged, (AudioSignal::ValuesChanged,
AudioUser::Popup, AudioUser::Popup,
(true, _, _, _)) => try_w!(notif.show_volume_notif(&apps.audio)), (true, _, _, _)) => try_w!(notif.show_volume_notif(apps.audio.as_ref())),
(AudioSignal::ValuesChanged, (AudioSignal::ValuesChanged,
AudioUser::Unknown, AudioUser::Unknown,
(_, _, true, _)) => try_w!(notif.show_volume_notif(&apps.audio)), (_, _, true, _)) => try_w!(notif.show_volume_notif(apps.audio.as_ref())),
(AudioSignal::ValuesChanged, (AudioSignal::ValuesChanged,
AudioUser::Hotkeys, AudioUser::Hotkeys,
(_, _, _, true)) => try_w!(notif.show_volume_notif(&apps.audio)), (_, _, _, true)) => try_w!(notif.show_volume_notif(apps.audio.as_ref())),
_ => (), _ => (),
} }
})); }));

View File

@ -6,7 +6,7 @@
//! but are important helpers. //! but are important helpers.
use audio::{Audio, AudioUser}; use audio::*;
use errors::*; use errors::*;
use prefs::*; use prefs::*;
use support_alsa::*; use support_alsa::*;
@ -54,14 +54,13 @@ pub fn lrint(v: f64, dir: VolDir) -> f64 {
/// Reload the audio system. /// Reload the audio system.
pub fn audio_reload(audio: &Audio, pub fn audio_reload<T>(audio: &T, prefs: &Prefs, user: AudioUser) -> Result<()>
prefs: &Prefs, where T: AudioFrontend
user: AudioUser) {
-> Result<()> {
let card = &prefs.device_prefs.card; let card = &prefs.device_prefs.card;
let channel = &prefs.device_prefs.channel; let channel = &prefs.device_prefs.channel;
// TODO: is this clone safe? // TODO: is this clone safe?
return audio.switch_acard(Some(card.clone()), Some(channel.clone()), user); return audio.switch_card(Some(card.clone()), Some(channel.clone()), user);
} }

View File

@ -2,7 +2,7 @@
use app_state::*; use app_state::*;
use audio::{AudioUser, AudioSignal}; use audio::*;
use gtk::DialogExt; use gtk::DialogExt;
use gtk::MessageDialogExt; use gtk::MessageDialogExt;
use gtk::WidgetExt; use gtk::WidgetExt;
@ -56,20 +56,22 @@ impl Gui {
/// Initialize the GUI system. /// Initialize the GUI system.
pub fn init(appstate: Rc<AppS>) { pub fn init<T>(appstate: Rc<AppS<T>>)
where T: AudioFrontend + 'static
{
{ {
/* "global" audio signal handler */ /* "global" audio signal handler */
let apps = appstate.clone(); let apps = appstate.clone();
appstate.audio.connect_handler( appstate.audio.connect_handler(
Box::new(move |s, u| match (s, u) { Box::new(move |s, u| match (s, u) {
(AudioSignal::CardDisconnected, _) => { (AudioSignal::CardDisconnected, _) => {
try_w!(audio_reload(&apps.audio, try_w!(audio_reload(apps.audio.as_ref(),
&apps.prefs.borrow(), &apps.prefs.borrow(),
AudioUser::Unknown)); AudioUser::Unknown));
}, },
(AudioSignal::CardError, _) => { (AudioSignal::CardError, _) => {
if run_audio_error_dialog(&apps.gui.popup_menu.menu_window) == (GTK_RESPONSE_YES as i32) { if run_audio_error_dialog(&apps.gui.popup_menu.menu_window) == (GTK_RESPONSE_YES as i32) {
try_w!(audio_reload(&apps.audio, try_w!(audio_reload(apps.audio.as_ref(),
&apps.prefs.borrow(), &apps.prefs.borrow(),
AudioUser::Unknown)); AudioUser::Unknown));
} }

View File

@ -12,7 +12,7 @@
//! * Quit //! * Quit
use app_state::*; use app_state::*;
use audio::AudioUser; use audio::*;
use gtk::prelude::*; use gtk::prelude::*;
use gtk; use gtk;
use std::rc::Rc; use std::rc::Rc;
@ -40,7 +40,9 @@ create_builder_item!(PopupMenu,
/// Initialize the popup menu subsystem, registering all callbacks. /// Initialize the popup menu subsystem, registering all callbacks.
pub fn init_popup_menu(appstate: Rc<AppS>) { pub fn init_popup_menu<T>(appstate: Rc<AppS<T>>)
where T: AudioFrontend + 'static
{
/* audio.connect_handler */ /* audio.connect_handler */
{ {
let apps = appstate.clone(); let apps = appstate.clone();
@ -113,7 +115,7 @@ pub fn init_popup_menu(appstate: Rc<AppS>) {
let apps = appstate.clone(); let apps = appstate.clone();
let reload_item = &appstate.gui.popup_menu.reload_item; let reload_item = &appstate.gui.popup_menu.reload_item;
reload_item.connect_activate(move |_| { reload_item.connect_activate(move |_| {
try_w!(audio_reload(&apps.audio, try_w!(audio_reload(apps.audio.as_ref(),
&apps.prefs.borrow(), &apps.prefs.borrow(),
AudioUser::Popup)) AudioUser::Popup))
}); });
@ -129,7 +131,9 @@ pub fn init_popup_menu(appstate: Rc<AppS>) {
/// When the about menu item is activated. /// When the about menu item is activated.
fn on_about_item_activate(appstate: &AppS) { fn on_about_item_activate<T>(appstate: &AppS<T>)
where T: AudioFrontend
{
let popup_menu = &appstate.gui.popup_menu.menu_window; let popup_menu = &appstate.gui.popup_menu.menu_window;
let about_dialog = create_about_dialog(); let about_dialog = create_about_dialog();
about_dialog.set_skip_taskbar_hint(true); about_dialog.set_skip_taskbar_hint(true);
@ -171,14 +175,18 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.",
/// When the Preferences item is activated. /// When the Preferences item is activated.
fn on_prefs_item_activate(appstate: &Rc<AppS>) { fn on_prefs_item_activate<T>(appstate: &Rc<AppS<T>>)
where T: AudioFrontend + 'static
{
/* TODO: only create if needed */ /* TODO: only create if needed */
show_prefs_dialog(appstate); show_prefs_dialog(appstate);
} }
/// When the Mute item is checked. /// When the Mute item is checked.
fn set_mute_check(apps: &Rc<AppS>) { fn set_mute_check<T>(apps: &Rc<AppS<T>>)
where T: AudioFrontend
{
let mute_check = &apps.gui.popup_menu.mute_check; let mute_check = &apps.gui.popup_menu.mute_check;
let m_muted = apps.audio.get_mute(); let m_muted = apps.audio.get_mute();
match m_muted { match m_muted {

View File

@ -62,17 +62,21 @@ impl PopupWindow {
/// Update the popup window state, including the slider /// Update the popup window state, including the slider
/// and the mute checkbutton. /// and the mute checkbutton.
pub fn update(&self, audio: &Audio) -> Result<()> { pub fn update<T>(&self, audio: &T) -> Result<()>
let cur_vol = audio.vol()?; where T: AudioFrontend
{
let cur_vol = audio.get_vol()?;
set_slider(&self.vol_scale_adj, cur_vol); set_slider(&self.vol_scale_adj, cur_vol);
self.update_mute_check(&audio); self.update_mute_check(audio);
return Ok(()); return Ok(());
} }
/// Update the mute checkbutton. /// Update the mute checkbutton.
pub fn update_mute_check(&self, audio: &Audio) { pub fn update_mute_check<T>(&self, audio: &T)
where T: AudioFrontend
{
let m_muted = audio.get_mute(); let m_muted = audio.get_mute();
glib::signal_handler_block(&self.mute_check, self.toggle_signal.get()); glib::signal_handler_block(&self.mute_check, self.toggle_signal.get());
@ -109,7 +113,9 @@ impl PopupWindow {
/// Initialize the popup window subsystem. /// Initialize the popup window subsystem.
pub fn init_popup_window(appstate: Rc<AppS>) { pub fn init_popup_window<T>(appstate: Rc<AppS<T>>)
where T: AudioFrontend + 'static
{
/* audio.connect_handler */ /* audio.connect_handler */
{ {
let apps = appstate.clone(); let apps = appstate.clone();
@ -130,11 +136,12 @@ pub fn init_popup_window(appstate: Rc<AppS>) {
* and not the real value reported by the audio system. * and not the real value reported by the audio system.
*/ */
(_, AudioUser::Popup) => { (_, AudioUser::Popup) => {
apps.gui.popup_window.update_mute_check(&apps.audio); apps.gui.popup_window.update_mute_check(apps.audio
.as_ref());
} }
/* external change, safe to update slider too */ /* external change, safe to update slider too */
(_, _) => { (_, _) => {
try_w!(apps.gui.popup_window.update(&apps.audio)); try_w!(apps.gui.popup_window.update(apps.audio.as_ref()));
} }
} }
})); }));
@ -213,12 +220,14 @@ pub fn init_popup_window(appstate: Rc<AppS>) {
/// When the popup window is shown. /// When the popup window is shown.
fn on_popup_window_show(appstate: &AppS) { fn on_popup_window_show<T>(appstate: &AppS<T>)
where T: AudioFrontend
{
let popup_window = &appstate.gui.popup_window; let popup_window = &appstate.gui.popup_window;
appstate.gui.popup_window.set_vol_increment(&appstate.prefs.borrow()); appstate.gui.popup_window.set_vol_increment(&appstate.prefs.borrow());
glib::signal_handler_block(&popup_window.vol_scale_adj, glib::signal_handler_block(&popup_window.vol_scale_adj,
popup_window.changed_signal.get()); popup_window.changed_signal.get());
try_w!(appstate.gui.popup_window.update(&appstate.audio)); try_w!(appstate.gui.popup_window.update(appstate.audio.as_ref()));
glib::signal_handler_unblock(&popup_window.vol_scale_adj, glib::signal_handler_unblock(&popup_window.vol_scale_adj,
popup_window.changed_signal.get()); popup_window.changed_signal.get());
popup_window.vol_scale.grab_focus(); popup_window.vol_scale.grab_focus();
@ -257,9 +266,11 @@ fn on_popup_window_event(w: &gtk::Window, e: &gdk::Event) -> gtk::Inhibit {
/// When the volume scale slider is moved. /// When the volume scale slider is moved.
fn on_vol_scale_value_changed(appstate: &AppS) { fn on_vol_scale_value_changed<T>(appstate: &AppS<T>)
where T: AudioFrontend
{
let audio = &appstate.audio; let audio = &appstate.audio;
let old_vol = try_w!(audio.vol()); let old_vol = try_w!(audio.get_vol());
let val = appstate.gui let val = appstate.gui
.popup_window .popup_window
@ -279,7 +290,9 @@ fn on_vol_scale_value_changed(appstate: &AppS) {
/// When the mute checkbutton is toggled. /// When the mute checkbutton is toggled.
fn on_mute_check_toggled(appstate: &AppS) { fn on_mute_check_toggled<T>(appstate: &AppS<T>)
where T: AudioFrontend
{
let audio = &appstate.audio; let audio = &appstate.audio;
try_w!(audio.toggle_mute(AudioUser::Popup)) try_w!(audio.toggle_mute(AudioUser::Popup))
} }

View File

@ -3,7 +3,7 @@
use app_state::*; use app_state::*;
use audio::{AudioUser, AudioSignal}; use audio::*;
use errors::*; use errors::*;
use gdk; use gdk;
use gtk::ResponseType; use gtk::ResponseType;
@ -334,7 +334,9 @@ impl PrefsDialog {
/// Show the preferences dialog. This is created and destroyed dynamically /// Show the preferences dialog. This is created and destroyed dynamically
/// and not persistent across the application lifetime. /// and not persistent across the application lifetime.
pub fn show_prefs_dialog(appstate: &Rc<AppS>) { pub fn show_prefs_dialog<T>(appstate: &Rc<AppS<T>>)
where T: AudioFrontend + 'static
{
if appstate.gui if appstate.gui
.prefs_dialog .prefs_dialog
.borrow() .borrow()
@ -359,7 +361,9 @@ pub fn show_prefs_dialog(appstate: &Rc<AppS>) {
/// Initialize the internal prefs dialog handler that connects to the audio /// Initialize the internal prefs dialog handler that connects to the audio
/// system. /// system.
pub fn init_prefs_callback(appstate: Rc<AppS>) { pub fn init_prefs_callback<T>(appstate: Rc<AppS<T>>)
where T: AudioFrontend + 'static
{
let apps = appstate.clone(); let apps = appstate.clone();
appstate.audio.connect_handler(Box::new(move |s, u| { appstate.audio.connect_handler(Box::new(move |s, u| {
/* skip if prefs window is not present */ /* skip if prefs window is not present */
@ -383,7 +387,9 @@ pub fn init_prefs_callback(appstate: Rc<AppS>) {
/// Initialize the preferences dialog gtk callbacks. /// Initialize the preferences dialog gtk callbacks.
fn init_prefs_dialog(appstate: &Rc<AppS>) { fn init_prefs_dialog<T>(appstate: &Rc<AppS<T>>)
where T: AudioFrontend + 'static
{
/* prefs_dialog.connect_show */ /* prefs_dialog.connect_show */
{ {
@ -486,7 +492,9 @@ fn init_prefs_dialog(appstate: &Rc<AppS>) {
/// Fill the card combo box in the Devices tab. /// Fill the card combo box in the Devices tab.
fn fill_card_combo(appstate: &AppS) { fn fill_card_combo<T>(appstate: &AppS<T>)
where T: AudioFrontend
{
let m_cc = appstate.gui.prefs_dialog.borrow(); let m_cc = appstate.gui.prefs_dialog.borrow();
let card_combo = &m_cc.as_ref().unwrap().card_combo; let card_combo = &m_cc.as_ref().unwrap().card_combo;
card_combo.remove_all(); card_combo.remove_all();
@ -513,7 +521,9 @@ fn fill_card_combo(appstate: &AppS) {
/// Fill the channel combo box in the Devices tab. /// Fill the channel combo box in the Devices tab.
fn fill_chan_combo(appstate: &AppS, cardname: Option<String>) { fn fill_chan_combo<T>(appstate: &AppS<T>, cardname: Option<String>)
where T: AudioFrontend
{
let m_cc = appstate.gui.prefs_dialog.borrow(); let m_cc = appstate.gui.prefs_dialog.borrow();
let chan_combo = &m_cc.as_ref().unwrap().chan_combo; let chan_combo = &m_cc.as_ref().unwrap().chan_combo;
chan_combo.remove_all(); chan_combo.remove_all();
@ -543,10 +553,12 @@ fn fill_chan_combo(appstate: &AppS, cardname: Option<String>) {
} }
fn on_hotkey_event_box_button_press_event(appstate: &AppS, fn on_hotkey_event_box_button_press_event<T>(appstate: &AppS<T>,
widget: &gtk::EventBox, widget: &gtk::EventBox,
event: &gdk::EventButton) event: &gdk::EventButton)
-> bool { -> bool
where T: AudioFrontend
{
let borrow = appstate.gui.prefs_dialog.borrow(); let borrow = appstate.gui.prefs_dialog.borrow();
let prefs_dialog = &borrow.as_ref().unwrap(); let prefs_dialog = &borrow.as_ref().unwrap();
/* we want a left-click */ /* we want a left-click */

View File

@ -96,16 +96,14 @@ impl TrayIcon {
/// Update the tooltip of the tray icon. /// Update the tooltip of the tray icon.
fn update_tooltip(&self, audio: &Audio) { fn update_tooltip<T>(&self, audio: &T)
let cardname = audio.acard where T: AudioFrontend
.borrow() {
.card_name() let cardname =
.unwrap_or(String::from("Unknown card")); audio.card_name().unwrap_or(String::from("Unknown card"));
let channame = audio.acard let channame =
.borrow() audio.chan_name().unwrap_or(String::from("unknown channel"));
.chan_name() let vol = audio.get_vol()
.unwrap_or(String::from("unknown channel"));
let vol = audio.vol()
.map(|s| format!("{}", s.round())) .map(|s| format!("{}", s.round()))
.unwrap_or(String::from("unknown volume")); .unwrap_or(String::from("unknown volume"));
let mute_info = { let mute_info = {
@ -127,11 +125,13 @@ impl TrayIcon {
/// Update the whole tray icon state. /// Update the whole tray icon state.
pub fn update_all(&self, pub fn update_all<T>(&self,
prefs: &Prefs, prefs: &Prefs,
audio: &Audio, audio: &T,
m_size: Option<i32>) m_size: Option<i32>)
-> Result<()> { -> Result<()>
where T: AudioFrontend
{
match m_size { match m_size {
Some(s) => { Some(s) => {
if s < ICON_MIN_SIZE { if s < ICON_MIN_SIZE {
@ -153,8 +153,8 @@ impl TrayIcon {
*self.volmeter.borrow_mut() = Some(volmeter); *self.volmeter.borrow_mut() = Some(volmeter);
} }
self.update_tooltip(&audio); self.update_tooltip(audio);
return self.update_vol_meter(audio.vol()?, audio.vol_level()); return self.update_vol_meter(audio.get_vol()?, audio.vol_level());
} }
} }
@ -378,7 +378,9 @@ impl AudioPix {
/// Initialize the tray icon subsystem. /// Initialize the tray icon subsystem.
pub fn init_tray_icon(appstate: Rc<AppS>) { pub fn init_tray_icon<T>(appstate: Rc<AppS<T>>)
where T: AudioFrontend + 'static
{
let tray_icon = &appstate.gui.tray_icon; let tray_icon = &appstate.gui.tray_icon;
tray_icon.status_icon.set_visible(true); tray_icon.status_icon.set_visible(true);
@ -388,9 +390,9 @@ pub fn init_tray_icon(appstate: Rc<AppS>) {
let apps = appstate.clone(); let apps = appstate.clone();
appstate.audio.connect_handler(Box::new(move |s, u| match (s, u) { appstate.audio.connect_handler(Box::new(move |s, u| match (s, u) {
(_, _) => { (_, _) => {
apps.gui.tray_icon.update_tooltip(&apps.audio); apps.gui.tray_icon.update_tooltip(apps.audio.as_ref());
try_w!(apps.gui.tray_icon.update_vol_meter(try_w!(apps.audio try_w!(apps.gui.tray_icon.update_vol_meter(try_w!(apps.audio
.vol()), .get_vol()),
apps.audio.vol_level())); apps.audio.vol_level()));
} }
})); }));
@ -401,7 +403,7 @@ pub fn init_tray_icon(appstate: Rc<AppS>) {
let apps = appstate.clone(); let apps = appstate.clone();
tray_icon.status_icon.connect_size_changed(move |_, size| { tray_icon.status_icon.connect_size_changed(move |_, size| {
try_wr!(apps.gui.tray_icon.update_all(&apps.prefs.borrow_mut(), try_wr!(apps.gui.tray_icon.update_all(&apps.prefs.borrow_mut(),
&apps.audio, apps.audio.as_ref(),
Some(size)), Some(size)),
false); false);
return false; return false;
@ -448,15 +450,17 @@ pub fn init_tray_icon(appstate: Rc<AppS>) {
)); ));
default_theme.connect_changed(move |_| { default_theme.connect_changed(move |_| {
let tray_icon = &apps.gui.tray_icon; let tray_icon = &apps.gui.tray_icon;
let audio = &apps.audio; try_e!(tray_icon.update_all(&apps.prefs.borrow_mut(),
try_e!(tray_icon.update_all(&apps.prefs.borrow_mut(), &audio, None)); apps.audio.as_ref(), None));
}); });
} }
} }
/// When the tray icon is activated. /// When the tray icon is activated.
fn on_tray_icon_activate(appstate: &AppS) { fn on_tray_icon_activate<T>(appstate: &AppS<T>)
where T: AudioFrontend
{
let popup_window = &appstate.gui.popup_window.popup_window; let popup_window = &appstate.gui.popup_window.popup_window;
if popup_window.get_visible() { if popup_window.get_visible() {
@ -468,7 +472,9 @@ fn on_tray_icon_activate(appstate: &AppS) {
/// When the popup menu is shown, hide the popup window, if any. /// When the popup menu is shown, hide the popup window, if any.
fn on_tray_icon_popup_menu(appstate: &AppS) { fn on_tray_icon_popup_menu<T>(appstate: &AppS<T>)
where T: AudioFrontend
{
let popup_window = &appstate.gui.popup_window.popup_window; let popup_window = &appstate.gui.popup_window.popup_window;
let popup_menu = &appstate.gui.popup_menu.menu; let popup_menu = &appstate.gui.popup_menu.menu;
@ -479,9 +485,11 @@ fn on_tray_icon_popup_menu(appstate: &AppS) {
/// When the mouse scroll event happens while the mouse pointer is /// When the mouse scroll event happens while the mouse pointer is
/// on the tray icon. /// on the tray icon.
fn on_tray_icon_scroll_event(appstate: &AppS, fn on_tray_icon_scroll_event<T>(appstate: &AppS<T>,
event: &gdk::EventScroll) event: &gdk::EventScroll)
-> bool { -> bool
where T: AudioFrontend
{
let scroll_dir: gdk::ScrollDirection = event.get_direction(); let scroll_dir: gdk::ScrollDirection = event.get_direction();
match scroll_dir { match scroll_dir {
@ -511,9 +519,11 @@ fn on_tray_icon_scroll_event(appstate: &AppS,
/// Basically when the tray icon is clicked (although we connect to the `release` /// Basically when the tray icon is clicked (although we connect to the `release`
/// event). This decides whether it was a left, right or middle-click and /// event). This decides whether it was a left, right or middle-click and
/// takes appropriate actions. /// takes appropriate actions.
fn on_tray_button_release_event(appstate: &Rc<AppS>, fn on_tray_button_release_event<T>(appstate: &Rc<AppS<T>>,
event_button: &gdk::EventButton) event_button: &gdk::EventButton)
-> bool { -> bool
where T: AudioFrontend + 'static
{
let button = event_button.get_button(); let button = event_button.get_button();
if button != 2 { if button != 2 {