aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Fiedler <git@bfiedler.ch>2021-09-06 15:28:05 +0200
committerIsaac Freund <ifreund@ifreund.xyz>2021-09-07 12:30:53 +0000
commit5f6428bafe41da2c5df6c6cb12ff65c225260322 (patch)
tree11dd47b2b4e3f8271fc7bbfef3071ff6926f4759
parent98aed8d47e7f5dfc3fef6290deb18e2fac01a46d (diff)
downloadriver-5f6428bafe41da2c5df6c6cb12ff65c225260322.tar.gz
river-5f6428bafe41da2c5df6c6cb12ff65c225260322.tar.xz
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'
-rw-r--r--doc/riverctl.1.scd18
-rwxr-xr-xexample/init4
-rw-r--r--river/Config.zig31
-rw-r--r--river/Decoration.zig5
-rw-r--r--river/XdgToplevel.zig6
-rw-r--r--river/command/filter.zig50
6 files changed, 80 insertions, 34 deletions
diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index 68cbbb6..dcb2148 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -28,15 +28,15 @@ over the Wayland protocol.
*close*
Close the focused view.
-*csd-filter-add* _app-id_
- Add _app-id_ to the CSD filter list. Views with this _app-id_ are
- told to use client side decoration instead of the default server
- side decoration. Note that this affects both new views, as well as already
- existing ones.
-
-*csd-filter-remove* _app-id_
- Remove an _app-id_ from the CSD filter list. Note that this affects both new
- views, as well as already existing ones.
+*csd-filter-add* *app-id*|*title* _pattern_
+ Add _pattern_ to the CSD filter list. Views with this _pattern_ are told to
+ use client side decoration instead of the default server side decoration.
+ Note that this affects new views as well as already existing ones. Title
+ updates are not taken into account.
+
+*csd-filter-remove* *app-id*|*title* _pattern_
+ Remove _pattern_ from the CSD filter list. Note that this affects new views
+ as well as already existing ones.
*exit*
Exit the compositor, terminating the Wayland session.
diff --git a/example/init b/example/init
index d3b9dc3..7ddd8eb 100755
--- a/example/init
+++ b/example/init
@@ -152,8 +152,8 @@ riverctl set-repeat 50 300
riverctl float-filter-add app-id float
riverctl float-filter-add title "popup title with spaces"
-# Set app-ids of views which should use client side decorations
-riverctl csd-filter-add "gedit"
+# Set app-ids and titles of views which should use client side decorations
+riverctl csd-filter-add app-id "gedit"
# Set and exec into the default layout generator, rivertile.
# River will send the process group of the init executable SIGTERM on exit.
diff --git a/river/Config.zig b/river/Config.zig
index 113264c..56d2bbe 100644
--- a/river/Config.zig
+++ b/river/Config.zig
@@ -62,8 +62,9 @@ modes: std.ArrayList(Mode),
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) = .{},
+/// Sets of app_ids and titles which are allowed to use client side decorations
+csd_filter_app_ids: std.StringHashMapUnmanaged(void) = .{},
+csd_filter_titles: std.StringHashMapUnmanaged(void) = .{},
/// The selected focus_follows_cursor mode
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.*);
- 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);
@@ -156,3 +163,19 @@ pub fn shouldFloat(self: Self, view: *View) bool {
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;
+}
diff --git a/river/Decoration.zig b/river/Decoration.zig
index 4159bc6..45263ac 100644
--- a/river/Decoration.zig
+++ b/river/Decoration.zig
@@ -26,6 +26,7 @@ const server = &@import("main.zig").server;
const util = @import("util.zig");
const Server = @import("Server.zig");
+const View = @import("View.zig");
xdg_toplevel_decoration: *wlr.XdgToplevelDecorationV1,
@@ -62,8 +63,8 @@ fn handleRequestMode(
) void {
const self = @fieldParentPtr(Self, "request_mode", listener);
- const toplevel = self.xdg_toplevel_decoration.surface.role_data.toplevel;
- if (toplevel.app_id != null and server.config.csd_filter.contains(mem.span(toplevel.app_id.?))) {
+ const view = @intToPtr(*View, self.xdg_toplevel_decoration.surface.data);
+ if (server.config.csdAllowed(view)) {
_ = self.xdg_toplevel_decoration.setMode(.client_side);
} else {
_ = self.xdg_toplevel_decoration.setMode(.server_side);
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index b837c0d..1606e6a 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -218,9 +218,9 @@ fn handleMap(listener: *wl.Listener(*wlr.XdgSurface), xdg_surface: *wlr.XdgSurfa
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.
- if (toplevel.app_id != null and server.config.csd_filter.contains(mem.span(toplevel.app_id.?))) {
+ // If the view has an app_id or title which is not configured to use client
+ // side decorations, inform it that it is tiled.
+ if (server.config.csdAllowed(view)) {
view.draw_borders = false;
} else {
_ = toplevel.setTiled(.{ .top = true, .bottom = true, .left = true, .right = true });
diff --git a/river/command/filter.zig b/river/command/filter.zig
index 6d6ac35..298b56d 100644
--- a/river/command/filter.zig
+++ b/river/command/filter.zig
@@ -79,15 +79,22 @@ pub fn csdFilterAdd(
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 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 gop = try server.config.csd_filter.getOrPut(util.gpa, args[1]);
+ const key = args[2];
+ const gop = try map.getOrPut(util.gpa, key);
if (gop.found_existing) return;
- errdefer assert(server.config.csd_filter.remove(args[1]));
- gop.key_ptr.* = try std.mem.dupe(util.gpa, u8, args[1]);
+ errdefer assert(map.remove(key));
+ gop.key_ptr.* = try std.mem.dupe(util.gpa, u8, key);
- csdFilterUpdateViews(args[1], .add);
+ csdFilterUpdateViews(kind, key, .add);
}
pub fn csdFilterRemove(
@@ -96,23 +103,29 @@ pub fn csdFilterRemove(
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 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,
+ };
- if (server.config.csd_filter.fetchRemove(args[1])) |kv| {
+ const key = args[2];
+ if (map.fetchRemove(key)) |kv| {
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;
while (decoration_it) |decoration_node| : (decoration_it = decoration_node.next) {
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;
switch (operation) {
.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);
+}