diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index 3a3ac1b..34d2435 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -298,9 +298,11 @@ 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. + - *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. - *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 f61c82c..8326688 100644 --- a/river/Config.zig +++ b/river/Config.zig @@ -58,11 +58,21 @@ pub const HideCursorWhenTypingMode = enum { enabled, }; +pub const PositionType = enum { + absolute, + at_mouse, +}; + pub const Position = struct { x: u31, y: u31, }; +pub const FloatPosition = union(PositionType) { + absolute: Position, + at_mouse, +}; + pub const Dimensions = struct { width: u31, height: u31, @@ -98,7 +108,7 @@ rules: struct { ssd: RuleList(bool) = .{}, tags: RuleList(u32) = .{}, output: RuleList([]const u8) = .{}, - position: RuleList(Position) = .{}, + position: RuleList(FloatPosition) = .{}, dimensions: RuleList(Dimensions) = .{}, fullscreen: RuleList(bool) = .{}, tearing: RuleList(bool) = .{}, diff --git a/river/View.zig b/river/View.zig index 57a8332..dcf8083 100644 --- a/river/View.zig +++ b/river/View.zig @@ -678,8 +678,17 @@ pub fn map(view: *View) !void { server.input_manager.defaultSeat().focused_output; if (server.config.rules.position.match(view)) |position| { - view.pending.box.x = position.x; - view.pending.box.y = position.y; + 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)); + }, + } } 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 5b64400..69fbb52 100644 --- a/river/command/rule.zig +++ b/river/command/rule.zig @@ -54,10 +54,17 @@ 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, - .position, .dimensions => 3, + .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; + }, }; if (result.args.len > positional_arguments_count) return Error.TooManyArguments; if (result.args.len < positional_arguments_count) return Error.NotEnoughArguments; @@ -111,16 +118,24 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void }); }, .position => { - 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 = .{ - .x = x, - .y = y, - }, - }); + 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, + } }, + }); + } }, .dimensions => { const width = try fmt.parseInt(u31, result.args[1], 10); @@ -277,7 +292,10 @@ 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); - try writer.print("{d},{d}\n", .{ rule.value.x, rule.value.y }); + switch (rule.value) { + .absolute => |pos| try writer.print("{d},{d}\n", .{ pos.x, pos.y }), + .at_mouse => try writer.print("mouse\n", .{}), + } } }, .dimensions => {