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

commit379fbb813b906183cfa2313899ea8d18a7595629
parent0062ad9456
authorIsaac Freund <[email protected]>
date2023-03-10 14:55
xdg-decoration: clean up implementation

We now send some protocol errors that wlroots 0.16 is missing [1].
This also allows us to access the xdg decoration from a view, which will
be necessary for some future changes.

[1]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4051

 build.zig                   |  2 ++
 deps/zig-wlroots            |  2 +-
 river/Decoration.zig        | 70 ---------------------------------------
 river/DecorationManager.zig | 57 --------------------------------
 river/IdleInhibitor.zig     |  4 +--
 river/Root.zig              |  4 +++
 river/Server.zig            | 29 ++++++++++++++--
 river/View.zig              |  6 ++++
 river/XdgDecoration.zig     | 80 +++++++++++++++++++++++++++++++++++++++++++++
 river/XdgToplevel.zig       | 26 +++++++++++----
 river/command/filter.zig    | 31 +++++++++---------
 11 files changed, 157 insertions(+), 154 deletions(-)

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 <https://www.gnu.org/licenses/>.
-
-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 <https://www.gnu.org/licenses/>.
-
-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 <https://www.gnu.org/licenses/>.
+
+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;
+                        },
+                    }
+                }
             }
         }
     }