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; + }, + } + } } } }