Compare commits

...

10 Commits

Author SHA1 Message Date
Zander671 050b9976f1 Merge branch 'main' of https://codeberg.org/river/river-classic 2026-05-03 06:09:03 -07:00
Isaac Freund 273f31016a build: bump version to 0.3.18-dev 2026-04-30 09:05:14 +02:00
Isaac Freund 53405ec4df build: bump version to 0.3.17 2026-04-30 09:03:31 +02:00
Isaac Freund 0d31ec658a riverctl, rivertile: don't forget to flush() 2026-04-30 09:02:38 +02:00
Adam C. Stephens 6962fe20be river: flush output before exiting 2026-04-30 08:59:57 +02:00
Isaac Freund 3456deeed4 build: bump version to 0.3.17-dev 2026-04-29 15:15:45 +02:00
Isaac Freund 2089f84b21 build: bump version to 0.3.16 2026-04-29 15:15:00 +02:00
Isaac Freund 5d8a493057 build: switch to translate-c package
We require a recent version for FreeBSD compatibility

References: https://codeberg.org/ziglang/translate-c/pulls/331
2026-04-29 13:33:52 +02:00
Isaac Freund 49721c5641 XdgToplevel: clip capture scene tree to geometry 2026-04-29 13:29:09 +02:00
Isaac Freund a955ae21e5 build: update to Zig 0.16 2026-04-29 13:22:40 +02:00
35 changed files with 298 additions and 277 deletions
+4 -4
View File
@@ -37,10 +37,10 @@ tasks:
cd .. cd ..
# Eat Github's resources rather than the Zig Software Foundation's resources! # 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 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.15.1.tar.xz tar xf zig-x86_64-linux-0.16.0.tar.xz
sudo mv zig-x86_64-linux-0.15.1/zig /usr/bin/ sudo mv zig-x86_64-linux-0.16.0/zig /usr/bin/
sudo mv zig-x86_64-linux-0.15.1/lib /usr/lib/zig sudo mv zig-x86_64-linux-0.16.0/lib /usr/lib/zig
- build: | - build: |
cd river-classic cd river-classic
zig build --summary all zig build --summary all
+8 -6
View File
@@ -34,16 +34,18 @@ tasks:
cd .. cd ..
# Eat Github's resources rather than the Zig Software Foundation's resources! # 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 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.15.1.tar.xz tar xf zig-x86_64-linux-0.16.0.tar.xz
sudo mv zig-x86_64-linux-0.15.1/zig /usr/bin/ sudo mv zig-x86_64-linux-0.16.0/zig /usr/bin/
sudo mv zig-x86_64-linux-0.15.1/lib /usr/lib/zig sudo mv zig-x86_64-linux-0.16.0/lib /usr/lib/zig
- build: | - build: |
cd river-classic 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: | - build_xwayland: |
cd river-classic cd river-classic
zig build --summary all -Dxwayland zig build -Dllvm --summary all -Dxwayland
- fmt: | - fmt: |
cd river-classic cd river-classic
zig fmt --check river/ zig fmt --check river/
+4 -4
View File
@@ -41,10 +41,10 @@ tasks:
cd .. cd ..
# Eat Github's resources rather than the Zig Software Foundation's resources! # 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 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.15.1.tar.xz tar xf zig-x86_64-freebsd-0.16.0.tar.xz
sudo mv zig-x86_64-freebsd-0.15.1/zig /usr/bin/ sudo mv zig-x86_64-freebsd-0.16.0/zig /usr/bin/
sudo mv zig-x86_64-freebsd-0.15.1/lib /usr/lib/zig sudo mv zig-x86_64-freebsd-0.16.0/lib /usr/lib/zig
- build: | - build: |
cd river-classic cd river-classic
zig build --summary all zig build --summary all
+1
View File
@@ -1,2 +1,3 @@
.zig-cache/ .zig-cache/
zig-out/ zig-out/
zig-pkg/
+1 -1
View File
@@ -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 installed. The "development" versions are required if applicable to your
distribution. distribution.
- [zig](https://ziglang.org/download/) 0.15 - [zig](https://ziglang.org/download/) 0.16
- wayland - wayland
- wayland-protocols - wayland-protocols
- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.20 - [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.20
+35 -43
View File
@@ -4,7 +4,11 @@ const Build = std.Build;
const fs = std.fs; const fs = std.fs;
const mem = std.mem; const mem = std.mem;
const manifest = @import("build.zig.zon");
const version = manifest.version;
const Scanner = @import("wayland").Scanner; const Scanner = @import("wayland").Scanner;
const Translator = @import("translate_c").Translator;
pub fn build(b: *Build) !void { pub fn build(b: *Build) !void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
@@ -12,6 +16,7 @@ pub fn build(b: *Build) !void {
const strip = b.option(bool, "strip", "Omit debug information") orelse false; 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 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) { const omit_frame_pointer = switch (optimize) {
.Debug, .ReleaseSafe => false, .Debug, .ReleaseSafe => false,
@@ -25,7 +30,6 @@ pub fn build(b: *Build) !void {
) orelse scdoc_found: { ) orelse scdoc_found: {
_ = b.findProgram(&.{"scdoc"}, &.{}) catch |err| switch (err) { _ = b.findProgram(&.{"scdoc"}, &.{}) catch |err| switch (err) {
error.FileNotFound => break :scdoc_found false, error.FileNotFound => break :scdoc_found false,
else => return err,
}; };
break :scdoc_found true; break :scdoc_found true;
}; };
@@ -61,7 +65,7 @@ pub fn build(b: *Build) !void {
const git_describe_long = b.runAllowFail( const git_describe_long = b.runAllowFail(
&.{ "git", "-C", b.build_root.path orelse ".", "describe", "--long" }, &.{ "git", "-C", b.build_root.path orelse ".", "describe", "--long" },
&ret, &ret,
.Inherit, .inherit,
) catch break :blk version; ) catch break :blk version;
var it = mem.splitScalar(u8, mem.trim(u8, git_describe_long, &std.ascii.whitespace), '-'); var it = mem.splitScalar(u8, mem.trim(u8, git_describe_long, &std.ascii.whitespace), '-');
@@ -154,6 +158,15 @@ pub fn build(b: *Build) !void {
const flags = b.createModule(.{ .root_source_file = b.path("common/flags.zig") }); const flags = b.createModule(.{ .root_source_file = b.path("common/flags.zig") });
const globber = b.createModule(.{ .root_source_file = b.path("common/globber.zig") }); const globber = b.createModule(.{ .root_source_file = b.path("common/globber.zig") });
const translate_c: Translator = .init(b.dependency("translate_c", .{}), .{
.name = "c",
.c_source_file = b.path("river/c.h"),
.target = target,
.optimize = optimize,
});
translate_c.linkSystemLibrary("libevdev", .{});
translate_c.linkSystemLibrary("libinput", .{});
{ {
const river = b.addExecutable(.{ const river = b.addExecutable(.{
.name = "river", .name = "river",
@@ -162,17 +175,19 @@ pub fn build(b: *Build) !void {
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.strip = strip, .strip = strip,
.link_libc = true,
}), }),
.use_llvm = use_llvm,
.use_lld = use_llvm,
}); });
river.root_module.addOptions("build_options", options); river.root_module.addOptions("build_options", options);
river.linkLibC(); river.root_module.linkSystemLibrary("libevdev", .{});
river.linkSystemLibrary("libevdev"); river.root_module.linkSystemLibrary("libinput", .{});
river.linkSystemLibrary("libinput"); river.root_module.linkSystemLibrary("wayland-server", .{});
river.linkSystemLibrary("wayland-server"); river.root_module.linkSystemLibrary("wlroots-0.20", .{});
river.linkSystemLibrary("wlroots-0.20"); river.root_module.linkSystemLibrary("xkbcommon", .{});
river.linkSystemLibrary("xkbcommon"); river.root_module.linkSystemLibrary("pixman-1", .{});
river.linkSystemLibrary("pixman-1");
river.root_module.addImport("wayland", wayland); river.root_module.addImport("wayland", wayland);
river.root_module.addImport("xkbcommon", xkbcommon); river.root_module.addImport("xkbcommon", xkbcommon);
@@ -180,8 +195,9 @@ pub fn build(b: *Build) !void {
river.root_module.addImport("wlroots", wlroots); river.root_module.addImport("wlroots", wlroots);
river.root_module.addImport("flags", flags); river.root_module.addImport("flags", flags);
river.root_module.addImport("globber", globber); river.root_module.addImport("globber", globber);
river.root_module.addImport("c", translate_c.mod);
river.addCSourceFile(.{ river.root_module.addCSourceFile(.{
.file = b.path("river/wlroots_log_wrapper.c"), .file = b.path("river/wlroots_log_wrapper.c"),
.flags = &.{ "-std=c99", "-O2" }, .flags = &.{ "-std=c99", "-O2" },
}); });
@@ -200,14 +216,16 @@ pub fn build(b: *Build) !void {
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.strip = strip, .strip = strip,
.link_libc = true,
}), }),
.use_llvm = use_llvm,
.use_lld = use_llvm,
}); });
riverctl.root_module.addOptions("build_options", options); riverctl.root_module.addOptions("build_options", options);
riverctl.root_module.addImport("flags", flags); riverctl.root_module.addImport("flags", flags);
riverctl.root_module.addImport("wayland", wayland); riverctl.root_module.addImport("wayland", wayland);
riverctl.linkLibC(); riverctl.root_module.linkSystemLibrary("wayland-client", .{});
riverctl.linkSystemLibrary("wayland-client");
riverctl.pie = pie; riverctl.pie = pie;
riverctl.root_module.omit_frame_pointer = omit_frame_pointer; riverctl.root_module.omit_frame_pointer = omit_frame_pointer;
@@ -223,14 +241,16 @@ pub fn build(b: *Build) !void {
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.strip = strip, .strip = strip,
.link_libc = true,
}), }),
.use_llvm = use_llvm,
.use_lld = use_llvm,
}); });
rivertile.root_module.addOptions("build_options", options); rivertile.root_module.addOptions("build_options", options);
rivertile.root_module.addImport("flags", flags); rivertile.root_module.addImport("flags", flags);
rivertile.root_module.addImport("wayland", wayland); rivertile.root_module.addImport("wayland", wayland);
rivertile.linkLibC(); rivertile.root_module.linkSystemLibrary("wayland-client", .{});
rivertile.linkSystemLibrary("wayland-client");
rivertile.pie = pie; rivertile.pie = pie;
rivertile.root_module.omit_frame_pointer = omit_frame_pointer; rivertile.root_module.omit_frame_pointer = omit_frame_pointer;
@@ -263,7 +283,7 @@ pub fn build(b: *Build) !void {
// This makes the caching work for the Workaround, and the extra argument is ignored by /bin/sh. // 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")); 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); b.getInstallStep().dependOn(&b.addInstallFile(stdout, "share/man/man1/" ++ page ++ ".1").step);
} }
} }
@@ -294,31 +314,3 @@ pub fn build(b: *Build) !void {
test_step.dependOn(&run_globber_test.step); 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");
+9 -5
View File
@@ -5,7 +5,7 @@
// When a release is tagged, the "-dev" suffix should be removed for the // When a release is tagged, the "-dev" suffix should be removed for the
// commit that gets tagged. Directly after the tagged commit, the version // commit that gets tagged. Directly after the tagged commit, the version
// should be bumped and the "-dev" suffix added. // should be bumped and the "-dev" suffix added.
.version = "0.3.16-dev", .version = "0.3.18-dev",
.paths = .{""}, .paths = .{""},
.dependencies = .{ .dependencies = .{
.pixman = .{ .pixman = .{
@@ -13,17 +13,21 @@
.hash = "pixman-0.3.0-LClMnz2VAAAs7QSCGwLimV5VUYx0JFnX5xWU6HwtMuDX", .hash = "pixman-0.3.0-LClMnz2VAAAs7QSCGwLimV5VUYx0JFnX5xWU6HwtMuDX",
}, },
.wayland = .{ .wayland = .{
.url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.4.0.tar.gz", .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz",
.hash = "wayland-0.4.0-lQa1khbMAQAsLS2eBR7M5lofyEGPIbu2iFDmoz8lPC27", .hash = "wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX",
}, },
.wlroots = .{ .wlroots = .{
.url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.20.0.tar.gz", .url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.20.1.tar.gz",
.hash = "wlroots-0.20.0-jmOlcmtCBADS6eoJ6mkeiSNZkibrhD-c5Qwn-LiM86r1", .hash = "wlroots-0.20.1-jmOlcqNVBAB3uB5oqBTzpRlwu-FmMyyZMVAWCe5kmcSt",
}, },
.xkbcommon = .{ .xkbcommon = .{
.url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.3.0.tar.gz", .url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.3.0.tar.gz",
.hash = "xkbcommon-0.3.0-VDqIe3K9AQB2fG5ZeRcMC9i7kfrp5m2rWgLrmdNn9azr", .hash = "xkbcommon-0.3.0-VDqIe3K9AQB2fG5ZeRcMC9i7kfrp5m2rWgLrmdNn9azr",
}, },
.translate_c = .{
.url = "git+https://codeberg.org/ziglang/translate-c/#7a1a9fdc4ab00835748a6657ecbb835e3d5d45f7",
.hash = "translate_c-0.0.0-Q_BUWvP1BgCjAk6PWv5286tOlvzD9-X-NkuTzh0KxY0Q",
},
}, },
.fingerprint = 0x3dae7aba2ea52a3b, .fingerprint = 0x3dae7aba2ea52a3b,
} }
+23 -58
View File
@@ -1,82 +1,51 @@
// Zero allocation argument parsing for unix-like systems. // SPDX-FileCopyrightText: © 2023 Isaac Freund
// Released under the Zero Clause BSD (0BSD) license: // SPDX-License-Identifier: 0BSD
//
// 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.
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
pub const Flag = struct { pub const Flag = struct {
name: [:0]const u8, name: []const u8,
kind: enum { boolean, arg }, kind: enum { boolean, arg },
}; };
pub fn parser(comptime Arg: type, comptime flags: []const Flag) type { pub fn parser(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)),
}
return struct { return struct {
pub const Result = struct { pub const Result = struct {
/// Remaining args after the recognized flags /// Remaining args after the recognized flags
args: []const Arg, args: []const [:0]const u8,
/// Data obtained from parsed flags /// Data obtained from parsed flags
flags: Flags, flags: Flags,
pub const Flags = flags_type: { pub const Flags = flags_type: {
var fields: []const std.builtin.Type.StructField = &.{}; const Attributes = std.builtin.Type.StructField.Attributes;
for (flags) |flag| { var names: [flags.len][]const u8 = undefined;
const field: std.builtin.Type.StructField = switch (flag.kind) { var types: [flags.len]type = undefined;
.boolean => .{ var attrs: [flags.len]Attributes = undefined;
.name = flag.name, for (flags, &names, &types, &attrs) |flag, *name, *ty, *attr| {
.type = bool, name.* = flag.name;
.default_value_ptr = &false, switch (flag.kind) {
.is_comptime = false, .boolean => {
.alignment = @alignOf(bool), ty.* = bool;
attr.* = .{ .default_value_ptr = &false };
}, },
.arg => .{ .arg => {
.name = flag.name, ty.* = ?[:0]const u8;
.type = ?[:0]const u8, attr.* = .{ .default_value_ptr = &@as(ty.*, 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" = .{ }
.layout = .auto, break :flags_type @Struct(.auto, null, &names, &types, &attrs);
.fields = fields,
.decls = &.{},
.is_tuple = false,
} });
}; };
}; };
pub fn parse(args: []const Arg) !Result { pub fn parse(args: []const [:0]const u8) error{MissingFlagArgument}!Result {
var result_flags: Result.Flags = .{}; var result_flags: Result.Flags = .{};
var i: usize = 0; var i: usize = 0;
outer: while (i < args.len) : (i += 1) { 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| { inline for (flags) |flag| {
if (mem.eql(u8, "-" ++ flag.name, arg)) { if (mem.eql(u8, "-" ++ flag.name, args[i])) {
switch (flag.kind) { switch (flag.kind) {
.boolean => @field(result_flags, flag.name) = true, .boolean => @field(result_flags, flag.name) = true,
.arg => { .arg => {
@@ -86,11 +55,7 @@ pub fn parser(comptime Arg: type, comptime flags: []const Flag) type {
"' requires an argument but none was provided!", .{}); "' requires an argument but none was provided!", .{});
return error.MissingFlagArgument; return error.MissingFlagArgument;
} }
@field(result_flags, flag.name) = switch (Arg) { @field(result_flags, flag.name) = args[i];
[*:0]const u8 => mem.sliceTo(args[i], 0),
[:0]const u8 => args[i],
else => unreachable,
};
}, },
} }
continue :outer; continue :outer;
+1 -1
View File
@@ -59,7 +59,7 @@ fn bind(client: *wl.Client, control: *Control, version: u32, id: u32) void {
client.postNoMemory(); client.postNoMemory();
return; return;
}; };
control.args_map.putNoClobber(.{ .client = client, .id = id }, .{}) catch { control.args_map.putNoClobber(.{ .client = client, .id = id }, .empty) catch {
control_v1.destroy(); control_v1.destroy();
client.postNoMemory(); client.postNoMemory();
return; return;
+3 -11
View File
@@ -26,7 +26,7 @@ const wayland = @import("wayland");
const wl = wayland.server.wl; const wl = wayland.server.wl;
const zwlr = wayland.server.zwlr; const zwlr = wayland.server.zwlr;
const c = @import("c.zig").c; const c = @import("c");
const server = &@import("main.zig").server; const server = &@import("main.zig").server;
const util = @import("util.zig"); const util = @import("util.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. /// 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. /// 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), 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), frame: wl.Listener(*wlr.Cursor) = wl.Listener(*wlr.Cursor).init(handleFrame),
@@ -1151,15 +1151,7 @@ pub fn updateState(cursor: *Cursor) void {
.passthrough => { .passthrough => {
cursor.updateFocusFollowsCursorTarget(); cursor.updateFocusFollowsCursorTarget();
if (!cursor.hidden) { if (!cursor.hidden) {
const now = posix.clock_gettime(.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported"); cursor.passthrough(util.msecTimestamp());
// 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);
} }
}, },
// TODO: Leave down mode if the target surface is no longer visible. // TODO: Leave down mode if the target surface is no longer visible.
+1 -1
View File
@@ -25,7 +25,7 @@ const wlr = @import("wlroots");
const log = std.log.scoped(.input_config); const log = std.log.scoped(.input_config);
const c = @import("c.zig").c; const c = @import("c");
const server = &@import("main.zig").server; const server = &@import("main.zig").server;
const util = @import("util.zig"); const util = @import("util.zig");
+1 -1
View File
@@ -24,7 +24,7 @@ const wl = @import("wayland").server.wl;
const globber = @import("globber"); const globber = @import("globber");
const c = @import("c.zig").c; const c = @import("c");
const server = &@import("main.zig").server; const server = &@import("main.zig").server;
const util = @import("util.zig"); const util = @import("util.zig");
+1 -1
View File
@@ -55,7 +55,7 @@ tablet_manager: *wlr.TabletManagerV2,
/// List of input device configurations. Ordered by glob generality, with /// List of input device configurations. Ordered by glob generality, with
/// the most general towards the start and the most specific towards the end. /// 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), devices: wl.list.Head(InputDevice, .link),
seats: wl.list.Head(Seat, .link), seats: wl.list.Head(Seat, .link),
+3 -3
View File
@@ -24,9 +24,9 @@ const PointerMapping = @import("PointerMapping.zig");
const SwitchMapping = @import("SwitchMapping.zig"); const SwitchMapping = @import("SwitchMapping.zig");
name: [:0]const u8, name: [:0]const u8,
mappings: std.ArrayListUnmanaged(Mapping) = .{}, mappings: std.ArrayList(Mapping) = .empty,
pointer_mappings: std.ArrayListUnmanaged(PointerMapping) = .{}, pointer_mappings: std.ArrayList(PointerMapping) = .empty,
switch_mappings: std.ArrayListUnmanaged(SwitchMapping) = .{}, switch_mappings: std.ArrayList(SwitchMapping) = .empty,
pub fn deinit(mode: *Mode) void { pub fn deinit(mode: *Mode) void {
util.gpa.free(mode.name); util.gpa.free(mode.name);
+1 -1
View File
@@ -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}), 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); scene_output.sendFrameDone(&now);
} }
+1 -1
View File
@@ -31,7 +31,7 @@ const View = @import("View.zig");
const log = std.log.scoped(.river_status); const log = std.log.scoped(.river_status);
resources: wl.list.Head(zriver.OutputStatusV1, null), resources: wl.list.Head(zriver.OutputStatusV1, null),
view_tags: std.ArrayListUnmanaged(u32) = .{}, view_tags: std.ArrayList(u32) = .empty,
focused_tags: u32 = 0, focused_tags: u32 = 0,
urgent_tags: u32 = 0, urgent_tags: u32 = 0,
+1 -4
View File
@@ -476,10 +476,7 @@ pub fn runCommand(seat: *Seat, args: []const [:0]const u8) void {
return; return;
}; };
if (out) |s| { if (out) |s| {
var stdout = std.fs.File.stdout().writer(&.{}); std.log.scoped(.command).info("mapped command output: {s}", .{s});
stdout.interface.print("{s}", .{s}) catch |err| {
std.log.scoped(.command).err("{s}: write to stdout failed {}", .{ args[0], err });
};
} }
} }
+3 -3
View File
@@ -26,7 +26,7 @@ const wayland = @import("wayland");
const wl = wayland.server.wl; const wl = wayland.server.wl;
const wp = wayland.server.wp; const wp = wayland.server.wp;
const c = @import("c.zig").c; const c = @import("c");
const util = @import("util.zig"); const util = @import("util.zig");
const Config = @import("Config.zig"); const Config = @import("Config.zig");
@@ -148,8 +148,8 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
server.* = .{ server.* = .{
.wl_server = wl_server, .wl_server = wl_server,
.sigint_source = try loop.addSignal(*wl.Server, posix.SIG.INT, terminate, wl_server), .sigint_source = try loop.addSignal(*wl.Server, @intFromEnum(posix.SIG.INT), terminate, wl_server),
.sigterm_source = try loop.addSignal(*wl.Server, posix.SIG.TERM, 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), .fixes = try wlr.Fixes.create(wl_server, 1),
+1 -1
View File
@@ -498,7 +498,7 @@ pub fn rootSurface(view: View) ?*wlr.Surface {
pub fn sendFrameDone(view: View) void { pub fn sendFrameDone(view: View) void {
assert(view.mapped and !view.destroying); 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); view.rootSurface().?.sendFrameDone(&now);
} }
+3
View File
@@ -314,6 +314,9 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
const toplevel: *XdgToplevel = @fieldParentPtr("commit", listener); const toplevel: *XdgToplevel = @fieldParentPtr("commit", listener);
const view = toplevel.view; const view = toplevel.view;
// NB: the subsurface tree is never empty here
view.image_capture_scene.tree.node.subsurfaceTreeSetClip(&toplevel.wlr_toplevel.base.geometry);
if (toplevel.wlr_toplevel.base.initial_commit) { if (toplevel.wlr_toplevel.base.initial_commit) {
_ = toplevel.wlr_toplevel.setWmCapabilities(.{ .fullscreen = true }); _ = toplevel.wlr_toplevel.setWmCapabilities(.{ .fullscreen = true });
+3
View File
@@ -0,0 +1,3 @@
#include <linux/input-event-codes.h>
#include <libevdev/libevdev.h>
#include <libinput.h>
-27
View File
@@ -1,27 +0,0 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2020 The River Developers
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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");
@cInclude("libinput.h");
});
+2
View File
@@ -124,6 +124,7 @@ pub const Error = error{
ConflictingOptions, ConflictingOptions,
WriteFailed, WriteFailed,
OutOfMemory, OutOfMemory,
Unexpected,
Other, Other,
}; };
@@ -167,6 +168,7 @@ pub fn errToMsg(err: Error) [:0]const u8 {
Error.CannotReadFile => "cannot read file", Error.CannotReadFile => "cannot read file",
Error.CannotParseFile => "cannot parse file", Error.CannotParseFile => "cannot parse file",
Error.WriteFailed, Error.OutOfMemory => "out of memory", Error.WriteFailed, Error.OutOfMemory => "out of memory",
Error.Unexpected => "unexpected error",
Error.Other => unreachable, Error.Other => unreachable,
}; };
} }
+7 -5
View File
@@ -31,7 +31,7 @@ pub fn keyboardLayout(
args: []const [:0]const u8, args: []const [:0]const u8,
_: *?[]const u8, _: *?[]const u8,
) Error!void { ) Error!void {
const result = flags.parser([:0]const u8, &.{ const result = flags.parser(&.{
.{ .name = "rules", .kind = .arg }, .{ .name = "rules", .kind = .arg },
.{ .name = "model", .kind = .arg }, .{ .name = "model", .kind = .arg },
.{ .name = "variant", .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.NotEnoughArguments;
if (args.len > 2) return Error.TooManyArguments; if (args.len > 2) return Error.TooManyArguments;
const file = std.fs.cwd().openFile(args[1], .{}) catch return error.CannotReadFile; const io = std.Io.Threaded.global_single_threaded.io();
defer file.close(); 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. // 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) { switch (err) {
error.FileTooBig, error.OutOfMemory => return error.OutOfMemory, error.OutOfMemory => return error.OutOfMemory,
else => return error.CannotReadFile, else => return error.CannotReadFile,
} }
}; };
+3 -3
View File
@@ -22,7 +22,7 @@ const wlr = @import("wlroots");
const xkb = @import("xkbcommon"); const xkb = @import("xkbcommon");
const flags = @import("flags"); const flags = @import("flags");
const c = @import("../c.zig").c; const c = @import("c");
const server = &@import("../main.zig").server; const server = &@import("../main.zig").server;
const util = @import("../util.zig"); const util = @import("../util.zig");
@@ -42,7 +42,7 @@ pub fn map(
args: []const [:0]const u8, args: []const [:0]const u8,
out: *?[]const u8, out: *?[]const u8,
) Error!void { ) Error!void {
const result = flags.parser([:0]const u8, &.{ const result = flags.parser(&.{
.{ .name = "release", .kind = .boolean }, .{ .name = "release", .kind = .boolean },
.{ .name = "repeat", .kind = .boolean }, .{ .name = "repeat", .kind = .boolean },
.{ .name = "layout", .kind = .arg }, .{ .name = "layout", .kind = .arg },
@@ -366,7 +366,7 @@ fn parseSwitchState(
/// Example: /// Example:
/// unmap normal Mod4+Shift Return /// unmap normal Mod4+Shift Return
pub fn unmap(seat: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!void { 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 }, .{ .name = "release", .kind = .boolean },
}).parse(args[1..]) catch { }).parse(args[1..]) catch {
return error.InvalidValue; return error.InvalidValue;
+2 -2
View File
@@ -54,10 +54,10 @@ pub fn sendToOutput(
_: *?[]const u8, _: *?[]const u8,
) Error!void { ) Error!void {
if (args.len < 2) return Error.NotEnoughArguments; if (args.len < 2) return Error.NotEnoughArguments;
const result = flags.parser([:0]const u8, &.{ const result = flags.parser(&.{
.{ .name = "current-tags", .kind = .boolean }, .{ .name = "current-tags", .kind = .boolean },
}).parse(args[1..]) catch { }).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.NotEnoughArguments;
if (result.args.len > 1) return Error.TooManyArguments; if (result.args.len > 1) return Error.TooManyArguments;
+4 -4
View File
@@ -49,7 +49,7 @@ const Action = enum {
}; };
pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void { 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 = "app-id", .kind = .arg },
.{ .name = "title", .kind = .arg }, .{ .name = "title", .kind = .arg },
}).parse(args[1..]) catch { }).parse(args[1..]) catch {
@@ -177,7 +177,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 { 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 = "app-id", .kind = .arg },
.{ .name = "title", .kind = .arg }, .{ .name = "title", .kind = .arg },
}).parse(args[1..]) catch { }).parse(args[1..]) catch {
@@ -251,7 +251,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); assert(buf.len <= width);
try writer.writeAll(buf); try writer.writeAll(buf);
try writer.splatByteAll(' ', width - buf.len); try writer.splatByteAll(' ', width - buf.len);
@@ -278,7 +278,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 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); 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(); defer buffer.deinit();
const writer = &buffer.writer; const writer = &buffer.writer;
+27 -8
View File
@@ -15,9 +15,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const posix = std.posix; const c = std.c;
const c = @import("../c.zig").c;
const util = @import("../util.zig"); const util = @import("../util.zig");
const process = @import("../process.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 child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", args[1], null };
const pid = posix.fork() catch { 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", .{}); out.* = try std.fmt.allocPrint(util.gpa, "fork/execve failed", .{});
return Error.Other; return Error.Other;
}
break :blk @intCast(rc);
}; };
if (pid == 0) { if (pid == 0) {
process.cleanupChild(); 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) { 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); c._exit(0);
} }
// Wait the intermediate child. // Wait the intermediate child.
const ret = posix.waitpid(pid, 0); const status: u32 = while (true) {
if (!posix.W.IFEXITED(ret.status) or var status: c_int = 0;
(posix.W.IFEXITED(ret.status) and posix.W.EXITSTATUS(ret.status) != 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", .{}); out.* = try std.fmt.allocPrint(util.gpa, "fork/execve failed", .{});
return Error.Other; return Error.Other;
+1 -1
View File
@@ -35,7 +35,7 @@ pub fn focusView(
args: []const [:0]const u8, args: []const [:0]const u8,
_: *?[]const u8, _: *?[]const u8,
) Error!void { ) Error!void {
const result = flags.parser([:0]const u8, &.{ const result = flags.parser(&.{
.{ .name = "skip-floating", .kind = .boolean }, .{ .name = "skip-floating", .kind = .boolean },
}).parse(args[1..]) catch { }).parse(args[1..]) catch {
return error.InvalidValue; return error.InvalidValue;
+57 -32
View File
@@ -16,15 +16,19 @@
const build_options = @import("build_options"); const build_options = @import("build_options");
const std = @import("std"); const std = @import("std");
const mem = std.mem; const Io = std.Io;
const fs = std.fs; const fs = std.fs;
const log = std.log; const log = std.log;
const mem = std.mem;
const posix = std.posix; const posix = std.posix;
const exit = std.process.exit;
const fatal = std.process.fatal;
const c = std.c;
const builtin = @import("builtin"); const builtin = @import("builtin");
const wlr = @import("wlroots"); const wlr = @import("wlroots");
const flags = @import("flags"); const flags = @import("flags");
const c = @import("c.zig").c;
const util = @import("util.zig"); const util = @import("util.zig");
const process = @import("process.zig"); const process = @import("process.zig");
@@ -43,30 +47,47 @@ const usage: []const u8 =
pub var server: Server = undefined; pub var server: Server = undefined;
pub fn main() anyerror!void { pub fn main(init: std.process.Init.Minimal) anyerror!void {
const result = flags.parser([*:0]const u8, &.{ 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 = "h", .kind = .boolean },
.{ .name = "version", .kind = .boolean }, .{ .name = "version", .kind = .boolean },
.{ .name = "c", .kind = .arg }, .{ .name = "c", .kind = .arg },
.{ .name = "log-level", .kind = .arg }, .{ .name = "log-level", .kind = .arg },
.{ .name = "no-xwayland", .kind = .boolean }, .{ .name = "no-xwayland", .kind = .boolean },
}).parse(std.os.argv[1..]) catch { }).parse(args[1..]) catch {
try fs.File.stderr().writeAll(usage); try stderr.writeAll(usage);
posix.exit(1); try stderr.flush();
exit(1);
}; };
if (result.flags.h) { if (result.flags.h) {
try fs.File.stdout().writeAll(usage); try stdout.writeAll(usage);
posix.exit(0); try stdout.flush();
exit(0);
} }
if (result.args.len != 0) { if (result.args.len != 0) {
log.err("unknown option '{s}'", .{result.args[0]}); log.err("unknown option '{s}'", .{result.args[0]});
try fs.File.stderr().writeAll(usage); try stderr.writeAll(usage);
posix.exit(1); try stderr.flush();
exit(1);
} }
if (result.flags.version) { if (result.flags.version) {
try fs.File.stdout().writeAll(build_options.version ++ "\n"); try stdout.writeAll(build_options.version ++ "\n");
posix.exit(0); try stdout.flush();
exit(0);
} }
if (result.flags.@"log-level") |level| { if (result.flags.@"log-level") |level| {
if (mem.eql(u8, level, "error")) { if (mem.eql(u8, level, "error")) {
@@ -79,8 +100,9 @@ pub fn main() anyerror!void {
runtime_log_level = .debug; runtime_log_level = .debug;
} else { } else {
log.err("invalid log level '{s}'", .{level}); log.err("invalid log level '{s}'", .{level});
try fs.File.stderr().writeAll(usage); try stderr.writeAll(usage);
posix.exit(1); try stderr.flush();
exit(1);
} }
} }
const runtime_xwayland = !result.flags.@"no-xwayland"; const runtime_xwayland = !result.flags.@"no-xwayland";
@@ -88,7 +110,7 @@ pub fn main() anyerror!void {
if (result.flags.c) |command| { if (result.flags.c) |command| {
break :blk try util.gpa.dupeZ(u8, command); break :blk try util.gpa.dupeZ(u8, command);
} else { } else {
break :blk try defaultInitPath(); break :blk try defaultInitPath(io, init.environ);
} }
}; };
@@ -120,10 +142,20 @@ pub fn main() anyerror!void {
const child_pgid = if (startup_command) |cmd| blk: { const child_pgid = if (startup_command) |cmd| blk: {
log.info("running init executable '{s}'", .{cmd}); log.info("running init executable '{s}'", .{cmd});
const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", cmd, null }; 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) { if (pid == 0) {
process.cleanupChild(); 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); util.gpa.free(cmd);
// Since the child has called setsid, the pid is the pgid // Since the child has called setsid, the pid is the pgid
@@ -140,22 +172,21 @@ pub fn main() anyerror!void {
log.info("shutting down", .{}); log.info("shutting down", .{});
} }
fn defaultInitPath() !?[:0]const u8 { fn defaultInitPath(io: Io, environ: std.process.Environ) !?[:0]const u8 {
const path = blk: { 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" }); 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" }); break :blk try fs.path.joinZ(util.gpa, &[_][]const u8{ home, ".config/river/init" });
} else { } else {
return null; 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 (err == error.PermissionDenied) {
if (posix.accessZ(path, posix.R_OK)) { if (Io.Dir.cwd().access(io, path, .{})) {
log.err("failed to run init executable {s}: the file is not executable", .{path}); fatal("failed to run init executable {s}: the file is not executable", .{path});
posix.exit(1);
} else |_| {} } else |_| {}
} }
log.err("failed to run init executable {s}: {s}", .{ path, @errorName(err) }); log.err("failed to run init executable {s}: {s}", .{ path, @errorName(err) });
@@ -186,13 +217,7 @@ pub fn logFn(
) void { ) void {
if (@intFromEnum(level) > @intFromEnum(runtime_log_level)) return; if (@intFromEnum(level) > @intFromEnum(runtime_log_level)) return;
const scope_prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; std.log.defaultLog(level, scope, format, args);
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 {};
} }
/// See wlroots_log_wrapper.c /// See wlroots_log_wrapper.c
+1 -3
View File
@@ -17,8 +17,6 @@
const std = @import("std"); const std = @import("std");
const posix = std.posix; const posix = std.posix;
const c = @import("c.zig").c;
var original_rlimit: ?posix.rlimit = null; var original_rlimit: ?posix.rlimit = null;
pub fn setup() void { pub fn setup() void {
@@ -60,7 +58,7 @@ pub fn setup() void {
} }
pub fn cleanupChild() 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; if (posix.system.sigprocmask(posix.SIG.SETMASK, &posix.sigemptyset(), null) < 0) unreachable;
const sig_dfl = posix.Sigaction{ const sig_dfl = posix.Sigaction{
+1 -1
View File
@@ -45,7 +45,7 @@ pub fn RuleList(comptime T: type) type {
/// Ordered from most specific to most general. /// Ordered from most specific to most general.
/// Ordered first by app-id generality then by title generality. /// 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 { pub fn deinit(list: *List) void {
for (list.rules.items) |rule| { for (list.rules.items) |rule| {
+19
View File
@@ -18,3 +18,22 @@ const std = @import("std");
/// The global general-purpose allocator used throughout river's code /// The global general-purpose allocator used throughout river's code
pub const gpa = std.heap.c_allocator; 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, &timespec))) {
.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),
));
}
+35 -22
View File
@@ -17,8 +17,11 @@
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
const fs = std.fs; const fs = std.fs;
const Io = std.Io;
const posix = std.posix; const posix = std.posix;
const assert = std.debug.assert; const assert = std.debug.assert;
const process = std.process;
const fatal = process.fatal;
const builtin = @import("builtin"); const builtin = @import("builtin");
const wayland = @import("wayland"); 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; const gpa = std.heap.c_allocator;
pub const Globals = struct { pub const Globals = struct {
@@ -45,8 +57,8 @@ pub const Globals = struct {
seat: ?*wl.Seat = null, seat: ?*wl.Seat = null,
}; };
pub fn main() !void { pub fn main(init: std.process.Init.Minimal) !void {
_main() catch |err| { _main(init) catch |err| {
switch (err) { switch (err) {
error.RiverControlNotAdvertised => fatal( error.RiverControlNotAdvertised => fatal(
\\The Wayland server does not support river-control-unstable-v1. \\The Wayland server does not support river-control-unstable-v1.
@@ -57,7 +69,7 @@ pub fn main() !void {
, .{}), , .{}),
error.ConnectFailed => { error.ConnectFailed => {
std.log.err("Unable to connect to the Wayland server.", .{}); 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.", .{}); fatal("WAYLAND_DISPLAY is not set.", .{});
} else { } else {
fatal("Does WAYLAND_DISPLAY contain the socket name of a running server?", .{}); fatal("Does WAYLAND_DISPLAY contain the socket name of a running server?", .{});
@@ -68,21 +80,27 @@ pub fn main() !void {
}; };
} }
fn _main() !void { fn _main(init: std.process.Init.Minimal) !void {
const result = flags.parser([*:0]const u8, &.{ const args = try init.args.toSlice(gpa);
defer gpa.free(args);
const result = flags.parser(&.{
.{ .name = "h", .kind = .boolean }, .{ .name = "h", .kind = .boolean },
.{ .name = "version", .kind = .boolean }, .{ .name = "version", .kind = .boolean },
}).parse(std.os.argv[1..]) catch { }).parse(args[1..]) catch {
try fs.File.stderr().writeAll(usage); try stderr.writeAll(usage);
posix.exit(1); try stderr.flush();
process.exit(1);
}; };
if (result.flags.h) { if (result.flags.h) {
try fs.File.stdout().writeAll(usage); try stdout.writeAll(usage);
posix.exit(0); try stdout.flush();
process.exit(0);
} }
if (result.flags.version) { if (result.flags.version) {
try fs.File.stdout().writeAll(@import("build_options").version ++ "\n"); try stdout.writeAll(@import("build_options").version ++ "\n");
posix.exit(0); try stdout.flush();
process.exit(0);
} }
const display = try wl.Display.connect(null); const display = try wl.Display.connect(null);
@@ -125,24 +143,19 @@ fn callbackListener(_: *zriver.CommandCallbackV1, event: zriver.CommandCallbackV
switch (event) { switch (event) {
.success => |success| { .success => |success| {
if (mem.len(success.output) > 0) { if (mem.len(success.output) > 0) {
var stdout = fs.File.stdout().writer(&.{}); stdout.print("{s}\n", .{success.output}) catch @panic("failed to write to stdout");
stdout.interface.print("{s}\n", .{success.output}) catch @panic("failed to write to stdout");
} }
posix.exit(0); process.exit(0);
}, },
.failure => |failure| { .failure => |failure| {
// A small hack to provide usage text when river reports an unknown command. // A small hack to provide usage text when river reports an unknown command.
if (mem.orderZ(u8, failure.failure_message, "unknown command") == .eq) { if (mem.orderZ(u8, failure.failure_message, "unknown command") == .eq) {
std.log.err("unknown command", .{}); std.log.err("unknown command", .{});
fs.File.stderr().writeAll(usage) catch {}; stderr.writeAll(usage) catch {};
posix.exit(1); stderr.flush() catch {};
process.exit(1);
} }
fatal("{s}", .{failure.failure_message}); fatal("{s}", .{failure.failure_message});
}, },
} }
} }
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.log.err(format, args);
posix.exit(1);
}
+29 -18
View File
@@ -40,6 +40,8 @@ const fmt = std.fmt;
const mem = std.mem; const mem = std.mem;
const math = std.math; const math = std.math;
const posix = std.posix; const posix = std.posix;
const process = std.process;
const fatal = std.process.fatal;
const assert = std.debug.assert; const assert = std.debug.assert;
const wayland = @import("wayland"); const wayland = @import("wayland");
@@ -307,8 +309,20 @@ const Output = struct {
} }
}; };
pub fn main() !void { pub fn main(init: std.process.Init.Minimal) !void {
const result = flags.parser([*:0]const u8, &[_]flags.Flag{ 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 = "h", .kind = .boolean },
.{ .name = "version", .kind = .boolean }, .{ .name = "version", .kind = .boolean },
.{ .name = "view-padding", .kind = .arg }, .{ .name = "view-padding", .kind = .arg },
@@ -316,19 +330,22 @@ pub fn main() !void {
.{ .name = "main-location", .kind = .arg }, .{ .name = "main-location", .kind = .arg },
.{ .name = "main-count", .kind = .arg }, .{ .name = "main-count", .kind = .arg },
.{ .name = "main-ratio", .kind = .arg }, .{ .name = "main-ratio", .kind = .arg },
}).parse(std.os.argv[1..]) catch { }).parse(args[1..]) catch {
try std.fs.File.stderr().writeAll(usage); try stderr.writeAll(usage);
posix.exit(1); try stderr.flush();
process.exit(1);
}; };
if (result.flags.h) { if (result.flags.h) {
try std.fs.File.stdout().writeAll(usage); try stdout.writeAll(usage);
posix.exit(0); try stdout.flush();
process.exit(0);
} }
if (result.args.len != 0) fatalPrintUsage("unknown option '{s}'", .{result.args[0]}); if (result.args.len != 0) fatalPrintUsage("unknown option '{s}'", .{result.args[0]});
if (result.flags.version) { if (result.flags.version) {
try std.fs.File.stdout().writeAll(@import("build_options").version ++ "\n"); try stdout.writeAll(@import("build_options").version ++ "\n");
posix.exit(0); try stdout.flush();
process.exit(0);
} }
if (result.flags.@"view-padding") |raw| { if (result.flags.@"view-padding") |raw| {
view_padding = fmt.parseUnsigned(u31, raw, 10) catch view_padding = fmt.parseUnsigned(u31, raw, 10) catch
@@ -356,8 +373,7 @@ pub fn main() !void {
} }
const display = wl.Display.connect(null) catch { const display = wl.Display.connect(null) catch {
std.debug.print("Unable to connect to Wayland server.\n", .{}); fatal("Unable to connect to Wayland server.\n", .{});
posix.exit(1);
}; };
defer display.disconnect(); defer display.disconnect();
@@ -407,15 +423,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 { fn fatalPrintUsage(comptime format: []const u8, args: anytype) noreturn {
std.log.err(format, args); std.log.err(format, args);
std.fs.File.stderr().writeAll(usage) catch {}; std.debug.print(usage, .{});
posix.exit(1); process.exit(1);
} }
fn saturatingCast(comptime T: type, x: anytype) T { fn saturatingCast(comptime T: type, x: anytype) T {