diff --git a/build.zig b/build.zig
index 65c2db3..a3f1bde 100644
--- a/build.zig
+++ b/build.zig
@@ -91,6 +91,7 @@ pub fn build(b: *zbs.Builder) !void {
scanner.addSystemProtocol("staging/ext-session-lock/ext-session-lock-v1.xml");
scanner.addSystemProtocol("unstable/pointer-gestures/pointer-gestures-unstable-v1.xml");
scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml");
+ scanner.addSystemProtocol("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-control-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-status-unstable-v1.xml");
scanner.addProtocolPath("protocol/river-layout-v3.xml");
@@ -109,6 +110,7 @@ pub fn build(b: *zbs.Builder) !void {
scanner.generate("xdg_wm_base", 2);
scanner.generate("zwp_pointer_gestures_v1", 3);
scanner.generate("zwp_pointer_constraints_v1", 1);
+ scanner.generate("zxdg_decoration_manager_v1", 1);
scanner.generate("ext_session_lock_manager_v1", 1);
scanner.generate("zriver_control_v1", 1);
diff --git a/deps/zig-wlroots b/deps/zig-wlroots
index f804c6d..c4cdb08 160000
--- a/deps/zig-wlroots
+++ b/deps/zig-wlroots
@@ -1 +1 @@
-Subproject commit f804c6d2ab1a240f7659c82325dc21ddcc6392b7
+Subproject commit c4cdb08505de19f6bfbf8e1825349b80c7696475
diff --git a/river/Decoration.zig b/river/Decoration.zig
deleted file mode 100644
index f2ef6c9..0000000
--- a/river/Decoration.zig
+++ /dev/null
@@ -1,70 +0,0 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2020 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 .
-
-const Self = @This();
-
-const std = @import("std");
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const Server = @import("Server.zig");
-const View = @import("View.zig");
-
-xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1,
-
-destroy: wl.Listener(*wlr.XdgToplevelDecorationV1) =
- wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleDestroy),
-request_mode: wl.Listener(*wlr.XdgToplevelDecorationV1) =
- wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleRequestMode),
-
-pub fn init(self: *Self, xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1) void {
- self.* = .{ .xdg_toplevel_decoration = xdg_toplevel_decoration };
-
- xdg_toplevel_decoration.events.destroy.add(&self.destroy);
- xdg_toplevel_decoration.events.request_mode.add(&self.request_mode);
-
- handleRequestMode(&self.request_mode, self.xdg_toplevel_decoration);
-}
-
-fn handleDestroy(
- listener: *wl.Listener(*wlr.XdgToplevelDecorationV1),
- _: *wlr.XdgToplevelDecorationV1,
-) void {
- const self = @fieldParentPtr(Self, "destroy", listener);
- self.destroy.link.remove();
- self.request_mode.link.remove();
-
- const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self);
- server.decoration_manager.decorations.remove(node);
- util.gpa.destroy(node);
-}
-
-fn handleRequestMode(
- listener: *wl.Listener(*wlr.XdgToplevelDecorationV1),
- _: *wlr.XdgToplevelDecorationV1,
-) void {
- const self = @fieldParentPtr(Self, "request_mode", listener);
-
- const view = @intToPtr(*View, self.xdg_toplevel_decoration.surface.data);
- if (server.config.csdAllowed(view)) {
- _ = self.xdg_toplevel_decoration.setMode(.client_side);
- } else {
- _ = self.xdg_toplevel_decoration.setMode(.server_side);
- }
-}
diff --git a/river/DecorationManager.zig b/river/DecorationManager.zig
deleted file mode 100644
index c2237bc..0000000
--- a/river/DecorationManager.zig
+++ /dev/null
@@ -1,57 +0,0 @@
-// This file is part of river, a dynamic tiling wayland compositor.
-//
-// Copyright 2020 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 .
-
-const Self = @This();
-
-const std = @import("std");
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const Decoration = @import("Decoration.zig");
-const Server = @import("Server.zig");
-
-/// List of all Decoration objects. This will clean itself up on exit through
-/// the wlr.XdgToplevelDecorationV1.events.destroy event.
-decorations: std.TailQueue(Decoration) = .{},
-
-xdg_decoration_manager: *wlr.XdgDecorationManagerV1,
-
-new_toplevel_decoration: wl.Listener(*wlr.XdgToplevelDecorationV1) =
- wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleNewToplevelDecoration),
-
-pub fn init(self: *Self) !void {
- self.* = .{
- .xdg_decoration_manager = try wlr.XdgDecorationManagerV1.create(server.wl_server),
- };
-
- self.xdg_decoration_manager.events.new_toplevel_decoration.add(&self.new_toplevel_decoration);
-}
-
-fn handleNewToplevelDecoration(
- listener: *wl.Listener(*wlr.XdgToplevelDecorationV1),
- xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1,
-) void {
- const self = @fieldParentPtr(Self, "new_toplevel_decoration", listener);
- const decoration_node = util.gpa.create(std.TailQueue(Decoration).Node) catch {
- xdg_toplevel_decoration.resource.postNoMemory();
- return;
- };
- decoration_node.data.init(xdg_toplevel_decoration);
- self.decorations.append(decoration_node);
-}
diff --git a/river/IdleInhibitor.zig b/river/IdleInhibitor.zig
index 2cebf97..4205276 100644
--- a/river/IdleInhibitor.zig
+++ b/river/IdleInhibitor.zig
@@ -11,7 +11,7 @@ const IdleInhibitorManager = @import("IdleInhibitorManager.zig");
inhibitor_manager: *IdleInhibitorManager,
inhibitor: *wlr.IdleInhibitorV1,
-destroy: wl.Listener(*wlr.IdleInhibitorV1) = wl.Listener(*wlr.IdleInhibitorV1).init(handleDestroy),
+destroy: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleDestroy),
pub fn init(self: *Self, inhibitor: *wlr.IdleInhibitorV1, inhibitor_manager: *IdleInhibitorManager) !void {
self.inhibitor_manager = inhibitor_manager;
@@ -22,7 +22,7 @@ pub fn init(self: *Self, inhibitor: *wlr.IdleInhibitorV1, inhibitor_manager: *Id
inhibitor_manager.idleInhibitCheckActive();
}
-fn handleDestroy(listener: *wl.Listener(*wlr.IdleInhibitorV1), _: *wlr.IdleInhibitorV1) void {
+fn handleDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
const self = @fieldParentPtr(Self, "destroy", listener);
self.destroy.link.remove();
diff --git a/river/Root.zig b/river/Root.zig
index 752a510..3c8a4b3 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -71,6 +71,8 @@ hidden: struct {
},
},
+views: wl.list.Head(View, .link),
+
new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOutput),
output_layout: *wlr.OutputLayout,
@@ -146,6 +148,7 @@ pub fn init(self: *Self) !void {
.wm_stack = undefined,
},
},
+ .views = undefined,
.output_layout = output_layout,
.output_manager = try wlr.OutputManagerV1.create(server.wl_server),
.power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server),
@@ -155,6 +158,7 @@ pub fn init(self: *Self) !void {
self.hidden.pending.wm_stack.init();
self.hidden.inflight.focus_stack.init();
self.hidden.inflight.wm_stack.init();
+ self.views.init();
server.backend.events.new_output.add(&self.new_output);
self.output_manager.events.apply.add(&self.manager_apply);
diff --git a/river/Server.zig b/river/Server.zig
index 456d3a5..3d6c63b 100644
--- a/river/Server.zig
+++ b/river/Server.zig
@@ -26,7 +26,6 @@ const util = @import("util.zig");
const Config = @import("Config.zig");
const Control = @import("Control.zig");
-const DecorationManager = @import("DecorationManager.zig");
const IdleInhibitorManager = @import("IdleInhibitorManager.zig");
const InputManager = @import("InputManager.zig");
const LayerSurface = @import("LayerSurface.zig");
@@ -36,6 +35,7 @@ const Output = @import("Output.zig");
const Root = @import("Root.zig");
const SceneNodeData = @import("SceneNodeData.zig");
const StatusManager = @import("StatusManager.zig");
+const XdgDecoration = @import("XdgDecoration.zig");
const XdgToplevel = @import("XdgToplevel.zig");
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
const XwaylandView = @import("XwaylandView.zig");
@@ -55,6 +55,9 @@ allocator: *wlr.Allocator,
xdg_shell: *wlr.XdgShell,
new_xdg_surface: wl.Listener(*wlr.XdgSurface),
+xdg_decoration_manager: *wlr.XdgDecorationManagerV1,
+new_toplevel_decoration: wl.Listener(*wlr.XdgToplevelDecorationV1),
+
layer_shell: *wlr.LayerShellV1,
new_layer_surface: wl.Listener(*wlr.LayerSurfaceV1),
@@ -66,7 +69,6 @@ foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1,
xdg_activation: *wlr.XdgActivationV1,
request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate),
-decoration_manager: DecorationManager,
input_manager: InputManager,
root: Root,
config: Config,
@@ -103,6 +105,10 @@ pub fn init(self: *Self) !void {
self.new_xdg_surface.setNotify(handleNewXdgSurface);
self.xdg_shell.events.new_surface.add(&self.new_xdg_surface);
+ self.xdg_decoration_manager = try wlr.XdgDecorationManagerV1.create(self.wl_server);
+ self.new_toplevel_decoration.setNotify(handleNewToplevelDecoration);
+ self.xdg_decoration_manager.events.new_toplevel_decoration.add(&self.new_toplevel_decoration);
+
self.layer_shell = try wlr.LayerShellV1.create(self.wl_server);
self.new_layer_surface.setNotify(handleNewLayerSurface);
self.layer_shell.events.new_surface.add(&self.new_layer_surface);
@@ -122,7 +128,6 @@ pub fn init(self: *Self) !void {
_ = try wlr.PrimarySelectionDeviceManagerV1.create(self.wl_server);
self.config = try Config.init();
- try self.decoration_manager.init();
try self.root.init();
// Must be called after root is initialized
try self.input_manager.init();
@@ -205,6 +210,24 @@ fn handleNewXdgSurface(_: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSu
};
}
+fn handleNewToplevelDecoration(
+ _: *wl.Listener(*wlr.XdgToplevelDecorationV1),
+ wlr_decoration: *wlr.XdgToplevelDecorationV1,
+) void {
+ const xdg_toplevel = @intToPtr(*XdgToplevel, wlr_decoration.surface.data);
+
+ // TODO(wlroots): The next wlroots version will handle this for us
+ if (xdg_toplevel.decoration != null) {
+ wlr_decoration.resource.postError(
+ .already_constructed,
+ "xdg_toplevel already has a decoration object",
+ );
+ return;
+ }
+
+ XdgDecoration.init(wlr_decoration);
+}
+
fn handleNewLayerSurface(listener: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void {
const self = @fieldParentPtr(Self, "new_layer_surface", listener);
diff --git a/river/View.zig b/river/View.zig
index 48ccadf..a436903 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -117,6 +117,9 @@ pub const State = struct {
/// The implementation of this view
impl: Impl,
+/// Link for Root.views
+link: wl.list.Link,
+
tree: *wlr.SceneTree,
surface_tree: *wlr.SceneTree,
saved_surface_tree: *wlr.SceneTree,
@@ -182,6 +185,7 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Self {
view.* = .{
.impl = impl,
+ .link = undefined,
.tree = tree,
.surface_tree = try tree.createSceneTree(),
.saved_surface_tree = try tree.createSceneTree(),
@@ -199,6 +203,7 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Self {
.inflight_focus_stack_link = undefined,
};
+ server.root.views.prepend(view);
server.root.hidden.pending.focus_stack.prepend(view);
server.root.hidden.pending.wm_stack.prepend(view);
server.root.hidden.inflight.focus_stack.prepend(view);
@@ -229,6 +234,7 @@ pub fn destroy(view: *Self) void {
view.tree.node.destroy();
view.popup_tree.node.destroy();
+ view.link.remove();
view.pending_focus_stack_link.remove();
view.pending_wm_stack_link.remove();
view.inflight_focus_stack_link.remove();
diff --git a/river/XdgDecoration.zig b/river/XdgDecoration.zig
new file mode 100644
index 0000000..8eb44b8
--- /dev/null
+++ b/river/XdgDecoration.zig
@@ -0,0 +1,80 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2023 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 .
+
+const XdgDecoration = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const wlr = @import("wlroots");
+const wl = @import("wayland").server.wl;
+
+const server = &@import("main.zig").server;
+const util = @import("util.zig");
+
+const XdgToplevel = @import("XdgToplevel.zig");
+
+wlr_decoration: *wlr.XdgToplevelDecorationV1,
+
+destroy: wl.Listener(*wlr.XdgToplevelDecorationV1) =
+ wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleDestroy),
+request_mode: wl.Listener(*wlr.XdgToplevelDecorationV1) =
+ wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleRequestMode),
+
+pub fn init(wlr_decoration: *wlr.XdgToplevelDecorationV1) void {
+ const xdg_toplevel = @intToPtr(*XdgToplevel, wlr_decoration.surface.data);
+
+ xdg_toplevel.decoration = .{ .wlr_decoration = wlr_decoration };
+ const decoration = &xdg_toplevel.decoration.?;
+
+ wlr_decoration.events.destroy.add(&decoration.destroy);
+ wlr_decoration.events.request_mode.add(&decoration.request_mode);
+
+ handleRequestMode(&decoration.request_mode, decoration.wlr_decoration);
+}
+
+// TODO(wlroots): remove this function when updating to 0.17.0
+// https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4051
+pub fn deinit(decoration: *XdgDecoration) void {
+ decoration.destroy.link.remove();
+ decoration.request_mode.link.remove();
+}
+
+fn handleDestroy(
+ listener: *wl.Listener(*wlr.XdgToplevelDecorationV1),
+ _: *wlr.XdgToplevelDecorationV1,
+) void {
+ const decoration = @fieldParentPtr(XdgDecoration, "destroy", listener);
+ const xdg_toplevel = @intToPtr(*XdgToplevel, decoration.wlr_decoration.surface.data);
+
+ decoration.deinit();
+
+ assert(xdg_toplevel.decoration != null);
+ xdg_toplevel.decoration = null;
+}
+
+fn handleRequestMode(
+ listener: *wl.Listener(*wlr.XdgToplevelDecorationV1),
+ _: *wlr.XdgToplevelDecorationV1,
+) void {
+ const decoration = @fieldParentPtr(XdgDecoration, "request_mode", listener);
+
+ const xdg_toplevel = @intToPtr(*XdgToplevel, decoration.wlr_decoration.surface.data);
+ if (server.config.csdAllowed(xdg_toplevel.view)) {
+ _ = decoration.wlr_decoration.setMode(.client_side);
+ } else {
+ _ = decoration.wlr_decoration.setMode(.server_side);
+ }
+}
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index f0bd28e..64c3f99 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -29,6 +29,7 @@ const Output = @import("Output.zig");
const Seat = @import("Seat.zig");
const XdgPopup = @import("XdgPopup.zig");
const View = @import("View.zig");
+const XdgDecoration = @import("XdgDecoration.zig");
const log = std.log.scoped(.xdg_shell);
@@ -37,6 +38,8 @@ view: *View,
xdg_toplevel: *wlr.XdgToplevel,
+decoration: ?XdgDecoration = null,
+
/// Initialized on map
geometry: wlr.Box = undefined,
@@ -74,15 +77,15 @@ pub fn create(xdg_toplevel: *wlr.XdgToplevel) error{OutOfMemory}!void {
} });
errdefer view.destroy();
- view.impl.xdg_toplevel.view = view;
-
_ = try view.surface_tree.createSceneXdgSurface(xdg_toplevel.base);
- xdg_toplevel.base.data = @ptrToInt(view);
- xdg_toplevel.base.surface.data = @ptrToInt(&view.tree.node);
-
- // Add listeners that are active over the view's entire lifetime
const self = &view.impl.xdg_toplevel;
+
+ self.view = view;
+
+ xdg_toplevel.base.data = @ptrToInt(self);
+
+ // Add listeners that are active over the toplevel's entire lifetime
xdg_toplevel.base.events.destroy.add(&self.destroy);
xdg_toplevel.base.events.map.add(&self.map);
xdg_toplevel.base.events.unmap.add(&self.unmap);
@@ -170,6 +173,17 @@ pub fn destroyPopups(self: Self) void {
fn handleDestroy(listener: *wl.Listener(void)) void {
const self = @fieldParentPtr(Self, "destroy", listener);
+ // TODO(wlroots): Replace this with an assertion when updating to wlroots 0.17.0
+ // https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4051
+ if (self.decoration) |*decoration| {
+ decoration.wlr_decoration.resource.postError(
+ .orphaned,
+ "xdg_toplevel destroyed before xdg_toplevel_decoration",
+ );
+ decoration.deinit();
+ self.decoration = null;
+ }
+
// Remove listeners that are active for the entire lifetime of the view
self.destroy.link.remove();
self.map.link.remove();
diff --git a/river/command/filter.zig b/river/command/filter.zig
index 8a69954..cb07c7d 100644
--- a/river/command/filter.zig
+++ b/river/command/filter.zig
@@ -116,21 +116,22 @@ pub fn csdFilterRemove(
}
fn csdFilterUpdateViews(kind: FilterKind, pattern: []const u8, operation: enum { add, remove }) void {
- var decoration_it = server.decoration_manager.decorations.first;
- while (decoration_it) |decoration_node| : (decoration_it = decoration_node.next) {
- const xdg_toplevel_decoration = decoration_node.data.xdg_toplevel_decoration;
-
- const view = @intToPtr(*View, xdg_toplevel_decoration.surface.data);
- if (viewMatchesPattern(kind, pattern, view)) {
- switch (operation) {
- .add => {
- _ = xdg_toplevel_decoration.setMode(.client_side);
- view.pending.borders = false;
- },
- .remove => {
- _ = xdg_toplevel_decoration.setMode(.server_side);
- view.pending.borders = true;
- },
+ var it = server.root.views.iterator(.forward);
+ while (it.next()) |view| {
+ if (view.impl == .xdg_toplevel) {
+ if (view.impl.xdg_toplevel.decoration) |decoration| {
+ if (viewMatchesPattern(kind, pattern, view)) {
+ switch (operation) {
+ .add => {
+ _ = decoration.wlr_decoration.setMode(.client_side);
+ view.pending.borders = false;
+ },
+ .remove => {
+ _ = decoration.wlr_decoration.setMode(.server_side);
+ view.pending.borders = true;
+ },
+ }
+ }
}
}
}