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. --- .gitignore | 2 + build.zig | 44 +++++++++++++ build.zig.zon | 18 ++++++ src/main.zig | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 264 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/main.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..941c29f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.zig-cache +zig-out/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..5e885fc --- /dev/null +++ b/build.zig @@ -0,0 +1,44 @@ +const std = @import("std"); + +const Scanner = @import("zig-wayland").Scanner; + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + + const optimize = b.standardOptimizeOption(.{}); + + const scanner = Scanner.create(b, .{}); + scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); + scanner.addSystemProtocol("staging/fractional-scale/fractional-scale-v1.xml"); + scanner.generate("wl_compositor", 6); + scanner.generate("wl_shm", 1); + scanner.generate("wl_output", 4); + scanner.generate("xdg_wm_base", 2); + scanner.generate("wp_fractional_scale_manager_v1", 1); + + const wayland = b.createModule(.{ .root_source_file = scanner.result }); + + const exe = b.addExecutable(.{ + .name = "zigway", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + exe.root_module.addImport("wayland", wayland); + exe.linkLibC(); + exe.linkSystemLibrary("wayland-client"); + + scanner.addCSource(exe); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..925fe00 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,18 @@ +.{ + .name = "hello-wl", + .version = "0.0.0", + //.minimum_zig_version = "0.11.0", + .dependencies = .{ + .@"zig-wayland" = .{ + .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.2.0.tar.gz", + .hash = "1220687c8c47a48ba285d26a05600f8700d37fc637e223ced3aa8324f3650bf52242", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + //"LICENSE", + //"README.md", + }, +} 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