diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index 7a96398..746593e 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -23,12 +23,12 @@ over the Wayland protocol. *csd-filter-add* _app-id_ Add _app-id_ to the CSD filter list. Views with this _app-id_ are told to use client side decoration instead of the default server - side decoration. Note that this affects only new views, not already + side decoration. Note that this affects both new views, as well as already existing ones. *csd-filter-remove* _app-id_ - Remove an _app-id_ from the CSD filter list. Note that this affects only new - views, not already existing ones. + Remove an _app-id_ from the CSD filter list. Note that this affects both new + views, as well as already existing ones. *exit* Exit the compositor, terminating the Wayland session. diff --git a/river/Decoration.zig b/river/Decoration.zig index 67bf5c2..2af5405 100644 --- a/river/Decoration.zig +++ b/river/Decoration.zig @@ -49,7 +49,10 @@ fn handleDestroy( const self = @fieldParentPtr(Self, "destroy", listener); self.destroy.link.remove(); self.request_mode.link.remove(); - util.gpa.destroy(self); + + const node = @fieldParentPtr(std.TailQueue(Self).Node, "data", self); + server.decoration_manager.decorations.remove(node); + util.gpa.destroy(node); } fn handleRequestMode( diff --git a/river/DecorationManager.zig b/river/DecorationManager.zig index 54ac850..bf335df 100644 --- a/river/DecorationManager.zig +++ b/river/DecorationManager.zig @@ -27,6 +27,10 @@ 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) = @@ -45,9 +49,10 @@ fn handleNewToplevelDecoration( xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1, ) void { const self = @fieldParentPtr(Self, "new_toplevel_decoration", listener); - const decoration = util.gpa.create(Decoration) catch { + const decoration_node = util.gpa.create(std.TailQueue(Decoration).Node) catch { xdg_toplevel_decoration.resource.postNoMemory(); return; }; - decoration.init(xdg_toplevel_decoration); + decoration_node.data.init(xdg_toplevel_decoration); + self.decorations.append(decoration_node); } diff --git a/river/command/filter.zig b/river/command/filter.zig index a7d79fa..2818c15 100644 --- a/river/command/filter.zig +++ b/river/command/filter.zig @@ -20,6 +20,8 @@ const std = @import("std"); const server = &@import("../main.zig").server; const util = @import("../util.zig"); +const View = @import("View.zig"); +const ViewStack = @import("view_stack.zig").ViewStack; const Error = @import("../command.zig").Error; const Seat = @import("../Seat.zig"); @@ -48,6 +50,7 @@ pub fn csdFilterAdd( out: *?[]const u8, ) Error!void { try modifyFilter(allocator, &server.config.csd_filter, args, .add); + csdFilterUpdateViews(args[1], .add); } pub fn csdFilterRemove( @@ -57,6 +60,7 @@ pub fn csdFilterRemove( out: *?[]const u8, ) Error!void { try modifyFilter(allocator, &server.config.csd_filter, args, .remove); + csdFilterUpdateViews(args[1], .remove); } fn modifyFilter( @@ -80,3 +84,47 @@ fn modifyFilter( list.appendAssumeCapacity(try std.mem.dupe(allocator, u8, args[1])); } } + +fn csdFilterUpdateViews(app_id: []const u8, operation: enum { add, remove }) void { + // There is no link between Decoration and View, so we need to iterate over + // both separately. Note that we do not need to arrange the outputs here; If + // the clients decoration mode changes, it will receive a configure event. + 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; + if (std.mem.eql( + u8, + std.mem.span(xdg_toplevel_decoration.surface.role_data.toplevel.app_id orelse return), + app_id, + )) { + _ = xdg_toplevel_decoration.setMode(switch (operation) { + .add => .client_side, + .remove => .server_side, + }); + } + } + + var output_it = server.root.outputs.first; + while (output_it) |output_node| : (output_it = output_node.next) { + var view_it = output_node.data.views.first; + while (view_it) |view_node| : (view_it = view_node.next) { + // CSD mode is not supported for XWayland views. + if (view_node.view.impl == .xwayland_view) continue; + + const view_app_id = std.mem.span(view_node.view.getAppId() orelse continue); + if (std.mem.eql(u8, app_id, view_app_id)) { + const toplevel = view_node.view.impl.xdg_toplevel.xdg_surface.role_data.toplevel; + switch (operation) { + .add => { + view_node.view.draw_borders = false; + _ = toplevel.setTiled(.{ .top = false, .bottom = false, .left = false, .right = false }); + }, + .remove => { + view_node.view.draw_borders = true; + _ = toplevel.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true }); + }, + } + } + } + } +}