summaryrefslogtreecommitdiffstats
path: root/src/x11.rs
blob: 27052b929758ec4a2477b3d6ff45b877d4ef7923 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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<Self> {
        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<RuleNames> {
    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(),
    })
}