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();