build: update to Zig 0.15

This commit is contained in:
Isaac Freund
2025-07-11 18:08:50 +02:00
parent 63542fdf3e
commit d72408df18
37 changed files with 323 additions and 274 deletions

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.14.0/zig-linux-x86_64-0.14.0.tar.xz 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-linux-x86_64-0.14.0.tar.xz tar xf zig-x86_64-linux-0.15.1.tar.xz
sudo mv zig-linux-x86_64-0.14.0/zig /usr/bin/ sudo mv zig-x86_64-linux-0.15.1/zig /usr/bin/
sudo mv zig-linux-x86_64-0.14.0/lib /usr/lib/zig sudo mv zig-x86_64-linux-0.15.1/lib /usr/lib/zig
- build: | - build: |
cd river cd river
zig build --summary all zig build --summary all

View File

@ -34,10 +34,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.14.0/zig-linux-x86_64-0.14.0.tar.xz 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-linux-x86_64-0.14.0.tar.xz tar xf zig-x86_64-linux-0.15.1.tar.xz
sudo mv zig-linux-x86_64-0.14.0/zig /usr/bin/ sudo mv zig-x86_64-linux-0.15.1/zig /usr/bin/
sudo mv zig-linux-x86_64-0.14.0/lib /usr/lib/zig sudo mv zig-x86_64-linux-0.15.1/lib /usr/lib/zig
- build: | - build: |
cd river cd river
zig build --summary all zig build --summary all

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.14.0/zig-freebsd-x86_64-0.14.0-unofficial.tar.xz 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-freebsd-x86_64-0.14.0-unofficial.tar.xz tar xf zig-x86_64-freebsd-0.15.1.tar.xz
sudo mv zig-freebsd-x86_64-0.14.0-unofficial/zig /usr/bin/ sudo mv zig-x86_64-freebsd-0.15.1/zig /usr/bin/
sudo mv zig-freebsd-x86_64-0.14.0-unofficial/lib /usr/lib/zig sudo mv zig-x86_64-freebsd-0.15.1/lib /usr/lib/zig
- build: | - build: |
cd river cd river
zig build --summary all zig build --summary all

View File

@ -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 installed. The "development" versions are required if applicable to your
distribution. distribution.
- [zig](https://ziglang.org/download/) 0.14 - [zig](https://ziglang.org/download/) 0.15
- wayland - wayland
- wayland-protocols - wayland-protocols
- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.19 - [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.19

View File

@ -12,7 +12,6 @@ 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 llvm = !(b.option(bool, "no-llvm", "(expirimental) Use non-LLVM x86 Zig backend") orelse false);
const omit_frame_pointer = switch (optimize) { const omit_frame_pointer = switch (optimize) {
.Debug, .ReleaseSafe => false, .Debug, .ReleaseSafe => false,
@ -150,12 +149,12 @@ pub fn build(b: *Build) !void {
{ {
const river = b.addExecutable(.{ const river = b.addExecutable(.{
.name = "river", .name = "river",
.root_source_file = b.path("river/main.zig"), .root_module = b.createModule(.{
.target = target, .root_source_file = b.path("river/main.zig"),
.optimize = optimize, .target = target,
.strip = strip, .optimize = optimize,
.use_llvm = llvm, .strip = strip,
.use_lld = llvm, }),
}); });
river.root_module.addOptions("build_options", options); river.root_module.addOptions("build_options", options);
@ -188,12 +187,12 @@ pub fn build(b: *Build) !void {
{ {
const riverctl = b.addExecutable(.{ const riverctl = b.addExecutable(.{
.name = "riverctl", .name = "riverctl",
.root_source_file = b.path("riverctl/main.zig"), .root_module = b.createModule(.{
.target = target, .root_source_file = b.path("riverctl/main.zig"),
.optimize = optimize, .target = target,
.strip = strip, .optimize = optimize,
.use_llvm = llvm, .strip = strip,
.use_lld = llvm, }),
}); });
riverctl.root_module.addOptions("build_options", options); riverctl.root_module.addOptions("build_options", options);
@ -211,12 +210,12 @@ pub fn build(b: *Build) !void {
{ {
const rivertile = b.addExecutable(.{ const rivertile = b.addExecutable(.{
.name = "rivertile", .name = "rivertile",
.root_source_file = b.path("rivertile/main.zig"), .root_module = b.createModule(.{
.target = target, .root_source_file = b.path("rivertile/main.zig"),
.optimize = optimize, .target = target,
.strip = strip, .optimize = optimize,
.use_llvm = llvm, .strip = strip,
.use_lld = llvm, }),
}); });
rivertile.root_module.addOptions("build_options", options); rivertile.root_module.addOptions("build_options", options);
@ -275,9 +274,11 @@ pub fn build(b: *Build) !void {
{ {
const globber_test = b.addTest(.{ const globber_test = b.addTest(.{
.root_source_file = b.path("common/globber.zig"), .root_module = b.createModule(.{
.target = target, .root_source_file = b.path("common/globber.zig"),
.optimize = optimize, .target = target,
.optimize = optimize,
}),
}); });
const run_globber_test = b.addRunArtifact(globber_test); const run_globber_test = b.addRunArtifact(globber_test);

View File

@ -13,12 +13,12 @@
.hash = "pixman-0.3.0-LClMnz2VAAAs7QSCGwLimV5VUYx0JFnX5xWU6HwtMuDX", .hash = "pixman-0.3.0-LClMnz2VAAAs7QSCGwLimV5VUYx0JFnX5xWU6HwtMuDX",
}, },
.wayland = .{ .wayland = .{
.url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.3.0.tar.gz", .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.4.0.tar.gz",
.hash = "wayland-0.3.0-lQa1kjPIAQDmhGYpY-zxiRzQJFHQ2VqhJkQLbKKdt5wl", .hash = "wayland-0.4.0-lQa1khbMAQAsLS2eBR7M5lofyEGPIbu2iFDmoz8lPC27",
}, },
.wlroots = .{ .wlroots = .{
.url = "https://codeberg.org/ifreund/zig-wlroots/archive/f92ba27133ecf702d85c9d3894f98a336389bbd9.tar.gz", .url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.19.3.tar.gz",
.hash = "wlroots-0.19.3-dev-jmOlcr7_AwClfjFwW8oOkWoqAbt9oPLqgdvfFYEXqlOF", .hash = "wlroots-0.19.3-jmOlcuL_AwBHhLCwpFsXbTizE3q9BugFmGX-XIxqcPMc",
}, },
.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",

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"); const c = @import("c.zig").c;
const server = &@import("main.zig").server; const server = &@import("main.zig").server;
const util = @import("util.zig"); const util = @import("util.zig");
@ -296,7 +296,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 // If this cursor belongs to the default seat, set the xcursor environment
// variables as well as the xwayland cursor theme. // variables as well as the xwayland cursor theme.
if (cursor.seat == server.input_manager.defaultSeat()) { 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); defer util.gpa.free(size_str);
if (c.setenv("XCURSOR_SIZE", size_str.ptr, 1) < 0) return error.OutOfMemory; 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; if (theme) |t| if (c.setenv("XCURSOR_THEME", t, 1) < 0) return error.OutOfMemory;
@ -378,8 +378,8 @@ fn handleAxis(listener: *wl.Listener(*wlr.Pointer.event.Axis), event: *wlr.Point
// @intFromFloat() call safe due to the max/min i32 not being exactly representable // @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 // 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. // allow users to set their scroll-factor to inf without crashing river.
math.minInt(i32) / 2, @as(f32, @floatFromInt(math.minInt(i32) / 2)),
math.maxInt(i32) / 2, @as(f32, @floatFromInt(math.maxInt(i32) / 2)),
)), )),
event.source, event.source,
event.relative_direction, event.relative_direction,

View File

@ -30,28 +30,29 @@ const View = @import("View.zig");
wlr_manager: *wlr.IdleInhibitManagerV1, wlr_manager: *wlr.IdleInhibitManagerV1,
new_idle_inhibitor: wl.Listener(*wlr.IdleInhibitorV1) = new_idle_inhibitor: wl.Listener(*wlr.IdleInhibitorV1) =
wl.Listener(*wlr.IdleInhibitorV1).init(handleNewIdleInhibitor), wl.Listener(*wlr.IdleInhibitorV1).init(handleNewIdleInhibitor),
inhibitors: std.DoublyLinkedList(IdleInhibitor) = .{}, inhibitors: wl.list.Head(IdleInhibitor, .link),
pub fn init(inhibit_manager: *IdleInhibitManager) !void { pub fn init(inhibit_manager: *IdleInhibitManager) !void {
inhibit_manager.* = .{ inhibit_manager.* = .{
.wlr_manager = try wlr.IdleInhibitManagerV1.create(server.wl_server), .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); inhibit_manager.wlr_manager.events.new_inhibitor.add(&inhibit_manager.new_idle_inhibitor);
} }
pub fn deinit(inhibit_manager: *IdleInhibitManager) void { pub fn deinit(inhibit_manager: *IdleInhibitManager) void {
while (inhibit_manager.inhibitors.pop()) |inhibitor| { while (inhibit_manager.inhibitors.first()) |inhibitor| {
inhibitor.data.destroy.link.remove(); inhibitor.destroy();
util.gpa.destroy(inhibitor);
} }
inhibit_manager.new_idle_inhibitor.link.remove(); inhibit_manager.new_idle_inhibitor.link.remove();
} }
pub fn checkActive(inhibit_manager: *IdleInhibitManager) void { pub fn checkActive(inhibit_manager: *IdleInhibitManager) void {
var inhibited = false; var inhibited = false;
var it = inhibit_manager.inhibitors.first; var it = inhibit_manager.inhibitors.iterator(.forward);
while (it) |node| : (it = node.next) { while (it.next()) |inhibitor| {
const node_data = SceneNodeData.fromSurface(node.data.wlr_inhibitor.surface) orelse continue; const node_data = SceneNodeData.fromSurface(inhibitor.wlr_inhibitor.surface) orelse continue;
switch (node_data.data) { switch (node_data.data) {
.view => |view| { .view => |view| {
if (view.current.output != null and if (view.current.output != null and
@ -79,13 +80,8 @@ pub fn checkActive(inhibit_manager: *IdleInhibitManager) void {
fn handleNewIdleInhibitor(listener: *wl.Listener(*wlr.IdleInhibitorV1), inhibitor: *wlr.IdleInhibitorV1) void { fn handleNewIdleInhibitor(listener: *wl.Listener(*wlr.IdleInhibitorV1), inhibitor: *wlr.IdleInhibitorV1) void {
const inhibit_manager: *IdleInhibitManager = @fieldParentPtr("new_idle_inhibitor", listener); const inhibit_manager: *IdleInhibitManager = @fieldParentPtr("new_idle_inhibitor", listener);
const inhibitor_node = util.gpa.create(std.DoublyLinkedList(IdleInhibitor).Node) catch return; IdleInhibitor.create(inhibitor, inhibit_manager) catch {
inhibitor_node.data.init(inhibitor, inhibit_manager) catch { std.log.err("out of memory", .{});
util.gpa.destroy(inhibitor_node);
return; return;
}; };
inhibit_manager.inhibitors.append(inhibitor_node);
inhibit_manager.checkActive();
} }

View File

@ -28,31 +28,38 @@ const IdleInhibitManager = @import("IdleInhibitManager.zig");
inhibit_manager: *IdleInhibitManager, inhibit_manager: *IdleInhibitManager,
wlr_inhibitor: *wlr.IdleInhibitorV1, wlr_inhibitor: *wlr.IdleInhibitorV1,
destroy: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleDestroy), listen_destroy: wl.Listener(*wlr.Surface) = 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.* = .{ inhibitor.* = .{
.inhibit_manager = inhibit_manager, .inhibit_manager = inhibit_manager,
.wlr_inhibitor = wlr_inhibitor, .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(); inhibit_manager.checkActive();
} }
fn handleDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void { pub fn destroy(inhibitor: *IdleInhibitor) void {
const inhibitor: *IdleInhibitor = @fieldParentPtr("destroy", listener); inhibitor.listen_destroy.link.remove();
inhibitor.destroy.link.remove(); inhibitor.link.remove();
const node: *std.DoublyLinkedList(IdleInhibitor).Node = @fieldParentPtr("data", inhibitor);
server.idle_inhibit_manager.inhibitors.remove(node);
inhibitor.inhibit_manager.checkActive(); 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();
} }

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"); const c = @import("c.zig").c;
const server = &@import("main.zig").server; const server = &@import("main.zig").server;
const util = @import("util.zig"); const util = @import("util.zig");
@ -373,7 +373,7 @@ pub fn parse(config: *InputConfig, setting: []const u8, value: []const u8) !void
return error.UnknownCommand; return error.UnknownCommand;
} }
pub fn write(config: *InputConfig, writer: anytype) !void { pub fn write(config: *InputConfig, writer: *std.Io.Writer) !void {
try writer.print("{s}\n", .{config.glob}); try writer.print("{s}\n", .{config.glob});
inline for (@typeInfo(InputConfig).@"struct".fields) |field| { inline for (@typeInfo(InputConfig).@"struct".fields) |field| {

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"); const c = @import("c.zig").c;
const server = &@import("main.zig").server; const server = &@import("main.zig").server;
const util = @import("util.zig"); const util = @import("util.zig");

View File

@ -55,10 +55,10 @@ 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) = .{},
devices: wl.list.Head(InputDevice, .link), devices: wl.list.Head(InputDevice, .link),
seats: std.DoublyLinkedList(Seat) = .{}, seats: wl.list.Head(Seat, .link),
exclusive_client: ?*wl.Client = null, exclusive_client: ?*wl.Client = null,
@ -74,9 +74,6 @@ new_text_input: wl.Listener(*wlr.TextInputV3) =
wl.Listener(*wlr.TextInputV3).init(handleNewTextInput), wl.Listener(*wlr.TextInputV3).init(handleNewTextInput),
pub fn init(input_manager: *InputManager) !void { pub fn init(input_manager: *InputManager) !void {
const seat_node = try util.gpa.create(std.DoublyLinkedList(Seat).Node);
errdefer util.gpa.destroy(seat_node);
input_manager.* = .{ input_manager.* = .{
// These are automatically freed when the display is destroyed // These are automatically freed when the display is destroyed
.idle_notifier = try wlr.IdleNotifierV1.create(server.wl_server), .idle_notifier = try wlr.IdleNotifierV1.create(server.wl_server),
@ -88,14 +85,14 @@ pub fn init(input_manager: *InputManager) !void {
.input_method_manager = try wlr.InputMethodManagerV2.create(server.wl_server), .input_method_manager = try wlr.InputMethodManagerV2.create(server.wl_server),
.text_input_manager = try wlr.TextInputManagerV3.create(server.wl_server), .text_input_manager = try wlr.TextInputManagerV3.create(server.wl_server),
.tablet_manager = try wlr.TabletManagerV2.create(server.wl_server), .tablet_manager = try wlr.TabletManagerV2.create(server.wl_server),
.configs = std.ArrayList(InputConfig).init(util.gpa),
.seats = undefined,
.devices = undefined, .devices = undefined,
}; };
input_manager.seats.init();
input_manager.devices.init(); input_manager.devices.init();
input_manager.seats.prepend(seat_node); try Seat.create(default_seat_name);
try seat_node.data.init(default_seat_name);
if (build_options.xwayland) { if (build_options.xwayland) {
if (server.xwayland) |xwayland| { if (server.xwayland) |xwayland| {
@ -121,19 +118,18 @@ pub fn deinit(input_manager: *InputManager) void {
input_manager.new_input_method.link.remove(); input_manager.new_input_method.link.remove();
input_manager.new_text_input.link.remove(); input_manager.new_text_input.link.remove();
while (input_manager.seats.pop()) |seat_node| { while (input_manager.seats.first()) |seat| {
seat_node.data.deinit(); seat.destroy();
util.gpa.destroy(seat_node);
} }
for (input_manager.configs.items) |*config| { for (input_manager.configs.items) |*config| {
config.deinit(); config.deinit();
} }
input_manager.configs.deinit(); input_manager.configs.deinit(util.gpa);
} }
pub fn defaultSeat(input_manager: InputManager) *Seat { pub fn defaultSeat(input_manager: *InputManager) *Seat {
return &input_manager.seats.first.?.data; return input_manager.seats.first().?;
} }
/// Returns true if input is currently allowed on the passed surface. /// Returns true if input is currently allowed on the passed surface.

View File

@ -57,33 +57,52 @@ pub const Pressed = struct {
assert(capacity == @typeInfo(std.meta.fieldInfo(wlr.Keyboard, .keycodes).type).array.len); assert(capacity == @typeInfo(std.meta.fieldInfo(wlr.Keyboard, .keycodes).type).array.len);
} }
keys: std.BoundedArray(Key, capacity) = .{}, keys: [capacity]Key,
len: usize,
const empty: Pressed = .{ .keys = undefined, .len = 0 };
pub fn slice(pressed: *Pressed) []Key {
return pressed.keys[0..pressed.len];
}
fn contains(pressed: *Pressed, code: u32) bool { fn contains(pressed: *Pressed, code: u32) bool {
for (pressed.keys.constSlice()) |item| { for (pressed.slice()) |item| {
if (item.code == code) return true; if (item.code == code) return true;
} }
return false; return false;
} }
fn addAssumeCapacity(pressed: *Pressed, new: Key) void { fn addAssumeCapacity(pressed: *Pressed, new: Key) void {
assert(pressed.len < pressed.keys.len);
assert(!pressed.contains(new.code)); assert(!pressed.contains(new.code));
pressed.keys.appendAssumeCapacity(new); pressed.keys[pressed.len] = new;
pressed.len += 1;
} }
fn remove(pressed: *Pressed, code: u32) ?KeyConsumer { fn remove(pressed: *Pressed, code: u32) ?KeyConsumer {
for (pressed.keys.constSlice(), 0..) |item, idx| { for (pressed.slice(), 0..) |item, idx| {
if (item.code == code) return pressed.keys.swapRemove(idx).consumer; if (item.code == code) return pressed.swapRemove(idx).consumer;
} }
return null; return null;
} }
fn swapRemove(pressed: *Pressed, index: usize) Key {
defer pressed.len -= 1;
if (index == pressed.len - 1) {
return pressed.keys[index];
}
const ret = pressed.keys[index];
pressed.keys[index] = pressed.keys[pressed.len - 1];
return ret;
}
}; };
device: InputDevice, device: InputDevice,
/// Pressed keys along with where their press event has been sent /// Pressed keys along with where their press event has been sent
pressed: Pressed = .{}, pressed: Pressed = .empty,
key: wl.Listener(*wlr.Keyboard.event.Key) = wl.Listener(*wlr.Keyboard.event.Key).init(handleKey), key: wl.Listener(*wlr.Keyboard.event.Key) = wl.Listener(*wlr.Keyboard.event.Key).init(handleKey),
modifiers: wl.Listener(*wlr.Keyboard) = wl.Listener(*wlr.Keyboard).init(handleModifiers), modifiers: wl.Listener(*wlr.Keyboard) = wl.Listener(*wlr.Keyboard).init(handleModifiers),
@ -203,7 +222,9 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
// Ignore key presses beyond 32 simultaneously pressed keys (see comments in Pressed). // Ignore key presses beyond 32 simultaneously pressed keys (see comments in Pressed).
// We must ensure capacity before calling handleMapping() to ensure that we either run // We must ensure capacity before calling handleMapping() to ensure that we either run
// both the press and release mapping for certain key or neither mapping. // both the press and release mapping for certain key or neither mapping.
keyboard.pressed.keys.ensureUnusedCapacity(1) catch return; if (keyboard.pressed.len >= keyboard.pressed.keys.len) {
return;
}
if (keyboard.device.seat.handleMapping(keycode, modifiers, released, xkb_state)) { if (keyboard.device.seat.handleMapping(keycode, modifiers, released, xkb_state)) {
break :blk .mapping; break :blk .mapping;

View File

@ -173,10 +173,8 @@ fn handleKeyboardInteractiveExclusive(output: *Output, consider: ?*LayerSurface)
assert(s.wlr_layer_surface.current.keyboard_interactive != .none); assert(s.wlr_layer_surface.current.keyboard_interactive != .none);
} }
var it = server.input_manager.seats.first; var it = server.input_manager.seats.iterator(.forward);
while (it) |node| : (it = node.next) { while (it.next()) |seat| {
const seat = &node.data;
if (seat.focused_output == output) { if (seat.focused_output == output) {
if (to_focus) |s| { if (to_focus) |s| {
// If we found a surface on the output that requires focus, grab the focus of all // If we found a surface on the output that requires focus, grab the focus of all

View File

@ -38,6 +38,9 @@ layout_v3: *river.LayoutV3,
namespace: []const u8, namespace: []const u8,
output: *Output, output: *Output,
// Output.layouts
link: wl.list.Link,
pub fn create(client: *wl.Client, version: u32, id: u32, output: *Output, namespace: []const u8) !void { pub fn create(client: *wl.Client, version: u32, id: u32, output: *Output, namespace: []const u8) !void {
const layout_v3 = try river.LayoutV3.create(client, version, id); const layout_v3 = try river.LayoutV3.create(client, version, id);
@ -47,21 +50,22 @@ pub fn create(client: *wl.Client, version: u32, id: u32, output: *Output, namesp
return; return;
} }
const node = try util.gpa.create(std.DoublyLinkedList(Layout).Node); const layout = try util.gpa.create(Layout);
errdefer util.gpa.destroy(node); errdefer util.gpa.destroy(layout);
node.data = .{ layout.* = .{
.layout_v3 = layout_v3, .layout_v3 = layout_v3,
.namespace = try util.gpa.dupe(u8, namespace), .namespace = try util.gpa.dupe(u8, namespace),
.output = output, .output = output,
.link = undefined,
}; };
output.layouts.append(node); output.layouts.append(layout);
layout_v3.setHandler(*Layout, handleRequest, handleDestroy, &node.data); layout_v3.setHandler(*Layout, handleRequest, handleDestroy, layout);
// If the namespace matches that of the output, set the layout as // If the namespace matches that of the output, set the layout as
// the active one of the output and arrange it. // the active one of the output and arrange it.
if (mem.eql(u8, namespace, output.layoutNamespace())) { if (mem.eql(u8, namespace, output.layoutNamespace())) {
output.layout = &node.data; output.layout = layout;
server.root.applyPending(); server.root.applyPending();
} }
} }
@ -71,17 +75,17 @@ pub fn create(client: *wl.Client, version: u32, id: u32, output: *Output, namesp
fn namespaceInUse(namespace: []const u8, output: *Output, client: *wl.Client) bool { fn namespaceInUse(namespace: []const u8, output: *Output, client: *wl.Client) bool {
var output_it = server.root.active_outputs.iterator(.forward); var output_it = server.root.active_outputs.iterator(.forward);
while (output_it.next()) |o| { while (output_it.next()) |o| {
var layout_it = output.layouts.first; var layout_it = output.layouts.iterator(.forward);
if (o == output) { if (o == output) {
// On this output, no other layout can have our namespace. // On this output, no other layout can have our namespace.
while (layout_it) |layout_node| : (layout_it = layout_node.next) { while (layout_it.next()) |layout| {
if (mem.eql(u8, namespace, layout_node.data.namespace)) return true; if (mem.eql(u8, namespace, layout.namespace)) return true;
} }
} else { } else {
// Layouts on other outputs may share the namespace, if they come from the same client. // Layouts on other outputs may share the namespace, if they come from the same client.
while (layout_it) |layout_node| : (layout_it = layout_node.next) { while (layout_it.next()) |layout| {
if (mem.eql(u8, namespace, layout_node.data.namespace) and if (mem.eql(u8, namespace, layout.namespace) and
client != layout_node.data.layout_v3.getClient()) return true; client != layout.layout_v3.getClient()) return true;
} }
} }
} }
@ -185,9 +189,7 @@ pub fn destroy(layout: *Layout) void {
.{ layout.namespace, layout.output.wlr_output.name }, .{ layout.namespace, layout.output.wlr_output.name },
); );
// Remove layout from the list layout.link.remove();
const node: *std.DoublyLinkedList(Layout).Node = @fieldParentPtr("data", layout);
layout.output.layouts.remove(node);
// If we are the currently active layout of an output, clean up. // If we are the currently active layout of an output, clean up.
if (layout.output.layout == layout) { if (layout.output.layout == layout) {
@ -208,5 +210,5 @@ pub fn destroy(layout: *Layout) void {
layout.layout_v3.setHandler(?*anyopaque, handleRequestInert, null, null); layout.layout_v3.setHandler(?*anyopaque, handleRequestInert, null, null);
util.gpa.free(layout.namespace); util.gpa.free(layout.namespace);
util.gpa.destroy(node); util.gpa.destroy(layout);
} }

View File

@ -110,9 +110,8 @@ fn handleLock(listener: *wl.Listener(*wlr.SessionLockV1), lock: *wlr.SessionLock
}; };
{ {
var it = server.input_manager.seats.first; var it = server.input_manager.seats.iterator(.forward);
while (it) |node| : (it = node.next) { while (it.next()) |seat| {
const seat = &node.data;
seat.setFocusRaw(.none); seat.setFocusRaw(.none);
// Enter locked mode // Enter locked mode
@ -213,9 +212,8 @@ fn handleUnlock(listener: *wl.Listener(void)) void {
} }
{ {
var it = server.input_manager.seats.first; var it = server.input_manager.seats.iterator(.forward);
while (it) |node| : (it = node.next) { while (it.next()) |seat| {
const seat = &node.data;
seat.setFocusRaw(.none); seat.setFocusRaw(.none);
// Exit locked mode // Exit locked mode

View File

@ -68,9 +68,8 @@ pub fn destroy(lock_surface: *LockSurface) void {
break .{ .lock_surface = @alignCast(@ptrCast(surface.data)) }; break .{ .lock_surface = @alignCast(@ptrCast(surface.data)) };
} else .none; } else .none;
var seat_it = server.input_manager.seats.first; var seat_it = server.input_manager.seats.iterator(.forward);
while (seat_it) |node| : (seat_it = node.next) { while (seat_it.next()) |seat| {
const seat = &node.data;
if (seat.focused == .lock_surface and seat.focused.lock_surface == lock_surface) { if (seat.focused == .lock_surface and seat.focused.lock_surface == lock_surface) {
seat.setFocusRaw(new_focus); seat.setFocusRaw(new_focus);
} }
@ -122,9 +121,8 @@ fn handleMap(listener: *wl.Listener(void)) void {
} }
fn updateFocus(lock_surface: *LockSurface) void { fn updateFocus(lock_surface: *LockSurface) void {
var it = server.input_manager.seats.first; var it = server.input_manager.seats.iterator(.forward);
while (it) |node| : (it = node.next) { while (it.next()) |seat| {
const seat = &node.data;
if (seat.focused != .lock_surface) { if (seat.focused != .lock_surface) {
seat.setFocusRaw(.{ .lock_surface = lock_surface }); seat.setFocusRaw(.{ .lock_surface = lock_surface });
} }

View File

@ -167,7 +167,7 @@ previous_tags: u32 = 1 << 0,
attach_mode: ?Config.AttachMode = null, attach_mode: ?Config.AttachMode = null,
/// List of all layouts /// List of all layouts
layouts: std.DoublyLinkedList(Layout) = .{}, layouts: wl.list.Head(Layout, .link),
/// The current layout namespace of the output. If null, /// The current layout namespace of the output. If null,
/// config.default_layout_namespace should be used instead. /// config.default_layout_namespace should be used instead.
@ -290,9 +290,12 @@ pub fn create(wlr_output: *wlr.Output) !void {
.height = height, .height = height,
}, },
.status = undefined, .status = undefined,
.layouts = undefined,
}; };
wlr_output.data = output; wlr_output.data = output;
output.layouts.init();
output.pending.focus_stack.init(); output.pending.focus_stack.init();
output.pending.wm_stack.init(); output.pending.wm_stack.init();
output.inflight.focus_stack.init(); output.inflight.focus_stack.init();
@ -416,7 +419,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), _: *wlr.Output) void {
assert(output.inflight.focus_stack.empty()); assert(output.inflight.focus_stack.empty());
assert(output.inflight.wm_stack.empty()); assert(output.inflight.wm_stack.empty());
assert(output.inflight.layout_demand == null); assert(output.inflight.layout_demand == null);
assert(output.layouts.len == 0); assert(output.layouts.length() == 0);
output.all_link.remove(); output.all_link.remove();
@ -452,7 +455,7 @@ fn handleRequestState(listener: *wl.Listener(*wlr.Output.event.RequestState), ev
// TODO double buffer output state changes for frame perfection and cleaner code. // TODO double buffer output state changes for frame perfection and cleaner code.
// Schedule a frame and commit in the frame handler. // Schedule a frame and commit in the frame handler.
// Get rid of this function. // Get rid of this function.
pub fn applyState(output: *Output, state: *wlr.Output.State) error{CommitFailed}!void { pub fn applyState(output: *Output, state: *const wlr.Output.State) error{CommitFailed}!void {
// We need to be precise about this state change to make assertions // We need to be precise about this state change to make assertions
// in updateLockRenderStateOnEnableDisable() possible. // in updateLockRenderStateOnEnableDisable() possible.
@ -619,7 +622,7 @@ fn handlePresent(
} }
fn setTitle(output: Output) void { fn setTitle(output: Output) void {
const title = fmt.allocPrintZ(util.gpa, "river - {s}", .{output.wlr_output.name}) catch return; const title = fmt.allocPrintSentinel(util.gpa, "river - {s}", .{output.wlr_output.name}, 0) catch return;
defer util.gpa.free(title); defer util.gpa.free(title);
if (output.wlr_output.isWl()) { if (output.wlr_output.isWl()) {
output.wlr_output.wlSetTitle(title); output.wlr_output.wlSetTitle(title);
@ -631,9 +634,9 @@ fn setTitle(output: Output) void {
pub fn handleLayoutNamespaceChange(output: *Output) void { pub fn handleLayoutNamespaceChange(output: *Output) void {
// The user changed the layout namespace of this output. Try to find a // The user changed the layout namespace of this output. Try to find a
// matching layout. // matching layout.
var it = output.layouts.first; var it = output.layouts.iterator(.forward);
output.layout = while (it) |node| : (it = node.next) { output.layout = while (it.next()) |layout| {
if (mem.eql(u8, output.layoutNamespace(), node.data.namespace)) break &node.data; if (mem.eql(u8, output.layoutNamespace(), layout.namespace)) break layout;
} else null; } else null;
server.root.applyPending(); server.root.applyPending();
} }

View File

@ -337,9 +337,8 @@ pub fn deactivateOutput(root: *Root, output: *Output) void {
} }
// If any seat has the removed output focused, focus the fallback one // If any seat has the removed output focused, focus the fallback one
var seat_it = server.input_manager.seats.first; var seat_it = server.input_manager.seats.iterator(.forward);
while (seat_it) |seat_node| : (seat_it = seat_node.next) { while (seat_it.next()) |seat| {
const seat = &seat_node.data;
if (seat.focused_output == output) { if (seat.focused_output == output) {
seat.focusOutput(fallback_output); seat.focusOutput(fallback_output);
} }
@ -353,7 +352,7 @@ pub fn deactivateOutput(root: *Root, output: *Output) void {
output.inflight.layout_demand = null; output.inflight.layout_demand = null;
root.notifyLayoutDemandDone(); root.notifyLayoutDemandDone();
} }
while (output.layouts.first) |node| node.data.destroy(); while (output.layouts.first()) |layout| layout.destroy();
// We must call reconfigureDevices here to unmap devices that might be mapped to this output // We must call reconfigureDevices here to unmap devices that might be mapped to this output
// in order to prevent a segfault in wlroots. // in order to prevent a segfault in wlroots.
@ -397,9 +396,8 @@ pub fn activateOutput(root: *Root, output: *Output) void {
} }
{ {
// Focus the new output with all seats // Focus the new output with all seats
var it = server.input_manager.seats.first; var it = server.input_manager.seats.iterator(.forward);
while (it) |seat_node| : (it = seat_node.next) { while (it.next()) |seat| {
const seat = &seat_node.data;
seat.focusOutput(output); seat.focusOutput(output);
} }
} }
@ -435,8 +433,8 @@ pub fn applyPending(root: *Root) void {
// state consistent. Instead of having focus(null) calls spread all // state consistent. Instead of having focus(null) calls spread all
// around the codebase and risk forgetting one, always ensure focus // around the codebase and risk forgetting one, always ensure focus
// state is synchronized here. // state is synchronized here.
var it = server.input_manager.seats.first; var it = server.input_manager.seats.iterator(.forward);
while (it) |node| : (it = node.next) node.data.focus(null); while (it.next()) |seat| seat.focus(null);
} }
// If there is already a transaction inflight, wait until it completes. // If there is already a transaction inflight, wait until it completes.
@ -546,11 +544,9 @@ pub fn applyPending(root: *Root) void {
} }
{ {
var it = server.input_manager.seats.first; var it = server.input_manager.seats.iterator(.forward);
while (it) |node| : (it = node.next) { while (it.next()) |seat| {
const cursor = &node.data.cursor; switch (seat.cursor.mode) {
switch (cursor.mode) {
.passthrough, .down => {}, .passthrough, .down => {},
inline .move, .resize => |data| { inline .move, .resize => |data| {
if (data.view.inflight.output == null or if (data.view.inflight.output == null or
@ -558,14 +554,14 @@ pub fn applyPending(root: *Root) void {
(!data.view.inflight.float and data.view.inflight.output.?.layout != null) or (!data.view.inflight.float and data.view.inflight.output.?.layout != null) or
data.view.inflight.fullscreen) data.view.inflight.fullscreen)
{ {
cursor.mode = .passthrough; seat.cursor.mode = .passthrough;
data.view.pending.resizing = false; data.view.pending.resizing = false;
data.view.inflight.resizing = false; data.view.inflight.resizing = false;
} }
}, },
} }
cursor.inflight_mode = cursor.mode; seat.cursor.inflight_mode = seat.cursor.mode;
} }
} }
@ -710,8 +706,8 @@ fn commitTransaction(root: *Root) void {
} }
{ {
var it = server.input_manager.seats.first; var it = server.input_manager.seats.iterator(.forward);
while (it) |node| : (it = node.next) node.data.cursor.updateState(); while (it.next()) |seat| seat.cursor.updateState();
} }
{ {

View File

@ -91,7 +91,7 @@ focused_output: ?*Output = null,
focused: FocusTarget = .none, focused: FocusTarget = .none,
/// List of status tracking objects relaying changes to this seat to clients. /// List of status tracking objects relaying changes to this seat to clients.
status_trackers: std.SinglyLinkedList(SeatStatus) = .{}, status_trackers: wl.list.Head(SeatStatus, .link),
/// The currently in progress drag operation type. /// The currently in progress drag operation type.
drag: enum { drag: enum {
@ -109,7 +109,13 @@ drag_destroy: wl.Listener(*wlr.Drag) = wl.Listener(*wlr.Drag).init(handleDragDes
request_set_primary_selection: wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection) = request_set_primary_selection: wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection) =
wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection).init(handleRequestSetPrimarySelection), wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection).init(handleRequestSetPrimarySelection),
pub fn init(seat: *Seat, name: [*:0]const u8) !void { // InputManager.seats
link: wl.list.Link = undefined,
pub fn create(name: [*:0]const u8) !void {
const seat = try util.gpa.create(Seat);
errdefer util.gpa.destroy(seat);
const event_loop = server.wl_server.getEventLoop(); const event_loop = server.wl_server.getEventLoop();
const mapping_repeat_timer = try event_loop.addTimer(*Seat, handleMappingRepeatTimeout, seat); const mapping_repeat_timer = try event_loop.addTimer(*Seat, handleMappingRepeatTimeout, seat);
errdefer mapping_repeat_timer.remove(); errdefer mapping_repeat_timer.remove();
@ -121,9 +127,14 @@ pub fn init(seat: *Seat, name: [*:0]const u8) !void {
.relay = undefined, .relay = undefined,
.mapping_repeat_timer = mapping_repeat_timer, .mapping_repeat_timer = mapping_repeat_timer,
.keyboard_group = try wlr.KeyboardGroup.create(), .keyboard_group = try wlr.KeyboardGroup.create(),
.status_trackers = undefined,
.link = undefined,
}; };
seat.wlr_seat.data = seat; seat.wlr_seat.data = seat;
server.input_manager.seats.append(seat);
seat.status_trackers.init();
try seat.cursor.init(seat); try seat.cursor.init(seat);
seat.relay.init(); seat.relay.init();
@ -135,7 +146,7 @@ pub fn init(seat: *Seat, name: [*:0]const u8) !void {
seat.wlr_seat.events.request_set_primary_selection.add(&seat.request_set_primary_selection); seat.wlr_seat.events.request_set_primary_selection.add(&seat.request_set_primary_selection);
} }
pub fn deinit(seat: *Seat) void { pub fn destroy(seat: *Seat) void {
{ {
var it = server.input_manager.devices.iterator(.forward); var it = server.input_manager.devices.iterator(.forward);
while (it.next()) |device| assert(device.seat != seat); while (it.next()) |device| assert(device.seat != seat);
@ -151,6 +162,10 @@ pub fn deinit(seat: *Seat) void {
seat.start_drag.link.remove(); seat.start_drag.link.remove();
if (seat.drag != .none) seat.drag_destroy.link.remove(); if (seat.drag != .none) seat.drag_destroy.link.remove();
seat.request_set_primary_selection.link.remove(); seat.request_set_primary_selection.link.remove();
seat.link.remove();
util.gpa.destroy(seat);
} }
/// Set the current focus. If a visible view is passed it will be focused. /// Set the current focus. If a visible view is passed it will be focused.
@ -295,8 +310,10 @@ pub fn setFocusRaw(seat: *Seat, new_focus: FocusTarget) void {
seat.cursor.may_need_warp = true; seat.cursor.may_need_warp = true;
// Inform any clients tracking status of the change // Inform any clients tracking status of the change
var it = seat.status_trackers.first; var it = seat.status_trackers.iterator(.forward);
while (it) |node| : (it = node.next) node.data.sendFocusedView(); while (it.next()) |tracker| {
tracker.sendFocusedView();
}
} }
/// Send keyboard enter/leave events and handle pointer constraints /// Send keyboard enter/leave events and handle pointer constraints
@ -314,14 +331,15 @@ fn keyboardNotifyEnter(seat: *Seat, wlr_surface: *wlr.Surface) void {
if (seat.wlr_seat.getKeyboard()) |wlr_keyboard| { if (seat.wlr_seat.getKeyboard()) |wlr_keyboard| {
const keyboard: *Keyboard = @alignCast(@ptrCast(wlr_keyboard.data)); const keyboard: *Keyboard = @alignCast(@ptrCast(wlr_keyboard.data));
var keycodes: std.BoundedArray(u32, Keyboard.Pressed.capacity) = .{}; var buffer: [Keyboard.Pressed.capacity]u32 = undefined;
for (keyboard.pressed.keys.constSlice()) |item| { var keycodes: std.ArrayList(u32) = .initBuffer(&buffer);
for (keyboard.pressed.slice()) |item| {
if (item.consumer == .focus) keycodes.appendAssumeCapacity(item.code); if (item.consumer == .focus) keycodes.appendAssumeCapacity(item.code);
} }
seat.wlr_seat.keyboardNotifyEnter( seat.wlr_seat.keyboardNotifyEnter(
wlr_surface, wlr_surface,
keycodes.constSlice(), keycodes.items,
&wlr_keyboard.modifiers, &wlr_keyboard.modifiers,
); );
} else { } else {
@ -334,15 +352,15 @@ pub fn focusOutput(seat: *Seat, output: ?*Output) void {
if (seat.focused_output == output) return; if (seat.focused_output == output) return;
if (seat.focused_output) |old| { if (seat.focused_output) |old| {
var it = seat.status_trackers.first; var it = seat.status_trackers.iterator(.forward);
while (it) |node| : (it = node.next) node.data.sendOutput(old, .unfocused); while (it.next()) |tracker| tracker.sendOutput(old, .unfocused);
} }
seat.focused_output = output; seat.focused_output = output;
if (seat.focused_output) |new| { if (seat.focused_output) |new| {
var it = seat.status_trackers.first; var it = seat.status_trackers.iterator(.forward);
while (it) |node| : (it = node.next) node.data.sendOutput(new, .focused); while (it.next()) |tracker| tracker.sendOutput(new, .focused);
} }
// Depending on configuration and cursor position, changing output focus // Depending on configuration and cursor position, changing output focus
@ -357,9 +375,9 @@ pub fn handleActivity(seat: Seat) void {
pub fn enterMode(seat: *Seat, mode_id: u32) void { pub fn enterMode(seat: *Seat, mode_id: u32) void {
seat.mode_id = mode_id; seat.mode_id = mode_id;
var it = seat.status_trackers.first; var it = seat.status_trackers.iterator(.forward);
while (it) |node| : (it = node.next) { while (it.next()) |tracker| {
node.data.sendMode(server.config.modes.items[mode_id].name); tracker.sendMode(server.config.modes.items[mode_id].name);
} }
} }
@ -453,8 +471,8 @@ pub fn runCommand(seat: *Seat, args: []const [:0]const u8) void {
return; return;
}; };
if (out) |s| { if (out) |s| {
const stdout = std.io.getStdOut().writer(); var stdout = std.fs.File.stdout().writer(&.{});
stdout.print("{s}", .{s}) catch |err| { stdout.interface.print("{s}", .{s}) catch |err| {
std.log.scoped(.command).err("{s}: write to stdout failed {}", .{ args[0], err }); std.log.scoped(.command).err("{s}: write to stdout failed {}", .{ args[0], err });
}; };
} }

View File

@ -31,8 +31,19 @@ const View = @import("View.zig");
seat: *Seat, seat: *Seat,
seat_status_v1: *zriver.SeatStatusV1, seat_status_v1: *zriver.SeatStatusV1,
pub fn init(seat_status: *SeatStatus, seat: *Seat, seat_status_v1: *zriver.SeatStatusV1) void { link: wl.list.Link,
seat_status.* = .{ .seat = seat, .seat_status_v1 = seat_status_v1 };
pub fn create(seat: *Seat, seat_status_v1: *zriver.SeatStatusV1) !void {
const seat_status = try util.gpa.create(SeatStatus);
errdefer util.gpa.destroy(seat_status);
seat_status.* = .{
.seat = seat,
.seat_status_v1 = seat_status_v1,
.link = undefined,
};
seat.status_trackers.append(seat_status);
errdefer comptime unreachable;
seat_status_v1.setHandler(*SeatStatus, handleRequest, handleDestroy, seat_status); seat_status_v1.setHandler(*SeatStatus, handleRequest, handleDestroy, seat_status);
@ -49,9 +60,8 @@ fn handleRequest(seat_status_v1: *zriver.SeatStatusV1, request: zriver.SeatStatu
} }
fn handleDestroy(_: *zriver.SeatStatusV1, seat_status: *SeatStatus) void { fn handleDestroy(_: *zriver.SeatStatusV1, seat_status: *SeatStatus) void {
const node: *std.SinglyLinkedList(SeatStatus).Node = @fieldParentPtr("data", seat_status); seat_status.link.remove();
seat_status.seat.status_trackers.remove(node); util.gpa.destroy(seat_status);
util.gpa.destroy(node);
} }
pub fn sendOutput(seat_status: SeatStatus, output: *Output, state: enum { focused, unfocused }) void { pub fn sendOutput(seat_status: SeatStatus, output: *Output, state: enum { focused, unfocused }) void {

View File

@ -24,7 +24,7 @@ const posix = std.posix;
const wlr = @import("wlroots"); const wlr = @import("wlroots");
const wl = @import("wayland").server.wl; const wl = @import("wayland").server.wl;
const c = @import("c.zig"); const c = @import("c.zig").c;
const util = @import("util.zig"); const util = @import("util.zig");
const Config = @import("Config.zig"); const Config = @import("Config.zig");

View File

@ -88,25 +88,21 @@ fn handleRequest(
const wlr_seat = wlr.Seat.Client.fromWlSeat(req.seat) orelse return; const wlr_seat = wlr.Seat.Client.fromWlSeat(req.seat) orelse return;
const seat: *Seat = @alignCast(@ptrCast(wlr_seat.seat.data)); const seat: *Seat = @alignCast(@ptrCast(wlr_seat.seat.data));
const node = util.gpa.create(std.SinglyLinkedList(SeatStatus).Node) catch {
status_manager_v1.getClient().postNoMemory();
log.err("out of memory", .{});
return;
};
const seat_status = zriver.SeatStatusV1.create( const seat_status = zriver.SeatStatusV1.create(
status_manager_v1.getClient(), status_manager_v1.getClient(),
status_manager_v1.getVersion(), status_manager_v1.getVersion(),
req.id, req.id,
) catch { ) catch {
status_manager_v1.getClient().postNoMemory(); status_manager_v1.getClient().postNoMemory();
util.gpa.destroy(node);
log.err("out of memory", .{}); log.err("out of memory", .{});
return; return;
}; };
node.data.init(seat, seat_status); SeatStatus.create(seat, seat_status) catch {
seat.status_trackers.prepend(node); status_manager_v1.getClient().postNoMemory();
log.err("out of memory", .{});
return;
};
}, },
} }
} }

View File

@ -277,11 +277,12 @@ pub fn resizeUpdatePosition(view: *View, width: i32, height: i32) void {
assert(view.inflight.resizing); assert(view.inflight.resizing);
const data = blk: { const data = blk: {
var it = server.input_manager.seats.first; var it = server.input_manager.seats.iterator(.forward);
while (it) |node| : (it = node.next) { while (it.next()) |seat| {
const cursor = &node.data.cursor; if (seat.cursor.inflight_mode == .resize and
if (cursor.inflight_mode == .resize and cursor.inflight_mode.resize.view == view) { seat.cursor.inflight_mode.resize.view == view)
break :blk cursor.inflight_mode.resize; {
break :blk seat.cursor.inflight_mode.resize;
} }
} else { } else {
// The view resizing state should never be set when the view is // The view resizing state should never be set when the view is
@ -710,8 +711,8 @@ pub fn map(view: *View) !void {
if (output) |o| { if (output) |o| {
view.setPendingOutput(o); view.setPendingOutput(o);
var it = server.input_manager.seats.first; var it = server.input_manager.seats.iterator(.forward);
while (it) |seat_node| : (it = seat_node.next) seat_node.data.focus(view); while (it.next()) |seat| seat.focus(view);
} else { } else {
log.debug("no output available for newly mapped view, adding to fallback stacks", .{}); log.debug("no output available for newly mapped view, adding to fallback stacks", .{});
@ -778,12 +779,12 @@ pub fn notifyTitle(view: *const View) void {
} }
// Send title to all status listeners attached to a seat which focuses this view // Send title to all status listeners attached to a seat which focuses this view
var seat_it = server.input_manager.seats.first; var seat_it = server.input_manager.seats.iterator(.forward);
while (seat_it) |seat_node| : (seat_it = seat_node.next) { while (seat_it.next()) |seat| {
if (seat_node.data.focused == .view and seat_node.data.focused.view == view) { if (seat.focused == .view and seat.focused.view == view) {
var client_it = seat_node.data.status_trackers.first; var it = seat.status_trackers.iterator(.forward);
while (client_it) |client_node| : (client_it = client_node.next) { while (it.next()) |tracker| {
client_node.data.sendFocusedView(); tracker.sendFocusedView();
} }
} }
} }

View File

@ -165,9 +165,8 @@ fn handleUnmap(listener: *wl.Listener(void)) void {
// If the unmapped surface is currently focused, pass keyboard focus // If the unmapped surface is currently focused, pass keyboard focus
// to the most appropriate surface. // to the most appropriate surface.
var seat_it = server.input_manager.seats.first; var seat_it = server.input_manager.seats.iterator(.forward);
while (seat_it) |seat_node| : (seat_it = seat_node.next) { while (seat_it.next()) |seat| {
const seat = &seat_node.data;
if (seat.focused == .view and seat.focused.view.impl == .xwayland_view and if (seat.focused == .view and seat.focused.view.impl == .xwayland_view and
seat.focused.view.impl.xwayland_view.xwayland_surface.pid == override_redirect.xwayland_surface.pid and seat.focused.view.impl.xwayland_view.xwayland_surface.pid == override_redirect.xwayland_surface.pid and
seat.wlr_seat.keyboard_state.focused_surface == override_redirect.xwayland_surface.surface) seat.wlr_seat.keyboard_state.focused_surface == override_redirect.xwayland_surface.surface)

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
pub usingnamespace @cImport({ pub const c = @cImport({
@cDefine("_POSIX_C_SOURCE", "200809L"); @cDefine("_POSIX_C_SOURCE", "200809L");
@cInclude("stdlib.h"); @cInclude("stdlib.h");

View File

@ -122,6 +122,7 @@ pub const Error = error{
CannotParseFile, CannotParseFile,
UnknownOption, UnknownOption,
ConflictingOptions, ConflictingOptions,
WriteFailed,
OutOfMemory, OutOfMemory,
Other, Other,
}; };
@ -165,7 +166,7 @@ pub fn errToMsg(err: Error) [:0]const u8 {
Error.InvalidValue => "invalid value", Error.InvalidValue => "invalid value",
Error.CannotReadFile => "cannot read file", Error.CannotReadFile => "cannot read file",
Error.CannotParseFile => "cannot parse file", Error.CannotParseFile => "cannot parse file",
Error.OutOfMemory => "out of memory", Error.WriteFailed, Error.OutOfMemory => "out of memory",
Error.Other => unreachable, Error.Other => unreachable,
}; };
} }

View File

@ -35,9 +35,8 @@ pub fn cursor(
if (args.len < 3) return Error.NotEnoughArguments; if (args.len < 3) return Error.NotEnoughArguments;
if (args.len > 3) return Error.TooManyArguments; if (args.len > 3) return Error.TooManyArguments;
server.config.cursor_hide_timeout = try std.fmt.parseInt(u31, args[2], 10); server.config.cursor_hide_timeout = try std.fmt.parseInt(u31, args[2], 10);
var seat_it = server.input_manager.seats.first; var seat_it = server.input_manager.seats.iterator(.forward);
while (seat_it) |seat_node| : (seat_it = seat_node.next) { while (seat_it.next()) |seat| {
const seat = &seat_node.data;
seat.cursor.unhide(); seat.cursor.unhide();
} }
} else if (std.mem.eql(u8, "when-typing", args[1])) { } else if (std.mem.eql(u8, "when-typing", args[1])) {

View File

@ -34,8 +34,8 @@ pub fn listInputs(
) Error!void { ) Error!void {
if (args.len > 1) return error.TooManyArguments; if (args.len > 1) return error.TooManyArguments;
var input_list = std.ArrayList(u8).init(util.gpa); var aw: std.Io.Writer.Allocating = .init(util.gpa);
const writer = input_list.writer();
var prev = false; var prev = false;
var it = server.input_manager.devices.iterator(.forward); var it = server.input_manager.devices.iterator(.forward);
@ -46,16 +46,16 @@ pub fn listInputs(
} }
} else false; } else false;
if (prev) try input_list.appendSlice("\n"); if (prev) try aw.writer.writeByte('\n');
prev = true; prev = true;
try writer.print("{s}\n\tconfigured: {}\n", .{ try aw.writer.print("{s}\n\tconfigured: {}\n", .{
device.identifier, device.identifier,
configured, configured,
}); });
} }
out.* = try input_list.toOwnedSlice(); out.* = try aw.toOwnedSlice();
} }
pub fn listInputConfigs( pub fn listInputConfigs(
@ -65,15 +65,14 @@ pub fn listInputConfigs(
) Error!void { ) Error!void {
if (args.len > 1) return error.TooManyArguments; if (args.len > 1) return error.TooManyArguments;
var input_list = std.ArrayList(u8).init(util.gpa); var aw: std.Io.Writer.Allocating = .init(util.gpa);
const writer = input_list.writer();
for (server.input_manager.configs.items, 0..) |*input_config, i| { for (server.input_manager.configs.items, 0..) |*input_config, i| {
if (i > 0) try writer.writeByte('\n'); if (i > 0) try aw.writer.writeByte('\n');
try input_config.write(writer); try input_config.write(&aw.writer);
} }
out.* = try input_list.toOwnedSlice(); out.* = try aw.toOwnedSlice();
} }
pub fn input( pub fn input(
@ -99,7 +98,7 @@ pub fn input(
}; };
errdefer util.gpa.free(input_config.glob); errdefer util.gpa.free(input_config.glob);
try server.input_manager.configs.ensureUnusedCapacity(1); try server.input_manager.configs.ensureUnusedCapacity(util.gpa, 1);
try input_config.parse(args[2], args[3]); try input_config.parse(args[2], args[3]);

View File

@ -71,9 +71,8 @@ pub fn sendLayoutCmd(
const output = seat.focused_output orelse return; const output = seat.focused_output orelse return;
const target_namespace = args[1]; const target_namespace = args[1];
var it = output.layouts.first; var it = output.layouts.iterator(.forward);
const layout = while (it) |node| : (it = node.next) { const layout = while (it.next()) |layout| {
const layout = &node.data;
if (mem.eql(u8, layout.namespace, target_namespace)) break layout; if (mem.eql(u8, layout.namespace, target_namespace)) break layout;
} else return; } else return;

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"); const c = @import("../c.zig").c;
const server = &@import("../main.zig").server; const server = &@import("../main.zig").server;
const util = @import("../util.zig"); const util = @import("../util.zig");

View File

@ -15,6 +15,7 @@
// 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 assert = std.debug.assert;
const fmt = std.fmt; const fmt = std.fmt;
const globber = @import("globber"); const globber = @import("globber");
@ -217,6 +218,12 @@ fn apply_tearing_rules() 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);
}
pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!void { pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!void {
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;
@ -237,11 +244,12 @@ 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.ArrayList(u8).init(util.gpa); var buffer = std.io.Writer.Allocating.init(util.gpa);
const writer = buffer.writer(); defer buffer.deinit();
const writer = &buffer.writer;
try fmt.formatBuf("title", .{ .width = title_column_max, .alignment = .left }, writer); try alignLeft("title", title_column_max, writer);
try fmt.formatBuf("app-id", .{ .width = app_id_column_max, .alignment = .left }, writer); try alignLeft("app-id", app_id_column_max, writer);
try writer.writeAll("action\n"); try writer.writeAll("action\n");
switch (rule_list) { switch (rule_list) {
@ -255,8 +263,8 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
else => unreachable, else => unreachable,
}; };
for (rules) |rule| { for (rules) |rule| {
try fmt.formatBuf(rule.title_glob, .{ .width = title_column_max, .alignment = .left }, writer); try alignLeft(rule.title_glob, title_column_max, writer);
try fmt.formatBuf(rule.app_id_glob, .{ .width = app_id_column_max, .alignment = .left }, writer); try alignLeft(rule.app_id_glob, app_id_column_max, writer);
try writer.print("{s}\n", .{switch (list) { try writer.print("{s}\n", .{switch (list) {
.float => if (rule.value) "float" else "no-float", .float => if (rule.value) "float" else "no-float",
.ssd => if (rule.value) "ssd" else "csd", .ssd => if (rule.value) "ssd" else "csd",
@ -269,22 +277,22 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
}, },
.tags => { .tags => {
for (server.config.rules.tags.rules.items) |rule| { for (server.config.rules.tags.rules.items) |rule| {
try fmt.formatBuf(rule.title_glob, .{ .width = title_column_max, .alignment = .left }, writer); try alignLeft(rule.title_glob, title_column_max, writer);
try fmt.formatBuf(rule.app_id_glob, .{ .width = app_id_column_max, .alignment = .left }, writer); try alignLeft(rule.app_id_glob, app_id_column_max, writer);
try writer.print("{b}\n", .{rule.value}); try writer.print("{b}\n", .{rule.value});
} }
}, },
.position => { .position => {
for (server.config.rules.position.rules.items) |rule| { for (server.config.rules.position.rules.items) |rule| {
try fmt.formatBuf(rule.title_glob, .{ .width = title_column_max, .alignment = .left }, writer); try alignLeft(rule.title_glob, title_column_max, writer);
try fmt.formatBuf(rule.app_id_glob, .{ .width = app_id_column_max, .alignment = .left }, writer); try alignLeft(rule.app_id_glob, app_id_column_max, writer);
try writer.print("{d},{d}\n", .{ rule.value.x, rule.value.y }); try writer.print("{d},{d}\n", .{ rule.value.x, rule.value.y });
} }
}, },
.dimensions => { .dimensions => {
for (server.config.rules.dimensions.rules.items) |rule| { for (server.config.rules.dimensions.rules.items) |rule| {
try fmt.formatBuf(rule.title_glob, .{ .width = title_column_max, .alignment = .left }, writer); try alignLeft(rule.title_glob, title_column_max, writer);
try fmt.formatBuf(rule.app_id_glob, .{ .width = app_id_column_max, .alignment = .left }, writer); try alignLeft(rule.app_id_glob, app_id_column_max, writer);
try writer.print("{d}x{d}\n", .{ rule.value.width, rule.value.height }); try writer.print("{d}x{d}\n", .{ rule.value.width, rule.value.height });
} }
}, },

View File

@ -17,7 +17,7 @@
const std = @import("std"); const std = @import("std");
const posix = std.posix; const posix = std.posix;
const c = @import("../c.zig"); 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");

View File

@ -18,14 +18,13 @@ const build_options = @import("build_options");
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 log = std.log; const log = std.log;
const posix = std.posix; const posix = std.posix;
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"); 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");
@ -52,21 +51,21 @@ pub fn main() anyerror!void {
.{ .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(std.os.argv[1..]) catch {
try io.getStdErr().writeAll(usage); try fs.File.stderr().writeAll(usage);
posix.exit(1); posix.exit(1);
}; };
if (result.flags.h) { if (result.flags.h) {
try io.getStdOut().writeAll(usage); try fs.File.stdout().writeAll(usage);
posix.exit(0); posix.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 io.getStdErr().writeAll(usage); try fs.File.stderr().writeAll(usage);
posix.exit(1); posix.exit(1);
} }
if (result.flags.version) { if (result.flags.version) {
try io.getStdOut().writeAll(build_options.version ++ "\n"); try fs.File.stdout().writeAll(build_options.version ++ "\n");
posix.exit(0); posix.exit(0);
} }
if (result.flags.@"log-level") |level| { if (result.flags.@"log-level") |level| {
@ -80,7 +79,7 @@ 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 io.getStdErr().writeAll(usage); try fs.File.stderr().writeAll(usage);
posix.exit(1); posix.exit(1);
} }
} }
@ -189,8 +188,8 @@ pub fn logFn(
const scope_prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; const scope_prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
const stderr = io.getStdErr().writer(); var stderr = fs.File.stderr().writer(&.{});
stderr.print(level.asText() ++ scope_prefix ++ format ++ "\n", args) catch {}; stderr.interface.print(level.asText() ++ scope_prefix ++ format ++ "\n", args) catch {};
} }
/// See wlroots_log_wrapper.c /// See wlroots_log_wrapper.c

View File

@ -17,7 +17,7 @@
const std = @import("std"); const std = @import("std");
const posix = std.posix; const posix = std.posix;
const c = @import("c.zig"); const c = @import("c.zig").c;
var original_rlimit: ?posix.rlimit = null; var original_rlimit: ?posix.rlimit = null;
@ -26,7 +26,7 @@ pub fn setup() void {
// has had its read end closed by another process. // has had its read end closed by another process.
const sig_ign = posix.Sigaction{ const sig_ign = posix.Sigaction{
.handler = .{ .handler = posix.SIG.IGN }, .handler = .{ .handler = posix.SIG.IGN },
.mask = posix.empty_sigset, .mask = posix.sigemptyset(),
.flags = 0, .flags = 0,
}; };
posix.sigaction(posix.SIG.PIPE, &sig_ign, null); posix.sigaction(posix.SIG.PIPE, &sig_ign, null);
@ -61,11 +61,11 @@ pub fn setup() void {
pub fn cleanupChild() void { pub fn cleanupChild() void {
if (c.setsid() < 0) unreachable; 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{ const sig_dfl = posix.Sigaction{
.handler = .{ .handler = posix.SIG.DFL }, .handler = .{ .handler = posix.SIG.DFL },
.mask = posix.empty_sigset, .mask = posix.sigemptyset(),
.flags = 0, .flags = 0,
}; };
posix.sigaction(posix.SIG.PIPE, &sig_dfl, null); posix.sigaction(posix.SIG.PIPE, &sig_dfl, null);

View File

@ -16,7 +16,7 @@
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
const io = std.io; const fs = std.fs;
const posix = std.posix; const posix = std.posix;
const assert = std.debug.assert; const assert = std.debug.assert;
const builtin = @import("builtin"); const builtin = @import("builtin");
@ -73,15 +73,15 @@ fn _main() !void {
.{ .name = "h", .kind = .boolean }, .{ .name = "h", .kind = .boolean },
.{ .name = "version", .kind = .boolean }, .{ .name = "version", .kind = .boolean },
}).parse(std.os.argv[1..]) catch { }).parse(std.os.argv[1..]) catch {
try io.getStdErr().writeAll(usage); try fs.File.stderr().writeAll(usage);
posix.exit(1); posix.exit(1);
}; };
if (result.flags.h) { if (result.flags.h) {
try io.getStdOut().writeAll(usage); try fs.File.stdout().writeAll(usage);
posix.exit(0); posix.exit(0);
} }
if (result.flags.version) { if (result.flags.version) {
try io.getStdOut().writeAll(@import("build_options").version ++ "\n"); try fs.File.stdout().writeAll(@import("build_options").version ++ "\n");
posix.exit(0); posix.exit(0);
} }
@ -125,8 +125,8 @@ 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) {
const stdout = io.getStdOut().writer(); 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); posix.exit(0);
}, },
@ -134,7 +134,7 @@ fn callbackListener(_: *zriver.CommandCallbackV1, event: zriver.CommandCallbackV
// 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", .{});
io.getStdErr().writeAll(usage) catch {}; fs.File.stderr().writeAll(usage) catch {};
posix.exit(1); posix.exit(1);
} }
fatal("{s}", .{failure.failure_message}); fatal("{s}", .{failure.failure_message});

View File

@ -91,15 +91,12 @@ const gpa = std.heap.c_allocator;
const Context = struct { const Context = struct {
initialized: bool = false, initialized: bool = false,
layout_manager: ?*river.LayoutManagerV3 = null, layout_manager: ?*river.LayoutManagerV3 = null,
outputs: std.DoublyLinkedList(Output) = .{}, outputs: wl.list.Head(Output, .link),
fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void { fn addOutput(context: *Context, registry: *wl.Registry, name: u32) !void {
const wl_output = try registry.bind(name, wl.Output, 3); const wl_output = try registry.bind(name, wl.Output, 3);
errdefer wl_output.release(); errdefer wl_output.release();
const node = try gpa.create(std.DoublyLinkedList(Output).Node); try Output.create(context, wl_output, name);
errdefer gpa.destroy(node);
try node.data.init(context, wl_output, name);
context.outputs.append(node);
} }
}; };
@ -113,15 +110,21 @@ const Output = struct {
layout: *river.LayoutV3 = undefined, layout: *river.LayoutV3 = undefined,
fn init(output: *Output, context: *Context, wl_output: *wl.Output, name: u32) !void { link: wl.list.Link,
fn create(context: *Context, wl_output: *wl.Output, name: u32) !void {
const output = try gpa.create(Output);
errdefer gpa.destroy(output);
output.* = .{ output.* = .{
.wl_output = wl_output, .wl_output = wl_output,
.name = name, .name = name,
.main_location = default_main_location, .main_location = default_main_location,
.main_count = default_main_count, .main_count = default_main_count,
.main_ratio = default_main_ratio, .main_ratio = default_main_ratio,
.link = undefined,
}; };
if (context.initialized) try output.getLayout(context); if (context.initialized) try output.getLayout(context);
context.outputs.append(output);
} }
fn getLayout(output: *Output, context: *Context) !void { fn getLayout(output: *Output, context: *Context) !void {
@ -130,9 +133,11 @@ const Output = struct {
output.layout.setListener(*Output, layoutListener, output); output.layout.setListener(*Output, layoutListener, output);
} }
fn deinit(output: *Output) void { fn destroy(output: *Output) void {
output.wl_output.release(); output.wl_output.release();
output.layout.destroy(); output.layout.destroy();
output.link.remove();
gpa.destroy(output);
} }
fn layoutListener(layout: *river.LayoutV3, event: river.LayoutV3.Event, output: *Output) void { fn layoutListener(layout: *river.LayoutV3, event: river.LayoutV3.Event, output: *Output) void {
@ -312,17 +317,17 @@ pub fn main() !void {
.{ .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(std.os.argv[1..]) catch {
try std.io.getStdErr().writeAll(usage); try std.fs.File.stderr().writeAll(usage);
posix.exit(1); posix.exit(1);
}; };
if (result.flags.h) { if (result.flags.h) {
try std.io.getStdOut().writeAll(usage); try std.fs.File.stdout().writeAll(usage);
posix.exit(0); posix.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.io.getStdOut().writeAll(@import("build_options").version ++ "\n"); try std.fs.File.stdout().writeAll(@import("build_options").version ++ "\n");
posix.exit(0); posix.exit(0);
} }
if (result.flags.@"view-padding") |raw| { if (result.flags.@"view-padding") |raw| {
@ -356,7 +361,10 @@ pub fn main() !void {
}; };
defer display.disconnect(); defer display.disconnect();
var context: Context = .{}; var context: Context = .{
.outputs = undefined,
};
context.outputs.init();
const registry = try display.getRegistry(); const registry = try display.getRegistry();
registry.setListener(*Context, registryListener, &context); registry.setListener(*Context, registryListener, &context);
@ -368,9 +376,8 @@ pub fn main() !void {
context.initialized = true; context.initialized = true;
var it = context.outputs.first; var it = context.outputs.iterator(.forward);
while (it) |node| : (it = node.next) { while (it.next()) |output| {
const output = &node.data;
try output.getLayout(&context); try output.getLayout(&context);
} }
@ -389,13 +396,10 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *
} }
}, },
.global_remove => |ev| { .global_remove => |ev| {
var it = context.outputs.first; var it = context.outputs.safeIterator(.forward);
while (it) |node| : (it = node.next) { while (it.next()) |output| {
const output = &node.data;
if (output.name == ev.name) { if (output.name == ev.name) {
context.outputs.remove(node); output.destroy();
output.deinit();
gpa.destroy(node);
break; break;
} }
} }
@ -410,7 +414,7 @@ fn fatal(comptime format: []const u8, args: anytype) noreturn {
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.io.getStdErr().writeAll(usage) catch {}; std.fs.File.stderr().writeAll(usage) catch {};
posix.exit(1); posix.exit(1);
} }