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 {