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
This commit is contained in:
Isaac Freund 2023-03-10 14:55:56 +01:00
parent fcb184f0bd
commit 05eac54b07
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
11 changed files with 157 additions and 154 deletions

View File

@ -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);

2
deps/zig-wlroots vendored

@ -1 +1 @@
Subproject commit f804c6d2ab1a240f7659c82325dc21ddcc6392b7
Subproject commit c4cdb08505de19f6bfbf8e1825349b80c7696475

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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();

80
river/XdgDecoration.zig Normal file
View File

@ -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);
}
}

View File

@ -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();

View File

@ -116,24 +116,25 @@ 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);
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 => {
_ = xdg_toplevel_decoration.setMode(.client_side);
_ = decoration.wlr_decoration.setMode(.client_side);
view.pending.borders = false;
},
.remove => {
_ = xdg_toplevel_decoration.setMode(.server_side);
_ = decoration.wlr_decoration.setMode(.server_side);
view.pending.borders = true;
},
}
}
}
}
}
}
fn viewMatchesPattern(kind: FilterKind, pattern: []const u8, view: *View) bool {