From 8b50eeb7eb3a696d50b5dc26e106412819697dc9 Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Thu, 10 Aug 2023 14:23:22 -0600 Subject: [PATCH] Get client rendering a window with low-level code --- examples/01_client_connect.zig | 304 ++++++++++++++++++++++++++++++++- src/core.zig | 139 +++++++++++++++ src/main.zig | 53 +++++- src/xdg.zig | 168 ++++++++++++++++++ 4 files changed, 646 insertions(+), 18 deletions(-) create mode 100644 src/xdg.zig diff --git a/examples/01_client_connect.zig b/examples/01_client_connect.zig index 928cd24..8e51778 100644 --- a/examples/01_client_connect.zig +++ b/examples/01_client_connect.zig @@ -48,11 +48,8 @@ pub fn main() !void { if (header.object_id == registry_id) { const event = try wayland.deserialize(wayland.core.Registry.Event, header, message_buffer.items); - std.debug.print("{} {s} ", .{ header.object_id, @tagName(event) }); switch (event) { .global => |global| { - std.debug.print("{} \"{}\" v{}\n", .{ global.name, std.zig.fmtEscapes(global.interface), global.version }); - var buffer: [20]u32 = undefined; if (std.mem.eql(u8, global.interface, "wl_shm")) { const message = try wayland.serialize( @@ -108,17 +105,306 @@ pub fn main() !void { try socket.writeAll(std.mem.sliceAsBytes(message)); } }, - .global_remove => std.debug.print("{}\n", .{std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items))}), + .global_remove => {}, } } else if (header.object_id == registry_done_id) { - std.debug.print("<-", .{}); - for (message_buffer.items) |word| { - std.debug.print(" {}", .{std.fmt.fmtSliceHexLower(std.mem.asBytes(&word))}); - } - std.debug.print(" (sync event id)\n", .{}); break; } else { std.debug.print("{} {x} \"{}\"\n", .{ header.object_id, header.size_and_opcode.opcode, std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items)) }); } } + + const surface_id = 8; + { + var buffer: [10]u32 = undefined; + const message = try wayland.serialize( + wayland.core.Compositor.Request, + &buffer, + compositor_id, + .{ .create_surface = .{ + .new_id = surface_id, + } }, + ); + try socket.writeAll(std.mem.sliceAsBytes(message)); + } + + const xdg_surface_id = 9; + { + var buffer: [10]u32 = undefined; + const message = try wayland.serialize( + wayland.xdg.WmBase.Request, + &buffer, + xdg_wm_base_id, + .{ .get_xdg_surface = .{ + .id = xdg_surface_id, + .surface = surface_id, + } }, + ); + try socket.writeAll(std.mem.sliceAsBytes(message)); + } + + const xdg_toplevel_id = 10; + { + var buffer: [10]u32 = undefined; + const message = try wayland.serialize( + wayland.xdg.Surface.Request, + &buffer, + xdg_surface_id, + .{ .get_toplevel = .{ + .id = xdg_toplevel_id, + } }, + ); + try socket.writeAll(std.mem.sliceAsBytes(message)); + } + + { + var buffer: [10]u32 = undefined; + const message = try wayland.serialize( + wayland.core.Surface.Request, + &buffer, + surface_id, + wayland.core.Surface.Request.commit, + ); + try socket.writeAll(std.mem.sliceAsBytes(message)); + } + + { + var buffer: [5]u32 = undefined; + const message = try wayland.serialize(wayland.core.Display.Request, &buffer, 1, .{ .sync = .{ .callback = registry_done_id } }); + try socket.writeAll(std.mem.sliceAsBytes(message)); + } + + var done = false; + var surface_configured = false; + while (!done or !surface_configured) { + var header: wayland.Header = undefined; + const header_bytes_read = try socket.readAll(std.mem.asBytes(&header)); + if (header_bytes_read < @sizeOf(wayland.Header)) { + return error.SocketClosed; + } + + try message_buffer.resize((header.size_and_opcode.size - @sizeOf(wayland.Header)) / @sizeOf(u32)); + const bytes_read = try socket.readAll(std.mem.sliceAsBytes(message_buffer.items)); + message_buffer.shrinkRetainingCapacity(bytes_read / @sizeOf(u32)); + + if (header.object_id == xdg_surface_id) { + const event = try wayland.deserialize(wayland.xdg.Surface.Event, header, message_buffer.items); + switch (event) { + .configure => |conf| { + var buffer: [10]u32 = undefined; + const message = try wayland.serialize( + wayland.xdg.Surface.Request, + &buffer, + xdg_surface_id, + .{ .ack_configure = .{ + .serial = conf.serial, + } }, + ); + try socket.writeAll(std.mem.sliceAsBytes(message)); + surface_configured = true; + }, + } + } else if (header.object_id == xdg_toplevel_id) { + // const event = try wayland.deserialize(wayland.xdg.Toplevel.Event, header, message_buffer.items); + // std.debug.print("<- {}\n", .{event}); + std.debug.print("<- xdg_toplevel@{} {s} {}\n", .{ header.object_id, @tagName(@as(std.meta.Tag(wayland.xdg.Toplevel.Event), @enumFromInt(header.size_and_opcode.opcode))), std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items)) }); + } else if (header.object_id == registry_done_id) { + done = true; + } else if (header.object_id == 1) { + const event = try wayland.deserialize(wayland.core.Display.Event, header, message_buffer.items); + switch (event) { + .@"error" => |err| std.debug.print("<- error({}): {} {s}\n", .{ err.object_id, err.code, err.message }), + .delete_id => |id| std.debug.print("<- delete_id {}\n", .{id.name}), + } + } else { + std.debug.print("{} {x} \"{}\"\n", .{ header.object_id, header.size_and_opcode.opcode, std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items)) }); + } + } + + // allocate a shared memory file for display purposes + const Pixel = [4]u8; + const framebuffer_size = [2]usize{ 128, 128 }; + const framebuffer_file_len = framebuffer_size[0] * framebuffer_size[1] * @sizeOf(Pixel); + + const framebuffer_fd = try std.os.memfd_create("my-wayland-framebuffer", 0); + try std.os.ftruncate(framebuffer_fd, framebuffer_file_len); + const framebuffer_bytes = try std.os.mmap(null, framebuffer_file_len, std.os.PROT.READ | std.os.PROT.WRITE, std.os.MAP.SHARED, framebuffer_fd, 0); + const framebuffer = @as([*][4]u8, @ptrCast(framebuffer_bytes.ptr))[0 .. framebuffer_bytes.len / @sizeOf([4]u8)]; + + // put some interesting colors into the framebuffer + for (framebuffer, 0..) |*pixel, i| { + pixel.* = .{ + @truncate(i), + @truncate(i + 64), + @truncate(i + 128), + 0xFF, + }; + } + + const wl_shm_pool_id = 11; + { + var buffer: [10]u32 = undefined; + const message = try wayland.serialize( + wayland.core.Shm.Request, + &buffer, + shm_id, + .{ .create_pool = .{ + .new_id = wl_shm_pool_id, + .size = framebuffer_file_len, + } }, + ); + // Send the file descriptor through a control message + const message_bytes = std.mem.sliceAsBytes(message); + const msg_iov = [_]std.os.iovec_const{ + .{ + .iov_base = message_bytes.ptr, + .iov_len = message_bytes.len, + }, + }; + const control_message = cmsg(std.os.fd_t){ + .level = std.os.SOL.SOCKET, + .type = 0x01, // value of SCM_RIGHTS + .data = framebuffer_fd, + }; + const socket_message = std.os.msghdr_const{ + .name = null, + .namelen = 0, + .iov = &msg_iov, + .iovlen = msg_iov.len, + // .control = null, + // .controllen = 0, + .control = &control_message, + .controllen = @sizeOf(cmsg(std.os.fd_t)), + .flags = 0, + }; + _ = try std.os.sendmsg(socket.handle, &socket_message, 0); + } + + const wl_buffer_id = 12; + { + var buffer: [10]u32 = undefined; + const message = try wayland.serialize( + wayland.core.ShmPool.Request, + &buffer, + wl_shm_pool_id, + .{ .create_buffer = .{ + .new_id = wl_buffer_id, + .offset = 0, + .width = framebuffer_size[0], + .height = framebuffer_size[1], + .stride = framebuffer_size[0] * @sizeOf([4]u8), + .format = @intFromEnum(wayland.core.Shm.Format.argb8888), + } }, + ); + try socket.writeAll(std.mem.sliceAsBytes(message)); + } + + { + var buffer: [10]u32 = undefined; + const message = try wayland.serialize( + wayland.core.Surface.Request, + &buffer, + surface_id, + .{ .attach = .{ + .buffer = wl_buffer_id, + .x = 0, + .y = 0, + } }, + ); + try socket.writeAll(std.mem.sliceAsBytes(message)); + } + + { + var buffer: [10]u32 = undefined; + const message = try wayland.serialize( + wayland.core.Surface.Request, + &buffer, + surface_id, + .{ .damage = .{ + .x = 0, + .y = 0, + .width = std.math.maxInt(i32), + .height = std.math.maxInt(i32), + } }, + ); + try socket.writeAll(std.mem.sliceAsBytes(message)); + } + + { + var buffer: [10]u32 = undefined; + const message = try wayland.serialize( + wayland.core.Surface.Request, + &buffer, + surface_id, + wayland.core.Surface.Request.commit, + ); + try socket.writeAll(std.mem.sliceAsBytes(message)); + } + + var running = true; + while (running) { + var header: wayland.Header = undefined; + const header_bytes_read = try socket.readAll(std.mem.asBytes(&header)); + if (header_bytes_read < @sizeOf(wayland.Header)) { + return error.SocketClosed; + } + + try message_buffer.resize((header.size_and_opcode.size - @sizeOf(wayland.Header)) / @sizeOf(u32)); + const bytes_read = try socket.readAll(std.mem.sliceAsBytes(message_buffer.items)); + message_buffer.shrinkRetainingCapacity(bytes_read / @sizeOf(u32)); + + if (header.object_id == xdg_surface_id) { + const event = try wayland.deserialize(wayland.xdg.Surface.Event, header, message_buffer.items); + switch (event) { + .configure => |conf| { + var buffer: [10]u32 = undefined; + const message = try wayland.serialize( + wayland.xdg.Surface.Request, + &buffer, + xdg_surface_id, + .{ .ack_configure = .{ + .serial = conf.serial, + } }, + ); + try socket.writeAll(std.mem.sliceAsBytes(message)); + }, + } + } else if (header.object_id == xdg_toplevel_id) { + // const event = try wayland.deserialize(wayland.xdg.Toplevel.Event, header, message_buffer.items); + // std.debug.print("<- {}\n", .{event}); + switch (@as(std.meta.Tag(wayland.xdg.Toplevel.Event), @enumFromInt(header.size_and_opcode.opcode))) { + .configure => { + const width: i32 = @intCast(message_buffer.items[0]); + const height: i32 = @intCast(message_buffer.items[1]); + std.debug.print("<- xdg_toplevel@{} configure <{}, {}>\n", .{ header.object_id, width, height }); + }, + .close => running = false, + else => |tag| std.debug.print("<- xdg_toplevel@{} {s} {}\n", .{ header.object_id, @tagName(tag), std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items)) }), + } + } else if (header.object_id == wl_buffer_id) { + const event = try wayland.deserialize(wayland.core.Buffer.Event, header, message_buffer.items); + std.debug.print("<- wl_buffer@{} {}\n", .{ header.object_id, event }); + } else if (header.object_id == 1) { + const event = try wayland.deserialize(wayland.core.Display.Event, header, message_buffer.items); + switch (event) { + .@"error" => |err| std.debug.print("<- error({}): {} {s}\n", .{ err.object_id, err.code, err.message }), + .delete_id => { + // TODO: add id to list of free ids + }, + } + } else { + std.debug.print("{} {x} \"{}\"\n", .{ header.object_id, header.size_and_opcode.opcode, std.zig.fmtEscapes(std.mem.sliceAsBytes(message_buffer.items)) }); + } + } +} + +fn cmsg(comptime T: type) type { + const padding_size = (@sizeOf(T) + @sizeOf(c_long) - 1) & ~(@as(usize, @sizeOf(c_long)) - 1); + return extern struct { + len: c_ulong = @sizeOf(@This()) - padding_size, + level: c_int, + type: c_int, + data: T, + _padding: [padding_size]u8 align(1) = [_]u8{0} ** padding_size, + }; } diff --git a/src/core.zig b/src/core.zig index 6c48993..807f8d8 100644 --- a/src/core.zig +++ b/src/core.zig @@ -83,3 +83,142 @@ pub const Registry = struct { }; }; }; + +pub const Compositor = struct { + pub const Request = union(Request.Tag) { + create_surface: CreateSurface, + create_region: CreateRegion, + + pub const Tag = enum(u16) { + create_surface, + create_region, + }; + + pub const CreateSurface = struct { + new_id: u32, + }; + + pub const CreateRegion = struct { + new_id: u32, + }; + }; +}; + +pub const ShmPool = struct { + pub const Request = union(Request.Tag) { + create_buffer: struct { + new_id: u32, + offset: i32, + width: i32, + height: i32, + stride: i32, + // Shm.Format + format: u32, + }, + destroy: void, + resize: struct { + size: i32, + }, + + pub const Tag = enum(u16) { + create_buffer, + destroy, + resize, + }; + }; +}; + +pub const Shm = struct { + pub const Request = union(Request.Tag) { + create_pool: CreatePool, + + pub const Tag = enum(u16) { + create_pool, + }; + + pub const CreatePool = struct { + new_id: u32, + // file descriptors are sent through a control message + // fd: u32, + size: u32, + }; + }; + + pub const Event = union(Event.Tag) { + format: Format, + + pub const Tag = enum(u16) { + @"error", + delete_id, + }; + }; + + pub const Format = enum(u32) { + argb8888, + xrgb8888, + _, + }; +}; + +pub const Surface = struct { + pub const Request = union(Request.Tag) { + destroy: void, + attach: struct { + buffer: u32, + x: i32, + y: i32, + }, + damage: struct { + x: i32, + y: i32, + width: i32, + height: i32, + }, + frame: struct { + /// id of a new callback object + callback: u32, + }, + set_opaque_region: struct { + region: u32, + }, + set_input_region: struct { + region: u32, + }, + commit: void, + + pub const Tag = enum(u16) { + destroy, + attach, + damage, + frame, + set_opaque_region, + set_input_region, + commit, + }; + }; + + pub const Event = union(Event.Tag) { + format: Format, + + pub const Tag = enum(u16) { + @"error", + delete_id, + }; + + pub const Format = enum(u32) { + argb8888, + xrgb8888, + _, + }; + }; +}; + +pub const Buffer = struct { + pub const Request = union(enum) { + destroy: void, + }; + + pub const Event = union(enum) { + release: void, + }; +}; diff --git a/src/main.zig b/src/main.zig index 099b4f4..f73b544 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,7 @@ const std = @import("std"); const testing = std.testing; pub const core = @import("./core.zig"); +pub const xdg = @import("./xdg.zig"); pub fn getDisplayPath(gpa: std.mem.Allocator) ![]u8 { const xdg_runtime_dir_path = try std.process.getEnvVarOwned(gpa, "XDG_RUNTIME_DIR"); @@ -54,22 +55,55 @@ test "header from []u32" { ); } +pub fn readUInt(buffer: []const u32, parent_pos: *usize) !u32 { + var pos = parent_pos.*; + if (pos >= buffer.len) return error.EndOfStream; + + const uint: u32 = @bitCast(buffer[pos]); + pos += 1; + + parent_pos.* = pos; + return uint; +} + +pub fn readInt(buffer: []const u32, parent_pos: *usize) !i32 { + var pos = parent_pos.*; + if (pos >= buffer.len) return error.EndOfStream; + + const int: i32 = @bitCast(buffer[pos]); + pos += 1; + + parent_pos.* = pos; + return int; +} + +pub fn readString(buffer: []const u32, parent_pos: *usize) ![:0]const u8 { + var pos = parent_pos.*; + + const len = try readUInt(buffer, &pos); + const wordlen = std.mem.alignForward(usize, len, @sizeOf(u32)) / @sizeOf(u32); + + if (pos + wordlen > buffer.len) return error.EndOfStream; + const string = std.mem.sliceAsBytes(buffer[pos..])[0 .. len - 1 :0]; + pos += std.mem.alignForward(usize, len, @sizeOf(u32)) / @sizeOf(u32); + + parent_pos.* = pos; + return string; +} + pub fn deserializeArguments(comptime Signature: type, buffer: []const u32) !Signature { + if (Signature == void) return {}; var result: Signature = undefined; var pos: usize = 0; inline for (std.meta.fields(Signature)) |field| { switch (@typeInfo(field.type)) { - .Int => { - @field(result, field.name) = @bitCast(buffer[pos]); - pos += 1; + .Int => |int_info| switch (int_info.signedness) { + .signed => @field(result, field.name) = try readInt(buffer, &pos), + .unsigned => @field(result, field.name) = try readUInt(buffer, &pos), }, .Pointer => |ptr| switch (ptr.size) { .Slice => { - const len = buffer[pos]; - pos += 1; - const byte_pos = pos * @sizeOf(u32); - @field(result, field.name) = std.mem.sliceAsBytes(buffer)[byte_pos..][0 .. len - 1 :0]; - pos += std.mem.alignForward(usize, len, @sizeOf(u32)) / @sizeOf(u32); + @field(result, field.name) = try readString(buffer, &pos); }, else => @compileError("Unsupported type " ++ @typeName(field.type)), }, @@ -114,12 +148,13 @@ pub fn calculateSerializedWordLen(comptime Signature: type, message: Signature) /// Message must live until the iovec array is written. pub fn serializeArguments(comptime Signature: type, buffer: []u32, message: Signature) ![]u32 { + if (Signature == void) return buffer[0..0]; var pos: usize = 0; inline for (std.meta.fields(Signature)) |field| { switch (@typeInfo(field.type)) { .Int => { if (pos >= buffer.len) return error.OutOfMemory; - buffer[pos] = @field(message, field.name); + buffer[pos] = @bitCast(@field(message, field.name)); pos += 1; }, .Pointer => |ptr| switch (ptr.size) { diff --git a/src/xdg.zig b/src/xdg.zig new file mode 100644 index 0000000..2de6f93 --- /dev/null +++ b/src/xdg.zig @@ -0,0 +1,168 @@ +pub const WmBase = struct { + pub const Request = union(Request.Tag) { + destroy: void, + create_positioner: struct { + id: u32, + }, + get_xdg_surface: struct { + id: u32, + surface: u32, + }, + pong: struct { + serial: u32, + }, + + pub const Tag = enum(u16) { + destroy, + create_positioner, + get_xdg_surface, + pong, + }; + }; + + pub const Event = union(Event.Tag) { + ping: struct { serial: u32 }, + + pub const Tag = enum(u16) { + ping, + }; + }; + + pub const Error = enum(u32) { + role, + defunct_surfaces, + not_the_topmost_popup, + invalid_popup_parent, + invalid_surface_state, + invalid_positioner, + unresponsive, + }; +}; + +pub const Surface = struct { + pub const Request = union(enum) { + destroy: void, + get_toplevel: struct { + id: u32, + }, + get_popup: struct { + id: u32, + /// Allows null + parent: u32, + positioner: u32, + }, + set_window_geometry: struct { + x: i32, + y: i32, + width: i32, + height: i32, + }, + ack_configure: struct { + serial: u32, + }, + }; + + pub const Event = union(enum) { + configure: struct { serial: u32 }, + }; + + pub const Error = enum(u32) { + not_constructed = 1, + already_constructed, + unconfigured_buffer, + invalid_serial, + invalid_size, + defunct_role_object, + }; +}; + +pub const Toplevel = struct { + pub const Request = union(enum) { + destroy: void, + set_parent: struct { + /// Allows null + parent: u32, + }, + set_title: struct { + title: []const u8, + }, + set_app_id: struct { + app_id: []const u8, + }, + show_window_menu: struct { + seat: u32, + serial: u32, + x: i32, + y: i32, + }, + move: struct { + seat: u32, + serial: u32, + }, + resize: struct { + seat: u32, + serial: u32, + edges: Toplevel.ResizeEdge, + }, + }; + + pub const Event = union(enum) { + configure: struct { + width: i32, + height: i32, + states: []Toplevel.State, + }, + close: void, + configure_bounds: struct { + width: i32, + height: i32, + }, + wm_capabilities: struct { + capabilities: []Toplevel.WmCapabilities, + }, + }; + + pub const ResizeEdge = enum(u32) { + none, + top, + bottom, + left, + top_left, + bottom_left, + right, + top_right, + bottom_right, + _, + }; + + pub const State = enum(u32) { + maximized, + fullscreen, + resizing, + activated, + tiled_left, + tiled_right, + tiled_top, + tiled_bottom, + suspended, + _, + }; + + pub const WmCapabilities = enum(u32) { + window_menu, + maximize, + fullscreen, + minimize, + _, + }; + + pub const Error = enum(u32) { + not_constructed = 1, + already_constructed, + unconfigured_buffer, + invalid_serial, + invalid_size, + defunct_role_object, + _, + }; +};