srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland
diff options
context:
space:
mode:
Diffstat (limited to 'crates/wayland')
-rw-r--r--crates/wayland/src/input.rs64
-rw-r--r--crates/wayland/src/state/mod.rs29
-rw-r--r--crates/wayland/src/udev/platform.rs2
-rw-r--r--crates/wayland/src/winit/connect.rs2
4 files changed, 76 insertions, 21 deletions
diff --git a/crates/wayland/src/input.rs b/crates/wayland/src/input.rs
index 2463d02..5cce9a4 100644
--- a/crates/wayland/src/input.rs
+++ b/crates/wayland/src/input.rs
@@ -213,7 +213,11 @@ fn notify_idle_activity(state: &mut CompState) {
/// staleness trigger (rapid clicks, a tap-to-click event with no
/// intervening motion delta, etc.) one cause at a time.
#[allow(clippy::type_complexity)]
-fn refresh_pointer_focus(state: &mut CompState, pos: Point<f64, Logical>, time: u32) -> (Option<(WindowId, TitlebarHit)>, bool, bool, Option<WindowId>) {
+fn refresh_pointer_focus(
+ state: &mut CompState,
+ pos: Point<f64, Logical>,
+ time: u32,
+) -> (Option<(WindowId, TitlebarHit)>, bool, bool, Option<WindowId>, Option<(WlSurface, Point<f64, Logical>)>) {
// Checked before literally everything else, including layer-shell --
// see `elements::popup_surface_under`'s own doc comment for why: a
// popup (tooltip, dropdown, right-click menu) always renders on top of
@@ -245,17 +249,17 @@ fn refresh_pointer_focus(state: &mut CompState, pos: Point<f64, Logical>, time:
.map(|(id, _)| id)
.or_else(|| under.as_ref().and_then(|(window, _)| dwindow_wl_surface(window)).and_then(|s| state.surface_to_id.get(&s).copied()));
- let Some(pointer) = state.seat.get_pointer() else { return (hit, over_layer_surface, over_content, hovered_id) };
- if let Some((surface, loc)) = popup_hit {
- let surface_loc = pos - loc.to_f64();
- pointer.motion(state, Some((surface, loc.to_f64())), &MotionEvent { location: surface_loc, serial: SERIAL_COUNTER.next_serial(), time });
+ let Some(pointer) = state.seat.get_pointer() else { return (hit, over_layer_surface, over_content, hovered_id, None) };
+ // Freshly resolved target from ordinary hit-testing - overridden below
+ // by `pointer_button_grab` when a button is held, per its own doc
+ // comment (the Wayland implicit-grab rule).
+ let resolved: Option<(WlSurface, Point<f64, Logical>)> = if let Some((surface, loc)) = popup_hit {
+ Some((surface, loc.to_f64()))
} else if let Some((surface, loc)) = layer_hit {
- let surface_loc = pos - loc.to_f64();
- pointer.motion(state, Some((surface, loc.to_f64())), &MotionEvent { location: surface_loc, serial: SERIAL_COUNTER.next_serial(), time });
+ Some((surface, loc.to_f64()))
} else if hit.is_some() {
- // Over our own decoration - no client focus.
- pointer.motion(state, None, &MotionEvent { location: pos, serial: SERIAL_COUNTER.next_serial(), time });
- } else if let Some((window, loc)) = under {
+ None // Over our own decoration - no client focus.
+ } else if let Some((window, loc)) = &under {
// `window.toplevel()` is only ever `Some` for a native xdg-shell
// surface - it's `None` for every XWayland window, and even for a
// plain xdg-shell one it's always the *root* surface regardless of
@@ -269,21 +273,21 @@ fn refresh_pointer_focus(state: &mut CompState, pos: Point<f64, Logical>, time:
// unifies the xdg-shell/X11 cases the way `dwindow_wl_surface` does
// elsewhere in this module.
let win_relative = pos - loc.to_f64();
- if let Some((surface, offset)) = window.surface_under(win_relative, WindowSurfaceType::ALL) {
- let surface_loc = win_relative - offset.to_f64();
- let surface_origin = (loc + offset).to_f64();
- pointer.motion(state, Some((surface, surface_origin)), &MotionEvent { location: surface_loc, serial: SERIAL_COUNTER.next_serial(), time });
- }
- } else if let Some((surface, loc)) = background_layer_surface_under(state, pos) {
+ window.surface_under(win_relative, WindowSurfaceType::ALL).map(|(surface, offset)| (surface, (*loc + offset).to_f64()))
+ } else {
// Bare desktop, no window there either - last chance for a
// `Bottom`/`Background` layer surface (see
// `layer_surface_under_layers`'s doc comment) before giving up.
- let surface_loc = pos - loc.to_f64();
- pointer.motion(state, Some((surface, loc.to_f64())), &MotionEvent { location: surface_loc, serial: SERIAL_COUNTER.next_serial(), time });
+ background_layer_surface_under(state, pos).map(|(surface, loc)| (surface, loc.to_f64()))
+ };
+ let delivery = state.pointer_button_grab.clone().or_else(|| resolved.clone());
+ if let Some((surface, origin)) = delivery {
+ let surface_loc = pos - origin;
+ pointer.motion(state, Some((surface, origin)), &MotionEvent { location: surface_loc, serial: SERIAL_COUNTER.next_serial(), time });
} else {
pointer.motion(state, None, &MotionEvent { location: pos, serial: SERIAL_COUNTER.next_serial(), time });
}
- (hit, over_layer_surface, over_content, hovered_id)
+ (hit, over_layer_surface, over_content, hovered_id, resolved)
}
pub(crate) fn handle_pointer_position(state: &mut CompState, pos: Point<f64, Logical>, time: u32) {
@@ -300,7 +304,7 @@ pub(crate) fn handle_pointer_position(state: &mut CompState, pos: Point<f64, Log
return;
}
- let (hit, over_layer_surface, over_content, hovered_id) = refresh_pointer_focus(state, pos, time);
+ let (hit, over_layer_surface, over_content, hovered_id, _) = refresh_pointer_focus(state, pos, time);
let Some(pointer) = state.seat.get_pointer() else { return };
// `PointerHandle::motion`/`button`/`axis` only queue the event with the
// active grab - nothing sends `wl_pointer.frame` on its own (confirmed
@@ -734,7 +738,22 @@ pub(crate) fn handle_pointer_button(state: &mut CompState, pos: Point<f64, Logic
// A no-op from the client's perspective when focus was already correct
// (an idempotent motion event at the same surface-local coordinates it
// already has), so this costs nothing in the common case.
- refresh_pointer_focus(state, pos, time);
+ //
+ // Also where `pointer_button_grab` starts and ends - see its own doc
+ // comment. Only the 0->1 transition captures a new grab target (a
+ // second button going down mid-gesture keeps whatever the first press
+ // already locked in); only the ->0 transition releases it, and not
+ // before this press/release's own `pointer.button()` below still goes
+ // out under the (still-active) grab.
+ let (.., resolved) = refresh_pointer_focus(state, pos, time);
+ if pressed {
+ if state.pointer_buttons_held == 0 {
+ state.pointer_button_grab = resolved;
+ }
+ state.pointer_buttons_held += 1;
+ } else {
+ state.pointer_buttons_held = state.pointer_buttons_held.saturating_sub(1);
+ }
if let Some(pointer) = state.seat.get_pointer() {
let button_state = if pressed { BackendButtonState::Pressed } else { BackendButtonState::Released };
pointer.button(state, &ButtonEvent { serial, time, button, state: button_state });
@@ -743,6 +762,9 @@ pub(crate) fn handle_pointer_button(state: &mut CompState, pos: Point<f64, Logic
// `frame` does.
pointer.frame(state);
}
+ if !pressed && state.pointer_buttons_held == 0 {
+ state.pointer_button_grab = None;
+ }
}
/// Shared between the winit (nested) and udev (bare-TTY) backends: both
diff --git a/crates/wayland/src/state/mod.rs b/crates/wayland/src/state/mod.rs
index 5643ebc..084e407 100644
--- a/crates/wayland/src/state/mod.rs
+++ b/crates/wayland/src/state/mod.rs
@@ -475,6 +475,35 @@ pub(crate) struct CompState {
/// event this session's earlier per-motion diagnostic-logging
/// regression already proved is worth being careful around) needs one.
pub(crate) last_idle_notify: Option<Instant>,
+ /// The Wayland-standard "implicit grab": once a pointer button goes
+ /// down over a surface, every subsequent motion/button event - no
+ /// matter where the pointer physically ends up - has to keep being
+ /// delivered to that *same* surface until every held button is
+ /// released, not whatever a fresh hit-test happens to land on next.
+ /// Nothing implemented this before: every motion event re-ran the same
+ /// popup/layer/content hit-test from scratch and called `pointer.
+ /// motion()` with whatever it found *right now*, so the moment a real
+ /// human's hand drifted even slightly outside the pressed surface's own
+ /// bounds mid-gesture (trivially easy during a fast drag - a mouse
+ /// does not move in a perfectly straight line), that client received an
+ /// unrequested `leave` in the middle of its own gesture. GTK's drag
+ /// recognizers (a `GtkHeaderBar`'s move-the-window gesture, concretely)
+ /// treat a mid-gesture `leave` as "this isn't coherent, abort" - which
+ /// reads as "dragging this window's title bar does nothing at all,"
+ /// live-reproduced this session. `(surface, origin)` is captured once,
+ /// from the same resolution `refresh_pointer_focus` already computes,
+ /// the instant the held-button count goes from 0 to 1; `origin` is
+ /// reused for every event under the grab so surface-local coordinates
+ /// keep updating correctly even though the target surface no longer
+ /// does.
+ pub(crate) pointer_button_grab: Option<(WlSurface, Point<f64, Logical>)>,
+ /// How many pointer buttons are currently held - what actually decides
+ /// when [`Self::pointer_button_grab`] starts (0 -> 1) and ends (only
+ /// once every button, not just one of several held at once, comes back
+ /// up), matching the real Wayland implicit-grab rule instead of the
+ /// single-button assumption that would break as soon as a drag and a
+ /// second accidental button overlapped.
+ pub(crate) pointer_buttons_held: u32,
/// Windows currently mid-tween - see `WindowAnim` and `sync_geometry`'s
/// `anim_from` handling. Driven forward once per frame by
/// `tick_animations`, called from both backends' poll loops.
diff --git a/crates/wayland/src/udev/platform.rs b/crates/wayland/src/udev/platform.rs
index 14dd677..88c0d8f 100644
--- a/crates/wayland/src/udev/platform.rs
+++ b/crates/wayland/src/udev/platform.rs
@@ -165,6 +165,8 @@ impl UdevPlatform {
_idle_inhibit_manager_state: smithay::wayland::idle_inhibit::IdleInhibitManagerState::new::<CompState>(&display_handle),
idle_inhibiting_surfaces: Vec::new(),
last_idle_notify: None,
+ pointer_button_grab: None,
+ pointer_buttons_held: 0,
window_anims: HashMap::new(),
last_broadcast_flags: HashMap::new(),
last_broadcast_workspace: None,
diff --git a/crates/wayland/src/winit/connect.rs b/crates/wayland/src/winit/connect.rs
index 41e9b2a..d18a489 100644
--- a/crates/wayland/src/winit/connect.rs
+++ b/crates/wayland/src/winit/connect.rs
@@ -135,6 +135,8 @@ impl WaylandPlatform {
_idle_inhibit_manager_state: smithay::wayland::idle_inhibit::IdleInhibitManagerState::new::<CompState>(&dh),
idle_inhibiting_surfaces: Vec::new(),
last_idle_notify: None,
+ pointer_button_grab: None,
+ pointer_buttons_held: 0,
window_anims: HashMap::new(),
last_broadcast_flags: HashMap::new(),
last_broadcast_workspace: None,