Add cursor warp option

This commit is contained in:
Alexander Rosenberg 2024-11-02 08:00:50 -07:00
parent d66decb7c4
commit 5080f07724
Signed by: Zander671
GPG Key ID: 5FD0394ADBD72730
8 changed files with 55 additions and 18 deletions

View File

@ -18,12 +18,11 @@
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
/// Validate a glob, returning error.InvalidGlob if it is empty, "**" or has a /// Validate a glob, returning error.InvalidGlob if is "**" or has a '*'
/// '*' at any position other than the first and/or last byte. /// at any position other than the first and/or last byte.
pub fn validate(glob: []const u8) error{InvalidGlob}!void { pub fn validate(glob: []const u8) error{InvalidGlob}!void {
switch (glob.len) { switch (glob.len) {
0 => return error.InvalidGlob, 0, 1 => {},
1 => {},
2 => if (glob[0] == '*' and glob[1] == '*') return error.InvalidGlob, 2 => if (glob[0] == '*' and glob[1] == '*') return error.InvalidGlob,
else => if (mem.indexOfScalar(u8, glob[1 .. glob.len - 1], '*') != null) { else => if (mem.indexOfScalar(u8, glob[1 .. glob.len - 1], '*') != null) {
return error.InvalidGlob; return error.InvalidGlob;
@ -34,6 +33,7 @@ pub fn validate(glob: []const u8) error{InvalidGlob}!void {
test validate { test validate {
const testing = std.testing; const testing = std.testing;
try validate("");
try validate("*"); try validate("*");
try validate("a"); try validate("a");
try validate("*a"); try validate("*a");
@ -48,7 +48,6 @@ test validate {
try validate("abc*"); try validate("abc*");
try validate("*abc*"); try validate("*abc*");
try testing.expectError(error.InvalidGlob, validate(""));
try testing.expectError(error.InvalidGlob, validate("**")); try testing.expectError(error.InvalidGlob, validate("**"));
try testing.expectError(error.InvalidGlob, validate("***")); try testing.expectError(error.InvalidGlob, validate("***"));
try testing.expectError(error.InvalidGlob, validate("a*c")); try testing.expectError(error.InvalidGlob, validate("a*c"));
@ -67,7 +66,9 @@ pub fn match(s: []const u8, glob: []const u8) bool {
validate(glob) catch unreachable; validate(glob) catch unreachable;
} }
if (glob.len == 1) { if (glob.len == 0) {
return s.len == 0;
} else if (glob.len == 1) {
return glob[0] == '*' or mem.eql(u8, s, glob); return glob[0] == '*' or mem.eql(u8, s, glob);
} }
@ -89,6 +90,9 @@ test match {
const testing = std.testing; const testing = std.testing;
try testing.expect(match("", "*")); try testing.expect(match("", "*"));
try testing.expect(match("", ""));
try testing.expect(!match("a", ""));
try testing.expect(!match("", "a"));
try testing.expect(match("a", "*")); try testing.expect(match("a", "*"));
try testing.expect(match("a", "*a*")); try testing.expect(match("a", "*a*"));
@ -165,8 +169,10 @@ pub fn order(a: []const u8, b: []const u8) std.math.Order {
return .lt; return .lt;
} }
const count_a = @as(u2, @intFromBool(a[0] == '*')) + @intFromBool(a[a.len - 1] == '*'); const count_a = if (a.len != 0) @as(u2, @intFromBool(a[0] == '*')) +
const count_b = @as(u2, @intFromBool(b[0] == '*')) + @intFromBool(b[b.len - 1] == '*'); @intFromBool(a[a.len - 1] == '*') else 0;
const count_b = if (b.len != 0) @as(u2, @intFromBool(b[0] == '*')) +
@intFromBool(b[b.len - 1] == '*') else 0;
if (count_a == 0 and count_b == 0) { if (count_a == 0 and count_b == 0) {
return .eq; return .eq;
@ -182,6 +188,7 @@ test order {
const testing = std.testing; const testing = std.testing;
const Order = std.math.Order; const Order = std.math.Order;
try testing.expectEqual(Order.eq, order("", ""));
try testing.expectEqual(Order.eq, order("*", "*")); try testing.expectEqual(Order.eq, order("*", "*"));
try testing.expectEqual(Order.eq, order("*a*", "*b*")); try testing.expectEqual(Order.eq, order("*a*", "*b*"));
try testing.expectEqual(Order.eq, order("a*", "*b")); try testing.expectEqual(Order.eq, order("a*", "*b"));
@ -204,6 +211,7 @@ test order {
"bababab", "bababab",
"b", "b",
"a", "a",
"",
}; };
for (descending, 0..) |a, i| { for (descending, 0..) |a, i| {

View File

@ -1,6 +1,6 @@
function __riverctl_completion () function __riverctl_completion ()
{ {
local rule_actions="float no-float ssd csd tags output position relative-position dimensions fullscreen no-fullscreen" local rule_actions="float no-float ssd csd tags output position relative-position dimensions fullscreen no-fullscreen warp no-warp"
if [ "${COMP_CWORD}" -eq 1 ] if [ "${COMP_CWORD}" -eq 1 ]
then then
OPTS=" \ OPTS=" \

View File

@ -91,10 +91,10 @@ complete -c riverctl -n '__fish_seen_subcommand_from default-attach-mode'
complete -c riverctl -n '__fish_seen_subcommand_from output-attach-mode' -n '__fish_riverctl_complete_arg 2' -a 'top bottom above below after' complete -c riverctl -n '__fish_seen_subcommand_from output-attach-mode' -n '__fish_riverctl_complete_arg 2' -a 'top bottom above below after'
complete -c riverctl -n '__fish_seen_subcommand_from focus-follows-cursor' -n '__fish_riverctl_complete_arg 2' -a 'disabled normal always' complete -c riverctl -n '__fish_seen_subcommand_from focus-follows-cursor' -n '__fish_riverctl_complete_arg 2' -a 'disabled normal always'
complete -c riverctl -n '__fish_seen_subcommand_from set-cursor-warp' -n '__fish_riverctl_complete_arg 2' -a 'disabled on-output-change on-focus-change' complete -c riverctl -n '__fish_seen_subcommand_from set-cursor-warp' -n '__fish_riverctl_complete_arg 2' -a 'disabled on-output-change on-focus-change'
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 warp'
# 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 relative-position dimensions fullscreen no-fullscreen set -l rule_actions float no-float ssd csd tags output position relative-position dimensions fullscreen no-fullscreen warp no-warp
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

@ -207,9 +207,9 @@ _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 relative-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 warp no-warp)'
;; ;;
list-rules) _alternative 'arguments:args:(float ssd tags output position dimensions fullscreen)' ;; list-rules) _alternative 'arguments:args:(float ssd tags output position dimensions fullscreen warp)' ;;
*) return 0 ;; *) return 0 ;;
esac esac
;; ;;

View File

@ -315,12 +315,16 @@ matches everything while _\*\*_ and the empty string are invalid.
view's preference. Applies to new and existing views. view's preference. Applies to new and existing views.
- *no-tearing*: Disable tearing for the view regardless of the view's - *no-tearing*: Disable tearing for the view regardless of the view's
preference. Applies to new and existing views. preference. Applies to new and existing views.
- *warp*: Always warp the cursor when switching to this view, regardless of
the _set-cursor-warp_ setting. Applies to new and existing views.
- *no-warp*: Never warp the cursor when switching to this view, regardless
of the _set-cursor-warp_ setting. Applies to new and existing views.
Both *float* and *no-float* rules are added to the same list, Both *float* and *no-float* rules are added to the same list,
which means that adding a *no-float* rule with the same arguments which means that adding a *no-float* rule with the same arguments
as a *float* rule will overwrite it. The same holds for *ssd* and as a *float* rule will overwrite it. The same holds for *ssd* and
*csd*, *fullscreen* and *no-fullscreen*, *tearing* and *csd*, *fullscreen* and *no-fullscreen*, *tearing* and
*no-tearing* rules. *no-tearing*, *warp* and *no-warp* rules.
If multiple rules in a list match a given view the most specific If multiple rules in a list match a given view the most specific
rule will be applied. For example with the following rules rule will be applied. For example with the following rules
@ -348,7 +352,7 @@ matches everything while _\*\*_ and the empty string are invalid.
*rule-del* [*-app-id* _glob_|*-title* _glob_] _action_ *rule-del* [*-app-id* _glob_|*-title* _glob_] _action_
Delete a rule created using *rule-add* with the given arguments. Delete a rule created using *rule-add* with the given arguments.
*list-rules* *float*|*ssd*|*tags*|*position*|*dimensions*|*fullscreen* *list-rules* *float*|*ssd*|*tags*|*position*|*dimensions*|*fullscreen*|*warp*
Print the specified rule list. The output is ordered from most specific Print the specified rule list. The output is ordered from most specific
to least specific, the same order in which views are checked against to least specific, the same order in which views are checked against
when searching for a match. Only the first matching rule in the list when searching for a match. Only the first matching rule in the list

View File

@ -108,6 +108,7 @@ rules: struct {
dimensions: RuleList(Dimensions) = .{}, dimensions: RuleList(Dimensions) = .{},
fullscreen: RuleList(bool) = .{}, fullscreen: RuleList(bool) = .{},
tearing: RuleList(bool) = .{}, tearing: RuleList(bool) = .{},
warp: RuleList(bool) = .{},
} = .{}, } = .{},
/// The selected focus_follows_cursor mode /// The selected focus_follows_cursor mode
@ -192,6 +193,7 @@ pub fn deinit(config: *Config) void {
config.rules.position.deinit(); config.rules.position.deinit();
config.rules.dimensions.deinit(); config.rules.dimensions.deinit();
config.rules.fullscreen.deinit(); config.rules.fullscreen.deinit();
config.rules.warp.deinit();
util.gpa.free(config.default_layout_namespace); util.gpa.free(config.default_layout_namespace);

View File

@ -1178,10 +1178,18 @@ fn warp(cursor: *Cursor) void {
const focused_output = cursor.seat.focused_output orelse return; const focused_output = cursor.seat.focused_output orelse return;
var mode = server.config.warp_cursor;
if (cursor.seat.focused == .view) {
const view = cursor.seat.focused.view;
if (server.config.rules.warp.match(view)) |w| {
mode = if (w) .@"on-focus-change" else .disabled;
}
}
// Warp pointer to center of the focused view/output (In layout coordinates) if enabled. // Warp pointer to center of the focused view/output (In layout coordinates) if enabled.
var output_layout_box: wlr.Box = undefined; var output_layout_box: wlr.Box = undefined;
server.root.output_layout.getBox(focused_output.wlr_output, &output_layout_box); server.root.output_layout.getBox(focused_output.wlr_output, &output_layout_box);
const target_box = switch (server.config.warp_cursor) { const target_box = switch (mode) {
.disabled => return, .disabled => return,
.@"on-output-change" => output_layout_box, .@"on-output-change" => output_layout_box,
.@"on-focus-change" => switch (cursor.seat.focused) { .@"on-focus-change" => switch (cursor.seat.focused) {

View File

@ -42,6 +42,8 @@ const Action = enum {
@"no-fullscreen", @"no-fullscreen",
tearing, tearing,
@"no-tearing", @"no-tearing",
warp,
@"no-warp",
}; };
pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void { pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void {
@ -57,7 +59,7 @@ 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; const action = std.meta.stringToEnum(Action, result.args[0]) orelse return Error.UnknownOption;
const positional_arguments_count: u8 = switch (action) { const positional_arguments_count: u8 = switch (action) {
.float, .@"no-float", .ssd, .csd, .fullscreen, .@"no-fullscreen", .tearing, .@"no-tearing" => 1, .float, .@"no-float", .ssd, .csd, .fullscreen, .@"no-fullscreen", .tearing, .@"no-tearing", .warp, .@"no-warp" => 1,
.tags, .output => 2, .tags, .output => 2,
.position, .dimensions => 3, .position, .dimensions => 3,
.@"relative-position" => 4, .@"relative-position" => 4,
@ -162,6 +164,13 @@ pub fn ruleAdd(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
.value = (action == .fullscreen), .value = (action == .fullscreen),
}); });
}, },
.warp, .@"no-warp" => {
try server.config.rules.warp.add(.{
.app_id_glob = app_id_glob,
.title_glob = title_glob,
.value = (action == .warp),
});
},
} }
} }
@ -212,6 +221,9 @@ pub fn ruleDel(_: *Seat, args: []const [:0]const u8, _: *?[]const u8) Error!void
_ = server.config.rules.tearing.del(rule); _ = server.config.rules.tearing.del(rule);
apply_tearing_rules(); apply_tearing_rules();
}, },
.warp, .@"no-warp" => {
_ = server.config.rules.warp.del(rule);
},
} }
} }
@ -250,6 +262,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
dimensions, dimensions,
fullscreen, fullscreen,
tearing, tearing,
warp,
}, args[1]) orelse return Error.UnknownOption; }, args[1]) orelse return Error.UnknownOption;
const max_glob_len = switch (rule_list) { const max_glob_len = switch (rule_list) {
inline else => |list| @field(server.config.rules, @tagName(list)).getMaxGlobLen(), inline else => |list| @field(server.config.rules, @tagName(list)).getMaxGlobLen(),
@ -265,13 +278,14 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
try writer.writeAll("action\n"); try writer.writeAll("action\n");
switch (rule_list) { switch (rule_list) {
inline .float, .ssd, .output, .fullscreen, .tearing => |list| { inline .float, .ssd, .output, .fullscreen, .tearing, .warp => |list| {
const rules = switch (list) { const rules = switch (list) {
.float => server.config.rules.float.rules.items, .float => server.config.rules.float.rules.items,
.ssd => server.config.rules.ssd.rules.items, .ssd => server.config.rules.ssd.rules.items,
.output => server.config.rules.output.rules.items, .output => server.config.rules.output.rules.items,
.fullscreen => server.config.rules.fullscreen.rules.items, .fullscreen => server.config.rules.fullscreen.rules.items,
.tearing => server.config.rules.tearing.rules.items, .tearing => server.config.rules.tearing.rules.items,
.warp => server.config.rules.warp.rules.items,
else => unreachable, else => unreachable,
}; };
for (rules) |rule| { for (rules) |rule| {
@ -283,6 +297,7 @@ pub fn listRules(_: *Seat, args: []const [:0]const u8, out: *?[]const u8) Error!
.output => rule.value, .output => rule.value,
.fullscreen => if (rule.value) "fullscreen" else "no-fullscreen", .fullscreen => if (rule.value) "fullscreen" else "no-fullscreen",
.tearing => if (rule.value) "tearing" else "no-tearing", .tearing => if (rule.value) "tearing" else "no-tearing",
.warp => if (rule.value) "warp" else "no-warp",
else => unreachable, else => unreachable,
}}); }});
} }