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*
|
||||||
Exit the compositor, terminating the Wayland session.
|
Exit the compositor, terminating the Wayland session.
|
||||||
|
|
||||||
*float-filter-add* _app-id_
|
*float-filter-add* *app-id*|*title* _pattern_
|
||||||
Add _app-id_ to the float filter list. Views with this _app-id_
|
Add a pattern to the float filter list. Note that this affects only new
|
||||||
will start floating. Note that this affects only new views, not already
|
views, not already existing ones. Title updates are also not taken into
|
||||||
existing ones.
|
account.
|
||||||
|
|
||||||
*float-filter-remove* _app-id_
|
*float-filter-remove* *app-id*|*title* _pattern_
|
||||||
Remove an _app-id_ from the float filter list. Note that this affects only
|
Remove an app-id or title from the float filter list. Note that this
|
||||||
new views, not already existing ones.
|
affects only new views, not already existing ones.
|
||||||
|
|
||||||
*focus-output* *next*|*previous*|*up*|*right*|*down*|*left*
|
*focus-output* *next*|*previous*|*up*|*right*|*down*|*left*
|
||||||
Focus the next or previous output or the closest output in any direction.
|
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 Server = @import("Server.zig");
|
||||||
const Mode = @import("Mode.zig");
|
const Mode = @import("Mode.zig");
|
||||||
const AttachMode = @import("view_stack.zig").AttachMode;
|
const AttachMode = @import("view_stack.zig").AttachMode;
|
||||||
|
const View = @import("View.zig");
|
||||||
|
|
||||||
pub const FocusFollowsCursorMode = enum {
|
pub const FocusFollowsCursorMode = enum {
|
||||||
disabled,
|
disabled,
|
||||||
|
@ -57,8 +58,9 @@ mode_to_id: std.StringHashMap(usize),
|
||||||
/// All user-defined keymap modes, indexed by mode id
|
/// All user-defined keymap modes, indexed by mode id
|
||||||
modes: std.ArrayList(Mode),
|
modes: std.ArrayList(Mode),
|
||||||
|
|
||||||
/// Set of app_ids which will be started floating
|
/// Sets of app_ids and titles which will be started floating
|
||||||
float_filter: std.StringHashMapUnmanaged(void) = .{},
|
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
|
/// Set of app_ids which are allowed to use client side decorations
|
||||||
csd_filter: std.StringHashMapUnmanaged(void) = .{},
|
csd_filter: std.StringHashMapUnmanaged(void) = .{},
|
||||||
|
@ -119,9 +121,15 @@ pub fn deinit(self: *Self) void {
|
||||||
self.modes.deinit();
|
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.*);
|
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);
|
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.current.float = true;
|
||||||
view.pending.float = true;
|
view.pending.float = true;
|
||||||
view.pending.box = view.float_box;
|
view.pending.box = view.float_box;
|
||||||
} else {
|
} else if (server.config.shouldFloat(view)) {
|
||||||
// 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.current.float = true;
|
||||||
view.pending.float = true;
|
view.pending.float = true;
|
||||||
view.pending.box = view.float_box;
|
view.pending.box = view.float_box;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the toplevel has an app_id which is not configured to use client side
|
// If the toplevel has an app_id which is not configured to use client side
|
||||||
// decorations, inform it that it is tiled.
|
// 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.current.float = true;
|
||||||
view.pending.float = true;
|
view.pending.float = true;
|
||||||
view.pending.box = view.float_box;
|
view.pending.box = view.float_box;
|
||||||
} else {
|
} else if (server.config.shouldFloat(view)) {
|
||||||
// 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.current.float = true;
|
||||||
view.pending.float = true;
|
view.pending.float = true;
|
||||||
view.pending.box = view.float_box;
|
view.pending.box = view.float_box;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
view.map() catch {
|
view.map() catch {
|
||||||
std.log.crit("out of memory", .{});
|
std.log.crit("out of memory", .{});
|
||||||
|
|
|
@ -27,19 +27,31 @@ const ViewStack = @import("view_stack.zig").ViewStack;
|
||||||
const Error = @import("../command.zig").Error;
|
const Error = @import("../command.zig").Error;
|
||||||
const Seat = @import("../Seat.zig");
|
const Seat = @import("../Seat.zig");
|
||||||
|
|
||||||
|
const FilterKind = enum {
|
||||||
|
@"app-id",
|
||||||
|
title,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn floatFilterAdd(
|
pub fn floatFilterAdd(
|
||||||
allocator: *mem.Allocator,
|
allocator: *mem.Allocator,
|
||||||
seat: *Seat,
|
seat: *Seat,
|
||||||
args: []const [:0]const u8,
|
args: []const [:0]const u8,
|
||||||
out: *?[]const u8,
|
out: *?[]const u8,
|
||||||
) Error!void {
|
) Error!void {
|
||||||
if (args.len < 2) return Error.NotEnoughArguments;
|
if (args.len < 3) return Error.NotEnoughArguments;
|
||||||
if (args.len > 2) return Error.TooManyArguments;
|
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;
|
if (gop.found_existing) return;
|
||||||
errdefer assert(server.config.float_filter.remove(args[1]));
|
errdefer assert(map.remove(args[1]));
|
||||||
gop.key_ptr.* = try std.mem.dupe(util.gpa, u8, args[1]);
|
gop.key_ptr.* = try std.mem.dupe(util.gpa, u8, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn floatFilterRemove(
|
pub fn floatFilterRemove(
|
||||||
|
@ -48,10 +60,17 @@ pub fn floatFilterRemove(
|
||||||
args: []const [:0]const u8,
|
args: []const [:0]const u8,
|
||||||
out: *?[]const u8,
|
out: *?[]const u8,
|
||||||
) Error!void {
|
) Error!void {
|
||||||
if (args.len < 2) return Error.NotEnoughArguments;
|
if (args.len < 3) return Error.NotEnoughArguments;
|
||||||
if (args.len > 2) return Error.TooManyArguments;
|
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(
|
pub fn csdFilterAdd(
|
||||||
|
|
Loading…
Reference in a new issue