Add cursor warp option
This commit is contained in:
		| @ -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| { | ||||||
|  | |||||||
| @ -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=" \ | ||||||
|  | |||||||
| @ -86,10 +86,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" | ||||||
|  | |||||||
| @ -202,9 +202,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 | ||||||
|         ;; |         ;; | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
| @ -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); | ||||||
|  |  | ||||||
|  | |||||||
| @ -1246,10 +1246,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) { | ||||||
|  | |||||||
| @ -43,6 +43,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 { | ||||||
| @ -58,7 +60,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, | ||||||
| @ -163,6 +165,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), | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -213,6 +222,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); | ||||||
|  |         }, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -251,6 +263,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(), | ||||||
| @ -266,13 +279,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| { | ||||||
| @ -284,6 +298,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, | ||||||
|                 }}); |                 }}); | ||||||
|             } |             } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user