Created
June 26, 2020 00:27
-
-
Save drslump/b96bb0852677bc0533e02ed82942e0b0 to your computer and use it in GitHub Desktop.
linux global modifier keys (rust)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "global-modifiers" | |
version = "0.1.0" | |
authors = ["drslump <[email protected]>"] | |
edition = "2018" | |
[dependencies] | |
enumset = "1.0.0" | |
x11 = { version = "2.18.2", features = ["xlib"] } | |
[[bin]] | |
name = "global-modifiers" | |
path = "main.rs" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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