From 95b15b6e0d44bcfb01749274d752515d937983f4 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Fri, 25 Sep 2026 02:59:07 +0100 Subject: [PATCH] feat: support focus follows mouse --- README.md | 1 + compositor/src/config.rs | 8 +++++++- compositor/src/input.rs | 21 +++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 91ba18e..dc9dbda 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Keyboard and direct-session touchpad settings are applied at startup: ```toml [input] +focus_follows_mouse = false xkb_layout = "us" xkb_variant = "" xkb_options = [] diff --git a/compositor/src/config.rs b/compositor/src/config.rs index 36267a9..740263c 100644 --- a/compositor/src/config.rs +++ b/compositor/src/config.rs @@ -112,6 +112,7 @@ struct BindingConfig { #[derive(Clone, Debug, PartialEq)] pub struct InputSettings { + pub focus_follows_mouse: bool, pub xkb_layout: String, pub xkb_variant: String, pub xkb_options: Vec, @@ -129,6 +130,8 @@ pub struct TouchpadSettings { #[derive(Debug, Deserialize)] struct InputConfig { + #[serde(default)] + focus_follows_mouse: bool, #[serde(default = "default_xkb_layout")] xkb_layout: String, #[serde(default)] @@ -146,6 +149,7 @@ struct InputConfig { impl Default for InputConfig { fn default() -> Self { Self { + focus_follows_mouse: false, xkb_layout: default_xkb_layout(), xkb_variant: String::new(), xkb_options: Vec::new(), @@ -454,6 +458,7 @@ impl Config { } Ok(InputSettings { + focus_follows_mouse: self.input.focus_follows_mouse, xkb_layout: self.input.xkb_layout.clone(), xkb_variant: self.input.xkb_variant.clone(), xkb_options: self.input.xkb_options.clone(), @@ -1095,12 +1100,13 @@ mod tests { #[test] fn parses_input_and_touchpad_settings() { let config = parse( - "[input]\nxkb_layout = \"us,de\"\nxkb_variant = \",nodeadkeys\"\nxkb_options = [\"grp:alt_shift_toggle\"]\nrepeat_rate = 30\nrepeat_delay_ms = 450\n\n[input.touchpad]\ntap = false\nnatural_scroll = false\ndisable_while_typing = true", + "[input]\nfocus_follows_mouse = true\nxkb_layout = \"us,de\"\nxkb_variant = \",nodeadkeys\"\nxkb_options = [\"grp:alt_shift_toggle\"]\nrepeat_rate = 30\nrepeat_delay_ms = 450\n\n[input.touchpad]\ntap = false\nnatural_scroll = false\ndisable_while_typing = true", ); assert_eq!( config.input_settings().unwrap(), InputSettings { + focus_follows_mouse: true, xkb_layout: "us,de".to_owned(), xkb_variant: ",nodeadkeys".to_owned(), xkb_options: vec!["grp:alt_shift_toggle".to_owned()], diff --git a/compositor/src/input.rs b/compositor/src/input.rs index 2c99357..8dce2d8 100644 --- a/compositor/src/input.rs +++ b/compositor/src/input.rs @@ -121,6 +121,7 @@ impl Ferese { }, ); pointer.frame(self); + self.focus_window_under_pointer(&pointer, position); self.activate_focused_pointer_constraint(&pointer); crate::backends::direct::render_all(self); } @@ -158,6 +159,7 @@ impl Ferese { }, ); pointer.frame(self); + self.focus_window_under_pointer(&pointer, location); self.activate_focused_pointer_constraint(&pointer); crate::backends::direct::render_all(self); } @@ -327,6 +329,25 @@ impl Ferese { self.relayout(); } + fn focus_window_under_pointer( + &mut self, + pointer: &PointerHandle, + position: Point, + ) { + if !self.input_settings.focus_follows_mouse || pointer.is_grabbed() { + return; + } + + let Some(window) = self.window_under_visual(position) else { + return; + }; + if self.window_ids.get(&window).copied() == self.focused_window { + return; + } + + self.focus_window_at(position, SERIAL_COUNTER.next_serial()); + } + fn clamp_pointer_position(&self, requested: Point) -> Point { self.space .outputs()