From bf4154007d7a418571f3d8f09835a351590e3b42 Mon Sep 17 00:00:00 2001 From: Doclic Date: Thu, 19 Oct 2023 12:58:11 +0200 Subject: [PATCH] river: add outputs rule --- completions/bash/riverctl | 4 +-- completions/fish/riverctl.fish | 6 ++--- completions/zsh/_riverctl | 4 +-- deps/zig-wlroots | 2 +- doc/riverctl.1.scd | 6 +++++ river/Config.zig | 29 ++++++++++++++++++++ river/View.zig | 3 ++- river/command/rule.zig | 48 +++++++++++++++++++++++----------- 8 files changed, 78 insertions(+), 24 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 ) - _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 index 021fb4b..0f07b2c 160000 --- a/deps/zig-wlroots +++ b/deps/zig-wlroots @@ -1 +1 @@ -Subproject commit 021fb4bbae7ea3806344102763adb63bf194ca7c +Subproject commit 0f07b2c666125d06529dfc688da4e71bff9a04f9 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();