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 ..
# 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
+8 -6
View File
@@ -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/
+4 -4
View File
@@ -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
+1
View File
@@ -1,2 +1,3 @@
.zig-cache/
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
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
+35 -43
View File
@@ -4,7 +4,11 @@ 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;
const Translator = @import("translate_c").Translator;
pub fn build(b: *Build) !void {
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 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 +30,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 +65,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), '-');
@@ -154,6 +158,15 @@ pub fn build(b: *Build) !void {
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 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(.{
.name = "river",
@@ -162,17 +175,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);
@@ -180,8 +195,9 @@ pub fn build(b: *Build) !void {
river.root_module.addImport("wlroots", wlroots);
river.root_module.addImport("flags", flags);
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"),
.flags = &.{ "-std=c99", "-O2" },
});
@@ -200,14 +216,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 +241,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 +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.
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 +314,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");
+9 -5
View File
@@ -5,7 +5,7 @@
// When a release is tagged, the "-dev" suffix should be removed for the
// commit that gets tagged. Directly after the tagged commit, the version
// should be bumped and the "-dev" suffix added.
.version = "0.3.16-dev",
.version = "0.3.18-dev",
.paths = .{""},
.dependencies = .{
.pixman = .{
@@ -13,17 +13,21 @@
.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",
.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,
}
+23 -58
View File
@@ -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;
+1 -1
View File
@@ -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;
+3 -11
View File
@@ -26,7 +26,7 @@ const wayland = @import("wayland");
const wl = wayland.server.wl;
const zwlr = wayland.server.zwlr;
const c = @import("c.zig").c;
const c = @import("c");
const server = &@import("main.zig").server;
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.
/// 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.
+1 -1
View File
@@ -25,7 +25,7 @@ const wlr = @import("wlroots");
const log = std.log.scoped(.input_config);
const c = @import("c.zig").c;
const c = @import("c");
const server = &@import("main.zig").server;
const util = @import("util.zig");
+1 -1
View File
@@ -24,7 +24,7 @@ const wl = @import("wayland").server.wl;
const globber = @import("globber");
const c = @import("c.zig").c;
const c = @import("c");
const server = &@import("main.zig").server;
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
/// 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),
+3 -3
View File
@@ -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);
+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}),
};
var now = posix.clock_gettime(.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported");
var now = util.timestamp();
scene_output.sendFrameDone(&now);
}
+1 -1
View File
@@ -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,
+1 -4
View File
@@ -476,10 +476,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});
}
}
+3 -3
View File
@@ -26,7 +26,7 @@ const wayland = @import("wayland");
const wl = wayland.server.wl;
const wp = wayland.server.wp;
const c = @import("c.zig").c;
const c = @import("c");
const util = @import("util.zig");
const Config = @import("Config.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),
+1 -1
View File
@@ -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);
}
+3
View File
@@ -314,6 +314,9 @@ fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
const toplevel: *XdgToplevel = @fieldParentPtr("commit", listener);
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) {
_ = 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,
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,
};
}
+7 -5
View File
@@ -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,
}
};
+3 -3
View File
@@ -22,7 +22,7 @@ const wlr = @import("wlroots");
const xkb = @import("xkbcommon");
const flags = @import("flags");
const c = @import("../c.zig").c;
const c = @import("c");
const server = &@import("../main.zig").server;
const util = @import("../util.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;
+2 -2
View File
@@ -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;
+4 -4
View File
@@ -49,7 +49,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 {
@@ -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 {
const result = flags.parser([:0]const u8, &.{
const result = flags.parser(&.{
.{ .name = "app-id", .kind = .arg },
.{ .name = "title", .kind = .arg },
}).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);
try writer.writeAll(buf);
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 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;
+27 -8
View File
@@ -15,9 +15,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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 {
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;
+1 -1
View File
@@ -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;
+57 -32
View File
@@ -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,47 @@ 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);
try stderr.flush();
exit(1);
};
if (result.flags.h) {
try fs.File.stdout().writeAll(usage);
posix.exit(0);
try stdout.writeAll(usage);
try stdout.flush();
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);
try stderr.flush();
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");
try stdout.flush();
exit(0);
}
if (result.flags.@"log-level") |level| {
if (mem.eql(u8, level, "error")) {
@@ -79,8 +100,9 @@ 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);
try stderr.flush();
exit(1);
}
}
const runtime_xwayland = !result.flags.@"no-xwayland";
@@ -88,7 +110,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 +142,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 +172,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 +217,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
+1 -3
View File
@@ -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{
+1 -1
View File
@@ -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| {
+19
View File
@@ -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, &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 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,27 @@ 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);
try stderr.flush();
process.exit(1);
};
if (result.flags.h) {
try fs.File.stdout().writeAll(usage);
posix.exit(0);
try stdout.writeAll(usage);
try stdout.flush();
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");
try stdout.flush();
process.exit(0);
}
const display = try wl.Display.connect(null);
@@ -125,24 +143,19 @@ 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 {};
stderr.flush() 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);
}
+29 -18
View File
@@ -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,22 @@ 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);
try stderr.flush();
process.exit(1);
};
if (result.flags.h) {
try std.fs.File.stdout().writeAll(usage);
posix.exit(0);
try stdout.writeAll(usage);
try stdout.flush();
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");
try stdout.flush();
process.exit(0);
}
if (result.flags.@"view-padding") |raw| {
view_padding = fmt.parseUnsigned(u31, raw, 10) catch
@@ -356,8 +373,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 +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 {
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 {