1use anyhow::{bail, ensure, Context, Result};
6use tokio::io::{AsyncRead, AsyncReadExt};
7
8pub const MAX_REPLY_BYTES: usize = 1 << 20;
12pub const MAX_PROPERTY_BYTES: usize = 64 * 1024;
14
15pub const MIT_MAGIC_COOKIE: &str = "MIT-MAGIC-COOKIE-1";
16
17pub const CREATE_WINDOW: u8 = 1;
18pub const DESTROY_WINDOW: u8 = 4;
19pub const MAP_WINDOW: u8 = 8;
20pub const UNMAP_WINDOW: u8 = 10;
21pub const CONFIGURE_WINDOW: u8 = 12;
22pub const GET_GEOMETRY: u8 = 14;
23pub const QUERY_TREE: u8 = 15;
24pub const INTERN_ATOM: u8 = 16;
25pub const GET_ATOM_NAME: u8 = 17;
26pub const CHANGE_PROPERTY: u8 = 18;
27pub const DELETE_PROPERTY: u8 = 19;
28pub const GET_PROPERTY: u8 = 20;
29pub const LIST_PROPERTIES: u8 = 21;
30pub const GET_INPUT_FOCUS: u8 = 43;
31pub const LIST_EXTENSIONS: u8 = 99;
32pub const BELL: u8 = 104;
33
34pub const EVENT_MASKS: &[(&str, u32)] = &[
36 ("keyboard", 0x0000_0003), ("pointer", 0x0000_004C), ("exposure", 0x0000_8000), ("structure", 0x0002_0000), ("focus", 0x0020_0000), ("property", 0x0040_0000), ];
43
44pub const PREDEFINED_ATOMS: [&str; 68] = [
46 "PRIMARY",
47 "SECONDARY",
48 "ARC",
49 "ATOM",
50 "BITMAP",
51 "CARDINAL",
52 "COLORMAP",
53 "CURSOR",
54 "CUT_BUFFER0",
55 "CUT_BUFFER1",
56 "CUT_BUFFER2",
57 "CUT_BUFFER3",
58 "CUT_BUFFER4",
59 "CUT_BUFFER5",
60 "CUT_BUFFER6",
61 "CUT_BUFFER7",
62 "DRAWABLE",
63 "FONT",
64 "INTEGER",
65 "PIXMAP",
66 "POINT",
67 "RECTANGLE",
68 "RESOURCE_MANAGER",
69 "RGB_COLOR_MAP",
70 "RGB_BEST_MAP",
71 "RGB_BLUE_MAP",
72 "RGB_DEFAULT_MAP",
73 "RGB_GRAY_MAP",
74 "RGB_GREEN_MAP",
75 "RGB_RED_MAP",
76 "STRING",
77 "VISUALID",
78 "WINDOW",
79 "WM_COMMAND",
80 "WM_HINTS",
81 "WM_CLIENT_MACHINE",
82 "WM_ICON_NAME",
83 "WM_ICON_SIZE",
84 "WM_NAME",
85 "WM_NORMAL_HINTS",
86 "WM_SIZE_HINTS",
87 "WM_ZOOM_HINTS",
88 "MIN_SPACE",
89 "NORM_SPACE",
90 "MAX_SPACE",
91 "END_SPACE",
92 "SUPERSCRIPT_X",
93 "SUPERSCRIPT_Y",
94 "SUBSCRIPT_X",
95 "SUBSCRIPT_Y",
96 "UNDERLINE_POSITION",
97 "UNDERLINE_THICKNESS",
98 "STRIKEOUT_ASCENT",
99 "STRIKEOUT_DESCENT",
100 "ITALIC_ANGLE",
101 "X_HEIGHT",
102 "QUAD_WIDTH",
103 "WEIGHT",
104 "POINT_SIZE",
105 "RESOLUTION",
106 "COPYRIGHT",
107 "NOTICE",
108 "FONT_NAME",
109 "FAMILY_NAME",
110 "FULL_NAME",
111 "CAP_HEIGHT",
112 "WM_CLASS",
113 "WM_TRANSIENT_FOR",
114];
115
116pub fn predefined_atom(name: &str) -> Option<u32> {
117 PREDEFINED_ATOMS
118 .iter()
119 .position(|a| *a == name)
120 .map(|i| i as u32 + 1)
121}
122
123pub fn predefined_name(atom: u32) -> Option<&'static str> {
124 PREDEFINED_ATOMS
125 .get((atom as usize).checked_sub(1)?)
126 .copied()
127}
128
129pub fn request_name(major: u8) -> &'static str {
130 match major {
131 CREATE_WINDOW => "CreateWindow",
132 DESTROY_WINDOW => "DestroyWindow",
133 MAP_WINDOW => "MapWindow",
134 UNMAP_WINDOW => "UnmapWindow",
135 CONFIGURE_WINDOW => "ConfigureWindow",
136 GET_GEOMETRY => "GetGeometry",
137 QUERY_TREE => "QueryTree",
138 INTERN_ATOM => "InternAtom",
139 GET_ATOM_NAME => "GetAtomName",
140 CHANGE_PROPERTY => "ChangeProperty",
141 DELETE_PROPERTY => "DeleteProperty",
142 GET_PROPERTY => "GetProperty",
143 LIST_PROPERTIES => "ListProperties",
144 GET_INPUT_FOCUS => "GetInputFocus",
145 LIST_EXTENSIONS => "ListExtensions",
146 BELL => "Bell",
147 _ => "unknown",
148 }
149}
150
151pub fn error_name(code: u8) -> &'static str {
152 match code {
153 1 => "BadRequest",
154 2 => "BadValue",
155 3 => "BadWindow",
156 4 => "BadPixmap",
157 5 => "BadAtom",
158 6 => "BadCursor",
159 7 => "BadFont",
160 8 => "BadMatch",
161 9 => "BadDrawable",
162 10 => "BadAccess",
163 11 => "BadAlloc",
164 12 => "BadColormap",
165 13 => "BadGContext",
166 14 => "BadIDChoice",
167 15 => "BadName",
168 16 => "BadLength",
169 17 => "BadImplementation",
170 _ => "extension error",
171 }
172}
173
174pub fn event_name(code: u8) -> &'static str {
175 match code {
176 2 => "KeyPress",
177 3 => "KeyRelease",
178 4 => "ButtonPress",
179 5 => "ButtonRelease",
180 6 => "MotionNotify",
181 7 => "EnterNotify",
182 8 => "LeaveNotify",
183 9 => "FocusIn",
184 10 => "FocusOut",
185 11 => "KeymapNotify",
186 12 => "Expose",
187 13 => "GraphicsExposure",
188 14 => "NoExposure",
189 15 => "VisibilityNotify",
190 16 => "CreateNotify",
191 17 => "DestroyNotify",
192 18 => "UnmapNotify",
193 19 => "MapNotify",
194 20 => "MapRequest",
195 21 => "ReparentNotify",
196 22 => "ConfigureNotify",
197 23 => "ConfigureRequest",
198 24 => "GravityNotify",
199 25 => "ResizeRequest",
200 26 => "CirculateNotify",
201 27 => "CirculateRequest",
202 28 => "PropertyNotify",
203 29 => "SelectionClear",
204 30 => "SelectionRequest",
205 31 => "SelectionNotify",
206 32 => "ColormapNotify",
207 33 => "ClientMessage",
208 34 => "MappingNotify",
209 35 => "GenericEvent",
210 _ => "extension event",
211 }
212}
213
214pub fn pad(n: usize) -> usize {
215 (4 - n % 4) % 4
216}
217
218pub fn u16_at(b: &[u8], at: usize) -> u16 {
219 u16::from_le_bytes([b[at], b[at + 1]])
220}
221
222pub fn i16_at(b: &[u8], at: usize) -> i16 {
223 u16_at(b, at) as i16
224}
225
226pub fn u32_at(b: &[u8], at: usize) -> u32 {
227 u32::from_le_bytes([b[at], b[at + 1], b[at + 2], b[at + 3]])
228}
229
230pub fn setup_request(cookie: Option<&[u8]>) -> Vec<u8> {
232 let (name, data): (&[u8], &[u8]) = match cookie {
233 Some(c) => (MIT_MAGIC_COOKIE.as_bytes(), c),
234 None => (b"", b""),
235 };
236 let mut out = vec![b'l', 0];
237 out.extend(11u16.to_le_bytes());
238 out.extend(0u16.to_le_bytes());
239 out.extend((name.len() as u16).to_le_bytes());
240 out.extend((data.len() as u16).to_le_bytes());
241 out.extend([0, 0]);
242 out.extend(name);
243 out.extend(vec![0; pad(name.len())]);
244 out.extend(data);
245 out.extend(vec![0; pad(data.len())]);
246 out
247}
248
249#[derive(Debug, Clone)]
250pub struct Screen {
251 pub root: u32,
252 pub white_pixel: u32,
253 pub black_pixel: u32,
254 pub width: u16,
255 pub height: u16,
256 pub width_mm: u16,
257 pub height_mm: u16,
258 pub root_visual: u32,
259 pub root_depth: u8,
260}
261
262#[derive(Debug, Clone)]
263pub struct Setup {
264 pub protocol_major: u16,
265 pub protocol_minor: u16,
266 pub release: u32,
267 pub vendor: String,
268 pub resource_id_base: u32,
269 pub resource_id_mask: u32,
270 pub max_request_bytes: usize,
272 pub screens: Vec<Screen>,
273}
274
275pub async fn read_setup<R: AsyncRead + Unpin>(r: &mut R) -> Result<Setup> {
278 let mut head = [0u8; 8];
279 r.read_exact(&mut head)
280 .await
281 .context("the X server closed the connection during setup")?;
282 let len = u16_at(&head, 6) as usize * 4;
283 let mut body = vec![0u8; len];
284 r.read_exact(&mut body)
285 .await
286 .context("the X server closed the connection during setup")?;
287 match head[0] {
288 1 => parse_setup(u16_at(&head, 2), u16_at(&head, 4), &body),
289 0 => {
290 let reason = &body[..(head[1] as usize).min(body.len())];
291 bail!(
292 "the X server refused the connection: {}",
293 crate::utils::sanitize::line_field(String::from_utf8_lossy(reason).trim())
294 )
295 }
296 2 => {
297 let reason = String::from_utf8_lossy(&body);
298 bail!(
299 "the X server asks for further authentication: {}",
300 crate::utils::sanitize::line_field(reason.trim_end_matches('\0').trim())
301 )
302 }
303 other => bail!("the X server answered setup with status {other}"),
304 }
305}
306
307fn parse_setup(major: u16, minor: u16, b: &[u8]) -> Result<Setup> {
308 ensure!(
309 b.len() >= 32,
310 "setup reply of {} bytes is too short",
311 b.len()
312 );
313 let vendor_len = u16_at(b, 16) as usize;
314 let screens_n = b[20] as usize;
315 let formats_n = b[21] as usize;
316 let mut at = 32 + vendor_len + pad(vendor_len);
317 ensure!(at <= b.len(), "setup vendor overruns the reply");
318 let vendor =
319 crate::utils::sanitize::line_field(&String::from_utf8_lossy(&b[32..32 + vendor_len]));
320 at += 8 * formats_n;
321 let mut screens = Vec::with_capacity(screens_n);
322 for _ in 0..screens_n {
323 ensure!(at + 40 <= b.len(), "setup screen overruns the reply");
324 let s = &b[at..];
325 screens.push(Screen {
326 root: u32_at(s, 0),
327 white_pixel: u32_at(s, 8),
328 black_pixel: u32_at(s, 12),
329 width: u16_at(s, 20),
330 height: u16_at(s, 22),
331 width_mm: u16_at(s, 24),
332 height_mm: u16_at(s, 26),
333 root_visual: u32_at(s, 32),
334 root_depth: s[38],
335 });
336 let depths = s[39] as usize;
337 at += 40;
338 for _ in 0..depths {
339 ensure!(at + 8 <= b.len(), "setup depth overruns the reply");
340 let visuals = u16_at(b, at + 2) as usize;
341 at += 8 + 24 * visuals;
342 }
343 }
344 ensure!(at <= b.len(), "setup visuals overrun the reply");
345 Ok(Setup {
346 protocol_major: major,
347 protocol_minor: minor,
348 release: u32_at(b, 0),
349 vendor,
350 resource_id_base: u32_at(b, 4),
351 resource_id_mask: u32_at(b, 8),
352 max_request_bytes: u16_at(b, 18) as usize * 4,
353 screens,
354 })
355}
356
357#[derive(Debug, Clone)]
358pub enum Packet {
359 Reply { seq: u16, body: Vec<u8> },
361 Error {
362 seq: u16,
363 code: u8,
364 bad_value: u32,
365 major: u8,
366 minor: u16,
367 },
368 Event {
370 code: u8,
371 synthetic: bool,
372 body: Vec<u8>,
373 },
374}
375
376pub async fn read_packet<R: AsyncRead + Unpin>(r: &mut R) -> Result<Option<Packet>> {
379 let mut head = [0u8; 32];
380 match r.read_exact(&mut head).await {
381 Ok(_) => {}
382 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
383 Err(e) => return Err(e.into()),
384 }
385 let seq = u16_at(&head, 2);
386 let code = head[0] & 0x7F;
387 let extended = head[0] == 1 || code == 35;
388 let mut body = head.to_vec();
389 if extended {
390 let extra = (u32_at(&head, 4) as usize).saturating_mul(4);
391 ensure!(
392 32usize.saturating_add(extra) <= MAX_REPLY_BYTES,
393 "the X server announced a {}-byte packet; the bound is {MAX_REPLY_BYTES}",
394 32usize.saturating_add(extra)
395 );
396 body.resize(32 + extra, 0);
397 r.read_exact(&mut body[32..]).await?;
398 }
399 Ok(Some(match head[0] {
400 0 => Packet::Error {
401 seq,
402 code: head[1],
403 bad_value: u32_at(&head, 4),
404 minor: u16_at(&head, 8),
405 major: head[10],
406 },
407 1 => Packet::Reply { seq, body },
408 _ => Packet::Event {
409 code,
410 synthetic: head[0] & 0x80 != 0,
411 body,
412 },
413 }))
414}
415
416pub fn request(opcode: u8, data: u8, body: &[u8], max_bytes: usize) -> Result<Vec<u8>> {
418 let total = 4 + body.len() + pad(body.len());
419 ensure!(
420 total <= max_bytes && total / 4 <= u16::MAX as usize,
421 "a {total}-byte {} request exceeds the server's {max_bytes}-byte maximum",
422 request_name(opcode)
423 );
424 let mut out = Vec::with_capacity(total);
425 out.push(opcode);
426 out.push(data);
427 out.extend(((total / 4) as u16).to_le_bytes());
428 out.extend(body);
429 out.resize(total, 0);
430 Ok(out)
431}
432
433pub fn u32s(values: &[u32]) -> Vec<u8> {
434 values.iter().flat_map(|v| v.to_le_bytes()).collect()
435}
436
437#[allow(clippy::too_many_arguments)]
438pub fn create_window(
439 wid: u32,
440 parent: u32,
441 x: i16,
442 y: i16,
443 width: u16,
444 height: u16,
445 border: u16,
446 background: u32,
447 event_mask: u32,
448) -> Vec<u8> {
449 let mut b = u32s(&[wid, parent]);
450 b.extend(x.to_le_bytes());
451 b.extend(y.to_le_bytes());
452 b.extend(width.to_le_bytes());
453 b.extend(height.to_le_bytes());
454 b.extend(border.to_le_bytes());
455 b.extend(1u16.to_le_bytes()); b.extend(u32s(&[0, 0x0000_0802, background, event_mask]));
458 b
459}
460
461pub fn configure_window(wid: u32, values: &[(u16, u32)]) -> Vec<u8> {
462 let mask = values.iter().fold(0u16, |m, (bit, _)| m | bit);
463 let mut b = u32s(&[wid]);
464 b.extend(mask.to_le_bytes());
465 b.extend([0, 0]);
466 let mut sorted = values.to_vec();
467 sorted.sort_by_key(|(bit, _)| *bit);
468 b.extend(u32s(&sorted.iter().map(|(_, v)| *v).collect::<Vec<_>>()));
469 b
470}
471
472pub fn intern_atom(name: &str) -> Vec<u8> {
473 let mut b = (name.len() as u16).to_le_bytes().to_vec();
474 b.extend([0, 0]);
475 b.extend(name.as_bytes());
476 b
477}
478
479pub fn change_property(wid: u32, property: u32, kind: u32, format: u8, data: &[u8]) -> Vec<u8> {
480 let mut b = u32s(&[wid, property, kind]);
481 b.push(format);
482 b.extend([0, 0, 0]);
483 let units = data.len() / (format as usize / 8);
484 b.extend((units as u32).to_le_bytes());
485 b.extend(data);
486 b
487}
488
489pub fn get_property(wid: u32, property: u32) -> Vec<u8> {
490 u32s(&[wid, property, 0, 0, (MAX_PROPERTY_BYTES / 4) as u32])
492}
493
494pub fn tail(body: &[u8]) -> &[u8] {
496 &body[32.min(body.len())..]
497}
498
499pub fn strs(count: usize, b: &[u8]) -> Vec<String> {
501 let mut out = Vec::new();
502 let mut at = 0;
503 for _ in 0..count {
504 let Some(&len) = b.get(at) else { break };
505 let Some(s) = b.get(at + 1..at + 1 + len as usize) else {
506 break;
507 };
508 out.push(String::from_utf8_lossy(s).into_owned());
509 at += 1 + len as usize;
510 }
511 out
512}