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:
parent
e59c2a73d7
commit
546252aecf
5 changed files with 70 additions and 37 deletions
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -212,16 +212,11 @@ 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))) {
|
||||
} 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
|
||||
// decorations, inform it that it is tiled.
|
||||
|
|
|
@ -196,16 +196,11 @@ 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))) {
|
||||
} else if (server.config.shouldFloat(view)) {
|
||||
view.current.float = true;
|
||||
view.pending.float = true;
|
||||
view.pending.box = view.float_box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view.map() catch {
|
||||
std.log.crit("out of memory", .{});
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue