Preserve shortcut focus during mouse movement
This commit is contained in:
1 parent
392e9cfeb0
commit
3d08d64c41
4 files changed
+230
-11
No files matched your search
+14
-3
@@ -531,7 +531,8 @@ impl Ferese {
|
||||
return;
|
||||
};
|
||||
let pointer = self.seat.get_pointer().expect("seat has a pointer");
|
||||
if self.capture_pointer_motion(pointer.current_location(), position, true) {
|
||||
let previous_position = pointer.current_location();
|
||||
if self.capture_pointer_motion(previous_position, position, true) {
|
||||
return;
|
||||
}
|
||||
let position = self.constrain_pointer_position(&pointer, position);
|
||||
@@ -547,7 +548,7 @@ impl Ferese {
|
||||
},
|
||||
);
|
||||
pointer.frame(self);
|
||||
self.focus_window_under_pointer(&pointer, position);
|
||||
self.focus_window_under_pointer(&pointer, previous_position, position);
|
||||
self.activate_focused_pointer_constraint(&pointer);
|
||||
crate::backends::direct::render_cursor(self);
|
||||
}
|
||||
@@ -593,7 +594,7 @@ impl Ferese {
|
||||
},
|
||||
);
|
||||
pointer.frame(self);
|
||||
self.focus_window_under_pointer(&pointer, location);
|
||||
self.focus_window_under_pointer(&pointer, position, location);
|
||||
self.activate_focused_pointer_constraint(&pointer);
|
||||
crate::backends::direct::render_cursor(self);
|
||||
}
|
||||
@@ -1262,6 +1263,7 @@ impl Ferese {
|
||||
fn focus_window_under_pointer(
|
||||
&mut self,
|
||||
pointer: &PointerHandle<Self>,
|
||||
previous_position: Point<f64, Logical>,
|
||||
position: Point<f64, Logical>,
|
||||
) {
|
||||
if self.session_lock.active() {
|
||||
@@ -1303,6 +1305,15 @@ impl Ferese {
|
||||
if self.windows.ids().get(&window).copied() == self.focused_window {
|
||||
return;
|
||||
}
|
||||
// Only entering a window changes hover focus. Motion within the same
|
||||
// window must not undo a keyboard, wheel or side-button selection.
|
||||
// Hit-test both positions against the current layout so a viewport
|
||||
// animation beneath the pointer does not count as entering a window.
|
||||
if self.window_under_visual(previous_position).as_ref() == Some(&window)
|
||||
&& self.layer_under(previous_position).is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self.focus_window_at(position, SERIAL_COUNTER.next_serial(), false);
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ fn on_demand_layer_focus_follows_opening_and_outside_clicks() {
|
||||
Some(panel.wl_surface().clone()),
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
);
|
||||
state.focus_window_under_pointer(&pointer, on_window);
|
||||
state.focus_window_under_pointer(&pointer, pointer.current_location(), on_window);
|
||||
assert_eq!(
|
||||
keyboard.current_focus(),
|
||||
Some(panel.wl_surface().clone()),
|
||||
|
||||
@@ -372,8 +372,8 @@ impl Ferese {
|
||||
mod routing_tests {
|
||||
use super::*;
|
||||
use crate::input::{
|
||||
ButtonEvent, ButtonState, Event, InputBackend, InputEvent, PointerButtonEvent,
|
||||
SERIAL_COUNTER,
|
||||
AbsolutePositionEvent, ButtonEvent, ButtonState, Event, InputBackend, InputEvent,
|
||||
PointerButtonEvent, PointerMotionEvent, SERIAL_COUNTER,
|
||||
};
|
||||
use smithay::backend::input::UnusedEvent;
|
||||
use smithay::backend::winit::WinitVirtualDevice;
|
||||
@@ -385,8 +385,8 @@ mod routing_tests {
|
||||
type PointerButtonEvent = Button;
|
||||
type KeyboardKeyEvent = UnusedEvent;
|
||||
type PointerAxisEvent = UnusedEvent;
|
||||
type PointerMotionEvent = UnusedEvent;
|
||||
type PointerMotionAbsoluteEvent = UnusedEvent;
|
||||
type PointerMotionEvent = Motion;
|
||||
type PointerMotionAbsoluteEvent = Motion;
|
||||
type GestureSwipeBeginEvent = UnusedEvent;
|
||||
type GestureSwipeUpdateEvent = UnusedEvent;
|
||||
type GestureSwipeEndEvent = UnusedEvent;
|
||||
@@ -407,6 +407,51 @@ mod routing_tests {
|
||||
type SwitchToggleEvent = UnusedEvent;
|
||||
type SpecialEvent = UnusedEvent;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Motion(f64, f64);
|
||||
|
||||
impl Event<Input> for Motion {
|
||||
fn time(&self) -> smithay::backend::input::InputTime {
|
||||
smithay::backend::input::InputTime::from_millis(1)
|
||||
}
|
||||
fn device(&self) -> WinitVirtualDevice {
|
||||
WinitVirtualDevice
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerMotionEvent<Input> for Motion {
|
||||
fn delta_x(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
fn delta_y(&self) -> f64 {
|
||||
self.1
|
||||
}
|
||||
fn delta_x_unaccel(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
fn delta_y_unaccel(&self) -> f64 {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
impl AbsolutePositionEvent<Input> for Motion {
|
||||
fn x(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
fn y(&self) -> f64 {
|
||||
self.1
|
||||
}
|
||||
fn x_transformed(&self, _width: i32) -> f64 {
|
||||
self.0
|
||||
}
|
||||
fn y_transformed(&self, _height: i32) -> f64 {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
impl smithay::backend::input::PointerMotionAbsoluteEvent<Input> for Motion {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Button(bool);
|
||||
impl Event<Input> for Button {
|
||||
@@ -430,6 +475,167 @@ mod routing_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_motion_within_a_window_preserves_shortcut_focus() {
|
||||
if !crate::startup_tests::private_runtime(
|
||||
"input::pointer::routing_tests::mouse_motion_within_a_window_preserves_shortcut_focus",
|
||||
) {
|
||||
return;
|
||||
}
|
||||
for absolute in [false, true] {
|
||||
let (mut state, mut events, _output) = crate::input::layer_focus_tests::fixture();
|
||||
state
|
||||
.reload_config_source(
|
||||
r#"
|
||||
animations { enabled #false; }
|
||||
input { focus-follows-mouse #true; }
|
||||
scrolling { default-column-width 0.3; }
|
||||
binding "Super+Shift+WheelUp" action="focus" argument="left"
|
||||
binding "Super+MouseBack" action="focus" argument="left"
|
||||
"#
|
||||
.into(),
|
||||
)
|
||||
.unwrap();
|
||||
let (first, _first_wire) =
|
||||
crate::state::test_window(&mut state, &mut events, 0xff112233);
|
||||
let (second, _second_wire) =
|
||||
crate::state::test_window(&mut state, &mut events, 0xff334455);
|
||||
let (third, _third_wire) =
|
||||
crate::state::test_window(&mut state, &mut events, 0xff556677);
|
||||
let first_id = state.windows.ids()[&first];
|
||||
let second_id = state.windows.ids()[&second];
|
||||
let third_id = state.windows.ids()[&third];
|
||||
let pointer = state.seat.get_pointer().unwrap();
|
||||
let keyboard = state.seat.get_keyboard().unwrap();
|
||||
let motion = |state: &mut Ferese, x, y| {
|
||||
let current = pointer.current_location();
|
||||
state.process_input_event::<Input>(if absolute {
|
||||
InputEvent::PointerMotionAbsolute {
|
||||
event: Motion(x, y),
|
||||
}
|
||||
} else {
|
||||
InputEvent::PointerMotion {
|
||||
event: Motion(x - current.x, y - current.y),
|
||||
}
|
||||
});
|
||||
};
|
||||
let rect = state.presented_window_rect(third_id).unwrap();
|
||||
let (x, y) = (rect.x + 20., rect.y + 20.);
|
||||
motion(&mut state, x, y);
|
||||
assert_eq!(state.focused_window, Some(third_id));
|
||||
|
||||
crate::input::binding_mode_tests::key(&mut state, 133, true); // Super.
|
||||
crate::input::binding_mode_tests::key(&mut state, 50, true); // Shift.
|
||||
assert_eq!(
|
||||
state.filter_pointer_scroll(
|
||||
"mouse".into(),
|
||||
AxisSource::Wheel,
|
||||
[0., -15.],
|
||||
[None, Some(-120.)],
|
||||
[false; 2],
|
||||
),
|
||||
[false, true]
|
||||
);
|
||||
assert_eq!(state.focused_window, Some(second_id));
|
||||
motion(&mut state, x + 1., y);
|
||||
assert_eq!(
|
||||
state.focused_window,
|
||||
Some(second_id),
|
||||
"wheel focus must survive mouse jitter"
|
||||
);
|
||||
|
||||
crate::input::binding_mode_tests::key(&mut state, 50, false);
|
||||
state.process_input_event::<Input>(InputEvent::PointerButton {
|
||||
event: Button(true),
|
||||
});
|
||||
assert_eq!(state.focused_window, Some(first_id));
|
||||
motion(&mut state, x + 2., y);
|
||||
assert_eq!(
|
||||
state.focused_window,
|
||||
Some(first_id),
|
||||
"side-button focus must survive mouse jitter"
|
||||
);
|
||||
state.process_input_event::<Input>(InputEvent::PointerButton {
|
||||
event: Button(false),
|
||||
});
|
||||
crate::input::binding_mode_tests::key(&mut state, 133, false);
|
||||
motion(&mut state, x + 3., y);
|
||||
assert_eq!(
|
||||
state.focused_window,
|
||||
Some(first_id),
|
||||
"releasing modifiers must preserve the selection"
|
||||
);
|
||||
assert_eq!(
|
||||
keyboard.current_focus(),
|
||||
Some(first.toplevel().unwrap().wl_surface().clone())
|
||||
);
|
||||
|
||||
let rect = state.presented_window_rect(second_id).unwrap();
|
||||
motion(&mut state, rect.x + 20., rect.y + 20.);
|
||||
assert_eq!(
|
||||
state.focused_window,
|
||||
Some(second_id),
|
||||
"entering a different window must focus it"
|
||||
);
|
||||
motion(&mut state, x, y);
|
||||
assert_eq!(state.focused_window, Some(third_id));
|
||||
|
||||
state.focus_direction(ferese_layout::Direction::Left);
|
||||
motion(&mut state, x + 1., y);
|
||||
assert_eq!(state.focused_window, Some(second_id));
|
||||
// A click still explicitly focuses the window under the pointer.
|
||||
state.process_input_event::<Input>(InputEvent::PointerButton {
|
||||
event: Button(true),
|
||||
});
|
||||
assert_eq!(state.focused_window, Some(third_id));
|
||||
state.process_input_event::<Input>(InputEvent::PointerButton {
|
||||
event: Button(false),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn viewport_scroll_under_pointer_does_not_restore_hover_focus() {
|
||||
if !crate::startup_tests::private_runtime(
|
||||
"input::pointer::routing_tests::viewport_scroll_under_pointer_does_not_restore_hover_focus",
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let (mut state, mut events, _output) = crate::input::layer_focus_tests::fixture();
|
||||
state.input_settings.focus_follows_mouse = true;
|
||||
let (first, _first_wire) = crate::state::test_window(&mut state, &mut events, 0xff112233);
|
||||
let (_second, _second_wire) =
|
||||
crate::state::test_window(&mut state, &mut events, 0xff334455);
|
||||
let (third, _third_wire) = crate::state::test_window(&mut state, &mut events, 0xff556677);
|
||||
let first_id = state.windows.ids()[&first];
|
||||
let rect = state
|
||||
.presented_window_rect(state.windows.ids()[&third])
|
||||
.unwrap();
|
||||
let pointer = state.seat.get_pointer().unwrap();
|
||||
state.process_input_event::<Input>(InputEvent::PointerMotionAbsolute {
|
||||
event: Motion(rect.x + 20., rect.y + 20.),
|
||||
});
|
||||
assert_eq!(
|
||||
state.window_under_visual(pointer.current_location()),
|
||||
Some(third.clone())
|
||||
);
|
||||
state.focus_direction(ferese_layout::Direction::Left);
|
||||
state.focus_direction(ferese_layout::Direction::Left);
|
||||
assert_eq!(state.focused_window, Some(first_id));
|
||||
assert_ne!(
|
||||
state.window_under_visual(pointer.current_location()),
|
||||
Some(third)
|
||||
);
|
||||
state.process_input_event::<Input>(InputEvent::PointerMotion {
|
||||
event: Motion(1., 0.),
|
||||
});
|
||||
assert_eq!(
|
||||
state.focused_window,
|
||||
Some(first_id),
|
||||
"layout movement beneath the pointer is not a hover entry"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pointer_bindings_use_modes_and_consume_releases_after_modifier_changes() {
|
||||
if !crate::startup_tests::private_runtime(
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
| `repeat-rate` | integer > 0 | `25` | Repeats per second |
|
||||
| `repeat-delay-ms` | integer ≥ 0 | `600` | Delay before repeat |
|
||||
|
||||
With focus-follows-mouse enabled, hovering a visible part of a window changes
|
||||
keyboard focus without raising it or moving the scrolling viewport. Clicking or
|
||||
using directional keyboard focus brings the selected window into view.
|
||||
With focus-follows-mouse enabled, moving into a visible part of another window
|
||||
changes keyboard focus without raising it or moving the scrolling viewport.
|
||||
If you select a window with a shortcut, moving the pointer within its current
|
||||
window keeps that selection until you cross into another window or click.
|
||||
Clicking or using directional focus brings the selected window into view.
|
||||
|
||||
In Overview (`Super+Tab`), click a window preview or a miniature inside a workspace
|
||||
card to activate that exact window. Hover highlights the target; directional
|
||||
|
||||
Reference in new issue
Block a user