From aaf02dc4e18961470b2cc982ff50104a9723a106 Mon Sep 17 00:00:00 2001 From: Nicolaus Jacobsen Date: Tue, 10 Mar 2026 09:36:13 +0100 Subject: [PATCH] config: add warp-mouse-to-focus action --- jay-config/src/_private/client.rs | 4 ++++ jay-config/src/_private/ipc.rs | 3 +++ jay-config/src/input.rs | 5 ++++ src/compositor.rs | 7 +++++- src/config/handler.rs | 9 +++++++ src/ifs/wl_seat.rs | 30 ++++++++++++++++++++++++ src/state.rs | 2 ++ toml-config/src/config.rs | 1 + toml-config/src/config/parsers/action.rs | 1 + toml-config/src/lib.rs | 4 ++++ toml-spec/spec/spec.generated.json | 3 ++- toml-spec/spec/spec.generated.md | 4 ++++ toml-spec/spec/spec.yaml | 3 +++ 13 files changed, 74 insertions(+), 2 deletions(-) diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index bd4989d2..8c434cb1 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -407,6 +407,10 @@ impl ConfigClient { self.send(&ClientMessage::SeatMove { seat, direction }); } + pub fn seat_warp_mouse_to_focus(&self, seat: Seat) { + self.send(&ClientMessage::SeatWarpMouseToFocus { seat }); + } + pub fn window_move(&self, window: Window, direction: Direction) { self.send(&ClientMessage::WindowMove { window, direction }); } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 29c90cf2..3487333b 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -858,6 +858,9 @@ pub enum ClientMessage<'a> { RemoveVirtualOutput { name: &'a str, }, + SeatWarpMouseToFocus { + seat: Seat, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index 2e985766..87e2ec7e 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -645,6 +645,11 @@ impl Seat { pub fn enable_unicode_input(self) { get!().seat_enable_unicode_input(self); } + + /// Warps the cursor to the center of the currently focused window. + pub fn warp_mouse_to_focus(self) { + get!().seat_warp_mouse_to_focus(self) + } } /// A focus-follows-mouse mode. diff --git a/src/compositor.rs b/src/compositor.rs index 43416809..c1bc089d 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -38,7 +38,7 @@ use { }, jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts}, wl_output::{BlendSpace, OutputId, PersistentOutputState, WlOutputGlobal}, - wl_seat::handle_position_hint_requests, + wl_seat::{handle_position_hint_requests, handle_warp_mouse_to_focus}, wl_surface::{ NoneSurfaceExt, xdg_surface::handle_xdg_surface_configure_events, zwp_input_popup_surface_v2::input_popup_positioning, @@ -377,6 +377,7 @@ fn start_compositor2( toplevel_managers: Default::default(), node_at_tree: Default::default(), position_hint_requests: Default::default(), + pending_warp_mouse_to_focus: Default::default(), backend_connector_state_serials: Default::default(), head_names: Default::default(), head_managers: Default::default(), @@ -605,6 +606,10 @@ fn start_global_event_handlers(state: &Rc) -> Vec> { "redraw control centers", redraw_control_centers(state.clone()), ), + eng.spawn( + "warp mouse to focus", + handle_warp_mouse_to_focus(state.clone()), + ), ] } diff --git a/src/config/handler.rs b/src/config/handler.rs index 2dd975da..3eadc619 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -2415,6 +2415,12 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_seat_warp_mouse_to_focus(&self, seat: Seat) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.schedule_warp_mouse_to_focus(); + Ok(()) + } + fn get_sized(&self, sized: Resizable) -> Result { use jay_config::theme::sized::*; let sized = match sized { @@ -3329,6 +3335,9 @@ impl ConfigProxyHandler { ClientMessage::SeatEnableUnicodeInput { seat } => self .handle_seat_enable_unicode_input(seat) .wrn("seat_enable_unicode_input")?, + ClientMessage::SeatWarpMouseToFocus { seat } => self + .handle_seat_warp_mouse_to_focus(seat) + .wrn("seat_warp_mouse_to_focus")?, ClientMessage::ConnectorSetUseNativeGamut { connector, use_native_gamut, diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index dc94c098..c4d50f0e 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -258,6 +258,7 @@ pub struct WlSeatGlobal { modifiers_forward: EventSource, simple_im: CloneCell>>, simple_im_enabled: Cell, + warp_mouse_to_focus_scheduled: Cell, } impl PartialEq for WlSeatGlobal { @@ -400,6 +401,7 @@ impl WlSeatGlobal { modifiers_forward: Default::default(), simple_im: CloneCell::new(simple_im), simple_im_enabled: Cell::new(true), + warp_mouse_to_focus_scheduled: Cell::new(false), }); slf.pointer_cursor.set_owner(slf.clone()); slf.modifiers_listener @@ -820,6 +822,12 @@ impl WlSeatGlobal { } } + pub fn schedule_warp_mouse_to_focus(self: &Rc) { + if !self.warp_mouse_to_focus_scheduled.replace(true) { + self.state.pending_warp_mouse_to_focus.push(self.clone()); + } + } + pub fn move_focused(self: &Rc, direction: Direction) { let kb_node = self.keyboard_node.get(); let Some(tl) = kb_node.node_toplevel() else { @@ -2007,3 +2015,25 @@ pub async fn handle_position_hint_requests(state: Rc) { req.seat.motion_event_abs(state.now_usec(), x, y); } } + +pub async fn handle_warp_mouse_to_focus(state: Rc) { + loop { + state.pending_warp_mouse_to_focus.non_empty().await; + state.eng.yield_now().await; + while let Some(seat) = state.pending_warp_mouse_to_focus.try_pop() { + seat.warp_mouse_to_focus_scheduled.set(false); + let Some(tl) = seat.keyboard_node.get().node_toplevel() else { + continue; + }; + let (x, y) = tl.node_absolute_position().center(); + let Some(target) = state.node_at(x, y).node.node_toplevel() else { + continue; + }; + if target.node_id() != tl.node_id() { + continue; + } + let (x, y) = (Fixed::from_int(x), Fixed::from_int(y)); + seat.motion_event_abs(state.now_usec(), x, y); + } + } +} diff --git a/src/state.rs b/src/state.rs index d80129a6..849097b8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -284,6 +284,7 @@ pub struct State { pub caps_thread: Option, pub node_at_tree: RefCell>, pub position_hint_requests: AsyncQueue, + pub pending_warp_mouse_to_focus: AsyncQueue>, pub backend_connector_state_serials: BackendConnectorStateSerials, pub head_names: HeadNames, pub head_managers: @@ -1175,6 +1176,7 @@ impl State { self.tl_matcher_manager.clear(); self.node_at_tree.borrow_mut().clear(); self.position_hint_requests.clear(); + self.pending_warp_mouse_to_focus.clear(); self.head_managers.clear(); self.head_managers_async.clear(); self.const_40hz_latch.clear(); diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 99938471..1caa117b 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -91,6 +91,7 @@ pub enum SimpleCommand { ReloadSimpleIm, EnableUnicodeInput, OpenControlCenter, + WarpMouseToFocus, } #[derive(Debug, Clone)] diff --git a/toml-config/src/config/parsers/action.rs b/toml-config/src/config/parsers/action.rs index d7834de8..e34a48c4 100644 --- a/toml-config/src/config/parsers/action.rs +++ b/toml-config/src/config/parsers/action.rs @@ -168,6 +168,7 @@ impl ActionParser<'_> { "reload-simple-im" => ReloadSimpleIm, "enable-unicode-input" => EnableUnicodeInput, "open-control-center" => OpenControlCenter, + "warp-mouse-to-focus" => WarpMouseToFocus, _ => { return Err( ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span) diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index f3e8e620..322d451f 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -247,6 +247,10 @@ impl Action { b.new(move || persistent.seat.enable_unicode_input()) } SimpleCommand::OpenControlCenter => b.new(open_control_center), + SimpleCommand::WarpMouseToFocus => { + let persistent = state.persistent.clone(); + b.new(move || persistent.seat.warp_mouse_to_focus()) + } }, Action::Multi { actions } => { let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect(); diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 73ac2e92..18c1a4ee 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -1995,7 +1995,8 @@ "toggle-simple-im-enabled", "reload-simple-im", "enable-unicode-input", - "open-control-center" + "open-control-center", + "warp-mouse-to-focus" ] }, "SimpleIm": { diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index c6a32487..1509b8ef 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -4559,6 +4559,10 @@ The string should have one of the following values: Opens the control center. +- `warp-mouse-to-focus`: + + Warps the cursor to the center of the currently focused window. + diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 38cfc7b8..8a645992 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -1193,6 +1193,9 @@ SimpleActionName: This has no effect if the simple IM is not currently active. - value: open-control-center description: Opens the control center. + - value: warp-mouse-to-focus + description: | + Warps the cursor to the center of the currently focused window. Color: