a73x

2c843d0d

feat(wayland): create surface + xdg_toplevel

a73x   2026-04-08 08:42

Commit message
feat(wayland): create surface + xdg_toplevel

Add Window struct with wl_surface/xdg_surface/xdg_toplevel, listeners
for ping/configure/close, and extend smoke test to create and destroy a
titled window.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

src/main.zig
Old New
@@ -16,16 +16,21 @@ pub fn main() !void {
16 } 16 }
17 17
18 if (args.len >= 2 and std.mem.eql(u8, args[1], "--wayland-smoke-test")) { 18 if (args.len >= 2 and std.mem.eql(u8, args[1], "--wayland-smoke-test")) {
19 return runWaylandSmokeTest(); 19 return runWaylandSmokeTest(alloc);
20 } 20 }
21 21
22 std.debug.print("waystty (run with --headless for CLI dump mode)\n", .{}); 22 std.debug.print("waystty (run with --headless for CLI dump mode)\n", .{});
23 } 23 }
24 24
25 fn runWaylandSmokeTest() !void { 25 fn runWaylandSmokeTest(alloc: std.mem.Allocator) !void {
26 var conn = try wayland_client.Connection.init(); 26 var conn = try wayland_client.Connection.init();
27 defer conn.deinit(); 27 defer conn.deinit();
28 std.debug.print("connected\n", .{}); 28 std.debug.print("connected\n", .{});
29
30 const window = try conn.createWindow(alloc, "waystty");
31 defer window.deinit();
32
33 std.debug.print("window created (w={d} h={d})\n", .{ window.width, window.height });
29 } 34 }
30 35
31 fn runHeadless(alloc: std.mem.Allocator) !void { 36 fn runHeadless(alloc: std.mem.Allocator) !void {
src/wayland.zig
Old New
@@ -10,6 +10,24 @@ pub const Globals = struct {
10 seat: ?*wl.Seat = null, 10 seat: ?*wl.Seat = null,
11 }; 11 };
12 12
13 pub const Window = struct {
14 alloc: std.mem.Allocator,
15 surface: *wl.Surface,
16 xdg_surface: *xdg.Surface,
17 xdg_toplevel: *xdg.Toplevel,
18 configured: bool = false,
19 should_close: bool = false,
20 width: u32 = 800,
21 height: u32 = 600,
22
23 pub fn deinit(self: *Window) void {
24 self.xdg_toplevel.destroy();
25 self.xdg_surface.destroy();
26 self.surface.destroy();
27 self.alloc.destroy(self);
28 }
29 };
30
13 pub const Connection = struct { 31 pub const Connection = struct {
14 display: *wl.Display, 32 display: *wl.Display,
15 registry: *wl.Registry, 33 registry: *wl.Registry,
@@ -41,8 +59,68 @@ pub const Connection = struct {
41 pub fn deinit(self: *Connection) void { 59 pub fn deinit(self: *Connection) void {
42 self.display.disconnect(); 60 self.display.disconnect();
43 } 61 }
62
63 pub fn createWindow(self: *Connection, alloc: std.mem.Allocator, title: [*:0]const u8) !*Window {
64 const compositor = self.globals.compositor orelse return error.NoCompositor;
65 const wm_base = self.globals.wm_base orelse return error.NoXdgWmBase;
66
67 const window = try alloc.create(Window);
68 errdefer alloc.destroy(window);
69
70 window.* = .{
71 .alloc = alloc,
72 .surface = try compositor.createSurface(),
73 .xdg_surface = undefined,
74 .xdg_toplevel = undefined,
75 };
76 errdefer window.surface.destroy();
77
78 window.xdg_surface = try wm_base.getXdgSurface(window.surface);
79 errdefer window.xdg_surface.destroy();
80
81 window.xdg_toplevel = try window.xdg_surface.getToplevel();
82
83 window.xdg_toplevel.setTitle(title);
84 window.xdg_toplevel.setAppId("waystty");
85
86 window.xdg_surface.setListener(*Window, xdgSurfaceListener, window);
87 window.xdg_toplevel.setListener(*Window, xdgToplevelListener, window);
88 wm_base.setListener(*xdg.WmBase, wmBaseListener, wm_base);
89
90 window.surface.commit();
91 _ = self.display.roundtrip();
92
93 return window;
94 }
44 }; 95 };
45 96
97 fn wmBaseListener(wm_base: *xdg.WmBase, event: xdg.WmBase.Event, _: *xdg.WmBase) void {
98 switch (event) {
99 .ping => |p| wm_base.pong(p.serial),
100 }
101 }
102
103 fn xdgSurfaceListener(surface: *xdg.Surface, event: xdg.Surface.Event, window: *Window) void {
104 switch (event) {
105 .configure => |c| {
106 surface.ackConfigure(c.serial);
107 window.configured = true;
108 },
109 }
110 }
111
112 fn xdgToplevelListener(_: *xdg.Toplevel, event: xdg.Toplevel.Event, window: *Window) void {
113 switch (event) {
114 .configure => |c| {
115 if (c.width > 0) window.width = @intCast(c.width);
116 if (c.height > 0) window.height = @intCast(c.height);
117 },
118 .close => window.should_close = true,
119 .configure_bounds => {},
120 .wm_capabilities => {},
121 }
122 }
123
46 fn registryListener( 124 fn registryListener(
47 registry: *wl.Registry, 125 registry: *wl.Registry,
48 event: wl.Registry.Event, 126 event: wl.Registry.Event,
@@ -54,7 +132,7 @@ fn registryListener(
54 if (std.mem.eql(u8, iface, std.mem.span(wl.Compositor.interface.name))) { 132 if (std.mem.eql(u8, iface, std.mem.span(wl.Compositor.interface.name))) {
55 globals.compositor = registry.bind(g.name, wl.Compositor, 6) catch return; 133 globals.compositor = registry.bind(g.name, wl.Compositor, 6) catch return;
56 } else if (std.mem.eql(u8, iface, std.mem.span(xdg.WmBase.interface.name))) { 134 } else if (std.mem.eql(u8, iface, std.mem.span(xdg.WmBase.interface.name))) {
57 globals.wm_base = registry.bind(g.name, xdg.WmBase, 6) catch return; 135 globals.wm_base = registry.bind(g.name, xdg.WmBase, 5) catch return;
58 } else if (std.mem.eql(u8, iface, std.mem.span(wl.Seat.interface.name))) { 136 } else if (std.mem.eql(u8, iface, std.mem.span(wl.Seat.interface.name))) {
59 globals.seat = registry.bind(g.name, wl.Seat, 9) catch return; 137 globals.seat = registry.bind(g.name, wl.Seat, 9) catch return;
60 } 138 }