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;
|
return;
|
||||||
};
|
};
|
||||||
let pointer = self.seat.get_pointer().expect("seat has a pointer");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
let position = self.constrain_pointer_position(&pointer, position);
|
let position = self.constrain_pointer_position(&pointer, position);
|
||||||
@@ -547,7 +548,7 @@ impl Ferese {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
pointer.frame(self);
|
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);
|
self.activate_focused_pointer_constraint(&pointer);
|
||||||
crate::backends::direct::render_cursor(self);
|
crate::backends::direct::render_cursor(self);
|
||||||
}
|
}
|
||||||
@@ -593,7 +594,7 @@ impl Ferese {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
self.focus_window_under_pointer(&pointer, location);
|
self.focus_window_under_pointer(&pointer, position, location);
|
||||||
self.activate_focused_pointer_constraint(&pointer);
|
self.activate_focused_pointer_constraint(&pointer);
|
||||||
crate::backends::direct::render_cursor(self);
|
crate::backends::direct::render_cursor(self);
|
||||||
}
|
}
|
||||||
@@ -1262,6 +1263,7 @@ impl Ferese {
|
|||||||
fn focus_window_under_pointer(
|
fn focus_window_under_pointer(
|
||||||
&mut self,
|
&mut self,
|
||||||
pointer: &PointerHandle<Self>,
|
pointer: &PointerHandle<Self>,
|
||||||
|
previous_position: Point<f64, Logical>,
|
||||||
position: Point<f64, Logical>,
|
position: Point<f64, Logical>,
|
||||||
) {
|
) {
|
||||||
if self.session_lock.active() {
|
if self.session_lock.active() {
|
||||||
@@ -1303,6 +1305,15 @@ impl Ferese {
|
|||||||
if self.windows.ids().get(&window).copied() == self.focused_window {
|
if self.windows.ids().get(&window).copied() == self.focused_window {
|
||||||
return;
|
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);
|
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()),
|
Some(panel.wl_surface().clone()),
|
||||||
SERIAL_COUNTER.next_serial(),
|
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!(
|
assert_eq!(
|
||||||
keyboard.current_focus(),
|
keyboard.current_focus(),
|
||||||
Some(panel.wl_surface().clone()),
|
Some(panel.wl_surface().clone()),
|
||||||
|
|||||||
@@ -372,8 +372,8 @@ impl Ferese {
|
|||||||
mod routing_tests {
|
mod routing_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::input::{
|
use crate::input::{
|
||||||
ButtonEvent, ButtonState, Event, InputBackend, InputEvent, PointerButtonEvent,
|
AbsolutePositionEvent, ButtonEvent, ButtonState, Event, InputBackend, InputEvent,
|
||||||
SERIAL_COUNTER,
|
PointerButtonEvent, PointerMotionEvent, SERIAL_COUNTER,
|
||||||
};
|
};
|
||||||
use smithay::backend::input::UnusedEvent;
|
use smithay::backend::input::UnusedEvent;
|
||||||
use smithay::backend::winit::WinitVirtualDevice;
|
use smithay::backend::winit::WinitVirtualDevice;
|
||||||
@@ -385,8 +385,8 @@ mod routing_tests {
|
|||||||
type PointerButtonEvent = Button;
|
type PointerButtonEvent = Button;
|
||||||
type KeyboardKeyEvent = UnusedEvent;
|
type KeyboardKeyEvent = UnusedEvent;
|
||||||
type PointerAxisEvent = UnusedEvent;
|
type PointerAxisEvent = UnusedEvent;
|
||||||
type PointerMotionEvent = UnusedEvent;
|
type PointerMotionEvent = Motion;
|
||||||
type PointerMotionAbsoluteEvent = UnusedEvent;
|
type PointerMotionAbsoluteEvent = Motion;
|
||||||
type GestureSwipeBeginEvent = UnusedEvent;
|
type GestureSwipeBeginEvent = UnusedEvent;
|
||||||
type GestureSwipeUpdateEvent = UnusedEvent;
|
type GestureSwipeUpdateEvent = UnusedEvent;
|
||||||
type GestureSwipeEndEvent = UnusedEvent;
|
type GestureSwipeEndEvent = UnusedEvent;
|
||||||
@@ -407,6 +407,51 @@ mod routing_tests {
|
|||||||
type SwitchToggleEvent = UnusedEvent;
|
type SwitchToggleEvent = UnusedEvent;
|
||||||
type SpecialEvent = 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)]
|
#[derive(Debug)]
|
||||||
struct Button(bool);
|
struct Button(bool);
|
||||||
impl Event<Input> for Button {
|
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]
|
#[test]
|
||||||
fn pointer_bindings_use_modes_and_consume_releases_after_modifier_changes() {
|
fn pointer_bindings_use_modes_and_consume_releases_after_modifier_changes() {
|
||||||
if !crate::startup_tests::private_runtime(
|
if !crate::startup_tests::private_runtime(
|
||||||
|
|||||||
@@ -9,9 +9,11 @@
|
|||||||
| `repeat-rate` | integer > 0 | `25` | Repeats per second |
|
| `repeat-rate` | integer > 0 | `25` | Repeats per second |
|
||||||
| `repeat-delay-ms` | integer ≥ 0 | `600` | Delay before repeat |
|
| `repeat-delay-ms` | integer ≥ 0 | `600` | Delay before repeat |
|
||||||
|
|
||||||
With focus-follows-mouse enabled, hovering a visible part of a window changes
|
With focus-follows-mouse enabled, moving into a visible part of another window
|
||||||
keyboard focus without raising it or moving the scrolling viewport. Clicking or
|
changes keyboard focus without raising it or moving the scrolling viewport.
|
||||||
using directional keyboard focus brings the selected window into view.
|
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
|
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
|
card to activate that exact window. Hover highlights the target; directional
|
||||||
|
|||||||
Reference in new issue
Block a user