Config
input {
warp-mouse-to-focus mode="center-xy-always"
}What did you do?
- Multi-monitor setup with
warp-mouse-to-focus mode="center-xy-always"enabled - Click a link, notification, or taskbar item on output A that activates an already-open window on output B via xdg-activation or foreign-toplevel
- Example: Firefox on output A activates a Firefox window/tab on output B
What happened?
Focus moves to the window on output B, but the cursor stays on output A.
warp-mouse-to-focus mode="center-xy-always" works for newly opened windows (compositor map path), but activation events (xdg-activation, foreign-toplevel) skip cursor relocation entirely.
What did you expect to happen?
Activation-triggered focus changes should behave like normal focus changes:
- If
warp-mouse-to-focusis set, cursor should warp to the focused window - Otherwise, if the active output changed, cursor should at minimum move to the focused output
Likely cause
State::focus_window() handles active-output change and cursor warp/move:
https://github.com/niri-wm/niri/blob/main/src/niri.rs#L976-L993
pub fn focus_window(&mut self, window: &Window) {
let active_output = self.niri.layout.active_output().cloned();
self.niri.layout.activate_window(window);
let new_active = self.niri.layout.active_output().cloned();
if new_active != active_output {
if !self.maybe_warp_cursor_to_focus_centered() {
self.move_cursor_to_output(&new_active.unwrap());
}
} else {
self.maybe_warp_cursor_to_focus();
}
self.niri.queue_redraw_all();
}But xdg_activation::request_activation() calls layout.activate_window() directly, skipping all the cursor-warp / output-move logic:
https://github.com/niri-wm/niri/blob/main/src/handlers/mod.rs
// request_activation()
self.niri.layout.activate_window(&window);
self.niri.layer_shell_on_demand_focus = None;
self.niri.queue_redraw_all();Same for ForeignToplevelHandler::activate():
self.niri.layout.activate_window(&window);
self.niri.layer_shell_on_demand_focus = None;
self.niri.queue_redraw_all();Suggested fix
Use self.focus_window(&window) in both activation handlers instead of calling self.niri.layout.activate_window(&window) directly — unless there are borrow-checker or protocol-specific reasons not to. focus_window already handles cursor warp, output change, and queue_redraw_all internally.
System
- niri version: 26.04 (Nixpkgs)
- outputs: DP-1 (2560×1440) + DP-2 (2560×1440)
Workaround until this is fixed
Sharing this for anyone else hitting this — note it also affects same-output cases (clicking a link in app A that activates the browser, clicking a taskbar/dock item…), and plain click-to-focus too, since both paths call
layout.activate_window()directly instead ofState::focus_window().You can get the warp back with a tiny IPC daemon: listen to the event stream and re-issue
focus-windowfor whichever window just gained focus. TheFocusWindowaction goes throughState::focus_window(), which applieswarp-mouse-to-focuswith your configured mode — so the cursor warps exactly the way niri does it for keyboard-initiated focus changes.Save as
~/.local/bin/warp-on-focus(make it executable; keep it anywhere in yourPATH):config.kdl:(If the script isn't in your
PATH, use its absolute path inspawn-at-startup— no~expansion there since niri doesn't spawn through a shell.)Why it's loop-safe: the
WindowFocusChangedevent is only emitted when the focused window actually changes (ipc_refresh_windows()compares against the event-stream state), and re-focusing the already-focused window emits nothing — so it settles after a single iteration. Verified: exactly one event per focus change, ~0% CPU at idle.Notes:
Requires
jq(rewrite in python/whatever if you prefer).Focus moving to a layer-shell surface emits
WindowFocusChanged { id: null }and is ignored, so launchers/overlays don't grab your cursor.With
warp-mouse-to-focus mode="center-xy-always"this also warps on plain click-to-focus (cursor jumps to the window center on every focus-changing click). If that's too aggressive, usewarp-mouse-to-focus mode="center-xy". the warp then only happens when the cursor is outside the newly focused window, and plain clicks don't move it at all.If you only want an on-demand warp, a keybind is enough:
Tested on niri 26.04. Delete the script +
spawn-at-startupline once this issue is fixed.+1 on the suggested fix, by the way — routing the activation handlers through
focus_window()makes the daemon unnecessary.