aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYaroslav de la Peña Smirnov <yps@yaroslavps.com>2024-09-07 21:10:38 +0300
committerYaroslav de la Peña Smirnov <yps@yaroslavps.com>2024-09-07 21:10:38 +0300
commit31b331a3a53884b41926bd6fee5dbc3505e8acde (patch)
tree1c7b3987073b92e70a017895ca2ba2f34f8e1fc6
downloadzigway-31b331a3a53884b41926bd6fee5dbc3505e8acde.tar.gz
zigway-31b331a3a53884b41926bd6fee5dbc3505e8acde.zip
init: zig wayland hello world client
Adapted the example from chapters 7 and 8 of the Wayland book (https://wayland-book.com/) into Zig.
-rw-r--r--.gitignore2
-rw-r--r--build.zig44
-rw-r--r--build.zig.zon18
-rw-r--r--src/main.zig200
4 files changed, 264 insertions, 0 deletions
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();
+ },
+ }
+}