Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
rivercompat: delete
This can be resurrected from the git history if desired in the future.
It hasn't done much but get in the way for a while.
As an example WM for documentation, I think C is the right language.
build.zig | 24 ----
rivercompat/Background.zig | 72 -----------
rivercompat/Config.zig | 33 -----
rivercompat/Output.zig | 225 ---------------------------------
rivercompat/PointerBinding.zig | 60 ---------
rivercompat/Seat.zig | 276 -----------------------------------------
rivercompat/Window.zig | 266 ---------------------------------------
rivercompat/WindowManager.zig | 139 ---------------------
rivercompat/XkbBinding.zig | 62 ---------
rivercompat/c.zig | 3 -
rivercompat/main.zig | 145 ----------------------
11 files changed, 1305 deletions(-)
diff --git a/build.zig b/build.zig
index ad37ef6..ad87479 100644
--- a/build.zig
+++ b/build.zig
@@ -168,30 +168,6 @@ pub fn build(b: *Build) !void {
b.installArtifact(river);
}
- {
- const rivercompat = b.addExecutable(.{
- .name = "rivercompat",
- .root_source_file = b.path("rivercompat/main.zig"),
- .target = target,
- .optimize = optimize,
- .strip = strip,
- .use_llvm = llvm,
- .use_lld = llvm,
- });
- rivercompat.root_module.addOptions("build_options", options);
-
- rivercompat.root_module.addImport("flags", flags);
- rivercompat.root_module.addImport("wayland", wayland);
- rivercompat.root_module.addImport("xkbcommon", xkbcommon);
- rivercompat.linkLibC();
- rivercompat.linkSystemLibrary("wayland-client");
-
- rivercompat.pie = pie;
- rivercompat.root_module.omit_frame_pointer = omit_frame_pointer;
-
- b.installArtifact(rivercompat);
- }
-
{
const wf = Build.Step.WriteFile.create(b);
const pc_file = wf.add("river-protocols.pc", b.fmt(
diff --git a/rivercompat/Background.zig b/rivercompat/Background.zig
deleted file mode 100644
index 0b02d10..0000000
--- a/rivercompat/Background.zig
+++ /dev/null
@@ -1,72 +0,0 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2025 The River Developers
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-const Background = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const math = std.math;
-const wayland = @import("wayland");
-const wl = wayland.client.wl;
-const wp = wayland.client.wp;
-const river = wayland.client.river;
-
-const wm = &@import("root").wm;
-
-new: bool,
-surface: *wl.Surface,
-viewport: *wp.Viewport,
-shell_surface_v1: *river.ShellSurfaceV1,
-node: *river.NodeV1,
-
-pub fn init(background: *Background) void {
- const surface = wm.compositor.createSurface() catch @panic("OOM");
- const viewport = wm.viewporter.getViewport(surface) catch @panic("OOM");
- const shell_surface_v1 = wm.wm_v1.getShellSurface(surface) catch @panic("OOM");
-
- background.* = .{
- .new = true,
- .surface = surface,
- .viewport = viewport,
- .shell_surface_v1 = shell_surface_v1,
- .node = shell_surface_v1.getNode() catch @panic("OOM"),
- };
-}
-
-pub fn manage(background: *Background) void {
- if (background.new) {
- background.node.placeBottom();
- background.node.setPosition(0, 0);
- background.shell_surface_v1.syncNextCommit();
-
- const rgb = 0xfdf6e3;
-
- const buffer = wm.single_pixel.createU32RgbaBuffer(
- @as(u32, (rgb >> 16) & 0xff) * (0xffff_ffff / 0xff),
- @as(u32, (rgb >> 8) & 0xff) * (0xffff_ffff / 0xff),
- @as(u32, (rgb >> 0) & 0xff) * (0xffff_ffff / 0xff),
- 0xffff_ffff,
- ) catch @panic("OOM");
- defer buffer.destroy();
-
- background.surface.attach(buffer, 0, 0);
-
- background.surface.damageBuffer(0, 0, math.maxInt(i32), math.maxInt(i32));
- background.viewport.setDestination(math.maxInt(i32) / 2, math.maxInt(i32) / 2);
- background.surface.commit();
- }
- background.new = false;
-}
diff --git a/rivercompat/Config.zig b/rivercompat/Config.zig
deleted file mode 100644
index 2faf3bb..0000000
--- a/rivercompat/Config.zig
+++ /dev/null
@@ -1,33 +0,0 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2025 The River Developers
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-const Config = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-
-const gpa = std.heap.c_allocator;
-
-border_width: u31 = 3,
-border_color_unfocused: u32 = 0x586e75,
-border_color_focused: u32 = 0x93a1a1,
-border_color_hovered: u32 = 0xdc322f,
-
-main_count: u31 = 1,
-main_location: enum { left, right, top, bottom } = .left,
-outer_padding: u31 = 10,
-window_padding: u31 = 10,
-main_ratio: f64 = 0.60,
diff --git a/rivercompat/Output.zig b/rivercompat/Output.zig
deleted file mode 100644
index 125c512..0000000
--- a/rivercompat/Output.zig
+++ /dev/null
@@ -1,225 +0,0 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2025 The River Developers
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-const Output = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wayland = @import("wayland");
-const wl = wayland.client.wl;
-const river = wayland.client.river;
-
-const Window = @import("Window.zig");
-
-const wm = &@import("root").wm;
-const gpa = std.heap.c_allocator;
-
-output_v1: *river.OutputV1,
-pending: struct {
- new: bool = false,
- removed: bool = false,
-},
-
-x: i32 = 0,
-y: i32 = 0,
-width: u31 = 0,
-height: u31 = 0,
-
-tags: u32 = (1 << 0),
-stack_focus: wl.list.Head(Window, .link_focus),
-stack_wm: wl.list.Head(Window, .link_wm),
-
-link: wl.list.Link,
-
-pub fn create(output_v1: *river.OutputV1) void {
- const output = gpa.create(Output) catch @panic("OOM");
- output.* = .{
- .output_v1 = output_v1,
- .pending = .{ .new = true },
- .stack_focus = undefined,
- .stack_wm = undefined,
- .link = undefined,
- };
- output.stack_focus.init();
- output.stack_wm.init();
- wm.outputs.append(output);
-
- output_v1.setListener(*Output, handleEvent, output);
-}
-
-fn handleEvent(output_v1: *river.OutputV1, event: river.OutputV1.Event, output: *Output) void {
- assert(output.output_v1 == output_v1);
- switch (event) {
- .removed => output.pending.removed = true,
- .wl_output => {},
- .position => |args| {
- output.x = args.x;
- output.y = args.y;
- },
- .dimensions => |args| {
- output.width = @intCast(args.width);
- output.height = @intCast(args.height);
- },
- }
-}
-
-pub fn manage(output: *Output) void {
- if (output.pending.removed) {
- // XXX
- output.output_v1.destroy();
- gpa.destroy(output);
- }
-
- if (output.pending.new) {
- {
- var it = wm.fallback_stack_wm.iterator(.forward);
- while (it.next()) |window| {
- window.output = output;
- window.link_wm.remove();
- window.link_focus.remove();
- output.stack_focus.prepend(window);
- output.stack_wm.prepend(window);
- }
- }
- {
- var it = wm.seats.iterator(.forward);
- while (it.next()) |seat| {
- if (seat.focused_output == null) {
- seat.focused_output = output;
- seat.focus(null);
- }
- }
- }
- }
-
- output.pending = .{};
-}
-
-pub fn layout(output: *Output) void {
- var count: u31 = 0;
- {
- var it = output.stack_wm.iterator(.forward);
- while (it.next()) |window| {
- if (window.tags & output.tags != 0 and window.op == .none) {
- count += 1;
- }
- }
- }
- if (count == 0) return;
-
- const main_count = @min(wm.config.main_count, count);
- const secondary_count = count -| main_count;
-
- const usable_width = switch (wm.config.main_location) {
- .left, .right => output.width -| (2 *| wm.config.outer_padding),
- .top, .bottom => output.height -| (2 *| wm.config.outer_padding),
- };
- const usable_height = switch (wm.config.main_location) {
- .left, .right => output.height -| (2 *| wm.config.outer_padding),
- .top, .bottom => output.width -| (2 *| wm.config.outer_padding),
- };
-
- // to make things pixel-perfect, we make the first main and first secondary
- // view slightly larger if the height is not evenly divisible
- var main_width: u31 = undefined;
- var main_height: u31 = undefined;
- var main_height_rem: u31 = undefined;
-
- var secondary_width: u31 = undefined;
- var secondary_height: u31 = undefined;
- var secondary_height_rem: u31 = undefined;
-
- if (secondary_count > 0) {
- main_width = @intFromFloat(wm.config.main_ratio * @as(f64, @floatFromInt(usable_width)));
- main_height = usable_height / main_count;
- main_height_rem = usable_height % main_count;
-
- secondary_width = usable_width - main_width;
- secondary_height = usable_height / secondary_count;
- secondary_height_rem = usable_height % secondary_count;
- } else {
- main_width = usable_width;
- main_height = usable_height / main_count;
- main_height_rem = usable_height % main_count;
- }
-
- {
- var i: u31 = 0;
- var it = output.stack_wm.iterator(.forward);
- while (it.next()) |window| {
- if (window.tags & output.tags == 0) continue;
- if (window.op != .none) continue;
- defer i += 1;
-
- var x: i32 = undefined;
- var y: i32 = undefined;
- var width: u31 = undefined;
- var height: u31 = undefined;
-
- if (i < main_count) {
- x = 0;
- y = (i * main_height) + if (i > 0) main_height_rem else 0;
- width = main_width;
- height = main_height + if (i == 0) main_height_rem else 0;
- } else {
- x = main_width;
- y = (i - main_count) * secondary_height + if (i > main_count) secondary_height_rem else 0;
- width = secondary_width;
- height = secondary_height + if (i == main_count) secondary_height_rem else 0;
- }
-
- x +|= wm.config.window_padding;
- y +|= wm.config.window_padding;
- width -|= 2 *| wm.config.window_padding;
- height -|= 2 *| wm.config.window_padding;
-
- switch (wm.config.main_location) {
- .left => window.layout(.{
- .x = x +| wm.config.outer_padding,
- .y = y +| wm.config.outer_padding,
- .width = width,
- .height = height,
- }),
- .right => window.layout(.{
- .x = usable_width - width - x +| wm.config.outer_padding,
- .y = y +| wm.config.outer_padding,
- .width = width,
- .height = height,
- }),
- .top => window.layout(.{
- .x = y +| wm.config.outer_padding,
- .y = x +| wm.config.outer_padding,
- .width = height,
- .height = width,
- }),
- .bottom => window.layout(.{
- .x = y +| wm.config.outer_padding,
- .y = usable_width - width - x +| wm.config.outer_padding,
- .width = height,
- .height = width,
- }),
- }
- window.window_v1.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true });
- }
- }
-
- {
- var it = output.stack_focus.iterator(.reverse);
- while (it.next()) |window| {
- window.node_v1.placeTop();
- }
- }
-}
diff --git a/rivercompat/PointerBinding.zig b/rivercompat/PointerBinding.zig
deleted file mode 100644
index 6779753..0000000
--- a/rivercompat/PointerBinding.zig
+++ /dev/null
@@ -1,60 +0,0 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2024 The River Developers
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-const PointerBinding = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wayland = @import("wayland");
-const wl = wayland.client.wl;
-const river = wayland.client.river;
-
-const Seat = @import("Seat.zig");
-const Window = @import("Window.zig");
-
-const gpa = std.heap.c_allocator;
-
-seat: *Seat,
-pointer_binding_v1: *river.PointerBindingV1,
-press_action: Seat.Action,
-release_action: ?Seat.Action,
-
-pub fn create(
- seat: *Seat,
- button: u32,
- modifiers: river.SeatV1.Modifiers,
- press_action: Seat.Action,
- release_action: ?Seat.Action,
-) void {
- const pointer_binding_v1 = seat.seat_v1.getPointerBinding(button, modifiers) catch @panic("OOM");
- const binding = gpa.create(PointerBinding) catch @panic("OOM");
- binding.* = .{
- .seat = seat,
- .pointer_binding_v1 = pointer_binding_v1,
- .press_action = press_action,
- .release_action = release_action,
- };
- pointer_binding_v1.setListener(*PointerBinding, handleEvent, binding);
- pointer_binding_v1.enable();
-}
-
-fn handleEvent(pointer_binding_v1: *river.PointerBindingV1, event: river.PointerBindingV1.Event, binding: *PointerBinding) void {
- assert(binding.pointer_binding_v1 == pointer_binding_v1);
- switch (event) {
- .pressed => binding.seat.execute(binding.press_action),
- .released => if (binding.release_action) |a| binding.seat.execute(a),
- }
-}
diff --git a/rivercompat/Seat.zig b/rivercompat/Seat.zig
deleted file mode 100644
index 9571acb..0000000
--- a/rivercompat/Seat.zig
+++ /dev/null
@@ -1,276 +0,0 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2024 The River Developers
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-const Seat = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wayland = @import("wayland");
-const xkb = @import("xkbcommon");
-const wl = wayland.client.wl;
-const river = wayland.client.river;
-
-const c = @import("c.zig");
-
-const Output = @import("Output.zig");
-const Window = @import("Window.zig");
-const XkbBinding = @import("XkbBinding.zig");
-const PointerBinding = @import("PointerBinding.zig");
-
-const wm = &@import("root").wm;
-const gpa = std.heap.c_allocator;
-
-const State = struct {
- new: bool = false,
- action: ?Action = null,
- window_interaction: ?*Window = null,
- shell_surface_interaction: ?*river.ShellSurfaceV1 = null,
- op_dx: i32 = 0,
- op_dy: i32 = 0,
-};
-
-seat_v1: *river.SeatV1,
-pending: State = .{},
-op: ?struct {
- dx: i32 = 0,
- dy: i32 = 0,
-} = null,
-focused: ?*Window = null,
-focused_output: ?*Output = null,
-hovered: ?*Window = null,
-link: wl.list.Link,
-
-pub fn create(seat_v1: *river.SeatV1) void {
- const seat = gpa.create(Seat) catch @panic("OOM");
- seat.* = .{
- .seat_v1 = seat_v1,
- .pending = .{ .new = true },
- .link = undefined,
- };
- wm.seats.append(seat);
-
- seat_v1.setListener(*Seat, handleEvent, seat);
-}
-
-fn handleEvent(seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: *Seat) void {
- assert(seat.seat_v1 == seat_v1);
- switch (event) {
- .removed => {
- seat_v1.destroy();
- gpa.destroy(seat);
- },
- .wl_seat => {},
- .pointer_enter => |args| {
- const window_v1 = args.window orelse return;
- const window: *Window = @ptrCast(@alignCast(window_v1.getUserData()));
- seat.hovered = window;
- },
- .pointer_leave => seat.hovered = null,
- .op_delta => |args| {
- seat.op.?.dx = args.dx;
- seat.op.?.dy = args.dy;
- },
- .op_release => {},
- .pointer_activity => {},
- .window_interaction => |args| {
- const window_v1 = args.window orelse return;
- const window: *Window = @ptrCast(@alignCast(window_v1.getUserData()));
- assert(seat.pending.window_interaction == null);
- seat.pending.window_interaction = window;
- },
- .shell_surface_interaction => |args| {
- assert(seat.pending.shell_surface_interaction == null);
- seat.pending.shell_surface_interaction = args.shell_surface orelse return;
- },
- }
-}
-
-pub fn manage(seat: *Seat) void {
- if (seat.pending.new) {
- seat.focused_output = wm.outputs.first();
-
- // These are my personal keybindings which probably only make sense on my weird keyboard layout
- XkbBinding.create(seat, xkb.Keysym.space, .{ .mod4 = true, .mod1 = true }, .{ .spawn = "foot" });
- XkbBinding.create(seat, xkb.Keysym.u, .{ .mod4 = true, .mod1 = true }, .close_focused);
- XkbBinding.create(seat, xkb.Keysym.a, .{ .mod4 = true }, .focus_prev);
- XkbBinding.create(seat, xkb.Keysym.e, .{ .mod4 = true }, .focus_next);
-
- XkbBinding.create(seat, xkb.Keysym.h, .{ .mod4 = true }, .hide_focused);
- XkbBinding.create(seat, xkb.Keysym.s, .{ .mod4 = true }, .show_all);
- PointerBinding.create(seat, c.BTN_LEFT, .{ .mod4 = true }, .move_start, .op_end);
- PointerBinding.create(seat, c.BTN_RIGHT, .{ .mod4 = true }, .resize_start, .op_end);
- PointerBinding.create(seat, c.BTN_MIDDLE, .{ .mod4 = true }, .close_focused, null);
- }
- if (seat.pending.window_interaction) |window| {
- seat.focus(window);
- }
- if (seat.pending.shell_surface_interaction) |shell_surface| {
- seat.seat_v1.focusShellSurface(shell_surface);
- }
- if (seat.pending.action) |action| {
- seat.execute(action);
- }
- seat.pending = .{};
-}
-
-pub const Action = union(enum) {
- focus_prev,
- focus_next,
- close_focused,
- hide_focused,
- show_all,
- move_start,
- resize_start,
- op_end,
- /// slice must have a static lifetime
- spawn: [:0]const u8,
-};
-
-pub fn execute(seat: *Seat, action: Action) void {
- switch (action) {
- .focus_prev => seat.focus(seat.actionTarget(.prev)),
- .focus_next => seat.focus(seat.actionTarget(.next)),
- .close_focused => if (seat.focused) |window| window.window_v1.close(),
- .hide_focused => if (seat.focused) |window| window.window_v1.hide(),
- .show_all => {
- var it = wm.windows.iterator(.forward);
- while (it.next()) |window| {
- window.window_v1.show();
- }
- },
- .move_start => {
- if (seat.op != null) return;
- seat.op = .{};
- seat.seat_v1.opStartPointer();
- var it = wm.windows.iterator(.forward);
- while (it.next()) |window| {
- if (window.op == .none) {
- window.op = .{ .move = .{
- .seat = seat,
- .start_x = window.box.x,
- .start_y = window.box.y,
- } };
- }
- }
- },
- .resize_start => {
- if (seat.op != null) return;
- seat.op = .{};
- seat.seat_v1.opStartPointer();
- var it = wm.windows.iterator(.forward);
- while (it.next()) |window| {
- if (window.op == .none) {
- window.op = .{ .resize = .{
- .seat = seat,
- .start_box = window.box,
- } };
- window.window_v1.informResizeStart();
- }
- }
- },
- .op_end => {
- seat.op = null;
- seat.seat_v1.opEnd();
- var it = wm.windows.iterator(.forward);
- while (it.next()) |window| {
- switch (window.op) {
- .none => {},
- inline .move, .resize => |op| {
- if (op.seat == seat) {
- window.op = .none;
- }
- if (window.op == .resize) {
- window.window_v1.informResizeEnd();
- }
- },
- }
- }
- },
- .spawn => |command| {
- if (std.posix.fork()) |pid| {
- if (pid == 0) {
- std.posix.execveZ("/bin/sh", &.{ "/bin/sh", "-c", command, null }, std.c.environ) catch {
- std.c._exit(1);
- };
- }
- } else |err| {
- std.log.err("fork() failed: {s}", .{@errorName(err)});
- }
- },
- }
-}
-
-pub fn focus(seat: *Seat, _target: ?*Window) void {
- if (seat.focused_output == null) return;
- if (wm.session_locked) return;
-
- var target = _target;
- if (target) |window| {
- if (window.output == null or window.output.?.tags & window.tags == 0) {
- target = null;
- } else if (window.output.? != seat.focused_output.?) {
- seat.focused_output = window.output;
- }
- }
-
- if (target == null) {
- var it = seat.focused_output.?.stack_focus.iterator(.forward);
- while (it.next()) |window| {
- if (window.tags & seat.focused_output.?.tags != 0) {
- target = window;
- break;
- }
- }
- }
-
- if (target) |window| {
- window.link_focus.remove();
- seat.focused_output.?.stack_focus.prepend(window);
- seat.seat_v1.focusWindow(window.window_v1);
- seat.focused = window;
- } else {
- seat.seat_v1.clearFocus();
- seat.focused = null;
- }
-}
-
-fn actionTarget(seat: *Seat, comptime dir: enum { next, prev }) ?*Window {
- const output = seat.focused_output orelse return null;
- if (seat.focused) |focused| {
- var it = output.stack_wm.iterator(switch (dir) {
- .next => .forward,
- .prev => .reverse,
- });
- while (it.next()) |window| {
- if (window == focused) break;
- } else {
- unreachable;
- }
- // Return the next window in the stack matching the tags if any.
- while (it.next()) |window| {
- if (output.tags & window.tags != 0) return window;
- }
- // Wrap and return the first window in the stack matching the tags if
- // any is found before completing the loop back to the focused window.
- while (it.next()) |window| {
- if (window == focused) return null;
- if (output.tags & window.tags != 0) return window;
- }
- unreachable;
- } else {
- return output.stack_wm.first();
- }
-}
diff --git a/rivercompat/Window.zig b/rivercompat/Window.zig
deleted file mode 100644
index 2fd8c66..0000000
--- a/rivercompat/Window.zig
+++ /dev/null
@@ -1,266 +0,0 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2024 The River Developers
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-const Window = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const math = std.math;
-const wayland = @import("wayland");
-const wl = wayland.client.wl;
-const wp = wayland.client.wp;
-const river = wayland.client.river;
-
-const Output = @import("Output.zig");
-const Seat = @import("Seat.zig");
-
-const wm = &@import("root").wm;
-const gpa = std.heap.c_allocator;
-
-window_v1: *river.WindowV1,
-node_v1: *river.NodeV1,
-pending: struct {
- new: bool = false,
- closed: bool = false,
-},
-
-box: Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
-
-output: ?*Output = null,
-tags: u32 = 0,
-
-op: union(enum) {
- none,
- move: struct {
- seat: *Seat,
- start_x: i32,
- start_y: i32,
- },
- resize: struct {
- seat: *Seat,
- start_box: Box,
- },
-} = .none,
-
-link: wl.list.Link,
-link_focus: wl.list.Link,
-link_wm: wl.list.Link,
-
-shadow_surface: *wl.Surface,
-shadow_decoration: *river.DecorationV1,
-shadow_viewport: *wp.Viewport,
-shadow_buffer: *wl.Buffer,
-
-pub fn create(window_v1: *river.WindowV1) void {
- const window = gpa.create(Window) catch @panic("OOM");
-
- const shadow_surface = wm.compositor.createSurface() catch @panic("OOM");
- const shadow_decoration = window_v1.getDecorationBelow(shadow_surface) catch @panic("OOM");
- const shadow_viewport = wm.viewporter.getViewport(shadow_surface) catch @panic("OOM");
- const shadow_buffer = wm.single_pixel.createU32RgbaBuffer(
- 0,
- 0,
- 0,
- @intFromFloat(0.75 * math.maxInt(u32)),
- ) catch @panic("OOM");
-
- window.* = .{
- .window_v1 = window_v1,
- .node_v1 = window_v1.getNode() catch @panic("OOM"),
- .pending = .{ .new = true },
- .link = undefined,
- .link_focus = undefined,
- .link_wm = undefined,
- .shadow_surface = shadow_surface,
- .shadow_decoration = shadow_decoration,
- .shadow_viewport = shadow_viewport,
- .shadow_buffer = shadow_buffer,
- };
- wm.windows.append(window);
- window.link_focus.init();
- window.link_wm.init();
-
- window_v1.setListener(*Window, handleEvent, window);
-}
-
-fn handleEvent(window_v1: *river.WindowV1, event: river.WindowV1.Event, window: *Window) void {
- assert(window.window_v1 == window_v1);
- switch (event) {
- .closed => window.pending.closed = true,
- .dimensions_hint => {},
- .dimensions => |args| {
- window.box.width = @intCast(args.width);
- window.box.height = @intCast(args.height);
- window.box.width += 2 * wm.config.border_width;
- window.box.height += 2 * wm.config.border_width;
- },
- .app_id => {},
- .title => {},
- .parent => {},
- .decoration_hint => {},
- .pointer_move_requested => {},
- .pointer_resize_requested => {},
- .show_window_menu_requested => {},
- .maximize_requested => {},
- .unmaximize_requested => {},
- .fullscreen_requested => {},
- .exit_fullscreen_requested => {},
- .minimize_requested => {},
- }
-}
-
-pub fn manage(window: *Window) void {
- if (window.pending.closed) {
- window.window_v1.destroy();
- window.link.remove();
- window.link_focus.remove();
- window.link_wm.remove();
- {
- var it = wm.seats.iterator(.forward);
- while (it.next()) |seat| {
- if (seat.focused == window) {
- seat.focus(null);
- }
- }
- }
- window.shadow_decoration.destroy();
- window.shadow_viewport.destroy();
- window.shadow_surface.destroy();
- window.shadow_buffer.destroy();
- gpa.destroy(window);
- return;
- }
-
- if (window.pending.new) {
- window.window_v1.useSsd();
-
- if (if (wm.seats.first()) |seat| seat.focused_output else null) |output| {
- window.output = output;
- window.tags = output.tags;
- window.link_wm.remove();
- window.link_focus.remove();
- output.stack_focus.prepend(window);
- output.stack_wm.prepend(window);
- {
- var it = wm.seats.iterator(.forward);
- while (it.next()) |seat| {
- seat.focus(window);
- }
- }
- } else {
- window.tags = (1 << 0); // XXX
- window.link_wm.remove();
- window.link_focus.remove();
- wm.fallback_stack_focus.prepend(window);
- wm.fallback_stack_wm.prepend(window);
- }
- }
-
- switch (window.op) {
- .none, .move => {},
- .resize => |op| {
- window.window_v1.setTiled(.{ .top = false, .bottom = false, .left = false, .right = false });
- // resize from top left corner
- window.proposeDimensions(
- @max(1, op.start_box.width - op.seat.op.?.dx),
- @max(1, op.start_box.height - op.seat.op.?.dy),
- );
- },
- }
-
- window.pending = .{};
-}
-
-pub fn render(window: *Window) void {
- if (window.box.width != 0 and window.box.height != 0) {
- window.shadow_surface.attach(window.shadow_buffer, 0, 0);
- window.shadow_surface.damageBuffer(0, 0, math.maxInt(i32), math.maxInt(i32));
- window.shadow_viewport.setDestination(window.box.width + 2 * wm.config.border_width, window.box.height + 2 * wm.config.border_width);
- window.shadow_decoration.setOffset(10 - wm.config.border_width, 10 - wm.config.border_width);
- window.shadow_decoration.syncNextCommit();
- window.shadow_surface.commit();
- }
-
- switch (window.op) {
- .none => {},
- .move => |op| {
- window.setPosition(
- op.seat.op.?.dx + op.start_x,
- op.seat.op.?.dy + op.start_y,
- );
- },
- .resize => |op| {
- // resize from top left corner
- window.setPosition(
- op.start_box.x + (@as(i32, op.start_box.width) - window.box.width),
- op.start_box.y + (@as(i32, op.start_box.height) - window.box.height),
- );
- },
- }
-
- {
- var it = wm.seats.iterator(.forward);
- while (it.next()) |seat| {
- if (seat.hovered == window) {
- window.setBorders(wm.config.border_color_hovered);
- break;
- } else if (seat.focused == window) {
- window.setBorders(wm.config.border_color_focused);
- break;
- }
- } else {
- window.setBorders(wm.config.border_color_unfocused);
- }
- }
-}
-
-fn setBorders(window: *Window, rgb: u32) void {
- window.window_v1.setBorders(
- .{ .left = true, .bottom = true, .top = true, .right = true },
- wm.config.border_width,
- @as(u32, (rgb >> 16) & 0xff) * (0xffff_ffff / 0xff),
- @as(u32, (rgb >> 8) & 0xff) * (0xffff_ffff / 0xff),
- @as(u32, (rgb >> 0) & 0xff) * (0xffff_ffff / 0xff),
- 0xffff_ffff,
- );
-}
-
-pub const Box = struct {
- x: i32,
- y: i32,
- width: u31,
- height: u31,
-};
-
-pub fn layout(window: *Window, box: Box) void {
- window.setPosition(box.x, box.y);
- window.proposeDimensions(box.width, box.height);
-}
-
-pub fn setPosition(window: *Window, x: i32, y: i32) void {
- window.box.x = x;
- window.box.y = y;
- window.node_v1.setPosition(x + wm.config.border_width, y + wm.config.border_width);
-}
-
-pub fn proposeDimensions(window: *Window, width: u31, height: u31) void {
- window.box.width = width;
- window.box.height = height;
- window.window_v1.proposeDimensions(
- @max(1, @as(i32, width) - 2 * wm.config.border_width),
- @max(1, @as(i32, height) - 2 * wm.config.border_width),
- );
-}
diff --git a/rivercompat/WindowManager.zig b/rivercompat/WindowManager.zig
deleted file mode 100644
index 5394703..0000000
--- a/rivercompat/WindowManager.zig
+++ /dev/null
@@ -1,139 +0,0 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2024 The River Developers
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-const WindowManager = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const main = @import("main.zig");
-const wayland = @import("wayland");
-const wl = wayland.client.wl;
-const wp = wayland.client.wp;
-const river = wayland.client.river;
-
-const Background = @import("Background.zig");
-const Config = @import("Config.zig");
-const Output = @import("Output.zig");
-const Seat = @import("Seat.zig");
-const Window = @import("Window.zig");
-
-wm_v1: *river.WindowManagerV1,
-xkb_bindings: *river.XkbBindingsV1,
-compositor: *wl.Compositor,
-viewporter: *wp.Viewporter,
-single_pixel: *wp.SinglePixelBufferManagerV1,
-
-session_locked: bool = false,
-
-windows: wl.list.Head(Window, .link),
-seats: wl.list.Head(Seat, .link),
-outputs: wl.list.Head(Output, .link),
-
-config: Config = .{},
-background: Background,
-
-fallback_stack_wm: wl.list.Head(Window, .link_wm),
-fallback_stack_focus: wl.list.Head(Window, .link_focus),
-
-pub fn init(
- wm: *WindowManager,
- wm_v1: *river.WindowManagerV1,
- xkb_bindings: *river.XkbBindingsV1,
- compositor: *wl.Compositor,
- viewporter: *wp.Viewporter,
- single_pixel: *wp.SinglePixelBufferManagerV1,
-) void {
- wm.* = .{
- .wm_v1 = wm_v1,
- .xkb_bindings = xkb_bindings,
- .compositor = compositor,
- .viewporter = viewporter,
- .single_pixel = single_pixel,
- .windows = undefined,
- .seats = undefined,
- .outputs = undefined,
- .background = undefined,
- .fallback_stack_wm = undefined,
- .fallback_stack_focus = undefined,
- };
- wm.windows.init();
- wm.seats.init();
- wm.outputs.init();
- wm.background.init();
- wm.fallback_stack_wm.init();
- wm.fallback_stack_focus.init();
-
- wm_v1.setListener(*WindowManager, handleEvent, wm);
-}
-
-fn handleEvent(wm_v1: *river.WindowManagerV1, event: river.WindowManagerV1.Event, wm: *WindowManager) void {
- assert(wm.wm_v1 == wm_v1);
- switch (event) {
- .unavailable => main.fatal("another window manager is already running", .{}),
- .finished => unreachable, // We never send river_window_manager_v1.stop
- .manage_start => {
- wm.manage();
- wm_v1.manageFinish();
- },
- .render_start => {
- wm.render();
- wm_v1.renderFinish();
- },
- .session_locked => wm.session_locked = true,
- .session_unlocked => wm.session_locked = false,
- .window => |args| Window.create(args.id),
- .output => |args| Output.create(args.id),
- .seat => |args| Seat.create(args.id),
- }
-}
-
-fn manage(wm: *WindowManager) void {
- wm.background.manage();
- {
- var it = wm.outputs.iterator(.forward);
- while (it.next()) |output| {
- output.manage();
- }
- }
- {
- var it = wm.seats.iterator(.forward);
- while (it.next()) |seat| {
- seat.manage();
- }
- }
- {
- var it = wm.windows.safeIterator(.forward);
- while (it.next()) |window| {
- window.manage();
- }
- }
-
- {
- var it = wm.outputs.iterator(.forward);
- while (it.next()) |output| {
- output.layout();
- }
- }
-}
-
-fn render(wm: *WindowManager) void {
- {
- var it = wm.windows.iterator(.forward);
- while (it.next()) |window| {
- window.render();
- }
- }
-}
diff --git a/rivercompat/XkbBinding.zig b/rivercompat/XkbBinding.zig
deleted file mode 100644
index 14663c3..0000000
--- a/rivercompat/XkbBinding.zig
+++ /dev/null
@@ -1,62 +0,0 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2024 The River Developers
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-const XkbBinding = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wayland = @import("wayland");
-const wl = wayland.client.wl;
-const river = wayland.client.river;
-
-const wm = &@import("root").wm;
-
-const Seat = @import("Seat.zig");
-const Window = @import("Window.zig");
-
-const gpa = std.heap.c_allocator;
-
-seat: *Seat,
-xkb_binding_v1: *river.XkbBindingV1,
-action: Seat.Action,
-
-pub fn create(
- seat: *Seat,
- keysym: u32,
- modifiers: river.SeatV1.Modifiers,
- action: Seat.Action,
-) void {
- const xkb_binding_v1 = wm.xkb_bindings.getXkbBinding(seat.seat_v1, keysym, modifiers) catch @panic("OOM");
- const binding = gpa.create(XkbBinding) catch @panic("OOM");
- binding.* = .{
- .seat = seat,
- .xkb_binding_v1 = xkb_binding_v1,
- .action = action,
- };
- xkb_binding_v1.setListener(*XkbBinding, handleEvent, binding);
- xkb_binding_v1.enable();
-}
-
-fn handleEvent(xkb_binding_v1: *river.XkbBindingV1, event: river.XkbBindingV1.Event, binding: *XkbBinding) void {
- assert(binding.xkb_binding_v1 == xkb_binding_v1);
- switch (event) {
- .pressed => {
- assert(binding.seat.pending.action == null);
- binding.seat.pending.action = binding.action;
- },
- .released => {},
- }
-}
diff --git a/rivercompat/c.zig b/rivercompat/c.zig
deleted file mode 100644
index 38eb7c2..0000000
--- a/rivercompat/c.zig
+++ /dev/null
@@ -1,3 +0,0 @@
-pub usingnamespace @cImport({
- @cInclude("linux/input-event-codes.h");
-});
diff --git a/rivercompat/main.zig b/rivercompat/main.zig
deleted file mode 100644
index 8f4d81b..0000000
--- a/rivercompat/main.zig
+++ /dev/null
@@ -1,145 +0,0 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2024 The River Developers
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 3.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-const std = @import("std");
-const fmt = std.fmt;
-const mem = std.mem;
-const math = std.math;
-const posix = std.posix;
-const assert = std.debug.assert;
-
-const wayland = @import("wayland");
-const wl = wayland.client.wl;
-const wp = wayland.client.wp;
-const river = wayland.client.river;
-const flags = @import("flags");
-
-const WindowManager = @import("WindowManager.zig");
-
-const gpa = std.heap.c_allocator;
-
-const usage =
- \\usage: rivercompat [options]
- \\
- \\ -h Print this help message and exit.
- \\ -version Print the version number and exit.
- \\
-;
-
-const Globals = struct {
- wm_v1: ?*river.WindowManagerV1 = null,
- xkb_bindings: ?*river.XkbBindingsV1 = null,
- compositor: ?*wl.Compositor = null,
- viewporter: ?*wp.Viewporter = null,
- single_pixel: ?*wp.SinglePixelBufferManagerV1 = null,
-
- fn handleEvent(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void {
- switch (event) {
- .global => |global| {
- if (mem.orderZ(u8, global.interface, river.WindowManagerV1.interface.name) == .eq) {
- globals.wm_v1 = registry.bind(global.name, river.WindowManagerV1, 1) catch return;
- } else if (mem.orderZ(u8, global.interface, river.XkbBindingsV1.interface.name) == .eq) {
- globals.xkb_bindings = registry.bind(global.name, river.XkbBindingsV1, 1) catch return;
- } else if (mem.orderZ(u8, global.interface, wl.Compositor.interface.name) == .eq) {
- globals.compositor = registry.bind(global.name, wl.Compositor, 4) catch return;
- } else if (mem.orderZ(u8, global.interface, wp.Viewporter.interface.name) == .eq) {
- globals.viewporter = registry.bind(global.name, wp.Viewporter, 1) catch return;
- } else if (mem.orderZ(u8, global.interface, wp.SinglePixelBufferManagerV1.interface.name) == .eq) {
- globals.single_pixel = registry.bind(global.name, wp.SinglePixelBufferManagerV1, 1) catch return;
- } else if (mem.orderZ(u8, global.interface, wl.Seat.interface.name) == .eq) {
- const wl_seat = registry.bind(global.name, wl.Seat, 1) catch return;
- _ = wl_seat.getKeyboard() catch return;
- }
- },
- .global_remove => {},
- }
- }
-};
-
-pub var wm: WindowManager = undefined;
-
-pub fn main() !void {
- const result = flags.parser([*:0]const u8, &[_]flags.Flag{
- .{ .name = "h", .kind = .boolean },
- .{ .name = "version", .kind = .boolean },
- }).parse(std.os.argv[1..]) catch {
- try std.io.getStdErr().writeAll(usage);
- posix.exit(1);
- };
- if (result.flags.h) {
- try std.io.getStdOut().writeAll(usage);
- posix.exit(0);
- }
- if (result.args.len != 0) fatalPrintUsage("unknown option '{s}'", .{result.args[0]});
-
- if (result.flags.version) {
- try std.io.getStdOut().writeAll(@import("build_options").version ++ "\n");
- posix.exit(0);
- }
-
- // Avoid the need to waitpid after fork/exec.
- // This approach doesn't need any cleanup post-fork/pre-exec as the
- // handler remains default and the SA_NOCLDWAIT flag is reset.
- posix.sigaction(posix.SIG.CHLD, &.{
- .handler = .{ .handler = posix.SIG.DFL },
- .mask = posix.empty_sigset,
- .flags = posix.SA.NOCLDWAIT,
- }, null);
-
- const display = wl.Display.connect(null) catch {
- std.debug.print("Unable to connect to Wayland server.\n", .{});
- posix.exit(1);
- };
- defer display.disconnect();
-
- // Things get really noisy if all children started by the wm also have WAYLAND_DEBUG set.
- _ = unsetenv("WAYLAND_DEBUG");
-
- var globals: Globals = .{};
- const registry = try display.getRegistry();
- registry.setListener(*Globals, Globals.handleEvent, &globals);
- if (display.roundtrip() != .SUCCESS) fatal("initial roundtrip failed", .{});
-
- const wm_v1 = globals.wm_v1 orelse
- fatal("wayland compositor does not support river-window-management-v1", .{});
- const xkb_bindings = globals.xkb_bindings orelse
- fatal("wayland compositor does not support river-xkb_bindings-v1", .{});
- const compositor = globals.compositor orelse
- fatal("wayland compositor does not support wl_compositor", .{});
- const viewporter = globals.viewporter orelse
- fatal("wayland compositor does not support viewporter", .{});
- const single_pixel = globals.single_pixel orelse
- fatal("wayland compositor does not support wp-single-pixel-buffer-v1", .{});
-
- wm.init(wm_v1, xkb_bindings, compositor, viewporter, single_pixel);
-
- while (true) {
- if (display.dispatch() != .SUCCESS) fatal("failed to dispatch wayland events", .{});
- }
-}
-
-pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
- std.log.err(format, args);
- posix.exit(1);
-}
-
-fn fatalPrintUsage(comptime format: []const u8, args: anytype) noreturn {
- std.log.err(format, args);
- std.io.getStdErr().writeAll(usage) catch {};
- posix.exit(1);
-}
-
-extern fn unsetenv(name: [*:0]const u8) c_int;