aboutsummaryrefslogtreecommitdiff
path: root/src/wl/display.zig
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2025-08-18 17:45:13 +0200
committerNathan Reiner <nathan@nathanreiner.xyz>2025-08-18 17:45:13 +0200
commit33e1de2710fe44512440e0e6055578d67dab330c (patch)
treedf8198ac5147f466bba600ca06dc1a319099d185 /src/wl/display.zig
First sketch of wayland interface
**WARNING** this implementation is not working properly yet.
Diffstat (limited to 'src/wl/display.zig')
-rw-r--r--src/wl/display.zig265
1 files changed, 265 insertions, 0 deletions
diff --git a/src/wl/display.zig b/src/wl/display.zig
new file mode 100644
index 0000000..4f04746
--- /dev/null
+++ b/src/wl/display.zig
@@ -0,0 +1,265 @@
+const std = @import("std");
+const wayland = @import("../root.zig");
+const wl = wayland.wl;
+
+const log = std.log.scoped(.display);
+
+const Self = @This();
+
+pub const Error = enum(u32) {
+ invalid_object = 0,
+ invalid_method = 1,
+ no_memory = 2,
+ implementation = 3,
+};
+
+pub const Request = union(enum) {
+ sync: struct { *wayland.Object },
+ get_registry: struct { *wayland.Object },
+};
+
+pub const Events = wayland.EventSet(Self, .{
+ err,
+ delete_id,
+});
+
+stream: std.net.Stream,
+interface: wayland.Object,
+registry: wl.Registry,
+
+pub fn init(
+ self: *Self,
+ allocator: std.mem.Allocator,
+ handlers: []const wl.Registry.GlobalHandler,
+) !void {
+ self.interface = wayland.Object.from_self(self);
+ try self.registry.init(allocator, self, handlers);
+}
+
+pub fn from_fd(fd: std.posix.fd_t) void {
+ var self: Self = undefined;
+ self.stream = std.net.Stream{ .handle = fd };
+ return self;
+}
+
+pub fn open(path: []const u8) !Self {
+ var self: Self = undefined;
+ self.stream = try std.net.connectUnixSocket(path);
+ return self;
+}
+
+pub fn close(self: *Self) void {
+ self.stream.close();
+}
+
+pub fn open_default(allocator: std.mem.Allocator) !Self {
+ const path = try default_path(allocator);
+ defer allocator.free(path);
+ return open(path);
+}
+
+pub fn default_path(allocator: std.mem.Allocator) ![]const u8 {
+ const env = try std.process.getEnvMap(allocator);
+ const xdg_runtime_dir = env.get("XDG_RUNTIME_DIR") orelse return error.MissingRuntimeDir;
+ const wayland_display = env.get("WAYLAND_DISPLAY") orelse "wayland-0";
+ return std.fmt.allocPrint(allocator, "{s}/{s}", .{ xdg_runtime_dir, wayland_display });
+}
+
+pub fn request(self: *Self, object: anytype, args: @TypeOf(object.*).Request) !void {
+ comptime std.debug.assert(@FieldType(@TypeOf(object.*), "interface") == wayland.Object);
+ comptime std.debug.assert(@typeInfo(@TypeOf(object.*).Request) == .@"union");
+ const tag_type = @typeInfo(@TypeOf(args)).@"union".tag_type.?;
+ const opcode: u32 = @intFromEnum(args);
+
+ inline for (@typeInfo(tag_type).@"enum".fields) |f| {
+ if (f.value == opcode) {
+ const data = @field(args, f.name);
+ const id = object.interface.id orelse return error.UnregisteredObject;
+
+ var size: u32 = 8;
+
+ inline for (data) |value| {
+ switch (@TypeOf(value)) {
+ u32, i32 => |t| size += @sizeOf(t),
+ *wayland.Object => size += @sizeOf(u32),
+ []const u8, []u8 => {
+ const padding_size: usize = @mod(@as(usize, @bitCast(-@as(isize, @intCast(value.len + 1)))), 4);
+ size += @as(u32, @intCast(@sizeOf(u32) + (value.len + 1) + padding_size));
+ },
+ else => |t| switch (@typeInfo(t)) {
+ .pointer => |ptr| if (ptr.size == .slice) {
+ const padding_size: usize = @mod(@as(usize, @bitCast(-@as(isize, @intCast(value.len * @sizeOf(ptr.child))))), 4);
+ size += @intCast(@sizeOf(u32) + (value.len * @sizeOf(ptr.child)) + padding_size);
+ } else @compileError(std.fmt.comptimePrint("size: unsupported type {}", .{t})),
+ else => @compileError(std.fmt.comptimePrint("size: unsupported type {}", .{t})),
+ },
+ }
+ }
+
+ _ = try self.stream.writeAll(&std.mem.toBytes(id));
+ _ = try self.stream.writeAll(&std.mem.toBytes((size << 16) | opcode));
+
+ inline for (data) |value| {
+ switch (@TypeOf(value)) {
+ u32, i32 => _ = try self.stream.writeAll(std.mem.asBytes(&value)),
+ *wayland.Object => try self.stream.writeAll(std.mem.asBytes(&(value.id orelse return error.UnregisteredObject))),
+ []const u8, []u8 => {
+ const padding_size: usize = @mod(@as(usize, @bitCast(-@as(isize, @intCast(value.len + 1)))), 4);
+ _ = try self.stream.writeAll(std.mem.asBytes(&@as(u32, @truncate(value.len + 1))));
+ _ = try self.stream.writeAll(value);
+ for (0..padding_size + 1) |_| {
+ _ = try self.stream.writeAll(std.mem.asBytes(&@as(u8, 0)));
+ }
+ },
+ else => |t| switch (@typeInfo(t)) {
+ .pointer => |ptr| if (ptr.size == .slice) {
+ const padding_size: usize = @mod(@as(usize, @bitCast(-@as(isize, @intCast(value.len * @sizeOf(ptr.child))))), 4);
+ _ = try self.stream.writeAll(std.mem.asBytes(&@as(u32, @truncate(value.len))));
+ _ = try self.stream.writeAll(std.mem.sliceAsBytes(value));
+ for (0..padding_size) |_| {
+ _ = try self.stream.writeAll(std.mem.asBytes(&@as(u8, 0)));
+ }
+ } else @compileError(std.fmt.comptimePrint("size: unsupported type {}", .{t})),
+ else => @compileError(std.fmt.comptimePrint("size: unsupported type {}", .{t})),
+ },
+ }
+ }
+ return;
+ }
+ }
+
+ return error.InvalidOpcode;
+}
+
+pub fn roundtrip(self: *Self, allocator: std.mem.Allocator) !void {
+ var done = false;
+ var callback: wl.Callback = undefined;
+ try callback.init(allocator, self, &done);
+
+ var context: wayland.Context = .{
+ .display = self,
+ .allocator = allocator,
+ };
+
+ while (!done) {
+ const endian = @import("builtin").target.cpu.arch.endian();
+ const reader = self.stream.reader();
+
+ const object_id = try reader.readInt(u32, endian);
+
+ const message_info = try reader.readInt(u32, endian);
+
+ const opcode: u16 = @truncate(message_info & 0xffff);
+ const size: u16 = @truncate(((message_info >> 16) & 0xffff));
+ const body_size = size - 2 * @sizeOf(u32);
+
+ const body = try allocator.alloc(u8, body_size);
+ defer allocator.free(body);
+ _ = try reader.readAll(body);
+
+ if (object_id >= self.registry.objects.items.len) {
+ return error.UnregisteredObject;
+ }
+
+ if (self.registry.objects.items[object_id]) |object| {
+ try object.on_event(&context, opcode, body);
+ } else {
+ return error.UnregisteredObject;
+ }
+ }
+}
+
+fn err(
+ self: *Self,
+ ctx: *wayland.Context,
+ object: *wayland.Object,
+ code: u32,
+ message: []const u8,
+) void {
+ _ = self;
+ _ = ctx;
+ log.err("{} ({}): {s}", .{ object, code, message });
+}
+
+fn delete_id(
+ self: *Self,
+ ctx: *wayland.Context,
+ id: u32,
+) void {
+ _ = ctx;
+ self.registry.release_object(id);
+}
+
+test "request" {
+ const Sample = struct {
+ pub const Request = union(enum) {
+ req1: struct { u32 },
+ req2: struct { *wayland.Object },
+ req3: struct { []const u8 },
+ req4: struct { []const u16 },
+ };
+
+ pub const Events = wayland.EventSet(@This(), .{});
+
+ interface: wayland.Object,
+
+ pub fn init(self: *@This()) void {
+ self.interface = wayland.Object.from_self(self);
+ }
+ };
+
+ const in, const out = try std.posix.pipe();
+ defer std.posix.close(in);
+
+ {
+ var sample: Sample = undefined;
+ sample.init();
+ sample.interface.id = 42;
+
+ var displ: Self = .from_fd(out);
+ defer displ.close();
+ try displ.init();
+ try displ.request(sample, .{ .req1 = .{432} });
+ try displ.request(sample, .{ .req2 = .{&sample.interface} });
+ try displ.request(sample, .{ .req3 = .{"abcd"} });
+ try displ.request(sample, .{ .req4 = .{&[_]u16{ 1, 32, 4, 5, 5 }} });
+ }
+
+ const file = std.fs.File{ .handle = in };
+ var test_buffer: [64]u8 = undefined;
+ _ = try file.readAll(test_buffer[0..12]);
+ try std.testing.expectEqualSlices(
+ u8,
+ &(std.mem.toBytes(@as(u32, 42)) ++
+ std.mem.toBytes(@as(u32, (12 << 16) | 0)) ++
+ std.mem.toBytes(@as(u32, 432))),
+ test_buffer[0..12],
+ );
+
+ _ = try file.readAll(test_buffer[0..12]);
+ try std.testing.expectEqualSlices(
+ u8,
+ &(std.mem.toBytes(@as(u32, 42)) ++
+ std.mem.toBytes(@as(u32, (12 << 16) | 1)) ++
+ std.mem.toBytes(@as(u32, 42))),
+ test_buffer[0..12],
+ );
+
+ _ = try file.readAll(test_buffer[0..20]);
+ try std.testing.expectEqualSlices(
+ u8,
+ &(std.mem.toBytes(@as(u32, 42)) ++
+ std.mem.toBytes(@as(u32, (20 << 16) | 2)) ++
+ std.mem.toBytes(@as(u32, 5))) ++ "abcd\x00\x00\x00\x00",
+ test_buffer[0..20],
+ );
+
+ _ = try file.readAll(test_buffer[0..24]);
+ try std.testing.expectEqualSlices(
+ u8,
+ &(std.mem.toBytes(@as(u32, 42)) ++
+ std.mem.toBytes(@as(u32, (24 << 16) | 3)) ++
+ std.mem.toBytes(@as(u32, 5))) ++ std.mem.toBytes([_]u16{ 1, 32, 4, 5, 5, 0 }),
+ test_buffer[0..24],
+ );
+}