293 lines
9.0 KiB
Rust
293 lines
9.0 KiB
Rust
|
//! 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::{Audio, AudioUser};
|
||
|
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 w_result::*;
|
||
|
use x11;
|
||
|
|
||
|
|
||
|
|
||
|
/// The possible Hotkeys for manipulating the volume.
|
||
|
pub struct Hotkeys {
|
||
|
enabled: bool,
|
||
|
mute_key: Option<Hotkey>,
|
||
|
up_key: Option<Hotkey>,
|
||
|
down_key: Option<Hotkey>,
|
||
|
|
||
|
// need this to access audio in 'key_filter'
|
||
|
audio: Rc<Audio>,
|
||
|
auto_unmute: bool,
|
||
|
}
|
||
|
|
||
|
impl Hotkeys {
|
||
|
/// Creates the hotkeys subsystem and binds the hotkeys.
|
||
|
pub fn new(prefs: &Prefs,
|
||
|
audio: Rc<Audio>)
|
||
|
-> WResult<Box<Hotkeys>, 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<String>| -> (Option<Hotkey>, 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 m_unmute_key.is_some() {
|
||
|
self.mute_key = Some(m_unmute_key.unwrap());
|
||
|
}
|
||
|
|
||
|
/* Setup volume uphotkey */
|
||
|
let (m_up_key, up_err) = new_hotkey(&hotkey_prefs.vol_up_key);
|
||
|
if m_up_key.is_some() {
|
||
|
self.up_key = Some(m_up_key.unwrap());
|
||
|
}
|
||
|
|
||
|
/* Setup volume down hotkey */
|
||
|
let (m_down_key, down_err) = new_hotkey(&hotkey_prefs.vol_down_key);
|
||
|
if m_down_key.is_some() {
|
||
|
self.down_key = Some(m_down_key.unwrap());
|
||
|
}
|
||
|
|
||
|
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 self.mute_key.is_some() {
|
||
|
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() {
|
||
|
warn!("Could not grab volume up key");
|
||
|
};
|
||
|
}
|
||
|
if self.down_key.is_some() {
|
||
|
if self.down_key
|
||
|
.as_ref()
|
||
|
.unwrap()
|
||
|
.grab()
|
||
|
.is_err() {
|
||
|
warn!("Could not grab volume down 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 self.mute_key.is_some() {
|
||
|
self.mute_key
|
||
|
.as_ref()
|
||
|
.unwrap()
|
||
|
.ungrab();
|
||
|
}
|
||
|
if self.up_key.is_some() {
|
||
|
self.up_key
|
||
|
.as_ref()
|
||
|
.unwrap()
|
||
|
.ungrab();
|
||
|
}
|
||
|
if self.down_key.is_some() {
|
||
|
self.down_key
|
||
|
.as_ref()
|
||
|
.unwrap()
|
||
|
.ungrab();
|
||
|
}
|
||
|
|
||
|
let data_ptr =
|
||
|
unsafe { mem::transmute::<&Hotkeys, glib_sys::gpointer>(self) };
|
||
|
hotkeys_remove_filter(Some(key_filter), data_ptr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Drop for Hotkeys {
|
||
|
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 {
|
||
|
let xevent = gdk_xevent as *mut x11::xlib::XKeyEvent;
|
||
|
|
||
|
let hotkeys: &Hotkeys =
|
||
|
unsafe { mem::transmute::<glib_sys::gpointer, &Hotkeys>(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::GdkFilterReturn::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::GdkFilterReturn::Continue;
|
||
|
}
|