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

commita20bc3d59299453f7876015931e08e22a99b778d
parent1bf2dbefb4
authorIsaac Freund <[email protected]>
date2026-03-11 11:40
river: detect riverctl in init file and bail

The goal of this commit is to give users that unwittingly end up with
river 0.4.0+ installed but expect river 0.3/river-classic a better
failure mode than an empty black screen that can only be exited by
switching VTs.

 river/main.zig | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/river/main.zig b/river/main.zig
index 4f21bd9..ad5b0e7 100644
--- a/river/main.zig
+++ b/river/main.zig
@@ -110,6 +110,8 @@ pub fn main() anyerror!void {
         }
     };
 
+    try detectClassic(startup_command);
+
     log.info("river version {s}, initializing server", .{build_options.version});
 
     river_init_wlroots_log(switch (runtime_log_level) {
@@ -182,6 +184,52 @@ fn defaultInitPath() !?[:0]const u8 {
     return path;
 }
 
+fn detectClassic(startup_command: ?[:0]const u8) !void {
+    const path = startup_command orelse return;
+    if (mem.indexOfScalar(u8, path, '/') == null) return;
+
+    const classic = grepRiverctl(path) catch |err| {
+        log.err("failed to detect riverctl usage in init file: {s}", .{@errorName(err)});
+        return;
+    };
+    if (classic) {
+        try stderr.print(
+            \\The init file {[path]s} contains the string "riverctl".
+            \\This river version ({[version]s}) does not support riverctl, you may
+            \\wish to install river-classic instead.
+            \\
+            \\River {[version]s} is a non-monolithic Wayland compositor and
+            \\requires a compatible window manager to be useful.
+            \\See https://isaacfreund.com/software/river for more information.
+            \\
+        , .{
+            .path = path,
+            .version = build_options.version,
+        });
+        try stderr.flush();
+        posix.exit(1);
+    }
+}
+
+fn grepRiverctl(path: [:0]const u8) !bool {
+    var file = try std.fs.openFileAbsoluteZ(path, .{});
+    defer file.close();
+    var buffer: [1024]u8 = undefined;
+    var file_reader = file.reader(&buffer);
+    const reader = &file_reader.interface;
+    while (true) {
+        _ = try reader.discardDelimiterExclusive('r');
+        const bytes = reader.peekArray("riverctl".len) catch |err| switch (err) {
+            error.EndOfStream => return false,
+            else => |e| return e,
+        };
+        if (mem.eql(u8, bytes, "riverctl")) {
+            return true;
+        }
+        reader.toss(1);
+    }
+}
+
 var stderr_buffer: [1024]u8 = undefined;
 var stderr_writer = fs.File.stderr().writer(&stderr_buffer);
 const stderr = &stderr_writer.interface;