i3status_rs/blocks/keyboard_layout/
xkb_event.rs

1use super::*;
2use x11rb_async::{
3    connection::{Connection as _, RequestConnection as _},
4    protocol::{
5        Event,
6        xkb::{
7            self, ConnectionExt as _, EventType, ID, MapPart, NameDetail, SelectEventsAux,
8            UseExtensionReply,
9        },
10        xproto::ConnectionExt as _,
11    },
12    rust_connection::RustConnection,
13};
14
15const XCB_XKB_MINOR_VERSION: u16 = 0;
16const XCB_XKB_MAJOR_VERSION: u16 = 1;
17
18pub(super) struct XkbEvent {
19    connection: RustConnection,
20}
21
22fn parse_layout(buf: &[u8], index: usize) -> Result<&str> {
23    let colon_i = buf.iter().position(|c| *c == b':').unwrap_or(buf.len());
24    let layout = buf[..colon_i]
25        .split(|&c| c == b'+')
26        .skip(1) // layout names start from index 1
27        .nth(index)
28        .error("Index out of range")?;
29    std::str::from_utf8(layout).error("non utf8 layout")
30}
31
32async fn get_layout(connection: &RustConnection) -> Result<String> {
33    let xkb_state = connection
34        .xkb_get_state(ID::USE_CORE_KBD.into())
35        .await
36        .error("xkb_get_state failed")?
37        .reply()
38        .await
39        .error("xkb_get_state reply failed")?;
40    let group: u8 = xkb_state.group.into();
41
42    let symbols_name = connection
43        .xkb_get_names(
44            ID::USE_CORE_KBD.into(),
45            NameDetail::GROUP_NAMES | NameDetail::SYMBOLS,
46        )
47        .await
48        .error("xkb_get_names failed")?
49        .reply()
50        .await
51        .error("xkb_get_names reply failed")?
52        .value_list
53        .symbols_name
54        .error("symbols_name is empty")?;
55
56    let name = connection
57        .get_atom_name(symbols_name)
58        .await
59        .error("get_atom_name failed")?
60        .reply()
61        .await
62        .error("get_atom_name reply failed")?
63        .name;
64    let layout = parse_layout(&name, group as _)?;
65
66    Ok(layout.to_owned())
67}
68
69async fn prefetch_xkb_extension(connection: &RustConnection) -> Result<UseExtensionReply> {
70    connection
71        .prefetch_extension_information(xkb::X11_EXTENSION_NAME)
72        .await
73        .error("prefetch_extension_information failed")?;
74
75    let reply = connection
76        .xkb_use_extension(XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION)
77        .await
78        .error("xkb_use_extension failed")?
79        .reply()
80        .await
81        .error("xkb_use_extension reply failed")?;
82
83    Ok(reply)
84}
85
86impl XkbEvent {
87    pub(super) async fn new() -> Result<Self> {
88        let (connection, _, drive) = RustConnection::connect(None)
89            .await
90            .error("Failed to open XCB connection")?;
91
92        tokio::spawn(drive);
93        let reply = prefetch_xkb_extension(&connection)
94            .await
95            .error("Failed to prefetch xkb extension")?;
96
97        if !reply.supported {
98            return Err(Error::new(
99                "This program requires the X11 server to support the XKB extension",
100            ));
101        }
102
103        connection
104            .xkb_select_events(
105                ID::USE_CORE_KBD.into(),
106                EventType::default(),
107                EventType::STATE_NOTIFY,
108                MapPart::default(),
109                MapPart::default(),
110                &SelectEventsAux::new(),
111            )
112            .await
113            .error("Failed to select events")?;
114
115        Ok(XkbEvent { connection })
116    }
117}
118
119#[async_trait]
120impl Backend for XkbEvent {
121    async fn get_info(&mut self) -> Result<Info> {
122        let cur_layout = get_layout(&self.connection)
123            .await
124            .error("Failed to get current layout")?;
125        Ok(Info::from_layout_variant_str(&cur_layout))
126    }
127
128    async fn wait_for_change(&mut self) -> Result<()> {
129        loop {
130            let event = self
131                .connection
132                .wait_for_event()
133                .await
134                .error("Failed to read the event")?;
135
136            if let Event::XkbStateNotify(e) = event {
137                if e.changed.contains(xkb::StatePart::GROUP_STATE) {
138                    return Ok(());
139                }
140            }
141        }
142    }
143}