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 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}