git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit97edf13573bf560f607b483b51a5a53306546cac
parent6cd771b580
authorLeon Henrik Plickat <[email protected]>
date2023-06-27 02:11
river: add tags rule

 completions/bash/riverctl      |  4 ++--
 completions/fish/riverctl.fish |  6 +++---
 completions/zsh/_riverctl      |  6 +++---
 doc/riverctl.1.scd             |  7 +++++--
 river/Config.zig               |  2 ++
 river/View.zig                 |  1 +
 river/command/rule.zig         | 41 ++++++++++++++++++++++++++++++++++++++---
 7 files changed, 54 insertions(+), 13 deletions(-)

diff --git a/completions/bash/riverctl b/completions/bash/riverctl
index d1de796..f3325d3 100644
--- a/completions/bash/riverctl
+++ b/completions/bash/riverctl
@@ -62,8 +62,8 @@ function __riverctl_completion ()
 			"focus-output"|"focus-view"|"send-to-output"|"swap") OPTS="next previous" ;;
 			"move"|"snap") OPTS="up down left right" ;;
 			"resize") OPTS="horizontal vertical" ;;
-			"rule-add"|"rule-del") OPTS="float no-float ssd csd" ;;
-			"list-rules") OPTS="float ssd" ;;
+			"rule-add"|"rule-del") OPTS="float no-float ssd csd tag" ;;
+			"list-rules") OPTS="float ssd tag" ;;
 			"map") OPTS="-release -repeat -layout" ;;
 			"unmap") OPTS="-release" ;;
 			"attach-mode") OPTS="top bottom" ;;
diff --git a/completions/fish/riverctl.fish b/completions/fish/riverctl.fish
index a207156..05829d0 100644
--- a/completions/fish/riverctl.fish
+++ b/completions/fish/riverctl.fish
@@ -83,9 +83,9 @@ complete -c riverctl -x -n '__fish_seen_subcommand_from unmap'                -a
 complete -c riverctl -x -n '__fish_seen_subcommand_from attach-mode'          -a 'top bottom'
 complete -c riverctl -x -n '__fish_seen_subcommand_from focus-follows-cursor' -a 'disabled normal always'
 complete -c riverctl -x -n '__fish_seen_subcommand_from set-cursor-warp'      -a 'disabled on-output-change on-focus-change'
-complete -c riverctl -x -n '__fish_seen_subcommand_from rule-add'             -a 'float no-float ssd csd'
-complete -c riverctl -x -n '__fish_seen_subcommand_from rule-del'             -a 'float no-float ssd csd'
-complete -c riverctl -x -n '__fish_seen_subcommand_from list-rules'           -a 'float ssd'
+complete -c riverctl -x -n '__fish_seen_subcommand_from rule-add'             -a 'float no-float ssd csd tag'
+complete -c riverctl -x -n '__fish_seen_subcommand_from rule-del'             -a 'float no-float ssd csd tag'
+complete -c riverctl -x -n '__fish_seen_subcommand_from list-rules'           -a 'float ssd tag'
 
 # Subcommands for 'input'
 complete -c riverctl -x -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 2' -a "(__riverctl_list_input_devices)"
diff --git a/completions/zsh/_riverctl b/completions/zsh/_riverctl
index 53b2194..e874c78 100644
--- a/completions/zsh/_riverctl
+++ b/completions/zsh/_riverctl
@@ -183,9 +183,9 @@ _riverctl()
                 focus-follows-cursor) _alternative 'arguments:args:(disabled normal always)' ;;
                 set-cursor-warp) _alternative 'arguments:args:(disabled on-output-change on-focus-change)' ;;
                 hide-cursor) _riverctl_hide_cursor ;;
-                rule-add) _alternative 'arguments:args:(float no-float ssd csd)' ;;
-                rule-del) _alternative 'arguments:args:(float no-float ssd csd)' ;;
-                list-rules) _alternative 'arguments:args:(float ssd)' ;;
+                rule-add) _alternative 'arguments:args:(float no-float ssd csd tag)' ;;
+                rule-del) _alternative 'arguments:args:(float no-float ssd csd tag)' ;;
+                list-rules) _alternative 'arguments:args:(float ssd tag)' ;;
                 *) return 0 ;;
             esac
         ;;
diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index 7f1fcc3..2f452fb 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -258,10 +258,11 @@ For example, _abc_ is matched by _a\*_, _\*a\*_, _\*b\*_, _\*c_, _abc_, and
 _\*_ but not matched by _\*a_, _b\*_, _\*b_, _c\*_, or _ab_. Note that _\*_
 matches everything while _\*\*_ and the empty string are invalid.
 
-*rule-add* _action_ [*-app-id* _glob_|*-title* _glob_]
+*rule-add* _action_ [*-app-id* _glob_|*-title* _glob_] [_argument_]
 	Add a rule that applies an _action_ to views with *app-id* and *title*
 	matched by the respective _glob_. Omitting *-app-id* or *-title*
 	is equivalent to passing *-app-id* _\*_ or *-title* _\*_.
+	Some actions require an _argument_.
 
 	The supported _action_ types are:
 
@@ -272,6 +273,8 @@ matches everything while _\*\*_ and the empty string are invalid.
 	  and existing views.
 	- *csd*: Use client-side decorations for the view. Applies to new
 	  and existing views.
+	- *tag*: Set the initial tags of the view. Requires the tags as
+	  an argument. Applies only to new views.
 
 	Both *float* and *no-float* rules are added to the same list,
 	which means that adding a *no-float* rule with the same arguments
@@ -300,7 +303,7 @@ matches everything while _\*\*_ and the empty string are invalid.
 *rule-del* _action_ [*-app-id* _glob_|*-title* _glob_]
 	Delete a rule created using *rule-add* with the given arguments.
 
-*list-rules* *float*|*ssd*
+*list-rules* *float*|*ssd*|*tag*
 	Print the specified rule list. The output is ordered from most specific
 	to least specific, the same order in which views are checked against
 	when searching for a match. Only the first matching rule in the list
diff --git a/river/Config.zig b/river/Config.zig
index 67b9771..71fe200 100644
--- a/river/Config.zig
+++ b/river/Config.zig
@@ -75,6 +75,7 @@ modes: std.ArrayListUnmanaged(Mode),
 
 float_rules: RuleList(bool) = .{},
 ssd_rules: RuleList(bool) = .{},
+tag_rules: RuleList(u32) = .{},
 
 /// The selected focus_follows_cursor mode
 focus_follows_cursor: FocusFollowsCursorMode = .disabled,
@@ -150,6 +151,7 @@ pub fn deinit(self: *Self) void {
 
     self.float_rules.deinit();
     self.ssd_rules.deinit();
+    self.tag_rules.deinit();
 
     util.gpa.free(self.default_layout_namespace);
 
diff --git a/river/View.zig b/river/View.zig
index 249cf0e..90c4509 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -508,6 +508,7 @@ pub fn map(view: *Self) !void {
         view.pending.box.y = @divTrunc(math.max(0, output.usable_box.height - view.pending.box.height), 2);
 
         view.pending.tags = blk: {
+            if (server.config.tag_rules.match(view)) |tags| break :blk tags;
             const tags = output.pending.tags & server.config.spawn_tagmask;
             break :blk if (tags != 0) tags else output.pending.tags;
         };
diff --git a/river/command/rule.zig b/river/command/rule.zig
index 965014b..773975e 100644
--- a/river/command/rule.zig
+++ b/river/command/rule.zig
@@ -32,6 +32,7 @@ const Action = enum {
     @"no-float",
     ssd,
     csd,
+    tag,
 };
 
 pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void {
@@ -44,9 +45,14 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
         return error.InvalidValue;
     };
 
-    if (result.args.len > 0) return Error.TooManyArguments;
-
     const action = std.meta.stringToEnum(Action, args[1]) orelse return Error.UnknownOption;
+
+    const positional_arguments_count: u8 = switch (action) {
+        .float, .@"no-float", .ssd, .csd => 0,
+        .tag => 1,
+    };
+    if (result.args.len > positional_arguments_count) return Error.TooManyArguments;
+
     const app_id_glob = result.flags.@"app-id" orelse "*";
     const title_glob = result.flags.title orelse "*";
 
@@ -70,6 +76,14 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
             apply_ssd_rules();
             server.root.applyPending();
         },
+        .tag => {
+            const tag = try fmt.parseInt(u32, result.args[0], 10);
+            try server.config.tag_rules.add(.{
+                .app_id_glob = app_id_glob,
+                .title_glob = title_glob,
+                .value = tag,
+            });
+        },
     }
 }
 
@@ -104,6 +118,12 @@ pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
             apply_ssd_rules();
             server.root.applyPending();
         },
+        .tag => {
+            server.config.tag_rules.del(.{
+                .app_id_glob = app_id_glob,
+                .title_glob = title_glob,
+            });
+        },
     }
 }
 
@@ -120,10 +140,15 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
     if (args.len < 2) return error.NotEnoughArguments;
     if (args.len > 2) return error.TooManyArguments;
 
-    const list = std.meta.stringToEnum(enum { float, ssd }, args[1]) orelse return Error.UnknownOption;
+    const list = std.meta.stringToEnum(enum {
+        float,
+        ssd,
+        tag,
+    }, args[1]) orelse return Error.UnknownOption;
     const max_glob_len = switch (list) {
         .float => server.config.float_rules.getMaxGlobLen(),
         .ssd => server.config.ssd_rules.getMaxGlobLen(),
+        .tag => server.config.tag_rules.getMaxGlobLen(),
     };
     const app_id_column_max = 2 + @max("app-id".len, max_glob_len.app_id);
     const title_column_max = 2 + @max("title".len, max_glob_len.title);
@@ -140,6 +165,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
             const rules = switch (list) {
                 .float => server.config.float_rules.rules.items,
                 .ssd => server.config.ssd_rules.rules.items,
+                else => unreachable,
             };
             for (rules) |rule| {
                 try fmt.formatBuf(rule.title_glob, .{ .width = title_column_max, .alignment = .Left }, writer);
@@ -147,9 +173,18 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
                 try writer.print("{s}\n", .{switch (list) {
                     .float => if (rule.value) "float" else "no-float",
                     .ssd => if (rule.value) "ssd" else "csd",
+                    else => unreachable,
                 }});
             }
         },
+        .tag => {
+            const rules = server.config.tag_rules.rules.items;
+            for (rules) |rule| {
+                try fmt.formatBuf(rule.title_glob, .{ .width = title_column_max, .alignment = .Left }, writer);
+                try fmt.formatBuf(rule.app_id_glob, .{ .width = app_id_column_max, .alignment = .Left }, writer);
+                try writer.print("{b}\n", .{rule.value});
+            }
+        },
     }
 
     out.* = buffer.toOwnedSlice();