git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitb9b60623890357660129e7113f893138ad3ce233
parent9f1336d6f6
authorIsaac Freund <[email protected]>
date2025-07-01 16:58
build: update to zig 0.14

Co-authored-by: Robert Günzler <[email protected]>

 .builds/alpine.yml           |  8 ++++----
 .builds/archlinux.yml        |  8 ++++----
 .builds/freebsd.yml          |  8 ++++----
 README.md                    |  2 +-
 build.zig                    | 44 +++++++++++++++++++++++++++++++++-----------
 build.zig.zon                | 33 ++++++++++++++++++++-------------
 common/flags.zig             |  6 +++---
 river/Cursor.zig             |  7 +++----
 river/IdleInhibitManager.zig |  4 ++--
 river/IdleInhibitor.zig      |  2 +-
 river/InputConfig.zig        |  2 +-
 river/Keyboard.zig           |  2 +-
 river/KeyboardGroup.zig      |  2 +-
 river/Output.zig             |  3 +--
 river/Scene.zig              |  2 +-
 river/Seat.zig               |  2 +-
 river/Window.zig             |  5 ++---
 river/WmNode.zig             |  2 +-
 river/process.zig            |  4 ++--
 rivercompat/main.zig         |  2 +-
 rivertile/main.zig           |  6 +++---
 21 files changed, 90 insertions(+), 64 deletions(-)

diff --git a/.builds/alpine.yml b/.builds/alpine.yml
index b72ef99..e03784a 100644
--- a/.builds/alpine.yml
+++ b/.builds/alpine.yml
@@ -43,10 +43,10 @@ tasks:
       cd ..
 
       # Eat Github's resources rather than the Zig Software Foundation's resources!
-      wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz
-      tar xf zig-linux-x86_64-0.13.0.tar.xz
-      sudo mv zig-linux-x86_64-0.13.0/zig /usr/bin/
-      sudo mv zig-linux-x86_64-0.13.0/lib /usr/lib/zig
+      wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz
+      tar xf zig-linux-x86_64-0.14.0.tar.xz
+      sudo mv zig-linux-x86_64-0.14.0/zig /usr/bin/
+      sudo mv zig-linux-x86_64-0.14.0/lib /usr/lib/zig
   - build: |
       cd river
       zig build --summary all
diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml
index d23f799..414ee76 100644
--- a/.builds/archlinux.yml
+++ b/.builds/archlinux.yml
@@ -41,10 +41,10 @@ tasks:
       cd ..
 
       # Eat Github's resources rather than the Zig Software Foundation's resources!
-      wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz
-      tar xf zig-linux-x86_64-0.13.0.tar.xz
-      sudo mv zig-linux-x86_64-0.13.0/zig /usr/bin/
-      sudo mv zig-linux-x86_64-0.13.0/lib /usr/lib/zig
+      wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz
+      tar xf zig-linux-x86_64-0.14.0.tar.xz
+      sudo mv zig-linux-x86_64-0.14.0/zig /usr/bin/
+      sudo mv zig-linux-x86_64-0.14.0/lib /usr/lib/zig
   - build: |
       cd river
       zig build --summary all
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
index 2928661..0756d14 100644
--- a/.builds/freebsd.yml
+++ b/.builds/freebsd.yml
@@ -46,10 +46,10 @@ tasks:
       cd ..
 
       # Eat Github's resources rather than the Zig Software Foundation's resources!
-      wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.13.0/zig-freebsd-x86_64-0.13.0.tar.xz
-      tar xf zig-freebsd-x86_64-0.13.0.tar.xz
-      sudo mv zig-freebsd-x86_64-0.13.0/zig /usr/bin/
-      sudo mv zig-freebsd-x86_64-0.13.0/lib /usr/lib/zig
+      wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.14.0/zig-freebsd-x86_64-0.14.0.tar.xz
+      tar xf zig-freebsd-x86_64-0.14.0.tar.xz
+      sudo mv zig-freebsd-x86_64-0.14.0/zig /usr/bin/
+      sudo mv zig-freebsd-x86_64-0.14.0/lib /usr/lib/zig
   - build: |
       cd river
       zig build --summary all
diff --git a/README.md b/README.md
index f8eb56e..41eb53d 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ To compile river first ensure that you have the following dependencies
 installed. The "development" versions are required if applicable to your
 distribution.
 
-- [zig](https://ziglang.org/download/) 0.13
+- [zig](https://ziglang.org/download/) 0.14
 - wayland
 - wayland-protocols
 - [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.18
diff --git a/build.zig b/build.zig
index 22d9acf..0c5ef6c 100644
--- a/build.zig
+++ b/build.zig
@@ -4,13 +4,7 @@ const Build = std.Build;
 const fs = std.fs;
 const mem = std.mem;
 
-const Scanner = @import("zig-wayland").Scanner;
-
-/// While a river release is in development, this string should contain the version in development
-/// with the "-dev" suffix.
-/// When a release is tagged, the "-dev" suffix should be removed for the commit that gets tagged.
-/// Directly after the tagged commit, the version should be bumped and the "-dev" suffix added.
-const version = "0.4.0-dev";
+const Scanner = @import("wayland").Scanner;
 
 pub fn build(b: *Build) !void {
     const target = b.standardTargetOptions(.{});
@@ -71,7 +65,7 @@ pub fn build(b: *Build) !void {
                 .Inherit,
             ) catch break :blk version;
 
-            var it = mem.split(u8, mem.trim(u8, git_describe_long, &std.ascii.whitespace), "-");
+            var it = mem.splitSequence(u8, mem.trim(u8, git_describe_long, &std.ascii.whitespace), "-");
             _ = it.next().?; // previous tag
             const commit_count = it.next().?;
             const commit_hash = it.next().?;
@@ -137,10 +131,10 @@ pub fn build(b: *Build) !void {
 
     const wayland = b.createModule(.{ .root_source_file = scanner.result });
 
-    const xkbcommon = b.dependency("zig-xkbcommon", .{}).module("xkbcommon");
-    const pixman = b.dependency("zig-pixman", .{}).module("pixman");
+    const xkbcommon = b.dependency("xkbcommon", .{}).module("xkbcommon");
+    const pixman = b.dependency("pixman", .{}).module("pixman");
 
-    const wlroots = b.dependency("zig-wlroots", .{}).module("wlroots");
+    const wlroots = b.dependency("wlroots", .{}).module("wlroots");
     wlroots.addImport("wayland", wayland);
     wlroots.addImport("xkbcommon", xkbcommon);
     wlroots.addImport("pixman", pixman);
@@ -316,3 +310,31 @@ pub fn build(b: *Build) !void {
         test_step.dependOn(&run_globber_test.step);
     }
 }
+
+const version = manifest.version;
+/// Getting rid of this wart requires upstream zig improvements.
+/// See: https://github.com/ziglang/zig/issues/22775
+const manifest: struct {
+    name: @Type(.enum_literal),
+    version: []const u8,
+    paths: []const []const u8,
+    dependencies: struct {
+        pixman: struct {
+            url: []const u8,
+            hash: []const u8,
+        },
+        wayland: struct {
+            url: []const u8,
+            hash: []const u8,
+        },
+        wlroots: struct {
+            url: []const u8,
+            hash: []const u8,
+        },
+        xkbcommon: struct {
+            url: []const u8,
+            hash: []const u8,
+        },
+    },
+    fingerprint: u64,
+} = @import("build.zig.zon");
diff --git a/build.zig.zon b/build.zig.zon
index c532bcc..228eda7 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -1,23 +1,30 @@
 .{
-    .name = "river",
+    .name = .river,
+    // While a river release is in development, this string should contain
+    // the version in development with the "-dev" suffix.
+    // When a release is tagged, the "-dev" suffix should be removed for the
+    // commit that gets tagged.
+    // Directly after the tagged commit, the version should be bumped and the
+    // "-dev" suffix added.
     .version = "0.4.0-dev",
     .paths = .{""},
     .dependencies = .{
-        .@"zig-pixman" = .{
-            .url = "https://codeberg.org/ifreund/zig-pixman/archive/v0.2.0.tar.gz",
-            .hash = "12209db20ce873af176138b76632931def33a10539387cba745db72933c43d274d56",
+        .pixman = .{
+            .url = "https://codeberg.org/ifreund/zig-pixman/archive/v0.3.0.tar.gz",
+            .hash = "pixman-0.3.0-LClMnz2VAAAs7QSCGwLimV5VUYx0JFnX5xWU6HwtMuDX",
         },
-        .@"zig-wayland" = .{
-            .url = "https://codeberg.org/ifreund/zig-wayland/archive/a5e2e9b6a6d7fba638ace4d4b24a3b576a02685b.tar.gz",
-            .hash = "1220d41b23ae70e93355bb29dac1c07aa6aeb92427a2dffc4375e94b4de18111248c",
+        .wayland = .{
+            .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.3.0.tar.gz",
+            .hash = "wayland-0.3.0-lQa1kjPIAQDmhGYpY-zxiRzQJFHQ2VqhJkQLbKKdt5wl",
         },
-        .@"zig-wlroots" = .{
-            .url = "https://codeberg.org/ifreund/zig-wlroots/archive/7fd9f01746e25c5f623384ebc2e45632e4a60965.tar.gz",
-            .hash = "122012d30acca9039fecdf28a0fe410dee5ebc42380b75902f60dbb19c15b8052516",
+        .wlroots = .{
+            .url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.18.2.tar.gz",
+            .hash = "wlroots-0.18.2-jmOlchnIAwBq45_cxU1V3OWErxxJjQZlc9PyJfR-l3uk",
         },
-        .@"zig-xkbcommon" = .{
-            .url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.2.0.tar.gz",
-            .hash = "1220c90b2228d65fd8427a837d31b0add83e9fade1dcfa539bb56fd06f1f8461605f",
+        .xkbcommon = .{
+            .url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.3.0.tar.gz",
+            .hash = "xkbcommon-0.3.0-VDqIe3K9AQB2fG5ZeRcMC9i7kfrp5m2rWgLrmdNn9azr",
         },
     },
+    .fingerprint = 0xf5e3672b8e8d6efc,
 }
diff --git a/common/flags.zig b/common/flags.zig
index 5b7b552..79adde9 100644
--- a/common/flags.zig
+++ b/common/flags.zig
@@ -42,21 +42,21 @@ pub fn parser(comptime Arg: type, comptime flags: []const Flag) type {
                         .boolean => .{
                             .name = flag.name,
                             .type = bool,
-                            .default_value = &false,
+                            .default_value_ptr = &false,
                             .is_comptime = false,
                             .alignment = @alignOf(bool),
                         },
                         .arg => .{
                             .name = flag.name,
                             .type = ?[:0]const u8,
-                            .default_value = &@as(?[:0]const u8, null),
+                            .default_value_ptr = &@as(?[:0]const u8, null),
                             .is_comptime = false,
                             .alignment = @alignOf(?[:0]const u8),
                         },
                     };
                     fields = fields ++ [_]std.builtin.Type.StructField{field};
                 }
-                break :flags_type @Type(.{ .Struct = .{
+                break :flags_type @Type(.{ .@"struct" = .{
                     .layout = .auto,
                     .fields = fields,
                     .decls = &.{},
diff --git a/river/Cursor.zig b/river/Cursor.zig
index 4f6de6d..2c83441 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -691,10 +691,9 @@ pub fn updateState(cursor: *Cursor) void {
         .passthrough => {
             cursor.updateHovered();
 
-            var now: posix.timespec = undefined;
-            posix.clock_gettime(posix.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
-            const msec: u32 = @intCast(now.tv_sec * std.time.ms_per_s +
-                @divTrunc(now.tv_nsec, std.time.ns_per_ms));
+            const now = posix.clock_gettime(posix.CLOCK.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported");
+            const msec: u32 = @intCast(now.sec * std.time.ms_per_s +
+                @divTrunc(now.nsec, std.time.ns_per_ms));
             cursor.passthrough(msec);
         },
         // TODO: Leave down mode if the target surface is no longer visible.
diff --git a/river/IdleInhibitManager.zig b/river/IdleInhibitManager.zig
index a131787..01d796d 100644
--- a/river/IdleInhibitManager.zig
+++ b/river/IdleInhibitManager.zig
@@ -30,7 +30,7 @@ const Window = @import("Window.zig");
 wlr_manager: *wlr.IdleInhibitManagerV1,
 new_idle_inhibitor: wl.Listener(*wlr.IdleInhibitorV1) =
     wl.Listener(*wlr.IdleInhibitorV1).init(handleNewIdleInhibitor),
-inhibitors: std.TailQueue(IdleInhibitor) = .{},
+inhibitors: std.DoublyLinkedList(IdleInhibitor) = .{},
 
 pub fn init(inhibit_manager: *IdleInhibitManager) !void {
     inhibit_manager.* = .{
@@ -69,7 +69,7 @@ pub fn checkActive(inhibit_manager: *IdleInhibitManager) void {
 
 fn handleNewIdleInhibitor(listener: *wl.Listener(*wlr.IdleInhibitorV1), inhibitor: *wlr.IdleInhibitorV1) void {
     const inhibit_manager: *IdleInhibitManager = @fieldParentPtr("new_idle_inhibitor", listener);
-    const inhibitor_node = util.gpa.create(std.TailQueue(IdleInhibitor).Node) catch return;
+    const inhibitor_node = util.gpa.create(std.DoublyLinkedList(IdleInhibitor).Node) catch return;
     inhibitor_node.data.init(inhibitor, inhibit_manager) catch {
         util.gpa.destroy(inhibitor_node);
         return;
diff --git a/river/IdleInhibitor.zig b/river/IdleInhibitor.zig
index 4babaa3..2b45786 100644
--- a/river/IdleInhibitor.zig
+++ b/river/IdleInhibitor.zig
@@ -49,7 +49,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
 
     inhibitor.destroy.link.remove();
 
-    const node: *std.TailQueue(IdleInhibitor).Node = @fieldParentPtr("data", inhibitor);
+    const node: *std.DoublyLinkedList(IdleInhibitor).Node = @fieldParentPtr("data", inhibitor);
     server.idle_inhibit_manager.inhibitors.remove(node);
 
     inhibitor.inhibit_manager.checkActive();
diff --git a/river/InputConfig.zig b/river/InputConfig.zig
index a98ff0e..fe7af14 100644
--- a/river/InputConfig.zig
+++ b/river/InputConfig.zig
@@ -295,7 +295,7 @@ pub fn apply(config: *const InputConfig, device: *InputDevice) void {
     const libinput_device: *c.libinput_device = @ptrCast(device.wlr_device.getLibinputDevice() orelse return);
     log.debug("applying input configuration '{s}' to device '{s}'.", .{ config.glob, device.identifier });
 
-    inline for (@typeInfo(InputConfig).Struct.fields) |field| {
+    inline for (@typeInfo(InputConfig).@"struct".fields) |field| {
         if (comptime mem.eql(u8, field.name, "glob")) continue;
 
         if (@field(config, field.name)) |setting| {
diff --git a/river/Keyboard.zig b/river/Keyboard.zig
index 7cf32ee..d70715d 100644
--- a/river/Keyboard.zig
+++ b/river/Keyboard.zig
@@ -62,7 +62,7 @@ pub const Pressed = struct {
         // Furthermore, wlroots will continue to forward key press/release events to river if more
         // than 32 keys are pressed. Therefore river chooses to ignore keypresses that would take
         // the keyboard beyond 32 simultaneously pressed keys.
-        assert(capacity == @typeInfo(std.meta.fieldInfo(wlr.Keyboard, .keycodes).type).Array.len);
+        assert(capacity == @typeInfo(std.meta.fieldInfo(wlr.Keyboard, .keycodes).type).array.len);
     }
 
     keys: std.BoundedArray(Key, capacity) = .{},
diff --git a/river/KeyboardGroup.zig b/river/KeyboardGroup.zig
index 248ee15..74c98a1 100644
--- a/river/KeyboardGroup.zig
+++ b/river/KeyboardGroup.zig
@@ -72,7 +72,7 @@ pub fn destroy(group: *KeyboardGroup) void {
 
     group.wlr_group.destroy();
 
-    const node: *std.TailQueue(KeyboardGroup).Node = @fieldParentPtr("data", group);
+    const node: *std.DoublyLinkedList(KeyboardGroup).Node = @fieldParentPtr("data", group);
     group.seat.keyboard_groups.remove(node);
     util.gpa.destroy(node);
 }
diff --git a/river/Output.zig b/river/Output.zig
index eb3295c..a146405 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -358,8 +358,7 @@ fn handleFrame(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) voi
         error.CommitFailed => log.err("output commit failed for {s}", .{wlr_output.name}),
     };
 
-    var now: posix.timespec = undefined;
-    posix.clock_gettime(posix.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
+    var now = posix.clock_gettime(posix.CLOCK.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported");
     output.scene_output.?.sendFrameDone(&now);
 }
 
diff --git a/river/Scene.zig b/river/Scene.zig
index f0d369a..db204c5 100644
--- a/river/Scene.zig
+++ b/river/Scene.zig
@@ -131,7 +131,7 @@ pub const SaveableSurfaces = struct {
     saved: *wlr.SceneTree,
 
     pub fn init(parent: *wlr.SceneTree) !SaveableSurfaces {
-        const surfaces = .{
+        const surfaces: SaveableSurfaces = .{
             .tree = try parent.createSceneTree(),
             .saved = try parent.createSceneTree(),
         };
diff --git a/river/Seat.zig b/river/Seat.zig
index 31f6e7e..f185429 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -159,7 +159,7 @@ op: ?struct {
 
 relay: InputRelay,
 
-keyboard_groups: std.TailQueue(KeyboardGroup) = .{},
+keyboard_groups: std.DoublyLinkedList(KeyboardGroup) = .{},
 
 focused: Focus = .none,
 
diff --git a/river/Window.zig b/river/Window.zig
index b0d4d90..3799e6f 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -673,7 +673,7 @@ pub fn updateRenderingStart(window: *Window) void {
     // The check for 0 width/height is necessary to handle timeout of the first configure sent.
     if (scheduled.width != 0 and scheduled.height != 0 and
         (scheduled.resend_dimensions or
-        scheduled.width != sent.width or scheduled.height != sent.height))
+            scheduled.width != sent.width or scheduled.height != sent.height))
     {
         if (window.object) |window_v1| {
             window_v1.sendDimensions(scheduled.width, scheduled.height);
@@ -778,8 +778,7 @@ pub fn rootSurface(window: Window) ?*wlr.Surface {
 pub fn sendFrameDone(window: Window) void {
     assert(window.mapped and !window.destroying);
 
-    var now: posix.timespec = undefined;
-    posix.clock_gettime(posix.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
+    var now = posix.clock_gettime(posix.CLOCK.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported");
     window.rootSurface().?.sendFrameDone(&now);
 }
 
diff --git a/river/WmNode.zig b/river/WmNode.zig
index 2315453..b300c15 100644
--- a/river/WmNode.zig
+++ b/river/WmNode.zig
@@ -31,7 +31,7 @@ const Type = union(enum) {
     window: *Window,
     shell_surface: *ShellSurface,
 };
-const Tag = @typeInfo(Type).Union.tag_type.?;
+const Tag = @typeInfo(Type).@"union".tag_type.?;
 
 tag: Tag,
 object: ?*river.NodeV1 = null,
diff --git a/river/process.zig b/river/process.zig
index c83f61f..fb160e9 100644
--- a/river/process.zig
+++ b/river/process.zig
@@ -29,7 +29,7 @@ pub fn setup() void {
         .mask = posix.empty_sigset,
         .flags = 0,
     };
-    posix.sigaction(posix.SIG.PIPE, &sig_ign, null) catch unreachable;
+    posix.sigaction(posix.SIG.PIPE, &sig_ign, null);
 
     // Most unix systems have a default limit of 1024 file descriptors and it
     // seems unlikely for this default to be universally raised due to the
@@ -68,7 +68,7 @@ pub fn cleanupChild() void {
         .mask = posix.empty_sigset,
         .flags = 0,
     };
-    posix.sigaction(posix.SIG.PIPE, &sig_dfl, null) catch unreachable;
+    posix.sigaction(posix.SIG.PIPE, &sig_dfl, null);
 
     if (original_rlimit) |original| {
         posix.setrlimit(.NOFILE, original) catch {
diff --git a/rivercompat/main.zig b/rivercompat/main.zig
index ce9977e..1f72d35 100644
--- a/rivercompat/main.zig
+++ b/rivercompat/main.zig
@@ -94,7 +94,7 @@ pub fn main() !void {
         .handler = .{ .handler = posix.SIG.DFL },
         .mask = posix.empty_sigset,
         .flags = posix.SA.NOCLDWAIT,
-    }, null) catch unreachable;
+    }, null);
 
     const display = wl.Display.connect(null) catch {
         std.debug.print("Unable to connect to Wayland server.\n", .{});
diff --git a/rivertile/main.zig b/rivertile/main.zig
index 10dc6cc..ad76ed9 100644
--- a/rivertile/main.zig
+++ b/rivertile/main.zig
@@ -91,12 +91,12 @@ const gpa = std.heap.c_allocator;
 const Context = struct {
     initialized: bool = false,
     layout_manager: ?*river.LayoutManagerV3 = null,
-    outputs: std.TailQueue(Output) = .{},
+    outputs: std.DoublyLinkedList(Output) = .{},
 
     fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void {
         const wl_output = try registry.bind(name, wl.Output, 3);
         errdefer wl_output.release();
-        const node = try gpa.create(std.TailQueue(Output).Node);
+        const node = try gpa.create(std.DoublyLinkedList(Output).Node);
         errdefer gpa.destroy(node);
         try node.data.init(context, wl_output, name);
         context.outputs.append(node);
@@ -140,7 +140,7 @@ const Output = struct {
             .namespace_in_use => fatal("namespace 'rivertile' already in use.", .{}),
 
             .user_command => |ev| {
-                var it = mem.tokenize(u8, mem.span(ev.command), " ");
+                var it = mem.tokenizeSequence(u8, mem.span(ev.command), " ");
                 const raw_cmd = it.next() orelse {
                     std.log.err("not enough arguments", .{});
                     return;