From 31b331a3a53884b41926bd6fee5dbc3505e8acde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaroslav=20de=20la=20Pe=C3=B1a=20Smirnov?= Date: Sat, 7 Sep 2024 21:10:38 +0300 Subject: init: zig wayland hello world client Adapted the example from chapters 7 and 8 of the Wayland book (https://wayland-book.com/) into Zig. --- src/main.zig | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/main.zig (limited to 'src') diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..5f83b8e --- /dev/null +++ b/src/main.zig @@ -0,0 +1,200 @@ +const std = @import("std"); +const mem = std.mem; +const posix = std.posix; + +const wayland = @import("wayland"); +const wl = wayland.client.wl; +const xdg = wayland.client.xdg; + +const RegistryContext = struct { + shm: ?*wl.Shm, + compositor: ?*wl.Compositor, + wm_base: ?*xdg.WmBase, +}; + +const Window = struct { + shm: *wl.Shm, + wl_surface: *wl.Surface, + width: i32, + height: i32, + offset: f32, + last_frame: u32, + run: bool, + + pub fn drawFrame(state: *Window) !*wl.Buffer { + const stride = state.width * 4; + const size = stride * state.height; + + const fd = try posix.memfd_create("zigway", 0); + defer posix.close(fd); + try posix.ftruncate(fd, @intCast(size)); + var data = try posix.mmap( + null, + @intCast(size), + posix.PROT.READ | posix.PROT.WRITE, + .{ .TYPE = .SHARED }, + fd, + 0, + ); + defer posix.munmap(data); + + const pool = try state.shm.createPool(fd, size); + defer pool.destroy(); + const buffer = try pool.createBuffer(0, state.width, state.height, stride, .argb8888); + + const canvas: *[]u32 = @ptrCast(&data); + const box_side = 12; + var offset: u32 = @intFromFloat(state.offset); + offset = @rem(offset, box_side); + const height: u32 = @intCast(state.height); + const width: u32 = @intCast(state.width); + for (0..height) |y| { + for (0..width) |x| { + if ((x + offset + (y + offset) / box_side * box_side) % (box_side * 2) < box_side) { + canvas.*[y * width + x] = 0xFF0E0E0E; + } else { + canvas.*[y * width + x] = 0xFF2F2F2F; + } + } + } + + buffer.setListener(?*void, bufferListener, null); + return buffer; + } +}; + +pub fn main() !void { + const display = try wl.Display.connect(null); + defer display.disconnect(); + const registry = try display.getRegistry(); + + var ctx = RegistryContext{ + .shm = null, + .compositor = null, + .wm_base = null, + }; + + registry.setListener(*RegistryContext, registryListener, &ctx); + if (display.roundtrip() != .SUCCESS) return error.RoundTripFailed; + + const compositor = ctx.compositor orelse return error.NoWlCompositor; + const wm_base = ctx.wm_base orelse return error.NoXdgWmBase; + + var state = Window{ + .shm = ctx.shm orelse return error.NoWlShm, + .wl_surface = try compositor.createSurface(), + .width = 480, + .height = 360, + .offset = 0.0, + .last_frame = 0, + .run = true, + }; + defer state.wl_surface.destroy(); + + const xdg_surface = try wm_base.getXdgSurface(state.wl_surface); + defer xdg_surface.destroy(); + const xdg_toplevel = try xdg_surface.getToplevel(); + defer xdg_toplevel.destroy(); + + xdg_surface.setListener(*Window, xdgSurfaceListener, &state); + xdg_toplevel.setListener(*Window, xdgToplevelListener, &state); + wm_base.setListener(?*void, xdgWmBaseListener, null); + + xdg_toplevel.setTitle("Hello, wayland!"); + + state.wl_surface.commit(); + + const wl_cb = try state.wl_surface.frame(); + wl_cb.setListener(*Window, frameListener, &state); + + while (state.run) { + if (display.dispatch() != .SUCCESS) return error.DispatchFailed; + } +} + +fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, ctx: *RegistryContext) void { + switch (event) { + .global => |global| { + std.debug.print("inteface: {s}, version {}, name {}\n", .{ + global.interface, + global.version, + global.name, + }); + if (mem.orderZ(u8, global.interface, wl.Compositor.getInterface().name) == .eq) { + ctx.compositor = registry.bind(global.name, wl.Compositor, 6) catch return; + } else if (mem.orderZ(u8, global.interface, wl.Shm.getInterface().name) == .eq) { + ctx.shm = registry.bind(global.name, wl.Shm, 1) catch return; + } else if (mem.orderZ(u8, global.interface, xdg.WmBase.getInterface().name) == .eq) { + ctx.wm_base = registry.bind(global.name, xdg.WmBase, 2) catch return; + } + }, + .global_remove => {}, + } +} + +fn xdgSurfaceListener(xdg_surface: *xdg.Surface, event: xdg.Surface.Event, state: *Window) void { + switch (event) { + .configure => |configure| { + xdg_surface.ackConfigure(configure.serial); + brk: { + const buffer = state.drawFrame() catch |err| { + std.debug.print("error drawing frame {}\n", .{err}); + break :brk; + }; + state.wl_surface.attach(buffer, 0, 0); + } + state.wl_surface.commit(); + }, + } +} + +fn xdgToplevelListener(_: *xdg.Toplevel, event: xdg.Toplevel.Event, state: *Window) void { + switch (event) { + .configure => |configure| { + state.height = if (configure.height != 0) configure.height else state.height; + state.width = if (configure.width != 0) configure.width else state.width; + }, + .close => state.run = false, + } +} +fn xdgWmBaseListener(wm_base: *xdg.WmBase, event: xdg.WmBase.Event, _: ?*void) void { + switch (event) { + .ping => |ping| { + wm_base.pong(ping.serial); + }, + } +} + +fn bufferListener(buffer: *wl.Buffer, event: wl.Buffer.Event, _: ?*void) void { + switch (event) { + .release => buffer.destroy(), + } +} + +fn frameListener(wl_cb: *wl.Callback, event: wl.Callback.Event, state: *Window) void { + switch (event) { + .done => |done| { + wl_cb.destroy(); + const new_cb = state.wl_surface.frame() catch |err| { + std.debug.print("unable to get frame {}\n", .{err}); + state.run = false; + return; + }; + new_cb.setListener(*Window, frameListener, state); + + if (state.last_frame != 0) { + const elapsed: f32 = @floatFromInt(done.callback_data -% state.last_frame); + state.offset += elapsed / 1000.0 * 48.0; + } + defer state.last_frame = done.callback_data; + + const buffer = state.drawFrame() catch |err| { + std.debug.print("error drawing frame {}\n", .{err}); + return; + }; + state.wl_surface.attach(buffer, 0, 0); + state.wl_surface.damageBuffer(0, 0, std.math.maxInt(i32), std.math.maxInt(i32)); + state.wl_surface.commit(); + }, + } +} -- cgit v1.2.3