diff --git a/Cargo.lock b/Cargo.lock index f860665..9150c0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,6 +404,16 @@ dependencies = [ "slab", ] +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "hashbrown" version = "0.12.1" @@ -626,12 +636,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" - [[package]] name = "polling" version = "2.2.0" @@ -1060,6 +1064,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "winapi-wsapoll" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1076,13 +1089,25 @@ dependencies = [ ] [[package]] -name = "x11" -version = "2.19.1" +name = "x11rb" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd0565fa8bfba8c5efe02725b14dff114c866724eff2cfd44d76cea74bcd87a" +checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507" dependencies = [ - "libc", - "pkg-config", + "gethostname", + "nix 0.24.2", + "winapi", + "winapi-wsapoll", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67" +dependencies = [ + "nix 0.24.2", ] [[package]] @@ -1105,7 +1130,7 @@ dependencies = [ "serde_with", "serde_yaml", "swayipc", - "x11", + "x11rb", "zbus", ] diff --git a/Cargo.toml b/Cargo.toml index 03ebfaf..6275fb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,10 +23,10 @@ serde_json = "1.0" serde_with = { version = "2.0", features = ["chrono"] } serde_yaml = "0.9" swayipc = { version = "3.0.0", optional = true } -x11_rs = { package = "x11", version = "2.19.1", features = ["xlib"], optional = true } +x11rb = { version = "0.10.1", optional = true } zbus = { version = "1.9.2", optional = true } [features] gnome = ["zbus"] sway = ["swayipc"] -x11 = ["x11_rs"] +x11 = ["x11rb"] diff --git a/src/client/x11_client.rs b/src/client/x11_client.rs index 0e99beb..d525b8d 100644 --- a/src/client/x11_client.rs +++ b/src/client/x11_client.rs @@ -1,35 +1,34 @@ use crate::client::Client; -use nix::libc; use std::env; -use x11_rs::xlib; +use x11rb::protocol::xproto::{self}; +use x11rb::protocol::xproto::{AtomEnum, Window}; +use x11rb::{protocol::xproto::get_property, rust_connection::RustConnection}; pub struct X11Client { - display: Option<*mut xlib::Display>, + connection: Option, } impl X11Client { pub fn new() -> X11Client { - X11Client { display: None } + X11Client { connection: None } } - fn connect(&mut self) -> *mut xlib::Display { - match self.display { - Some(display) => display, - None => { - if let Err(env::VarError::NotPresent) = env::var("DISPLAY") { - println!("$DISPLAY is not set. Defaulting to DISPLAY=:0"); - env::set_var("DISPLAY", ":0"); - } + fn connect(&mut self) { + if self.connection.is_some() { + return; + } - let display = unsafe { xlib::XOpenDisplay(std::ptr::null()) }; - if display.is_null() { - let var = env::var("DISPLAY").unwrap(); - println!("warning: Failed to connect to X11."); - println!("If you saw \"No protocol specified\", try running `xhost +SI:localuser:root`."); - println!("If not, make sure `echo $DISPLAY` outputs xremap's $DISPLAY ({}).", var); - } - self.display = Some(display); - display + if let Err(env::VarError::NotPresent) = env::var("DISPLAY") { + println!("$DISPLAY is not set. Defaulting to DISPLAY=:0"); + env::set_var("DISPLAY", ":0"); + } + match x11rb::connect(None) { + Ok((connection, _)) => self.connection = Some(connection), + Err(error) => { + let var = env::var("DISPLAY").unwrap(); + println!("warning: Failed to connect to X11: {error}"); + println!("If you saw \"No protocol specified\", try running `xhost +SI:localuser:root`."); + println!("If not, make sure `echo $DISPLAY` outputs xremap's $DISPLAY ({var})."); } } } @@ -37,74 +36,66 @@ impl X11Client { impl Client for X11Client { fn supported(&mut self) -> bool { - let display = self.connect(); - if display.is_null() { - false - } else { - let mut focused_window = 0; - let mut focus_state = 0; - unsafe { xlib::XGetInputFocus(display, &mut focused_window, &mut focus_state) }; - focused_window > 0 - } + self.connect(); + return self.connection.is_some(); + // TODO: Test XGetInputFocus and focused_window > 0? } fn current_application(&mut self) -> Option { - if !self.supported() { - return None; + self.connect(); + if let Some(conn) = &self.connection { + let mut window = get_focus_window(conn)?; + loop { + if let Some(wm_class) = get_wm_class(conn, window) { + // Workaround: https://github.com/JetBrains/jdk8u_jdk/blob/master/src/solaris/classes/sun/awt/X11/XFocusProxyWindow.java#L35 + if &wm_class != "FocusProxy" { + return Some(wm_class); + } + } + + window = get_parent_window(conn, window)?; + } } + return None; + } +} - let display = self.connect(); - let mut focused_window = 0; - let mut focus_state = 0; - unsafe { xlib::XGetInputFocus(display, &mut focused_window, &mut focus_state) }; +fn get_focus_window(conn: &RustConnection) -> Option { + if let Ok(cookie) = xproto::get_input_focus(conn) { + if let Ok(reply) = cookie.reply() { + return Some(reply.focus); + } + } + return None; +} - let mut x_class_hint = xlib::XClassHint { - res_name: std::ptr::null_mut(), - res_class: std::ptr::null_mut(), - }; - let mut wm_class = String::new(); - loop { - unsafe { - if xlib::XGetClassHint(display, focused_window, &mut x_class_hint) == 1 { - if !x_class_hint.res_name.is_null() { - xlib::XFree(x_class_hint.res_name as *mut std::ffi::c_void); - } +fn get_parent_window(conn: &RustConnection, window: Window) -> Option { + if let Ok(cookie) = xproto::query_tree(conn, window) { + if let Ok(reply) = cookie.reply() { + return Some(reply.parent); + } + } + return None; +} - if !x_class_hint.res_class.is_null() { - // Note: into_string() seems to free `x_class_hint.res_class`. So XFree isn't needed. - wm_class = std::ffi::CString::from_raw(x_class_hint.res_class as *mut libc::c_char) - .into_string() - .unwrap(); - // Workaround: https://github.com/JetBrains/jdk8u_jdk/blob/master/src/solaris/classes/sun/awt/X11/XFocusProxyWindow.java#L35 - if &wm_class != "FocusProxy" { - break; - } - } - } +fn get_wm_class(conn: &RustConnection, window: Window) -> Option { + if let Ok(cookie) = get_property(conn, false, window, AtomEnum::WM_CLASS, AtomEnum::STRING, 0, 1024) { + if let Ok(reply) = cookie.reply() { + if reply.value.is_empty() { + return None; } - let mut nchildren: u32 = 0; - let mut root: xlib::Window = 0; - let mut parent: xlib::Window = 0; - let mut children: *mut xlib::Window = &mut 0; - unsafe { - if xlib::XQueryTree(display, focused_window, &mut root, &mut parent, &mut children, &mut nchildren) == 0 - { - break; - } - } - if !children.is_null() { - unsafe { - xlib::XFree(children as *mut std::ffi::c_void); + if let Some(delimiter) = reply.value.iter().position(|byte| *byte == '\0' as u8) { + let value = reply.value[(delimiter + 1)..].to_vec(); + if let Some(end) = value.iter().position(|byte| *byte == '\0' as u8) { + if end == value.len() - 1 { + if let Ok(string) = String::from_utf8(value[..end].to_vec()) { + return Some(string); + } + } } } - - // The root client's parent is NULL. Avoid querying it to prevent SEGV on XGetClientHint. - if parent == 0 { - return None; - } - focused_window = parent; } - Some(wm_class) } + return None; }