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], ); }