From 546252aecf1dccbf74aaf6f14add63c2c6db938c Mon Sep 17 00:00:00 2001 From: Ben Fiedler Date: Tue, 31 Aug 2021 21:26:17 +0200 Subject: [PATCH] river: Allow floating based on window titles This extends the `float-filter-add` command to allow matching on window titles as well, using a `float-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 floats all windows with the title 'asdf with spaces'. riverctl float-filter-add title 'asdf with spaces' --- doc/riverctl.1.scd | 14 +++++++------- river/Config.zig | 32 ++++++++++++++++++++++++++++---- river/XdgToplevel.zig | 13 ++++--------- river/XwaylandView.zig | 13 ++++--------- river/command/filter.zig | 35 +++++++++++++++++++++++++++-------- 5 files changed, 70 insertions(+), 37 deletions(-) diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index ae6ac48..68cbbb6 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -41,14 +41,14 @@ over the Wayland protocol. *exit* Exit the compositor, terminating the Wayland session. -*float-filter-add* _app-id_ - Add _app-id_ to the float filter list. Views with this _app-id_ - will start floating. Note that this affects only new views, not already - existing ones. +*float-filter-add* *app-id*|*title* _pattern_ + Add a pattern to the float filter list. Note that this affects only new + views, not already existing ones. Title updates are also not taken into + account. -*float-filter-remove* _app-id_ - Remove an _app-id_ from the float filter list. Note that this affects only - new views, not already existing ones. +*float-filter-remove* *app-id*|*title* _pattern_ + Remove an app-id or title from the float filter list. Note that this + affects only new views, not already existing ones. *focus-output* *next*|*previous*|*up*|*right*|*down*|*left* Focus the next or previous output or the closest output in any direction. diff --git a/river/Config.zig b/river/Config.zig index af80a19..113264c 100644 --- a/river/Config.zig +++ b/river/Config.zig @@ -24,6 +24,7 @@ const util = @import("util.zig"); const Server = @import("Server.zig"); const Mode = @import("Mode.zig"); const AttachMode = @import("view_stack.zig").AttachMode; +const View = @import("View.zig"); pub const FocusFollowsCursorMode = enum { disabled, @@ -57,8 +58,9 @@ mode_to_id: std.StringHashMap(usize), /// All user-defined keymap modes, indexed by mode id modes: std.ArrayList(Mode), -/// Set of app_ids which will be started floating -float_filter: std.StringHashMapUnmanaged(void) = .{}, +/// Sets of app_ids and titles which will be started floating +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) = .{}, @@ -119,9 +121,15 @@ pub fn deinit(self: *Self) void { self.modes.deinit(); { - var it = self.float_filter.keyIterator(); + var it = self.float_filter_app_ids.keyIterator(); while (it.next()) |key| util.gpa.free(key.*); - self.float_filter.deinit(util.gpa); + self.float_filter_app_ids.deinit(util.gpa); + } + + { + var it = self.float_filter_titles.keyIterator(); + while (it.next()) |key| util.gpa.free(key.*); + self.float_filter_titles.deinit(util.gpa); } { @@ -132,3 +140,19 @@ pub fn deinit(self: *Self) void { util.gpa.free(self.default_layout_namespace); } + +pub fn shouldFloat(self: Self, view: *View) bool { + if (view.getAppId()) |app_id| { + if (self.float_filter_app_ids.contains(std.mem.span(app_id))) { + return true; + } + } + + if (view.getTitle()) |title| { + if (self.float_filter_titles.contains(std.mem.span(title))) { + return true; + } + } + + return false; +} diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig index dfdf188..b837c0d 100644 --- a/river/XdgToplevel.zig +++ b/river/XdgToplevel.zig @@ -212,15 +212,10 @@ fn handleMap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurfa view.current.float = true; view.pending.float = true; view.pending.box = view.float_box; - } else { - // Make views with app_ids listed in the float filter float - if (toplevel.app_id) |app_id| { - if (server.config.float_filter.contains(mem.span(app_id))) { - view.current.float = true; - view.pending.float = true; - view.pending.box = view.float_box; - } - } + } else if (server.config.shouldFloat(view)) { + view.current.float = true; + view.pending.float = true; + view.pending.box = view.float_box; } // If the toplevel has an app_id which is not configured to use client side diff --git a/river/XwaylandView.zig b/river/XwaylandView.zig index e467dc2..2b428e1 100644 --- a/river/XwaylandView.zig +++ b/river/XwaylandView.zig @@ -196,15 +196,10 @@ fn handleMap(listener: *wl.Listener(*wlr.XwaylandSurface), xwayland_surface: *wl view.current.float = true; view.pending.float = true; view.pending.box = view.float_box; - } else { - // Make views with app_ids listed in the float filter float - if (self.xwayland_surface.class) |app_id| { - if (server.config.float_filter.contains(std.mem.span(app_id))) { - view.current.float = true; - view.pending.float = true; - view.pending.box = view.float_box; - } - } + } else if (server.config.shouldFloat(view)) { + view.current.float = true; + view.pending.float = true; + view.pending.box = view.float_box; } view.map() catch { diff --git a/river/command/filter.zig b/river/command/filter.zig index 4886aed..f8f6143 100644 --- a/river/command/filter.zig +++ b/river/command/filter.zig @@ -27,19 +27,31 @@ const ViewStack = @import("view_stack.zig").ViewStack; const Error = @import("../command.zig").Error; const Seat = @import("../Seat.zig"); +const FilterKind = enum { + @"app-id", + title, +}; + pub fn floatFilterAdd( allocator: *mem.Allocator, seat: *Seat, 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.float_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.float_filter_app_ids, + .title => &server.config.float_filter_titles, + }; + + const key = args[2]; + const gop = try map.getOrPut(util.gpa, key); if (gop.found_existing) return; - errdefer assert(server.config.float_filter.remove(args[1])); - gop.key_ptr.* = try std.mem.dupe(util.gpa, u8, args[1]); + errdefer assert(map.remove(args[1])); + gop.key_ptr.* = try std.mem.dupe(util.gpa, u8, key); } pub fn floatFilterRemove( @@ -48,10 +60,17 @@ pub fn floatFilterRemove( 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.float_filter.fetchRemove(args[1])) |kv| util.gpa.free(kv.key); + const kind = std.meta.stringToEnum(FilterKind, args[1]) orelse return Error.UnknownOption; + const map = switch (kind) { + .@"app-id" => &server.config.float_filter_app_ids, + .title => &server.config.float_filter_titles, + }; + + const key = args[2]; + if (map.fetchRemove(key)) |kv| util.gpa.free(kv.key); } pub fn csdFilterAdd(