Support wm_class for X11

pull/39/head
Takashi Kokubun 3 years ago
parent 63ec6948e6
commit 39dbc8a1fe
No known key found for this signature in database
GPG Key ID: 6FFC433B12EE23DD

17
Cargo.lock generated

@ -120,6 +120,12 @@ dependencies = [
"memoffset", "memoffset",
] ]
[[package]]
name = "pkg-config"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.34" version = "1.0.34"
@ -220,6 +226,16 @@ dependencies = [
"tap", "tap",
] ]
[[package]]
name = "x11"
version = "2.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd0565fa8bfba8c5efe02725b14dff114c866724eff2cfd44d76cea74bcd87a"
dependencies = [
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "xremap" name = "xremap"
version = "0.1.0" version = "0.1.0"
@ -230,6 +246,7 @@ dependencies = [
"nix", "nix",
"serde", "serde",
"serde_yaml", "serde_yaml",
"x11",
] ]
[[package]] [[package]]

@ -12,3 +12,4 @@ lazy_static = "1.4.0"
nix = "0.23.1" nix = "0.23.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8" serde_yaml = "0.8"
x11 = { version = "2.19.1", features = ["xlib"] }

@ -106,12 +106,12 @@ keymap:
# workaround prefix key bug # workaround prefix key bug
M-r: M-r:
remap: remap:
3: C-M-3 '3': C-M-3
0: C-M-0 '0': C-M-0
- name: Chrome, Slack (modified from Default) - name: Chrome, Slack (modified from Default)
wm_class: wm_class:
not: [Google-chrome, Slack] only: [Google-chrome, Slack]
remap: remap:
# Emacs basic # Emacs basic
C-b: left C-b: left
@ -185,10 +185,10 @@ keymap:
wm_class: wm_class:
only: Nocturn only: Nocturn
remap: remap:
Super-j: M-j, Super-j: M-j
Super-k: M-k, Super-k: M-k
Super-o: M-o, Super-o: M-o
Super-p: M-p, Super-p: M-p
Super-Enter: Shift-Enter Super-Enter: Shift-Enter
- name: Slack - name: Slack

@ -4,7 +4,7 @@
# a: b # a: b
keymap: keymap:
- name: Global - name: Global
wm_class:
only: Slack
remap: remap:
C-i: C-i: C-u
remap:
C-a: C-u

@ -0,0 +1 @@
pub mod x11_client;

@ -0,0 +1,116 @@
pub struct X11Client {
// Both of them are lazily initialized
display: Option<*mut x11::xlib::Display>,
supported: Option<bool>,
last_wm_class: String,
}
impl X11Client {
pub fn new() -> X11Client {
X11Client {
display: None,
supported: None,
last_wm_class: String::new(),
}
}
pub fn supported(&mut self) -> bool {
match self.supported {
Some(supported) => supported,
None => {
let display = self.display();
let mut focused_window = 0;
let mut focus_state = 0;
unsafe { x11::xlib::XGetInputFocus(display, &mut focused_window, &mut focus_state) };
let supported = focused_window > 0;
self.supported = Some(supported);
supported
},
}
}
pub fn current_wm_class(&mut self) -> Option<String> {
if !self.supported() {
return None
}
let display = self.display();
let mut focused_window = 0;
let mut focus_state = 0;
unsafe { x11::xlib::XGetInputFocus(display, &mut focused_window, &mut focus_state) };
let mut x_class_hint = x11::xlib::XClassHint {
res_name: std::ptr::null_mut(),
res_class: std::ptr::null_mut(),
};
loop {
unsafe {
if x11::xlib::XGetClassHint(display, focused_window, &mut x_class_hint) == 1 {
break;
}
}
let mut nchildren: u32 = 0;
let mut root: x11::xlib::Window = 0;
let mut parent: x11::xlib::Window = 0;
let mut children: *mut x11::xlib::Window = &mut 0;
unsafe {
if x11::xlib::XQueryTree(
display,
focused_window,
&mut root,
&mut parent,
&mut children,
&mut nchildren,
) == 0
{
break;
}
}
if !children.is_null() {
unsafe {
x11::xlib::XFree(children as *mut std::ffi::c_void);
}
}
// The root client's parent is NULL. Avoid querying it to prevent SEGV on XGetClientHint.
if parent == 0 {
return None;
}
focused_window = parent;
}
if !x_class_hint.res_name.is_null() {
unsafe {
x11::xlib::XFree(x_class_hint.res_name as *mut std::ffi::c_void);
}
}
if !x_class_hint.res_class.is_null() {
let wm_class = unsafe {
// Note: This seems to free `x_class_hint.res_class`. So XFree on it would double-free it.
std::ffi::CString::from_raw(x_class_hint.res_class as *mut i8)
.into_string()
.unwrap()
};
if &self.last_wm_class != &wm_class {
self.last_wm_class = wm_class.clone();
println!("wm_class: {}", &wm_class);
}
Some(wm_class)
} else {
None
}
}
fn display(&mut self) -> *mut x11::xlib::Display {
match self.display {
Some(display) => display,
None => {
let display = unsafe { x11::xlib::XOpenDisplay(std::ptr::null()) };
self.display = Some(display);
display
}
}
}
}

@ -1,7 +1,7 @@
use crate::config::key::parse_key; use crate::config::key::parse_key;
use evdev::Key; use evdev::Key;
use serde::de; use serde::de;
use serde::de::Visitor; use serde::de::{Visitor};
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use std::error; use std::error;
use std::fmt::Formatter; use std::fmt::Formatter;

@ -6,9 +6,11 @@ use evdev::{EventType, InputEvent, Key};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use crate::client::x11_client::X11Client;
pub struct EventHandler { pub struct EventHandler {
device: VirtualDevice, device: VirtualDevice,
x11_client: X11Client,
override_remap: Option<HashMap<KeyPress, Vec<Action>>>, override_remap: Option<HashMap<KeyPress, Vec<Action>>>,
shift: bool, shift: bool,
control: bool, control: bool,
@ -20,6 +22,7 @@ impl EventHandler {
pub fn new(device: VirtualDevice) -> EventHandler { pub fn new(device: VirtualDevice) -> EventHandler {
EventHandler { EventHandler {
device, device,
x11_client: X11Client::new(),
override_remap: None, override_remap: None,
shift: false, shift: false,
control: false, control: false,
@ -69,6 +72,7 @@ impl EventHandler {
if !is_pressed(value) { if !is_pressed(value) {
return None; return None;
} }
let mut wm_class_cache: Option<String> = None;
let key_press = KeyPress { let key_press = KeyPress {
key: key.clone(), key: key.clone(),
@ -86,6 +90,28 @@ impl EventHandler {
} }
for keymap in &config.keymap { for keymap in &config.keymap {
if let Some(actions) = keymap.remap.get(&key_press) { if let Some(actions) = keymap.remap.get(&key_press) {
// Lazily check wm_class as needed
if let Some(wm_class_matcher) = &keymap.wm_class {
if let None = &wm_class_cache {
match self.x11_client.current_wm_class() {
Some(wm_class) => wm_class_cache = Some(wm_class),
None => wm_class_cache = Some(String::new()),
}
}
if let Some(wm_class) = &wm_class_cache {
if let Some(wm_class_only) = &wm_class_matcher.only {
if !wm_class_only.contains(wm_class) {
continue;
}
}
if let Some(wm_class_not) = &wm_class_matcher.not {
if wm_class_not.contains(wm_class) {
continue;
}
}
}
}
return Some(actions.iter().map(|a| a.clone()).collect()); return Some(actions.iter().map(|a| a.clone()).collect());
} }
} }

@ -6,6 +6,7 @@ use std::process::exit;
extern crate getopts; extern crate getopts;
mod client;
mod config; mod config;
mod event_handler; mod event_handler;
mod input; mod input;

Loading…
Cancel
Save