aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDoclic <doclic@tutanota.com>2023-10-19 12:58:11 +0200
committerDoclic <doclic@tutanota.com>2023-10-19 12:58:11 +0200
commitbf4154007d7a418571f3d8f09835a351590e3b42 (patch)
tree8e2110948ab444f9a3c037e2ed23ec97a2ceb813
parent206bb2e713a2acc560dfd9a6f04c2961d62fac7e (diff)
downloadriver-bf4154007d7a418571f3d8f09835a351590e3b42.tar.gz
river-bf4154007d7a418571f3d8f09835a351590e3b42.tar.xz
river: add outputs rule
-rw-r--r--completions/bash/riverctl4
-rw-r--r--completions/fish/riverctl.fish6
-rw-r--r--completions/zsh/_riverctl4
m---------deps/zig-wlroots0
-rw-r--r--doc/riverctl.1.scd6
-rw-r--r--river/Config.zig29
-rw-r--r--river/View.zig3
-rw-r--r--river/command/rule.zig48
8 files changed, 77 insertions, 23 deletions
diff --git a/completions/bash/riverctl b/completions/bash/riverctl
index 395c6f2..d964e7b 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"
+ local rule_actions="float no-float ssd csd tag output"
if [ "${COMP_CWORD}" -eq 1 ]
then
OPTS=" \
@@ -65,7 +65,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" ;;
+ "list-rules") OPTS="float ssd tag output" ;;
"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 065d439..cbddafb 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 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'
+complete -c riverctl -x -n '__fish_seen_subcommand_from rule-add' -a 'float no-float ssd csd tag output'
+complete -c riverctl -x -n '__fish_seen_subcommand_from rule-del' -a 'float no-float ssd csd tag output'
+complete -c riverctl -x -n '__fish_seen_subcommand_from list-rules' -a 'float ssd tag output'
# 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 6d2b3b3..d2cf8ee 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)'
+ _arguments '1: :(-app-id -title)' '2: : ' ':: :(float no-float ssd csd tag output)'
;;
- list-rules) _alternative 'arguments:args:(float ssd tag)' ;;
+ list-rules) _alternative 'arguments:args:(float ssd tag output)' ;;
*) return 0 ;;
esac
;;
diff --git a/deps/zig-wlroots b/deps/zig-wlroots
-Subproject 021fb4bbae7ea3806344102763adb63bf194ca7
+Subproject 0f07b2c666125d06529dfc688da4e71bff9a04f
diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index b599a90..6438970 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -276,6 +276,12 @@ matches everything while _\*\*_ and the empty string are invalid.
and existing views.
- *tag*: Set the initial tags of the view. Requires the tags as
an argument. Applies only to new views.
+ - *output*: Set the initial output of the view. Requires the output
+ as an argument. Applies only to new views. The output can be specified
+ either by connector name (such as _HDMI-A-1_, or _DP-2_), or by
+ identifier in the form of _MAKE MODEL SERIAL_, for example for an output
+ with make: _HP Inc._, model: _HP 22w_, and serial: _CNC93720WF_, the
+ identifier would be: _HP Inc. HP 22w CNC93720WF_.
Both *float* and *no-float* rules are added to the same list,
which means that adding a *no-float* rule with the same arguments
diff --git a/river/Config.zig b/river/Config.zig
index 71fe200..2a3fd90 100644
--- a/river/Config.zig
+++ b/river/Config.zig
@@ -17,15 +17,19 @@
const Self = @This();
const std = @import("std");
+const fmt = std.fmt;
const mem = std.mem;
const globber = @import("globber");
const xkb = @import("xkbcommon");
+const server = &@import("main.zig").server;
const util = @import("util.zig");
const Server = @import("Server.zig");
+const Output = @import("Output.zig");
const Mode = @import("Mode.zig");
const RuleList = @import("rule_list.zig").RuleList;
+const View = @import("View.zig");
pub const AttachMode = enum {
top,
@@ -76,6 +80,7 @@ modes: std.ArrayListUnmanaged(Mode),
float_rules: RuleList(bool) = .{},
ssd_rules: RuleList(bool) = .{},
tag_rules: RuleList(u32) = .{},
+output_rules: RuleList([]const u8) = .{},
/// The selected focus_follows_cursor mode
focus_follows_cursor: FocusFollowsCursorMode = .disabled,
@@ -152,9 +157,33 @@ pub fn deinit(self: *Self) void {
self.float_rules.deinit();
self.ssd_rules.deinit();
self.tag_rules.deinit();
+ for (self.output_rules.rules.items) |rule| {
+ util.gpa.free(rule.value);
+ }
+ self.output_rules.deinit();
util.gpa.free(self.default_layout_namespace);
self.keymap.unref();
self.xkb_context.unref();
}
+
+pub fn outputRuleMatch(self: *Self, view: *View) !?*Output {
+ const output_name = self.output_rules.match(view) orelse return null;
+ var it = server.root.active_outputs.iterator(.forward);
+ while (it.next()) |output| {
+ const wlr_output = output.wlr_output;
+ if (mem.eql(u8, output_name, mem.span(wlr_output.name))) return output;
+
+ // This allows matching with "Maker Model Serial" instead of "Connector"
+ const maker = wlr_output.make orelse "Unknown";
+ const model = wlr_output.model orelse "Unknown";
+ const serial = wlr_output.serial orelse "Unknown";
+ const identifier = try fmt.allocPrint(util.gpa, "{s} {s} {s}", .{ maker, model, serial });
+ defer util.gpa.free(identifier);
+
+ if (mem.eql(u8, output_name, identifier)) return output;
+ }
+
+ return null;
+}
diff --git a/river/View.zig b/river/View.zig
index 09236a0..25f44ea 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -492,7 +492,8 @@ pub fn map(view: *Self) !void {
view.pending.ssd = ssd;
}
- if (server.input_manager.defaultSeat().focused_output) |output| {
+ 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);
diff --git a/river/command/rule.zig b/river/command/rule.zig
index a2dc9da..63d2bb9 100644
--- a/river/command/rule.zig
+++ b/river/command/rule.zig
@@ -33,6 +33,7 @@ const Action = enum {
ssd,
csd,
tag,
+ output,
};
pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void {
@@ -49,7 +50,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 => 2,
+ .tag, .output => 2,
};
if (result.args.len > positional_arguments_count) return Error.TooManyArguments;
if (result.args.len < positional_arguments_count) return Error.NotEnoughArguments;
@@ -85,6 +86,15 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
.value = tag,
});
},
+ .output => {
+ const output_name = try util.gpa.dupe(u8, result.args[1]);
+ errdefer util.gpa.free(output_name);
+ try server.config.output_rules.add(.{
+ .app_id_glob = app_id_glob,
+ .title_glob = title_glob,
+ .value = output_name,
+ });
+ },
}
}
@@ -100,29 +110,27 @@ pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
if (result.args.len < 1) return Error.NotEnoughArguments;
const action = std.meta.stringToEnum(Action, result.args[0]) orelse return Error.UnknownOption;
- const app_id_glob = result.flags.@"app-id" orelse "*";
- const title_glob = result.flags.title orelse "*";
+ const rule = .{
+ .app_id_glob = result.flags.@"app-id" orelse "*",
+ .title_glob = result.flags.title orelse "*",
+ };
switch (action) {
.float, .@"no-float" => {
- _ = server.config.float_rules.del(.{
- .app_id_glob = app_id_glob,
- .title_glob = title_glob,
- });
+ _ = server.config.float_rules.del(rule);
},
.ssd, .csd => {
- _ = server.config.ssd_rules.del(.{
- .app_id_glob = app_id_glob,
- .title_glob = title_glob,
- });
+ _ = server.config.ssd_rules.del(rule);
apply_ssd_rules();
server.root.applyPending();
},
.tag => {
- _ = server.config.tag_rules.del(.{
- .app_id_glob = app_id_glob,
- .title_glob = title_glob,
- });
+ _ = server.config.tag_rules.del(rule);
+ },
+ .output => {
+ if (server.config.output_rules.del(rule)) |output_rule| {
+ util.gpa.free(output_rule);
+ }
},
}
}
@@ -144,11 +152,13 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
float,
ssd,
tag,
+ output,
}, 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(),
};
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);
@@ -185,6 +195,14 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
try writer.print("{b}\n", .{rule.value});
}
},
+ .output => {
+ const rules = server.config.output_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("{s}\n", .{rule.value});
+ }
+ },
}
out.* = try buffer.toOwnedSlice();