1
0
Fork 0
forked from wry/wry

Merge pull request #820 from mahkoh/jorth/warp-to-focused

config: add warp-mouse-to-focus action
This commit is contained in:
mahkoh 2026-03-20 15:10:03 +01:00 committed by GitHub
commit 6727f3e4bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 74 additions and 2 deletions

View file

@ -407,6 +407,10 @@ impl ConfigClient {
self.send(&ClientMessage::SeatMove { seat, direction }); 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) { pub fn window_move(&self, window: Window, direction: Direction) {
self.send(&ClientMessage::WindowMove { window, direction }); self.send(&ClientMessage::WindowMove { window, direction });
} }

View file

@ -858,6 +858,9 @@ pub enum ClientMessage<'a> {
RemoveVirtualOutput { RemoveVirtualOutput {
name: &'a str, name: &'a str,
}, },
SeatWarpMouseToFocus {
seat: Seat,
},
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]

View file

@ -645,6 +645,11 @@ impl Seat {
pub fn enable_unicode_input(self) { pub fn enable_unicode_input(self) {
get!().seat_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. /// A focus-follows-mouse mode.

View file

@ -38,7 +38,7 @@ use {
}, },
jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts}, jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts},
wl_output::{BlendSpace, OutputId, PersistentOutputState, WlOutputGlobal}, 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::{ wl_surface::{
NoneSurfaceExt, xdg_surface::handle_xdg_surface_configure_events, NoneSurfaceExt, xdg_surface::handle_xdg_surface_configure_events,
zwp_input_popup_surface_v2::input_popup_positioning, zwp_input_popup_surface_v2::input_popup_positioning,
@ -377,6 +377,7 @@ fn start_compositor2(
toplevel_managers: Default::default(), toplevel_managers: Default::default(),
node_at_tree: Default::default(), node_at_tree: Default::default(),
position_hint_requests: Default::default(), position_hint_requests: Default::default(),
pending_warp_mouse_to_focus: Default::default(),
backend_connector_state_serials: Default::default(), backend_connector_state_serials: Default::default(),
head_names: Default::default(), head_names: Default::default(),
head_managers: Default::default(), head_managers: Default::default(),
@ -605,6 +606,10 @@ fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {
"redraw control centers", "redraw control centers",
redraw_control_centers(state.clone()), redraw_control_centers(state.clone()),
), ),
eng.spawn(
"warp mouse to focus",
handle_warp_mouse_to_focus(state.clone()),
),
] ]
} }

View file

@ -2415,6 +2415,12 @@ impl ConfigProxyHandler {
Ok(()) 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<ThemeSized, CphError> { fn get_sized(&self, sized: Resizable) -> Result<ThemeSized, CphError> {
use jay_config::theme::sized::*; use jay_config::theme::sized::*;
let sized = match sized { let sized = match sized {
@ -3329,6 +3335,9 @@ impl ConfigProxyHandler {
ClientMessage::SeatEnableUnicodeInput { seat } => self ClientMessage::SeatEnableUnicodeInput { seat } => self
.handle_seat_enable_unicode_input(seat) .handle_seat_enable_unicode_input(seat)
.wrn("seat_enable_unicode_input")?, .wrn("seat_enable_unicode_input")?,
ClientMessage::SeatWarpMouseToFocus { seat } => self
.handle_seat_warp_mouse_to_focus(seat)
.wrn("seat_warp_mouse_to_focus")?,
ClientMessage::ConnectorSetUseNativeGamut { ClientMessage::ConnectorSetUseNativeGamut {
connector, connector,
use_native_gamut, use_native_gamut,

View file

@ -258,6 +258,7 @@ pub struct WlSeatGlobal {
modifiers_forward: EventSource<dyn LedsListener>, modifiers_forward: EventSource<dyn LedsListener>,
simple_im: CloneCell<Option<Rc<SimpleIm>>>, simple_im: CloneCell<Option<Rc<SimpleIm>>>,
simple_im_enabled: Cell<bool>, simple_im_enabled: Cell<bool>,
warp_mouse_to_focus_scheduled: Cell<bool>,
} }
impl PartialEq for WlSeatGlobal { impl PartialEq for WlSeatGlobal {
@ -400,6 +401,7 @@ impl WlSeatGlobal {
modifiers_forward: Default::default(), modifiers_forward: Default::default(),
simple_im: CloneCell::new(simple_im), simple_im: CloneCell::new(simple_im),
simple_im_enabled: Cell::new(true), simple_im_enabled: Cell::new(true),
warp_mouse_to_focus_scheduled: Cell::new(false),
}); });
slf.pointer_cursor.set_owner(slf.clone()); slf.pointer_cursor.set_owner(slf.clone());
slf.modifiers_listener slf.modifiers_listener
@ -820,6 +822,12 @@ impl WlSeatGlobal {
} }
} }
pub fn schedule_warp_mouse_to_focus(self: &Rc<Self>) {
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<Self>, direction: Direction) { pub fn move_focused(self: &Rc<Self>, direction: Direction) {
let kb_node = self.keyboard_node.get(); let kb_node = self.keyboard_node.get();
let Some(tl) = kb_node.node_toplevel() else { let Some(tl) = kb_node.node_toplevel() else {
@ -2007,3 +2015,25 @@ pub async fn handle_position_hint_requests(state: Rc<State>) {
req.seat.motion_event_abs(state.now_usec(), x, y); req.seat.motion_event_abs(state.now_usec(), x, y);
} }
} }
pub async fn handle_warp_mouse_to_focus(state: Rc<State>) {
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);
}
}
}

View file

@ -284,6 +284,7 @@ pub struct State {
pub caps_thread: Option<PrCapsThread>, pub caps_thread: Option<PrCapsThread>,
pub node_at_tree: RefCell<Vec<FoundNode>>, pub node_at_tree: RefCell<Vec<FoundNode>>,
pub position_hint_requests: AsyncQueue<PositionHintRequest>, pub position_hint_requests: AsyncQueue<PositionHintRequest>,
pub pending_warp_mouse_to_focus: AsyncQueue<Rc<WlSeatGlobal>>,
pub backend_connector_state_serials: BackendConnectorStateSerials, pub backend_connector_state_serials: BackendConnectorStateSerials,
pub head_names: HeadNames, pub head_names: HeadNames,
pub head_managers: pub head_managers:
@ -1175,6 +1176,7 @@ impl State {
self.tl_matcher_manager.clear(); self.tl_matcher_manager.clear();
self.node_at_tree.borrow_mut().clear(); self.node_at_tree.borrow_mut().clear();
self.position_hint_requests.clear(); self.position_hint_requests.clear();
self.pending_warp_mouse_to_focus.clear();
self.head_managers.clear(); self.head_managers.clear();
self.head_managers_async.clear(); self.head_managers_async.clear();
self.const_40hz_latch.clear(); self.const_40hz_latch.clear();

View file

@ -91,6 +91,7 @@ pub enum SimpleCommand {
ReloadSimpleIm, ReloadSimpleIm,
EnableUnicodeInput, EnableUnicodeInput,
OpenControlCenter, OpenControlCenter,
WarpMouseToFocus,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -168,6 +168,7 @@ impl ActionParser<'_> {
"reload-simple-im" => ReloadSimpleIm, "reload-simple-im" => ReloadSimpleIm,
"enable-unicode-input" => EnableUnicodeInput, "enable-unicode-input" => EnableUnicodeInput,
"open-control-center" => OpenControlCenter, "open-control-center" => OpenControlCenter,
"warp-mouse-to-focus" => WarpMouseToFocus,
_ => { _ => {
return Err( return Err(
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span) ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)

View file

@ -247,6 +247,10 @@ impl Action {
b.new(move || persistent.seat.enable_unicode_input()) b.new(move || persistent.seat.enable_unicode_input())
} }
SimpleCommand::OpenControlCenter => b.new(open_control_center), 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 } => { Action::Multi { actions } => {
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect(); let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();

View file

@ -1995,7 +1995,8 @@
"toggle-simple-im-enabled", "toggle-simple-im-enabled",
"reload-simple-im", "reload-simple-im",
"enable-unicode-input", "enable-unicode-input",
"open-control-center" "open-control-center",
"warp-mouse-to-focus"
] ]
}, },
"SimpleIm": { "SimpleIm": {

View file

@ -4559,6 +4559,10 @@ The string should have one of the following values:
Opens the control center. Opens the control center.
- `warp-mouse-to-focus`:
Warps the cursor to the center of the currently focused window.
<a name="types-SimpleIm"></a> <a name="types-SimpleIm"></a>

View file

@ -1193,6 +1193,9 @@ SimpleActionName:
This has no effect if the simple IM is not currently active. This has no effect if the simple IM is not currently active.
- value: open-control-center - value: open-control-center
description: Opens the 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: Color: