Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
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) {