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:
parent
98aed8d47e
commit
5f6428bafe
@ -28,15 +28,15 @@ over the Wayland protocol.
|
|||||||
*close*
|
*close*
|
||||||
Close the focused view.
|
Close the focused view.
|
||||||
|
|
||||||
*csd-filter-add* _app-id_
|
*csd-filter-add* *app-id*|*title* _pattern_
|
||||||
Add _app-id_ to the CSD filter list. Views with this _app-id_ are
|
Add _pattern_ to the CSD filter list. Views with this _pattern_ are told to
|
||||||
told to use client side decoration instead of the default server
|
use client side decoration instead of the default server side decoration.
|
||||||
side decoration. Note that this affects both new views, as well as already
|
Note that this affects new views as well as already existing ones. Title
|
||||||
existing ones.
|
updates are not taken into account.
|
||||||
|
|
||||||
*csd-filter-remove* _app-id_
|
*csd-filter-remove* *app-id*|*title* _pattern_
|
||||||
Remove an _app-id_ from the CSD filter list. Note that this affects both new
|
Remove _pattern_ from the CSD filter list. Note that this affects new views
|
||||||
views, as well as already existing ones.
|
as well as already existing ones.
|
||||||
|
|
||||||
*exit*
|
*exit*
|
||||||
Exit the compositor, terminating the Wayland session.
|
Exit the compositor, terminating the Wayland session.
|
||||||
|
@ -152,8 +152,8 @@ riverctl set-repeat 50 300
|
|||||||
riverctl float-filter-add app-id float
|
riverctl float-filter-add app-id float
|
||||||
riverctl float-filter-add title "popup title with spaces"
|
riverctl float-filter-add title "popup title with spaces"
|
||||||
|
|
||||||
# Set app-ids of views which should use client side decorations
|
# Set app-ids and titles of views which should use client side decorations
|
||||||
riverctl csd-filter-add "gedit"
|
riverctl csd-filter-add app-id "gedit"
|
||||||
|
|
||||||
# Set and exec into the default layout generator, rivertile.
|
# Set and exec into the default layout generator, rivertile.
|
||||||
# River will send the process group of the init executable SIGTERM on exit.
|
# River will send the process group of the init executable SIGTERM on exit.
|
||||||
|
@ -62,8 +62,9 @@ modes: std.ArrayList(Mode),
|
|||||||
float_filter_app_ids: std.StringHashMapUnmanaged(void) = .{},
|
float_filter_app_ids: std.StringHashMapUnmanaged(void) = .{},
|
||||||
float_filter_titles: std.StringHashMapUnmanaged(void) = .{},
|
float_filter_titles: std.StringHashMapUnmanaged(void) = .{},
|
||||||
|
|
||||||
/// Set of app_ids which are allowed to use client side decorations
|
/// Sets of app_ids and titles which are allowed to use client side decorations
|
||||||
csd_filter: std.StringHashMapUnmanaged(void) = .{},
|
csd_filter_app_ids: std.StringHashMapUnmanaged(void) = .{},
|
||||||
|
csd_filter_titles: std.StringHashMapUnmanaged(void) = .{},
|
||||||
|
|
||||||
/// The selected focus_follows_cursor mode
|
/// The selected focus_follows_cursor mode
|
||||||
focus_follows_cursor: FocusFollowsCursorMode = .disabled,
|
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.*);
|
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);
|
util.gpa.free(self.default_layout_namespace);
|
||||||
@ -156,3 +163,19 @@ pub fn shouldFloat(self: Self, view: *View) bool {
|
|||||||
|
|
||||||
return false;
|
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 util = @import("util.zig");
|
||||||
|
|
||||||
const Server = @import("Server.zig");
|
const Server = @import("Server.zig");
|
||||||
|
const View = @import("View.zig");
|
||||||
|
|
||||||
xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1,
|
xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1,
|
||||||
|
|
||||||
@ -62,8 +63,8 @@ fn handleRequestMode(
|
|||||||
) void {
|
) void {
|
||||||
const self = @fieldParentPtr(Self, "request_mode", listener);
|
const self = @fieldParentPtr(Self, "request_mode", listener);
|
||||||
|
|
||||||
const toplevel = self.xdg_toplevel_decoration.surface.role_data.toplevel;
|
const view = @intToPtr(*View, self.xdg_toplevel_decoration.surface.data);
|
||||||
if (toplevel.app_id != null and server.config.csd_filter.contains(mem.span(toplevel.app_id.?))) {
|
if (server.config.csdAllowed(view)) {
|
||||||
_ = self.xdg_toplevel_decoration.setMode(.client_side);
|
_ = self.xdg_toplevel_decoration.setMode(.client_side);
|
||||||
} else {
|
} else {
|
||||||
_ = self.xdg_toplevel_decoration.setMode(.server_side);
|
_ = 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;
|
view.pending.box = view.float_box;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the toplevel has an app_id which is not configured to use client side
|
// If the view has an app_id or title which is not configured to use client
|
||||||
// decorations, inform it that it is tiled.
|
// 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 (server.config.csdAllowed(view)) {
|
||||||
view.draw_borders = false;
|
view.draw_borders = false;
|
||||||
} else {
|
} else {
|
||||||
_ = toplevel.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true });
|
_ = toplevel.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true });
|
||||||
|
@ -79,15 +79,22 @@ pub fn csdFilterAdd(
|
|||||||
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.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;
|
if (gop.found_existing) return;
|
||||||
errdefer assert(server.config.csd_filter.remove(args[1]));
|
errdefer assert(map.remove(key));
|
||||||
gop.key_ptr.* = try std.mem.dupe(util.gpa, u8, args[1]);
|
gop.key_ptr.* = try std.mem.dupe(util.gpa, u8, key);
|
||||||
|
|
||||||
csdFilterUpdateViews(args[1], .add);
|
csdFilterUpdateViews(kind, key, .add);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn csdFilterRemove(
|
pub fn csdFilterRemove(
|
||||||
@ -96,23 +103,29 @@ pub fn csdFilterRemove(
|
|||||||
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.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);
|
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;
|
var decoration_it = server.decoration_manager.decorations.first;
|
||||||
while (decoration_it) |decoration_node| : (decoration_it = decoration_node.next) {
|
while (decoration_it) |decoration_node| : (decoration_it = decoration_node.next) {
|
||||||
const xdg_toplevel_decoration = decoration_node.data.xdg_toplevel_decoration;
|
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;
|
const toplevel = view.impl.xdg_toplevel.xdg_surface.role_data.toplevel;
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
.add => {
|
.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);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user