diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2025-08-18 17:45:13 +0200 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2025-08-18 17:45:13 +0200 |
| commit | 33e1de2710fe44512440e0e6055578d67dab330c (patch) | |
| tree | df8198ac5147f466bba600ca06dc1a319099d185 /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.zig | 265 |
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], + ); +} |