From 89abd83302928e984e5a9fa14c5232e14ea50226 Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Sun, 7 Sep 2025 06:22:53 -0600 Subject: [PATCH] RENDER extension support with example --- build.zig | 1 + examples/renderext.zig | 509 +++++++++++++++++++++++++++++++++++++ src/render.zig | 556 ++++++++++++++++++++++++++++++++++++----- src/x.zig | 4 + 4 files changed, 1003 insertions(+), 67 deletions(-) create mode 100644 examples/renderext.zig diff --git a/build.zig b/build.zig index 43363bb..75bd9f1 100644 --- a/build.zig +++ b/build.zig @@ -11,6 +11,7 @@ const examples = [_][]const u8{ "keys", "input", "dbe", + "renderext", }; pub fn build(b: *std.Build) void { diff --git a/examples/renderext.zig b/examples/renderext.zig new file mode 100644 index 0000000..68aeaf3 --- /dev/null +++ b/examples/renderext.zig @@ -0,0 +1,509 @@ +//! An example of using the "Rendering" (RENDER) +const std = @import("std"); +const x11 = @import("x11"); +const common = @import("common.zig"); + +var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); +const allocator = arena.allocator(); + +const window_width = 800; +const window_height = 600; + +const Ids = struct { + base: x11.ResourceBase, + pub fn window(self: Ids) x11.Window { + return self.base.add(0).window(); + } + pub fn picture(self: Ids) x11.render.Picture { + return self.base.add(1).picture(); + } + pub fn glyphset(self: Ids) x11.render.GlyphSet { + return self.base.add(2).glyph_set(); + } + pub fn solid_picture(self: Ids) x11.render.Picture { + return self.base.add(3).picture(); + } +}; + +fn createSimpleGlyphData(width: u16, height: u16) []u8 { + const size = width * height; + const data = allocator.alloc(u8, size) catch unreachable; + + // Create a simple filled rectangle glyph + for (data) |*byte| { + byte.* = 0xFF; + } + + return data; +} + +pub fn main() !u8 { + try x11.wsaStartup(); + const conn = try common.connect(allocator); + defer std.posix.shutdown(conn.sock, .both) catch {}; + + const screen = blk: { + const fixed = conn.setup.fixed(); + inline for (@typeInfo(@TypeOf(fixed.*)).@"struct".fields) |field| { + std.log.debug("{s}: {any}", .{ field.name, @field(fixed, field.name) }); + } + std.log.debug("vendor: {s}", .{try conn.setup.getVendorSlice(fixed.vendor_len)}); + const format_list_offset = x11.ConnectSetup.getFormatListOffset(fixed.vendor_len); + const format_list_limit = x11.ConnectSetup.getFormatListLimit(format_list_offset, fixed.format_count); + std.log.debug("fmt list off={} limit={}", .{ format_list_offset, format_list_limit }); + const formats = try conn.setup.getFormatList(format_list_offset, format_list_limit); + for (formats, 0..) |format, i| { + std.log.debug("format[{}] depth={:3} bpp={:3} scanpad={:3}", .{ i, format.depth, format.bits_per_pixel, format.scanline_pad }); + } + const screen = conn.setup.getFirstScreenPtr(format_list_limit); + break :blk screen; + }; + + var sequence: u16 = 0; + const ids: Ids = .{ .base = conn.setup.fixed().resource_id_base }; + + // Setup read buffer for receiving messages + const double_buf = try x11.DoubleBuffer.init( + std.mem.alignForward(usize, 4096, std.heap.pageSize()), + .{ .memfd_name = "XRenderExample" }, + ); + var buf = double_buf.contiguousReadBuffer(); + + // Query for XRender extension + const maybe_render_ext = try common.getExtensionInfo(conn.sock, &sequence, &buf, x11.render.name.nativeSlice()); + if (maybe_render_ext == null) { + std.log.err("RENDER extension not available", .{}); + return 1; + } + const render_ext_opcode = maybe_render_ext.?.opcode; + std.log.info("RENDER opcode: {}", .{render_ext_opcode}); + + // From the specification: + // > The client must negotiate the version of the extension before executing + // > extension requests. Behavior of the server is undefined otherwise. + { + var msg_buf: [x11.render.query_version.len]u8 = undefined; + x11.render.query_version.serialize(&msg_buf, render_ext_opcode, .{ + .major_version = 0, + .minor_version = 11, + }); + try conn.sendOne(&sequence, &msg_buf); + } + { + const reader = common.SocketReader{ .context = conn.sock }; + _ = try x11.readOneMsg(reader, @alignCast(buf.nextReadBuffer())); + switch (x11.serverMsgTaggedUnion(@alignCast(buf.double_buffer_ptr))) { + .reply => |msg_reply| { + const reply: *x11.render.query_version.Reply = @ptrCast(msg_reply); + std.log.info("RENDER version: {}.{}", .{ reply.major_version, reply.minor_version }); + if (reply.major_version != 0) { + std.log.err("unsupported RENDER major version {}", .{reply.major_version}); + return 1; + } + if (reply.minor_version < 11) { + std.log.err("unsupported RENDER minor version {}", .{reply.minor_version}); + return 1; + } + }, + else => |msg| { + std.log.err("expected a reply but got {}", .{msg}); + return 1; + }, + } + } + + // Query picture formats to find a suitable format for glyphs + { + var msg_buf: [x11.render.query_pict_formats.len]u8 = undefined; + x11.render.query_pict_formats.serialize(&msg_buf, render_ext_opcode); + try conn.sendOne(&sequence, &msg_buf); + } + var glyph_format: x11.render.PictureFormat = undefined; + var window_format: x11.render.PictureFormat = undefined; + _ = &glyph_format; + _ = &window_format; + { + const reader = common.SocketReader{ .context = conn.sock }; + const msg_len = try x11.readOneMsg(reader, @alignCast(buf.nextReadBuffer())); + switch (x11.serverMsgTaggedUnion(@alignCast(buf.double_buffer_ptr))) { + .reply => |msg_reply| { + const reply: *x11.render.query_pict_formats.Reply = @ptrCast(msg_reply); + std.log.info("reply (len={}) {}", .{ msg_len, reply }); + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // TODO: verify the counts add up to the message length + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + // The data after the fixed reply header contains: + // 1. PictFormInfo array (count = reply.num_formats) + // 2. PictScreen array (count = reply.num_screens) + // 3. Subpixel array (count = reply.num_subpixel) + + // Get pointer to the data after the fixed reply + const data_ptr = @as([*]align(4) u8, @ptrCast(msg_reply)) + @sizeOf(x11.render.query_pict_formats.Reply); + + // Parse the PictFormInfo array + const pict_format_infos = @as([*]align(4) x11.render.PictureFormatInfo, @ptrCast(data_ptr))[0..reply.num_formats]; + + // Find suitable formats + // Look for A8 format (8-bit alpha, typically used for glyphs) + for (pict_format_infos, 0..) |format_info, index| { + std.log.info("PictFormat {}: id={} depth={} red(shift={},mask=0x{x}) green(shift={},mask=0x{x}) blue(shift={},mask=0x{x}) alpha(shift={},mask=0x{x})", .{ + index, + @intFromEnum(format_info.id), + format_info.depth, + format_info.direct.red.shift, + format_info.direct.red.mask, + format_info.direct.green.shift, + format_info.direct.green.mask, + format_info.direct.blue.shift, + format_info.direct.blue.mask, + format_info.direct.alpha.shift, + format_info.direct.alpha.mask, + }); + if (format_info.type == .direct and + format_info.depth == 8 and + format_info.direct.alpha.mask == 0xFF and + format_info.direct.red.mask == 0 and + format_info.direct.green.mask == 0 and + format_info.direct.blue.mask == 0) + { + glyph_format = format_info.id; + std.log.info("Found A8 glyph format: {}", .{glyph_format}); + break; + } + } + + // Find format matching window depth (typically 24 or 32 bit ARGB) + for (pict_format_infos) |format_info| { + if (format_info.type == .direct and + format_info.depth == screen.root_depth) + { + window_format = format_info.id; + std.log.info("Found window format (depth {}): {}", .{ screen.root_depth, window_format }); + break; + } + } + + // Alternative: if you need a specific ARGB32 format + for (pict_format_infos) |format_info| { + if (format_info.type == .direct and + format_info.depth == 32 and + format_info.direct.alpha.mask == 0xFF and + format_info.direct.alpha.shift == 24 and + format_info.direct.red.mask == 0xFF and + format_info.direct.red.shift == 16 and + format_info.direct.green.mask == 0xFF and + format_info.direct.green.shift == 8 and + format_info.direct.blue.mask == 0xFF and + format_info.direct.blue.shift == 0) + { + // This would be a standard ARGB32 format + // You might want to use this instead of window_format + std.log.info("Found ARGB32 format: {}", .{format_info.id}); + } + } + }, + else => |msg| { + std.log.err("expected a reply but got {}", .{msg}); + return 1; + }, + } + + // _ = @as(*x11.render.query_pict_formats.Reply, @ptrCast(@alignCast(buf.double_buffer_ptr))); + + // // For simplicity, we'll use hardcoded format IDs + // // In a real application, you'd parse the format list + // glyph_format = @enumFromInt(0x26); // Typically A8 format + // window_format = @enumFromInt(0x24); // Typically ARGB32 format + } + + if (true) @panic("todo"); + + // Create window + { + var msg_buf: [x11.create_window.max_len]u8 = undefined; + const len = x11.create_window.serialize(&msg_buf, .{ + .window_id = ids.window(), + .parent_window_id = screen.root, + .depth = 0, + .x = 0, + .y = 0, + .width = window_width, + .height = window_height, + .border_width = 0, + .class = .input_output, + .visual_id = screen.root_visual, + }, .{ + .bg_pixel = 0xFFFFFFFF, + .event_mask = .{ .exposure = 1 }, + }); + try conn.sendOne(&sequence, msg_buf[0..len]); + } + + { + var msg_buf: [x11.map_window.len]u8 = undefined; + x11.map_window.serialize(&msg_buf, ids.window()); + try conn.sendOne(&sequence, &msg_buf); + } + + // Create picture for window + { + var msg_buf: [256]u8 = undefined; + const len = x11.render.create_picture.serialize(&msg_buf, render_ext_opcode, .{ + .picture = ids.picture(), + .drawable = ids.window().drawable(), + .format = window_format, + }, .{}); + try conn.sendOne(&sequence, msg_buf[0..len]); + } + + // Create solid color picture for text + { + var msg_buf: [x11.render.create_solid_fill.len]u8 = undefined; + x11.render.create_solid_fill.serialize(&msg_buf, render_ext_opcode, .{ + .picture = ids.solid_picture(), + .color = .{ + .red = 0, + .green = 0, + .blue = 0, + .alpha = 0xFFFF, + }, + }); + try conn.sendOne(&sequence, &msg_buf); + } + + // Create glyph set + { + var msg_buf: [x11.render.create_glyph_set.len]u8 = undefined; + x11.render.create_glyph_set.serialize(&msg_buf, render_ext_opcode, .{ + .gsid = ids.glyphset(), + .format = glyph_format, + }); + try conn.sendOne(&sequence, &msg_buf); + } + + // Add some glyphs to the glyph set + { + const glyph_width = 16; + const glyph_height = 20; + + // Create glyphs for letters A-Z (simplified) + var glyphs: [26]x11.render.Glyph = undefined; + var glyph_infos: [26]x11.render.GlyphInfo = undefined; + + for (0..26) |i| { + glyphs[i] = @intCast(65 + i); // ASCII A-Z + glyph_infos[i] = .{ + .width = glyph_width, + .height = glyph_height, + .x = 0, + .y = @intCast(glyph_height - 4), + .x_off = @intCast(glyph_width + 2), + .y_off = 0, + }; + } + + // Create simple glyph data (all pixels on for demonstration) + const data_per_glyph = glyph_width * glyph_height; + const total_data_size = data_per_glyph * 26; + var glyph_data = try allocator.alloc(u8, total_data_size); + defer allocator.free(glyph_data); + + // Fill with simple pattern + for (0..26) |i| { + const offset = i * data_per_glyph; + for (0..data_per_glyph) |j| { + // Create different patterns for different letters + if ((i + j) % 3 == 0) { + glyph_data[offset + j] = 0xFF; + } else if ((i + j) % 3 == 1) { + glyph_data[offset + j] = 0xAA; + } else { + glyph_data[offset + j] = 0x55; + } + } + } + + const msg_len = x11.render.add_glyphs.getLen(26, total_data_size); + const msg_buf = try allocator.alloc(u8, msg_len); + defer allocator.free(msg_buf); + + x11.render.add_glyphs.serialize(msg_buf.ptr, render_ext_opcode, .{ + .gsid = ids.glyphset(), + .glyphs = &glyphs, + .glyph_infos = &glyph_infos, + .data = glyph_data, + }); + try conn.sendOne(&sequence, msg_buf); + } + + // Clear the window background with a filled rectangle + { + const rect = [_]x11.Rectangle{ + .{ .x = 0, .y = 0, .width = window_width, .height = window_height }, + }; + + const msg_len = x11.render.fill_rectangles.getLen(1); + var msg_buf: [256]u8 = undefined; + x11.render.fill_rectangles.serialize(&msg_buf, render_ext_opcode, .{ + .op = .src, + .dst = ids.picture(), + .color = .{ + .red = 0xFFFF, + .green = 0xFFFF, + .blue = 0xFFFF, + .alpha = 0xFFFF, + }, + }, &rect); + try conn.sendOne(&sequence, msg_buf[0..msg_len]); + } + + // Render text "HELLO WORLD" using composite_glyphs8 + { + const text = "HELLO WORLD"; + var glyphelt_data: [256]u8 = undefined; + var offset: usize = 0; + + // Number of glyphs + glyphelt_data[offset] = @intCast(text.len); + offset += 1; + + // Padding + glyphelt_data[offset] = 0; + offset += 1; + glyphelt_data[offset] = 0; + offset += 1; + glyphelt_data[offset] = 0; + offset += 1; + + // Delta x,y from origin + x11.writeIntNative(i16, glyphelt_data[offset..].ptr, 50); + offset += 2; + x11.writeIntNative(i16, glyphelt_data[offset..].ptr, 100); + offset += 2; + + // Glyph indices + for (text) |char| { + glyphelt_data[offset] = char; + offset += 1; + } + + const msg_len = x11.render.composite_glyphs8.getLen(@intCast(offset)); + var msg_buf: [256]u8 = undefined; + x11.render.composite_glyphs8.serialize(&msg_buf, render_ext_opcode, .{ + .op = .over, + .src = ids.solid_picture(), + .dst = ids.picture(), + .mask_format = .none, + .gsid = ids.glyphset(), + .src_x = 0, + .src_y = 0, + }, glyphelt_data[0..offset]); + try conn.sendOne(&sequence, msg_buf[0..msg_len]); + } + + while (true) { + var event_buf: [1024]u8 = undefined; + const recv_len = x11.readSock(conn.sock, &event_buf, 0) catch |err| switch (err) { + error.WouldBlock => continue, + else => |e| return e, + }; + + if (recv_len == 0) continue; + const event_type = event_buf[0] & 0x7f; + + switch (event_type) { + @intFromEnum(x11.EventCode.key_press) => { + std.log.info("Key pressed, exiting...", .{}); + break; + }, + @intFromEnum(x11.EventCode.expose) => { + // Re-render on expose + const rect = [_]x11.Rectangle{ + .{ .x = 0, .y = 0, .width = window_width, .height = window_height }, + }; + + // Clear background + const msg_len = x11.render.fill_rectangles.getLen(1); + var msg_buf: [256]u8 = undefined; + x11.render.fill_rectangles.serialize(&msg_buf, render_ext_opcode, .{ + .op = .src, + .dst = ids.picture(), + .color = .{ + .red = 0xFFFF, + .green = 0xFFFF, + .blue = 0xFFFF, + .alpha = 0xFFFF, + }, + }, &rect); + try conn.sendOne(&sequence, msg_buf[0..msg_len]); + + // Render text again + const text = "HELLO WORLD"; + var glyphelt_data: [256]u8 = undefined; + var offset: usize = 0; + + glyphelt_data[offset] = @intCast(text.len); + offset += 1; + glyphelt_data[offset] = 0; + offset += 1; + glyphelt_data[offset] = 0; + offset += 1; + glyphelt_data[offset] = 0; + offset += 1; + + x11.writeIntNative(i16, glyphelt_data[offset..].ptr, 50); + offset += 2; + x11.writeIntNative(i16, glyphelt_data[offset..].ptr, 100); + offset += 2; + + for (text) |char| { + glyphelt_data[offset] = char; + offset += 1; + } + + const render_msg_len = x11.render.composite_glyphs8.getLen(@intCast(offset)); + var render_msg_buf: [256]u8 = undefined; + x11.render.composite_glyphs8.serialize(&render_msg_buf, render_ext_opcode, .{ + .op = .over, + .src = ids.solid_picture(), + .dst = ids.picture(), + .mask_format = .none, + .gsid = ids.glyphset(), + .src_x = 0, + .src_y = 0, + }, glyphelt_data[0..offset]); + try conn.sendOne(&sequence, render_msg_buf[0..render_msg_len]); + }, + else => {}, + } + } + + // Cleanup + { + var msg_buf: [x11.render.free_glyph_set.len]u8 = undefined; + x11.render.free_glyph_set.serialize(&msg_buf, render_ext_opcode, .{ + .gsid = ids.glyphset(), + }); + try conn.sendOne(&sequence, &msg_buf); + } + + { + var msg_buf: [x11.render.free_picture.len]u8 = undefined; + x11.render.free_picture.serialize(&msg_buf, render_ext_opcode, .{ + .picture = ids.picture(), + }); + try conn.sendOne(&sequence, &msg_buf); + } + + { + var msg_buf: [x11.render.free_picture.len]u8 = undefined; + x11.render.free_picture.serialize(&msg_buf, render_ext_opcode, .{ + .picture = ids.solid_picture(), + }); + try conn.sendOne(&sequence, &msg_buf); + } + + return 0; +} diff --git a/src/render.zig b/src/render.zig index fcb80c8..cd48b52 100644 --- a/src/render.zig +++ b/src/render.zig @@ -3,6 +3,8 @@ const std = @import("std"); const x11 = @import("x.zig"); +pub const name = x11.Slice(u16, [*]const u8).initComptime("RENDER"); + pub const Picture = enum(u32) { none = 0, _, @@ -41,49 +43,42 @@ pub const PictureFormat = enum(u32) { } }; +pub const GlyphSet = enum(u32) { + none = 0, + _, +}; + pub const ExtOpcode = enum(u8) { query_version = 0, query_pict_formats = 1, - // opcode 3 reserved for QueryDithers + query_pict_index_values = 2, create_picture = 4, - // change_picture = 5, - // set_picture_clip_rectangles = 6, + change_picture = 5, + set_picture_clip_rectangles = 6, free_picture = 7, composite = 8, - // opcode 9 reserved for Scale - // trapezoids = 10, - // triangles = 11, - // tri_strip = 12, - // tri_fan = 13, - // opcode 14 reserved for ColorTrapezoids - // opcode 15 reserved for ColorTriangles - // opcode 16 reserved for Transform - // create_glyph_set = 17, - // reference_glyph_set = 18, - // free_glyph_set = 19, - // add_glyphs = 20, - // opcode 21 reserved for AddGlyphsFromPicture - // free_glyphs = 22, - // composite_glyphs_8 = 23, - // composite_glyphs_16 = 24, - // composite_glyphs_32 = 25, - // new in version 0.1 - // fill_rectangles = 26, - // new in version 0.5 - // create_cursor = 27, - // new in version 0.6 - // set_picture_transform = 28, - // query_filters = 29, - // set_picture_filter = 30, - // new in version 0.8 - // create_anim_cursor = 31, - // new in version 0.9 - // add_traps = 32, - // new in version 0.10 - // create_solid_fill = 33, - // create_linear_gradient = 34, - // create_radial_gradient = 35, - // create_conical_gradient = 36, + trapezoids = 10, + triangles = 11, + tri_strip = 12, + tri_fan = 13, + create_glyph_set = 17, + reference_glyph_set = 18, + free_glyph_set = 19, + add_glyphs = 20, + add_glyphs_from_picture = 21, + free_glyphs = 22, + composite_glyphs8 = 23, + composite_glyphs16 = 24, + composite_glyphs32 = 25, + fill_rectangles = 26, + create_cursor = 27, + set_picture_transform = 28, + query_filters = 29, + set_picture_filter = 30, + create_solid_fill = 33, + create_linear_gradient = 34, + create_radial_gradient = 35, + create_conical_gradient = 36, }; pub const ErrorCode = enum(u8) { @@ -95,6 +90,10 @@ pub const ErrorCode = enum(u8) { _, // allow unknown errors }; +pub const Glyph = u32; + +pub const Fixed = i32; + // Disjoint* and Conjoint* are new in version 0.2 // PDF blend modes are new in version 0.11 pub const PictureOperation = enum(u8) { @@ -155,6 +154,56 @@ pub const PictureOperation = enum(u8) { hsl_saturation = 60, hsl_color = 61, hsl_luminosity = 62, + _, +}; + +pub const SubPixel = enum(u8) { + unknown = 0, + horizontal_rgb = 1, + horizontal_bgr = 2, + vertical_rgb = 3, + vertical_bgr = 4, + none = 5, + _, +}; + +pub const Color = extern struct { + red: u16, + green: u16, + blue: u16, + alpha: u16, +}; + +pub const ChannelInfo = extern struct { + shift: u16, + mask: u16, +}; + +pub const GlyphInfo = extern struct { + width: u16, + height: u16, + x: i16, + y: i16, + x_off: i16, + y_off: i16, +}; + +pub const GlyphElt8 = struct { + dx: i16, + dy: i16, + glyphs: []const u8, +}; + +pub const GlyphElt16 = struct { + dx: i16, + dy: i16, + glyphs: []const u16, +}; + +pub const GlyphElt32 = struct { + dx: i16, + dy: i16, + glyphs: []const u32, }; pub const query_version = struct { @@ -183,7 +232,7 @@ pub const query_version = struct { word_len: u32, major_version: u32, minor_version: u32, - reserved: [15]u8, + reserved: [16]u8, }; comptime { std.debug.assert(@sizeOf(Reply) == 32); @@ -193,26 +242,23 @@ pub const query_version = struct { pub const PictureType = enum(u8) { indexed = 0, direct = 1, + _, }; pub const DirectFormat = extern struct { - red_shift: u16, - red_mask: u16, - green_shift: u16, - green_mask: u16, - blue_shift: u16, - blue_mask: u16, - alpha_shift: u16, - alpha_mask: u16, + red: ChannelInfo, + green: ChannelInfo, + blue: ChannelInfo, + alpha: ChannelInfo, }; pub const PictureFormatInfo = extern struct { - picture_format_id: PictureFormat, + id: PictureFormat, type: PictureType, depth: u8, _: [2]u8, direct: DirectFormat, - colormap: u32, + colormap: x11.ColorMap, }; comptime { if (@sizeOf(PictureFormatInfo) != 28) @compileError("PictureFormatInfo size is wrong"); @@ -226,9 +272,9 @@ pub const query_pict_formats = struct { pub fn serialize(buf: [*]u8, ext_opcode: u8) void { buf[0] = ext_opcode; buf[1] = @intFromEnum(ExtOpcode.query_pict_formats); + std.debug.assert(len & 0x3 == 0); x11.writeIntNative(u16, buf + 2, len >> 2); } - pub const Reply = extern struct { response_type: x11.ReplyKind, unused_pad: u8, @@ -334,23 +380,38 @@ pub const create_picture = struct { } x11.writeIntNative(u32, buf + non_option_len - 4, option_mask); x11.writeIntNative(u16, buf + 2, request_len >> 2); + std.debug.assert(request_len & 0x3 == 0); return request_len; } }; -/// Combine the src and destination pictures with the specified operation. -/// -/// For example, if you want to copy the src picture to the destination picture, you -/// would use `PictureOperation.over`. +pub const free_picture = struct { + pub const len = + 2 // extension and command opcodes + + 2 // request length + + 4 // picture + ; + pub const Args = struct { + picture: Picture, + }; + pub fn serialize(buf: [*]u8, ext_opcode: u8, args: Args) void { + buf[0] = ext_opcode; + buf[1] = @intFromEnum(ExtOpcode.free_picture); + std.debug.assert(len & 0x3 == 0); + x11.writeIntNative(u16, buf + 2, len >> 2); + x11.writeIntNative(u32, buf + 4, @intFromEnum(args.picture)); + } +}; + pub const composite = struct { pub const len = 2 // extension and command opcodes + 2 // request length - + 1 // picture operation - + 3 // padding - + 4 // src_picture_id - + 4 // mask_picture_id - + 4 // dst_picture_id + + 1 // op + + 3 // pad + + 4 // src + + 4 // mask + + 4 // dst + 2 // src_x + 2 // src_y + 2 // mask_x @@ -361,10 +422,10 @@ pub const composite = struct { + 2 // height ; pub const Args = struct { - picture_operation: PictureOperation, - src_picture_id: Picture, - mask_picture_id: Picture, - dst_picture_id: Picture, + op: PictureOperation, + src: Picture, + mask: Picture, + dst: Picture, src_x: i16, src_y: i16, mask_x: i16, @@ -377,12 +438,15 @@ pub const composite = struct { pub fn serialize(buf: [*]u8, ext_opcode: u8, args: Args) void { buf[0] = ext_opcode; buf[1] = @intFromEnum(ExtOpcode.composite); + std.debug.assert(len & 0x3 == 0); x11.writeIntNative(u16, buf + 2, len >> 2); - buf[4] = @intFromEnum(args.picture_operation); - // 3 bytes of padding - x11.writeIntNative(u32, buf + 8, @intFromEnum(args.src_picture_id)); - x11.writeIntNative(u32, buf + 12, @intFromEnum(args.mask_picture_id)); - x11.writeIntNative(u32, buf + 16, @intFromEnum(args.dst_picture_id)); + buf[4] = @intFromEnum(args.op); + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + x11.writeIntNative(u32, buf + 8, args.src.toId()); + x11.writeIntNative(u32, buf + 12, args.mask.toId()); + x11.writeIntNative(u32, buf + 16, args.dst.toId()); x11.writeIntNative(i16, buf + 20, args.src_x); x11.writeIntNative(i16, buf + 22, args.src_y); x11.writeIntNative(i16, buf + 24, args.mask_x); @@ -393,3 +457,361 @@ pub const composite = struct { x11.writeIntNative(u16, buf + 34, args.height); } }; + +pub const create_glyph_set = struct { + pub const len = + 2 // extension and command opcodes + + 2 // request length + + 4 // gsid + + 4 // format + ; + pub const Args = struct { + gsid: GlyphSet, + format: PictureFormat, + }; + pub fn serialize(buf: [*]u8, ext_opcode: u8, args: Args) void { + buf[0] = ext_opcode; + buf[1] = @intFromEnum(ExtOpcode.create_glyph_set); + std.debug.assert(len & 0x3 == 0); + x11.writeIntNative(u16, buf + 2, len >> 2); + x11.writeIntNative(u32, buf + 4, @intFromEnum(args.gsid)); + x11.writeIntNative(u32, buf + 8, @intFromEnum(args.format)); + } +}; + +pub const free_glyph_set = struct { + pub const len = + 2 // extension and command opcodes + + 2 // request length + + 4 // gsid + ; + pub const Args = struct { + gsid: GlyphSet, + }; + pub fn serialize(buf: [*]u8, ext_opcode: u8, args: Args) void { + buf[0] = ext_opcode; + buf[1] = @intFromEnum(ExtOpcode.free_glyph_set); + std.debug.assert(len & 0x3 == 0); + x11.writeIntNative(u16, buf + 2, len >> 2); + x11.writeIntNative(u32, buf + 4, @intFromEnum(args.gsid)); + } +}; + +pub const add_glyphs = struct { + pub const non_list_len = + 2 // extension and command opcodes + + 2 // request length + + 4 // gsid + + 4 // num_glyphs + ; + pub fn getLen(num_glyphs: u32, data_len: u32) u32 { + const glyph_ids_len = num_glyphs * 4; + const glyph_info_len = num_glyphs * @sizeOf(GlyphInfo); + const padded_data_len = (data_len + 3) & ~@as(u32, 3); + return non_list_len + glyph_ids_len + glyph_info_len + padded_data_len; + } + pub const Args = struct { + gsid: GlyphSet, + glyphs: []const Glyph, + glyph_infos: []const GlyphInfo, + data: []const u8, + }; + pub fn serialize(buf: [*]u8, ext_opcode: u8, args: Args) void { + const num_glyphs: u32 = @intCast(args.glyphs.len); + std.debug.assert(args.glyph_infos.len == num_glyphs); + + buf[0] = ext_opcode; + buf[1] = @intFromEnum(ExtOpcode.add_glyphs); + x11.writeIntNative(u32, buf + 4, @intFromEnum(args.gsid)); + x11.writeIntNative(u32, buf + 8, num_glyphs); + + var offset: usize = non_list_len; + + for (args.glyphs) |glyph| { + x11.writeIntNative(u32, buf + offset, glyph); + offset += 4; + } + + for (args.glyph_infos) |info| { + x11.writeIntNative(u16, buf + offset, info.width); + x11.writeIntNative(u16, buf + offset + 2, info.height); + x11.writeIntNative(i16, buf + offset + 4, info.x); + x11.writeIntNative(i16, buf + offset + 6, info.y); + x11.writeIntNative(i16, buf + offset + 8, info.x_off); + x11.writeIntNative(i16, buf + offset + 10, info.y_off); + offset += @sizeOf(GlyphInfo); + } + + @memcpy(buf[offset .. offset + args.data.len], args.data); + offset += args.data.len; + + while (offset & 3 != 0) : (offset += 1) { + buf[offset] = 0; + } + + const len = offset; + std.debug.assert(len & 0x3 == 0); + x11.writeIntNative(u16, buf + 2, @intCast(len >> 2)); + } +}; + +pub const free_glyphs = struct { + pub const non_list_len = + 2 // extension and command opcodes + + 2 // request length + + 4 // gsid + ; + pub fn getLen(num_glyphs: u32) u32 { + return non_list_len + (num_glyphs * 4); + } + pub const Args = struct { + gsid: GlyphSet, + glyphs: []const Glyph, + }; + pub fn serialize(buf: [*]u8, ext_opcode: u8, args: Args) void { + buf[0] = ext_opcode; + buf[1] = @intFromEnum(ExtOpcode.free_glyphs); + x11.writeIntNative(u32, buf + 4, args.gsid.toId()); + + var offset: usize = non_list_len; + for (args.glyphs) |glyph| { + x11.writeIntNative(u32, buf + offset, glyph); + offset += 4; + } + + const len = offset; + std.debug.assert(len & 0x3 == 0); + x11.writeIntNative(u16, buf + 2, @intCast(len >> 2)); + } +}; + +pub const composite_glyphs8 = struct { + pub const non_list_len = + 2 // extension and command opcodes + + 2 // request length + + 1 // op + + 3 // pad + + 4 // src + + 4 // dst + + 4 // mask_format + + 4 // gsid + + 2 // src_x + + 2 // src_y + ; + pub fn getLen(glyphelt_bytes: u32) u32 { + const padded_len = (glyphelt_bytes + 3) & ~@as(u32, 3); + return non_list_len + padded_len; + } + pub const Args = struct { + op: PictureOperation, + src: Picture, + dst: Picture, + mask_format: PictureFormat, + gsid: GlyphSet, + src_x: i16, + src_y: i16, + }; + pub fn serialize(buf: [*]u8, ext_opcode: u8, args: Args, glyphelts: []const u8) void { + buf[0] = ext_opcode; + buf[1] = @intFromEnum(ExtOpcode.composite_glyphs8); + buf[4] = @intFromEnum(args.op); + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + x11.writeIntNative(u32, buf + 8, @intFromEnum(args.src)); + x11.writeIntNative(u32, buf + 12, @intFromEnum(args.dst)); + x11.writeIntNative(u32, buf + 16, @intFromEnum(args.mask_format)); + x11.writeIntNative(u32, buf + 20, @intFromEnum(args.gsid)); + x11.writeIntNative(i16, buf + 24, args.src_x); + x11.writeIntNative(i16, buf + 26, args.src_y); + + var offset: usize = non_list_len; + @memcpy(buf[offset .. offset + glyphelts.len], glyphelts); + offset += glyphelts.len; + + while (offset & 3 != 0) : (offset += 1) { + buf[offset] = 0; + } + + const len = offset; + std.debug.assert(len & 0x3 == 0); + x11.writeIntNative(u16, buf + 2, @intCast(len >> 2)); + } +}; + +pub const composite_glyphs16 = struct { + pub const non_list_len = + 2 // extension and command opcodes + + 2 // request length + + 1 // op + + 3 // pad + + 4 // src + + 4 // dst + + 4 // mask_format + + 4 // gsid + + 2 // src_x + + 2 // src_y + ; + pub fn getLen(glyphelt_bytes: u32) u32 { + const padded_len = (glyphelt_bytes + 3) & ~@as(u32, 3); + return non_list_len + padded_len; + } + pub const Args = struct { + op: PictureOperation, + src: Picture, + dst: Picture, + mask_format: PictureFormat, + gsid: GlyphSet, + src_x: i16, + src_y: i16, + }; + pub fn serialize(buf: [*]u8, ext_opcode: u8, args: Args, glyphelts: []const u8) void { + buf[0] = ext_opcode; + buf[1] = @intFromEnum(ExtOpcode.composite_glyphs16); + buf[4] = @intFromEnum(args.op); + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + x11.writeIntNative(u32, buf + 8, args.src.toId()); + x11.writeIntNative(u32, buf + 12, args.dst.toId()); + x11.writeIntNative(u32, buf + 16, args.mask_format.toId()); + x11.writeIntNative(u32, buf + 20, args.gsid.toId()); + x11.writeIntNative(i16, buf + 24, args.src_x); + x11.writeIntNative(i16, buf + 26, args.src_y); + + var offset: usize = non_list_len; + @memcpy(buf[offset .. offset + glyphelts.len], glyphelts); + offset += glyphelts.len; + + while (offset & 3 != 0) : (offset += 1) { + buf[offset] = 0; + } + + const len = offset; + std.debug.assert(len & 0x3 == 0); + x11.writeIntNative(u16, buf + 2, @intCast(len >> 2)); + } +}; + +pub const composite_glyphs32 = struct { + pub const non_list_len = + 2 // extension and command opcodes + + 2 // request length + + 1 // op + + 3 // pad + + 4 // src + + 4 // dst + + 4 // mask_format + + 4 // gsid + + 2 // src_x + + 2 // src_y + ; + pub fn getLen(glyphelt_bytes: u32) u32 { + const padded_len = (glyphelt_bytes + 3) & ~@as(u32, 3); + return non_list_len + padded_len; + } + pub const Args = struct { + op: PictureOperation, + src: Picture, + dst: Picture, + mask_format: PictureFormat, + gsid: GlyphSet, + src_x: i16, + src_y: i16, + }; + pub fn serialize(buf: [*]u8, ext_opcode: u8, args: Args, glyphelts: []const u8) void { + buf[0] = ext_opcode; + buf[1] = @intFromEnum(ExtOpcode.composite_glyphs32); + buf[4] = @intFromEnum(args.op); + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + x11.writeIntNative(u32, buf + 8, args.src.toId()); + x11.writeIntNative(u32, buf + 12, args.dst.toId()); + x11.writeIntNative(u32, buf + 16, args.mask_format.toId()); + x11.writeIntNative(u32, buf + 20, args.gsid.toId()); + x11.writeIntNative(i16, buf + 24, args.src_x); + x11.writeIntNative(i16, buf + 26, args.src_y); + + var offset: usize = non_list_len; + @memcpy(buf[offset .. offset + glyphelts.len], glyphelts); + offset += glyphelts.len; + + while (offset & 3 != 0) : (offset += 1) { + buf[offset] = 0; + } + + const len = offset; + std.debug.assert(len & 0x3 == 0); + x11.writeIntNative(u16, buf + 2, @intCast(len >> 2)); + } +}; + +pub const fill_rectangles = struct { + pub const non_list_len = + 2 // extension and command opcodes + + 2 // request length + + 1 // op + + 3 // pad + + 4 // dst + + 8 // color + ; + pub fn getLen(num_rects: u32) u32 { + return non_list_len + (num_rects * 8); + } + pub const Args = struct { + op: PictureOperation, + dst: Picture, + color: Color, + }; + pub fn serialize(buf: [*]u8, ext_opcode: u8, args: Args, rects: []const x11.Rectangle) void { + buf[0] = ext_opcode; + buf[1] = @intFromEnum(ExtOpcode.fill_rectangles); + buf[4] = @intFromEnum(args.op); + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + x11.writeIntNative(u32, buf + 8, @intFromEnum(args.dst)); + x11.writeIntNative(u16, buf + 12, args.color.red); + x11.writeIntNative(u16, buf + 14, args.color.green); + x11.writeIntNative(u16, buf + 16, args.color.blue); + x11.writeIntNative(u16, buf + 18, args.color.alpha); + + var offset: usize = non_list_len; + for (rects) |rect| { + x11.writeIntNative(i16, buf + offset, rect.x); + x11.writeIntNative(i16, buf + offset + 2, rect.y); + x11.writeIntNative(u16, buf + offset + 4, rect.width); + x11.writeIntNative(u16, buf + offset + 6, rect.height); + offset += 8; + } + + const len = offset; + std.debug.assert(len & 0x3 == 0); + x11.writeIntNative(u16, buf + 2, @intCast(len >> 2)); + } +}; + +pub const create_solid_fill = struct { + pub const len = + 2 // extension and command opcodes + + 2 // request length + + 4 // picture + + 8 // color + ; + pub const Args = struct { + picture: Picture, + color: Color, + }; + pub fn serialize(buf: [*]u8, ext_opcode: u8, args: Args) void { + buf[0] = ext_opcode; + buf[1] = @intFromEnum(ExtOpcode.create_solid_fill); + std.debug.assert(len & 0x3 == 0); + x11.writeIntNative(u16, buf + 2, len >> 2); + x11.writeIntNative(u32, buf + 4, @intFromEnum(args.picture)); + x11.writeIntNative(u16, buf + 8, args.color.red); + x11.writeIntNative(u16, buf + 10, args.color.green); + x11.writeIntNative(u16, buf + 12, args.color.blue); + x11.writeIntNative(u16, buf + 14, args.color.alpha); + } +}; diff --git a/src/x.zig b/src/x.zig index b791f74..5218182 100644 --- a/src/x.zig +++ b/src/x.zig @@ -928,6 +928,10 @@ pub const Resource = enum(u32) { return @enumFromInt(@intFromEnum(r)); } + pub fn glyph_set(r: Resource) render.GlyphSet { + return @enumFromInt(@intFromEnum(r)); + } + pub fn format(v: Resource, fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { _ = fmt; _ = opt;