river: Allow applying CSD based on window titles
This extends the `csd-filter-add` command to allow matching on window
titles as well, using a `csd-filter-add kind pattern` syntax. The
following kinds are supported:
  * `title`, which matches window titles
  * `app-id`, which matches app ids
Only exact matches are considered.
As an example following configuration applies client-side decorations to
all windows with the title 'asdf with spaces'.
    riverctl csd-filter-add title 'asdf with spaces'
			
			
This commit is contained in:
		
				
					committed by
					
						
						Isaac Freund
					
				
			
			
				
	
			
			
			
						parent
						
							98aed8d47e
						
					
				
				
					commit
					5f6428bafe
				
			@ -62,8 +62,9 @@ modes: std.ArrayList(Mode),
 | 
			
		||||
float_filter_app_ids: std.StringHashMapUnmanaged(void) = .{},
 | 
			
		||||
float_filter_titles: std.StringHashMapUnmanaged(void) = .{},
 | 
			
		||||
 | 
			
		||||
/// Set of app_ids which are allowed to use client side decorations
 | 
			
		||||
csd_filter: std.StringHashMapUnmanaged(void) = .{},
 | 
			
		||||
/// Sets of app_ids and titles which are allowed to use client side decorations
 | 
			
		||||
csd_filter_app_ids: std.StringHashMapUnmanaged(void) = .{},
 | 
			
		||||
csd_filter_titles: std.StringHashMapUnmanaged(void) = .{},
 | 
			
		||||
 | 
			
		||||
/// The selected focus_follows_cursor mode
 | 
			
		||||
focus_follows_cursor: FocusFollowsCursorMode = .disabled,
 | 
			
		||||
@ -133,9 +134,15 @@ pub fn deinit(self: *Self) void {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        var it = self.csd_filter.keyIterator();
 | 
			
		||||
        var it = self.csd_filter_app_ids.keyIterator();
 | 
			
		||||
        while (it.next()) |key| util.gpa.free(key.*);
 | 
			
		||||
        self.csd_filter.deinit(util.gpa);
 | 
			
		||||
        self.csd_filter_app_ids.deinit(util.gpa);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        var it = self.csd_filter_titles.keyIterator();
 | 
			
		||||
        while (it.next()) |key| util.gpa.free(key.*);
 | 
			
		||||
        self.csd_filter_titles.deinit(util.gpa);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    util.gpa.free(self.default_layout_namespace);
 | 
			
		||||
@ -156,3 +163,19 @@ pub fn shouldFloat(self: Self, view: *View) bool {
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn csdAllowed(self: Self, view: *View) bool {
 | 
			
		||||
    if (view.getAppId()) |app_id| {
 | 
			
		||||
        if (self.csd_filter_app_ids.contains(std.mem.span(app_id))) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (view.getTitle()) |title| {
 | 
			
		||||
        if (self.csd_filter_titles.contains(std.mem.span(title))) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,7 @@ 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,
 | 
			
		||||
 | 
			
		||||
@ -62,8 +63,8 @@ fn handleRequestMode(
 | 
			
		||||
) void {
 | 
			
		||||
    const self = @fieldParentPtr(Self, "request_mode", listener);
 | 
			
		||||
 | 
			
		||||
    const toplevel = self.xdg_toplevel_decoration.surface.role_data.toplevel;
 | 
			
		||||
    if (toplevel.app_id != null and server.config.csd_filter.contains(mem.span(toplevel.app_id.?))) {
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
@ -218,9 +218,9 @@ fn handleMap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurfa
 | 
			
		||||
        view.pending.box = view.float_box;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If the toplevel has an app_id which is not configured to use client side
 | 
			
		||||
    // decorations, inform it that it is tiled.
 | 
			
		||||
    if (toplevel.app_id != null and server.config.csd_filter.contains(mem.span(toplevel.app_id.?))) {
 | 
			
		||||
    // If the view has an app_id or title which is not configured to use client
 | 
			
		||||
    // side decorations, inform it that it is tiled.
 | 
			
		||||
    if (server.config.csdAllowed(view)) {
 | 
			
		||||
        view.draw_borders = false;
 | 
			
		||||
    } else {
 | 
			
		||||
        _ = toplevel.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true });
 | 
			
		||||
 | 
			
		||||
@ -79,15 +79,22 @@ pub fn csdFilterAdd(
 | 
			
		||||
    args: []const [:0]const u8,
 | 
			
		||||
    out: *?[]const u8,
 | 
			
		||||
) Error!void {
 | 
			
		||||
    if (args.len < 2) return Error.NotEnoughArguments;
 | 
			
		||||
    if (args.len > 2) return Error.TooManyArguments;
 | 
			
		||||
    if (args.len < 3) return Error.NotEnoughArguments;
 | 
			
		||||
    if (args.len > 3) return Error.TooManyArguments;
 | 
			
		||||
 | 
			
		||||
    const gop = try server.config.csd_filter.getOrPut(util.gpa, args[1]);
 | 
			
		||||
    const kind = std.meta.stringToEnum(FilterKind, args[1]) orelse return Error.UnknownOption;
 | 
			
		||||
    const map = switch (kind) {
 | 
			
		||||
        .@"app-id" => &server.config.csd_filter_app_ids,
 | 
			
		||||
        .title => &server.config.csd_filter_titles,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const key = args[2];
 | 
			
		||||
    const gop = try map.getOrPut(util.gpa, key);
 | 
			
		||||
    if (gop.found_existing) return;
 | 
			
		||||
    errdefer assert(server.config.csd_filter.remove(args[1]));
 | 
			
		||||
    gop.key_ptr.* = try std.mem.dupe(util.gpa, u8, args[1]);
 | 
			
		||||
    errdefer assert(map.remove(key));
 | 
			
		||||
    gop.key_ptr.* = try std.mem.dupe(util.gpa, u8, key);
 | 
			
		||||
 | 
			
		||||
    csdFilterUpdateViews(args[1], .add);
 | 
			
		||||
    csdFilterUpdateViews(kind, key, .add);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn csdFilterRemove(
 | 
			
		||||
@ -96,23 +103,29 @@ pub fn csdFilterRemove(
 | 
			
		||||
    args: []const [:0]const u8,
 | 
			
		||||
    out: *?[]const u8,
 | 
			
		||||
) Error!void {
 | 
			
		||||
    if (args.len < 2) return Error.NotEnoughArguments;
 | 
			
		||||
    if (args.len > 2) return Error.TooManyArguments;
 | 
			
		||||
    if (args.len < 3) return Error.NotEnoughArguments;
 | 
			
		||||
    if (args.len > 3) return Error.TooManyArguments;
 | 
			
		||||
 | 
			
		||||
    if (server.config.csd_filter.fetchRemove(args[1])) |kv| {
 | 
			
		||||
    const kind = std.meta.stringToEnum(FilterKind, args[1]) orelse return Error.UnknownOption;
 | 
			
		||||
    const map = switch (kind) {
 | 
			
		||||
        .@"app-id" => &server.config.csd_filter_app_ids,
 | 
			
		||||
        .title => &server.config.csd_filter_titles,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const key = args[2];
 | 
			
		||||
    if (map.fetchRemove(key)) |kv| {
 | 
			
		||||
        util.gpa.free(kv.key);
 | 
			
		||||
        csdFilterUpdateViews(args[1], .remove);
 | 
			
		||||
        csdFilterUpdateViews(kind, key, .remove);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn csdFilterUpdateViews(app_id: []const u8, operation: enum { add, remove }) void {
 | 
			
		||||
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);
 | 
			
		||||
        const view_app_id = mem.span(view.getAppId()) orelse continue;
 | 
			
		||||
 | 
			
		||||
        if (mem.eql(u8, app_id, view_app_id)) {
 | 
			
		||||
        const view = @intToPtr(*View, xdg_toplevel_decoration.surface.data);
 | 
			
		||||
        if (viewMatchesPattern(kind, pattern, view)) {
 | 
			
		||||
            const toplevel = view.impl.xdg_toplevel.xdg_surface.role_data.toplevel;
 | 
			
		||||
            switch (operation) {
 | 
			
		||||
                .add => {
 | 
			
		||||
@ -129,3 +142,12 @@ fn csdFilterUpdateViews(app_id: []const u8, operation: enum { add, remove }) voi
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn viewMatchesPattern(kind: FilterKind, pattern: []const u8, view: *View) bool {
 | 
			
		||||
    const p = switch (kind) {
 | 
			
		||||
        .@"app-id" => mem.span(view.getAppId()),
 | 
			
		||||
        .title => mem.span(view.getTitle()),
 | 
			
		||||
    } orelse return false;
 | 
			
		||||
 | 
			
		||||
    return mem.eql(u8, pattern, p);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user