From 72cc33ac7d6dc4cf2fd1792356b29c27a4a0172d Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Wed, 19 Jul 2017 12:12:08 +0200 Subject: [PATCH] Restructure modules --- src/alsa_backend.rs | 573 ------------------ src/app_state.rs | 80 +-- src/audio/alsa/backend.rs | 353 +++++++++++ src/audio/alsa/card.rs | 254 ++++++++ src/audio/alsa/mod.rs | 4 + src/{audio_frontend.rs => audio/frontend.rs} | 36 +- src/audio/mod.rs | 4 + src/bin.rs | 2 +- src/errors.rs | 4 +- src/hotkey.rs | 48 +- src/hotkeys.rs | 152 +++-- src/lib.rs | 22 +- src/notif.rs | 39 +- src/prefs.rs | 91 +-- src/{support_alsa.rs => support/alsa.rs} | 21 +- src/{support_audio.rs => support/audio.rs} | 27 +- src/{support_cmd.rs => support/cmd.rs} | 9 +- src/{ => support}/gdk_x11.rs | 19 +- src/{glade_helpers.rs => support/glade.rs} | 0 src/support/mod.rs | 13 + src/{support_ui.rs => support/ui.rs} | 23 +- src/ui/entry.rs | 137 +++++ .../hotkey_dialog.rs} | 82 +-- src/ui/mod.rs | 8 + src/{ui_popup_menu.rs => ui/popup_menu.rs} | 90 +-- .../popup_window.rs} | 207 +++---- .../prefs_dialog.rs} | 417 ++++++++----- src/{ui_tray_icon.rs => ui/tray_icon.rs} | 307 ++++++---- src/ui_entry.rs | 123 ---- 29 files changed, 1706 insertions(+), 1439 deletions(-) delete mode 100644 src/alsa_backend.rs create mode 100644 src/audio/alsa/backend.rs create mode 100644 src/audio/alsa/card.rs create mode 100644 src/audio/alsa/mod.rs rename src/{audio_frontend.rs => audio/frontend.rs} (85%) create mode 100644 src/audio/mod.rs rename src/{support_alsa.rs => support/alsa.rs} (90%) rename src/{support_audio.rs => support/audio.rs} (89%) rename src/{support_cmd.rs => support/cmd.rs} (76%) rename src/{ => support}/gdk_x11.rs (81%) rename src/{glade_helpers.rs => support/glade.rs} (100%) create mode 100644 src/support/mod.rs rename src/{support_ui.rs => support/ui.rs} (82%) create mode 100644 src/ui/entry.rs rename src/{ui_hotkey_dialog.rs => ui/hotkey_dialog.rs} (60%) create mode 100644 src/ui/mod.rs rename src/{ui_popup_menu.rs => ui/popup_menu.rs} (70%) rename src/{ui_popup_window.rs => ui/popup_window.rs} (60%) rename src/{ui_prefs_dialog.rs => ui/prefs_dialog.rs} (62%) rename src/{ui_tray_icon.rs => ui/tray_icon.rs} (66%) delete mode 100644 src/ui_entry.rs diff --git a/src/alsa_backend.rs b/src/alsa_backend.rs deleted file mode 100644 index 611663217..000000000 --- a/src/alsa_backend.rs +++ /dev/null @@ -1,573 +0,0 @@ -#![allow(illegal_floating_point_literal_pattern)] - -//! Alsa audio subsystem. -//! -//! This mod mainly defines the `AlsaCard` struct, which is the only data -//! structure interacting directly with the alsa library. -//! No other struct should directly interact with the alsa bindings. - - -use alsa::card::Card; -use alsa::mixer::SelemChannelId::*; -use alsa::mixer::{Mixer, Selem, SelemId}; -use alsa::poll::PollDescriptors; -use alsa_sys; -use audio_frontend::*; -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::*; - - - -#[derive(Clone, Copy, Debug)] -/// An "external" alsa card event, potentially triggered by anything. -pub enum AlsaEvent { - /// An error. - AlsaCardError, - /// Alsa card is disconnected. - AlsaCardDiconnected, - /// The values of the mixer changed, including mute state. - AlsaCardValuesChanged, -} - - -/// A fairly high-level alsa card struct. We save some redundant -/// information in order to access it more easily, in addition to -/// some information that is not purely alsa related (like callbacks). -pub struct AlsaCard { - _cannot_construct: (), - /// The raw alsa card. - pub card: Card, - /// The raw mixer. - pub mixer: Mixer, - /// The simple element ID. `Selem` doesn't implement the Copy trait - /// so we save the ID instead and can get the `Selem` by lookup. - pub selem_id: SelemId, - /// Watch IDs from polling the alsa card. We need them when we - /// drop the card, so we can unregister the polling. - pub watch_ids: Cell>, - /// Callback for the various `AlsaEvent`s. - pub cb: Rc, -} - - -impl AlsaCard { - /// Create a new alsa card. Tries very hard to get a valid, playable - /// card and mixer, so this is not a 'strict' function. - /// ## `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(Box)` on success, `Err(error)` otherwise. - pub fn new(card_name: Option, - elem_name: Option, - cb: Rc) - -> Result> { - let card = { - match card_name { - Some(name) => { - if name == "(default)" { - let default = get_default_alsa_card(); - if alsa_card_has_playable_selem(&default) { - default - } else { - warn!("Default alsa card not playabla, trying others"); - get_first_playable_alsa_card()? - } - } else { - let mycard = get_alsa_card_by_name(name.clone()); - match mycard { - Ok(card) => card, - Err(_) => { - warn!("Card {} not playable, trying others", - name); - get_first_playable_alsa_card()? - } - } - } - } - None => get_first_playable_alsa_card()?, - } - }; - let mixer = get_mixer(&card)?; - - let selem_id = { - let requested_selem = - get_playable_selem_by_name(&mixer, - elem_name.unwrap_or(String::from("Master"))); - match requested_selem { - Ok(s) => s.get_id(), - Err(_) => { - warn!("No playable Selem found, trying others"); - get_first_playable_selem(&mixer)?.get_id() - } - } - }; - - let vec_pollfd = PollDescriptors::get(&mixer)?; - - let acard = Box::new(AlsaCard { - _cannot_construct: (), - card, - mixer, - selem_id, - watch_ids: Cell::new(vec![]), - cb, - }); - - let watch_ids = AlsaCard::watch_poll_descriptors(vec_pollfd, - acard.as_ref()); - acard.watch_ids.set(watch_ids); - - return Ok(acard); - } - - /// Get the `Selem`, looked up by the `SelemId`. - fn selem(&self) -> Selem { - let selem_id = &self.selem_id; - let selem = self.mixer.find_selem(selem_id); - return selem.unwrap(); - } - - /// Watch the given alsa card poll descriptors and - /// return the corresponding watch IDs for saving - /// in the `AlsaCard` struct. - fn watch_poll_descriptors(polls: Vec, - acard: &AlsaCard) - -> Vec { - let mut watch_ids: Vec = vec![]; - let acard_ptr = - unsafe { mem::transmute::<&AlsaCard, glib_sys::gpointer>(acard) }; - for poll in polls { - let gioc: *mut glib_sys::GIOChannel = - unsafe { glib_sys::g_io_channel_unix_new(poll.fd) }; - let id = unsafe { - glib_sys::g_io_add_watch( - gioc, - glib_sys::GIOCondition::from_bits( - glib_sys::G_IO_IN.bits() | glib_sys::G_IO_ERR.bits(), - ).unwrap(), - Some(watch_cb), - acard_ptr, - ) - }; - watch_ids.push(id); - unsafe { glib_sys::g_io_channel_unref(gioc) } - } - - return watch_ids; - } - - /// Unwatch the given poll descriptors. - fn unwatch_poll_descriptors(watch_ids: &Vec) { - for watch_id in watch_ids { - unsafe { - glib_sys::g_source_remove(*watch_id); - } - } - } -} - - -impl Drop for AlsaCard { - /// Destructs the watch IDs corresponding to the current poll descriptors. - fn drop(&mut self) { - debug!("Destructing watch_ids: {:?}", self.watch_ids.get_mut()); - AlsaCard::unwatch_poll_descriptors(&self.watch_ids.get_mut()); - } -} - - -/// 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, - data: glib_sys::gpointer) - -> glib_sys::gboolean { - - let acard = - unsafe { mem::transmute::(data) }; - let cb = &acard.cb; - - unsafe { - let mixer_ptr = - mem::transmute::<&Mixer, &*mut alsa_sys::snd_mixer_t>(&acard.mixer); - alsa_sys::snd_mixer_handle_events(*mixer_ptr); - }; - - if cond == glib_sys::G_IO_ERR { - return false as glib_sys::gboolean; - } - - let mut sread: size_t = 1; - let mut buf: Vec = vec![0; 256]; - - while sread > 0 { - let stat: glib_sys::GIOStatus = - unsafe { - glib_sys::g_io_channel_read_chars(chan, - buf.as_mut_ptr() as *mut u8, - 256, - &mut sread as *mut size_t, - ptr::null_mut()) - }; - - match stat { - glib_sys::G_IO_STATUS_AGAIN => { - debug!("G_IO_STATUS_AGAIN"); - continue; - } - glib_sys::G_IO_STATUS_NORMAL => { - error!("Alsa failed to clear the channel"); - cb(AlsaEvent::AlsaCardError); - } - glib_sys::G_IO_STATUS_ERROR => (), - glib_sys::G_IO_STATUS_EOF => { - error!("GIO error has occurred"); - cb(AlsaEvent::AlsaCardError); - } - } - return true as glib_sys::gboolean; - } - cb(AlsaEvent::AlsaCardValuesChanged); - - 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 48a21d27a..32055cb35 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -1,16 +1,16 @@ //! Global application state. -use alsa_backend::*; -use audio_frontend::*; +use audio::alsa::backend::*; +use audio::frontend::*; use errors::*; use gtk; use hotkeys::Hotkeys; use prefs::*; use std::cell::RefCell; use std::rc::Rc; -use support_audio::*; -use ui_entry::Gui; +use support::audio::*; +use ui::entry::Gui; #[cfg(feature = "notify")] use notif::*; @@ -20,7 +20,8 @@ use notif::*; // TODO: destructors /// The global application state struct. pub struct AppS - where T: AudioFrontend +where + T: AudioFrontend, { _cant_construct: (), /// Mostly static GUI state. @@ -42,56 +43,55 @@ pub struct AppS pub fn new_alsa_appstate() -> AppS { 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)); + 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 AppS - where T: AudioFrontend +where + T: AudioFrontend, { /// Create an application state instance. There should really only be one. pub fn new(prefs: RefCell, audio: Rc) -> Self { let builder_popup_window = - gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), - "/data/ui/popup-window.glade"))); + gtk::Builder::new_from_string(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/data/ui/popup-window.glade" + ))); let builder_popup_menu = - gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), - "/data/ui/popup-menu.glade"))); + gtk::Builder::new_from_string(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/data/ui/popup-menu.glade" + ))); // TODO: better error handling #[cfg(feature = "notify")] let notif = result_warn!(Notif::new(&prefs.borrow()), None).ok(); - - let hotkeys = unwrap_error!(wresult_warn!( - Hotkeys::new(&prefs.borrow(), - audio.clone()), None), - None); + let hotkeys = + unwrap_error!( + wresult_warn!(Hotkeys::new(&prefs.borrow(), audio.clone()), None), + None + ); let gui = Gui::new(builder_popup_window, builder_popup_menu, &prefs.borrow()); return AppS { - _cant_construct: (), - gui, - audio, - prefs, - #[cfg(feature = "notify")] - notif, - hotkeys: RefCell::new(hotkeys), - }; + _cant_construct: (), + gui, + audio, + prefs, + notif, + hotkeys: RefCell::new(hotkeys), + }; } @@ -100,9 +100,11 @@ impl AppS /// Update the tray icon state. pub fn update_tray_icon(&self) -> Result<()> { debug!("Update tray icon!"); - return self.gui.tray_icon.update_all(&self.prefs.borrow(), - self.audio.as_ref(), - None); + return self.gui.tray_icon.update_all( + &self.prefs.borrow(), + self.audio.as_ref(), + None, + ); } /// Update the Popup Window state. @@ -116,7 +118,9 @@ impl AppS pub fn update_notify(&self) { match self.notif { Some(ref n) => n.reload(&self.prefs.borrow()), - None => warn!("Notification system not unitialized, skipping update"), + None => { + warn!("Notification system not unitialized, skipping update") + } } } diff --git a/src/audio/alsa/backend.rs b/src/audio/alsa/backend.rs new file mode 100644 index 000000000..6429a1142 --- /dev/null +++ b/src/audio/alsa/backend.rs @@ -0,0 +1,353 @@ +#![allow(illegal_floating_point_literal_pattern)] + +//! Alsa audio backend subsystem. +//! +//! This mod mainly defines the `AlsaBackend` struct. + + +use alsa_lib::mixer::SelemChannelId::*; +use audio::alsa::card::*; +use audio::frontend::*; +use errors::*; +use glib; +use std::cell::Cell; +use std::cell::RefCell; +use std::rc::Rc; +use support::alsa::*; +use support::audio::*; + + + +/// 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(); + } +} + + +/// 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/audio/alsa/card.rs b/src/audio/alsa/card.rs new file mode 100644 index 000000000..759859c65 --- /dev/null +++ b/src/audio/alsa/card.rs @@ -0,0 +1,254 @@ +//! Alsa audio subsystem. +//! +//! This mod mainly defines the `AlsaCard` struct, which is the only data +//! structure interacting directly with the alsa library. +//! No other struct should directly interact with the alsa bindings. + + +use alsa_lib::card::Card; +use alsa_lib::mixer::{Mixer, Selem, SelemId}; +use alsa_lib::poll::PollDescriptors; +use alsa_sys; +use errors::*; +use glib_sys; +use libc::c_uint; +use libc::pollfd; +use libc::size_t; +use std::cell::Cell; +use std::mem; +use std::ptr; +use std::rc::Rc; +use std::u8; +use support::alsa::*; + + + +#[derive(Clone, Copy, Debug)] +/// An "external" alsa card event, potentially triggered by anything. +pub enum AlsaEvent { + /// An error. + AlsaCardError, + /// Alsa card is disconnected. + AlsaCardDiconnected, + /// The values of the mixer changed, including mute state. + AlsaCardValuesChanged, +} + + +/// A fairly high-level alsa card struct. We save some redundant +/// information in order to access it more easily, in addition to +/// some information that is not purely alsa related (like callbacks). +pub struct AlsaCard { + _cannot_construct: (), + /// The raw alsa card. + pub card: Card, + /// The raw mixer. + pub mixer: Mixer, + /// The simple element ID. `Selem` doesn't implement the Copy trait + /// so we save the ID instead and can get the `Selem` by lookup. + pub selem_id: SelemId, + /// Watch IDs from polling the alsa card. We need them when we + /// drop the card, so we can unregister the polling. + pub watch_ids: Cell>, + /// Callback for the various `AlsaEvent`s. + pub cb: Rc, +} + + +impl AlsaCard { + /// Create a new alsa card. Tries very hard to get a valid, playable + /// card and mixer, so this is not a 'strict' function. + /// ## `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(Box)` on success, `Err(error)` otherwise. + pub fn new( + card_name: Option, + elem_name: Option, + cb: Rc, + ) -> Result> { + let card = { + match card_name { + Some(name) => { + if name == "(default)" { + let default = get_default_alsa_card(); + if alsa_card_has_playable_selem(&default) { + default + } else { + warn!( + "Default alsa card not playabla, trying others" + ); + get_first_playable_alsa_card()? + } + } else { + let mycard = get_alsa_card_by_name(name.clone()); + match mycard { + Ok(card) => card, + Err(_) => { + warn!( + "Card {} not playable, trying others", + name + ); + get_first_playable_alsa_card()? + } + } + } + } + None => get_first_playable_alsa_card()?, + } + }; + let mixer = get_mixer(&card)?; + + let selem_id = { + let requested_selem = get_playable_selem_by_name( + &mixer, + elem_name.unwrap_or(String::from("Master")), + ); + match requested_selem { + Ok(s) => s.get_id(), + Err(_) => { + warn!("No playable Selem found, trying others"); + get_first_playable_selem(&mixer)?.get_id() + } + } + }; + + let vec_pollfd = PollDescriptors::get(&mixer)?; + + let acard = Box::new(AlsaCard { + _cannot_construct: (), + card, + mixer, + selem_id, + watch_ids: Cell::new(vec![]), + cb, + }); + + let watch_ids = + AlsaCard::watch_poll_descriptors(vec_pollfd, acard.as_ref()); + acard.watch_ids.set(watch_ids); + + return Ok(acard); + } + + /// Get the `Selem`, looked up by the `SelemId`. + pub fn selem(&self) -> Selem { + let selem_id = &self.selem_id; + let selem = self.mixer.find_selem(selem_id); + return selem.unwrap(); + } + + /// Watch the given alsa card poll descriptors and + /// return the corresponding watch IDs for saving + /// in the `AlsaCard` struct. + fn watch_poll_descriptors( + polls: Vec, + acard: &AlsaCard, + ) -> Vec { + let mut watch_ids: Vec = vec![]; + let acard_ptr = + unsafe { mem::transmute::<&AlsaCard, glib_sys::gpointer>(acard) }; + for poll in polls { + let gioc: *mut glib_sys::GIOChannel = + unsafe { glib_sys::g_io_channel_unix_new(poll.fd) }; + let id = unsafe { + glib_sys::g_io_add_watch( + gioc, + glib_sys::GIOCondition::from_bits( + glib_sys::G_IO_IN.bits() | glib_sys::G_IO_ERR.bits(), + ).unwrap(), + Some(watch_cb), + acard_ptr, + ) + }; + watch_ids.push(id); + unsafe { glib_sys::g_io_channel_unref(gioc) } + } + + return watch_ids; + } + + /// Unwatch the given poll descriptors. + fn unwatch_poll_descriptors(watch_ids: &Vec) { + for watch_id in watch_ids { + unsafe { + glib_sys::g_source_remove(*watch_id); + } + } + } +} + + +impl Drop for AlsaCard { + /// Destructs the watch IDs corresponding to the current poll descriptors. + fn drop(&mut self) { + debug!("Destructing watch_ids: {:?}", self.watch_ids.get_mut()); + AlsaCard::unwatch_poll_descriptors(&self.watch_ids.get_mut()); + } +} + + +/// The C callback function registered in `watch_poll_descriptors()`. +extern "C" fn watch_cb( + chan: *mut glib_sys::GIOChannel, + cond: glib_sys::GIOCondition, + data: glib_sys::gpointer, +) -> glib_sys::gboolean { + + let acard = + unsafe { mem::transmute::(data) }; + let cb = &acard.cb; + + unsafe { + let mixer_ptr = + mem::transmute::<&Mixer, &*mut alsa_sys::snd_mixer_t>(&acard.mixer); + alsa_sys::snd_mixer_handle_events(*mixer_ptr); + }; + + if cond == glib_sys::G_IO_ERR { + return false as glib_sys::gboolean; + } + + let mut sread: size_t = 1; + let mut buf: Vec = vec![0; 256]; + + while sread > 0 { + let stat: glib_sys::GIOStatus = unsafe { + glib_sys::g_io_channel_read_chars( + chan, + buf.as_mut_ptr() as *mut u8, + 256, + &mut sread as *mut size_t, + ptr::null_mut(), + ) + }; + + match stat { + glib_sys::G_IO_STATUS_AGAIN => { + debug!("G_IO_STATUS_AGAIN"); + continue; + } + glib_sys::G_IO_STATUS_NORMAL => { + error!("Alsa failed to clear the channel"); + cb(AlsaEvent::AlsaCardError); + } + glib_sys::G_IO_STATUS_ERROR => (), + glib_sys::G_IO_STATUS_EOF => { + error!("GIO error has occurred"); + cb(AlsaEvent::AlsaCardError); + } + } + return true as glib_sys::gboolean; + } + cb(AlsaEvent::AlsaCardValuesChanged); + + return true as glib_sys::gboolean; +} diff --git a/src/audio/alsa/mod.rs b/src/audio/alsa/mod.rs new file mode 100644 index 000000000..b942874b8 --- /dev/null +++ b/src/audio/alsa/mod.rs @@ -0,0 +1,4 @@ +//! The alsa backend subsystem. + +pub mod backend; +pub mod card; diff --git a/src/audio_frontend.rs b/src/audio/frontend.rs similarity index 85% rename from src/audio_frontend.rs rename to src/audio/frontend.rs index 8d284fb5f..dc85683a2 100644 --- a/src/audio_frontend.rs +++ b/src/audio/frontend.rs @@ -13,7 +13,7 @@ use std::cell::Ref; use std::cell::RefCell; use std::f64; use std::rc::Rc; -use support_audio::*; +use support::audio::*; @@ -76,25 +76,30 @@ impl Handlers { } -// TODO: explain more, specify details that need to be implemented +/// This is the audio frontend, which can be implemented by different backends, +/// e.g. Alsa or PulseAudio. The high-level UI code only calls these +/// functions, never the underlying backend functions. The backend +/// implementation must ensure proper state and consistency, especially +/// wrt handlers and switching the card. pub trait AudioFrontend { /// Switches the current card. Must invoke handlers. /// ## `user` /// Where the card switch originates from. - fn switch_card(&self, - card_name: Option, - elem_name: Option, - user: AudioUser) - -> Result<()>; + fn switch_card( + &self, + card_name: Option, + elem_name: Option, + user: AudioUser, + ) -> Result<()>; /// Current volume. Between 0 and 100. - /// This always gets the volume of the `FrontRight` channel, because the + /// This always gets the volume of the `FrontRight` channel, because that /// seems to be the safest bet. fn get_vol(&self) -> Result; /// Set the current volume. Must invoke handlers. /// ## `new_vol` - /// Set the volume to this value. + /// Set the volume to this value. From 0 to 100. /// ## `user` /// Where the card switch originates from. /// ## `dir` @@ -102,12 +107,13 @@ pub trait AudioFrontend { /// or increase. This helps with rounding problems. /// ## `auto_unmute` /// Whether to automatically unmute if the volume changes. - fn set_vol(&self, - new_vol: f64, - user: AudioUser, - dir: VolDir, - auto_unmute: bool) - -> Result<()>; + fn set_vol( + &self, + new_vol: f64, + user: AudioUser, + dir: VolDir, + auto_unmute: bool, + ) -> Result<()>; /// Current volume level, nicely usable for e.g. selecting from a set /// of images. diff --git a/src/audio/mod.rs b/src/audio/mod.rs new file mode 100644 index 000000000..230129e32 --- /dev/null +++ b/src/audio/mod.rs @@ -0,0 +1,4 @@ +//! The audio subsystem with its frontend and backends. + +pub mod alsa; +pub mod frontend; diff --git a/src/bin.rs b/src/bin.rs index 2022c37cd..d191d68af 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -20,7 +20,7 @@ fn main() { let apps = Rc::new(new_alsa_appstate()); - ui_entry::init(apps); + ui::entry::init(apps); gtk::main(); diff --git a/src/errors.rs b/src/errors.rs index e50d4ce00..feeec62e6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,6 @@ #![allow(missing_docs)] -use alsa; +use alsa_lib; use png; use std::convert::From; use std; @@ -10,7 +10,7 @@ use toml; error_chain! { foreign_links { - Alsa(alsa::Error); + Alsa(alsa_lib::Error); IO(std::io::Error); Toml(toml::de::Error); Png(png::DecodingError); diff --git a/src/hotkey.rs b/src/hotkey.rs index 8db5df570..08a9b726d 100644 --- a/src/hotkey.rs +++ b/src/hotkey.rs @@ -7,11 +7,11 @@ use errors::*; use gdk; use gdk_sys; -use gdk_x11::*; use gtk; -use x11; use libc::c_int; use libc::c_uint; +use support::gdk_x11::*; +use x11; /// `xmodmap -pm` @@ -50,8 +50,9 @@ impl Hotkey { let mod_bits = mods.bits(); let sym = unsafe { x11::xlib::XkbKeycodeToKeysym(display, code as u8, 0, 0) }; - let gtk_accel = gtk::accelerator_name(sym as u32, mods) - .ok_or("Could net get accelerator name")?; + let gtk_accel = gtk::accelerator_name(sym as u32, mods).ok_or( + "Could net get accelerator name", + )?; let hotkey = Hotkey { code, @@ -84,13 +85,15 @@ impl Hotkey { /* Grab the key */ for key in KEY_MASKS.iter() { unsafe { - x11::xlib::XGrabKey(display, - self.code, - self.mod_bits | key, - gdk_x11_get_default_root_xwindow(), - 1, - x11::xlib::GrabModeAsync, - x11::xlib::GrabModeAsync); + x11::xlib::XGrabKey( + display, + self.code, + self.mod_bits | key, + gdk_x11_get_default_root_xwindow(), + 1, + x11::xlib::GrabModeAsync, + x11::xlib::GrabModeAsync, + ); } } @@ -119,10 +122,12 @@ impl Hotkey { for key in KEY_MASKS.iter() { unsafe { - x11::xlib::XUngrabKey(display, - self.code, - self.mod_bits | key, - gdk_x11_get_default_root_xwindow()); + x11::xlib::XUngrabKey( + display, + self.code, + self.mod_bits | key, + gdk_x11_get_default_root_xwindow(), + ); } } } @@ -159,8 +164,10 @@ pub fn hotkey_accel_to_code(accel: &str) -> (gdk::key, gdk::ModifierType) { unsafe { if sym != 0 { - return (x11::xlib::XKeysymToKeycode(display, sym as u64) as i32, - mods); + return ( + x11::xlib::XKeysymToKeycode(display, sym as u64) as i32, + mods, + ); } else { return (-1, mods); } @@ -170,9 +177,10 @@ pub fn hotkey_accel_to_code(accel: &str) -> (gdk::key, gdk::ModifierType) { static mut GRAB_ERROR: u8 = 0; -extern "C" fn grab_error_handler(_: *mut x11::xlib::Display, - _: *mut x11::xlib::XErrorEvent) - -> c_int { +extern "C" fn grab_error_handler( + _: *mut x11::xlib::Display, + _: *mut x11::xlib::XErrorEvent, +) -> c_int { warn!("Error while grabbing hotkey"); unsafe { GRAB_ERROR = 1; diff --git a/src/hotkeys.rs b/src/hotkeys.rs index 887059749..e57d815a9 100644 --- a/src/hotkeys.rs +++ b/src/hotkeys.rs @@ -5,18 +5,18 @@ //! before they can be interpreted by Gtk/Gdk. -use audio_frontend::*; +use audio::frontend::*; use errors::*; use errors; use gdk; use gdk_sys; -use gdk_x11; use glib::translate::*; use glib_sys; use hotkey::*; use prefs::*; use std::mem; use std::rc::Rc; +use support::gdk_x11; use w_result::*; use x11; @@ -24,7 +24,8 @@ use x11; /// The possible Hotkeys for manipulating the volume. pub struct Hotkeys - where T: AudioFrontend +where + T: AudioFrontend, { enabled: bool, mute_key: Option, @@ -37,31 +38,30 @@ pub struct Hotkeys } impl Hotkeys - where T: AudioFrontend +where + T: AudioFrontend, { /// Creates the hotkeys subsystem and binds the hotkeys. - pub fn new(prefs: &Prefs, - audio: Rc) - -> WResult>, errors::Error, errors::Error> { + pub fn new( + prefs: &Prefs, + audio: Rc, + ) -> WResult>, errors::Error, errors::Error> { debug!("Creating hotkeys control"); - let mut hotkeys = - Box::new(Hotkeys { - enabled: false, - mute_key: None, - up_key: None, - down_key: None, - audio: audio, - auto_unmute: prefs.behavior_prefs.unmute_on_vol_change, - }); + let mut hotkeys = Box::new(Hotkeys { + enabled: false, + mute_key: None, + up_key: None, + down_key: None, + audio: audio, + auto_unmute: prefs.behavior_prefs.unmute_on_vol_change, + }); let mut warn = vec![]; push_warning!(hotkeys.reload(prefs), warn); /* bind hotkeys */ - let data_ptr = - unsafe { - mem::transmute::<&Hotkeys, - glib_sys::gpointer>(hotkeys.as_ref()) - }; + let data_ptr = unsafe { + mem::transmute::<&Hotkeys, glib_sys::gpointer>(hotkeys.as_ref()) + }; hotkeys_add_filter(Some(key_filter::), data_ptr); return WOk(hotkeys, warn); } @@ -135,29 +135,17 @@ impl Hotkeys pub fn bind(&self) { debug!("Bind hotkeys"); if self.mute_key.is_some() { - if self.mute_key - .as_ref() - .unwrap() - .grab() - .is_err() { + if self.mute_key.as_ref().unwrap().grab().is_err() { warn!("Could not grab mute key"); }; } if self.up_key.is_some() { - if self.up_key - .as_ref() - .unwrap() - .grab() - .is_err() { + if self.up_key.as_ref().unwrap().grab().is_err() { warn!("Could not grab volume up key"); }; } if self.down_key.is_some() { - if self.down_key - .as_ref() - .unwrap() - .grab() - .is_err() { + if self.down_key.as_ref().unwrap().grab().is_err() { warn!("Could not grab volume down key"); }; } @@ -171,22 +159,13 @@ impl Hotkeys pub fn unbind(&self) { debug!("Unbind hotkeys"); if self.mute_key.is_some() { - self.mute_key - .as_ref() - .unwrap() - .ungrab(); + self.mute_key.as_ref().unwrap().ungrab(); } if self.up_key.is_some() { - self.up_key - .as_ref() - .unwrap() - .ungrab(); + self.up_key.as_ref().unwrap().ungrab(); } if self.down_key.is_some() { - self.down_key - .as_ref() - .unwrap() - .ungrab(); + self.down_key.as_ref().unwrap().ungrab(); } let data_ptr = @@ -196,7 +175,8 @@ impl Hotkeys } impl Drop for Hotkeys - where T: AudioFrontend +where + T: AudioFrontend, { fn drop(&mut self) { debug!("Freeing hotkeys"); @@ -215,13 +195,15 @@ impl Drop for Hotkeys /// Attaches the `key_filter()` function as a filter /// to the root window, so it will intercept window events. -fn hotkeys_add_filter(function: gdk_sys::GdkFilterFunc, - data: glib_sys::gpointer) { +fn hotkeys_add_filter( + function: gdk_sys::GdkFilterFunc, + data: glib_sys::gpointer, +) { // TODO: all the unwrapping :/ let window = gdk_x11::gdk_x11_window_foreign_new_for_display( &mut gdk::Display::get_default().unwrap(), - gdk_x11::gdk_x11_get_default_root_xwindow() - ).unwrap(); + gdk_x11::gdk_x11_get_default_root_xwindow(), + ).unwrap(); unsafe { gdk_sys::gdk_window_add_filter(window.to_glib_none().0, function, data); @@ -231,18 +213,22 @@ fn hotkeys_add_filter(function: gdk_sys::GdkFilterFunc, /// Removes the previously attached `key_filter()` function from /// the root window. -fn hotkeys_remove_filter(function: gdk_sys::GdkFilterFunc, - data: glib_sys::gpointer) { +fn hotkeys_remove_filter( + function: gdk_sys::GdkFilterFunc, + data: glib_sys::gpointer, +) { // TODO: all the unwrapping :/ let window = gdk_x11::gdk_x11_window_foreign_new_for_display( &mut gdk::Display::get_default().unwrap(), - gdk_x11::gdk_x11_get_default_root_xwindow() - ).unwrap(); + gdk_x11::gdk_x11_get_default_root_xwindow(), + ).unwrap(); unsafe { - gdk_sys::gdk_window_remove_filter(window.to_glib_none().0, - function, - data); + gdk_sys::gdk_window_remove_filter( + window.to_glib_none().0, + function, + data, + ); } } @@ -250,11 +236,13 @@ fn hotkeys_remove_filter(function: gdk_sys::GdkFilterFunc, /// This function is called before Gtk/Gdk can respond /// to any(!) window event and handles pressed hotkeys. -extern "C" fn key_filter(gdk_xevent: *mut gdk_sys::GdkXEvent, - _: *mut gdk_sys::GdkEvent, - data: glib_sys::gpointer) - -> gdk_sys::GdkFilterReturn - where T: AudioFrontend +extern "C" fn key_filter( + gdk_xevent: *mut gdk_sys::GdkXEvent, + _: *mut gdk_sys::GdkEvent, + data: glib_sys::gpointer, +) -> gdk_sys::GdkFilterReturn +where + T: AudioFrontend, { let xevent = gdk_xevent as *mut x11::xlib::XKeyEvent; @@ -275,25 +263,31 @@ extern "C" fn key_filter(gdk_xevent: *mut gdk_sys::GdkXEvent, if mute_key.as_ref().is_some() && - mute_key.as_ref() - .unwrap() - .matches(xevent_key as i32, - gdk::ModifierType::from_bits(xevent_state).unwrap()) { + mute_key.as_ref().unwrap().matches( + xevent_key as i32, + gdk::ModifierType::from_bits(xevent_state) + .unwrap(), + ) + { just_warn!(audio.toggle_mute(AudioUser::Hotkeys)); } else if up_key.as_ref().is_some() && - up_key.as_ref() - .unwrap() - .matches(xevent_key as i32, - gdk::ModifierType::from_bits(xevent_state) - .unwrap()) { + up_key.as_ref().unwrap().matches( + xevent_key as i32, + gdk::ModifierType::from_bits( + xevent_state, + ).unwrap(), + ) + { just_warn!(audio.increase_vol(AudioUser::Hotkeys, hotkeys.auto_unmute)); } else if down_key.as_ref().is_some() && - down_key.as_ref() - .unwrap() - .matches(xevent_key as i32, - gdk::ModifierType::from_bits(xevent_state) - .unwrap()) { + down_key.as_ref().unwrap().matches( + xevent_key as i32, + gdk::ModifierType::from_bits( + xevent_state, + ).unwrap(), + ) + { just_warn!(audio.decrease_vol(AudioUser::Hotkeys, hotkeys.auto_unmute)); } diff --git a/src/lib.rs b/src/lib.rs index 16a2f2e22..8829cff2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ extern crate serde_derive; extern crate toml; extern crate serde; -extern crate alsa; +extern crate alsa as alsa_lib; extern crate alsa_sys; extern crate ffi; extern crate gdk; @@ -73,27 +73,15 @@ extern crate lazy_static; #[macro_use] pub mod errors; -#[macro_use] -pub mod glade_helpers; - -pub mod alsa_backend; pub mod app_state; -pub mod audio_frontend; -pub mod gdk_x11; +pub mod audio; pub mod hotkey; pub mod hotkeys; pub mod prefs; -pub mod support_alsa; -pub mod support_audio; -pub mod support_cmd; #[macro_use] -pub mod support_ui; -pub mod ui_entry; -pub mod ui_hotkey_dialog; -pub mod ui_popup_menu; -pub mod ui_popup_window; -pub mod ui_prefs_dialog; -pub mod ui_tray_icon; +pub mod support; +#[macro_use] +pub mod ui; #[cfg(feature = "notify")] pub mod notif; diff --git a/src/notif.rs b/src/notif.rs index 01a82b5e6..38f868512 100644 --- a/src/notif.rs +++ b/src/notif.rs @@ -5,7 +5,7 @@ use app_state::*; -use audio_frontend::*; +use audio::frontend::*; use errors::*; use glib::prelude::*; use libnotify; @@ -63,18 +63,23 @@ impl Notif { self.from_external.set(prefs.notify_prefs.notify_external); self.volume_notif.set_timeout(timeout as i32); - self.volume_notif.set_hint("x-canonical-private-synchronous", - Some("".to_variant())); + self.volume_notif.set_hint( + "x-canonical-private-synchronous", + Some("".to_variant()), + ); self.text_notif.set_timeout(timeout as i32); - self.text_notif.set_hint("x-canonical-private-synchronous", - Some("".to_variant())); + self.text_notif.set_hint( + "x-canonical-private-synchronous", + Some("".to_variant()), + ); } /// Shows a volume notification, e.g. for volume or mute state change. pub fn show_volume_notif(&self, audio: &T) -> Result<()> - where T: AudioFrontend + where + T: AudioFrontend, { let vol = audio.get_vol()?; let vol_level = audio.vol_level(); @@ -93,17 +98,24 @@ impl Notif { match vol_level { VolLevel::Muted => String::from("Volume muted"), _ => { - format!("{} ({})\nVolume: {}", - audio.card_name()?, - audio.chan_name()?, - vol as i32) + format!( + "{} ({})\nVolume: {}", + audio.card_name()?, + audio.chan_name()?, + vol as i32 + ) } } }; // TODO: error handling - self.volume_notif.update(summary.as_str(), None, Some(icon)).unwrap(); - self.volume_notif.set_hint("value", Some((vol as i32).to_variant())); + self.volume_notif + .update(summary.as_str(), None, Some(icon)) + .unwrap(); + self.volume_notif.set_hint( + "value", + Some((vol as i32).to_variant()), + ); // TODO: error handling self.volume_notif.show().unwrap(); @@ -132,7 +144,8 @@ impl Drop for Notif { /// Initialize the notification subsystem. pub fn init_notify(appstate: Rc>) - where T: AudioFrontend + 'static +where + T: AudioFrontend + 'static, { { /* connect handler */ diff --git a/src/prefs.rs b/src/prefs.rs index 12e428a34..a45eb64cd 100644 --- a/src/prefs.rs +++ b/src/prefs.rs @@ -73,16 +73,16 @@ impl From for i32 { /// Device preferences tab. pub struct DevicePrefs { pub card: String, - pub channel: String, + pub channel: String, // TODO: normalize volume? } impl Default for DevicePrefs { fn default() -> DevicePrefs { return DevicePrefs { - card: String::from("(default)"), - channel: String::from("Master"), - }; + card: String::from("(default)"), + channel: String::from("Master"), + }; } } @@ -94,18 +94,18 @@ pub struct ViewPrefs { pub draw_vol_meter: bool, pub vol_meter_offset: i32, pub system_theme: bool, - pub vol_meter_color: VolColor, + pub vol_meter_color: VolColor, // TODO: Display text folume/text volume pos? } impl Default for ViewPrefs { fn default() -> ViewPrefs { return ViewPrefs { - draw_vol_meter: true, - vol_meter_offset: 10, - system_theme: true, - vol_meter_color: VolColor::default(), - }; + draw_vol_meter: true, + vol_meter_offset: 10, + system_theme: true, + vol_meter_color: VolColor::default(), + }; } } @@ -122,10 +122,10 @@ pub struct VolColor { impl Default for VolColor { fn default() -> VolColor { return VolColor { - red: 0.960784313725, - green: 0.705882352941, - blue: 0.0, - }; + red: 0.960784313725, + green: 0.705882352941, + blue: 0.0, + }; } } @@ -145,13 +145,13 @@ pub struct BehaviorPrefs { impl Default for BehaviorPrefs { fn default() -> BehaviorPrefs { return BehaviorPrefs { - unmute_on_vol_change: true, - vol_control_cmd: None, - vol_scroll_step: 5.0, - vol_fine_scroll_step: 1.0, - middle_click_action: MiddleClickAction::default(), - custom_command: None, - }; + unmute_on_vol_change: true, + vol_control_cmd: None, + vol_scroll_step: 5.0, + vol_fine_scroll_step: 1.0, + middle_click_action: MiddleClickAction::default(), + custom_command: None, + }; } } @@ -173,13 +173,13 @@ pub struct NotifyPrefs { impl Default for NotifyPrefs { fn default() -> NotifyPrefs { return NotifyPrefs { - enable_notifications: true, - notifcation_timeout: 1500, - notify_mouse_scroll: true, - notify_popup: true, - notify_external: true, - notify_hotkeys: true, - }; + enable_notifications: true, + notifcation_timeout: 1500, + notify_mouse_scroll: true, + notify_popup: true, + notify_external: true, + notify_hotkeys: true, + }; } } @@ -249,22 +249,26 @@ impl Prefs { /// Store the current preferences to the config file. pub fn store_config(&self) -> Result<()> { - let config_path = get_xdg_dirs().place_config_file("pnmixer.toml") + let config_path = get_xdg_dirs() + .place_config_file("pnmixer.toml") .chain_err(|| { - format!("Could not create config directory at {:?}", - get_xdg_dirs().get_config_home()) - })?; + format!( + "Could not create config directory at {:?}", + get_xdg_dirs().get_config_home() + ) + })?; debug!("Storing config in {:?}", config_path); - let mut f = File::create(config_path.clone()) - .chain_err(|| format!("Could not open/create config file {:?} for writing", - config_path))?; - f.write_all(self.to_str().as_bytes()) - .chain_err(|| { - format!("Could not write to config file {:?}", - config_path) - })?; + let mut f = File::create(config_path.clone()).chain_err(|| { + format!( + "Could not open/create config file {:?} for writing", + config_path + ) + })?; + f.write_all(self.to_str().as_bytes()).chain_err(|| { + format!("Could not write to config file {:?}", config_path) + })?; return Ok(()); } @@ -297,9 +301,10 @@ impl Prefs { } impl Display for Prefs { - fn fmt(&self, - f: &mut Formatter) - -> std::result::Result<(), std::fmt::Error> { + fn fmt( + &self, + f: &mut Formatter, + ) -> std::result::Result<(), std::fmt::Error> { let s = self.to_str(); return write!(f, "{}", s); } diff --git a/src/support_alsa.rs b/src/support/alsa.rs similarity index 90% rename from src/support_alsa.rs rename to src/support/alsa.rs index cc6b9c320..86c0b9e25 100644 --- a/src/support_alsa.rs +++ b/src/support/alsa.rs @@ -4,9 +4,9 @@ //! out the details we don't care about. -use alsa::card::Card; -use alsa::mixer::{Mixer, Selem, Elem}; -use alsa; +use alsa_lib::card::Card; +use alsa_lib::mixer::{Mixer, Selem, Elem}; +use alsa_lib; use errors::*; use libc::c_int; @@ -25,8 +25,8 @@ pub fn get_alsa_card_by_id(index: c_int) -> Card { /// Get all available alsa cards. -pub fn get_alsa_cards() -> alsa::card::Iter { - return alsa::card::Iter::new(); +pub fn get_alsa_cards() -> alsa_lib::card::Iter { + return alsa_lib::card::Iter::new(); } @@ -144,13 +144,12 @@ pub fn get_playable_selem_names(mixer: &Mixer) -> Vec { /// Get a playable `Selem` by the given name. -pub fn get_playable_selem_by_name(mixer: &Mixer, - name: String) - -> Result { +pub fn get_playable_selem_by_name( + mixer: &Mixer, + name: String, +) -> Result { for selem in get_playable_selems(mixer) { - let n = selem.get_id() - .get_name() - .map(|y| String::from(y))?; + let n = selem.get_id().get_name().map(|y| String::from(y))?; if n == name { return Ok(selem); diff --git a/src/support_audio.rs b/src/support/audio.rs similarity index 89% rename from src/support_audio.rs rename to src/support/audio.rs index b094c7e39..93d99c6b3 100644 --- a/src/support_audio.rs +++ b/src/support/audio.rs @@ -6,10 +6,10 @@ //! but are important helpers. -use audio_frontend::*; +use audio::frontend::*; use errors::*; use prefs::*; -use support_alsa::*; +use support::alsa::*; #[derive(Clone, Copy, Debug)] @@ -55,7 +55,8 @@ pub fn lrint(v: f64, dir: VolDir) -> f64 { /// Reload the audio system. pub fn audio_reload(audio: &T, prefs: &Prefs, user: AudioUser) -> Result<()> - where T: AudioFrontend +where + T: AudioFrontend, { let card = &prefs.device_prefs.card; let channel = &prefs.device_prefs.channel; @@ -69,10 +70,12 @@ pub fn audio_reload(audio: &T, prefs: &Prefs, user: AudioUser) -> Result<()> /// of the volume level. pub fn vol_to_percent(vol: i64, range: (i64, i64)) -> Result { let (min, max) = range; - ensure!(min < max, - "Invalid playback volume range [{} - {}]", - min, - max); + ensure!( + min < max, + "Invalid playback volume range [{} - {}]", + min, + max + ); let perc = ((vol - min) as f64) / ((max - min) as f64) * 100.0; return Ok(perc); } @@ -83,10 +86,12 @@ pub fn vol_to_percent(vol: i64, range: (i64, i64)) -> Result { /// range. pub fn percent_to_vol(vol: f64, range: (i64, i64), dir: VolDir) -> Result { let (min, max) = range; - ensure!(min < max, - "Invalid playback volume range [{} - {}]", - min, - max); + ensure!( + min < max, + "Invalid playback volume range [{} - {}]", + min, + max + ); let _v = lrint(vol / 100.0 * ((max - min) as f64), dir) + (min as f64); return Ok(_v as i64); diff --git a/src/support_cmd.rs b/src/support/cmd.rs similarity index 76% rename from src/support_cmd.rs rename to src/support/cmd.rs index 36414f9ba..1679c79f9 100644 --- a/src/support_cmd.rs +++ b/src/support/cmd.rs @@ -25,9 +25,8 @@ pub fn execute_vol_control_command(prefs: &Prefs) -> Result<()> { /// Try to execute the given command asynchronously via gtk. pub fn execute_command(cmd: &str) -> Result<()> { return glib::spawn_command_line_async(cmd) - .map_err(|e| { - std::io::Error::new(std::io::ErrorKind::Other, - e.description()) - }) - .from_err(); + .map_err(|e| { + std::io::Error::new(std::io::ErrorKind::Other, e.description()) + }) + .from_err(); } diff --git a/src/gdk_x11.rs b/src/support/gdk_x11.rs similarity index 81% rename from src/gdk_x11.rs rename to src/support/gdk_x11.rs index 6428aeab0..5a3258b84 100644 --- a/src/gdk_x11.rs +++ b/src/support/gdk_x11.rs @@ -18,8 +18,10 @@ mod ffi { extern "C" { pub fn gdk_x11_get_default_xdisplay() -> *mut Display; pub fn gdk_x11_get_default_root_xwindow() -> Window; - pub fn gdk_x11_window_foreign_new_for_display(display: *mut GdkDisplay, -window: Window) -> * mut GdkWindow; + pub fn gdk_x11_window_foreign_new_for_display( + display: *mut GdkDisplay, + window: Window, + ) -> *mut GdkWindow; } } @@ -64,14 +66,17 @@ pub fn gdk_x11_get_default_root_xwindow() -> Window { /// /// a GdkWindow wrapper for the native window, or `None` if the window has been /// destroyed. The wrapper will be newly created, if one doesn’t exist already. -pub fn gdk_x11_window_foreign_new_for_display(gdk_display: &mut gdk::Display, - xwindow: Window) - -> Option { +pub fn gdk_x11_window_foreign_new_for_display( + gdk_display: &mut gdk::Display, + xwindow: Window, +) -> Option { unsafe { let display: *mut GdkDisplay = mut_override(gdk_display.to_glib_none().0); - return from_glib_full( - ffi::gdk_x11_window_foreign_new_for_display(display, xwindow)); + return from_glib_full(ffi::gdk_x11_window_foreign_new_for_display( + display, + xwindow, + )); } } diff --git a/src/glade_helpers.rs b/src/support/glade.rs similarity index 100% rename from src/glade_helpers.rs rename to src/support/glade.rs diff --git a/src/support/mod.rs b/src/support/mod.rs new file mode 100644 index 000000000..f4e4ee122 --- /dev/null +++ b/src/support/mod.rs @@ -0,0 +1,13 @@ +//! Support subsystem, with no specific logical coherence. +//! +//! This module provides helper/support functions of various types that +//! don't logically fit elsewhere. + +pub mod alsa; +pub mod audio; +pub mod cmd; +pub mod gdk_x11; +#[macro_use] +pub mod glade; +#[macro_use] +pub mod ui; diff --git a/src/support_ui.rs b/src/support/ui.rs similarity index 82% rename from src/support_ui.rs rename to src/support/ui.rs index 8638aa59b..a4464b185 100644 --- a/src/support_ui.rs +++ b/src/support/ui.rs @@ -28,18 +28,21 @@ pub fn copy_pixbuf(pixbuf: &gdk_pixbuf::Pixbuf) -> gdk_pixbuf::Pixbuf { /// Get a pixbuf by name from the given theme with the requested size. /// Note that the size is not enforced, but rather a hint. -pub fn pixbuf_new_from_theme(icon_name: &str, - size: i32, - theme: >k::IconTheme) - -> Result { +pub fn pixbuf_new_from_theme( + icon_name: &str, + size: i32, + theme: >k::IconTheme, +) -> Result { - let icon_info = - theme.lookup_icon(icon_name, size, gtk::IconLookupFlags::empty()) - .ok_or(format!("Couldn't find icon {}", icon_name))?; + let icon_info = theme + .lookup_icon(icon_name, size, gtk::IconLookupFlags::empty()) + .ok_or(format!("Couldn't find icon {}", icon_name))?; - debug!("Loading stock icon {} from {:?}", - icon_name, - icon_info.get_filename().unwrap_or(PathBuf::new())); + debug!( + "Loading stock icon {} from {:?}", + icon_name, + icon_info.get_filename().unwrap_or(PathBuf::new()) + ); // TODO: propagate error let pixbuf = icon_info.load_icon().unwrap(); diff --git a/src/ui/entry.rs b/src/ui/entry.rs new file mode 100644 index 000000000..62bb84b90 --- /dev/null +++ b/src/ui/entry.rs @@ -0,0 +1,137 @@ +//! Global GUI state. + + +use app_state::*; +use audio::frontend::*; +use gtk::DialogExt; +use gtk::MessageDialogExt; +use gtk::WidgetExt; +use gtk::WindowExt; +use gtk; +use gtk_sys::GTK_RESPONSE_YES; +use prefs::*; +use std::cell::RefCell; +use std::rc::Rc; +use support::audio::*; +use ui::popup_menu::*; +use ui::popup_window::*; +use ui::prefs_dialog::*; +use ui::tray_icon::*; + +#[cfg(feature = "notify")] +use notif::*; + + + +/// The GUI struct mostly describing the main widgets (mostly wrapped) +/// the user interacts with. +pub struct Gui { + _cant_construct: (), + /// The tray icon. + pub tray_icon: TrayIcon, + /// The popup window. + pub popup_window: PopupWindow, + /// The popup menu. + pub popup_menu: PopupMenu, + /* prefs_dialog is dynamically created and destroyed */ + /// The preferences dialog. + pub prefs_dialog: RefCell>, +} + +impl Gui { + /// Constructor. The prefs dialog is initialized as `None`. + pub fn new( + builder_popup_window: gtk::Builder, + builder_popup_menu: gtk::Builder, + prefs: &Prefs, + ) -> Gui { + return Gui { + _cant_construct: (), + tray_icon: TrayIcon::new(prefs).unwrap(), + popup_window: PopupWindow::new(builder_popup_window), + popup_menu: PopupMenu::new(builder_popup_menu), + prefs_dialog: RefCell::new(None), + }; + } +} + + +/// Initialize the GUI system. +pub fn init(appstate: Rc>) +where + T: AudioFrontend + 'static, +{ + { + /* "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, + )); + } + } + _ => (), + }), + ); + + } + + init_tray_icon(appstate.clone()); + init_popup_window(appstate.clone()); + init_popup_menu(appstate.clone()); + init_prefs_callback(appstate.clone()); + + #[cfg(feature = "notify")] init_notify(appstate.clone()); +} + + +/// Used to run a dialog when an audio error occured, suggesting the user +/// may reload the audio system either manually or by confirming the dialog +/// via the confirmation button. +/// +/// # Returns +/// +/// `GTK_RESPONSE_YES` if the user wants to reload the audio system, +/// `GTK_RESPONSE_NO` otherwise. +fn run_audio_error_dialog(parent: >k::Window) -> i32 { + error!( + "Connection with audio failed, you probably need to restart pnmixer." + ); + + let dialog = gtk::MessageDialog::new( + Some(parent), + gtk::DIALOG_DESTROY_WITH_PARENT, + gtk::MessageType::Error, + gtk::ButtonsType::YesNo, + "Warning: Connection to sound system failed.", + ); + dialog.set_property_secondary_text(Some( + "Do you want to re-initialize the audio connection ? + +If you do not, you will either need to restart PNMixer +or select the 'Reload Audio' option in the right-click +menu in order for PNMixer to function.", + )); + + dialog.set_title("PNMixer-rs Error"); + + let resp = dialog.run(); + dialog.destroy(); + + return resp; +} diff --git a/src/ui_hotkey_dialog.rs b/src/ui/hotkey_dialog.rs similarity index 60% rename from src/ui_hotkey_dialog.rs rename to src/ui/hotkey_dialog.rs index f1e858f86..fcfbc6292 100644 --- a/src/ui_hotkey_dialog.rs +++ b/src/ui/hotkey_dialog.rs @@ -26,23 +26,26 @@ pub struct HotkeyDialog { impl HotkeyDialog { /// Creates a new hotkey dialog. pub fn new

(parent: &P, hotkey: String) -> HotkeyDialog - where P: IsA + where + P: IsA, { - let builder = - gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), - "/data/ui/hotkey-dialog.glade"))); + let builder = gtk::Builder::new_from_string(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/data/ui/hotkey-dialog.glade" + ))); - let hotkey_dialog: gtk::Dialog = builder.get_object("hotkey_dialog") - .unwrap(); - let instruction_label: gtk::Label = builder.get_object("instruction_label") - .unwrap(); - let key_pressed_label: gtk::Label = builder.get_object("key_pressed_label") - .unwrap(); + let hotkey_dialog: gtk::Dialog = + builder.get_object("hotkey_dialog").unwrap(); + let instruction_label: gtk::Label = + builder.get_object("instruction_label").unwrap(); + let key_pressed_label: gtk::Label = + builder.get_object("key_pressed_label").unwrap(); hotkey_dialog.set_title(format!("Set {} HotKey", hotkey).as_str()); - instruction_label.set_markup(format!("Press new HotKey for {}", - hotkey) - .as_str()); + instruction_label.set_markup( + format!("Press new HotKey for {}", hotkey) + .as_str(), + ); hotkey_dialog.set_transient_for(parent); @@ -63,16 +66,19 @@ impl HotkeyDialog { &mut keyval as *mut c_uint, std::ptr::null_mut(), std::ptr::null_mut(), - &mut consumed as *mut gdk_sys::GdkModifierType); + &mut consumed as *mut gdk_sys::GdkModifierType, + ); let consumed: gdk::ModifierType = from_glib(!consumed); state = state & consumed; state = state & gtk::accelerator_get_default_mod_mask(); let key_text = gtk::accelerator_name(keyval, state); - key_pressed_label.set_text(key_text - .unwrap_or(String::from("(None)")) - .as_str()); + key_pressed_label.set_text( + key_text + .unwrap_or(String::from("(None)")) + .as_str(), + ); }; return Inhibit(false); }); @@ -86,28 +92,30 @@ impl HotkeyDialog { }); return HotkeyDialog { - hotkey_dialog, - key_pressed_label, - }; + hotkey_dialog, + key_pressed_label, + }; } /// Runs the hotkey dialog and returns a String representing the hotkey /// that has been pressed. pub fn run(&self) -> Result { self.hotkey_dialog.show_now(); - let device = gtk::get_current_event_device() - .ok_or("Could not get current device")?; - let window = self.hotkey_dialog - .get_window() - .ok_or("Could not get window")?; + let device = gtk::get_current_event_device().ok_or( + "Could not get current device", + )?; + let window = self.hotkey_dialog.get_window().ok_or( + "Could not get window", + )?; - let m_grab_status = - device.grab(&window, - gdk::GrabOwnership::Application, - true, - gdk::KEY_PRESS_MASK, - None, - gdk_sys::GDK_CURRENT_TIME as u32); + let m_grab_status = device.grab( + &window, + gdk::GrabOwnership::Application, + true, + gdk::KEY_PRESS_MASK, + None, + gdk_sys::GDK_CURRENT_TIME as u32, + ); if m_grab_status != gdk::GrabStatus::Success { bail!("Could not grab the keyboard"); @@ -117,12 +125,14 @@ impl HotkeyDialog { device.ungrab(gdk_sys::GDK_CURRENT_TIME as u32); if resp != gtk::ResponseType::Ok.into() { - bail!(ErrorKind::GtkResponseCancel(String::from("not assigning hotkey"))); + bail!(ErrorKind::GtkResponseCancel( + String::from("not assigning hotkey"), + )); } - return Ok(self.key_pressed_label - .get_text() - .ok_or("Could not get text")?); + return Ok(self.key_pressed_label.get_text().ok_or( + "Could not get text", + )?); } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 000000000..31acf9a24 --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,8 @@ +//! The UI subsystem. + +pub mod entry; +pub mod hotkey_dialog; +pub mod popup_menu; +pub mod popup_window; +pub mod prefs_dialog; +pub mod tray_icon; diff --git a/src/ui_popup_menu.rs b/src/ui/popup_menu.rs similarity index 70% rename from src/ui_popup_menu.rs rename to src/ui/popup_menu.rs index d4f9803bd..d4ea752ef 100644 --- a/src/ui_popup_menu.rs +++ b/src/ui/popup_menu.rs @@ -12,46 +12,46 @@ //! * Quit use app_state::*; -use audio_frontend::*; +use audio::frontend::*; use gtk::prelude::*; use gtk; use std::rc::Rc; -use support_audio::*; -use support_cmd::*; -use ui_prefs_dialog::*; +use support::audio::*; +use support::cmd::*; +use ui::prefs_dialog::*; const VERSION: &'static str = env!("CARGO_PKG_VERSION"); -create_builder_item!(PopupMenu, - menu_window: gtk::Window, - menubar: gtk::MenuBar, - menu: gtk::Menu, - about_item: gtk::MenuItem, - mixer_item: gtk::MenuItem, - mute_item: gtk::MenuItem, - mute_check: gtk::CheckButton, - prefs_item: gtk::MenuItem, - quit_item: gtk::MenuItem, - reload_item: gtk::MenuItem); +create_builder_item!( + PopupMenu, + menu_window: gtk::Window, + menubar: gtk::MenuBar, + menu: gtk::Menu, + about_item: gtk::MenuItem, + mixer_item: gtk::MenuItem, + mute_item: gtk::MenuItem, + mute_check: gtk::CheckButton, + prefs_item: gtk::MenuItem, + quit_item: gtk::MenuItem, + reload_item: gtk::MenuItem +); /// Initialize the popup menu subsystem, registering all callbacks. pub fn init_popup_menu(appstate: Rc>) - where T: AudioFrontend + 'static +where + T: AudioFrontend + 'static, { /* 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() { + if !apps.gui.popup_menu.menu.get_visible() { return; } match (s, u) { @@ -64,10 +64,9 @@ pub fn init_popup_menu(appstate: Rc>) /* popup_menu.menu.connect_show */ { let apps = appstate.clone(); - appstate.gui - .popup_menu - .menu - .connect_show(move |_| set_mute_check(&apps)); + appstate.gui.popup_menu.menu.connect_show( + move |_| set_mute_check(&apps), + ); } @@ -76,8 +75,10 @@ pub fn init_popup_menu(appstate: Rc>) let apps = appstate.clone(); let mixer_item = &appstate.gui.popup_menu.mixer_item; mixer_item.connect_activate(move |_| { - let _ = result_warn!(execute_vol_control_command(&apps.prefs.borrow()), - Some(&apps.gui.popup_menu.menu_window)); + let _ = result_warn!( + execute_vol_control_command(&apps.prefs.borrow()), + Some(&apps.gui.popup_menu.menu_window) + ); }); } @@ -85,10 +86,8 @@ pub fn init_popup_menu(appstate: Rc>) { let apps = appstate.clone(); let mute_item = &appstate.gui.popup_menu.mute_item; - mute_item.connect_activate(move |_| { - if apps.audio.has_mute() { - try_w!(apps.audio.toggle_mute(AudioUser::Popup)); - } + mute_item.connect_activate(move |_| if apps.audio.has_mute() { + try_w!(apps.audio.toggle_mute(AudioUser::Popup)); }); } @@ -96,18 +95,18 @@ pub fn init_popup_menu(appstate: Rc>) { let apps = appstate.clone(); let about_item = &appstate.gui.popup_menu.about_item; - about_item.connect_activate(move |_| { - on_about_item_activate(&apps); - }); + about_item.connect_activate( + move |_| { on_about_item_activate(&apps); }, + ); } /* prefs_item.connect_activate_link */ { let apps = appstate.clone(); let prefs_item = &appstate.gui.popup_menu.prefs_item; - prefs_item.connect_activate(move |_| { - on_prefs_item_activate(&apps); - }); + prefs_item.connect_activate( + move |_| { on_prefs_item_activate(&apps); }, + ); } /* reload_item.connect_activate_link */ @@ -115,10 +114,12 @@ pub fn init_popup_menu(appstate: Rc>) let apps = appstate.clone(); let reload_item = &appstate.gui.popup_menu.reload_item; reload_item.connect_activate(move |_| { - try_w!(audio_reload(apps.audio.as_ref(), - &apps.prefs.borrow(), - AudioUser::Popup)) - }); + try_w!(audio_reload( + apps.audio.as_ref(), + &apps.prefs.borrow(), + AudioUser::Popup, + )) + }); } @@ -132,7 +133,8 @@ pub fn init_popup_menu(appstate: Rc>) /// When the about menu item is activated. fn on_about_item_activate(appstate: &AppS) - where T: AudioFrontend +where + T: AudioFrontend, { let popup_menu = &appstate.gui.popup_menu.menu_window; let about_dialog = create_about_dialog(); @@ -176,7 +178,8 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.", /// When the Preferences item is activated. fn on_prefs_item_activate(appstate: &Rc>) - where T: AudioFrontend + 'static +where + T: AudioFrontend + 'static, { /* TODO: only create if needed */ show_prefs_dialog(appstate); @@ -185,7 +188,8 @@ fn on_prefs_item_activate(appstate: &Rc>) /// When the Mute item is checked. fn set_mute_check(apps: &Rc>) - where T: AudioFrontend +where + T: AudioFrontend, { let mute_check = &apps.gui.popup_menu.mute_check; let m_muted = apps.audio.get_mute(); diff --git a/src/ui_popup_window.rs b/src/ui/popup_window.rs similarity index 60% rename from src/ui_popup_window.rs rename to src/ui/popup_window.rs index 2d6dec58c..69ff00805 100644 --- a/src/ui_popup_window.rs +++ b/src/ui/popup_window.rs @@ -5,7 +5,7 @@ use app_state::*; -use audio_frontend::*; +use audio::frontend::*; use errors::*; use gdk::DeviceExt; use gdk::{GrabOwnership, GrabStatus, BUTTON_PRESS_MASK, KEY_PRESS_MASK}; @@ -18,8 +18,8 @@ use gtk; use prefs::*; use std::cell::Cell; use std::rc::Rc; -use support_audio::*; -use support_cmd::*; +use support::audio::*; +use support::cmd::*; @@ -49,21 +49,22 @@ impl PopupWindow { /// Constructor. pub fn new(builder: gtk::Builder) -> PopupWindow { return PopupWindow { - _cant_construct: (), - popup_window: builder.get_object("popup_window").unwrap(), - vol_scale_adj: builder.get_object("vol_scale_adj").unwrap(), - vol_scale: builder.get_object("vol_scale").unwrap(), - mute_check: builder.get_object("mute_check").unwrap(), - mixer_button: builder.get_object("mixer_button").unwrap(), - toggle_signal: Cell::new(0), - changed_signal: Cell::new(0), - }; + _cant_construct: (), + popup_window: builder.get_object("popup_window").unwrap(), + vol_scale_adj: builder.get_object("vol_scale_adj").unwrap(), + vol_scale: builder.get_object("vol_scale").unwrap(), + mute_check: builder.get_object("mute_check").unwrap(), + mixer_button: builder.get_object("mixer_button").unwrap(), + toggle_signal: Cell::new(0), + changed_signal: Cell::new(0), + }; } /// Update the popup window state, including the slider /// and the mute checkbutton. pub fn update(&self, audio: &T) -> Result<()> - where T: AudioFrontend + where + T: AudioFrontend, { let cur_vol = audio.get_vol()?; set_slider(&self.vol_scale_adj, cur_vol); @@ -75,7 +76,8 @@ impl PopupWindow { /// Update the mute checkbutton. pub fn update_mute_check(&self, audio: &T) - where T: AudioFrontend + where + T: AudioFrontend, { let m_muted = audio.get_mute(); @@ -97,34 +99,36 @@ impl PopupWindow { } } - glib::signal_handler_unblock(&self.mute_check, - self.toggle_signal.get()); + glib::signal_handler_unblock( + &self.mute_check, + self.toggle_signal.get(), + ); } /// Set the page increment fro the volume scale adjustment based on the /// preferences. fn set_vol_increment(&self, prefs: &Prefs) { - self.vol_scale_adj - .set_page_increment(prefs.behavior_prefs.vol_scroll_step); - self.vol_scale_adj - .set_step_increment(prefs.behavior_prefs.vol_fine_scroll_step); + self.vol_scale_adj.set_page_increment( + prefs.behavior_prefs.vol_scroll_step, + ); + self.vol_scale_adj.set_step_increment( + prefs.behavior_prefs.vol_fine_scroll_step, + ); } } /// Initialize the popup window subsystem. pub fn init_popup_window(appstate: Rc>) - where T: AudioFrontend + 'static +where + T: AudioFrontend + 'static, { /* 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() { + if !apps.gui.popup_window.popup_window.get_visible() { return; } match (s, u) { @@ -136,8 +140,9 @@ pub fn init_popup_window(appstate: Rc>) * and not the real value reported by the audio system. */ (_, AudioUser::Popup) => { - apps.gui.popup_window.update_mute_check(apps.audio - .as_ref()); + apps.gui.popup_window.update_mute_check( + apps.audio.as_ref(), + ); } /* external change, safe to update slider too */ (_, _) => { @@ -150,70 +155,47 @@ pub fn init_popup_window(appstate: Rc>) /* mute_check.connect_toggled */ { let _appstate = appstate.clone(); - let mute_check = &appstate.clone() - .gui - .popup_window - .mute_check; - let toggle_signal = - mute_check.connect_toggled(move |_| { - on_mute_check_toggled(&_appstate) - }); - appstate.gui - .popup_window - .toggle_signal - .set(toggle_signal); + let mute_check = &appstate.clone().gui.popup_window.mute_check; + let toggle_signal = mute_check.connect_toggled(move |_| { + on_mute_check_toggled(&_appstate) + }); + appstate.gui.popup_window.toggle_signal.set(toggle_signal); } /* popup_window.connect_show */ { let _appstate = appstate.clone(); - let popup_window = &appstate.clone() - .gui - .popup_window - .popup_window; + let popup_window = &appstate.clone().gui.popup_window.popup_window; popup_window.connect_show(move |_| on_popup_window_show(&_appstate)); } /* vol_scale_adj.connect_value_changed */ { let _appstate = appstate.clone(); - let vol_scale_adj = &appstate.clone() - .gui - .popup_window - .vol_scale_adj; - let changed_signal = vol_scale_adj.connect_value_changed( - move |_| on_vol_scale_value_changed(&_appstate), - ); + let vol_scale_adj = &appstate.clone().gui.popup_window.vol_scale_adj; + let changed_signal = vol_scale_adj.connect_value_changed(move |_| { + on_vol_scale_value_changed(&_appstate) + }); - appstate.gui - .popup_window - .changed_signal - .set(changed_signal); + appstate.gui.popup_window.changed_signal.set(changed_signal); } /* popup_window.connect_event */ { - let popup_window = &appstate.clone() - .gui - .popup_window - .popup_window; + let popup_window = &appstate.clone().gui.popup_window.popup_window; popup_window.connect_event(move |w, e| on_popup_window_event(w, e)); } /* mixer_button.connect_clicked */ { let apps = appstate.clone(); - let mixer_button = &appstate.clone() - .gui - .popup_window - .mixer_button; + let mixer_button = &appstate.clone().gui.popup_window.mixer_button; mixer_button.connect_clicked(move |_| { - apps.gui - .popup_window - .popup_window - .hide(); - let _ = result_warn!(execute_vol_control_command(&apps.prefs.borrow()), - Some(&apps.gui.popup_menu.menu_window)); + apps.gui.popup_window.popup_window.hide(); + let _ = result_warn!( + execute_vol_control_command(&apps.prefs.borrow()), + Some(&apps.gui.popup_menu.menu_window) + ); }); } } @@ -221,15 +203,22 @@ pub fn init_popup_window(appstate: Rc>) /// When the popup window is shown. fn on_popup_window_show(appstate: &AppS) - where T: AudioFrontend +where + T: AudioFrontend, { let popup_window = &appstate.gui.popup_window; - appstate.gui.popup_window.set_vol_increment(&appstate.prefs.borrow()); - glib::signal_handler_block(&popup_window.vol_scale_adj, - popup_window.changed_signal.get()); + appstate.gui.popup_window.set_vol_increment( + &appstate.prefs.borrow(), + ); + glib::signal_handler_block( + &popup_window.vol_scale_adj, + popup_window.changed_signal.get(), + ); try_w!(appstate.gui.popup_window.update(appstate.audio.as_ref())); - glib::signal_handler_unblock(&popup_window.vol_scale_adj, - popup_window.changed_signal.get()); + glib::signal_handler_unblock( + &popup_window.vol_scale_adj, + popup_window.changed_signal.get(), + ); popup_window.vol_scale.grab_focus(); try_w!(grab_devices(&appstate.gui.popup_window.popup_window)); } @@ -267,31 +256,29 @@ fn on_popup_window_event(w: >k::Window, e: &gdk::Event) -> gtk::Inhibit { /// When the volume scale slider is moved. fn on_vol_scale_value_changed(appstate: &AppS) - where T: AudioFrontend +where + T: AudioFrontend, { let audio = &appstate.audio; let old_vol = try_w!(audio.get_vol()); - let val = appstate.gui - .popup_window - .vol_scale - .get_value(); + let val = appstate.gui.popup_window.vol_scale.get_value(); let dir = vol_change_to_voldir(old_vol, val); - try_w!(audio.set_vol(val, - AudioUser::Popup, - dir, - appstate.prefs - .borrow() - .behavior_prefs - .unmute_on_vol_change)); + try_w!(audio.set_vol( + val, + AudioUser::Popup, + dir, + appstate.prefs.borrow().behavior_prefs.unmute_on_vol_change, + )); } /// When the mute checkbutton is toggled. fn on_mute_check_toggled(appstate: &AppS) - where T: AudioFrontend +where + T: AudioFrontend, { let audio = &appstate.audio; try_w!(audio.toggle_mute(AudioUser::Popup)) @@ -311,32 +298,40 @@ fn grab_devices(window: >k::Window) -> Result<()> { let gdk_window = window.get_window().ok_or("No window?!")?; /* Grab the mouse */ - let m_grab_status = - device.grab(&gdk_window, - GrabOwnership::None, - true, - BUTTON_PRESS_MASK, - None, - GDK_CURRENT_TIME as u32); + let m_grab_status = device.grab( + &gdk_window, + GrabOwnership::None, + true, + BUTTON_PRESS_MASK, + None, + GDK_CURRENT_TIME as u32, + ); if m_grab_status != GrabStatus::Success { - warn!("Could not grab {}", - device.get_name().unwrap_or(String::from("UNKNOWN DEVICE"))); + warn!( + "Could not grab {}", + device.get_name().unwrap_or(String::from("UNKNOWN DEVICE")) + ); } /* Grab the keyboard */ - let k_dev = device.get_associated_device() - .ok_or("Couldn't get associated device")?; + let k_dev = device.get_associated_device().ok_or( + "Couldn't get associated device", + )?; - let k_grab_status = k_dev.grab(&gdk_window, - GrabOwnership::None, - true, - KEY_PRESS_MASK, - None, - GDK_CURRENT_TIME as u32); + let k_grab_status = k_dev.grab( + &gdk_window, + GrabOwnership::None, + true, + KEY_PRESS_MASK, + None, + GDK_CURRENT_TIME as u32, + ); if k_grab_status != GrabStatus::Success { - warn!("Could not grab {}", - k_dev.get_name().unwrap_or(String::from("UNKNOWN DEVICE"))); + warn!( + "Could not grab {}", + k_dev.get_name().unwrap_or(String::from("UNKNOWN DEVICE")) + ); } return Ok(()); diff --git a/src/ui_prefs_dialog.rs b/src/ui/prefs_dialog.rs similarity index 62% rename from src/ui_prefs_dialog.rs rename to src/ui/prefs_dialog.rs index de3a41a51..ee4b313cb 100644 --- a/src/ui_prefs_dialog.rs +++ b/src/ui/prefs_dialog.rs @@ -3,7 +3,7 @@ use app_state::*; -use audio_frontend::*; +use audio::frontend::*; use errors::*; use gdk; use gtk::ResponseType; @@ -13,8 +13,8 @@ use prefs::*; use std::ascii::AsciiExt; use std::cell::RefCell; use std::rc::Rc; -use support_audio::*; -use ui_hotkey_dialog::HotkeyDialog; +use support::audio::*; +use ui::hotkey_dialog::HotkeyDialog; @@ -74,9 +74,10 @@ pub struct PrefsDialog { impl PrefsDialog { fn new() -> PrefsDialog { - let builder = - gtk::Builder::new_from_string(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), - "/data/ui/prefs-dialog.glade"))); + let builder = gtk::Builder::new_from_string(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/data/ui/prefs-dialog.glade" + ))); let prefs_dialog = PrefsDialog { _cant_construct: (), prefs_dialog: builder.get_object("prefs_dialog").unwrap(), @@ -87,32 +88,44 @@ impl PrefsDialog { chan_combo: builder.get_object("chan_combo").unwrap(), /* ViewPrefs */ - vol_meter_draw_check: builder.get_object("vol_meter_draw_check") + vol_meter_draw_check: builder + .get_object("vol_meter_draw_check") .unwrap(), - vol_meter_pos_spin: builder.get_object("vol_meter_pos_spin") + vol_meter_pos_spin: builder + .get_object("vol_meter_pos_spin") .unwrap(), - vol_meter_color_button: builder.get_object("vol_meter_color_button") + vol_meter_color_button: builder + .get_object("vol_meter_color_button") .unwrap(), system_theme: builder.get_object("system_theme").unwrap(), /* BehaviorPrefs */ - unmute_on_vol_change: builder.get_object("unmute_on_vol_change") + unmute_on_vol_change: builder + .get_object("unmute_on_vol_change") .unwrap(), vol_control_entry: builder.get_object("vol_control_entry").unwrap(), scroll_step_spin: builder.get_object("scroll_step_spin").unwrap(), - fine_scroll_step_spin: builder.get_object("fine_scroll_step_spin") + fine_scroll_step_spin: builder + .get_object("fine_scroll_step_spin") .unwrap(), - middle_click_combo: builder.get_object("middle_click_combo") + middle_click_combo: builder + .get_object("middle_click_combo") .unwrap(), custom_entry: builder.get_object("custom_entry").unwrap(), /* NotifyPrefs */ #[cfg(feature = "notify")] + #[cfg(feature = "notify")] + #[cfg(feature = "notify")] + #[cfg(feature = "notify")] noti_enable_check: builder.get_object("noti_enable_check").unwrap(), #[cfg(feature = "notify")] noti_timeout_spin: builder.get_object("noti_timeout_spin").unwrap(), // noti_hotkey_check: builder.get_object("noti_hotkey_check").unwrap(), #[cfg(feature = "notify")] + #[cfg(feature = "notify")] + #[cfg(feature = "notify")] + #[cfg(feature = "notify")] noti_mouse_check: builder.get_object("noti_mouse_check").unwrap(), #[cfg(feature = "notify")] noti_popup_check: builder.get_object("noti_popup_check").unwrap(), @@ -122,33 +135,41 @@ impl PrefsDialog { noti_hotkey_check: builder.get_object("noti_hotkey_check").unwrap(), /* HotkeyPrefs */ - hotkeys_enable_check: builder.get_object("hotkeys_enable_check") + hotkeys_enable_check: builder + .get_object("hotkeys_enable_check") .unwrap(), - hotkeys_mute_label: builder.get_object("hotkeys_mute_label") + hotkeys_mute_label: builder + .get_object("hotkeys_mute_label") .unwrap(), hotkeys_up_label: builder.get_object("hotkeys_up_label").unwrap(), - hotkeys_down_label: builder.get_object("hotkeys_down_label") + hotkeys_down_label: builder + .get_object("hotkeys_down_label") .unwrap(), /* Hotkey stuff (not prefs) */ - hotkeys_mute_eventbox: builder.get_object("hotkeys_mute_eventbox") + hotkeys_mute_eventbox: builder + .get_object("hotkeys_mute_eventbox") .unwrap(), - hotkeys_up_eventbox: builder.get_object("hotkeys_up_eventbox") + hotkeys_up_eventbox: builder + .get_object("hotkeys_up_eventbox") .unwrap(), - hotkeys_down_eventbox: builder.get_object("hotkeys_down_eventbox") + hotkeys_down_eventbox: builder + .get_object("hotkeys_down_eventbox") .unwrap(), hotkey_dialog: RefCell::new(None), }; #[cfg(feature = "notify")] - let notify_tab: gtk::Box = builder.get_object("noti_vbox_enabled") - .unwrap(); + let notify_tab: gtk::Box = + builder.get_object("noti_vbox_enabled").unwrap(); #[cfg(not(feature = "notify"))] - let notify_tab: gtk::Box = builder.get_object("noti_vbox_disabled") - .unwrap(); + let notify_tab: gtk::Box = + builder.get_object("noti_vbox_disabled").unwrap(); - prefs_dialog.notebook.append_page(¬ify_tab, - Some(>k::Label::new(Some("Notifications")))); + prefs_dialog.notebook.append_page( + ¬ify_tab, + Some(>k::Label::new(Some("Notifications"))), + ); return prefs_dialog; } @@ -161,9 +182,13 @@ impl PrefsDialog { self.chan_combo.remove_all(); /* ViewPrefs */ - self.vol_meter_draw_check.set_active(prefs.view_prefs.draw_vol_meter); - self.vol_meter_pos_spin.set_value(prefs.view_prefs.vol_meter_offset as - f64); + self.vol_meter_draw_check.set_active( + prefs.view_prefs.draw_vol_meter, + ); + self.vol_meter_pos_spin.set_value( + prefs.view_prefs.vol_meter_offset as + f64, + ); let rgba = gdk::RGBA { red: prefs.view_prefs.vol_meter_color.red, @@ -175,64 +200,105 @@ impl PrefsDialog { self.system_theme.set_active(prefs.view_prefs.system_theme); /* BehaviorPrefs */ - self.unmute_on_vol_change - .set_active(prefs.behavior_prefs.unmute_on_vol_change); - self.vol_control_entry.set_text(prefs.behavior_prefs - .vol_control_cmd - .as_ref() - .unwrap_or(&String::from("")) - .as_str()); - self.scroll_step_spin.set_value(prefs.behavior_prefs.vol_scroll_step); - self.fine_scroll_step_spin - .set_value(prefs.behavior_prefs.vol_fine_scroll_step); + self.unmute_on_vol_change.set_active( + prefs + .behavior_prefs + .unmute_on_vol_change, + ); + self.vol_control_entry.set_text( + prefs + .behavior_prefs + .vol_control_cmd + .as_ref() + .unwrap_or(&String::from("")) + .as_str(), + ); + self.scroll_step_spin.set_value( + prefs.behavior_prefs.vol_scroll_step, + ); + self.fine_scroll_step_spin.set_value( + prefs + .behavior_prefs + .vol_fine_scroll_step, + ); // TODO: make sure these values always match, must be a better way // also check to_prefs() self.middle_click_combo.append_text("Toggle Mute"); self.middle_click_combo.append_text("Show Preferences"); self.middle_click_combo.append_text("Volume Control"); - self.middle_click_combo.append_text("Custom Command (set below)"); - self.middle_click_combo.set_active(prefs.behavior_prefs - .middle_click_action - .into()); - self.custom_entry.set_text(prefs.behavior_prefs - .custom_command - .as_ref() - .unwrap_or(&String::from("")) - .as_str()); + self.middle_click_combo.append_text( + "Custom Command (set below)", + ); + self.middle_click_combo.set_active( + prefs + .behavior_prefs + .middle_click_action + .into(), + ); + self.custom_entry.set_text( + prefs + .behavior_prefs + .custom_command + .as_ref() + .unwrap_or(&String::from("")) + .as_str(), + ); /* NotifyPrefs */ #[cfg(feature = "notify")] { - self.noti_enable_check - .set_active(prefs.notify_prefs.enable_notifications); - self.noti_timeout_spin - .set_value(prefs.notify_prefs.notifcation_timeout as f64); - self.noti_mouse_check - .set_active(prefs.notify_prefs.notify_mouse_scroll); - self.noti_popup_check.set_active(prefs.notify_prefs.notify_popup); - self.noti_ext_check.set_active(prefs.notify_prefs.notify_external); - self.noti_hotkey_check - .set_active(prefs.notify_prefs.notify_hotkeys); + self.noti_enable_check.set_active( + prefs + .notify_prefs + .enable_notifications, + ); + self.noti_timeout_spin.set_value( + prefs.notify_prefs.notifcation_timeout as + f64, + ); + self.noti_mouse_check.set_active( + prefs.notify_prefs.notify_mouse_scroll, + ); + self.noti_popup_check.set_active( + prefs.notify_prefs.notify_popup, + ); + self.noti_ext_check.set_active( + prefs.notify_prefs.notify_external, + ); + self.noti_hotkey_check.set_active( + prefs.notify_prefs.notify_hotkeys, + ); } /* hotkey prefs */ - self.hotkeys_enable_check.set_active(prefs.hotkey_prefs.enable_hotkeys); - self.hotkeys_mute_label.set_text(prefs.hotkey_prefs - .mute_unmute_key - .clone() - .unwrap_or(String::from("(None)")) - .as_str()); - self.hotkeys_up_label.set_text(prefs.hotkey_prefs - .vol_up_key - .clone() - .unwrap_or(String::from("(None)")) - .as_str()); - self.hotkeys_down_label.set_text(prefs.hotkey_prefs - .vol_down_key - .clone() - .unwrap_or(String::from("(None)")) - .as_str()); + self.hotkeys_enable_check.set_active( + prefs.hotkey_prefs.enable_hotkeys, + ); + self.hotkeys_mute_label.set_text( + prefs + .hotkey_prefs + .mute_unmute_key + .clone() + .unwrap_or(String::from("(None)")) + .as_str(), + ); + self.hotkeys_up_label.set_text( + prefs + .hotkey_prefs + .vol_up_key + .clone() + .unwrap_or(String::from("(None)")) + .as_str(), + ); + self.hotkeys_down_label.set_text( + prefs + .hotkey_prefs + .vol_down_key + .clone() + .unwrap_or(String::from("(None)")) + .as_str(), + ); } @@ -264,19 +330,16 @@ impl PrefsDialog { vol_meter_color, }; - let vol_control_cmd = - self.vol_control_entry.get_text().and_then(|x| if x.is_empty() { - None - } else { - Some(x) - }); + let vol_control_cmd = self.vol_control_entry.get_text().and_then(|x| { + if x.is_empty() { None } else { Some(x) } + }); let custom_command = self.custom_entry.get_text().and_then(|x| if x.is_empty() { - None - } else { - Some(x) - }); + None + } else { + Some(x) + }); let behavior_prefs = BehaviorPrefs { unmute_on_vol_change: self.unmute_on_vol_change.get_active(), @@ -291,42 +354,48 @@ impl PrefsDialog { let notify_prefs = NotifyPrefs { enable_notifications: self.noti_enable_check.get_active(), notifcation_timeout: self.noti_timeout_spin.get_value_as_int() as - i64, + i64, notify_mouse_scroll: self.noti_mouse_check.get_active(), notify_popup: self.noti_popup_check.get_active(), notify_external: self.noti_ext_check.get_active(), notify_hotkeys: self.noti_hotkey_check.get_active(), }; - let hotkey_prefs = HotkeyPrefs { - enable_hotkeys: self.hotkeys_enable_check.get_active(), - mute_unmute_key: - self.hotkeys_mute_label.get_text().and_then(|s| if s == - "(None)" { - None - } else { - Some(s) - }), - vol_up_key: - self.hotkeys_up_label.get_text().and_then(|s| if s == - "(None)" { - None - } else { - Some(s) - }), - vol_down_key: self.hotkeys_down_label.get_text().and_then(|s| { - if s == "(None)" { None } else { Some(s) } - }), - }; + let hotkey_prefs = + HotkeyPrefs { + enable_hotkeys: self.hotkeys_enable_check.get_active(), + mute_unmute_key: self.hotkeys_mute_label.get_text().and_then( + |s| { + if s == "(None)" { None } else { Some(s) } + }, + ), + vol_up_key: self.hotkeys_up_label.get_text().and_then( + |s| if s == + "(None)" + { + None + } else { + Some(s) + }, + ), + vol_down_key: self.hotkeys_down_label.get_text().and_then( + |s| if s == + "(None)" + { + None + } else { + Some(s) + }, + ), + }; return Prefs { - device_prefs, - view_prefs, - behavior_prefs, - #[cfg(feature = "notify")] - notify_prefs, - hotkey_prefs, - }; + device_prefs, + view_prefs, + behavior_prefs, + notify_prefs, + hotkey_prefs, + }; } } @@ -335,12 +404,10 @@ impl PrefsDialog { /// Show the preferences dialog. This is created and destroyed dynamically /// and not persistent across the application lifetime. pub fn show_prefs_dialog(appstate: &Rc>) - where T: AudioFrontend + 'static +where + T: AudioFrontend + 'static, { - if appstate.gui - .prefs_dialog - .borrow() - .is_some() { + if appstate.gui.prefs_dialog.borrow().is_some() { return; } @@ -362,15 +429,13 @@ pub fn show_prefs_dialog(appstate: &Rc>) /// Initialize the internal prefs dialog handler that connects to the audio /// system. pub fn init_prefs_callback(appstate: Rc>) - where T: AudioFrontend + 'static +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() { + if apps.gui.prefs_dialog.borrow().is_none() { return; } @@ -388,7 +453,8 @@ pub fn init_prefs_callback(appstate: Rc>) /// Initialize the preferences dialog gtk callbacks. fn init_prefs_dialog(appstate: &Rc>) - where T: AudioFrontend + 'static +where + T: AudioFrontend + 'static, { /* prefs_dialog.connect_show */ @@ -397,9 +463,9 @@ fn init_prefs_dialog(appstate: &Rc>) let m_pd = appstate.gui.prefs_dialog.borrow(); let pd = m_pd.as_ref().unwrap(); pd.prefs_dialog.connect_show(move |_| { - fill_card_combo(&apps); - fill_chan_combo(&apps, None); - }); + fill_card_combo(&apps); + fill_chan_combo(&apps, None); + }); } /* card_combo.connect_changed */ @@ -425,7 +491,8 @@ fn init_prefs_dialog(appstate: &Rc>) pd.prefs_dialog.connect_response(move |_, response_id| { if response_id == ResponseType::Ok.into() || - response_id == ResponseType::Apply.into() { + response_id == ResponseType::Apply.into() + { let mut prefs = apps.prefs.borrow_mut(); let prefs_dialog = apps.gui.prefs_dialog.borrow(); *prefs = prefs_dialog.as_ref().unwrap().to_prefs(); @@ -434,23 +501,25 @@ fn init_prefs_dialog(appstate: &Rc>) if response_id != ResponseType::Apply.into() { let mut prefs_dialog = apps.gui.prefs_dialog.borrow_mut(); - prefs_dialog.as_ref() - .unwrap() - .prefs_dialog - .destroy(); + prefs_dialog.as_ref().unwrap().prefs_dialog.destroy(); *prefs_dialog = None; } if response_id == ResponseType::Ok.into() || - response_id == ResponseType::Apply.into() { + response_id == ResponseType::Apply.into() + { try_w!(apps.update_popup_window()); try_w!(apps.update_tray_icon()); - let _ = result_warn!(apps.update_hotkeys(), - Some(&apps.gui.popup_menu.menu_window)); + let _ = result_warn!( + apps.update_hotkeys(), + Some(&apps.gui.popup_menu.menu_window) + ); apps.update_notify(); try_w!(apps.update_audio(AudioUser::PrefsWindow)); - let _ = result_warn!(apps.update_config(), - Some(&apps.gui.popup_menu.menu_window)); + let _ = result_warn!( + apps.update_config(), + Some(&apps.gui.popup_menu.menu_window) + ); } }); @@ -462,9 +531,13 @@ fn init_prefs_dialog(appstate: &Rc>) let m_pd = appstate.gui.prefs_dialog.borrow(); let pd = m_pd.as_ref().unwrap(); - pd.hotkeys_mute_eventbox.connect_button_press_event(move |w, e| { - return Inhibit(on_hotkey_event_box_button_press_event(&apps, &w, e)); - }); + pd.hotkeys_mute_eventbox.connect_button_press_event( + move |w, e| { + return Inhibit( + on_hotkey_event_box_button_press_event(&apps, &w, e), + ); + }, + ); } /* prefs_dialog.hotkeys_up_eventbox */ @@ -473,9 +546,13 @@ fn init_prefs_dialog(appstate: &Rc>) let m_pd = appstate.gui.prefs_dialog.borrow(); let pd = m_pd.as_ref().unwrap(); - pd.hotkeys_up_eventbox.connect_button_press_event(move |w, e| { - return Inhibit(on_hotkey_event_box_button_press_event(&apps, &w, e)); - }); + pd.hotkeys_up_eventbox.connect_button_press_event( + move |w, e| { + return Inhibit( + on_hotkey_event_box_button_press_event(&apps, &w, e), + ); + }, + ); } /* prefs_dialog.hotkeys_down_eventbox */ @@ -484,16 +561,21 @@ fn init_prefs_dialog(appstate: &Rc>) let m_pd = appstate.gui.prefs_dialog.borrow(); let pd = m_pd.as_ref().unwrap(); - pd.hotkeys_down_eventbox.connect_button_press_event(move |w, e| { - return Inhibit(on_hotkey_event_box_button_press_event(&apps, &w, e)); - }); + pd.hotkeys_down_eventbox.connect_button_press_event( + move |w, e| { + return Inhibit( + on_hotkey_event_box_button_press_event(&apps, &w, e), + ); + }, + ); } } /// Fill the card combo box in the Devices tab. fn fill_card_combo(appstate: &AppS) - where T: AudioFrontend +where + T: AudioFrontend, { let m_cc = appstate.gui.prefs_dialog.borrow(); let card_combo = &m_cc.as_ref().unwrap().card_combo; @@ -501,8 +583,8 @@ fn fill_card_combo(appstate: &AppS) let audio = &appstate.audio; /* set card combo */ - let cur_card_name = try_w!(audio.card_name(), - "Can't get current card name!"); + let cur_card_name = + try_w!(audio.card_name(), "Can't get current card name!"); let available_card_names = get_playable_card_names(); /* set_active_id doesn't work, so save the index */ @@ -522,7 +604,8 @@ fn fill_card_combo(appstate: &AppS) /// Fill the channel combo box in the Devices tab. fn fill_chan_combo(appstate: &AppS, cardname: Option) - where T: AudioFrontend +where + T: AudioFrontend, { let m_cc = appstate.gui.prefs_dialog.borrow(); let chan_combo = &m_cc.as_ref().unwrap().chan_combo; @@ -553,11 +636,13 @@ fn fill_chan_combo(appstate: &AppS, cardname: Option) } -fn on_hotkey_event_box_button_press_event(appstate: &AppS, - widget: >k::EventBox, - event: &gdk::EventButton) - -> bool - where T: AudioFrontend +fn on_hotkey_event_box_button_press_event( + appstate: &AppS, + widget: >k::EventBox, + event: &gdk::EventButton, +) -> bool +where + T: AudioFrontend, { let borrow = appstate.gui.prefs_dialog.borrow(); let prefs_dialog = &borrow.as_ref().unwrap(); @@ -573,13 +658,20 @@ fn on_hotkey_event_box_button_press_event(appstate: &AppS, let (hotkey_label, hotkey) = { if *widget == prefs_dialog.hotkeys_mute_eventbox { - (prefs_dialog.hotkeys_mute_label.clone(), - String::from("Mute/Unmute")) + ( + prefs_dialog.hotkeys_mute_label.clone(), + String::from("Mute/Unmute"), + ) } else if *widget == prefs_dialog.hotkeys_up_eventbox { - (prefs_dialog.hotkeys_up_label.clone(), String::from("Volume Up")) + ( + prefs_dialog.hotkeys_up_label.clone(), + String::from("Volume Up"), + ) } else if *widget == prefs_dialog.hotkeys_down_eventbox { - (prefs_dialog.hotkeys_down_label.clone(), - String::from("Volume Down")) + ( + prefs_dialog.hotkeys_down_label.clone(), + String::from("Volume Down"), + ) } else { warn!("Unknown hotkey eventbox"); return false; @@ -598,10 +690,7 @@ fn on_hotkey_event_box_button_press_event(appstate: &AppS, let hotkey_dialog = &prefs_dialog.hotkey_dialog; *hotkey_dialog.borrow_mut() = Some(HotkeyDialog::new(&prefs_dialog.prefs_dialog, hotkey)); - let key_pressed = hotkey_dialog.borrow() - .as_ref() - .unwrap() - .run(); + let key_pressed = hotkey_dialog.borrow().as_ref().unwrap().run(); *hotkey_dialog.borrow_mut() = None; /* Bind hotkeys */ @@ -623,8 +712,10 @@ fn on_hotkey_event_box_button_press_event(appstate: &AppS, } Err(e) => { // Could not grab hotkey, most likely - error_dialog!(e.description(), - Some(&appstate.gui.popup_menu.menu_window)); + error_dialog!( + e.description(), + Some(&appstate.gui.popup_menu.menu_window) + ); warn!("{}", e); return false; } diff --git a/src/ui_tray_icon.rs b/src/ui/tray_icon.rs similarity index 66% rename from src/ui_tray_icon.rs rename to src/ui/tray_icon.rs index fa29ea61e..39a77a508 100644 --- a/src/ui_tray_icon.rs +++ b/src/ui/tray_icon.rs @@ -5,7 +5,7 @@ use app_state::*; -use audio_frontend::*; +use audio::frontend::*; use errors::*; use gdk; use gdk_pixbuf; @@ -16,9 +16,9 @@ use prefs::{Prefs, MiddleClickAction}; use std::cell::Cell; use std::cell::RefCell; use std::rc::Rc; -use support_cmd::*; -use support_ui::*; -use ui_prefs_dialog::show_prefs_dialog; +use support::cmd::*; +use support::ui::*; +use ui::prefs_dialog::show_prefs_dialog; @@ -63,21 +63,22 @@ impl TrayIcon { let status_icon = gtk::StatusIcon::new(); return Ok(TrayIcon { - _cant_construct: (), - volmeter, - audio_pix: RefCell::new(audio_pix), - status_icon, - icon_size: Cell::new(ICON_MIN_SIZE), - }); + _cant_construct: (), + volmeter, + audio_pix: RefCell::new(audio_pix), + status_icon, + icon_size: Cell::new(ICON_MIN_SIZE), + }); } /// Update the volume meter Pixbuf, which is drawn on top of the /// actual Pixbuf. - fn update_vol_meter(&self, - cur_vol: f64, - vol_level: VolLevel) - -> Result<()> { + fn update_vol_meter( + &self, + cur_vol: f64, + vol_level: VolLevel, + ) -> Result<()> { let audio_pix = self.audio_pix.borrow(); let pixbuf = audio_pix.select_pix(vol_level); @@ -97,13 +98,15 @@ impl TrayIcon { /// Update the tooltip of the tray icon. fn update_tooltip(&self, audio: &T) - where T: AudioFrontend + where + T: AudioFrontend, { let cardname = audio.card_name().unwrap_or(String::from("Unknown card")); let channame = audio.chan_name().unwrap_or(String::from("unknown channel")); - let vol = audio.get_vol() + let vol = audio + .get_vol() .map(|s| format!("{}", s.round())) .unwrap_or(String::from("unknown volume")); let mute_info = { @@ -115,22 +118,27 @@ impl TrayIcon { "" } }; - self.status_icon.set_tooltip_text(format!("{} ({})\nVolume: {}{}", - cardname, - channame, - vol, - mute_info) - .as_str()); + self.status_icon.set_tooltip_text( + format!( + "{} ({})\nVolume: {}{}", + cardname, + channame, + vol, + mute_info + ).as_str(), + ); } /// Update the whole tray icon state. - pub fn update_all(&self, - prefs: &Prefs, - audio: &T, - m_size: Option) - -> Result<()> - where T: AudioFrontend + pub fn update_all( + &self, + prefs: &Prefs, + audio: &T, + m_size: Option, + ) -> Result<()> + where + T: AudioFrontend, { match m_size { Some(s) => { @@ -178,32 +186,38 @@ impl VolMeter { /// Constructor. `width` and `row` are initialized with default values. fn new(prefs: &Prefs) -> VolMeter { return VolMeter { - red: (prefs.view_prefs.vol_meter_color.red * 255.0) as u8, - green: (prefs.view_prefs.vol_meter_color.green * 255.0) as - u8, - blue: (prefs.view_prefs.vol_meter_color.blue * 255.0) as u8, - x_offset_pct: prefs.view_prefs.vol_meter_offset as i64, - y_offset_pct: 10, - /* dynamic */ - width: Cell::new(0), - row: RefCell::new(vec![]), - }; + red: (prefs.view_prefs.vol_meter_color.red * 255.0) as u8, + green: (prefs.view_prefs.vol_meter_color.green * 255.0) as u8, + blue: (prefs.view_prefs.vol_meter_color.blue * 255.0) as u8, + x_offset_pct: prefs.view_prefs.vol_meter_offset as i64, + y_offset_pct: 10, + /* dynamic */ + width: Cell::new(0), + row: RefCell::new(vec![]), + }; } // TODO: cache input pixbuf? /// Draw the volume meter on top of the actual tray icon Pixbuf. - fn meter_draw(&self, - volume: i64, - pixbuf: &gdk_pixbuf::Pixbuf) - -> Result { + fn meter_draw( + &self, + volume: i64, + pixbuf: &gdk_pixbuf::Pixbuf, + ) -> Result { - ensure!(pixbuf.get_colorspace() == gdk_pixbuf_sys::GDK_COLORSPACE_RGB, - "Invalid colorspace in pixbuf"); - ensure!(pixbuf.get_bits_per_sample() == 8, - "Invalid bits per sample in pixbuf"); + ensure!( + pixbuf.get_colorspace() == gdk_pixbuf_sys::GDK_COLORSPACE_RGB, + "Invalid colorspace in pixbuf" + ); + ensure!( + pixbuf.get_bits_per_sample() == 8, + "Invalid bits per sample in pixbuf" + ); ensure!(pixbuf.get_has_alpha(), "No alpha channel in pixbuf"); - ensure!(pixbuf.get_n_channels() == 4, - "Invalid number of channels in pixbuf"); + ensure!( + pixbuf.get_n_channels() == 4, + "Invalid number of channels in pixbuf" + ); let i_width = pixbuf.get_width() as i64; let i_height = pixbuf.get_height() as i64; @@ -212,16 +226,21 @@ impl VolMeter { let vm_width = i_width / 6; let x = (self.x_offset_pct as f64 * - ((i_width - vm_width) as f64 / 100.0)) as i64; - ensure!(x >= 0 && (x + vm_width) <= i_width, - "x coordinate invalid: {}", - x); + ((i_width - vm_width) as f64 / 100.0)) as + i64; + ensure!( + x >= 0 && (x + vm_width) <= i_width, + "x coordinate invalid: {}", + x + ); let y = (self.y_offset_pct as f64 * (i_height as f64 / 100.0)) as i64; let vm_height = ((i_height - (y * 2)) as f64 * (volume as f64 / 100.0)) as i64; - ensure!(y >= 0 && (y + vm_height) <= i_height, - "y coordinate invalid: {}", - y); + ensure!( + y >= 0 && (y + vm_height) <= i_height, + "y coordinate invalid: {}", + y + ); /* Let's check if the icon width changed, in which case we * must reinit our internal row of pixels. @@ -257,8 +276,9 @@ impl VolMeter { let p_index = ((row_offset * rowstride) + col_offset) as usize; let row = self.row.borrow(); - pixels[p_index..p_index + row.len()] - .copy_from_slice(row.as_ref()); + pixels[p_index..p_index + row.len()].copy_from_slice( + row.as_ref(), + ); } } @@ -283,22 +303,22 @@ pub struct AudioPix { impl Default for AudioPix { fn default() -> AudioPix { - let dummy_pixbuf = - unsafe { - gdk_pixbuf::Pixbuf::new(gdk_pixbuf_sys::GDK_COLORSPACE_RGB, - false, - 8, - 1, - 1) - .unwrap() - }; + let dummy_pixbuf = unsafe { + gdk_pixbuf::Pixbuf::new( + gdk_pixbuf_sys::GDK_COLORSPACE_RGB, + false, + 8, + 1, + 1, + ).unwrap() + }; return AudioPix { - muted: dummy_pixbuf.clone(), - low: dummy_pixbuf.clone(), - medium: dummy_pixbuf.clone(), - high: dummy_pixbuf.clone(), - off: dummy_pixbuf.clone(), - }; + muted: dummy_pixbuf.clone(), + low: dummy_pixbuf.clone(), + medium: dummy_pixbuf.clone(), + high: dummy_pixbuf.clone(), + off: dummy_pixbuf.clone(), + }; } } @@ -352,11 +372,21 @@ impl AudioPix { } } else { AudioPix { - muted: pixbuf_new_from_png!("../data/pixmaps/pnmixer-muted.png")?, - low: pixbuf_new_from_png!("../data/pixmaps/pnmixer-low.png")?, - medium: pixbuf_new_from_png!("../data/pixmaps/pnmixer-medium.png")?, - high: pixbuf_new_from_png!("../data/pixmaps/pnmixer-high.png")?, - off: pixbuf_new_from_png!("../data/pixmaps/pnmixer-off.png")?, + muted: pixbuf_new_from_png!( + "../../data/pixmaps/pnmixer-muted.png" + )?, + low: pixbuf_new_from_png!( + "../../data/pixmaps/pnmixer-low.png" + )?, + medium: pixbuf_new_from_png!( + "../../data/pixmaps/pnmixer-medium.png" + )?, + high: pixbuf_new_from_png!( + "../../data/pixmaps/pnmixer-high.png" + )?, + off: pixbuf_new_from_png!( + "../../data/pixmaps/pnmixer-off.png" + )?, } } }; @@ -379,7 +409,8 @@ impl AudioPix { /// Initialize the tray icon subsystem. pub fn init_tray_icon(appstate: Rc>) - where T: AudioFrontend + 'static +where + T: AudioFrontend + 'static, { let tray_icon = &appstate.gui.tray_icon; @@ -388,24 +419,31 @@ pub fn init_tray_icon(appstate: Rc>) /* connect audio handler */ { let apps = appstate.clone(); - 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())); - } - })); + 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(), + )); + } + }), + ); } /* tray_icon.connect_size_changed */ { let apps = appstate.clone(); tray_icon.status_icon.connect_size_changed(move |_, size| { - try_wr!(apps.gui.tray_icon.update_all(&apps.prefs.borrow_mut(), - apps.audio.as_ref(), - Some(size)), - false); + try_wr!( + apps.gui.tray_icon.update_all( + &apps.prefs.borrow_mut(), + apps.audio.as_ref(), + Some(size), + ), + false + ); return false; }); } @@ -413,9 +451,9 @@ pub fn init_tray_icon(appstate: Rc>) /* tray_icon.connect_activate */ { let apps = appstate.clone(); - tray_icon.status_icon.connect_activate(move |_| { - on_tray_icon_activate(&apps) - }); + tray_icon.status_icon.connect_activate( + move |_| on_tray_icon_activate(&apps), + ); } /* tray_icon.connect_scroll_event */ @@ -446,12 +484,15 @@ pub fn init_tray_icon(appstate: Rc>) { let apps = appstate.clone(); let default_theme = try_w!(gtk::IconTheme::get_default().ok_or( - "Couldn't get default icon theme", - )); + "Couldn't get default icon theme", + )); default_theme.connect_changed(move |_| { let tray_icon = &apps.gui.tray_icon; - try_e!(tray_icon.update_all(&apps.prefs.borrow_mut(), - apps.audio.as_ref(), None)); + try_e!(tray_icon.update_all( + &apps.prefs.borrow_mut(), + apps.audio.as_ref(), + None, + )); }); } } @@ -459,7 +500,8 @@ pub fn init_tray_icon(appstate: Rc>) /// When the tray icon is activated. fn on_tray_icon_activate(appstate: &AppS) - where T: AudioFrontend +where + T: AudioFrontend, { let popup_window = &appstate.gui.popup_window.popup_window; @@ -473,7 +515,8 @@ fn on_tray_icon_activate(appstate: &AppS) /// When the popup menu is shown, hide the popup window, if any. fn on_tray_icon_popup_menu(appstate: &AppS) - where T: AudioFrontend +where + T: AudioFrontend, { let popup_window = &appstate.gui.popup_window.popup_window; let popup_menu = &appstate.gui.popup_menu.menu; @@ -485,29 +528,41 @@ fn on_tray_icon_popup_menu(appstate: &AppS) /// When the mouse scroll event happens while the mouse pointer is /// on the tray icon. -fn on_tray_icon_scroll_event(appstate: &AppS, - event: &gdk::EventScroll) - -> bool - where T: AudioFrontend +fn on_tray_icon_scroll_event( + appstate: &AppS, + event: &gdk::EventScroll, +) -> bool +where + T: AudioFrontend, { let scroll_dir: gdk::ScrollDirection = event.get_direction(); match scroll_dir { gdk::ScrollDirection::Up => { - try_wr!(appstate.audio.increase_vol(AudioUser::TrayIcon, - appstate.prefs - .borrow() - .behavior_prefs - .unmute_on_vol_change), - false); + try_wr!( + appstate.audio.increase_vol( + AudioUser::TrayIcon, + appstate + .prefs + .borrow() + .behavior_prefs + .unmute_on_vol_change, + ), + false + ); } gdk::ScrollDirection::Down => { - try_wr!(appstate.audio.decrease_vol(AudioUser::TrayIcon, - appstate.prefs - .borrow() - .behavior_prefs - .unmute_on_vol_change), - false); + try_wr!( + appstate.audio.decrease_vol( + AudioUser::TrayIcon, + appstate + .prefs + .borrow() + .behavior_prefs + .unmute_on_vol_change, + ), + false + ); } _ => (), } @@ -519,10 +574,12 @@ fn on_tray_icon_scroll_event(appstate: &AppS, /// 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 /// takes appropriate actions. -fn on_tray_button_release_event(appstate: &Rc>, - event_button: &gdk::EventButton) - -> bool - where T: AudioFrontend + 'static +fn on_tray_button_release_event( + appstate: &Rc>, + event_button: &gdk::EventButton, +) -> bool +where + T: AudioFrontend + 'static, { let button = event_button.get_button(); @@ -545,14 +602,18 @@ fn on_tray_button_release_event(appstate: &Rc>, // TODO &MiddleClickAction::ShowPreferences => show_prefs_dialog(&appstate), &MiddleClickAction::VolumeControl => { - let _ = result_warn!(execute_vol_control_command(&appstate.prefs.borrow()), - Some(&appstate.gui.popup_menu.menu_window)); + let _ = result_warn!( + execute_vol_control_command(&appstate.prefs.borrow()), + Some(&appstate.gui.popup_menu.menu_window) + ); } &MiddleClickAction::CustomCommand => { match custom_command { &Some(ref cmd) => { - let _ = result_warn!(execute_command(cmd.as_str()), - Some(&appstate.gui.popup_menu.menu_window)); + let _ = result_warn!( + execute_command(cmd.as_str()), + Some(&appstate.gui.popup_menu.menu_window) + ); } &None => warn!("No custom command found"), } diff --git a/src/ui_entry.rs b/src/ui_entry.rs deleted file mode 100644 index 94793a4eb..000000000 --- a/src/ui_entry.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Global GUI state. - - -use app_state::*; -use audio_frontend::*; -use gtk::DialogExt; -use gtk::MessageDialogExt; -use gtk::WidgetExt; -use gtk::WindowExt; -use gtk; -use gtk_sys::GTK_RESPONSE_YES; -use prefs::*; -use std::cell::RefCell; -use std::rc::Rc; -use support_audio::*; -use ui_popup_menu::*; -use ui_popup_window::*; -use ui_prefs_dialog::*; -use ui_tray_icon::*; - -#[cfg(feature = "notify")] -use notif::*; - - - -/// The GUI struct mostly describing the main widgets (mostly wrapped) -/// the user interacts with. -pub struct Gui { - _cant_construct: (), - /// The tray icon. - pub tray_icon: TrayIcon, - /// The popup window. - pub popup_window: PopupWindow, - /// The popup menu. - pub popup_menu: PopupMenu, - /* prefs_dialog is dynamically created and destroyed */ - /// The preferences dialog. - pub prefs_dialog: RefCell>, -} - -impl Gui { - /// Constructor. The prefs dialog is initialized as `None`. - pub fn new(builder_popup_window: gtk::Builder, - builder_popup_menu: gtk::Builder, - prefs: &Prefs) - -> Gui { - return Gui { - _cant_construct: (), - tray_icon: TrayIcon::new(prefs).unwrap(), - popup_window: PopupWindow::new(builder_popup_window), - popup_menu: PopupMenu::new(builder_popup_menu), - prefs_dialog: RefCell::new(None), - }; - } -} - - -/// Initialize the GUI system. -pub fn init(appstate: Rc>) - where T: AudioFrontend + 'static -{ - { - /* "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)); - } - }, - _ => (), - } - )); - - } - - init_tray_icon(appstate.clone()); - init_popup_window(appstate.clone()); - init_popup_menu(appstate.clone()); - init_prefs_callback(appstate.clone()); - - #[cfg(feature = "notify")] - init_notify(appstate.clone()); -} - - -/// Used to run a dialog when an audio error occured, suggesting the user -/// may reload the audio system either manually or by confirming the dialog -/// via the confirmation button. -/// -/// # Returns -/// -/// `GTK_RESPONSE_YES` if the user wants to reload the audio system, -/// `GTK_RESPONSE_NO` otherwise. -fn run_audio_error_dialog(parent: >k::Window) -> i32 { - error!("Connection with audio failed, you probably need to restart pnmixer."); - - let dialog = gtk::MessageDialog::new(Some(parent), - gtk::DIALOG_DESTROY_WITH_PARENT, - gtk::MessageType::Error, - gtk::ButtonsType::YesNo, - "Warning: Connection to sound system failed."); - dialog.set_property_secondary_text(Some("Do you want to re-initialize the audio connection ? - -If you do not, you will either need to restart PNMixer -or select the 'Reload Audio' option in the right-click -menu in order for PNMixer to function.")); - - dialog.set_title("PNMixer-rs Error"); - - let resp = dialog.run(); - dialog.destroy(); - - return resp; -}