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

commitcedb6073c302e98f7a896c6ce10ded6509c1f27b
parent8a042ad372
authorIsaac Freund <[email protected]>
date2025-03-23 09:52
rivercompat: reorganize global state

 rivercompat/{ShellSurface.zig => Background.zig} | 34 ++++------
 rivercompat/Config.zig                           | 31 +++++++++
 rivercompat/Output.zig                           | 85 ++++++++++++------------
 rivercompat/Seat.zig                             | 16 ++---
 rivercompat/Window.zig                           | 59 +++++++++-------
 rivercompat/WindowManager.zig                    | 32 ++++-----
 rivercompat/main.zig                             |  5 +-
 7 files changed, 145 insertions(+), 117 deletions(-)

diff --git a/rivercompat/ShellSurface.zig b/rivercompat/Background.zig
similarity index 67%
rename from rivercompat/ShellSurface.zig
rename to rivercompat/Background.zig
index 605e3c7..2ec3e6f 100644
--- a/rivercompat/ShellSurface.zig
+++ b/rivercompat/Background.zig
@@ -1,6 +1,6 @@
 // This file is part of river, a dynamic tiling wayland compositor.
 //
-// Copyright 2024 The River Developers
+// 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
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
-const ShellSurface = @This();
+const Background = @This();
 
 const std = @import("std");
 const assert = std.debug.assert;
@@ -24,9 +24,7 @@ const wl = wayland.client.wl;
 const wp = wayland.client.wp;
 const river = wayland.client.river;
 
-const WindowManager = @import("WindowManager.zig");
-
-const gpa = std.heap.c_allocator;
+const wm = &@import("root").wm;
 
 const State = struct {
     new: bool = false,
@@ -37,30 +35,26 @@ viewport: *wp.Viewport,
 shell_surface_v1: *river.ShellSurfaceV1,
 node: *river.NodeV1,
 pending: State,
-link: wl.list.Link,
 
-pub fn create(wm: *WindowManager) void {
-    const shell_surface = gpa.create(ShellSurface) catch @panic("OOM");
+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");
 
-    shell_surface.* = .{
+    background.* = .{
         .surface = surface,
         .viewport = viewport,
         .shell_surface_v1 = shell_surface_v1,
         .node = shell_surface_v1.getNode() catch @panic("OOM"),
         .pending = .{ .new = true },
-        .link = undefined,
     };
-    wm.shell_surfaces.append(shell_surface);
 }
 
-pub fn updateWindowing(shell_surface: *ShellSurface, wm: *WindowManager) void {
-    if (shell_surface.pending.new) {
-        shell_surface.node.placeBottom();
-        shell_surface.node.setPosition(0, 0);
-        shell_surface.shell_surface_v1.syncNextCommit();
+pub fn updateWindowing(background: *Background) void {
+    if (background.pending.new) {
+        background.node.placeBottom();
+        background.node.setPosition(0, 0);
+        background.shell_surface_v1.syncNextCommit();
 
         const rgb = 0xfdf6e3;
 
@@ -72,10 +66,10 @@ pub fn updateWindowing(shell_surface: *ShellSurface, wm: *WindowManager) void {
         ) catch @panic("OOM");
         defer buffer.destroy();
 
-        shell_surface.surface.attach(buffer, 0, 0);
+        background.surface.attach(buffer, 0, 0);
 
-        shell_surface.surface.damageBuffer(0, 0, math.maxInt(i32), math.maxInt(i32));
-        shell_surface.viewport.setDestination(math.maxInt(i32) / 2, math.maxInt(i32) / 2);
-        shell_surface.surface.commit();
+        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();
     }
 }
diff --git a/rivercompat/Config.zig b/rivercompat/Config.zig
new file mode 100644
index 0000000..b81bb24
--- /dev/null
+++ b/rivercompat/Config.zig
@@ -0,0 +1,31 @@
+// 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: u32 = 0x586e75,
+
+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
index 24f786d..636e9a4 100644
--- a/rivercompat/Output.zig
+++ b/rivercompat/Output.zig
@@ -23,11 +23,10 @@ const wl = wayland.client.wl;
 const river = wayland.client.river;
 
 const Window = @import("Window.zig");
-const WindowManager = @import("WindowManager.zig");
 
+const wm = &@import("root").wm;
 const gpa = std.heap.c_allocator;
 
-wm: *WindowManager,
 output_v1: *river.OutputV1,
 pending: struct {
     new: bool = false,
@@ -45,10 +44,9 @@ stack_wm: wl.list.Head(Window, .link_wm),
 
 link: wl.list.Link,
 
-pub fn create(wm: *WindowManager, output_v1: *river.OutputV1) void {
+pub fn create(output_v1: *river.OutputV1) void {
     const output = gpa.create(Output) catch @panic("OOM");
     output.* = .{
-        .wm = wm,
         .output_v1 = output_v1,
         .pending = .{ .new = true },
         .stack_focus = undefined,
@@ -77,7 +75,7 @@ fn handleEvent(output_v1: *river.OutputV1, event: river.OutputV1.Event, output:
     }
 }
 
-pub fn updateWindowing(output: *Output, wm: *WindowManager) void {
+pub fn updateWindowing(output: *Output) void {
     if (output.pending.removed) {
         // XXX
         output.output_v1.destroy();
@@ -109,14 +107,6 @@ pub fn updateWindowing(output: *Output, wm: *WindowManager) void {
     output.pending = .{};
 }
 
-const config = struct {
-    const main_count = 1;
-    const main_location: enum { left, right, top, bottom } = .left;
-    const outer_padding = 10;
-    const window_padding = 10;
-    const main_ratio = 0.60;
-};
-
 pub fn layout(output: *Output) void {
     var count: u31 = 0;
     {
@@ -129,16 +119,16 @@ pub fn layout(output: *Output) void {
     }
     if (count == 0) return;
 
-    const main_count = @min(config.main_count, count);
+    const main_count = @min(wm.config.main_count, count);
     const secondary_count = count -| main_count;
 
-    const usable_width = switch (config.main_location) {
-        .left, .right => output.width -| (2 *| config.outer_padding),
-        .top, .bottom => output.height -| (2 *| config.outer_padding),
+    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 (config.main_location) {
-        .left, .right => output.height -| (2 *| config.outer_padding),
-        .top, .bottom => output.width -| (2 *| 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
@@ -152,7 +142,7 @@ pub fn layout(output: *Output) void {
     var secondary_height_rem: u31 = undefined;
 
     if (secondary_count > 0) {
-        main_width = @intFromFloat(config.main_ratio * @as(f64, @floatFromInt(usable_width)));
+        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;
 
@@ -189,29 +179,38 @@ pub fn layout(output: *Output) void {
                 height = secondary_height + if (i == main_count) secondary_height_rem else 0;
             }
 
-            x +|= config.window_padding;
-            y +|= config.window_padding;
-            width -|= 2 *| config.window_padding;
-            height -|= 2 *| config.window_padding;
-
-            switch (config.main_location) {
-                .left => {
-                    window.node_v1.setPosition(x +| config.outer_padding, y +| config.outer_padding);
-                    window.window_v1.proposeDimensions(width, height);
-                },
-                .right => {
-                    window.node_v1.setPosition(usable_width - width - x +| config.outer_padding, y +| config.outer_padding);
-                    window.window_v1.proposeDimensions(width, height);
-                },
-                .top => {
-                    window.node_v1.setPosition(y +| config.outer_padding, x +| config.outer_padding);
-                    window.window_v1.proposeDimensions(height, width);
-                },
-                .bottom => {
-                    window.node_v1.setPosition(y +| config.outer_padding, usable_width - width - x +| config.outer_padding);
-                    window.window_v1.proposeDimensions(height, width);
-                },
+            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 });
         }
     }
 
diff --git a/rivercompat/Seat.zig b/rivercompat/Seat.zig
index febd173..4ffcdd0 100644
--- a/rivercompat/Seat.zig
+++ b/rivercompat/Seat.zig
@@ -27,10 +27,10 @@ const c = @import("c.zig");
 
 const Output = @import("Output.zig");
 const Window = @import("Window.zig");
-const WindowManager = @import("WindowManager.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 {
@@ -40,17 +40,15 @@ const State = struct {
     shell_surface_interaction: ?*river.ShellSurfaceV1 = null,
 };
 
-wm: *WindowManager,
 seat_v1: *river.SeatV1,
 pending: State = .{},
 focused: ?*Window = null,
 focused_output: ?*Output = null,
 link: wl.list.Link,
 
-pub fn create(wm: *WindowManager, seat_v1: *river.SeatV1) void {
+pub fn create(seat_v1: *river.SeatV1) void {
     const seat = gpa.create(Seat) catch @panic("OOM");
     seat.* = .{
-        .wm = wm,
         .seat_v1 = seat_v1,
         .pending = .{ .new = true },
         .link = undefined,
@@ -85,7 +83,7 @@ fn handleEvent(seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: *Seat) v
 
 pub fn updateWindowing(seat: *Seat) void {
     if (seat.pending.new) {
-        seat.focused_output = seat.wm.outputs.first();
+        seat.focused_output = wm.outputs.first();
 
         XkbBinding.create(seat, xkb.Keysym.n, .{ .mod4 = true }, .focus_next);
         XkbBinding.create(seat, xkb.Keysym.h, .{ .mod4 = true }, .hide_focused);
@@ -123,21 +121,21 @@ pub fn execute(seat: *Seat, action: Action) void {
         .close_focused => if (seat.focused) |window| window.window_v1.close(),
         .hide_focused => if (seat.focused) |window| window.window_v1.hide(),
         .show_all => {
-            var it = seat.wm.windows.iterator(.forward);
+            var it = wm.windows.iterator(.forward);
             while (it.next()) |window| {
                 window.window_v1.show();
             }
         },
         .move_start => {
             seat.seat_v1.opStartPointer();
-            var it = seat.wm.windows.iterator(.forward);
+            var it = wm.windows.iterator(.forward);
             while (it.next()) |window| {
                 seat.seat_v1.opAddMoveWindow(window.window_v1);
             }
         },
         .resize_start => {
             seat.seat_v1.opStartPointer();
-            var it = seat.wm.windows.iterator(.forward);
+            var it = wm.windows.iterator(.forward);
             while (it.next()) |window| {
                 seat.seat_v1.opAddResizeWindow(window.window_v1, .{
                     .top = true,
@@ -151,7 +149,7 @@ pub fn execute(seat: *Seat, action: Action) void {
 
 pub fn focus(seat: *Seat, _target: ?*Window) void {
     if (seat.focused_output == null) return;
-    if (seat.wm.session_locked) return;
+    if (wm.session_locked) return;
 
     var target = _target;
     if (target) |window| {
diff --git a/rivercompat/Window.zig b/rivercompat/Window.zig
index 9cd8a1b..12d492c 100644
--- a/rivercompat/Window.zig
+++ b/rivercompat/Window.zig
@@ -25,8 +25,8 @@ const wp = wayland.client.wp;
 const river = wayland.client.river;
 
 const Output = @import("Output.zig");
-const WindowManager = @import("WindowManager.zig");
 
+const wm = &@import("root").wm;
 const gpa = std.heap.c_allocator;
 
 window_v1: *river.WindowV1,
@@ -35,12 +35,11 @@ windowing: struct {
     new: bool = false,
     closed: bool = false,
 },
-rendering: struct {
-    x: i32 = 0,
-    y: i32 = 0,
-    width: i32 = 0,
-    height: i32 = 0,
-} = .{},
+
+x: i32 = 0,
+y: i32 = 0,
+width: i32 = 0,
+height: i32 = 0,
 
 output: ?*Output = null,
 tags: u32 = 0,
@@ -54,7 +53,7 @@ shadow_decoration: *river.DecorationV1,
 shadow_viewport: *wp.Viewport,
 shadow_buffer: *wl.Buffer,
 
-pub fn create(window_v1: *river.WindowV1, wm: *WindowManager) void {
+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");
@@ -92,8 +91,8 @@ fn handleEvent(window_v1: *river.WindowV1, event: river.WindowV1.Event, window:
         .closed => window.windowing.closed = true,
         .dimensions_hint => {},
         .dimensions => |args| {
-            window.rendering.width = args.width;
-            window.rendering.height = args.height;
+            window.width = args.width;
+            window.height = args.height;
         },
         .app_id => {},
         .title => {},
@@ -110,7 +109,7 @@ fn handleEvent(window_v1: *river.WindowV1, event: river.WindowV1.Event, window:
     }
 }
 
-pub fn updateWindowing(window: *Window, wm: *WindowManager) void {
+pub fn updateWindowing(window: *Window) void {
     if (window.windowing.closed) {
         window.window_v1.destroy();
         window.link.remove();
@@ -129,13 +128,12 @@ pub fn updateWindowing(window: *Window, wm: *WindowManager) void {
     if (window.windowing.new) {
         window.window_v1.useSsd();
 
-        const rgb = 0x586e75;
         window.window_v1.setBorders(
-            .{ .left = true, .bottom = true, .top = false, .right = true },
-            6, // 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),
+            .{ .left = true, .bottom = true, .top = true, .right = true },
+            wm.config.border_width,
+            @as(u32, (wm.config.border_color >> 16) & 0xff) * (0xffff_ffff / 0xff),
+            @as(u32, (wm.config.border_color >> 8) & 0xff) * (0xffff_ffff / 0xff),
+            @as(u32, (wm.config.border_color >> 0) & 0xff) * (0xffff_ffff / 0xff),
             0xffff_ffff,
         );
 
@@ -164,15 +162,30 @@ pub fn updateWindowing(window: *Window, wm: *WindowManager) void {
     window.windowing = .{};
 }
 
-pub fn updateRendering(window: *Window, wm: *WindowManager) void {
-    _ = wm;
-
-    if (window.rendering.width != 0 and window.rendering.height != 0) {
+pub fn updateRendering(window: *Window) void {
+    if (window.width != 0 and window.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.rendering.width, window.rendering.height);
-        window.shadow_decoration.setOffset(20, 20);
+        window.shadow_viewport.setDestination(window.width + 2 * wm.config.border_width, window.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();
     }
 }
+
+pub const Box = struct {
+    x: i32,
+    y: i32,
+    width: u31,
+    height: u31,
+};
+
+pub fn layout(
+    window: *Window,
+    box: Box,
+) void {
+    window.x = box.x + wm.config.border_width;
+    window.y = box.y + wm.config.border_width;
+    window.node_v1.setPosition(window.x, window.y);
+    window.window_v1.proposeDimensions(box.width - 2 * wm.config.border_width, box.height - 2 * wm.config.border_width);
+}
diff --git a/rivercompat/WindowManager.zig b/rivercompat/WindowManager.zig
index e0ccf73..2485719 100644
--- a/rivercompat/WindowManager.zig
+++ b/rivercompat/WindowManager.zig
@@ -24,9 +24,10 @@ 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 ShellSurface = @import("ShellSurface.zig");
 const Window = @import("Window.zig");
 
 wm_v1: *river.WindowManagerV1,
@@ -39,7 +40,9 @@ session_locked: bool = false,
 windows: wl.list.Head(Window, .link),
 seats: wl.list.Head(Seat, .link),
 outputs: wl.list.Head(Output, .link),
-shell_surfaces: wl.list.Head(ShellSurface, .link),
+
+config: Config = .{},
+background: Background,
 
 fallback_stack_wm: wl.list.Head(Window, .link_wm),
 fallback_stack_focus: wl.list.Head(Window, .link_focus),
@@ -59,20 +62,18 @@ pub fn init(
         .windows = undefined,
         .seats = undefined,
         .outputs = undefined,
-        .shell_surfaces = undefined,
+        .background = undefined,
         .fallback_stack_wm = undefined,
         .fallback_stack_focus = undefined,
     };
     wm.windows.init();
     wm.seats.init();
     wm.outputs.init();
-    wm.shell_surfaces.init();
+    wm.background.init();
     wm.fallback_stack_wm.init();
     wm.fallback_stack_focus.init();
 
     wm_v1.setListener(*WindowManager, handleEvent, wm);
-
-    ShellSurface.create(wm);
 }
 
 fn handleEvent(wm_v1: *river.WindowManagerV1, event: river.WindowManagerV1.Event, wm: *WindowManager) void {
@@ -90,17 +91,18 @@ fn handleEvent(wm_v1: *river.WindowManagerV1, event: river.WindowManagerV1.Event
         },
         .session_locked => wm.session_locked = true,
         .session_unlocked => wm.session_locked = false,
-        .window => |args| Window.create(args.id, wm),
-        .output => |args| Output.create(wm, args.id),
-        .seat => |args| Seat.create(wm, args.id),
+        .window => |args| Window.create(args.id),
+        .output => |args| Output.create(args.id),
+        .seat => |args| Seat.create(args.id),
     }
 }
 
 fn updateWindowing(wm: *WindowManager) void {
+    wm.background.updateWindowing();
     {
         var it = wm.outputs.iterator(.forward);
         while (it.next()) |output| {
-            output.updateWindowing(wm);
+            output.updateWindowing();
         }
     }
     {
@@ -112,13 +114,7 @@ fn updateWindowing(wm: *WindowManager) void {
     {
         var it = wm.windows.safeIterator(.forward);
         while (it.next()) |window| {
-            window.updateWindowing(wm);
-        }
-    }
-    {
-        var it = wm.shell_surfaces.iterator(.forward);
-        while (it.next()) |shell_surface| {
-            shell_surface.updateWindowing(wm);
+            window.updateWindowing();
         }
     }
 
@@ -134,7 +130,7 @@ fn updateRendering(wm: *WindowManager) void {
     {
         var it = wm.windows.iterator(.forward);
         while (it.next()) |window| {
-            window.updateRendering(wm);
+            window.updateRendering();
         }
     }
 }
diff --git a/rivercompat/main.zig b/rivercompat/main.zig
index 681a7c8..5ecc4ea 100644
--- a/rivercompat/main.zig
+++ b/rivercompat/main.zig
@@ -66,9 +66,7 @@ const Globals = struct {
     }
 };
 
-const Output = struct {
-    output_v1: *river.OutputV1,
-};
+pub var wm: WindowManager = undefined;
 
 pub fn main() !void {
     const result = flags.parser([*:0]const u8, &[_]flags.Flag{
@@ -109,7 +107,6 @@ pub fn main() !void {
     const single_pixel = globals.single_pixel orelse
         fatal("wayland compositor does not support wp-single-pixel-buffer-v1", .{});
 
-    var wm: WindowManager = undefined;
     wm.init(wm_v1, compositor, viewporter, single_pixel);
 
     while (true) {