Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
build: update to Zig 0.15
.builds/alpine.yml | 8 +-
.builds/archlinux.yml | 8 +-
.builds/freebsd.yml | 8 +-
README.md | 2 +-
build.zig | 41 ++--
build.zig.zon | 8 +-
common/deque.zig | 431 +++++++++++++++++++++++++++++++++++++++++++
river/Cursor.zig | 8 +-
river/Decoration.zig | 4 +-
river/IdleInhibitManager.zig | 25 ++-
river/IdleInhibitor.zig | 35 ++--
river/InputConfig.zig | 2 +-
river/InputDevice.zig | 2 +-
river/InputManager.zig | 5 +-
river/KeyboardGroup.zig | 84 ++++-----
river/Output.zig | 2 +-
river/OutputManager.zig | 6 +-
river/PointerBinding.zig | 4 +-
river/Seat.zig | 25 ++-
river/Server.zig | 2 +-
river/ShellSurface.zig | 4 +-
river/XkbBinding.zig | 6 +-
river/c.zig | 2 +-
river/main.zig | 27 ++-
river/process.zig | 8 +-
25 files changed, 600 insertions(+), 157 deletions(-)
diff --git a/.builds/alpine.yml b/.builds/alpine.yml
index e03784a..b2a8892 100644
--- a/.builds/alpine.yml
+++ b/.builds/alpine.yml
@@ -43,10 +43,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.14.0/zig-linux-x86_64-0.14.0.tar.xz
- tar xf zig-linux-x86_64-0.14.0.tar.xz
- sudo mv zig-linux-x86_64-0.14.0/zig /usr/bin/
- sudo mv zig-linux-x86_64-0.14.0/lib /usr/lib/zig
+ 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
- build: |
cd river
zig build --summary all
diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml
index 414ee76..a6c25ab 100644
--- a/.builds/archlinux.yml
+++ b/.builds/archlinux.yml
@@ -41,10 +41,10 @@ tasks:
cd ..
# Eat Github's resources rather than the Zig Software Foundation's resources!
- wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz
- tar xf zig-linux-x86_64-0.14.0.tar.xz
- sudo mv zig-linux-x86_64-0.14.0/zig /usr/bin/
- sudo mv zig-linux-x86_64-0.14.0/lib /usr/lib/zig
+ 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
- build: |
cd river
zig build --summary all
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
index 0756d14..d49fe20 100644
--- a/.builds/freebsd.yml
+++ b/.builds/freebsd.yml
@@ -46,10 +46,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.14.0/zig-freebsd-x86_64-0.14.0.tar.xz
- tar xf zig-freebsd-x86_64-0.14.0.tar.xz
- sudo mv zig-freebsd-x86_64-0.14.0/zig /usr/bin/
- sudo mv zig-freebsd-x86_64-0.14.0/lib /usr/lib/zig
+ 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
- build: |
cd river
zig build --summary all
diff --git a/README.md b/README.md
index 3b0bf28..8c94d05 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ To compile river 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.14
+- [zig](https://ziglang.org/download/) 0.15
- wayland
- wayland-protocols
- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.19
diff --git a/build.zig b/build.zig
index ad87479..3b56e34 100644
--- a/build.zig
+++ b/build.zig
@@ -12,7 +12,6 @@ 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 llvm = !(b.option(bool, "no-llvm", "(expirimental) Use non-LLVM x86 Zig backend") orelse false);
const omit_frame_pointer = switch (optimize) {
.Debug, .ReleaseSafe => false,
@@ -128,16 +127,17 @@ 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 slotmap = b.createModule(.{ .root_source_file = b.path("common/slotmap.zig") });
+ const deque = b.createModule(.{ .root_source_file = b.path("common/deque.zig") });
{
const river = b.addExecutable(.{
.name = "river",
- .root_source_file = b.path("river/main.zig"),
- .target = target,
- .optimize = optimize,
- .strip = strip,
- .use_llvm = llvm,
- .use_lld = llvm,
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("river/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .strip = strip,
+ }),
});
river.root_module.addOptions("build_options", options);
@@ -156,6 +156,7 @@ pub fn build(b: *Build) !void {
river.root_module.addImport("flags", flags);
river.root_module.addImport("globber", globber);
river.root_module.addImport("slotmap", slotmap);
+ river.root_module.addImport("deque", deque);
river.addCSourceFile(.{
.file = b.path("river/wlroots_log_wrapper.c"),
@@ -200,22 +201,36 @@ pub fn build(b: *Build) !void {
{
const globber_test = b.addTest(.{
- .root_source_file = b.path("common/globber.zig"),
- .target = target,
- .optimize = optimize,
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("common/globber.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
});
const run_globber_test = b.addRunArtifact(globber_test);
const slotmap_test = b.addTest(.{
- .root_source_file = b.path("common/slotmap.zig"),
- .target = target,
- .optimize = optimize,
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("common/slotmap.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
});
const run_slotmap_test = b.addRunArtifact(slotmap_test);
+ const deque_test = b.addTest(.{
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("common/deque.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
+ });
+ const run_deque_test = b.addRunArtifact(deque_test);
+
const test_step = b.step("test", "Run the tests");
test_step.dependOn(&run_globber_test.step);
test_step.dependOn(&run_slotmap_test.step);
+ test_step.dependOn(&run_deque_test.step);
}
}
diff --git a/build.zig.zon b/build.zig.zon
index 0a9a658..e27b6b6 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -14,12 +14,12 @@
.hash = "pixman-0.3.0-LClMnz2VAAAs7QSCGwLimV5VUYx0JFnX5xWU6HwtMuDX",
},
.wayland = .{
- .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.3.0.tar.gz",
- .hash = "wayland-0.3.0-lQa1kjPIAQDmhGYpY-zxiRzQJFHQ2VqhJkQLbKKdt5wl",
+ .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.4.0.tar.gz",
+ .hash = "wayland-0.4.0-lQa1khbMAQAsLS2eBR7M5lofyEGPIbu2iFDmoz8lPC27",
},
.wlroots = .{
- .url = "git+https://codeberg.org/ifreund/zig-wlroots#e96d90ce224d10278fc31ca7c3150cbd1919ef1c",
- .hash = "wlroots-0.19.3-dev-jmOlcrcBBAB4-xzzlJBEPEIJwD9Fnzk2VSPHPCWIgTjV",
+ .url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.19.3.tar.gz",
+ .hash = "wlroots-0.19.3-jmOlcuL_AwBHhLCwpFsXbTizE3q9BugFmGX-XIxqcPMc",
},
.xkbcommon = .{
.url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.3.0.tar.gz",
diff --git a/common/deque.zig b/common/deque.zig
new file mode 100644
index 0000000..012bd1a
--- /dev/null
+++ b/common/deque.zig
@@ -0,0 +1,431 @@
+const std = @import("std");
+const assert = std.debug.assert;
+const Allocator = std.mem.Allocator;
+
+/// A contiguous, growable, double-ended queue.
+///
+/// Pushing/popping items from either end of the queue is O(1).
+pub fn Deque(comptime T: type) type {
+ return struct {
+ const Self = @This();
+
+ /// A ring buffer.
+ buffer: []T,
+ /// The index in buffer where the first item in the logical deque is stored.
+ head: usize,
+ /// The number of items stored in the logical deque.
+ len: usize,
+
+ /// A Deque containing no elements.
+ pub const empty: Self = .{
+ .buffer = &.{},
+ .head = 0,
+ .len = 0,
+ };
+
+ /// Initialize with capacity to hold `capacity` elements.
+ /// The resulting capacity will equal `capacity` exactly.
+ /// Deinitialize with `deinit`.
+ pub fn initCapacity(gpa: Allocator, capacity: usize) Allocator.Error!Self {
+ var deque: Self = .empty;
+ try deque.ensureTotalCapacityPrecise(gpa, capacity);
+ return deque;
+ }
+
+ /// Initialize with externally-managed memory. The buffer determines the
+ /// capacity and the deque is initially empty.
+ ///
+ /// When initialized this way, all functions that accept an Allocator
+ /// argument cause illegal behavior.
+ pub fn initBuffer(buffer: []T) Self {
+ return .{
+ .buffer = buffer,
+ .head = 0,
+ .len = 0,
+ };
+ }
+
+ /// Release all allocated memory.
+ pub fn deinit(deque: *Self, gpa: Allocator) void {
+ gpa.free(deque.buffer);
+ deque.* = undefined;
+ }
+
+ /// Modify the deque so that it can hold at least `new_capacity` items.
+ /// Implements super-linear growth to achieve amortized O(1) push/pop operations.
+ /// Invalidates element pointers if additional memory is needed.
+ pub fn ensureTotalCapacity(deque: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void {
+ if (deque.buffer.len >= new_capacity) return;
+ return deque.ensureTotalCapacityPrecise(gpa, growCapacity(deque.buffer.len, new_capacity));
+ }
+
+ /// If the current capacity is less than `new_capacity`, this function will
+ /// modify the deque so that it can hold exactly `new_capacity` items.
+ /// Invalidates element pointers if additional memory is needed.
+ pub fn ensureTotalCapacityPrecise(deque: *Self, gpa: Allocator, new_capacity: usize) Allocator.Error!void {
+ if (deque.buffer.len >= new_capacity) return;
+ const old_buffer = deque.buffer;
+ if (gpa.remap(old_buffer, new_capacity)) |new_buffer| {
+ // If the items wrap around the end of the buffer we need to do
+ // a memcpy to prevent a gap after resizing the buffer.
+ if (deque.head > old_buffer.len - deque.len) {
+ // The gap splits the items in the deque into head and tail parts.
+ // Choose the shorter part to copy.
+ const head = new_buffer[deque.head..old_buffer.len];
+ const tail = new_buffer[0 .. deque.len - head.len];
+ if (head.len > tail.len and new_buffer.len - old_buffer.len > tail.len) {
+ @memcpy(new_buffer[old_buffer.len..][0..tail.len], tail);
+ } else {
+ // In this case overlap is possible if e.g. the capacity increase is 1
+ // and head.len is greater than 1.
+ deque.head = new_buffer.len - head.len;
+ @memmove(new_buffer[deque.head..][0..head.len], head);
+ }
+ }
+ deque.buffer = new_buffer;
+ } else {
+ const new_buffer = try gpa.alloc(T, new_capacity);
+ if (deque.head < old_buffer.len - deque.len) {
+ @memcpy(new_buffer[0..deque.len], old_buffer[deque.head..][0..deque.len]);
+ } else {
+ const head = old_buffer[deque.head..];
+ const tail = old_buffer[0 .. deque.len - head.len];
+ @memcpy(new_buffer[0..head.len], head);
+ @memcpy(new_buffer[head.len..][0..tail.len], tail);
+ }
+ deque.head = 0;
+ deque.buffer = new_buffer;
+ gpa.free(old_buffer);
+ }
+ }
+
+ /// Modify the deque so that it can hold at least `additional_count` **more** items.
+ /// Invalidates element pointers if additional memory is needed.
+ pub fn ensureUnusedCapacity(
+ deque: *Self,
+ gpa: Allocator,
+ additional_count: usize,
+ ) Allocator.Error!void {
+ return deque.ensureTotalCapacity(gpa, try addOrOom(deque.len, additional_count));
+ }
+
+ /// Add one item to the front of the deque.
+ ///
+ /// Invalidates element pointers if additional memory is needed.
+ pub fn pushFront(deque: *Self, gpa: Allocator, item: T) error{OutOfMemory}!void {
+ try deque.ensureUnusedCapacity(gpa, 1);
+ deque.pushFrontAssumeCapacity(item);
+ }
+
+ /// Add one item to the front of the deque.
+ ///
+ /// Never invalidates element pointers.
+ ///
+ /// If the deque lacks unused capacity for the additional item, returns
+ /// `error.OutOfMemory`.
+ pub fn pushFrontBounded(deque: *Self, item: T) error{OutOfMemory}!void {
+ if (deque.buffer.len - deque.len == 0) return error.OutOfMemory;
+ return deque.pushFrontAssumeCapacity(item);
+ }
+
+ /// Add one item to the front of the deque.
+ ///
+ /// Never invalidates element pointers.
+ ///
+ /// Asserts that the deque can hold one additional item.
+ pub fn pushFrontAssumeCapacity(deque: *Self, item: T) void {
+ assert(deque.len < deque.buffer.len);
+ if (deque.head == 0) {
+ deque.head = deque.buffer.len;
+ }
+ deque.head -= 1;
+ deque.buffer[deque.head] = item;
+ deque.len += 1;
+ }
+
+ /// Add one item to the front of the deque.
+ ///
+ /// Invalidates element pointers if additional memory is needed.
+ pub fn pushBack(deque: *Self, gpa: Allocator, item: T) error{OutOfMemory}!void {
+ try deque.ensureUnusedCapacity(gpa, 1);
+ deque.pushBackAssumeCapacity(item);
+ }
+
+ /// Add one item to the back of the deque.
+ ///
+ /// Never invalidates element pointers.
+ ///
+ /// If the deque lacks unused capacity for the additional item, returns
+ /// `error.OutOfMemory`.
+ pub fn pushBackBounded(deque: *Self, item: T) error{OutOfMemory}!void {
+ if (deque.buffer.len - deque.len == 0) return error.OutOfMemory;
+ deque.pushBackAssumeCapacity(item);
+ }
+
+ /// Add one item to the back of the deque.
+ ///
+ /// Never invalidates element pointers.
+ ///
+ /// Asserts that the deque can hold one additional item.
+ pub fn pushBackAssumeCapacity(deque: *Self, item: T) void {
+ assert(deque.len < deque.buffer.len);
+ const buffer_index = deque.bufferIndex(deque.len);
+ deque.buffer[buffer_index] = item;
+ deque.len += 1;
+ }
+
+ /// Return the first item in the deque or null if empty.
+ pub fn front(deque: *const Self) ?T {
+ if (deque.len == 0) return null;
+ return deque.buffer[deque.head];
+ }
+
+ /// Return the last item in the deque or null if empty.
+ pub fn back(deque: *const Self) ?T {
+ if (deque.len == 0) return null;
+ return deque.buffer[deque.bufferIndex(deque.len - 1)];
+ }
+
+ /// Return the item at the given index in the deque.
+ ///
+ /// The first item in the queue is at index 0.
+ ///
+ /// Asserts that the index is in-bounds.
+ pub fn at(deque: *const Self, index: usize) T {
+ assert(index < deque.len);
+ return deque.buffer[deque.bufferIndex(index)];
+ }
+
+ /// Remove and return the first item in the deque or null if empty.
+ pub fn popFront(deque: *Self) ?T {
+ if (deque.len == 0) return null;
+ const pop_index = deque.head;
+ deque.head = deque.bufferIndex(1);
+ deque.len -= 1;
+ return deque.buffer[pop_index];
+ }
+
+ /// Remove and return the last item in the deque or null if empty.
+ pub fn popBack(deque: *Self) ?T {
+ if (deque.len == 0) return null;
+ deque.len -= 1;
+ return deque.buffer[deque.bufferIndex(deque.len)];
+ }
+
+ pub const Iterator = struct {
+ deque: *const Self,
+ index: usize,
+
+ pub fn next(it: *Iterator) ?T {
+ if (it.index < it.deque.len) {
+ defer it.index += 1;
+ return it.deque.at(it.index);
+ } else {
+ return null;
+ }
+ }
+ };
+
+ /// Iterates over all items in the deque in order from front to back.
+ pub fn iterator(deque: *const Self) Iterator {
+ return .{ .deque = deque, .index = 0 };
+ }
+
+ /// Returns the index in `buffer` where the element at the given
+ /// index in the logical deque is stored.
+ fn bufferIndex(deque: *const Self, index: usize) usize {
+ // This is written with this confusing style to prevent overflow.
+ if (deque.head >= deque.buffer.len - index) {
+ return index - (deque.buffer.len - deque.head);
+ } else {
+ return deque.head + index;
+ }
+ }
+
+ const init_capacity: comptime_int = @max(1, std.atomic.cache_line / @sizeOf(T));
+
+ /// Called when memory growth is necessary. Returns a capacity larger than
+ /// minimum that grows super-linearly.
+ fn growCapacity(current: usize, minimum: usize) usize {
+ var new = current;
+ while (true) {
+ new +|= new / 2 + init_capacity;
+ if (new >= minimum) return new;
+ }
+ }
+ };
+}
+
+/// Integer addition returning `error.OutOfMemory` on overflow.
+fn addOrOom(a: usize, b: usize) error{OutOfMemory}!usize {
+ const result, const overflow = @addWithOverflow(a, b);
+ if (overflow != 0) return error.OutOfMemory;
+ return result;
+}
+
+test "basic" {
+ const testing = std.testing;
+ const gpa = testing.allocator;
+
+ var q: Deque(u32) = .empty;
+ defer q.deinit(gpa);
+
+ try testing.expectEqual(null, q.popFront());
+ try testing.expectEqual(null, q.popBack());
+
+ try q.pushBack(gpa, 1);
+ try q.pushBack(gpa, 2);
+ try q.pushBack(gpa, 3);
+ try q.pushFront(gpa, 0);
+
+ try testing.expectEqual(0, q.popFront());
+ try testing.expectEqual(1, q.popFront());
+ try testing.expectEqual(3, q.popBack());
+ try testing.expectEqual(2, q.popFront());
+ try testing.expectEqual(null, q.popFront());
+ try testing.expectEqual(null, q.popBack());
+}
+
+test "buffer" {
+ const testing = std.testing;
+
+ var buffer: [4]u32 = undefined;
+ var q: Deque(u32) = .initBuffer(&buffer);
+
+ try testing.expectEqual(null, q.popFront());
+ try testing.expectEqual(null, q.popBack());
+
+ try q.pushBackBounded(1);
+ try q.pushBackBounded(2);
+ try q.pushBackBounded(3);
+ try q.pushFrontBounded(0);
+ try testing.expectError(error.OutOfMemory, q.pushBackBounded(4));
+
+ try testing.expectEqual(0, q.popFront());
+ try testing.expectEqual(1, q.popFront());
+ try testing.expectEqual(3, q.popBack());
+ try testing.expectEqual(2, q.popFront());
+ try testing.expectEqual(null, q.popFront());
+ try testing.expectEqual(null, q.popBack());
+}
+
+test "slow growth" {
+ const testing = std.testing;
+ const gpa = testing.allocator;
+
+ var q: Deque(i32) = .empty;
+ defer q.deinit(gpa);
+
+ try q.ensureTotalCapacityPrecise(gpa, 1);
+ q.pushBackAssumeCapacity(1);
+ try q.ensureTotalCapacityPrecise(gpa, 2);
+ q.pushFrontAssumeCapacity(0);
+ try q.ensureTotalCapacityPrecise(gpa, 3);
+ q.pushBackAssumeCapacity(2);
+ try q.ensureTotalCapacityPrecise(gpa, 5);
+ q.pushBackAssumeCapacity(3);
+ q.pushFrontAssumeCapacity(-1);
+ try q.ensureTotalCapacityPrecise(gpa, 6);
+ q.pushFrontAssumeCapacity(-2);
+
+ try testing.expectEqual(-2, q.popFront());
+ try testing.expectEqual(-1, q.popFront());
+ try testing.expectEqual(3, q.popBack());
+ try testing.expectEqual(0, q.popFront());
+ try testing.expectEqual(2, q.popBack());
+ try testing.expectEqual(1, q.popBack());
+ try testing.expectEqual(null, q.popFront());
+ try testing.expectEqual(null, q.popBack());
+}
+
+test "fuzz against ArrayList oracle" {
+ try std.testing.fuzz({}, fuzzAgainstArrayList, .{});
+}
+
+test "dumb fuzz against ArrayList oracle" {
+ const testing = std.testing;
+ const gpa = testing.allocator;
+
+ const input = try gpa.alloc(u8, 1024);
+ defer gpa.free(input);
+
+ var prng = std.Random.DefaultPrng.init(testing.random_seed);
+ prng.random().bytes(input);
+
+ try fuzzAgainstArrayList({}, input);
+}
+
+fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void {
+ const testing = std.testing;
+ const gpa = testing.allocator;
+
+ var q: Deque(u32) = .empty;
+ defer q.deinit(gpa);
+ var l: std.ArrayList(u32) = .empty;
+ defer l.deinit(gpa);
+
+ if (input.len < 2) return;
+
+ var prng = std.Random.DefaultPrng.init(input[0]);
+ const random = prng.random();
+
+ const Action = enum {
+ push_back,
+ push_front,
+ pop_back,
+ pop_front,
+ grow,
+ /// Sentinel to avoid hardcoding the cast below
+ max,
+ };
+ for (input[1..]) |byte| {
+ switch (@as(Action, @enumFromInt(byte % (@intFromEnum(Action.max))))) {
+ .push_back => {
+ const item = random.int(u8);
+ try testing.expectEqual(
+ l.appendBounded(item),
+ q.pushBackBounded(item),
+ );
+ },
+ .push_front => {
+ const item = random.int(u8);
+ try testing.expectEqual(
+ l.insertBounded(0, item),
+ q.pushFrontBounded(item),
+ );
+ },
+ .pop_back => {
+ try testing.expectEqual(l.pop(), q.popBack());
+ },
+ .pop_front => {
+ try testing.expectEqual(
+ if (l.items.len > 0) l.orderedRemove(0) else null,
+ q.popFront(),
+ );
+ },
+ // Growing by small, random, linear amounts seems to better test
+ // ensureTotalCapacityPrecise(), which is the most complex part
+ // of the Deque implementation.
+ .grow => {
+ const growth = random.int(u3);
+ try l.ensureTotalCapacityPrecise(gpa, l.items.len + growth);
+ try q.ensureTotalCapacityPrecise(gpa, q.len + growth);
+ },
+ .max => unreachable,
+ }
+ try testing.expectEqual(l.getLastOrNull(), q.back());
+ try testing.expectEqual(
+ if (l.items.len > 0) l.items[0] else null,
+ q.front(),
+ );
+ try testing.expectEqual(l.items.len, q.len);
+ try testing.expectEqual(l.capacity, q.buffer.len);
+ {
+ var it = q.iterator();
+ for (l.items) |item| {
+ try testing.expectEqual(item, it.next());
+ }
+ try testing.expectEqual(null, it.next());
+ }
+ }
+}
diff --git a/river/Cursor.zig b/river/Cursor.zig
index 997eef8..1a6ec15 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -26,7 +26,7 @@ const wayland = @import("wayland");
const wl = wayland.server.wl;
const zwlr = wayland.server.zwlr;
-const c = @import("c.zig");
+const c = @import("c.zig").c;
const server = &@import("main.zig").server;
const util = @import("util.zig");
@@ -225,7 +225,7 @@ pub fn setTheme(cursor: *Cursor, theme: ?[*:0]const u8, _size: ?u32) !void {
// If this cursor belongs to the default seat, set the xcursor environment
// variables as well as the xwayland cursor theme.
if (cursor.seat == server.input_manager.defaultSeat()) {
- const size_str = try std.fmt.allocPrintZ(util.gpa, "{}", .{size});
+ const size_str = try std.fmt.allocPrintSentinel(util.gpa, "{}", .{size}, 0);
defer util.gpa.free(size_str);
if (c.setenv("XCURSOR_SIZE", size_str.ptr, 1) < 0) return error.OutOfMemory;
if (theme) |t| if (c.setenv("XCURSOR_THEME", t, 1) < 0) return error.OutOfMemory;
@@ -532,8 +532,8 @@ pub fn processAxis(cursor: *Cursor, event: *const wlr.Pointer.event.Axis) void {
// @intFromFloat() call safe due to the max/min i32 not being exactly representable
// by an f32. Dividing by 2 is a low effort way to ensure the value is in bounds and
// allow users to set their scroll-factor to inf without crashing river.
- math.minInt(i32) / 2,
- math.maxInt(i32) / 2,
+ @as(f32, @floatFromInt(math.minInt(i32) / 2)),
+ @as(f32, @floatFromInt(math.maxInt(i32) / 2)),
)),
event.source,
event.relative_direction,
diff --git a/river/Decoration.zig b/river/Decoration.zig
index a47817d..2bf1a80 100644
--- a/river/Decoration.zig
+++ b/river/Decoration.zig
@@ -135,7 +135,7 @@ fn handleRequest(
}
}
-fn clientCommit(wlr_surface: *wlr.Surface) callconv(.C) void {
+fn clientCommit(wlr_surface: *wlr.Surface) callconv(.c) void {
if (wlr_surface.role != &role) return;
const resource = wlr_surface.role_resource orelse return;
const decoration: *Decoration = @ptrCast(@alignCast(resource.getUserData()));
@@ -144,7 +144,7 @@ fn clientCommit(wlr_surface: *wlr.Surface) callconv(.C) void {
}
}
-fn commit(wlr_surface: *wlr.Surface) callconv(.C) void {
+fn commit(wlr_surface: *wlr.Surface) callconv(.c) void {
if (wlr_surface.hasBuffer()) {
wlr_surface.map();
}
diff --git a/river/IdleInhibitManager.zig b/river/IdleInhibitManager.zig
index 44785cc..ca7e01f 100644
--- a/river/IdleInhibitManager.zig
+++ b/river/IdleInhibitManager.zig
@@ -29,28 +29,30 @@ const Window = @import("Window.zig");
wlr_manager: *wlr.IdleInhibitManagerV1,
new_idle_inhibitor: wl.Listener(*wlr.IdleInhibitorV1) = .init(handleNewIdleInhibitor),
-inhibitors: std.DoublyLinkedList(IdleInhibitor) = .{},
+inhibitors: wl.list.Head(IdleInhibitor, .link),
pub fn init(inhibit_manager: *IdleInhibitManager) !void {
inhibit_manager.* = .{
.wlr_manager = try wlr.IdleInhibitManagerV1.create(server.wl_server),
+ .inhibitors = undefined,
};
+ inhibit_manager.inhibitors.init();
+
inhibit_manager.wlr_manager.events.new_inhibitor.add(&inhibit_manager.new_idle_inhibitor);
}
pub fn deinit(inhibit_manager: *IdleInhibitManager) void {
- while (inhibit_manager.inhibitors.pop()) |inhibitor| {
- inhibitor.data.destroy.link.remove();
- util.gpa.destroy(inhibitor);
+ while (inhibit_manager.inhibitors.first()) |inhibitor| {
+ inhibitor.destroy();
}
inhibit_manager.new_idle_inhibitor.link.remove();
}
pub fn checkActive(inhibit_manager: *IdleInhibitManager) void {
var inhibited = false;
- var it = inhibit_manager.inhibitors.first;
- while (it) |node| : (it = node.next) {
- const node_data = SceneNodeData.fromSurface(node.data.wlr_inhibitor.surface) orelse continue;
+ var it = inhibit_manager.inhibitors.iterator(.forward);
+ while (it.next()) |inhibitor| {
+ const node_data = SceneNodeData.fromSurface(inhibitor.wlr_inhibitor.surface) orelse continue;
switch (node_data.data) {
.window => {
inhibited = true; // XXX be strict
@@ -68,13 +70,8 @@ pub fn checkActive(inhibit_manager: *IdleInhibitManager) void {
fn handleNewIdleInhibitor(listener: *wl.Listener(*wlr.IdleInhibitorV1), inhibitor: *wlr.IdleInhibitorV1) void {
const inhibit_manager: *IdleInhibitManager = @fieldParentPtr("new_idle_inhibitor", listener);
- const inhibitor_node = util.gpa.create(std.DoublyLinkedList(IdleInhibitor).Node) catch return;
- inhibitor_node.data.init(inhibitor, inhibit_manager) catch {
- util.gpa.destroy(inhibitor_node);
+ IdleInhibitor.create(inhibitor, inhibit_manager) catch {
+ std.log.err("out of memory", .{});
return;
};
-
- inhibit_manager.inhibitors.append(inhibitor_node);
-
- inhibit_manager.checkActive();
}
diff --git a/river/IdleInhibitor.zig b/river/IdleInhibitor.zig
index 9e2ccc5..d0b9466 100644
--- a/river/IdleInhibitor.zig
+++ b/river/IdleInhibitor.zig
@@ -28,31 +28,38 @@ const IdleInhibitManager = @import("IdleInhibitManager.zig");
inhibit_manager: *IdleInhibitManager,
wlr_inhibitor: *wlr.IdleInhibitorV1,
-destroy: wl.Listener(*wlr.Surface) = .init(handleDestroy),
+listen_destroy: wl.Listener(*wlr.Surface) = .init(handleDestroy),
+
+link: wl.list.Link,
+
+pub fn create(wlr_inhibitor: *wlr.IdleInhibitorV1, inhibit_manager: *IdleInhibitManager) !void {
+ const inhibitor = try util.gpa.create(IdleInhibitor);
+ errdefer util.gpa.destroy(inhibitor);
-pub fn init(
- inhibitor: *IdleInhibitor,
- wlr_inhibitor: *wlr.IdleInhibitorV1,
- inhibit_manager: *IdleInhibitManager,
-) !void {
inhibitor.* = .{
.inhibit_manager = inhibit_manager,
.wlr_inhibitor = wlr_inhibitor,
+ .link = undefined,
};
- wlr_inhibitor.events.destroy.add(&inhibitor.destroy);
+ wlr_inhibitor.events.destroy.add(&inhibitor.listen_destroy);
+
+ inhibit_manager.inhibitors.append(inhibitor);
inhibit_manager.checkActive();
}
-fn handleDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
- const inhibitor: *IdleInhibitor = @fieldParentPtr("destroy", listener);
-
- inhibitor.destroy.link.remove();
+pub fn destroy(inhibitor: *IdleInhibitor) void {
+ inhibitor.listen_destroy.link.remove();
- const node: *std.DoublyLinkedList(IdleInhibitor).Node = @fieldParentPtr("data", inhibitor);
- server.idle_inhibit_manager.inhibitors.remove(node);
+ inhibitor.link.remove();
inhibitor.inhibit_manager.checkActive();
- util.gpa.destroy(node);
+ util.gpa.destroy(inhibitor);
+}
+
+fn handleDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
+ const inhibitor: *IdleInhibitor = @fieldParentPtr("listen_destroy", listener);
+
+ inhibitor.destroy();
}
diff --git a/river/InputConfig.zig b/river/InputConfig.zig
index fe7af14..59f31be 100644
--- a/river/InputConfig.zig
+++ b/river/InputConfig.zig
@@ -25,7 +25,7 @@ const wlr = @import("wlroots");
const log = std.log.scoped(.input);
-const c = @import("c.zig");
+const c = @import("c.zig").c;
const server = &@import("main.zig").server;
const util = @import("util.zig");
diff --git a/river/InputDevice.zig b/river/InputDevice.zig
index 09a6767..7629755 100644
--- a/river/InputDevice.zig
+++ b/river/InputDevice.zig
@@ -24,7 +24,7 @@ const wl = @import("wayland").server.wl;
const globber = @import("globber");
-const c = @import("c.zig");
+const c = @import("c.zig").c;
const server = &@import("main.zig").server;
const util = @import("util.zig");
diff --git a/river/InputManager.zig b/river/InputManager.zig
index c8c2102..9c6b3a5 100644
--- a/river/InputManager.zig
+++ b/river/InputManager.zig
@@ -55,7 +55,7 @@ tablet_manager: *wlr.TabletManagerV2,
/// List of input device configurations. Ordered by glob generality, with
/// the most general towards the start and the most specific towards the end.
-configs: std.ArrayList(InputConfig),
+configs: std.ArrayList(InputConfig) = .empty,
devices: wl.list.Head(InputDevice, .link),
seats: wl.list.Head(Seat, .link),
@@ -78,7 +78,6 @@ pub fn init(input_manager: *InputManager) !void {
.input_method_manager = try wlr.InputMethodManagerV2.create(server.wl_server),
.text_input_manager = try wlr.TextInputManagerV3.create(server.wl_server),
.tablet_manager = try wlr.TabletManagerV2.create(server.wl_server),
- .configs = std.ArrayList(InputConfig).init(util.gpa),
.devices = undefined,
.seats = undefined,
@@ -119,7 +118,7 @@ pub fn deinit(input_manager: *InputManager) void {
for (input_manager.configs.items) |*config| {
config.deinit();
}
- input_manager.configs.deinit();
+ input_manager.configs.deinit(util.gpa);
}
pub fn defaultSeat(input_manager: *InputManager) *Seat {
diff --git a/river/KeyboardGroup.zig b/river/KeyboardGroup.zig
index 89f3f15..8dd2c4d 100644
--- a/river/KeyboardGroup.zig
+++ b/river/KeyboardGroup.zig
@@ -41,53 +41,23 @@ const KeyConsumer = union(enum) {
focus,
};
-pub const Pressed = struct {
- const Key = struct {
- /// The raw libinput keycode, not the xkb keycode
- code: u32,
- consumer: KeyConsumer,
- count: u32,
- };
-
- pub const capacity = 32;
-
- comptime {
- // wlroots uses a buffer of length 32 to track pressed keys and does not track pressed
- // keys beyond that limit. It seems likely that this can cause some inconsistency within
- // wlroots in the case that someone has 32 fingers and the hardware supports N-key rollover.
- //
- // Furthermore, wlroots will continue to forward key press/release events to river if more
- // than 32 keys are pressed. Therefore river chooses to ignore keypresses that would take
- // the keyboard beyond 32 simultaneously pressed keys.
- assert(capacity == @typeInfo(std.meta.fieldInfo(wlr.Keyboard, .keycodes).type).array.len);
- }
-
- keys: std.BoundedArray(Key, capacity) = .{},
-
- fn get(pressed: *Pressed, code: u32) ?*Key {
- for (pressed.keys.slice()) |*key| {
- if (key.code == code) return key;
- }
- return null;
- }
-
- fn add(pressed: *Pressed, new: Key) void {
- assert(pressed.get(new.code) == null);
- pressed.keys.appendAssumeCapacity(new);
- }
-
- /// Asserts that the key is present and has count == 0.
- fn remove(pressed: *Pressed, code: u32) KeyConsumer {
- for (pressed.keys.constSlice(), 0..) |key, idx| {
- if (key.code == code) {
- assert(key.count == 0);
- return pressed.keys.swapRemove(idx).consumer;
- }
- }
- unreachable;
- }
+const Press = struct {
+ consumer: KeyConsumer,
+ count: u32,
};
+pub const pressed_count_max = 32;
+comptime {
+ // wlroots uses a buffer of length 32 to track pressed keys and does not track pressed
+ // keys beyond that limit. It seems likely that this can cause some inconsistency within
+ // wlroots in the case that someone has 32 fingers and the hardware supports N-key rollover.
+ //
+ // Furthermore, wlroots will continue to forward key press/release events to river if more
+ // than 32 keys are pressed. Therefore river chooses to ignore keypresses that would take
+ // the keyboard beyond 32 simultaneously pressed keys.
+ assert(pressed_count_max == @typeInfo(std.meta.fieldInfo(wlr.Keyboard, .keycodes).type).array.len);
+}
+
ref_count: u32 = 1,
seat: *Seat,
@@ -100,8 +70,9 @@ virtual: bool,
/// setting keyboard focus.
state: wlr.Keyboard,
-/// Pressed keys along with where their press event has been sent
-pressed: Pressed = .{},
+/// Maps from pressed libinput keycode (not xkb keycode) to information
+/// about where the press event has been sent.
+pressed: std.AutoArrayHashMapUnmanaged(u32, Press) = .empty,
key: wl.Listener(*wlr.Keyboard.event.Key) = .init(handleKey),
modifiers: wl.Listener(*wlr.Keyboard) = .init(handleModifiers),
@@ -115,6 +86,10 @@ pub fn create(seat: *Seat, keymap: ?*xkb.Keymap, virtual: bool) !*KeyboardGroup
.state = undefined,
.link = undefined,
};
+
+ try group.pressed.ensureTotalCapacity(util.gpa, pressed_count_max);
+ errdefer comptime unreachable;
+
seat.keyboard_groups.append(group);
group.state.init(&.{
@@ -161,11 +136,13 @@ pub fn unref(group: *KeyboardGroup) void {
group.state.finish();
+ group.pressed.deinit(util.gpa);
+
util.gpa.destroy(group);
}
pub fn processKey(group: *KeyboardGroup, event: *const wlr.Keyboard.event.Key) void {
- if (group.pressed.get(event.keycode)) |key| {
+ if (group.pressed.getPtr(event.keycode)) |key| {
assert(key.count > 0);
if (event.state == .pressed) {
key.count += 1;
@@ -183,7 +160,7 @@ pub fn processKey(group: *KeyboardGroup, event: *const wlr.Keyboard.event.Key) v
}
}
} else if (event.state == .pressed) {
- if (group.pressed.keys.ensureUnusedCapacity(1)) {
+ if (group.pressed.count() < pressed_count_max) {
var key_event: wlr.Keyboard.event.Key = .{
.time_msec = event.time_msec,
.keycode = event.keycode,
@@ -192,7 +169,7 @@ pub fn processKey(group: *KeyboardGroup, event: *const wlr.Keyboard.event.Key) v
};
// Calls handleKey(), which will add to pressed
group.state.notifyKey(&key_event);
- } else |_| {}
+ }
}
// Release events without a prior press event are ignored.
}
@@ -211,7 +188,9 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
const consumer: KeyConsumer = blk: {
if (event.state == .released) {
// Decision is made on press; release only follows it
- break :blk group.pressed.remove(event.keycode);
+ const kv = group.pressed.fetchSwapRemove(event.keycode).?;
+ assert(kv.value.count == 0);
+ break :blk kv.value.consumer;
}
// Translate libinput keycode -> xkbcommon
const xkb_keycode = event.keycode + 8;
@@ -235,8 +214,7 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
};
if (event.state == .pressed) {
- group.pressed.add(.{
- .code = event.keycode,
+ group.pressed.putAssumeCapacityNoClobber(event.keycode, .{
.consumer = consumer,
.count = 1,
});
diff --git a/river/Output.zig b/river/Output.zig
index 5f709aa..3eef3ba 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -153,7 +153,7 @@ pub fn create(wlr_output: *wlr.Output) !void {
errdefer util.gpa.destroy(output);
{
- const title = try fmt.allocPrintZ(util.gpa, "river - {s}", .{wlr_output.name});
+ const title = try fmt.allocPrintSentinel(util.gpa, "river - {s}", .{wlr_output.name}, 0);
defer util.gpa.free(title);
if (wlr_output.isWl()) {
wlr_output.wlSetTitle(title);
diff --git a/river/OutputManager.zig b/river/OutputManager.zig
index 7cbb11c..ee63d3c 100644
--- a/river/OutputManager.zig
+++ b/river/OutputManager.zig
@@ -305,15 +305,15 @@ pub fn commitOutputState(om: *OutputManager) void {
};
if (need_modeset) {
- var states = std.ArrayList(wlr.Backend.OutputState).init(util.gpa);
- defer states.deinit();
+ var states: std.ArrayList(wlr.Backend.OutputState) = .empty;
+ defer states.deinit(util.gpa);
defer for (states.items) |*s| s.base.finish();
{
var it = wm.wm_sent.outputs.iterator(.forward);
while (it.next()) |output| {
const wlr_output = output.wlr_output orelse continue;
- const state = states.addOne() catch {
+ const state = states.addOne(util.gpa) catch {
log.err("out of memory", .{});
return;
};
diff --git a/river/PointerBinding.zig b/river/PointerBinding.zig
index bbc8816..4da340c 100644
--- a/river/PointerBinding.zig
+++ b/river/PointerBinding.zig
@@ -23,7 +23,7 @@ const wayland = @import("wayland");
const wl = wayland.server.wl;
const river = wayland.server.river;
-const c = @import("c.zig");
+const c = @import("c.zig").c;
const server = &@import("main.zig").server;
const util = @import("util.zig");
@@ -72,7 +72,7 @@ pub fn create(
log.debug("new river_pointer_binding_v1: button: {d}({?s}) modifiers: {d}", .{
button,
- c.libevdev_event_code_get_name(c.EV_KEY, button),
+ @as(?[*:0]const u8, c.libevdev_event_code_get_name(c.EV_KEY, button)),
@as(u32, @bitCast(modifiers)),
});
diff --git a/river/Seat.zig b/river/Seat.zig
index b2e9e0f..27c8192 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -24,6 +24,7 @@ const wayland = @import("wayland");
const wl = wayland.server.wl;
const river = wayland.server.river;
const xkb = @import("xkbcommon");
+const Deque = @import("deque").Deque;
const server = &@import("main.zig").server;
const util = @import("util.zig");
@@ -102,9 +103,6 @@ pub const Focus = union(enum) {
}
};
-/// XXX experiment with different sizes here, consider making dynamic
-const EventQueue = std.fifo.LinearFifo(Event, .{ .Static = 1024 });
-
wlr_seat: *wlr.Seat,
link: wl.list.Link,
@@ -113,7 +111,7 @@ destroying: bool = false,
object: ?*river.SeatV1 = null,
-event_queue: EventQueue = EventQueue.init(),
+event_queue: Deque(Event),
/// State to be sent to the wm in the next manage sequence.
wm_scheduled: struct {
@@ -185,9 +183,14 @@ pub fn create(name: [*:0]const u8) !void {
const seat = try util.gpa.create(Seat);
errdefer util.gpa.destroy(seat);
+ // XXX have actual reasoning for choosing this capacity.
+ var event_queue: Deque(Event) = try .initCapacity(util.gpa, 1024);
+ errdefer event_queue.deinit(util.gpa);
+
seat.* = .{
// This will be automatically destroyed when the display is destroyed
.wlr_seat = try wlr.Seat.create(server.wl_server, name),
+ .event_queue = event_queue,
.link = undefined,
.link_sent = undefined,
.xkb_bindings = undefined,
@@ -225,6 +228,7 @@ pub fn destroy(seat: *Seat) void {
seat.link.remove();
seat.link_sent.remove();
+ seat.event_queue.deinit(util.gpa);
seat.cursor.deinit();
seat.request_set_selection.link.remove();
@@ -237,7 +241,7 @@ pub fn destroy(seat: *Seat) void {
pub fn queueEvent(seat: *Seat, event: Event) !void {
seat.handleActivity();
- seat.event_queue.writeItem(event) catch {
+ seat.event_queue.pushBackBounded(event) catch {
log.err("dropping {s} event, no space in event queue", .{@tagName(event)});
return error.QueueFull;
};
@@ -256,7 +260,7 @@ pub fn processEvents(seat: *Seat) void {
while (!server.wm.wm_scheduled.dirty) {
assert(server.wm.state == .idle);
- const event = seat.event_queue.readItem() orelse break;
+ const event = seat.event_queue.popFront() orelse break;
const pg = server.input_manager.pointer_gestures;
switch (event) {
@@ -573,14 +577,15 @@ fn keyboardNotifyEnter(seat: *Seat, wlr_surface: *wlr.Surface) void {
if (seat.wlr_seat.getKeyboard()) |wlr_keyboard| {
const group: *KeyboardGroup = @alignCast(@ptrCast(wlr_keyboard.data));
- var keycodes: std.BoundedArray(u32, KeyboardGroup.Pressed.capacity) = .{};
- for (group.pressed.keys.constSlice()) |item| {
- if (item.consumer == .focus) keycodes.appendAssumeCapacity(item.code);
+ var buffer: [KeyboardGroup.pressed_count_max]u32 = undefined;
+ var keycodes: std.ArrayList(u32) = .initBuffer(&buffer);
+ for (group.pressed.keys(), group.pressed.values()) |keycode, press| {
+ if (press.consumer == .focus) keycodes.appendAssumeCapacity(keycode);
}
seat.wlr_seat.keyboardNotifyEnter(
wlr_surface,
- keycodes.constSlice(),
+ keycodes.items,
&group.state.modifiers,
);
} else {
diff --git a/river/Server.zig b/river/Server.zig
index 4835695..97246e6 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -24,7 +24,7 @@ const posix = std.posix;
const wlr = @import("wlroots");
const wl = @import("wayland").server.wl;
-const c = @import("c.zig");
+const c = @import("c.zig").c;
const util = @import("util.zig");
const Config = @import("Config.zig");
diff --git a/river/ShellSurface.zig b/river/ShellSurface.zig
index 6bd9fbd..3908156 100644
--- a/river/ShellSurface.zig
+++ b/river/ShellSurface.zig
@@ -135,7 +135,7 @@ fn handleRequest(
}
}
-fn clientCommit(wlr_surface: *wlr.Surface) callconv(.C) void {
+fn clientCommit(wlr_surface: *wlr.Surface) callconv(.c) void {
if (wlr_surface.role != &role) return;
const resource = wlr_surface.role_resource orelse return;
@@ -146,7 +146,7 @@ fn clientCommit(wlr_surface: *wlr.Surface) callconv(.C) void {
}
}
-fn commit(wlr_surface: *wlr.Surface) callconv(.C) void {
+fn commit(wlr_surface: *wlr.Surface) callconv(.c) void {
if (wlr_surface.hasBuffer()) {
wlr_surface.map();
}
diff --git a/river/XkbBinding.zig b/river/XkbBinding.zig
index 3568cda..ed87c1e 100644
--- a/river/XkbBinding.zig
+++ b/river/XkbBinding.zig
@@ -100,9 +100,9 @@ fn handleDestroy(_: *river.XkbBindingV1, binding: *XkbBinding) void {
{
var it = binding.seat.keyboard_groups.iterator(.forward);
while (it.next()) |group| {
- for (group.pressed.keys.slice()) |*key| {
- if (key.consumer == .binding and key.consumer.binding == binding) {
- key.consumer.binding = null;
+ for (group.pressed.values()) |*press| {
+ if (press.consumer == .binding and press.consumer.binding == binding) {
+ press.consumer.binding = null;
}
}
}
diff --git a/river/c.zig b/river/c.zig
index 05b5c25..59dd8a0 100644
--- a/river/c.zig
+++ b/river/c.zig
@@ -14,7 +14,7 @@
// 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 usingnamespace @cImport({
+pub const c = @cImport({
@cDefine("_POSIX_C_SOURCE", "200809L");
@cInclude("stdlib.h");
diff --git a/river/main.zig b/river/main.zig
index 2b689c2..17a68f5 100644
--- a/river/main.zig
+++ b/river/main.zig
@@ -25,7 +25,7 @@ const builtin = @import("builtin");
const wlr = @import("wlroots");
const flags = @import("flags");
-const c = @import("c.zig");
+const c = @import("c.zig").c;
const util = @import("util.zig");
const process = @import("process.zig");
@@ -53,21 +53,25 @@ pub fn main() anyerror!void {
.{ .name = "log-scopes", .kind = .arg },
.{ .name = "no-xwayland", .kind = .boolean },
}).parse(std.os.argv[1..]) catch {
- try io.getStdErr().writeAll(usage);
+ try stderr.writeAll(usage);
+ try stderr.flush();
posix.exit(1);
};
if (result.flags.h) {
- try io.getStdOut().writeAll(usage);
+ try stdout.writeAll(usage);
+ try stdout.flush();
posix.exit(0);
}
if (result.args.len != 0) {
log.err("unknown option '{s}'", .{result.args[0]});
- try io.getStdErr().writeAll(usage);
+ try stderr.writeAll(usage);
+ try stderr.flush();
posix.exit(1);
}
if (result.flags.version) {
- try io.getStdOut().writeAll(build_options.version ++ "\n");
+ try stdout.writeAll(build_options.version ++ "\n");
+ try stdout.flush();
posix.exit(0);
}
if (result.flags.@"log-level") |level| {
@@ -81,7 +85,6 @@ pub fn main() anyerror!void {
runtime_log_level = .debug;
} else {
log.err("invalid log level '{s}'", .{level});
- try io.getStdErr().writeAll(usage);
posix.exit(1);
}
}
@@ -186,6 +189,14 @@ fn defaultInitPath() !?[:0]const u8 {
return path;
}
+var stderr_buffer: [1024]u8 = undefined;
+var stderr_writer = fs.File.stderr().writer(&stderr_buffer);
+const stderr = &stderr_writer.interface;
+
+var stdout_buffer: [1024]u8 = undefined;
+var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
+const stdout = &stdout_writer.interface;
+
// Scopes should be added to this list sparingly.
// Only add new scopes if filtering based on them would be meaningful.
const LogScope = enum {
@@ -225,8 +236,8 @@ pub fn logFn(
const scope_prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
- const stderr = io.getStdErr().writer();
- stderr.print(level.asText() ++ scope_prefix ++ format ++ "\n", args) catch {};
+ stderr.print(level.asText() ++ scope_prefix ++ format ++ "\n", args) catch return;
+ stderr.flush() catch return;
}
/// See wlroots_log_wrapper.c
diff --git a/river/process.zig b/river/process.zig
index fb160e9..7709a7d 100644
--- a/river/process.zig
+++ b/river/process.zig
@@ -17,7 +17,7 @@
const std = @import("std");
const posix = std.posix;
-const c = @import("c.zig");
+const c = @import("c.zig").c;
var original_rlimit: ?posix.rlimit = null;
@@ -26,7 +26,7 @@ pub fn setup() void {
// has had its read end closed by another process.
const sig_ign = posix.Sigaction{
.handler = .{ .handler = posix.SIG.IGN },
- .mask = posix.empty_sigset,
+ .mask = posix.sigemptyset(),
.flags = 0,
};
posix.sigaction(posix.SIG.PIPE, &sig_ign, null);
@@ -61,11 +61,11 @@ pub fn setup() void {
pub fn cleanupChild() void {
if (c.setsid() < 0) unreachable;
- if (posix.system.sigprocmask(posix.SIG.SETMASK, &posix.empty_sigset, null) < 0) unreachable;
+ if (posix.system.sigprocmask(posix.SIG.SETMASK, &posix.sigemptyset(), null) < 0) unreachable;
const sig_dfl = posix.Sigaction{
.handler = .{ .handler = posix.SIG.DFL },
- .mask = posix.empty_sigset,
+ .mask = posix.sigemptyset(),
.flags = 0,
};
posix.sigaction(posix.SIG.PIPE, &sig_dfl, null);