Allow floating views to appear at the mouse

This commit is contained in:
Alexander Rosenberg 2025-06-29 19:38:18 +09:00
parent 0c095f0c93
commit dc1d5c8418
Signed by: Zander671
GPG Key ID: 5FD0394ADBD72730
7 changed files with 57 additions and 16 deletions

View File

@ -1,6 +1,6 @@
function __riverctl_completion () 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 ] if [ "${COMP_CWORD}" -eq 1 ]
then then
OPTS=" \ OPTS=" \

View File

@ -89,7 +89,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' 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' # 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 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 '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" 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"

View File

@ -202,7 +202,7 @@ _riverctl()
# In case of a new rule added in river, we just need # In case of a new rule added in river, we just need
# to add it to the third option between '()', # to add it to the third option between '()',
# i.e (float no-float <new-option>) # i.e (float no-float <new-option>)
_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)' ;; list-rules) _alternative 'arguments:args:(float ssd tags output position dimensions fullscreen)' ;;
*) return 0 ;; *) return 0 ;;

View File

@ -298,9 +298,13 @@ matches everything while _\*\*_ and the empty string are invalid.
with make: _HP Inc._, model: _HP 22w_, and serial: _CNC93720WF_, the with make: _HP Inc._, model: _HP 22w_, and serial: _CNC93720WF_, the
identifier would be: _HP Inc. HP 22w CNC93720WF_. If the make, model, or identifier would be: _HP Inc. HP 22w CNC93720WF_. If the make, model, or
serial is unknown, the word "Unknown" is used instead. serial is unknown, the word "Unknown" is used instead.
- *position*: Set the initial position of the view, clamping to the - *position*: Set the initial position of the view, clamping to the bounds
bounds of the output. Requires x and y coordinates of the view as of the output. Requires x and y coordinates of the view as arguments, both
arguments, both of which must be non-negative. 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 - *dimensions*: Set the initial dimensions of the view, clamping to the
constraints of the view. Requires width and height of the view as 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. arguments, both of which must be non-negative. Applies only to new views.

View File

@ -58,9 +58,15 @@ pub const HideCursorWhenTypingMode = enum {
enabled, enabled,
}; };
pub const Anchor = enum {
absolute,
mouse,
};
pub const Position = struct { pub const Position = struct {
x: u31, anchor: Anchor,
y: u31, x: i31,
y: i31,
}; };
pub const Dimensions = struct { pub const Dimensions = struct {

View File

@ -681,8 +681,18 @@ pub fn map(view: *View) !void {
server.input_manager.defaultSeat().focused_output; server.input_manager.defaultSeat().focused_output;
if (server.config.rules.position.match(view)) |position| { if (server.config.rules.position.match(view)) |position| {
view.pending.box.x = position.x; var base_x: i31 = 0;
view.pending.box.y = position.y; 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| { } else if (output) |o| {
// Center the initial pending box on the output // Center the initial pending box on the output
view.pending.box.x = @divTrunc(@max(0, o.usable_box.width - view.pending.box.width), 2); view.pending.box.x = @divTrunc(@max(0, o.usable_box.width - view.pending.box.width), 2);

View File

@ -26,6 +26,7 @@ const util = @import("../util.zig");
const Error = @import("../command.zig").Error; const Error = @import("../command.zig").Error;
const Seat = @import("../Seat.zig"); const Seat = @import("../Seat.zig");
const View = @import("../View.zig"); const View = @import("../View.zig");
const Anchor = @import("../Config.zig").Anchor;
const RuleGlobs = @import("../rule_list.zig").RuleGlobs; const RuleGlobs = @import("../rule_list.zig").RuleGlobs;
const Action = enum { const Action = enum {
@ -36,6 +37,7 @@ const Action = enum {
tags, tags,
output, output,
position, position,
@"relative-position",
dimensions, dimensions,
fullscreen, fullscreen,
@"no-fullscreen", @"no-fullscreen",
@ -59,6 +61,7 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
.float, .@"no-float", .ssd, .csd, .fullscreen, .@"no-fullscreen", .tearing, .@"no-tearing" => 1, .float, .@"no-float", .ssd, .csd, .fullscreen, .@"no-fullscreen", .tearing, .@"no-tearing" => 1,
.tags, .output => 2, .tags, .output => 2,
.position, .dimensions => 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.TooManyArguments;
if (result.args.len < positional_arguments_count) return Error.NotEnoughArguments; if (result.args.len < positional_arguments_count) return Error.NotEnoughArguments;
@ -112,14 +115,32 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
}); });
}, },
.position => { .position => {
const x = try fmt.parseInt(u31, result.args[1], 10); const x = try fmt.parseInt(i31, result.args[1], 10);
const y = try fmt.parseInt(u31, result.args[2], 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(.{ try server.config.rules.position.add(.{
.app_id_glob = app_id_glob, .app_id_glob = app_id_glob,
.title_glob = title_glob, .title_glob = title_glob,
.value = .{ .value = .{
.x = x, .anchor = .absolute,
.y = y, .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,
}, },
}); });
}, },
@ -179,7 +200,7 @@ pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
util.gpa.free(output_rule); util.gpa.free(output_rule);
} }
}, },
.position => { .position, .@"relative-position" => {
_ = server.config.rules.position.del(rule); _ = server.config.rules.position.del(rule);
}, },
.dimensions => { .dimensions => {
@ -278,7 +299,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
for (server.config.rules.position.rules.items) |rule| { 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.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 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 }); try writer.print("{s},{d},{d}", .{ @tagName(rule.value.anchor), rule.value.x, rule.value.y });
} }
}, },
.dimensions => { .dimensions => {