Merge pull request #785 from Ktrompfl/warp-cursor-on-move-focus
Add mouse-refocus option to center cursor on focused window
This commit is contained in:
commit
4955c8d248
14 changed files with 134 additions and 6 deletions
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -871,6 +871,10 @@ pub enum ClientMessage<'a> {
|
|||
dx2: i32,
|
||||
dy2: i32,
|
||||
},
|
||||
SeatSetMouseFollowsFocus {
|
||||
seat: Seat,
|
||||
enabled: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1084,6 +1084,7 @@ impl WlSeatGlobal {
|
|||
}
|
||||
}
|
||||
self.focus_node(node);
|
||||
self.maybe_schedule_warp_mouse_to_focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue