pnmixer-rust/src/hotkey.rs

190 lines
5.0 KiB
Rust

//! The hotkey subsystem.
//!
//! This file defines what's a hotkey and deals with the low-level XKBlib and
//! Gtk/Gdk.
use errors::*;
use gdk;
use gdk_sys;
use gtk;
use libc::c_int;
use libc::c_uint;
use support::gdk_x11::*;
use x11;
/// `xmodmap -pm`
/// List of key modifiers which will be ignored whenever
/// we check whether the defined hotkeys have been pressed.
lazy_static! {
static ref KEY_MASKS: Vec<c_uint> = vec![
gdk_sys::GdkModifierType::empty().bits(), // No Modkey
gdk_sys::GDK_MOD2_MASK.bits(), // Numlock
gdk_sys::GDK_LOCK_MASK.bits(), // Capslock
gdk_sys::GDK_MOD2_MASK.bits() | gdk_sys::GDK_LOCK_MASK.bits(),
];
}
#[derive(Debug)]
/// A hotkey, described by the underlying gdk/X11 representation.
pub struct Hotkey {
/// The hardware keycode.
pub code: gdk::key,
/// The modifier keys and mouse button that have been pressed
/// in addition to the main key (e.g. Numlock/Capslock).
/// This is the raw bit representation and can be converted to
/// `gtk::ModifierType` via `self.mods()`.
pub mod_bits: u32, // Makes serialize/deserialize easier
/// X key symbol.
pub sym: u64,
/// Gtk Accelerator string.
pub gtk_accel: String,
}
impl Hotkey {
/// Creates a new hotkey and grabs it.
pub fn new(code: gdk::key, mods: gdk::ModifierType) -> Result<Hotkey> {
let display = gdk_x11_get_default_xdisplay();
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 hotkey = Hotkey {
code,
mod_bits,
sym,
gtk_accel,
};
hotkey.grab()?;
return Ok(hotkey);
}
/// Creates a new hotkey from an accelerator string and grabs it.
pub fn new_from_accel(accel: &str) -> Result<Hotkey> {
let (code, mods) = hotkey_accel_to_code(accel);
return Hotkey::new(code, mods);
}
/// Grab a key manually. Should be paired with a ungrab() call.
pub fn grab(&self) -> Result<()> {
let display = gdk_x11_get_default_xdisplay();
/* Init error handling */
let old_hdlr = unsafe {
GRAB_ERROR = 0;
x11::xlib::XSetErrorHandler(Some(grab_error_handler))
};
/* 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,
);
}
}
/* Synchronize X */
unsafe {
x11::xlib::XFlush(display);
x11::xlib::XSync(display, false as c_int);
}
/* Restore error handler */
unsafe {
x11::xlib::XSetErrorHandler(old_hdlr);
}
/* Check for error */
unsafe {
ensure!(GRAB_ERROR == 0, "Error grabbing");
}
return Ok(());
}
/// Ungrab a key manually. Should be paired with a grab() call.
pub fn ungrab(&self) {
let display = gdk_x11_get_default_xdisplay();
for key in KEY_MASKS.iter() {
unsafe {
x11::xlib::XUngrabKey(
display,
self.code,
self.mod_bits | key,
gdk_x11_get_default_root_xwindow(),
);
}
}
}
/// Checks if the keycode we got (minus modifiers like
/// numlock/capslock) matches the hotkey.
/// Thus numlock + o will match o.
pub fn matches(&self, code: gdk::key, mods: gdk::ModifierType) -> bool {
if code != self.code {
return false;
}
for key in KEY_MASKS.iter() {
if (self.mod_bits | key) == mods.bits() {
return true;
}
}
return false;
}
}
impl Drop for Hotkey {
fn drop(&mut self) {
debug!("Ungrabbing hotkey");
self.ungrab();
}
}
/// Translate a Gtk Accelerator string to a key code and mods.
pub fn hotkey_accel_to_code(accel: &str) -> (gdk::key, gdk::ModifierType) {
let display = gdk_x11_get_default_xdisplay();
let (sym, mods) = gtk::accelerator_parse(accel);
unsafe {
if sym != 0 {
return (
x11::xlib::XKeysymToKeycode(display, sym as u64) as i32,
mods,
);
} else {
return (-1, mods);
}
}
}
static mut GRAB_ERROR: u8 = 0;
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;
}
return 0;
}