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'
This commit is contained in:
Ben Fiedler 2021-08-31 21:26:17 +02:00 committed by Isaac Freund
parent e59c2a73d7
commit 546252aecf
5 changed files with 70 additions and 37 deletions

View file

@ -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.

View file

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

View file

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

View file

@ -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 {

View file

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