From d66decb7c4b72d1e34b2b6f643fd6a7542ff24e3 Mon Sep 17 00:00:00 2001 From: Alexander Rosenberg Date: Fri, 1 Nov 2024 22:08:20 -0700 Subject: [PATCH] Open view relative to mouse, take 2 --- completions/bash/riverctl | 2 +- completions/fish/riverctl.fish | 2 +- completions/zsh/_riverctl | 2 +- doc/riverctl.1.scd | 8 +++-- river/Config.zig | 16 ++++----- river/View.zig | 19 +++++----- river/command/rule.zig | 65 ++++++++++++++++++---------------- 7 files changed, 58 insertions(+), 56 deletions(-) diff --git a/completions/bash/riverctl b/completions/bash/riverctl index a889d4c..35e46df 100644 --- a/completions/bash/riverctl +++ b/completions/bash/riverctl @@ -1,6 +1,6 @@ function __riverctl_completion () { - local rule_actions="float no-float ssd csd tags output position dimensions fullscreen no-fullscreen" + local rule_actions="float no-float ssd csd tags output position relative-position dimensions fullscreen no-fullscreen" if [ "${COMP_CWORD}" -eq 1 ] then OPTS=" \ diff --git a/completions/fish/riverctl.fish b/completions/fish/riverctl.fish index 50458b4..d443f92 100644 --- a/completions/fish/riverctl.fish +++ b/completions/fish/riverctl.fish @@ -94,7 +94,7 @@ complete -c riverctl -n '__fish_seen_subcommand_from set-cursor-warp' complete -c riverctl -n '__fish_seen_subcommand_from list-rules' -n '__fish_riverctl_complete_arg 2' -a 'float ssd tags output position dimensions fullscreen' # Options and subcommands for 'rule-add' and 'rule-del' -set -l rule_actions float no-float ssd csd tags output position dimensions fullscreen no-fullscreen +set -l rule_actions float no-float ssd csd tags output position relative-position dimensions fullscreen no-fullscreen 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 ad372d3..e597dd3 100644 --- a/completions/zsh/_riverctl +++ b/completions/zsh/_riverctl @@ -207,7 +207,7 @@ _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 tags output position dimensions fullscreen no-fullscreen)' + _arguments '1: :(-app-id -title)' '2: : ' ':: :(float no-float ssd csd tags output position relative-position dimensions fullscreen no-fullscreen)' ;; list-rules) _alternative 'arguments:args:(float ssd tags output position dimensions fullscreen)' ;; *) return 0 ;; diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index 34d2435..6112dcb 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -300,9 +300,11 @@ matches everything while _\*\*_ and the empty string are invalid. 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. Optionally, the string "mouse" can appear - as the only argument. In this case, the view will appear at the mouse's - position. Applies only to new views. + of which must be non-negative. Applies only to new views. + - *relative-position*: Set the position of the view relative to + something. Requires the anchor and the x and y coordinates of the + view. The coordinates are either positive or negative numbers that are + relative to the anchor. 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. diff --git a/river/Config.zig b/river/Config.zig index 8326688..dac8ae1 100644 --- a/river/Config.zig +++ b/river/Config.zig @@ -58,19 +58,15 @@ pub const HideCursorWhenTypingMode = enum { enabled, }; -pub const PositionType = enum { +pub const Anchor = enum { absolute, - at_mouse, + mouse, }; pub const Position = struct { - x: u31, - y: u31, -}; - -pub const FloatPosition = union(PositionType) { - absolute: Position, - at_mouse, + anchor: Anchor, + x: i31, + y: i31, }; pub const Dimensions = struct { @@ -108,7 +104,7 @@ rules: struct { ssd: RuleList(bool) = .{}, tags: RuleList(u32) = .{}, output: RuleList([]const u8) = .{}, - position: RuleList(FloatPosition) = .{}, + position: RuleList(Position) = .{}, dimensions: RuleList(Dimensions) = .{}, fullscreen: RuleList(bool) = .{}, tearing: RuleList(bool) = .{}, diff --git a/river/View.zig b/river/View.zig index dcf8083..75c0e9f 100644 --- a/river/View.zig +++ b/river/View.zig @@ -678,17 +678,18 @@ pub fn map(view: *View) !void { server.input_manager.defaultSeat().focused_output; if (server.config.rules.position.match(view)) |position| { - switch (position) { - .absolute => |pos| { - view.pending.box.x = pos.x; - view.pending.box.y = pos.y; - }, - .at_mouse => { - const cursor = server.input_manager.defaultSeat().cursor.wlr_cursor; - view.pending.box.x = @as(c_int, @intFromFloat(cursor.x)); - view.pending.box.y = @as(c_int, @intFromFloat(cursor.y)); + var base_x: i31 = 0; + var base_y: i31 = 0; + switch (position.anchor) { + .absolute => {}, + .mouse => { + const cursor = server.input_manager.defaultSeat().wlr_seat.pointer_state; + base_x = @intCast(@as(i31, @intFromFloat(cursor.sx))); + base_y = @intCast(@as(i31, @intFromFloat(cursor.sy))); }, } + view.pending.box.x = base_x + position.x; + view.pending.box.y = base_y + position.y; } else if (output) |o| { // Center the initial pending box on the output view.pending.box.x = @divTrunc(@max(0, o.usable_box.width - view.pending.box.width), 2); diff --git a/river/command/rule.zig b/river/command/rule.zig index 69fbb52..6891618 100644 --- a/river/command/rule.zig +++ b/river/command/rule.zig @@ -26,6 +26,7 @@ const util = @import("../util.zig"); const Error = @import("../command.zig").Error; const Seat = @import("../Seat.zig"); const View = @import("../View.zig"); +const Anchor = @import("../Config.zig").Anchor; const Action = enum { float, @@ -35,6 +36,7 @@ const Action = enum { tags, output, position, + @"relative-position", dimensions, fullscreen, @"no-fullscreen", @@ -54,17 +56,11 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void const action = std.meta.stringToEnum(Action, result.args[0]) orelse return Error.UnknownOption; - var pos_is_mouse = false; const positional_arguments_count: u8 = switch (action) { .float, .@"no-float", .ssd, .csd, .fullscreen, .@"no-fullscreen", .tearing, .@"no-tearing" => 1, .tags, .output => 2, - .dimensions => 3, - .position => blk: { - if (result.args.len >= 2 and std.mem.eql(u8, result.args[1], "mouse")) { - pos_is_mouse = true; - break :blk 2; - } else break :blk 3; - }, + .position, .dimensions => 3, + .@"relative-position" => 4, }; if (result.args.len > positional_arguments_count) return Error.TooManyArguments; if (result.args.len < positional_arguments_count) return Error.NotEnoughArguments; @@ -118,24 +114,34 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void }); }, .position => { - if (pos_is_mouse) { - try server.config.rules.position.add(.{ - .app_id_glob = app_id_glob, - .title_glob = title_glob, - .value = .at_mouse, - }); - } else { - const x = try fmt.parseInt(u31, result.args[1], 10); - const y = try fmt.parseInt(u31, result.args[2], 10); - try server.config.rules.position.add(.{ - .app_id_glob = app_id_glob, - .title_glob = title_glob, - .value = .{ .absolute = .{ - .x = x, - .y = y, - } }, - }); - } + const x = try fmt.parseInt(i31, result.args[1], 10); + const y = try fmt.parseInt(i31, result.args[2], 10); + if (x < 0 or y < 0) return Error.OutOfBounds; + try server.config.rules.position.add(.{ + .app_id_glob = app_id_glob, + .title_glob = title_glob, + .value = .{ + .anchor = .absolute, + .x = @intCast(x), + .y = @intCast(y), + }, + }); + }, + .@"relative-position" => { + const anchor = std.meta.stringToEnum(Anchor, result.args[1]) orelse return Error.UnknownOption; + // force the use of the normal position command for absolute positions + if (anchor == .absolute) return Error.UnknownOption; + const x_off = try fmt.parseInt(i31, result.args[2], 10); + const y_off = try fmt.parseInt(i31, result.args[3], 10); + try server.config.rules.position.add(.{ + .app_id_glob = app_id_glob, + .title_glob = title_glob, + .value = .{ + .anchor = anchor, + .x = x_off, + .y = y_off, + }, + }); }, .dimensions => { const width = try fmt.parseInt(u31, result.args[1], 10); @@ -193,7 +199,7 @@ pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void util.gpa.free(output_rule); } }, - .position => { + .position, .@"relative-position" => { _ = server.config.rules.position.del(rule); }, .dimensions => { @@ -292,10 +298,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error! for (server.config.rules.position.rules.items) |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); - switch (rule.value) { - .absolute => |pos| try writer.print("{d},{d}\n", .{ pos.x, pos.y }), - .at_mouse => try writer.print("mouse\n", .{}), - } + try writer.print("{s},{d},{d}", .{ @tagName(rule.value.anchor), rule.value.x, rule.value.y }); } }, .dimensions => {