1f3f2cdf
feat(wayland): connect + bind globals
a73x 2026-04-08 08:40
Wire zig-wayland scanner into build.zig, create src/wayland.zig with a Connection that connects to wl_display and binds wl_compositor, xdg_wm_base, and wl_seat. Add --wayland-smoke-test branch to main.zig. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
diff --git a/build.zig b/build.zig index 591d705..29b5596 100644 --- a/build.zig +++ b/build.zig @@ -8,6 +8,32 @@ pub fn build(b: *std.Build) void { // materializes the package; subsequent builds use the local cache. const ghostty_dep = b.lazyDependency("ghostty", .{}); // zig-wayland scanner — generates protocol bindings at build time const wayland_dep = b.dependency("wayland", .{}); const Scanner = @import("wayland").Scanner; const scanner = Scanner.create(b, .{}); scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); scanner.generate("wl_compositor", 6); scanner.generate("wl_seat", 9); scanner.generate("xdg_wm_base", 6); // wayland module — generated bindings + our Connection wrapper const wayland_generated_mod = b.createModule(.{ .root_source_file = scanner.result, .target = target, .optimize = optimize, }); const wayland_mod = b.createModule(.{ .root_source_file = b.path("src/wayland.zig"), .target = target, .optimize = optimize, .link_libc = true, }); wayland_mod.addImport("wayland", wayland_generated_mod); wayland_mod.linkSystemLibrary("wayland-client", .{}); _ = wayland_dep; // referenced via Scanner // vt module — wraps ghostty-vt const vt_mod = b.createModule(.{ .root_source_file = b.path("src/vt.zig"), @@ -25,6 +51,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); exe_mod.addImport("vt", vt_mod); exe_mod.addImport("wayland-client", wayland_mod); const exe = b.addExecutable(.{ .name = "waystty", @@ -72,6 +99,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); main_test_mod.addImport("vt", vt_mod); main_test_mod.addImport("wayland-client", wayland_mod); const main_tests = b.addTest(.{ .root_module = main_test_mod, }); diff --git a/src/main.zig b/src/main.zig index 3fc8430..8f617a3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,7 @@ const std = @import("std"); const vt = @import("vt"); const pty = @import("pty"); const wayland_client = @import("wayland-client"); pub fn main() !void { var gpa: std.heap.DebugAllocator(.{}) = .init; @@ -14,9 +15,19 @@ pub fn main() !void { return runHeadless(alloc); } if (args.len >= 2 and std.mem.eql(u8, args[1], "--wayland-smoke-test")) { return runWaylandSmokeTest(); } std.debug.print("waystty (run with --headless for CLI dump mode)\n", .{}); } fn runWaylandSmokeTest() !void { var conn = try wayland_client.Connection.init(); defer conn.deinit(); std.debug.print("connected\n", .{}); } fn runHeadless(alloc: std.mem.Allocator) !void { const shell: [:0]const u8 = "/bin/sh"; diff --git a/src/wayland.zig b/src/wayland.zig new file mode 100644 index 0000000..fe93e39 --- /dev/null +++ b/src/wayland.zig @@ -0,0 +1,64 @@ const std = @import("std"); const posix = std.posix; const wayland = @import("wayland"); const wl = wayland.client.wl; const xdg = wayland.client.xdg; pub const Globals = struct { compositor: ?*wl.Compositor = null, wm_base: ?*xdg.WmBase = null, seat: ?*wl.Seat = null, }; pub const Connection = struct { display: *wl.Display, registry: *wl.Registry, globals: Globals, pub fn init() !Connection { const display = try wl.Display.connect(null); errdefer display.disconnect(); const registry = try display.getRegistry(); errdefer registry.destroy(); var globals = Globals{}; registry.setListener(*Globals, registryListener, &globals); if (display.roundtrip() != .SUCCESS) return error.RoundtripFailed; if (globals.compositor == null) return error.NoCompositor; if (globals.wm_base == null) return error.NoXdgWmBase; if (globals.seat == null) return error.NoSeat; return .{ .display = display, .registry = registry, .globals = globals, }; } pub fn deinit(self: *Connection) void { self.display.disconnect(); } }; fn registryListener( registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals, ) void { switch (event) { .global => |g| { const iface = std.mem.span(g.interface); if (std.mem.eql(u8, iface, std.mem.span(wl.Compositor.interface.name))) { globals.compositor = registry.bind(g.name, wl.Compositor, 6) catch return; } else if (std.mem.eql(u8, iface, std.mem.span(xdg.WmBase.interface.name))) { globals.wm_base = registry.bind(g.name, xdg.WmBase, 6) catch return; } else if (std.mem.eql(u8, iface, std.mem.span(wl.Seat.interface.name))) { globals.seat = registry.bind(g.name, wl.Seat, 9) catch return; } }, .global_remove => {}, } }