From 8036ae2bd179d66f6317e5c7cbd388e88877d9f7 Mon Sep 17 00:00:00 2001 From: shironeko Date: Mon, 1 Aug 2022 20:31:50 -0400 Subject: Cursor: add on-focus-change option Warp the cursor to the center of the focused view if the cursor is not in the bounding box of that view already. This helps the user to keep track of their cursor when they mostly use the keyboard and the cursor becomes hidden most of the time, also helps trackpad/trackpoint users. --- completions/bash/riverctl | 2 +- completions/fish/riverctl.fish | 2 +- completions/zsh/_riverctl | 2 +- doc/riverctl.1.scd | 4 +++- river/Config.zig | 1 + river/Cursor.zig | 50 ++++++++++++++++++++++++++++++++++++++++++ river/Seat.zig | 30 +++++++------------------ 7 files changed, 65 insertions(+), 26 deletions(-) diff --git a/completions/bash/riverctl b/completions/bash/riverctl index 9793b6f..03ed4f4 100644 --- a/completions/bash/riverctl +++ b/completions/bash/riverctl @@ -64,7 +64,7 @@ function __riverctl_completion () "unmap") OPTS="-release" ;; "attach-mode") OPTS="top bottom" ;; "focus-follows-cursor") OPTS="disabled normal always" ;; - "set-cursor-warp") OPTS="disabled on-output-change" ;; + "set-cursor-warp") OPTS="disabled on-output-change on-focus-change" ;; "hide-cursor") OPTS="timeout when-typing" ;; *) return ;; esac diff --git a/completions/fish/riverctl.fish b/completions/fish/riverctl.fish index f24f32a..39ebc7d 100644 --- a/completions/fish/riverctl.fish +++ b/completions/fish/riverctl.fish @@ -79,7 +79,7 @@ complete -c riverctl -x -n '__fish_seen_subcommand_from map' -a complete -c riverctl -x -n '__fish_seen_subcommand_from unmap' -a '-release' complete -c riverctl -x -n '__fish_seen_subcommand_from attach-mode' -a 'top bottom' complete -c riverctl -x -n '__fish_seen_subcommand_from focus-follows-cursor' -a 'disabled normal always' -complete -c riverctl -x -n '__fish_seen_subcommand_from set-cursor-warp' -a 'disabled on-output-change' +complete -c riverctl -x -n '__fish_seen_subcommand_from set-cursor-warp' -a 'disabled on-output-change on-focus-change' # Subcommands for 'input' complete -c riverctl -x -n '__fish_seen_subcommand_from input; and __fish_riverctl_complete_arg 2' -a "(__riverctl_list_input_devices)" diff --git a/completions/zsh/_riverctl b/completions/zsh/_riverctl index f303e5e..8ed1fe5 100644 --- a/completions/zsh/_riverctl +++ b/completions/zsh/_riverctl @@ -178,7 +178,7 @@ _riverctl() unmap) _alternative 'arguments:optional:(-release)' ;; attach-mode) _alternative 'arguments:args:(top bottom)' ;; focus-follows-cursor) _alternative 'arguments:args:(disabled normal always)' ;; - set-cursor-warp) _alternative 'arguments:args:(disabled on-output-change)' ;; + set-cursor-warp) _alternative 'arguments:args:(disabled on-output-change on-focus-change)' ;; hide-cursor) _riverctl_hide_cursor ;; *) return 0 ;; esac diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index b75fcae..aaddd9b 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -307,12 +307,14 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_ Hide the cursor when pressing any non-modifier key. Show the cursor again on any movement. -*set-cursor-warp* *disabled*|*on-output-change* +*set-cursor-warp* *disabled*|*on-output-change*|*on-focus-change* Set the cursor warp mode. There are two available modes: - _disabled_: Cursor will not be warped. This is the default. - _on-output-change_: When a different output is focused, the cursor will be warped to its center. + - _on-focus-change_: When a different view/output is focused, the cursor will be + warped to its center. *set-repeat* _rate_ _delay_ Set the keyboard repeat rate to _rate_ key repeats per second and diff --git a/river/Config.zig b/river/Config.zig index f654f99..b105a4e 100644 --- a/river/Config.zig +++ b/river/Config.zig @@ -36,6 +36,7 @@ pub const FocusFollowsCursorMode = enum { pub const WarpCursorMode = enum { disabled, @"on-output-change", + @"on-focus-change", }; pub const HideCursorWhenTypingMode = enum { diff --git a/river/Cursor.zig b/river/Cursor.zig index 1d83444..3f15f3b 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -105,6 +105,7 @@ pressed_count: u32 = 0, hide_cursor_timer: *wl.EventSource, hidden: bool = false, +may_need_warp: bool = false, axis: wl.Listener(*wlr.Pointer.event.Axis) = wl.Listener(*wlr.Pointer.event.Axis).init(handleAxis), frame: wl.Listener(*wlr.Cursor) = wl.Listener(*wlr.Cursor).init(handleFrame), @@ -1050,6 +1051,9 @@ pub fn checkFocusFollowsCursor(self: *Self) void { /// the target view of a cursor operation potentially being moved to a non-visible tag, /// becoming fullscreen, etc. pub fn updateState(self: *Self) void { + if (self.may_need_warp) { + self.warp(); + } if (self.shouldPassthrough()) { self.mode = .passthrough; var now: os.timespec = undefined; @@ -1103,3 +1107,49 @@ fn passthrough(self: *Self, time: u32) void { self.clearFocus(); } } + +fn warp(self: *Self) void { + self.may_need_warp = false; + if (self.seat.focused_output == &server.root.noop_output) return; + // Warp pointer to center of the focused view/output (In layout coordinates) if enabled. + var output_layout_box: wlr.Box = undefined; + server.root.output_layout.getBox(self.seat.focused_output.wlr_output, &output_layout_box); + const target_box = switch (server.config.warp_cursor) { + .disabled => return, + .@"on-output-change" => output_layout_box, + .@"on-focus-change" => switch (self.seat.focused) { + .layer, .lock_surface, .none => output_layout_box, + .view => |view| wlr.Box{ + .x = output_layout_box.x + view.current.box.x, + .y = output_layout_box.y + view.current.box.y, + .width = view.current.box.width, + .height = view.current.box.height, + }, + .xwayland_override_redirect => |or_window| wlr.Box{ + .x = or_window.xwayland_surface.x, + .y = or_window.xwayland_surface.y, + .width = or_window.xwayland_surface.width, + .height = or_window.xwayland_surface.height, + }, + }, + }; + // Checking against the usable box here gives much better UX when, for example, + // a status bar allows using the pointer to change tag/view focus. + const usable_box = self.seat.focused_output.usable_box; + const usable_layout_box = wlr.Box{ + .x = output_layout_box.x + usable_box.x, + .y = output_layout_box.y + usable_box.y, + .width = usable_box.width, + .height = usable_box.height, + }; + if (!output_layout_box.containsPoint(self.wlr_cursor.x, self.wlr_cursor.y) or + (usable_layout_box.containsPoint(self.wlr_cursor.x, self.wlr_cursor.y) and + !target_box.containsPoint(self.wlr_cursor.x, self.wlr_cursor.y))) + { + const lx = @intToFloat(f64, target_box.x + @divTrunc(target_box.width, 2)); + const ly = @intToFloat(f64, target_box.y + @divTrunc(target_box.height, 2)); + if (!self.wlr_cursor.warp(null, lx, ly)) { + log.err("failed to warp cursor on focus change", .{}); + } + } +} diff --git a/river/Seat.zig b/river/Seat.zig index 870744b..d678e83 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -265,6 +265,10 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { PointerConstraint.warpToHint(&self.cursor); constraint.sendDeactivated(); self.cursor.constraint = null; + } else { + // Depending on configuration and cursor position, changing keyboard focus + // may cause the cursor to be warped. + self.cursor.may_need_warp = true; } } else { self.wlr_seat.keyboardClearFocus(); @@ -273,6 +277,10 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void { PointerConstraint.warpToHint(&self.cursor); constraint.sendDeactivated(); self.cursor.constraint = null; + } else { + // Depending on configuration and cursor position, changing keyboard focus + // may cause the cursor to be warped. + self.cursor.may_need_warp = true; } } @@ -313,28 +321,6 @@ pub fn focusOutput(self: *Self, output: *Output) void { it = self.status_trackers.first; while (it) |node| : (it = node.next) node.data.sendOutput(.focused); - - if (self.focused_output == &server.root.noop_output) return; - - // Warp pointer to center of newly focused output (In layout coordinates), - // but only if cursor is not already on the output and this feature is enabled. - switch (server.config.warp_cursor) { - .disabled => {}, - .@"on-output-change" => { - var layout_box: wlr.Box = undefined; - server.root.output_layout.getBox(output.wlr_output, &layout_box); - if (!layout_box.containsPoint(self.cursor.wlr_cursor.x, self.cursor.wlr_cursor.y)) { - var output_width: i32 = undefined; - var output_height: i32 = undefined; - output.wlr_output.effectiveResolution(&output_width, &output_height); - const lx = @intToFloat(f64, layout_box.x + @divTrunc(output_width, 2)); - const ly = @intToFloat(f64, layout_box.y + @divTrunc(output_height, 2)); - if (!self.cursor.wlr_cursor.warp(null, lx, ly)) { - log.err("failed to warp cursor on output change", .{}); - } - } - }, - } } pub fn handleActivity(self: Self) void { -- cgit v1.2.3