diff --git a/.builds/alpine.yml b/.builds/alpine.yml
index ae040fc..3b72907 100644
--- a/.builds/alpine.yml
+++ b/.builds/alpine.yml
@@ -37,10 +37,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.15.1/zig-x86_64-linux-0.15.1.tar.xz
- tar xf zig-x86_64-linux-0.15.1.tar.xz
- sudo mv zig-x86_64-linux-0.15.1/zig /usr/bin/
- sudo mv zig-x86_64-linux-0.15.1/lib /usr/lib/zig
+ wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz
+ tar xf zig-x86_64-linux-0.16.0.tar.xz
+ sudo mv zig-x86_64-linux-0.16.0/zig /usr/bin/
+ sudo mv zig-x86_64-linux-0.16.0/lib /usr/lib/zig
- build: |
cd river-classic
zig build --summary all
diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml
index be19c56..28bc349 100644
--- a/.builds/archlinux.yml
+++ b/.builds/archlinux.yml
@@ -34,16 +34,18 @@ 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.15.1/zig-x86_64-linux-0.15.1.tar.xz
- tar xf zig-x86_64-linux-0.15.1.tar.xz
- sudo mv zig-x86_64-linux-0.15.1/zig /usr/bin/
- sudo mv zig-x86_64-linux-0.15.1/lib /usr/lib/zig
+ wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz
+ tar xf zig-x86_64-linux-0.16.0.tar.xz
+ sudo mv zig-x86_64-linux-0.16.0/zig /usr/bin/
+ sudo mv zig-x86_64-linux-0.16.0/lib /usr/lib/zig
- build: |
cd river-classic
- zig build --summary all
+ # Arch's glibc version has SFrame sections in crt objects, force llvm/lld usage as a workaround
+ # https://codeberg.org/ziglang/zig/issues/31272
+ zig build -Dllvm --summary all
- build_xwayland: |
cd river-classic
- zig build --summary all -Dxwayland
+ zig build -Dllvm --summary all -Dxwayland
- fmt: |
cd river-classic
zig fmt --check river/
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
index 3191571..2209785 100644
--- a/.builds/freebsd.yml
+++ b/.builds/freebsd.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.15.1/zig-x86_64-freebsd-0.15.1.tar.xz
- tar xf zig-x86_64-freebsd-0.15.1.tar.xz
- sudo mv zig-x86_64-freebsd-0.15.1/zig /usr/bin/
- sudo mv zig-x86_64-freebsd-0.15.1/lib /usr/lib/zig
+ wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.16.0/zig-x86_64-freebsd-0.16.0.tar.xz
+ tar xf zig-x86_64-freebsd-0.16.0.tar.xz
+ sudo mv zig-x86_64-freebsd-0.16.0/zig /usr/bin/
+ sudo mv zig-x86_64-freebsd-0.16.0/lib /usr/lib/zig
- build: |
cd river-classic
zig build --summary all
diff --git a/.gitignore b/.gitignore
index 3389c86..03cb27d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
.zig-cache/
zig-out/
+zig-pkg/
diff --git a/README.md b/README.md
index 0f84f01..90e06fc 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ To compile river-classic 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.15
+- [zig](https://ziglang.org/download/) 0.16
- wayland
- wayland-protocols
- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.20
diff --git a/build.zig b/build.zig
index 81128dc..0ba03fb 100644
--- a/build.zig
+++ b/build.zig
@@ -4,6 +4,9 @@ const Build = std.Build;
const fs = std.fs;
const mem = std.mem;
+const manifest = @import("build.zig.zon");
+const version = manifest.version;
+
const Scanner = @import("wayland").Scanner;
pub fn build(b: *Build) !void {
@@ -12,6 +15,7 @@ pub fn build(b: *Build) !void {
const strip = b.option(bool, "strip", "Omit debug information") orelse false;
const pie = b.option(bool, "pie", "Build a Position Independent Executable") orelse false;
+ const use_llvm = b.option(bool, "llvm", "Force use of Zig's LLVM backend and the lld linker") orelse false;
const omit_frame_pointer = switch (optimize) {
.Debug, .ReleaseSafe => false,
@@ -25,7 +29,6 @@ pub fn build(b: *Build) !void {
) orelse scdoc_found: {
_ = b.findProgram(&.{"scdoc"}, &.{}) catch |err| switch (err) {
error.FileNotFound => break :scdoc_found false,
- else => return err,
};
break :scdoc_found true;
};
@@ -61,7 +64,7 @@ pub fn build(b: *Build) !void {
const git_describe_long = b.runAllowFail(
&.{ "git", "-C", b.build_root.path orelse ".", "describe", "--long" },
&ret,
- .Inherit,
+ .inherit,
) catch break :blk version;
var it = mem.splitScalar(u8, mem.trim(u8, git_describe_long, &std.ascii.whitespace), '-');
@@ -162,17 +165,19 @@ pub fn build(b: *Build) !void {
.target = target,
.optimize = optimize,
.strip = strip,
+ .link_libc = true,
}),
+ .use_llvm = use_llvm,
+ .use_lld = use_llvm,
});
river.root_module.addOptions("build_options", options);
- river.linkLibC();
- river.linkSystemLibrary("libevdev");
- river.linkSystemLibrary("libinput");
- river.linkSystemLibrary("wayland-server");
- river.linkSystemLibrary("wlroots-0.20");
- river.linkSystemLibrary("xkbcommon");
- river.linkSystemLibrary("pixman-1");
+ river.root_module.linkSystemLibrary("libevdev", .{});
+ river.root_module.linkSystemLibrary("libinput", .{});
+ river.root_module.linkSystemLibrary("wayland-server", .{});
+ river.root_module.linkSystemLibrary("wlroots-0.20", .{});
+ river.root_module.linkSystemLibrary("xkbcommon", .{});
+ river.root_module.linkSystemLibrary("pixman-1", .{});
river.root_module.addImport("wayland", wayland);
river.root_module.addImport("xkbcommon", xkbcommon);
@@ -181,7 +186,7 @@ pub fn build(b: *Build) !void {
river.root_module.addImport("flags", flags);
river.root_module.addImport("globber", globber);
- river.addCSourceFile(.{
+ river.root_module.addCSourceFile(.{
.file = b.path("river/wlroots_log_wrapper.c"),
.flags = &.{ "-std=c99", "-O2" },
});
@@ -200,14 +205,16 @@ pub fn build(b: *Build) !void {
.target = target,
.optimize = optimize,
.strip = strip,
+ .link_libc = true,
}),
+ .use_llvm = use_llvm,
+ .use_lld = use_llvm,
});
riverctl.root_module.addOptions("build_options", options);
riverctl.root_module.addImport("flags", flags);
riverctl.root_module.addImport("wayland", wayland);
- riverctl.linkLibC();
- riverctl.linkSystemLibrary("wayland-client");
+ riverctl.root_module.linkSystemLibrary("wayland-client", .{});
riverctl.pie = pie;
riverctl.root_module.omit_frame_pointer = omit_frame_pointer;
@@ -223,14 +230,16 @@ pub fn build(b: *Build) !void {
.target = target,
.optimize = optimize,
.strip = strip,
+ .link_libc = true,
}),
+ .use_llvm = use_llvm,
+ .use_lld = use_llvm,
});
rivertile.root_module.addOptions("build_options", options);
rivertile.root_module.addImport("flags", flags);
rivertile.root_module.addImport("wayland", wayland);
- rivertile.linkLibC();
- rivertile.linkSystemLibrary("wayland-client");
+ rivertile.root_module.linkSystemLibrary("wayland-client", .{});
rivertile.pie = pie;
rivertile.root_module.omit_frame_pointer = omit_frame_pointer;
@@ -263,7 +272,7 @@ pub fn build(b: *Build) !void {
// This makes the caching work for the Workaround, and the extra argument is ignored by /bin/sh.
scdoc.addFileArg(b.path("doc/" ++ page ++ ".1.scd"));
- const stdout = scdoc.captureStdOut();
+ const stdout = scdoc.captureStdOut(.{});
b.getInstallStep().dependOn(&b.addInstallFile(stdout, "share/man/man1/" ++ page ++ ".1").step);
}
}
@@ -294,31 +303,3 @@ 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 ba347f5..6c5f5ec 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -13,12 +13,12 @@
.hash = "pixman-0.3.0-LClMnz2VAAAs7QSCGwLimV5VUYx0JFnX5xWU6HwtMuDX",
},
.wayland = .{
- .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.4.0.tar.gz",
- .hash = "wayland-0.4.0-lQa1khbMAQAsLS2eBR7M5lofyEGPIbu2iFDmoz8lPC27",
+ .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz",
+ .hash = "wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX",
},
.wlroots = .{
- .url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.20.0.tar.gz",
- .hash = "wlroots-0.20.0-jmOlcmtCBADS6eoJ6mkeiSNZkibrhD-c5Qwn-LiM86r1",
+ .url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.20.1.tar.gz",
+ .hash = "wlroots-0.20.1-jmOlcqNVBAB3uB5oqBTzpRlwu-FmMyyZMVAWCe5kmcSt",
},
.xkbcommon = .{
.url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.3.0.tar.gz",
diff --git a/common/flags.zig b/common/flags.zig
index 79adde9..72f197f 100644
--- a/common/flags.zig
+++ b/common/flags.zig
@@ -1,82 +1,51 @@
-// Zero allocation argument parsing for unix-like systems.
-// Released under the Zero Clause BSD (0BSD) license:
-//
-// Copyright 2023 Isaac Freund
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+// SPDX-FileCopyrightText: © 2023 Isaac Freund
+// SPDX-License-Identifier: 0BSD
const std = @import("std");
const mem = std.mem;
pub const Flag = struct {
- name: [:0]const u8,
+ name: []const u8,
kind: enum { boolean, arg },
};
-pub fn parser(comptime Arg: type, comptime flags: []const Flag) type {
- switch (Arg) {
- // TODO consider allowing []const u8
- [:0]const u8, [*:0]const u8 => {}, // ok
- else => @compileError("invalid argument type: " ++ @typeName(Arg)),
- }
+pub fn parser(comptime flags: []const Flag) type {
return struct {
pub const Result = struct {
/// Remaining args after the recognized flags
- args: []const Arg,
+ args: []const [:0]const u8,
/// Data obtained from parsed flags
flags: Flags,
pub const Flags = flags_type: {
- var fields: []const std.builtin.Type.StructField = &.{};
- for (flags) |flag| {
- const field: std.builtin.Type.StructField = switch (flag.kind) {
- .boolean => .{
- .name = flag.name,
- .type = bool,
- .default_value_ptr = &false,
- .is_comptime = false,
- .alignment = @alignOf(bool),
+ const Attributes = std.builtin.Type.StructField.Attributes;
+ var names: [flags.len][]const u8 = undefined;
+ var types: [flags.len]type = undefined;
+ var attrs: [flags.len]Attributes = undefined;
+ for (flags, &names, &types, &attrs) |flag, *name, *ty, *attr| {
+ name.* = flag.name;
+ switch (flag.kind) {
+ .boolean => {
+ ty.* = bool;
+ attr.* = .{ .default_value_ptr = &false };
},
- .arg => .{
- .name = flag.name,
- .type = ?[:0]const u8,
- .default_value_ptr = &@as(?[:0]const u8, null),
- .is_comptime = false,
- .alignment = @alignOf(?[:0]const u8),
+ .arg => {
+ ty.* = ?[:0]const u8;
+ attr.* = .{ .default_value_ptr = &@as(ty.*, null) };
},
- };
- fields = fields ++ [_]std.builtin.Type.StructField{field};
+ }
}
- break :flags_type @Type(.{ .@"struct" = .{
- .layout = .auto,
- .fields = fields,
- .decls = &.{},
- .is_tuple = false,
- } });
+ break :flags_type @Struct(.auto, null, &names, &types, &attrs);
};
};
- pub fn parse(args: []const Arg) !Result {
+ pub fn parse(args: []const [:0]const u8) error{MissingFlagArgument}!Result {
var result_flags: Result.Flags = .{};
var i: usize = 0;
outer: while (i < args.len) : (i += 1) {
- const arg = switch (Arg) {
- [*:0]const u8 => mem.sliceTo(args[i], 0),
- [:0]const u8 => args[i],
- else => unreachable,
- };
inline for (flags) |flag| {
- if (mem.eql(u8, "-" ++ flag.name, arg)) {
+ if (mem.eql(u8, "-" ++ flag.name, args[i])) {
switch (flag.kind) {
.boolean => @field(result_flags, flag.name) = true,
.arg => {
@@ -86,11 +55,7 @@ pub fn parser(comptime Arg: type, comptime flags: []const Flag) type {
"' requires an argument but none was provided!", .{});
return error.MissingFlagArgument;
}
- @field(result_flags, flag.name) = switch (Arg) {
- [*:0]const u8 => mem.sliceTo(args[i], 0),
- [:0]const u8 => args[i],
- else => unreachable,
- };
+ @field(result_flags, flag.name) = args[i];
},
}
continue :outer;
diff --git a/river/Control.zig b/river/Control.zig
index 06e30d8..f55ea14 100644
--- a/river/Control.zig
+++ b/river/Control.zig
@@ -59,7 +59,7 @@ fn bind(client: *wl.Client, control: *Control, version: u32, id: u32) void {
client.postNoMemory();
return;
};
- control.args_map.putNoClobber(.{ .client = client, .id = id }, .{}) catch {
+ control.args_map.putNoClobber(.{ .client = client, .id = id }, .empty) catch {
control_v1.destroy();
client.postNoMemory();
return;
diff --git a/river/Cursor.zig b/river/Cursor.zig
index 0d5c472..feb19d2 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -158,7 +158,7 @@ focus_follows_cursor_target: ?*View = null,
/// Keeps track of the last known location of all touch points in layout coordinates.
/// This information is necessary for proper touch dnd support if there are multiple touch points.
-touch_points: std.AutoHashMapUnmanaged(i32, LayoutPoint) = .{},
+touch_points: std.AutoHashMapUnmanaged(i32, LayoutPoint) = .empty,
axis: wl.Listener(*wlr.Pointer.event.Axis) = wl.Listener(*wlr.Pointer.event.Axis).init(handleAxis),
frame: wl.Listener(*wlr.Cursor) = wl.Listener(*wlr.Cursor).init(handleFrame),
@@ -1151,15 +1151,7 @@ pub fn updateState(cursor: *Cursor) void {
.passthrough => {
cursor.updateFocusFollowsCursorTarget();
if (!cursor.hidden) {
- const now = posix.clock_gettime(.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported");
- // 2^32-1 milliseconds is ~50 days, which is a realistic uptime.
- // This means that we must wrap if the monotonic time is greater than
- // 2^32-1 milliseconds and hope that clients don't get too confused.
- const msec: u32 = @intCast(@rem(
- now.sec *% std.time.ms_per_s +% @divTrunc(now.nsec, std.time.ns_per_ms),
- math.maxInt(u32),
- ));
- cursor.passthrough(msec);
+ cursor.passthrough(util.msecTimestamp());
}
},
// TODO: Leave down mode if the target surface is no longer visible.
diff --git a/river/InputManager.zig b/river/InputManager.zig
index 96df752..b523276 100644
--- a/river/InputManager.zig
+++ b/river/InputManager.zig
@@ -55,7 +55,7 @@ tablet_manager: *wlr.TabletManagerV2,
/// List of input device configurations. Ordered by glob generality, with
/// the most general towards the start and the most specific towards the end.
-configs: std.ArrayList(InputConfig) = .{},
+configs: std.ArrayList(InputConfig) = .empty,
devices: wl.list.Head(InputDevice, .link),
seats: wl.list.Head(Seat, .link),
diff --git a/river/Mode.zig b/river/Mode.zig
index d0f70d2..1cd4f98 100644
--- a/river/Mode.zig
+++ b/river/Mode.zig
@@ -24,9 +24,9 @@ const PointerMapping = @import("PointerMapping.zig");
const SwitchMapping = @import("SwitchMapping.zig");
name: [:0]const u8,
-mappings: std.ArrayListUnmanaged(Mapping) = .{},
-pointer_mappings: std.ArrayListUnmanaged(PointerMapping) = .{},
-switch_mappings: std.ArrayListUnmanaged(SwitchMapping) = .{},
+mappings: std.ArrayList(Mapping) = .empty,
+pointer_mappings: std.ArrayList(PointerMapping) = .empty,
+switch_mappings: std.ArrayList(SwitchMapping) = .empty,
pub fn deinit(mode: *Mode) void {
util.gpa.free(mode.name);
diff --git a/river/Output.zig b/river/Output.zig
index 7a8228c..5b20a41 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -537,7 +537,7 @@ fn handleFrame(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
error.CommitFailed => log.err("output commit failed for {s}", .{output.wlr_output.name}),
};
- var now = posix.clock_gettime(.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported");
+ var now = util.timestamp();
scene_output.sendFrameDone(&now);
}
diff --git a/river/OutputStatus.zig b/river/OutputStatus.zig
index 7e05cf8..a6a4157 100644
--- a/river/OutputStatus.zig
+++ b/river/OutputStatus.zig
@@ -31,7 +31,7 @@ const View = @import("View.zig");
const log = std.log.scoped(.river_status);
resources: wl.list.Head(zriver.OutputStatusV1, null),
-view_tags: std.ArrayListUnmanaged(u32) = .{},
+view_tags: std.ArrayList(u32) = .empty,
focused_tags: u32 = 0,
urgent_tags: u32 = 0,
diff --git a/river/Seat.zig b/river/Seat.zig
index ae7e298..20ab1f7 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -471,10 +471,7 @@ pub fn runCommand(seat: *Seat, args: []const [:0]const u8) void {
return;
};
if (out) |s| {
- var stdout = std.fs.File.stdout().writer(&.{});
- stdout.interface.print("{s}", .{s}) catch |err| {
- std.log.scoped(.command).err("{s}: write to stdout failed {}", .{ args[0], err });
- };
+ std.log.scoped(.command).info("mapped command output: {s}", .{s});
}
}
diff --git a/river/Server.zig b/river/Server.zig
index c7b5663..dc277c5 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -148,8 +148,8 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
server.* = .{
.wl_server = wl_server,
- .sigint_source = try loop.addSignal(*wl.Server, posix.SIG.INT, terminate, wl_server),
- .sigterm_source = try loop.addSignal(*wl.Server, posix.SIG.TERM, terminate, wl_server),
+ .sigint_source = try loop.addSignal(*wl.Server, @intFromEnum(posix.SIG.INT), terminate, wl_server),
+ .sigterm_source = try loop.addSignal(*wl.Server, @intFromEnum(posix.SIG.TERM), terminate, wl_server),
.fixes = try wlr.Fixes.create(wl_server, 1),
diff --git a/river/View.zig b/river/View.zig
index 8cba7d0..5e4bf74 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -498,7 +498,7 @@ pub fn rootSurface(view: View) ?*wlr.Surface {
pub fn sendFrameDone(view: View) void {
assert(view.mapped and !view.destroying);
- const now = posix.clock_gettime(.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported");
+ const now = util.timestamp();
view.rootSurface().?.sendFrameDone(&now);
}
diff --git a/river/c.zig b/river/c.zig
index 59dd8a0..9ef5b97 100644
--- a/river/c.zig
+++ b/river/c.zig
@@ -15,11 +15,6 @@
// along with this program. If not, see .
pub const c = @cImport({
- @cDefine("_POSIX_C_SOURCE", "200809L");
-
- @cInclude("stdlib.h");
- @cInclude("unistd.h");
-
@cInclude("linux/input-event-codes.h");
@cInclude("libevdev/libevdev.h");
diff --git a/river/command.zig b/river/command.zig
index c21c0b3..ad4b194 100644
--- a/river/command.zig
+++ b/river/command.zig
@@ -124,6 +124,7 @@ pub const Error = error{
ConflictingOptions,
WriteFailed,
OutOfMemory,
+ Unexpected,
Other,
};
@@ -167,6 +168,7 @@ pub fn errToMsg(err: Error) [:0]const u8 {
Error.CannotReadFile => "cannot read file",
Error.CannotParseFile => "cannot parse file",
Error.WriteFailed, Error.OutOfMemory => "out of memory",
+ Error.Unexpected => "unexpected error",
Error.Other => unreachable,
};
}
diff --git a/river/command/keyboard.zig b/river/command/keyboard.zig
index c09db1f..52e0a16 100644
--- a/river/command/keyboard.zig
+++ b/river/command/keyboard.zig
@@ -31,7 +31,7 @@ pub fn keyboardLayout(
args: []const [:0]const u8,
_: *?[]const u8,
) Error!void {
- const result = flags.parser([:0]const u8, &.{
+ const result = flags.parser(&.{
.{ .name = "rules", .kind = .arg },
.{ .name = "model", .kind = .arg },
.{ .name = "variant", .kind = .arg },
@@ -69,13 +69,15 @@ pub fn keyboardLayoutFile(
if (args.len < 2) return Error.NotEnoughArguments;
if (args.len > 2) return Error.TooManyArguments;
- const file = std.fs.cwd().openFile(args[1], .{}) catch return error.CannotReadFile;
- defer file.close();
+ const io = std.Io.Threaded.global_single_threaded.io();
+ const file = std.Io.Dir.cwd().openFile(io, args[1], .{}) catch return error.CannotReadFile;
+ defer file.close(io);
+ var reader = file.reader(io, &.{});
// 1 GiB is arbitrarily chosen as an exceedingly large but not infinite upper bound.
- const file_bytes = file.readToEndAlloc(util.gpa, 1024 * 1024 * 1024) catch |err| {
+ const file_bytes = reader.interface.allocRemaining(util.gpa, .limited(1024 * 1024 * 1024)) catch |err| {
switch (err) {
- error.FileTooBig, error.OutOfMemory => return error.OutOfMemory,
+ error.OutOfMemory => return error.OutOfMemory,
else => return error.CannotReadFile,
}
};
diff --git a/river/command/map.zig b/river/command/map.zig
index 2778fa6..cd966f4 100644
--- a/river/command/map.zig
+++ b/river/command/map.zig
@@ -42,7 +42,7 @@ pub fn map(
args: []const [:0]const u8,
out: *?[]const u8,
) Error!void {
- const result = flags.parser([:0]const u8, &.{
+ const result = flags.parser(&.{
.{ .name = "release", .kind = .boolean },
.{ .name = "repeat", .kind = .boolean },
.{ .name = "layout", .kind = .arg },
@@ -366,7 +366,7 @@ fn parseSwitchState(
/// Example:
/// unmap normal Mod4+Shift Return
pub fn unmap(seat: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!void {
- const result = flags.parser([:0]const u8, &.{
+ const result = flags.parser(&.{
.{ .name = "release", .kind = .boolean },
}).parse(args[1..]) catch {
return error.InvalidValue;
diff --git a/river/command/output.zig b/river/command/output.zig
index 6dccf80..d9bf1b3 100644
--- a/river/command/output.zig
+++ b/river/command/output.zig
@@ -54,10 +54,10 @@ pub fn sendToOutput(
_: *?[]const u8,
) Error!void {
if (args.len < 2) return Error.NotEnoughArguments;
- const result = flags.parser([:0]const u8, &.{
+ const result = flags.parser(&.{
.{ .name = "current-tags", .kind = .boolean },
}).parse(args[1..]) catch {
- return error.InvalidOption;
+ return error.InvalidValue;
};
if (result.args.len < 1) return Error.NotEnoughArguments;
if (result.args.len > 1) return Error.TooManyArguments;
diff --git a/river/command/rule.zig b/river/command/rule.zig
index 082783a..ff6b0a4 100644
--- a/river/command/rule.zig
+++ b/river/command/rule.zig
@@ -45,7 +45,7 @@ const Action = enum {
};
pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void {
- const result = flags.parser([:0]const u8, &.{
+ const result = flags.parser(&.{
.{ .name = "app-id", .kind = .arg },
.{ .name = "title", .kind = .arg },
}).parse(args[1..]) catch {
@@ -147,7 +147,7 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
}
pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void {
- const result = flags.parser([:0]const u8, &.{
+ const result = flags.parser(&.{
.{ .name = "app-id", .kind = .arg },
.{ .name = "title", .kind = .arg },
}).parse(args[1..]) catch {
@@ -218,7 +218,7 @@ fn apply_tearing_rules() void {
}
}
-fn alignLeft(buf: []const u8, width: usize, writer: *std.io.Writer) Error!void {
+fn alignLeft(buf: []const u8, width: usize, writer: *std.Io.Writer) Error!void {
assert(buf.len <= width);
try writer.writeAll(buf);
try writer.splatByteAll(' ', width - buf.len);
@@ -244,7 +244,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
const app_id_column_max = 2 + @max("app-id".len, max_glob_len.app_id);
const title_column_max = 2 + @max("title".len, max_glob_len.title);
- var buffer = std.io.Writer.Allocating.init(util.gpa);
+ var buffer = std.Io.Writer.Allocating.init(util.gpa);
defer buffer.deinit();
const writer = &buffer.writer;
diff --git a/river/command/spawn.zig b/river/command/spawn.zig
index 0462abe..ba63731 100644
--- a/river/command/spawn.zig
+++ b/river/command/spawn.zig
@@ -15,9 +15,8 @@
// along with this program. If not, see .
const std = @import("std");
-const posix = std.posix;
+const c = std.c;
-const c = @import("../c.zig").c;
const util = @import("../util.zig");
const process = @import("../process.zig");
@@ -35,26 +34,46 @@ pub fn spawn(
const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", args[1], null };
- const pid = posix.fork() catch {
- out.* = try std.fmt.allocPrint(util.gpa, "fork/execve failed", .{});
- return Error.Other;
+ const pid: c.pid_t = blk: {
+ const rc = c.fork();
+ if (c.errno(rc) != .SUCCESS) {
+ out.* = try std.fmt.allocPrint(util.gpa, "fork/execve failed", .{});
+ return Error.Other;
+ }
+ break :blk @intCast(rc);
};
if (pid == 0) {
process.cleanupChild();
- const pid2 = posix.fork() catch c._exit(1);
+ const pid2: c.pid_t = blk: {
+ const rc = c.fork();
+ if (c.errno(rc) != .SUCCESS) {
+ c._exit(1);
+ }
+ break :blk @intCast(rc);
+ };
+
if (pid2 == 0) {
- posix.execveZ("/bin/sh", &child_args, std.c.environ) catch c._exit(1);
+ _ = c.execve("/bin/sh", &child_args, c.environ);
+ c._exit(1); // only reachable if execve fails
}
c._exit(0);
}
// Wait the intermediate child.
- const ret = posix.waitpid(pid, 0);
- if (!posix.W.IFEXITED(ret.status) or
- (posix.W.IFEXITED(ret.status) and posix.W.EXITSTATUS(ret.status) != 0))
+ const status: u32 = while (true) {
+ var status: c_int = 0;
+ switch (c.errno(c.waitpid(pid, &status, 0))) {
+ .SUCCESS => break @bitCast(status),
+ .INTR => continue,
+ else => return Error.Unexpected, // should never happen, but don't trust the kernel
+ }
+ };
+
+ if (!c.W.IFEXITED(status) or
+ (c.W.IFEXITED(status) and c.W.EXITSTATUS(status) != 0))
{
out.* = try std.fmt.allocPrint(util.gpa, "fork/execve failed", .{});
return Error.Other;
diff --git a/river/command/view_operations.zig b/river/command/view_operations.zig
index 3f8a214..eb8020b 100644
--- a/river/command/view_operations.zig
+++ b/river/command/view_operations.zig
@@ -35,7 +35,7 @@ pub fn focusView(
args: []const [:0]const u8,
_: *?[]const u8,
) Error!void {
- const result = flags.parser([:0]const u8, &.{
+ const result = flags.parser(&.{
.{ .name = "skip-floating", .kind = .boolean },
}).parse(args[1..]) catch {
return error.InvalidValue;
diff --git a/river/main.zig b/river/main.zig
index d2f9481..3346b50 100644
--- a/river/main.zig
+++ b/river/main.zig
@@ -16,15 +16,19 @@
const build_options = @import("build_options");
const std = @import("std");
-const mem = std.mem;
+const Io = std.Io;
const fs = std.fs;
const log = std.log;
+const mem = std.mem;
const posix = std.posix;
+const exit = std.process.exit;
+const fatal = std.process.fatal;
+const c = std.c;
+
const builtin = @import("builtin");
const wlr = @import("wlroots");
const flags = @import("flags");
-const c = @import("c.zig").c;
const util = @import("util.zig");
const process = @import("process.zig");
@@ -43,30 +47,43 @@ const usage: []const u8 =
pub var server: Server = undefined;
-pub fn main() anyerror!void {
- const result = flags.parser([*:0]const u8, &.{
+pub fn main(init: std.process.Init.Minimal) anyerror!void {
+ const io = std.Io.Threaded.global_single_threaded.io();
+
+ var stdout_buffer: [64]u8 = undefined;
+ var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
+ const stdout = &stdout_writer.interface;
+
+ var stderr_buffer: [64]u8 = undefined;
+ var stderr_writer = Io.File.stderr().writer(io, &stderr_buffer);
+ const stderr = &stderr_writer.interface;
+
+ const args = try init.args.toSlice(util.gpa);
+ defer util.gpa.free(args);
+
+ const result = flags.parser(&.{
.{ .name = "h", .kind = .boolean },
.{ .name = "version", .kind = .boolean },
.{ .name = "c", .kind = .arg },
.{ .name = "log-level", .kind = .arg },
.{ .name = "no-xwayland", .kind = .boolean },
- }).parse(std.os.argv[1..]) catch {
- try fs.File.stderr().writeAll(usage);
- posix.exit(1);
+ }).parse(args[1..]) catch {
+ try stderr.writeAll(usage);
+ exit(1);
};
if (result.flags.h) {
- try fs.File.stdout().writeAll(usage);
- posix.exit(0);
+ try stdout.writeAll(usage);
+ exit(0);
}
if (result.args.len != 0) {
log.err("unknown option '{s}'", .{result.args[0]});
- try fs.File.stderr().writeAll(usage);
- posix.exit(1);
+ try stderr.writeAll(usage);
+ exit(1);
}
if (result.flags.version) {
- try fs.File.stdout().writeAll(build_options.version ++ "\n");
- posix.exit(0);
+ try stdout.writeAll(build_options.version ++ "\n");
+ exit(0);
}
if (result.flags.@"log-level") |level| {
if (mem.eql(u8, level, "error")) {
@@ -79,8 +96,8 @@ pub fn main() anyerror!void {
runtime_log_level = .debug;
} else {
log.err("invalid log level '{s}'", .{level});
- try fs.File.stderr().writeAll(usage);
- posix.exit(1);
+ try stderr.writeAll(usage);
+ exit(1);
}
}
const runtime_xwayland = !result.flags.@"no-xwayland";
@@ -88,7 +105,7 @@ pub fn main() anyerror!void {
if (result.flags.c) |command| {
break :blk try util.gpa.dupeZ(u8, command);
} else {
- break :blk try defaultInitPath();
+ break :blk try defaultInitPath(io, init.environ);
}
};
@@ -120,10 +137,20 @@ pub fn main() anyerror!void {
const child_pgid = if (startup_command) |cmd| blk: {
log.info("running init executable '{s}'", .{cmd});
const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", cmd, null };
- const pid = try posix.fork();
+
+ const pid: c.pid_t = pid: {
+ const rc = c.fork();
+ switch (c.errno(rc)) {
+ .SUCCESS => {},
+ else => |err| fatal("failed to start init process: {}", .{err}),
+ }
+ break :pid @intCast(rc);
+ };
+
if (pid == 0) {
process.cleanupChild();
- posix.execveZ("/bin/sh", &child_args, std.c.environ) catch c._exit(1);
+ _ = c.execve("/bin/sh", &child_args, c.environ);
+ c._exit(1); // only reachable if execve fails
}
util.gpa.free(cmd);
// Since the child has called setsid, the pid is the pgid
@@ -140,22 +167,21 @@ pub fn main() anyerror!void {
log.info("shutting down", .{});
}
-fn defaultInitPath() !?[:0]const u8 {
+fn defaultInitPath(io: Io, environ: std.process.Environ) !?[:0]const u8 {
const path = blk: {
- if (posix.getenv("XDG_CONFIG_HOME")) |xdg_config_home| {
+ if (environ.getPosix("XDG_CONFIG_HOME")) |xdg_config_home| {
break :blk try fs.path.joinZ(util.gpa, &[_][]const u8{ xdg_config_home, "river/init" });
- } else if (posix.getenv("HOME")) |home| {
+ } else if (environ.getPosix("HOME")) |home| {
break :blk try fs.path.joinZ(util.gpa, &[_][]const u8{ home, ".config/river/init" });
} else {
return null;
}
};
- posix.accessZ(path, posix.X_OK) catch |err| {
+ Io.Dir.cwd().access(io, path, .{ .execute = true }) catch |err| {
if (err == error.PermissionDenied) {
- if (posix.accessZ(path, posix.R_OK)) {
- log.err("failed to run init executable {s}: the file is not executable", .{path});
- posix.exit(1);
+ if (Io.Dir.cwd().access(io, path, .{})) {
+ fatal("failed to run init executable {s}: the file is not executable", .{path});
} else |_| {}
}
log.err("failed to run init executable {s}: {s}", .{ path, @errorName(err) });
@@ -186,13 +212,7 @@ pub fn logFn(
) void {
if (@intFromEnum(level) > @intFromEnum(runtime_log_level)) return;
- const scope_prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
-
- var buffer: [256]u8 = undefined;
- const stderr = std.debug.lockStderrWriter(&buffer);
- defer std.debug.unlockStderrWriter();
-
- stderr.print(level.asText() ++ scope_prefix ++ format ++ "\n", args) catch {};
+ std.log.defaultLog(level, scope, format, args);
}
/// See wlroots_log_wrapper.c
diff --git a/river/process.zig b/river/process.zig
index 7709a7d..2189f6a 100644
--- a/river/process.zig
+++ b/river/process.zig
@@ -17,8 +17,6 @@
const std = @import("std");
const posix = std.posix;
-const c = @import("c.zig").c;
-
var original_rlimit: ?posix.rlimit = null;
pub fn setup() void {
@@ -60,7 +58,7 @@ pub fn setup() void {
}
pub fn cleanupChild() void {
- if (c.setsid() < 0) unreachable;
+ if (std.c.setsid() < 0) unreachable;
if (posix.system.sigprocmask(posix.SIG.SETMASK, &posix.sigemptyset(), null) < 0) unreachable;
const sig_dfl = posix.Sigaction{
diff --git a/river/rule_list.zig b/river/rule_list.zig
index 8c5edd3..f4b133f 100644
--- a/river/rule_list.zig
+++ b/river/rule_list.zig
@@ -45,7 +45,7 @@ pub fn RuleList(comptime T: type) type {
/// Ordered from most specific to most general.
/// Ordered first by app-id generality then by title generality.
- rules: std.ArrayListUnmanaged(Rule) = .{},
+ rules: std.ArrayList(Rule) = .empty,
pub fn deinit(list: *List) void {
for (list.rules.items) |rule| {
diff --git a/river/util.zig b/river/util.zig
index efbaf34..d8c4042 100644
--- a/river/util.zig
+++ b/river/util.zig
@@ -18,3 +18,22 @@ const std = @import("std");
/// The global general-purpose allocator used throughout river's code
pub const gpa = std.heap.c_allocator;
+
+pub fn timestamp() std.c.timespec {
+ var timespec: std.c.timespec = undefined;
+ switch (std.c.errno(std.c.clock_gettime(std.c.CLOCK.MONOTONIC, ×pec))) {
+ .SUCCESS => return timespec,
+ else => @panic("CLOCK_MONOTONIC not supported"),
+ }
+}
+
+pub fn msecTimestamp() u32 {
+ const now = timestamp();
+ // 2^32-1 milliseconds is ~50 days, which is a realistic uptime.
+ // This means that we must wrap if the monotonic time is greater than
+ // 2^32-1 milliseconds and hope that clients don't get too confused.
+ return @intCast(@rem(
+ now.sec *% std.time.ms_per_s +% @divTrunc(now.nsec, std.time.ns_per_ms),
+ std.math.maxInt(u32),
+ ));
+}
diff --git a/riverctl/main.zig b/riverctl/main.zig
index 39abacf..840fa9f 100644
--- a/riverctl/main.zig
+++ b/riverctl/main.zig
@@ -17,8 +17,11 @@
const std = @import("std");
const mem = std.mem;
const fs = std.fs;
+const Io = std.Io;
const posix = std.posix;
const assert = std.debug.assert;
+const process = std.process;
+const fatal = process.fatal;
const builtin = @import("builtin");
const wayland = @import("wayland");
@@ -38,6 +41,15 @@ const usage =
\\
;
+const io = Io.Threaded.global_single_threaded.io();
+var stdout_buffer: [64]u8 = undefined;
+var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
+const stdout = &stdout_writer.interface;
+
+var stderr_buffer: [64]u8 = undefined;
+var stderr_writer = Io.File.stderr().writer(io, &stderr_buffer);
+const stderr = &stderr_writer.interface;
+
const gpa = std.heap.c_allocator;
pub const Globals = struct {
@@ -45,8 +57,8 @@ pub const Globals = struct {
seat: ?*wl.Seat = null,
};
-pub fn main() !void {
- _main() catch |err| {
+pub fn main(init: std.process.Init.Minimal) !void {
+ _main(init) catch |err| {
switch (err) {
error.RiverControlNotAdvertised => fatal(
\\The Wayland server does not support river-control-unstable-v1.
@@ -57,7 +69,7 @@ pub fn main() !void {
, .{}),
error.ConnectFailed => {
std.log.err("Unable to connect to the Wayland server.", .{});
- if (posix.getenvZ("WAYLAND_DISPLAY") == null) {
+ if (init.environ.getPosix("WAYLAND_DISPLAY") == null) {
fatal("WAYLAND_DISPLAY is not set.", .{});
} else {
fatal("Does WAYLAND_DISPLAY contain the socket name of a running server?", .{});
@@ -68,21 +80,24 @@ pub fn main() !void {
};
}
-fn _main() !void {
- const result = flags.parser([*:0]const u8, &.{
+fn _main(init: std.process.Init.Minimal) !void {
+ const args = try init.args.toSlice(gpa);
+ defer gpa.free(args);
+
+ const result = flags.parser(&.{
.{ .name = "h", .kind = .boolean },
.{ .name = "version", .kind = .boolean },
- }).parse(std.os.argv[1..]) catch {
- try fs.File.stderr().writeAll(usage);
- posix.exit(1);
+ }).parse(args[1..]) catch {
+ try stderr.writeAll(usage);
+ process.exit(1);
};
if (result.flags.h) {
- try fs.File.stdout().writeAll(usage);
- posix.exit(0);
+ try stdout.writeAll(usage);
+ process.exit(0);
}
if (result.flags.version) {
- try fs.File.stdout().writeAll(@import("build_options").version ++ "\n");
- posix.exit(0);
+ try stdout.writeAll(@import("build_options").version ++ "\n");
+ process.exit(0);
}
const display = try wl.Display.connect(null);
@@ -125,24 +140,18 @@ fn callbackListener(_: *zriver.CommandCallbackV1, event: zriver.CommandCallbackV
switch (event) {
.success => |success| {
if (mem.len(success.output) > 0) {
- var stdout = fs.File.stdout().writer(&.{});
- stdout.interface.print("{s}\n", .{success.output}) catch @panic("failed to write to stdout");
+ stdout.print("{s}\n", .{success.output}) catch @panic("failed to write to stdout");
}
- posix.exit(0);
+ process.exit(0);
},
.failure => |failure| {
// A small hack to provide usage text when river reports an unknown command.
if (mem.orderZ(u8, failure.failure_message, "unknown command") == .eq) {
std.log.err("unknown command", .{});
- fs.File.stderr().writeAll(usage) catch {};
- posix.exit(1);
+ stderr.writeAll(usage) catch {};
+ process.exit(1);
}
fatal("{s}", .{failure.failure_message});
},
}
}
-
-fn fatal(comptime format: []const u8, args: anytype) noreturn {
- std.log.err(format, args);
- posix.exit(1);
-}
diff --git a/rivertile/main.zig b/rivertile/main.zig
index 972641e..31b994a 100644
--- a/rivertile/main.zig
+++ b/rivertile/main.zig
@@ -40,6 +40,8 @@ const fmt = std.fmt;
const mem = std.mem;
const math = std.math;
const posix = std.posix;
+const process = std.process;
+const fatal = std.process.fatal;
const assert = std.debug.assert;
const wayland = @import("wayland");
@@ -307,8 +309,20 @@ const Output = struct {
}
};
-pub fn main() !void {
- const result = flags.parser([*:0]const u8, &[_]flags.Flag{
+pub fn main(init: std.process.Init.Minimal) !void {
+ const args = try init.args.toSlice(gpa);
+ defer gpa.free(args);
+
+ const io = std.Io.Threaded.global_single_threaded.io();
+ var stdout_buffer: [64]u8 = undefined;
+ var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer);
+ const stdout = &stdout_writer.interface;
+
+ var stderr_buffer: [64]u8 = undefined;
+ var stderr_writer = std.Io.File.stderr().writer(io, &stderr_buffer);
+ const stderr = &stderr_writer.interface;
+
+ const result = flags.parser(&.{
.{ .name = "h", .kind = .boolean },
.{ .name = "version", .kind = .boolean },
.{ .name = "view-padding", .kind = .arg },
@@ -316,19 +330,19 @@ pub fn main() !void {
.{ .name = "main-location", .kind = .arg },
.{ .name = "main-count", .kind = .arg },
.{ .name = "main-ratio", .kind = .arg },
- }).parse(std.os.argv[1..]) catch {
- try std.fs.File.stderr().writeAll(usage);
- posix.exit(1);
+ }).parse(args[1..]) catch {
+ try stderr.writeAll(usage);
+ process.exit(1);
};
if (result.flags.h) {
- try std.fs.File.stdout().writeAll(usage);
- posix.exit(0);
+ try stdout.writeAll(usage);
+ process.exit(0);
}
if (result.args.len != 0) fatalPrintUsage("unknown option '{s}'", .{result.args[0]});
if (result.flags.version) {
- try std.fs.File.stdout().writeAll(@import("build_options").version ++ "\n");
- posix.exit(0);
+ try stdout.writeAll(@import("build_options").version ++ "\n");
+ process.exit(0);
}
if (result.flags.@"view-padding") |raw| {
view_padding = fmt.parseUnsigned(u31, raw, 10) catch
@@ -356,8 +370,7 @@ pub fn main() !void {
}
const display = wl.Display.connect(null) catch {
- std.debug.print("Unable to connect to Wayland server.\n", .{});
- posix.exit(1);
+ fatal("Unable to connect to Wayland server.\n", .{});
};
defer display.disconnect();
@@ -407,15 +420,10 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *
}
}
-fn fatal(comptime format: []const u8, args: anytype) noreturn {
- std.log.err(format, args);
- posix.exit(1);
-}
-
fn fatalPrintUsage(comptime format: []const u8, args: anytype) noreturn {
std.log.err(format, args);
- std.fs.File.stderr().writeAll(usage) catch {};
- posix.exit(1);
+ std.debug.print(usage, .{});
+ process.exit(1);
}
fn saturatingCast(comptime T: type, x: anytype) T {