Skip to main content

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