lib/sys/src/x11/protocol.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const Opcode = enum(u8) {
4 create_window = 1,
5 change_window_attributes = 2,
6 destroy_window = 4,
7 map_window = 8,
8 unmap_window = 10,
9 configure_window = 12,
10 intern_atom = 16,
11 change_property = 18,
12 get_property = 20,
13 set_selection_owner = 22,
14 convert_selection = 24,
15 send_event = 25,
16 open_font = 45,
17 close_font = 46,
18 create_gc = 55,
19 free_gc = 60,
20 put_image = 72,
21 get_image = 73,
22 create_glyph_cursor = 94,
23 free_cursor = 95,
24 query_extension = 98,
25 get_keyboard_mapping = 101,
26 };
27
28 pub const EventCode = enum(u8) {
29 @"error" = 0,
30 reply = 1,
31 key_press = 2,
32 key_release = 3,
33 button_press = 4,
34 button_release = 5,
35 motion_notify = 6,
36 enter_notify = 7,
37 leave_notify = 8,
38 focus_in = 9,
39 focus_out = 10,
40 expose = 12,
41 destroy_notify = 17,
42 unmap_notify = 18,
43 map_notify = 19,
44 configure_notify = 22,
45 property_notify = 28,
46 selection_clear = 29,
47 selection_request = 30,
48 selection_notify = 31,
49 client_message = 33,
50 _,
51 };
52
53 pub const event_mask = struct {
54 pub const key_press: u32 = 0x0000_0001;
55 pub const key_release: u32 = 0x0000_0002;
56 pub const button_press: u32 = 0x0000_0004;
57 pub const button_release: u32 = 0x0000_0008;
58 pub const enter_window: u32 = 0x0000_0010;
59 pub const leave_window: u32 = 0x0000_0020;
60 pub const pointer_motion: u32 = 0x0000_0040;
61 pub const exposure: u32 = 0x0000_8000;
62 pub const structure_notify: u32 = 0x0002_0000;
63 pub const focus_change: u32 = 0x0020_0000;
64 pub const property_change: u32 = 0x0040_0000;
65 };
66
67 pub const state_mask = struct {
68 pub const shift: u16 = 0x0001;
69 pub const lock: u16 = 0x0002;
70 pub const control: u16 = 0x0004;
71 pub const mod1: u16 = 0x0008;
72 pub const mod4: u16 = 0x0040;
73 };
74
75 pub const window_class_input_output: u16 = 1;
76 pub const window_value_back_pixel: u32 = 0x0000_0002;
77 pub const window_value_event_mask: u32 = 0x0000_0800;
78 pub const property_replace: u8 = 0;
79 pub const format_zpixmap: u8 = 2;
80 pub const configure_value_width: u16 = 0x0004;
81 pub const configure_value_height: u16 = 0x0008;
82 pub const window_value_cursor: u32 = 0x0000_4000;
83 pub const current_time: u32 = 0;
84 pub const atom_none: u32 = 0;
85 pub const minimum_request_byte_count = 56;
86 pub const maximum_request_byte_count = @as(usize, std.math.maxInt(u16)) * 4;
87 pub const setup_request_byte_count = 48;
88 pub const maximum_setup_response_byte_count = @as(usize, std.math.maxInt(u16)) * 4;
89 pub const maximum_reply_extra_byte_count = @as(u64, std.math.maxInt(u32)) * 4;
90 pub const maximum_keyboard_mapping_reply_byte_count =
91 @as(usize, std.math.maxInt(u8)) * std.math.maxInt(u8) * 4;
92 pub const reply_discard_scratch_byte_count = 4096;
93
94 pub const RequestError = error{
95 RequestCapacityExceeded,
96 RequestLengthOverflow,
97 };
98
99 pub const Request = struct {
100 storage: []u8,
101 limit: usize,
102 len: usize = 0,
103 rejection_count: u64 = 0,
104
105 pub fn init(storage: []u8, limit: usize) Request {
106 std.debug.assert(limit > 0);
107 std.debug.assert(limit <= storage.len);
108 std.debug.assert(limit <= maximum_request_byte_count);
109 std.debug.assert(limit % 4 == 0);
110 return .{
111 .storage = storage,
112 .limit = limit,
113 };
114 }
115
116 pub fn reset(self: *Request) void {
117 std.debug.assert(self.len <= self.limit);
118 self.len = 0;
119 }
120
121 pub fn bytes(self: *const Request) []const u8 {
122 std.debug.assert(self.len <= self.limit);
123 return self.storage[0..self.len];
124 }
125
126 pub fn byteCapacity(self: *const Request) usize {
127 return self.limit;
128 }
129
130 pub fn reserve(self: *Request, additional: usize) RequestError!void {
131 std.debug.assert(self.len <= self.limit);
132 const total = std.math.add(usize, self.len, additional) catch {
133 self.reject();
134 return error.RequestCapacityExceeded;
135 };
136 if (total > self.limit) {
137 self.reject();
138 return error.RequestCapacityExceeded;
139 }
140 }
141
142 pub fn capacityRejectionCount(self: *const Request) u64 {
143 return self.rejection_count;
144 }
145
146 fn appendSliceAssumeCapacity(self: *Request, data: []const u8) void {
147 std.debug.assert(data.len <= self.limit - self.len);
148 @memcpy(self.storage[self.len..][0..data.len], data);
149 self.len += data.len;
150 }
151
152 fn appendByteAssumeCapacity(self: *Request, byte: u8) void {
153 std.debug.assert(self.len < self.limit);
154 self.storage[self.len] = byte;
155 self.len += 1;
156 }
157
158 fn appendZeroesAssumeCapacity(self: *Request, count: usize) void {
159 std.debug.assert(count <= self.limit - self.len);
160 @memset(self.storage[self.len..][0..count], 0);
161 self.len += count;
162 }
163
164 fn reject(self: *Request) void {
165 self.rejection_count +|= 1;
166 }
167 };
168
169 pub fn pad4(len: usize) usize {
170 return (4 - (len % 4)) % 4;
171 }
172
173 fn requestUnits(byte_len: usize) u16 {
174 std.debug.assert(byte_len % 4 == 0);
175 std.debug.assert(byte_len <= maximum_request_byte_count);
176 return @intCast(byte_len / 4);
177 }
178
179 pub const Writer = struct {
180 request: *Request,
181
182 pub fn init(request: *Request, byte_count: usize) RequestError!Writer {
183 try request.reserve(byte_count);
184 return .{ .request = request };
185 }
186
187 pub fn u8v(self: Writer, value: u8) RequestError!void {
188 self.request.appendByteAssumeCapacity(value);
189 }
190
191 pub fn u16v(self: Writer, value: u16) RequestError!void {
192 var scratch: [2]u8 = undefined;
193 std.mem.writeInt(u16, &scratch, value, .little);
194 self.request.appendSliceAssumeCapacity(&scratch);
195 }
196
197 pub fn i16v(self: Writer, value: i16) RequestError!void {
198 try self.u16v(@bitCast(value));
199 }
200
201 pub fn u32v(self: Writer, value: u32) RequestError!void {
202 var scratch: [4]u8 = undefined;
203 std.mem.writeInt(u32, &scratch, value, .little);
204 self.request.appendSliceAssumeCapacity(&scratch);
205 }
206
207 pub fn bytes(self: Writer, value: []const u8) RequestError!void {
208 self.request.appendSliceAssumeCapacity(value);
209 }
210
211 pub fn paddedBytes(self: Writer, value: []const u8) RequestError!void {
212 try self.bytes(value);
213 try self.zeroes(pad4(value.len));
214 }
215
216 pub fn zeroes(self: Writer, count: usize) RequestError!void {
217 self.request.appendZeroesAssumeCapacity(count);
218 }
219 };
220
221 fn paddedRequestBytes(base: usize, bytes: usize) RequestError!usize {
222 const unpadded = std.math.add(usize, base, bytes) catch
223 return error.RequestLengthOverflow;
224 return std.math.add(usize, unpadded, pad4(bytes)) catch
225 return error.RequestLengthOverflow;
226 }
227
228 fn u16Length(len: usize) RequestError!u16 {
229 if (len > std.math.maxInt(u16)) return error.RequestLengthOverflow;
230 return @intCast(len);
231 }
232
233 fn requestHeader(writer: Writer, opcode: Opcode, data: u8, units: u16) RequestError!void {
234 try writer.u8v(@backingInt(opcode));
235 try writer.u8v(data);
236 try writer.u16v(units);
237 }
238
239 pub fn setupRequest(
240 request: *Request,
241 auth_name: []const u8,
242 auth_data: []const u8,
243 ) RequestError!void {
244 var byte_count = try paddedRequestBytes(12, auth_name.len);
245 byte_count = try paddedRequestBytes(byte_count, auth_data.len);
246 const writer = try Writer.init(request, byte_count);
247 try writer.u8v(0x6C);
248 try writer.u8v(0);
249 try writer.u16v(11);
250 try writer.u16v(0);
251 try writer.u16v(try u16Length(auth_name.len));
252 try writer.u16v(try u16Length(auth_data.len));
253 try writer.u16v(0);
254 try writer.paddedBytes(auth_name);
255 try writer.paddedBytes(auth_data);
256 }
257
258 pub const CreateWindow = struct {
259 window: u32,
260 parent: u32,
261 depth: u8,
262 visual: u32,
263 x: i16 = 0,
264 y: i16 = 0,
265 width: u16,
266 height: u16,
267 background_pixel: u32 = 0,
268 events: u32,
269 };
270
271 pub fn createWindow(buffer: *Request, request: CreateWindow) RequestError!void {
272 const writer = try Writer.init(buffer, 40);
273 try requestHeader(writer, .create_window, request.depth, 8 + 2);
274 try writer.u32v(request.window);
275 try writer.u32v(request.parent);
276 try writer.i16v(request.x);
277 try writer.i16v(request.y);
278 try writer.u16v(request.width);
279 try writer.u16v(request.height);
280 try writer.u16v(0);
281 try writer.u16v(window_class_input_output);
282 try writer.u32v(request.visual);
283 try writer.u32v(window_value_back_pixel | window_value_event_mask);
284 try writer.u32v(request.background_pixel);
285 try writer.u32v(request.events);
286 }
287
288 pub fn windowRequest(buffer: *Request, opcode: Opcode, window: u32) RequestError!void {
289 const writer = try Writer.init(buffer, 8);
290 try requestHeader(writer, opcode, 0, 2);
291 try writer.u32v(window);
292 }
293
294 pub fn changeCursor(buffer: *Request, window: u32, cursor: u32) RequestError!void {
295 const writer = try Writer.init(buffer, 16);
296 try requestHeader(writer, .change_window_attributes, 0, 4);
297 try writer.u32v(window);
298 try writer.u32v(window_value_cursor);
299 try writer.u32v(cursor);
300 }
301
302 pub fn internAtom(buffer: *Request, name: []const u8) RequestError!void {
303 const payload = try paddedRequestBytes(8, name.len);
304 const name_len = try u16Length(name.len);
305 const writer = try Writer.init(buffer, payload);
306 try requestHeader(writer, .intern_atom, 0, requestUnits(payload));
307 try writer.u16v(name_len);
308 try writer.u16v(0);
309 try writer.paddedBytes(name);
310 }
311
312 pub const ChangeProperty = struct {
313 window: u32,
314 property: u32,
315 property_type: u32,
316 format: u8,
317 data: []const u8,
318 };
319
320 pub fn changeProperty(buffer: *Request, request: ChangeProperty) RequestError!void {
321 const payload = try paddedRequestBytes(24, request.data.len);
322 const writer = try Writer.init(buffer, payload);
323 try requestHeader(writer, .change_property, property_replace, requestUnits(payload));
324 try writer.u32v(request.window);
325 try writer.u32v(request.property);
326 try writer.u32v(request.property_type);
327 try writer.u8v(request.format);
328 try writer.zeroes(3);
329 try writer.u32v(@intCast(request.data.len / (request.format / 8)));
330 try writer.paddedBytes(request.data);
331 }
332
333 pub const GetProperty = struct {
334 window: u32,
335 property: u32,
336 property_type: u32,
337 long_offset: u32 = 0,
338 long_length: u32,
339 delete: bool = false,
340 };
341
342 pub fn getProperty(buffer: *Request, request: GetProperty) RequestError!void {
343 const writer = try Writer.init(buffer, 24);
344 try requestHeader(writer, .get_property, @intFromBool(request.delete), 6);
345 try writer.u32v(request.window);
346 try writer.u32v(request.property);
347 try writer.u32v(request.property_type);
348 try writer.u32v(request.long_offset);
349 try writer.u32v(request.long_length);
350 }
351
352 pub fn setSelectionOwner(
353 buffer: *Request,
354 owner: u32,
355 selection: u32,
356 ) RequestError!void {
357 const writer = try Writer.init(buffer, 16);
358 try requestHeader(writer, .set_selection_owner, 0, 4);
359 try writer.u32v(owner);
360 try writer.u32v(selection);
361 try writer.u32v(current_time);
362 }
363
364 pub const ConvertSelection = struct {
365 requestor: u32,
366 selection: u32,
367 target: u32,
368 property: u32,
369 };
370
371 pub fn convertSelection(buffer: *Request, request: ConvertSelection) RequestError!void {
372 const writer = try Writer.init(buffer, 24);
373 try requestHeader(writer, .convert_selection, 0, 6);
374 try writer.u32v(request.requestor);
375 try writer.u32v(request.selection);
376 try writer.u32v(request.target);
377 try writer.u32v(request.property);
378 try writer.u32v(current_time);
379 }
380
381 pub const ClientMessage = struct {
382 window: u32,
383 message_type: u32,
384 data: [5]u32,
385 };
386
387 pub fn sendClientMessage(
388 buffer: *Request,
389 destination: u32,
390 destination_mask: u32,
391 message: ClientMessage,
392 ) RequestError!void {
393 var raw = @as([message_length]u8, @splat(0));
394 raw[0] = @backingInt(EventCode.client_message);
395 raw[1] = 32;
396 std.mem.writeInt(u32, raw[4..8], message.window, .little);
397 std.mem.writeInt(u32, raw[8..12], message.message_type, .little);
398 for (message.data, 0..) |word, i| {
399 std.mem.writeInt(u32, raw[12 + i * 4 ..][0..4], word, .little);
400 }
401 try sendEvent(buffer, destination, destination_mask, &raw);
402 }
403
404 pub const SelectionNotify = struct {
405 time: u32,
406 requestor: u32,
407 selection: u32,
408 target: u32,
409 property: u32,
410 };
411
412 pub fn sendSelectionNotify(
413 buffer: *Request,
414 selection: SelectionNotify,
415 ) RequestError!void {
416 var raw = @as([message_length]u8, @splat(0));
417 raw[0] = @backingInt(EventCode.selection_notify);
418 std.mem.writeInt(u32, raw[4..8], selection.time, .little);
419 std.mem.writeInt(u32, raw[8..12], selection.requestor, .little);
420 std.mem.writeInt(u32, raw[12..16], selection.selection, .little);
421 std.mem.writeInt(u32, raw[16..20], selection.target, .little);
422 std.mem.writeInt(u32, raw[20..24], selection.property, .little);
423 try sendEvent(buffer, selection.requestor, 0, &raw);
424 }
425
426 pub fn sendEvent(
427 buffer: *Request,
428 destination: u32,
429 destination_mask: u32,
430 raw: *const [message_length]u8,
431 ) RequestError!void {
432 const writer = try Writer.init(buffer, 44);
433 try requestHeader(writer, .send_event, 0, 11);
434 try writer.u32v(destination);
435 try writer.u32v(destination_mask);
436 try writer.bytes(raw);
437 }
438
439 pub fn configureSize(buffer: *Request, window: u32, width: u16, height: u16) RequestError!void {
440 const writer = try Writer.init(buffer, 20);
441 try requestHeader(writer, .configure_window, 0, 5);
442 try writer.u32v(window);
443 try writer.u16v(configure_value_width | configure_value_height);
444 try writer.u16v(0);
445 try writer.u32v(width);
446 try writer.u32v(height);
447 }
448
449 pub fn createGc(buffer: *Request, gc: u32, drawable: u32) RequestError!void {
450 const writer = try Writer.init(buffer, 16);
451 try requestHeader(writer, .create_gc, 0, 4);
452 try writer.u32v(gc);
453 try writer.u32v(drawable);
454 try writer.u32v(0);
455 }
456
457 pub fn freeResource(buffer: *Request, opcode: Opcode, id: u32) RequestError!void {
458 const writer = try Writer.init(buffer, 8);
459 try requestHeader(writer, opcode, 0, 2);
460 try writer.u32v(id);
461 }
462
463 pub const PutImageRows = struct {
464 drawable: u32,
465 gc: u32,
466 width: u16,
467 row_count: u16,
468 dst_x: i16,
469 dst_y: i16,
470 depth: u8,
471 pixels: []const u8,
472 };
473
474 pub fn putImageRows(buffer: *Request, request: PutImageRows) RequestError!void {
475 const payload = try paddedRequestBytes(24, request.pixels.len);
476 const writer = try Writer.init(buffer, payload);
477 try requestHeader(writer, .put_image, format_zpixmap, requestUnits(payload));
478 try writer.u32v(request.drawable);
479 try writer.u32v(request.gc);
480 try writer.u16v(request.width);
481 try writer.u16v(request.row_count);
482 try writer.i16v(request.dst_x);
483 try writer.i16v(request.dst_y);
484 try writer.u8v(0);
485 try writer.u8v(request.depth);
486 try writer.u16v(0);
487 try writer.paddedBytes(request.pixels);
488 }
489
490 pub const GetImage = struct {
491 drawable: u32,
492 x: i16,
493 y: i16,
494 width: u16,
495 height: u16,
496 };
497
498 pub fn getImage(buffer: *Request, request: GetImage) RequestError!void {
499 const writer = try Writer.init(buffer, 20);
500 try requestHeader(writer, .get_image, format_zpixmap, 5);
501 try writer.u32v(request.drawable);
502 try writer.i16v(request.x);
503 try writer.i16v(request.y);
504 try writer.u16v(request.width);
505 try writer.u16v(request.height);
506 try writer.u32v(0xFFFF_FFFF);
507 }
508
509 pub fn openFont(buffer: *Request, font: u32, name: []const u8) RequestError!void {
510 const payload = try paddedRequestBytes(12, name.len);
511 const name_len = try u16Length(name.len);
512 const writer = try Writer.init(buffer, payload);
513 try requestHeader(writer, .open_font, 0, requestUnits(payload));
514 try writer.u32v(font);
515 try writer.u16v(name_len);
516 try writer.u16v(0);
517 try writer.paddedBytes(name);
518 }
519
520 pub const GlyphCursor = struct {
521 cursor: u32,
522 font: u32,
523 glyph: u16,
524 };
525
526 pub fn createGlyphCursor(buffer: *Request, request: GlyphCursor) RequestError!void {
527 const writer = try Writer.init(buffer, 32);
528 try requestHeader(writer, .create_glyph_cursor, 0, 8);
529 try writer.u32v(request.cursor);
530 try writer.u32v(request.font);
531 try writer.u32v(request.font);
532 try writer.u16v(request.glyph);
533 try writer.u16v(request.glyph + 1);
534 try writer.u16v(0);
535 try writer.u16v(0);
536 try writer.u16v(0);
537 try writer.u16v(0xFFFF);
538 try writer.u16v(0xFFFF);
539 try writer.u16v(0xFFFF);
540 }
541
542 pub fn queryExtension(buffer: *Request, name: []const u8) RequestError!void {
543 const payload = try paddedRequestBytes(8, name.len);
544 const name_len = try u16Length(name.len);
545 const writer = try Writer.init(buffer, payload);
546 try requestHeader(writer, .query_extension, 0, requestUnits(payload));
547 try writer.u16v(name_len);
548 try writer.u16v(0);
549 try writer.paddedBytes(name);
550 }
551
552 pub fn getKeyboardMapping(buffer: *Request, first_keycode: u8, count: u8) RequestError!void {
553 const writer = try Writer.init(buffer, 8);
554 try requestHeader(writer, .get_keyboard_mapping, 0, 2);
555 try writer.u8v(first_keycode);
556 try writer.u8v(count);
557 try writer.u16v(0);
558 }
559
560 pub const Setup = struct {
561 resource_id_base: u32,
562 resource_id_mask: u32,
563 max_request_units: u32,
564 image_byte_order_msb: bool,
565 min_keycode: u8,
566 max_keycode: u8,
567 root: u32,
568 root_depth: u8,
569 root_visual: u32,
570 root_width: u16,
571 root_height: u16,
572 visual_depth: u8,
573 visual: u32,
574 red_mask: u32,
575 green_mask: u32,
576 blue_mask: u32,
577 };
578
579 pub const SetupError = error{
580 SetupFailed,
581 SetupTruncated,
582 NoUsableScreen,
583 NoUsableVisual,
584 };
585
586 const SetupReader = struct {
587 bytes: []const u8,
588 index: usize = 0,
589
590 fn skip(self: *SetupReader, count: usize) SetupError!void {
591 if (self.index + count > self.bytes.len) return error.SetupTruncated;
592 self.index += count;
593 }
594
595 fn u8v(self: *SetupReader) SetupError!u8 {
596 if (self.index + 1 > self.bytes.len) return error.SetupTruncated;
597 const value = self.bytes[self.index];
598 self.index += 1;
599 return value;
600 }
601
602 fn u16v(self: *SetupReader) SetupError!u16 {
603 if (self.index + 2 > self.bytes.len) return error.SetupTruncated;
604 const value = std.mem.readInt(u16, self.bytes[self.index..][0..2], .little);
605 self.index += 2;
606 return value;
607 }
608
609 fn u32v(self: *SetupReader) SetupError!u32 {
610 if (self.index + 4 > self.bytes.len) return error.SetupTruncated;
611 const value = std.mem.readInt(u32, self.bytes[self.index..][0..4], .little);
612 self.index += 4;
613 return value;
614 }
615 };
616
617 pub fn parseSetup(body: []const u8, screen_index: u32) SetupError!Setup {
618 var reader = SetupReader{ .bytes = body };
619 var setup: Setup = undefined;
620
621 try reader.skip(4);
622 setup.resource_id_base = try reader.u32v();
623 setup.resource_id_mask = try reader.u32v();
624 try reader.skip(4);
625 const vendor_len = try reader.u16v();
626 setup.max_request_units = try reader.u16v();
627 const screen_count = try reader.u8v();
628 const format_count = try reader.u8v();
629 setup.image_byte_order_msb = (try reader.u8v()) != 0;
630 try reader.skip(3);
631 setup.min_keycode = try reader.u8v();
632 setup.max_keycode = try reader.u8v();
633 try reader.skip(4);
634 try reader.skip(vendor_len + pad4(vendor_len));
635 try reader.skip(@as(usize, format_count) * 8);
636
637 if (screen_index >= screen_count) return error.NoUsableScreen;
638
639 var screen: u32 = 0;
640 while (screen < screen_count) : (screen += 1) {
641 const root = try reader.u32v();
642 try reader.skip(16);
643 const width = try reader.u16v();
644 const height = try reader.u16v();
645 try reader.skip(8);
646 const root_visual = try reader.u32v();
647 try reader.skip(2);
648 const root_depth = try reader.u8v();
649 const depth_count = try reader.u8v();
650
651 const selected = screen == screen_index;
652 if (selected) {
653 setup.root = root;
654 setup.root_depth = root_depth;
655 setup.root_visual = root_visual;
656 setup.root_width = width;
657 setup.root_height = height;
658 setup.visual = 0;
659 }
660
661 var depth_index: u8 = 0;
662 while (depth_index < depth_count) : (depth_index += 1) {
663 const depth = try reader.u8v();
664 try reader.skip(1);
665 const visual_count = try reader.u16v();
666 try reader.skip(4);
667
668 var visual_index: u16 = 0;
669 while (visual_index < visual_count) : (visual_index += 1) {
670 const visual_id = try reader.u32v();
671 const class = try reader.u8v();
672 try reader.skip(3);
673 const red_mask = try reader.u32v();
674 const green_mask = try reader.u32v();
675 const blue_mask = try reader.u32v();
676 try reader.skip(4);
677
678 if (selected and class == 4 and (depth == 24 or depth == 32)) {
679 const replace = setup.visual == 0 or visual_id == setup.root_visual;
680 if (replace) {
681 setup.visual = visual_id;
682 setup.visual_depth = depth;
683 setup.red_mask = red_mask;
684 setup.green_mask = green_mask;
685 setup.blue_mask = blue_mask;
686 }
687 }
688 }
689 }
690
691 if (selected and setup.visual == 0) return error.NoUsableVisual;
692 }
693
694 return setup;
695 }
696
697 pub const KeyEvent = struct {
698 keycode: u8,
699 x: i16,
700 y: i16,
701 state: u16,
702 };
703
704 pub const ButtonEvent = struct {
705 button: u8,
706 x: i16,
707 y: i16,
708 state: u16,
709 };
710
711 pub const MotionEvent = struct {
712 x: i16,
713 y: i16,
714 state: u16,
715 };
716
717 pub const ConfigureEvent = struct {
718 window: u32,
719 width: u16,
720 height: u16,
721 };
722
723 pub const ClientMessageEvent = struct {
724 window: u32,
725 message_type: u32,
726 data: [5]u32,
727 };
728
729 pub const SelectionEvent = struct {
730 requestor: u32,
731 selection: u32,
732 target: u32,
733 property: u32,
734 };
735
736 pub const SelectionRequestEvent = struct {
737 time: u32,
738 owner: u32,
739 requestor: u32,
740 selection: u32,
741 target: u32,
742 property: u32,
743 };
744
745 pub const SelectionClearEvent = struct {
746 owner: u32,
747 selection: u32,
748 };
749
750 pub const FocusEvent = struct {
751 focused: bool,
752 mode: u8,
753 };
754
755 pub const Message = union(enum) {
756 key_press: KeyEvent,
757 key_release: KeyEvent,
758 button_press: ButtonEvent,
759 button_release: ButtonEvent,
760 motion: MotionEvent,
761 configure: ConfigureEvent,
762 client_message: ClientMessageEvent,
763 selection_clear: SelectionClearEvent,
764 selection_request: SelectionRequestEvent,
765 selection_notify: SelectionEvent,
766 focus: FocusEvent,
767 enter,
768 leave,
769 expose,
770 destroy_notify,
771 protocol_error: u8,
772 reply,
773 ignored,
774 };
775
776 pub const message_length = 32;
777
778 pub fn decodeMessage(bytes: *const [message_length]u8) Message {
779 const code: EventCode = @fromBackingInt(@intCast(bytes[0] & 0x7F));
780 return switch (code) {
781 .@"error" => .{ .protocol_error = bytes[1] },
782 .reply => .reply,
783 .key_press => .{ .key_press = decodeKey(bytes) },
784 .key_release => .{ .key_release = decodeKey(bytes) },
785 .button_press => .{ .button_press = decodeButton(bytes) },
786 .button_release => .{ .button_release = decodeButton(bytes) },
787 .motion_notify => .{ .motion = .{
788 .x = readI16(bytes, 24),
789 .y = readI16(bytes, 26),
790 .state = readU16(bytes, 28),
791 } },
792 .configure_notify => .{ .configure = .{
793 .window = readU32(bytes, 8),
794 .width = readU16(bytes, 20),
795 .height = readU16(bytes, 22),
796 } },
797 .client_message => blk: {
798 var data: [5]u32 = undefined;
799 for (&data, 0..) |*word, i| {
800 word.* = readU32(bytes, 12 + i * 4);
801 }
802 break :blk .{ .client_message = .{
803 .window = readU32(bytes, 4),
804 .message_type = readU32(bytes, 8),
805 .data = data,
806 } };
807 },
808 .selection_clear => .{ .selection_clear = .{
809 .owner = readU32(bytes, 8),
810 .selection = readU32(bytes, 12),
811 } },
812 .selection_request => .{ .selection_request = .{
813 .time = readU32(bytes, 4),
814 .owner = readU32(bytes, 8),
815 .requestor = readU32(bytes, 12),
816 .selection = readU32(bytes, 16),
817 .target = readU32(bytes, 20),
818 .property = readU32(bytes, 24),
819 } },
820 .selection_notify => .{ .selection_notify = .{
821 .requestor = readU32(bytes, 8),
822 .selection = readU32(bytes, 12),
823 .target = readU32(bytes, 16),
824 .property = readU32(bytes, 20),
825 } },
826 .focus_in => .{ .focus = .{ .focused = true, .mode = bytes[8] } },
827 .focus_out => .{ .focus = .{ .focused = false, .mode = bytes[8] } },
828 .enter_notify => .enter,
829 .leave_notify => .leave,
830 .expose => .expose,
831 .destroy_notify => .destroy_notify,
832 else => .ignored,
833 };
834 }
835
836 fn decodeKey(bytes: *const [message_length]u8) KeyEvent {
837 return .{
838 .keycode = bytes[1],
839 .x = readI16(bytes, 24),
840 .y = readI16(bytes, 26),
841 .state = readU16(bytes, 28),
842 };
843 }
844
845 fn decodeButton(bytes: *const [message_length]u8) ButtonEvent {
846 return .{
847 .button = bytes[1],
848 .x = readI16(bytes, 24),
849 .y = readI16(bytes, 26),
850 .state = readU16(bytes, 28),
851 };
852 }
853
854 fn readU16(bytes: []const u8, offset: usize) u16 {
855 return std.mem.readInt(u16, bytes[offset..][0..2], .little);
856 }
857
858 fn readI16(bytes: []const u8, offset: usize) i16 {
859 return @bitCast(readU16(bytes, offset));
860 }
861
862 fn readU32(bytes: []const u8, offset: usize) u32 {
863 return std.mem.readInt(u32, bytes[offset..][0..4], .little);
864 }
865
866 test "pad4 rounds byte lengths to protocol units" {
867 try std.testing.expectEqual(@as(usize, 0), pad4(0));
868 try std.testing.expectEqual(@as(usize, 3), pad4(1));
869 try std.testing.expectEqual(@as(usize, 2), pad4(2));
870 try std.testing.expectEqual(@as(usize, 1), pad4(3));
871 try std.testing.expectEqual(@as(usize, 0), pad4(4));
872 }
873
874 test "setup request frames auth strings with padding" {
875 var storage: [setup_request_byte_count]u8 = undefined;
876 var request = Request.init(&storage, storage.len);
877
878 try setupRequest(&request, "MIT-MAGIC-COOKIE-1", "0123456789abcdef");
879 const bytes = request.bytes();
880 try std.testing.expectEqual(@as(usize, 12 + 20 + 16), bytes.len);
881 try std.testing.expectEqual(@as(u8, 0x6C), bytes[0]);
882 try std.testing.expectEqual(@as(u16, 11), std.mem.readInt(u16, bytes[2..4], .little));
883 try std.testing.expectEqualStrings("MIT-MAGIC-COOKIE-1", bytes[12..30]);
884 try std.testing.expectEqual(@as(u8, 0), bytes[30]);
885 try std.testing.expectEqual(@as(u8, 0), bytes[31]);
886 }
887
888 test "requests encode lengths in four byte units" {
889 var storage: [64]u8 = undefined;
890 var request = Request.init(&storage, storage.len);
891
892 try internAtom(&request, "WM_PROTOCOLS");
893 try std.testing.expectEqual(@as(usize, 20), request.bytes().len);
894 try std.testing.expectEqual(@as(u8, @backingInt(Opcode.intern_atom)), request.bytes()[0]);
895 try std.testing.expectEqual(@as(u16, 5), std.mem.readInt(u16, request.bytes()[2..4], .little));
896 try std.testing.expectEqual(@as(u16, 12), std.mem.readInt(u16, request.bytes()[4..6], .little));
897
898 request.reset();
899 try createWindow(&request, .{
900 .window = 2,
901 .parent = 1,
902 .depth = 24,
903 .visual = 3,
904 .width = 640,
905 .height = 480,
906 .events = event_mask.key_press,
907 });
908 try std.testing.expectEqual(@as(usize, 40), request.bytes().len);
909 try std.testing.expectEqual(@as(u16, 10), std.mem.readInt(u16, request.bytes()[2..4], .little));
910 }
911
912 test "clipboard owner and notify requests retain selection protocol fields" {
913 var storage: [64]u8 = undefined;
914 var request = Request.init(&storage, storage.len);
915
916 try setSelectionOwner(&request, 0x1122_3344, 0x5566_7788);
917 var bytes = request.bytes();
918 try std.testing.expectEqual(@as(usize, 16), bytes.len);
919 try std.testing.expectEqual(
920 @as(u8, @backingInt(Opcode.set_selection_owner)),
921 bytes[0],
922 );
923 try std.testing.expectEqual(@as(u16, 4), readU16(bytes, 2));
924 try std.testing.expectEqual(@as(u32, 0x1122_3344), readU32(bytes, 4));
925 try std.testing.expectEqual(@as(u32, 0x5566_7788), readU32(bytes, 8));
926 try std.testing.expectEqual(current_time, readU32(bytes, 12));
927
928 request.reset();
929 try sendSelectionNotify(&request, .{
930 .time = 0x0102_0304,
931 .requestor = 0x1112_1314,
932 .selection = 0x2122_2324,
933 .target = 0x3132_3334,
934 .property = 0x4142_4344,
935 });
936 bytes = request.bytes();
937 try std.testing.expectEqual(@as(usize, 44), bytes.len);
938 try std.testing.expectEqual(@as(u8, @backingInt(Opcode.send_event)), bytes[0]);
939 try std.testing.expectEqual(@as(u32, 0x1112_1314), readU32(bytes, 4));
940 try std.testing.expectEqual(
941 @as(u8, @backingInt(EventCode.selection_notify)),
942 bytes[12],
943 );
944 try std.testing.expectEqual(@as(u32, 0x0102_0304), readU32(bytes, 16));
945 try std.testing.expectEqual(@as(u32, 0x1112_1314), readU32(bytes, 20));
946 try std.testing.expectEqual(@as(u32, 0x2122_2324), readU32(bytes, 24));
947 try std.testing.expectEqual(@as(u32, 0x3132_3334), readU32(bytes, 28));
948 try std.testing.expectEqual(@as(u32, 0x4142_4344), readU32(bytes, 32));
949 }
950
951 test "minimum connection request storage admits the largest fixed batch" {
952 var storage: [minimum_request_byte_count]u8 = undefined;
953 var request = Request.init(&storage, storage.len);
954 try createWindow(&request, .{
955 .window = 2,
956 .parent = 1,
957 .depth = 24,
958 .visual = 3,
959 .width = 640,
960 .height = 480,
961 .events = event_mask.key_press,
962 });
963 try createGc(&request, 4, 2);
964 try std.testing.expectEqual(minimum_request_byte_count, request.bytes().len);
965 const expected = storage;
966 try std.testing.expectError(
967 error.RequestCapacityExceeded,
968 windowRequest(&request, .map_window, 2),
969 );
970 try std.testing.expectEqualSlices(u8, &expected, request.bytes());
971 }
972
973 test "put image rows pads pixel payloads" {
974 var storage: [32]u8 = undefined;
975 var buffer = Request.init(&storage, storage.len);
976
977 const pixels = @as([8]u8, @splat(0xAB));
978 try putImageRows(&buffer, .{
979 .drawable = 5,
980 .gc = 6,
981 .width = 2,
982 .row_count = 1,
983 .dst_x = 4,
984 .dst_y = 3,
985 .depth = 24,
986 .pixels = &pixels,
987 });
988 const bytes = buffer.bytes();
989 try std.testing.expectEqual(@as(usize, 32), bytes.len);
990 try std.testing.expectEqual(@as(u16, 8), std.mem.readInt(u16, bytes[2..4], .little));
991 try std.testing.expectEqual(@as(u8, format_zpixmap), bytes[1]);
992 try std.testing.expectEqual(@as(i16, 4), std.mem.readInt(i16, bytes[16..18], .little));
993 try std.testing.expectEqual(@as(i16, 3), std.mem.readInt(i16, bytes[18..20], .little));
994 }
995
996 test "request capacity rejects before mutation and saturates status" {
997 var storage: [20]u8 = undefined;
998 var request = Request.init(&storage, storage.len);
999 try internAtom(&request, "WM_PROTOCOLS");
1000 const expected = storage;
1001 try std.testing.expectError(
1002 error.RequestCapacityExceeded,
1003 windowRequest(&request, .map_window, 1),
1004 );
1005 try std.testing.expectEqualSlices(u8, &expected, request.bytes());
1006 try std.testing.expectEqual(@as(u64, 1), request.capacityRejectionCount());
1007
1008 request.reset();
1009 try std.testing.expectError(
1010 error.RequestCapacityExceeded,
1011 createWindow(&request, .{
1012 .window = 2,
1013 .parent = 1,
1014 .depth = 24,
1015 .visual = 3,
1016 .width = 640,
1017 .height = 480,
1018 .events = event_mask.key_press,
1019 }),
1020 );
1021 try std.testing.expectEqual(@as(usize, 0), request.bytes().len);
1022
1023 request.rejection_count = std.math.maxInt(u64);
1024 try std.testing.expectError(
1025 error.RequestCapacityExceeded,
1026 createWindow(&request, .{
1027 .window = 2,
1028 .parent = 1,
1029 .depth = 24,
1030 .visual = 3,
1031 .width = 640,
1032 .height = 480,
1033 .events = event_mask.key_press,
1034 }),
1035 );
1036 try std.testing.expectEqual(std.math.maxInt(u64), request.capacityRejectionCount());
1037 try std.testing.expectError(
1038 error.RequestLengthOverflow,
1039 u16Length(@as(usize, std.math.maxInt(u16)) + 1),
1040 );
1041 try std.testing.expectError(
1042 error.RequestLengthOverflow,
1043 paddedRequestBytes(std.math.maxInt(usize), 1),
1044 );
1045 }
1046
1047 test "decode message reads key and configure events" {
1048 var raw = @as([message_length]u8, @splat(0));
1049 raw[0] = @backingInt(EventCode.key_press);
1050 raw[1] = 38;
1051 std.mem.writeInt(u16, raw[24..26], 100, .little);
1052 std.mem.writeInt(u16, raw[26..28], 200, .little);
1053 std.mem.writeInt(u16, raw[28..30], state_mask.shift, .little);
1054
1055 const key = decodeMessage(&raw);
1056 try std.testing.expectEqual(@as(u8, 38), key.key_press.keycode);
1057 try std.testing.expectEqual(@as(i16, 100), key.key_press.x);
1058 try std.testing.expectEqual(state_mask.shift, key.key_press.state);
1059
1060 raw = @as([message_length]u8, @splat(0));
1061 raw[0] = @backingInt(EventCode.configure_notify);
1062 std.mem.writeInt(u32, raw[8..12], 77, .little);
1063 std.mem.writeInt(u16, raw[20..22], 1280, .little);
1064 std.mem.writeInt(u16, raw[22..24], 800, .little);
1065
1066 const configure = decodeMessage(&raw);
1067 try std.testing.expectEqual(@as(u32, 77), configure.configure.window);
1068 try std.testing.expectEqual(@as(u16, 1280), configure.configure.width);
1069 try std.testing.expectEqual(@as(u16, 800), configure.configure.height);
1070 }
1071
1072 test "decode message retains clipboard clear and request fields" {
1073 var raw = @as([message_length]u8, @splat(0));
1074 raw[0] = @backingInt(EventCode.selection_clear);
1075 std.mem.writeInt(u32, raw[8..12], 0x0102_0304, .little);
1076 std.mem.writeInt(u32, raw[12..16], 0x1112_1314, .little);
1077 const clear = decodeMessage(&raw).selection_clear;
1078 try std.testing.expectEqual(@as(u32, 0x0102_0304), clear.owner);
1079 try std.testing.expectEqual(@as(u32, 0x1112_1314), clear.selection);
1080
1081 raw = @as([message_length]u8, @splat(0));
1082 raw[0] = @backingInt(EventCode.selection_request);
1083 std.mem.writeInt(u32, raw[4..8], 0x0102_0304, .little);
1084 std.mem.writeInt(u32, raw[8..12], 0x1112_1314, .little);
1085 std.mem.writeInt(u32, raw[12..16], 0x2122_2324, .little);
1086 std.mem.writeInt(u32, raw[16..20], 0x3132_3334, .little);
1087 std.mem.writeInt(u32, raw[20..24], 0x4142_4344, .little);
1088 std.mem.writeInt(u32, raw[24..28], 0x5152_5354, .little);
1089 const selection = decodeMessage(&raw).selection_request;
1090 try std.testing.expectEqual(@as(u32, 0x0102_0304), selection.time);
1091 try std.testing.expectEqual(@as(u32, 0x1112_1314), selection.owner);
1092 try std.testing.expectEqual(@as(u32, 0x2122_2324), selection.requestor);
1093 try std.testing.expectEqual(@as(u32, 0x3132_3334), selection.selection);
1094 try std.testing.expectEqual(@as(u32, 0x4142_4344), selection.target);
1095 try std.testing.expectEqual(@as(u32, 0x5152_5354), selection.property);
1096 }
1097
1098 test "decode message surfaces errors and unknown codes" {
1099 var raw = @as([message_length]u8, @splat(0));
1100 raw[0] = @backingInt(EventCode.@"error");
1101 raw[1] = 9;
1102 try std.testing.expectEqual(@as(u8, 9), decodeMessage(&raw).protocol_error);
1103
1104 raw[0] = 99;
1105 try std.testing.expectEqual(Message.ignored, decodeMessage(&raw));
1106 }