1
0
Fork 0
forked from wry/wry

config: add mouse-follows-focus option

This commit is contained in:
Nicolaus Jacobsen 2026-03-10 09:36:13 +01:00 committed by Julian Orth
parent 0d4ee299d4
commit 216d104b73
14 changed files with 134 additions and 6 deletions

View file

@ -1108,6 +1108,10 @@ impl ConfigClient {
self.send(&ClientMessage::SeatEnableUnicodeInput { seat });
}
pub fn seat_set_mouse_follows_focus(&self, seat: Seat, enabled: bool) {
self.send(&ClientMessage::SeatSetMouseFollowsFocus { seat, enabled });
}
pub fn set_show_float_pin_icon(&self, show: bool) {
self.send(&ClientMessage::SetShowFloatPinIcon { show });
}

View file

@ -871,6 +871,10 @@ pub enum ClientMessage<'a> {
dx2: i32,
dy2: i32,
},
SeatSetMouseFollowsFocus {
seat: Seat,
enabled: bool,
},
}
#[derive(Serialize, Deserialize, Debug)]

View file

@ -655,6 +655,15 @@ impl Seat {
pub fn resize(self, dx1: i32, dy1: i32, dx2: i32, dy2: i32) {
self.window().resize(dx1, dy1, dx2, dy2);
}
/// Sets whether the cursor should automatically move to the center of a window
/// when focus changes via keyboard commands (move-left, focus-right, show-workspace, etc.).
///
/// The default is `false`.
#[deprecated = "This setting is unstable and might be removed in the future"]
pub fn unstable_set_mouse_follows_focus(self, enabled: bool) {
get!().seat_set_mouse_follows_focus(self, enabled)
}
}
/// A focus-follows-mouse mode.

View file

@ -2425,6 +2425,16 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_seat_set_mouse_follows_focus(
&self,
seat: Seat,
enabled: bool,
) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
seat.set_mouse_follows_focus(enabled);
Ok(())
}
fn get_sized(&self, sized: Resizable) -> Result<ThemeSized, CphError> {
use jay_config::theme::sized::*;
let sized = match sized {
@ -2597,6 +2607,7 @@ impl ConfigProxyHandler {
return Err(CphError::WindowNotVisible(window_id));
}
seat.focus_toplevel(window);
seat.maybe_schedule_warp_mouse_to_focus();
Ok(())
}
@ -3354,6 +3365,9 @@ impl ConfigProxyHandler {
ClientMessage::SeatWarpMouseToFocus { seat } => self
.handle_seat_warp_mouse_to_focus(seat)
.wrn("seat_warp_mouse_to_focus")?,
ClientMessage::SeatSetMouseFollowsFocus { seat, enabled } => self
.handle_seat_set_mouse_follows_focus(seat, enabled)
.wrn("seat_set_mouse_follows_focus")?,
ClientMessage::ConnectorSetUseNativeGamut {
connector,
use_native_gamut,

View file

@ -232,6 +232,9 @@ impl InputPane {
bool(ui, "Focus Follows Mouse", seat.focus_follows_mouse(), |v| {
seat.set_focus_follows_mouse(v);
});
bool(ui, "Mouse Follows Focus", seat.mouse_follows_focus(), |v| {
seat.set_mouse_follows_focus(v);
});
combo_box_ui(
ui,
"Fallback Output Mode",

View file

@ -260,6 +260,8 @@ pub struct WlSeatGlobal {
simple_im: CloneCell<Option<Rc<SimpleIm>>>,
simple_im_enabled: Cell<bool>,
warp_mouse_to_focus_scheduled: Cell<bool>,
warp_mouse_to_focus_skip_target_check: Cell<bool>,
mouse_follows_focus: Cell<bool>,
}
impl PartialEq for WlSeatGlobal {
@ -403,6 +405,8 @@ impl WlSeatGlobal {
simple_im: CloneCell::new(simple_im),
simple_im_enabled: Cell::new(true),
warp_mouse_to_focus_scheduled: Cell::new(false),
warp_mouse_to_focus_skip_target_check: Cell::new(false),
mouse_follows_focus: Cell::new(false),
});
slf.pointer_cursor.set_owner(slf.clone());
slf.modifiers_listener
@ -537,12 +541,13 @@ impl WlSeatGlobal {
self.get_cursor_output()
}
pub fn set_workspace(&self, ws: &Rc<WorkspaceNode>) {
pub fn set_workspace(self: &Rc<Self>, ws: &Rc<WorkspaceNode>) {
let tl = match self.keyboard_node.get().node_toplevel() {
Some(tl) => tl,
_ => return,
};
toplevel_set_workspace(&self.state, tl, ws);
self.maybe_schedule_warp_mouse_to_focus();
}
pub fn mark_last_active(self: &Rc<Self>) {
@ -743,6 +748,7 @@ impl WlSeatGlobal {
&& let Some(tl) = parent.node_toplevel()
{
self.focus_node(tl);
self.maybe_schedule_warp_mouse_to_focus();
}
}
@ -802,6 +808,7 @@ impl WlSeatGlobal {
.find_output_in_direction(&ws.output.get(), direction)
{
target.take_keyboard_navigation_focus(self, direction);
self.maybe_schedule_warp_mouse_to_focus();
}
return;
}
@ -821,6 +828,14 @@ impl WlSeatGlobal {
c.move_focus_from_child(self, tl.deref(), direction);
}
}
self.maybe_schedule_warp_mouse_to_focus();
}
pub fn maybe_schedule_warp_mouse_to_focus(self: &Rc<Self>) {
if self.mouse_follows_focus() {
self.warp_mouse_to_focus_skip_target_check.set(true);
self.schedule_warp_mouse_to_focus();
}
}
pub fn schedule_warp_mouse_to_focus(self: &Rc<Self>) {
@ -848,10 +863,12 @@ impl WlSeatGlobal {
{
let ws = target.ensure_workspace();
toplevel_set_workspace(&self.state, tl, &ws);
self.maybe_schedule_warp_mouse_to_focus();
} else if let Some(parent) = data.parent.get()
&& let Some(c) = parent.node_into_container()
{
c.move_child(tl, direction);
self.maybe_schedule_warp_mouse_to_focus();
}
}
@ -972,6 +989,7 @@ impl WlSeatGlobal {
}
}
self.focus_node(node);
self.maybe_schedule_warp_mouse_to_focus();
}
pub fn focus_prev(self: &Rc<Self>) {
@ -1035,6 +1053,7 @@ impl WlSeatGlobal {
n.deref()
.clone()
.node_do_focus(self, Direction::Unspecified);
self.maybe_schedule_warp_mouse_to_focus();
return;
}
}
@ -1046,6 +1065,7 @@ impl WlSeatGlobal {
n.deref()
.clone()
.node_do_focus(self, Direction::Unspecified);
self.maybe_schedule_warp_mouse_to_focus();
return;
}
}
@ -1087,6 +1107,7 @@ impl WlSeatGlobal {
&& ws.container_visible()
{
self.focus_node(ws.clone());
self.maybe_schedule_warp_mouse_to_focus();
return;
}
None
@ -1111,6 +1132,7 @@ impl WlSeatGlobal {
if let Some(n) = node {
if node_viable(&*n) {
n.node_do_focus(self, Direction::Unspecified);
self.maybe_schedule_warp_mouse_to_focus();
return;
}
}
@ -1164,6 +1186,7 @@ impl WlSeatGlobal {
};
if node.node_visible() && node.node_accepts_focus() {
node.node_do_focus(self, Direction::Unspecified);
self.maybe_schedule_warp_mouse_to_focus();
}
}
@ -1507,6 +1530,15 @@ impl WlSeatGlobal {
self.focus_follows_mouse.get()
}
pub fn set_mouse_follows_focus(&self, enabled: bool) {
self.mouse_follows_focus.set(enabled);
self.state.trigger_cci(CCI_INPUT);
}
pub fn mouse_follows_focus(&self) -> bool {
self.mouse_follows_focus.get()
}
pub fn set_fallback_output_mode(&self, fallback_output_mode: FallbackOutputMode) {
self.fallback_output_mode.set(fallback_output_mode);
self.state.trigger_cci(CCI_INPUT);
@ -2023,15 +2055,18 @@ pub async fn handle_warp_mouse_to_focus(state: Rc<State>) {
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 skip_target_check = seat.warp_mouse_to_focus_skip_target_check.take();
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;
if !skip_target_check {
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, Warp);

View file

@ -1084,6 +1084,7 @@ impl WlSeatGlobal {
}
}
self.focus_node(node);
self.maybe_schedule_warp_mouse_to_focus();
}
}

View file

@ -974,6 +974,7 @@ impl State {
}
};
self.show_workspace2(Some(seat), &ws.output.get(), &ws);
seat.maybe_schedule_warp_mouse_to_focus();
}
pub fn float_map_ws(&self) -> Rc<WorkspaceNode> {

View file

@ -563,6 +563,7 @@ pub struct Config {
pub workspace_display_order: Option<WorkspaceDisplayOrder>,
pub simple_im: Option<SimpleIm>,
pub fallback_output_mode: Option<FallbackOutputMode>,
pub mouse_follows_focus: Option<bool>,
}
#[derive(Debug, Error)]

View file

@ -154,6 +154,7 @@ impl Parser for ConfigParser<'_> {
fallback_output_mode_val,
egui_val,
clean_logs_older_than_val,
mouse_follows_focus,
),
) = ext.extract((
(
@ -214,6 +215,7 @@ impl Parser for ConfigParser<'_> {
opt(val("fallback-output-mode")),
opt(val("egui")),
opt(val("clean-logs-older-than")),
recover(opt(bol("unstable-mouse-follows-focus"))),
),
))?;
let mut keymap = None;
@ -615,6 +617,7 @@ impl Parser for ConfigParser<'_> {
workspace_display_order,
simple_im,
fallback_output_mode,
mouse_follows_focus: mouse_follows_focus.despan(),
})
}
}

View file

@ -1664,6 +1664,12 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
if let Some(f) = &config.egui.monospace_fonts {
set_egui_monospace_fonts(f.iter().map(|s| &**s));
}
if let Some(mouse_follows_focus) = config.mouse_follows_focus {
#[expect(deprecated)]
persistent
.seat
.unstable_set_mouse_follows_focus(mouse_follows_focus);
}
}
fn create_command(exec: &Exec) -> Command {

View file

@ -1061,6 +1061,10 @@
"type": "boolean",
"description": "Configures whether moving the mouse over a window automatically moves the keyboard\nfocus to that window.\n\nThe default is `true`.\n"
},
"unstable-mouse-follows-focus": {
"type": "boolean",
"description": "Configures whether the mouse cursor is automatically centered on the active window\nwhen focus changes via keyboard commands.\n\nWhen enabled, the cursor will be automatically positioned to the center of the\nactive window when focus changes through keyboard commands such as `focus-left`,\n`focus-right`, `show-workspace`, etc.\n\nThe default is `false`.\n\nThis option is unstable due to various issues. It is not subject to the usual\nsemver guarantees.\n\n- Example:\n\n ```toml\n unstable-mouse-follows-focus = true\n ```\n"
},
"window-management-key": {
"type": "string",
"description": "Configures a key that will enable window management mode while pressed.\n\nIn window management mode, floating windows can be moved by pressing the left\nmouse button and all windows can be resize by pressing the right mouse button.\n\n- Example:\n\n ```toml\n window-management-key = \"Alt_L\"\n ```\n"

View file

@ -2076,6 +2076,28 @@ The table has the following fields:
The value of this field should be a boolean.
- `unstable-mouse-follows-focus` (optional):
Configures whether the mouse cursor is automatically centered on the active window
when focus changes via keyboard commands.
When enabled, the cursor will be automatically positioned to the center of the
active window when focus changes through keyboard commands such as `focus-left`,
`focus-right`, `show-workspace`, etc.
The default is `false`.
This option is unstable due to various issues. It is not subject to the usual
semver guarantees.
- Example:
```toml
unstable-mouse-follows-focus = true
```
The value of this field should be a boolean.
- `window-management-key` (optional):
Configures a key that will enable window management mode while pressed.

View file

@ -2865,6 +2865,27 @@ Config:
focus to that window.
The default is `true`.
unstable-mouse-follows-focus:
kind: boolean
required: false
description: |
Configures whether the mouse cursor is automatically centered on the active window
when focus changes via keyboard commands.
When enabled, the cursor will be automatically positioned to the center of the
active window when focus changes through keyboard commands such as `focus-left`,
`focus-right`, `show-workspace`, etc.
The default is `false`.
This option is unstable due to various issues. It is not subject to the usual
semver guarantees.
- Example:
```toml
unstable-mouse-follows-focus = true
```
window-management-key:
kind: string
required: false