//! The hotkeys subsystem. //! //! This handles the PNMixer-rs specific hotkeys as a whole, //! including communication with Xlib and intercepting key presses //! before they can be interpreted by Gtk/Gdk. use audio::frontend::*; use errors::*; use errors; use gdk; use gdk_sys; 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; /// The possible Hotkeys for manipulating the volume. pub struct Hotkeys where T: AudioFrontend, { enabled: bool, mute_key: Option, up_key: Option, down_key: Option, // need this to access audio in 'key_filter' audio: Rc, auto_unmute: bool, } impl Hotkeys where T: AudioFrontend, { /// Creates the hotkeys subsystem and binds the hotkeys. 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 warn = vec![]; push_warning!(hotkeys.reload(prefs), warn); /* bind hotkeys */ 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); } /// Reload the Hotkeys from the preferences. /// If hotkeys are disabled, just sets all members to `None`. /// This has to be called each time the preferences are modified. /// /// # Returns /// /// `Ok(())` on success, otherwise `Err(str)` if some of the hotkeys /// could not be grabbed, where `str` is a String that can be /// presented via e.g. `run_error_dialog()`. pub fn reload(&mut self, prefs: &Prefs) -> Result<()> { self.enabled = prefs.hotkey_prefs.enable_hotkeys; self.mute_key = None; self.up_key = None; self.down_key = None; /* Return if hotkeys are disabled */ if self.enabled == false { return Ok(()); } let hotkey_prefs = &prefs.hotkey_prefs; let new_hotkey = |keyname: &Option| -> (Option, bool) { match keyname { &Some(ref k) => { let hotkey = Hotkey::new_from_accel(k.as_str()); if hotkey.as_ref().is_err() { (None, true) } else { (Some(hotkey.unwrap()), false) } } &None => (None, false), // no actual error, just no key } }; /* Setup mute hotkey */ let (m_unmute_key, mute_err) = new_hotkey(&hotkey_prefs.mute_unmute_key); if let Some(key) = m_unmute_key { self.mute_key = Some(key); } /* Setup volume uphotkey */ let (m_up_key, up_err) = new_hotkey(&hotkey_prefs.vol_up_key); if let Some(key) = m_up_key { self.up_key = Some(key); } /* Setup volume down hotkey */ let (m_down_key, down_err) = new_hotkey(&hotkey_prefs.vol_down_key); if let Some(key) = m_down_key { self.down_key = Some(key); } if mute_err || up_err || down_err { bail!("Could not grab the following hotkeys:\n{}{}{}", if mute_err { "(Mute/Unmute)\n" } else { "" }, if up_err { "(Volume Up)\n" } else { "" }, if down_err { "(Volume Down)\n" } else { "" }, ); } return Ok(()); } /// Bind hotkeys manually. Should be paired with an `unbind()` call. pub fn bind(&self) { debug!("Bind hotkeys"); if let Some(ref key) = self.mute_key { if key.grab().is_err() { warn!("Could not grab mute key"); } } if let Some(ref key) = self.up_key { if key.grab().is_err() { warn!("Could not grab mute key"); } } if let Some(ref key) = self.down_key { if key.grab().is_err() { warn!("Could not grab mute key"); } } let data_ptr = unsafe { mem::transmute::<&Hotkeys, glib_sys::gpointer>(self) }; hotkeys_add_filter(Some(key_filter::), data_ptr); } /// Unbind hotkeys manually. Should be paired with a `bind()` call. pub fn unbind(&self) { debug!("Unbind hotkeys"); if let Some(ref key) = self.mute_key { key.ungrab(); } if let Some(ref key) = self.up_key { key.ungrab(); } if let Some(ref key) = self.down_key { key.ungrab(); } let data_ptr = unsafe { mem::transmute::<&Hotkeys, glib_sys::gpointer>(self) }; hotkeys_remove_filter(Some(key_filter::), data_ptr); } } impl Drop for Hotkeys where T: AudioFrontend, { fn drop(&mut self) { debug!("Freeing hotkeys"); self.mute_key = None; self.up_key = None; self.down_key = None; let data_ptr = unsafe { mem::transmute::<&mut Hotkeys, glib_sys::gpointer>(self) }; hotkeys_remove_filter(Some(key_filter::), data_ptr) } } /// 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, ) { // 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(); unsafe { gdk_sys::gdk_window_add_filter(window.to_glib_none().0, function, data); } } /// Removes the previously attached `key_filter()` function from /// the root window. 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(); unsafe { gdk_sys::gdk_window_remove_filter( window.to_glib_none().0, function, data, ); } } /// 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, { let xevent = gdk_xevent as *mut x11::xlib::XKeyEvent; let hotkeys: &Hotkeys = unsafe { mem::transmute::>(data) }; let mute_key = &hotkeys.mute_key; let up_key = &hotkeys.up_key; let down_key = &hotkeys.down_key; let audio = &hotkeys.audio; let xevent_type = unsafe { (*xevent).type_ }; if xevent_type == x11::xlib::KeyPress { return gdk_sys::GDK_FILTER_CONTINUE; } let xevent_key = unsafe { (*xevent).keycode }; let xevent_state = unsafe { (*xevent).state }; if mute_key.as_ref().is_some() && 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(), ) { 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(), ) { just_warn!(audio.decrease_vol(AudioUser::Hotkeys, hotkeys.auto_unmute)); } return gdk_sys::GDK_FILTER_CONTINUE; }