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 }