git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit73c78cc8582c03404f05f68f034d0b0eae34a115
parent94abafc035
authorIsaac Freund <[email protected]>
date2025-08-23 10:58
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;