From 247d2805db0c1b686ef4c6cc0f786ff416aeb7e3 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 11 Jul 2026 01:46:59 +0200 Subject: init --- src/x11.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/x11.rs (limited to 'src/x11.rs') diff --git a/src/x11.rs b/src/x11.rs new file mode 100644 index 0000000..27052b9 --- /dev/null +++ b/src/x11.rs @@ -0,0 +1,92 @@ +use x11rb::connection::{Connection, RequestConnection}; +use x11rb::protocol::xkb::{self, ConnectionExt as XkbConnectionExt}; +use x11rb::protocol::xproto::{AtomEnum, ConnectionExt}; +use x11rb::rust_connection::RustConnection; + +#[derive(Debug, Default)] +pub struct RuleNames { + pub rules: String, + pub model: String, + pub layout: String, + pub variant: String, + pub options: String, +} + +pub struct X11Connection { + pub conn: RustConnection, + pub root: x11rb::protocol::xproto::Window, + xkb_ok: bool, +} + +impl X11Connection { + pub fn open() -> Option { + let (conn, screen_num) = x11rb::connect(None).ok()?; + let root = conn.setup().roots[screen_num].root; + let xkb_ok = conn + .extension_information(xkb::X11_EXTENSION_NAME) + .ok() + .flatten() + .is_some(); + if xkb_ok { + let _ = conn.xkb_use_extension(1, 0).ok(); + } + Some(Self { conn, root, xkb_ok }) + } + + pub fn modifier_mask(&self) -> u16 { + self.conn + .query_pointer(self.root) + .ok() + .and_then(|c| c.reply().ok()) + .map(|r| r.mask.into()) + .unwrap_or(0) + } + + pub fn active_group(&self) -> usize { + if !self.xkb_ok { + return 0; + } + self.conn + .xkb_get_state(0x0100) + .ok() + .and_then(|c| c.reply().ok()) + .map(|r| u8::from(r.group) as usize) + .unwrap_or(0) + } +} + +pub fn get_xkb_rule_names() -> Option { + let (conn, screen_num) = x11rb::connect(None).ok()?; + let screen = &conn.setup().roots[screen_num]; + + let atom = conn + .intern_atom(false, b"_XKB_RULES_NAMES") + .ok()? + .reply() + .ok()? + .atom; + + let prop = conn + .get_property(false, screen.root, atom, AtomEnum::ANY, 0, 1024) + .ok()? + .reply() + .ok()?; + + if prop.value.is_empty() || prop.format == 0 { + return None; + } + + let parts: Vec<&str> = prop + .value + .split(|&b| b == 0) + .filter_map(|s| std::str::from_utf8(s).ok()) + .collect(); + + Some(RuleNames { + rules: parts.first().map(|s| s.to_string()).unwrap_or_default(), + model: parts.get(1).map(|s| s.to_string()).unwrap_or_default(), + layout: parts.get(2).map(|s| s.to_string()).unwrap_or_default(), + variant: parts.get(3).map(|s| s.to_string()).unwrap_or_default(), + options: parts.get(4).map(|s| s.to_string()).unwrap_or_default(), + }) +} -- cgit v1.2.3