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 {