aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpolykernel <81340136+polykernel@users.noreply.github.com>2023-08-29 17:54:13 -0400
committerpolykernel <81340136+polykernel@users.noreply.github.com>2023-11-08 00:46:55 -0500
commita0ea456ab2c8ea40232d920c2837cd6222c8c0c3 (patch)
tree977688c38a13b6d6870390c76f7a0124d15c66fe
parent18a440b6063db07604fa8626fda893cc77d841dc (diff)
downloadriver-a0ea456ab2c8ea40232d920c2837cd6222c8c0c3.tar.gz
river-a0ea456ab2c8ea40232d920c2837cd6222c8c0c3.tar.xz
river: add position and dimensions rules
This commit adds position and dimensions rules for configuring the initial position and dimensions of views. When a view is not matched by any position rules, it is centered in the avaliable output space matching the current behavior. If the provided position rule places the view outside of the output, the view's position is clamped to the output bounds (with respect to borders). When a view is not matched by any dimensions rules, no default dimensions is set by the server. If the provided dimensions rule exceeds the minimum or maximum width/height constraints of the view, the view's width/height is clamped to the constraints. Position and dimensions rules have no effect if a view is started fullscreen or is not floating. A view must be matched by a float rule in order for them to take effect.
-rw-r--r--completions/bash/riverctl4
-rw-r--r--completions/fish/riverctl.fish4
-rw-r--r--completions/zsh/_riverctl4
-rw-r--r--doc/riverctl.1.scd19
-rw-r--r--river/Config.zig16
-rw-r--r--river/View.zig16
-rw-r--r--river/command/rule.zig53
7 files changed, 104 insertions, 12 deletions
diff --git a/completions/bash/riverctl b/completions/bash/riverctl
index 4b19d4c..c92e001 100644
--- a/completions/bash/riverctl
+++ b/completions/bash/riverctl
@@ -1,6 +1,6 @@
function __riverctl_completion ()
{
- local rule_actions="float no-float ssd csd tag output"
+ local rule_actions="float no-float ssd csd tag output position dimensions"
if [ "${COMP_CWORD}" -eq 1 ]
then
OPTS=" \
@@ -66,7 +66,7 @@ function __riverctl_completion ()
"move"|"snap") OPTS="up down left right" ;;
"resize") OPTS="horizontal vertical" ;;
"rule-add"|"rule-del") OPTS="-app-id -title $rule_actions" ;;
- "list-rules") OPTS="float ssd tag output" ;;
+ "list-rules") OPTS="float ssd tag output position dimensions" ;;
"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 c69b232..5f596b9 100644
--- a/completions/fish/riverctl.fish
+++ b/completions/fish/riverctl.fish
@@ -88,10 +88,10 @@ complete -c riverctl -n '__fish_seen_subcommand_from unmap'
complete -c riverctl -n '__fish_seen_subcommand_from attach-mode' -n '__fish_riverctl_complete_arg 2' -a 'top bottom'
complete -c riverctl -n '__fish_seen_subcommand_from focus-follows-cursor' -n '__fish_riverctl_complete_arg 2' -a 'disabled normal always'
complete -c riverctl -n '__fish_seen_subcommand_from set-cursor-warp' -n '__fish_riverctl_complete_arg 2' -a 'disabled on-output-change on-focus-change'
-complete -c riverctl -n '__fish_seen_subcommand_from list-rules' -n '__fish_riverctl_complete_arg 2' -a 'float ssd tag output'
+complete -c riverctl -n '__fish_seen_subcommand_from list-rules' -n '__fish_riverctl_complete_arg 2' -a 'float ssd tag output position dimensions'
# Options and subcommands for 'rule-add' and 'rule-del'
-set -l rule_actions float no-float ssd csd tag output
+set -l rule_actions float no-float ssd csd tag output position dimensions
complete -c riverctl -n '__fish_seen_subcommand_from rule-add rule-del' -n "not __fish_seen_subcommand_from $rule_actions" -n 'not __fish_seen_argument -o app-id' -o 'app-id' -r
complete -c riverctl -n '__fish_seen_subcommand_from rule-add rule-del' -n "not __fish_seen_subcommand_from $rule_actions" -n 'not __fish_seen_argument -o title' -o 'title' -r
complete -c riverctl -n '__fish_seen_subcommand_from rule-add rule-del' -n "not __fish_seen_subcommand_from $rule_actions" -n 'test (math (count (commandline -opc)) % 2) -eq 0' -a "$rule_actions"
diff --git a/completions/zsh/_riverctl b/completions/zsh/_riverctl
index d2cf8ee..2a50abd 100644
--- a/completions/zsh/_riverctl
+++ b/completions/zsh/_riverctl
@@ -183,9 +183,9 @@ _riverctl()
# In case of a new rule added in river, we just need
# to add it to the third option between '()',
# i.e (float no-float <new-option>)
- _arguments '1: :(-app-id -title)' '2: : ' ':: :(float no-float ssd csd tag output)'
+ _arguments '1: :(-app-id -title)' '2: : ' ':: :(float no-float ssd csd tag output position dimensions)'
;;
- list-rules) _alternative 'arguments:args:(float ssd tag output)' ;;
+ list-rules) _alternative 'arguments:args:(float ssd tag output position dimensions)' ;;
*) return 0 ;;
esac
;;
diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index a418322..803f56c 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -274,11 +274,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* [*-app-id* _glob_|*-title* _glob_] _action_ [_argument_]
+*rule-add* [*-app-id* _glob_|*-title* _glob_] _action_ [_arguments_]
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_.
+ Some actions require one or more _arguments_.
The supported _action_ types are:
@@ -298,6 +298,15 @@ matches everything while _\*\*_ and the empty string are invalid.
with make: _HP Inc._, model: _HP 22w_, and serial: _CNC93720WF_, the
identifier would be: _HP Inc. HP 22w CNC93720WF_. If the make, model, or
serial is unknown, the word "Unknown" is used instead.
+ - *position*: Set the initial position of the view, clamping to the
+ bounds of the output. Requires x and y coordinates of the view as
+ arguments, both of which must be non-negative. Applies only to new views.
+ - *dimensions*: Set the initial dimensions of the view, clamping to the
+ constraints of the view. Requires width and height of the view as
+ arguments, both of which must be non-negative. Applies only to new views.
+ - *fullscreen*: Make the view fullscreen. Applies only to new views.
+ - *no-fullscreen*: Don't make the view fullscreen. 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
@@ -323,10 +332,14 @@ matches everything while _\*\*_ and the empty string are invalid.
wishes of the client and may start the view floating based on simple
heuristics intended to catch popup-like views.
+ If a view is started fullscreen or is not floating, then *position* and
+ *dimensions* rules will have no effect A view must be matched by a *float*
+ rule in order for them to take effect.
+
*rule-del* [*-app-id* _glob_|*-title* _glob_] _action_
Delete a rule created using *rule-add* with the given arguments.
-*list-rules* *float*|*ssd*|*tag*
+*list-rules* *float*|*ssd*|*tag*|*position*|*dimensions*
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 2a3fd90..a765d54 100644
--- a/river/Config.zig
+++ b/river/Config.zig
@@ -55,6 +55,16 @@ pub const HideCursorWhenTypingMode = enum {
enabled,
};
+pub const Position = struct {
+ x: u31,
+ y: u31,
+};
+
+pub const Dimensions = struct {
+ width: u31,
+ height: u31,
+};
+
/// Color of background in RGBA with premultiplied alpha (alpha should only affect nested sessions)
background_color: [4]f32 = [_]f32{ 0.0, 0.16862745, 0.21176471, 1.0 }, // Solarized base03
@@ -81,6 +91,9 @@ float_rules: RuleList(bool) = .{},
ssd_rules: RuleList(bool) = .{},
tag_rules: RuleList(u32) = .{},
output_rules: RuleList([]const u8) = .{},
+position_rules: RuleList(Position) = .{},
+dimensions_rules: RuleList(Dimensions) = .{},
+fullscreen_rules: RuleList(bool) = .{},
/// The selected focus_follows_cursor mode
focus_follows_cursor: FocusFollowsCursorMode = .disabled,
@@ -161,6 +174,9 @@ pub fn deinit(self: *Self) void {
util.gpa.free(rule.value);
}
self.output_rules.deinit();
+ self.position_rules.deinit();
+ self.dimensions_rules.deinit();
+ self.fullscreen_rules.deinit();
util.gpa.free(self.default_layout_namespace);
diff --git a/river/View.zig b/river/View.zig
index 25f44ea..36d1e12 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -494,9 +494,19 @@ pub fn map(view: *Self) !void {
const focused_output = server.input_manager.defaultSeat().focused_output;
if (try server.config.outputRuleMatch(view) orelse focused_output) |output| {
- // Center the initial pending box on the output
- view.pending.box.x = @divTrunc(@max(0, output.usable_box.width - view.pending.box.width), 2);
- view.pending.box.y = @divTrunc(@max(0, output.usable_box.height - view.pending.box.height), 2);
+ if (server.config.position_rules.match(view)) |position| {
+ view.pending.box.x = position.x;
+ view.pending.box.y = position.y;
+ } else {
+ // Center the initial pending box on the output
+ view.pending.box.x = @divTrunc(@max(0, output.usable_box.width - view.pending.box.width), 2);
+ view.pending.box.y = @divTrunc(@max(0, output.usable_box.height - view.pending.box.height), 2);
+ }
+
+ if (server.config.dimensions_rules.match(view)) |dimensions| {
+ view.pending.box.width = dimensions.width;
+ view.pending.box.height = dimensions.height;
+ }
view.pending.tags = blk: {
if (server.config.tag_rules.match(view)) |tags| break :blk tags;
diff --git a/river/command/rule.zig b/river/command/rule.zig
index 63d2bb9..1ccc1ce 100644
--- a/river/command/rule.zig
+++ b/river/command/rule.zig
@@ -34,6 +34,8 @@ const Action = enum {
csd,
tag,
output,
+ position,
+ dimensions,
};
pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void {
@@ -51,6 +53,7 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
const positional_arguments_count: u8 = switch (action) {
.float, .@"no-float", .ssd, .csd => 1,
.tag, .output => 2,
+ .position, .dimensions => 3,
};
if (result.args.len > positional_arguments_count) return Error.TooManyArguments;
if (result.args.len < positional_arguments_count) return Error.NotEnoughArguments;
@@ -95,6 +98,30 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
.value = output_name,
});
},
+ .position => {
+ const x = try fmt.parseInt(u31, result.args[1], 10);
+ const y = try fmt.parseInt(u31, result.args[2], 10);
+ try server.config.position_rules.add(.{
+ .app_id_glob = app_id_glob,
+ .title_glob = title_glob,
+ .value = .{
+ .x = x,
+ .y = y,
+ },
+ });
+ },
+ .dimensions => {
+ const width = try fmt.parseInt(u31, result.args[1], 10);
+ const height = try fmt.parseInt(u31, result.args[2], 10);
+ try server.config.dimensions_rules.add(.{
+ .app_id_glob = app_id_glob,
+ .title_glob = title_glob,
+ .value = .{
+ .width = width,
+ .height = height,
+ },
+ });
+ },
}
}
@@ -132,6 +159,12 @@ pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
util.gpa.free(output_rule);
}
},
+ .position => {
+ _ = server.config.position_rules.del(rule);
+ },
+ .dimensions => {
+ _ = server.config.dimensions_rules.del(rule);
+ },
}
}
@@ -153,12 +186,16 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
ssd,
tag,
output,
+ position,
+ dimensions,
}, 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(),
.output => server.config.output_rules.getMaxGlobLen(),
+ .position => server.config.position_rules.getMaxGlobLen(),
+ .dimensions => server.config.dimensions_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);
@@ -203,6 +240,22 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
try writer.print("{s}\n", .{rule.value});
}
},
+ .position => {
+ const rules = server.config.position_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("{d},{d}\n", .{ rule.value.x, rule.value.y });
+ }
+ },
+ .dimensions => {
+ const rules = server.config.dimensions_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("{d}x{d}\n", .{ rule.value.width, rule.value.height });
+ }
+ },
}
out.* = try buffer.toOwnedSlice();