Skip to content

Instantly share code, notes, and snippets.

@drslump
Created June 26, 2020 00:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drslump/b96bb0852677bc0533e02ed82942e0b0 to your computer and use it in GitHub Desktop.
Save drslump/b96bb0852677bc0533e02ed82942e0b0 to your computer and use it in GitHub Desktop.
linux global modifier keys (rust)
[package]
name = "global-modifiers"
version = "0.1.0"
authors = ["drslump <drslump@pollinimini.net>"]
edition = "2018"
[dependencies]
enumset = "1.0.0"
x11 = { version = "2.18.2", features = ["xlib"] }
[[bin]]
name = "global-modifiers"
path = "main.rs"
// Requires an X11 window server.
//
// NOTE! It will not work for all apps, for instance the LxTerminal seems
// to capture the keyboard before we can process the events.
//
use std::mem::MaybeUninit;
use x11::xlib;
use enumset::{EnumSet, EnumSetType};
macro_rules! json_bool {
($e:expr) => (if $e { "true" } else { "false" });
}
struct OwnedDisplay {
inner: *mut xlib::Display,
}
impl OwnedDisplay {
fn new() -> Option<Self> {
unsafe {
let display = xlib::XOpenDisplay(std::ptr::null());
if display.is_null() {
return None;
}
xlib::XSynchronize(display, 1);
Some(Self { inner: display })
}
}
}
impl From<*mut xlib::Display> for OwnedDisplay {
fn from(item: *mut xlib::Display) -> Self {
unsafe { xlib::XSynchronize(item, 1); }
Self { inner: item }
}
}
impl Into<*mut xlib::Display> for &OwnedDisplay {
fn into(self) -> *mut xlib::Display {
self.inner
}
}
impl Drop for OwnedDisplay {
fn drop(&mut self) {
eprintln!("Closing display");
unsafe { xlib::XCloseDisplay(self.inner) };
}
}
#[derive(Debug, EnumSetType)]
enum Modifiers {
None,
Meta,
Control,
Alt,
Shift,
}
fn dump_json(mods: EnumSet<Modifiers>) {
println!(
"{{\"meta\":{},\"control\":{},\"alt\":{},\"shift\":{}}}",
json_bool!(mods.contains(Modifiers::Meta)),
json_bool!(mods.contains(Modifiers::Control)),
json_bool!(mods.contains(Modifiers::Alt)),
json_bool!(mods.contains(Modifiers::Shift)),
);
}
/// Every time the focused window changes we need to register our interest
/// on its events so we keep being notified.
fn track_input_events_for_focused_window(od: &OwnedDisplay) {
let mut focused: xlib::Window = 0;
let mut revert: i32 = 0;
unsafe {
xlib::XGetInputFocus(od.into(), &mut focused, &mut revert);
if focused == xlib::PointerRoot as u64 {
focused = xlib::XDefaultRootWindow(od.into());
}
xlib::XSelectInput(od.into(), focused, xlib::KeyPressMask | xlib::KeyReleaseMask | xlib::FocusChangeMask);
}
}
fn next_event(od: &OwnedDisplay) -> Option<xlib::XEvent> {
let mut ev = MaybeUninit::<xlib::XEvent>::uninit();
unsafe {
if xlib::XNextEvent(od.into(), ev.as_mut_ptr()) < 0 {
return None;
}
return Some(ev.assume_init());
}
}
fn modifier_for_keycode(keycode: u32) -> Option<Modifiers> {
match keycode {
// AltL | AltR | AltGr?
64 | 108 | 92 => Some(Modifiers::Alt),
// ControlL | ControlR
37 | 105 => Some(Modifiers::Control),
// ShiftL | ShiftR
50 | 62 => Some(Modifiers::Shift),
// MetaL | MetaR
133 | 134 => Some(Modifiers::Meta),
_ => {
// println!("Key: {}", key.keycode);
None
}
}
}
fn runloop(display: &OwnedDisplay) {
let mut pressed = EnumSet::new();
track_input_events_for_focused_window(display);
loop {
if let Some(ev) = next_event(display) {
match ev.get_type() {
xlib::FocusOut => track_input_events_for_focused_window(display),
xlib::KeyPress => {
let key = unsafe { ev.key };
if let Some(modifier) = modifier_for_keycode(key.keycode) {
if !pressed.contains(modifier) {
pressed.insert(modifier);
dump_json(pressed);
}
}
}
xlib::KeyRelease => {
let key = unsafe { ev.key };
if let Some(modifier) = modifier_for_keycode(key.keycode) {
if pressed.contains(modifier) {
pressed.remove(modifier);
dump_json(pressed);
}
}
}
_ => {}
}
}
}
}
fn main() -> Result<(), std::io::Error> {
match OwnedDisplay::new() {
Some(display) => {
runloop(&display);
return Ok(());
},
None => {
eprintln!("Unable to connect to display server");
std::process::exit(1)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment