i3status_rs/blocks/keyboard_layout/
xkb_event.rs1use 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) .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}