From 1b065d91468b84f68881aaf66993c03f2145c7b6 Mon Sep 17 00:00:00 2001 From: 4Luke4 <39967126+4Luke4@users.noreply.github.com> Date: Thu, 23 Jul 2026 14:06:04 +0200 Subject: [PATCH] Fix Android pointer button state recovery --- src/android/backend/wayland/event_handler.rs | 288 +++++++++++-------- 1 file changed, 169 insertions(+), 119 deletions(-) diff --git a/src/android/backend/wayland/event_handler.rs b/src/android/backend/wayland/event_handler.rs index 4eafb43..a40bac5 100644 --- a/src/android/backend/wayland/event_handler.rs +++ b/src/android/backend/wayland/event_handler.rs @@ -5,6 +5,7 @@ use crate::android::{ write_guest_output_state, CentralizedEvent, WaylandBackend, }, }; +use smithay::backend::input::ButtonState; use smithay::backend::renderer::element::surface::{ render_elements_from_surface_tree, WaylandSurfaceRenderElement, }; @@ -13,9 +14,7 @@ use smithay::backend::renderer::gles::GlesRenderer; use smithay::backend::renderer::utils::draw_render_elements; use smithay::backend::renderer::{Color32F, Frame, Renderer}; use smithay::input::keyboard::FilterResult; -use smithay::backend::input::ButtonState; use smithay::input::pointer; -use smithay::reexports::wayland_server::protocol::wl_pointer::ButtonState as WlButtonState; use smithay::utils::{Point, Rectangle, Transform, SERIAL_COUNTER}; use smithay::wayland::shell::xdg::ToplevelSurface; use smithay::{ @@ -25,7 +24,10 @@ use smithay::{ }, output::{Mode, Scale}, }; -use std::sync::Arc; +use std::{ + collections::HashSet, + sync::{Arc, LazyLock, Mutex}, +}; use winit::event_loop::{ActiveEventLoop, ControlFlow}; /// Linux input event code for the left mouse button (`BTN_LEFT`). @@ -33,6 +35,15 @@ const BTN_LEFT: u32 = 0x110; /// How far a finger must travel before a touch becomes a drag (press-and-hold) rather than a tap. const TAP_DRAG_THRESHOLD_PX: f64 = 25.0; +/// Physical pointer buttons currently held by Android. +/// +/// Android's `button_state()` describes the state *after* an event. On some devices a button +/// release is therefore delivered with an empty mask and winit reports it as a left-button +/// release. Retaining the compositor-side state lets us reconcile that malformed release with +/// the one button that is actually held, preventing a permanent Wayland pointer grab. +static PRESSED_POINTER_BUTTONS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); + /** * As we currently use Xwayland, there is only 1 surface */ @@ -45,11 +56,21 @@ fn get_surface(state: &State) -> Option { .cloned() } -fn pointer_focus(state: &State) -> Option<(smithay::reexports::wayland_server::protocol::wl_surface::WlSurface, Point)> { +fn pointer_focus( + state: &State, +) -> Option<( + smithay::reexports::wayland_server::protocol::wl_surface::WlSurface, + Point, +)> { get_surface(state).map(|surface| (surface.wl_surface().clone(), (0f64, 0f64).into())) } -fn emit_pointer_motion(compositor: &mut crate::android::backend::wayland::Compositor, x: f64, y: f64, time: u32) { +fn emit_pointer_motion( + compositor: &mut crate::android::backend::wayland::Compositor, + x: f64, + y: f64, + time: u32, +) { let pointer = compositor.pointer.clone(); let state = &mut compositor.state; if let Some(focus) = pointer_focus(state) { @@ -67,9 +88,7 @@ fn emit_pointer_motion(compositor: &mut crate::android::backend::wayland::Compos } } -/// Press the left button. Also moves keyboard focus to the surface under the pointer. -fn emit_pointer_press(compositor: &mut crate::android::backend::wayland::Compositor, time: u32) { - let pointer = compositor.pointer.clone(); +fn focus_keyboard(compositor: &mut crate::android::backend::wayland::Compositor) { let state = &mut compositor.state; if let Some(surface) = get_surface(state) { compositor.keyboard.set_focus( @@ -78,39 +97,96 @@ fn emit_pointer_press(compositor: &mut crate::android::backend::wayland::Composi SERIAL_COUNTER.next_serial().into(), ); } +} - let serial = SERIAL_COUNTER.next_serial(); +fn emit_pointer_button( + compositor: &mut crate::android::backend::wayland::Compositor, + button: u32, + state: ButtonState, + time: u32, +) { + let pointer = compositor.pointer.clone(); + let compositor_state = &mut compositor.state; pointer.button( - state, + compositor_state, &pointer::ButtonEvent { - button: BTN_LEFT, - state: ButtonState::Pressed, - serial, + button, + state, + serial: SERIAL_COUNTER.next_serial(), time, }, ); - pointer.frame(state); + pointer.frame(compositor_state); +} + +/// Press the left button. Also moves keyboard focus to the surface under the pointer. +fn emit_pointer_press( + compositor: &mut crate::android::backend::wayland::Compositor, + time: u32, +) { + focus_keyboard(compositor); + emit_pointer_button(compositor, BTN_LEFT, ButtonState::Pressed, time); } /// Release the left button. -fn emit_pointer_release(compositor: &mut crate::android::backend::wayland::Compositor, time: u32) { - let pointer = compositor.pointer.clone(); - let state = &mut compositor.state; - let serial = SERIAL_COUNTER.next_serial(); - pointer.button( - state, - &pointer::ButtonEvent { - button: BTN_LEFT, - state: ButtonState::Released, - serial, - time, - }, - ); - pointer.frame(state); +fn emit_pointer_release( + compositor: &mut crate::android::backend::wayland::Compositor, + time: u32, +) { + emit_pointer_button(compositor, BTN_LEFT, ButtonState::Released, time); +} + +/// Release every physical pointer button known to be held. +fn release_physical_pointer_buttons( + compositor: &mut crate::android::backend::wayland::Compositor, + time: u32, +) { + let buttons = { + let mut pressed = PRESSED_POINTER_BUTTONS + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + pressed.drain().collect::>() + }; + + for button in buttons { + emit_pointer_button(compositor, button, ButtonState::Released, time); + } +} + +/// Reconcile an Android/winit button event with the compositor's known button state. +fn reconcile_pointer_button(button: u32, state: ButtonState) -> u32 { + let mut pressed = PRESSED_POINTER_BUTTONS + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + + match state { + ButtonState::Pressed => { + pressed.insert(button); + button + } + ButtonState::Released => { + let resolved = if pressed.contains(&button) { + button + } else if pressed.len() == 1 { + // Android reports an empty post-release mask on several devices. winit maps that + // to BTN_LEFT, so use the sole held button as the real release target. + *pressed.iter().next().expect("set length was checked") + } else { + button + }; + pressed.remove(&resolved); + resolved + } + } } /// A full tap: move to the location, then a press immediately followed by a release. -fn emit_pointer_click(compositor: &mut crate::android::backend::wayland::Compositor, x: f64, y: f64, time: u32) { +fn emit_pointer_click( + compositor: &mut crate::android::backend::wayland::Compositor, + x: f64, + y: f64, + time: u32, +) { emit_pointer_motion(compositor, x, y, time); emit_pointer_press(compositor, time); emit_pointer_release(compositor, time); @@ -130,23 +206,29 @@ pub fn handle(event: CentralizedEvent, backend: &mut WaylandBackend, event_loop: return; } - // Redraw the application. - // - // It's preferable for applications that do not render continuously to render in - // this event rather than in AboutToWait, since rendering in here allows - // the program to gracefully handle redraws requested by the OS. - - // Draw. - - // Queue a RedrawRequested event. - // - // You only need to call this if you've determined that you need to redraw in - // applications which do not always need to. Applications that redraw continuously - // can render here instead. if let Some(winit) = backend.graphic_renderer.as_ref() { winit.window().request_redraw(); } } + CentralizedEvent::Focus(focused) => { + if !focused { + let time = backend.compositor.start_time.elapsed().as_millis() as u32; + + // Android can interrupt a gesture without delivering its release/cancel event. + // Unwind both synthesized touch drags and physical mouse grabs before the app is + // backgrounded so input works immediately when focus returns. + if backend.pointer_pressed { + emit_pointer_release(&mut backend.compositor, time); + backend.pointer_pressed = false; + } + release_physical_pointer_buttons(&mut backend.compositor, time); + backend.touch_points.clear(); + backend.scroll_centroid = None; + backend.touch_gesture_was_multi_touch = false; + backend.touch_down_position = None; + backend.key_counter = 0; + } + } CentralizedEvent::Input(event) => match event { InputEvent::Keyboard { event } => { let compositor = &mut backend.compositor; @@ -159,38 +241,32 @@ pub fn handle(event: CentralizedEvent, backend: &mut WaylandBackend, event_loop: event.state(), serial, time, - |_, _, _| { - // - FilterResult::Forward - }, + |_, _, _| FilterResult::Forward, ); } InputEvent::TouchDown { event } => { - // Just move the cursor. Defer the button press until the finger moves - // (a drag) or lifts (a tap), so a second finger landing for a scroll - // doesn't leave a stray press held down. - emit_pointer_motion(&mut backend.compositor, event.x(), event.y(), event.time_msec()); + emit_pointer_motion( + &mut backend.compositor, + event.x(), + event.y(), + event.time_msec(), + ); } InputEvent::TouchMotion { event } => { let time = event.time_msec(); let (x, y) = (event.x(), event.y()); - // Once the finger travels past the threshold, press the button so the - // motion that follows reads as a drag. The centralizer only emits this - // event for a genuine single-finger gesture (never the leftover finger of - // a two-finger scroll), so this can't be mistaken for a scroll. if !backend.pointer_pressed { let start = backend.touch_down_position; let far_enough = start .map(|s| { let dx = s.x - x; let dy = s.y - y; - dx * dx + dy * dy > TAP_DRAG_THRESHOLD_PX * TAP_DRAG_THRESHOLD_PX + dx * dx + dy * dy + > TAP_DRAG_THRESHOLD_PX * TAP_DRAG_THRESHOLD_PX }) .unwrap_or(false); if far_enough { - // Anchor the drag at where the finger first landed so the grab / - // selection starts there, not where we crossed the threshold. if let Some(s) = start { emit_pointer_motion(&mut backend.compositor, s.x, s.y, time); } @@ -206,11 +282,9 @@ pub fn handle(event: CentralizedEvent, backend: &mut WaylandBackend, event_loop: emit_pointer_motion(&mut backend.compositor, event.x, event.y, time); if backend.pointer_pressed { - // End of a drag. emit_pointer_release(&mut backend.compositor, time); backend.pointer_pressed = false; } else if event.emit_click { - // A tap that never became a drag → synthesize a click. emit_pointer_click(&mut backend.compositor, event.x, event.y, time); } } @@ -239,35 +313,17 @@ pub fn handle(event: CentralizedEvent, backend: &mut WaylandBackend, event_loop: pointer.frame(&mut compositor.state); } InputEvent::PointerButton { event, .. } => { - let serial = SERIAL_COUNTER.next_serial(); - let button = event.button_code(); - - let state = WlButtonState::from(event.state()); - - let compositor = &mut backend.compositor; - let pointer = compositor.pointer.clone(); - - if let Some(surface) = get_surface(&compositor.state) { - compositor.keyboard.set_focus( - &mut compositor.state, - Some(surface.wl_surface().clone()), - 0.into(), - ); - } - pointer.button( - &mut compositor.state, - &pointer::ButtonEvent { - button, - state: state.try_into().unwrap(), - serial, - time: event.time_msec(), - }, + let state = event.state(); + let button = reconcile_pointer_button(event.button_code(), state); + focus_keyboard(&mut backend.compositor); + emit_pointer_button( + &mut backend.compositor, + button, + state, + event.time_msec(), ); - pointer.frame(&mut compositor.state); } InputEvent::PointerAxis { event } => { - // A scroll means a second finger landed; drop any button the first - // finger may have pressed so we don't scroll with it held. if backend.pointer_pressed { emit_pointer_release(&mut backend.compositor, event.time_msec()); backend.pointer_pressed = false; @@ -281,40 +337,37 @@ pub fn handle(event: CentralizedEvent, backend: &mut WaylandBackend, event_loop: let horizontal_amount_discrete = event.amount_v120(Axis::Horizontal); let vertical_amount_discrete = event.amount_v120(Axis::Vertical); - { - let mut frame = - pointer::AxisFrame::new(event.time_msec()).source(event.source()); - if horizontal_amount != 0.0 { - frame = frame.relative_direction( - Axis::Horizontal, - event.relative_direction(Axis::Horizontal), - ); - frame = frame.value(Axis::Horizontal, horizontal_amount); - if let Some(discrete) = horizontal_amount_discrete { - frame = frame.v120(Axis::Horizontal, discrete as i32); - } - } - if vertical_amount != 0.0 { - frame = frame.relative_direction( - Axis::Vertical, - event.relative_direction(Axis::Vertical), - ); - frame = frame.value(Axis::Vertical, vertical_amount); - if let Some(discrete) = vertical_amount_discrete { - frame = frame.v120(Axis::Vertical, discrete as i32); - } - } - if event.amount(Axis::Horizontal) == Some(0.0) { - frame = frame.stop(Axis::Horizontal); + let mut frame = pointer::AxisFrame::new(event.time_msec()).source(event.source()); + if horizontal_amount != 0.0 { + frame = frame.relative_direction( + Axis::Horizontal, + event.relative_direction(Axis::Horizontal), + ); + frame = frame.value(Axis::Horizontal, horizontal_amount); + if let Some(discrete) = horizontal_amount_discrete { + frame = frame.v120(Axis::Horizontal, discrete as i32); } - if event.amount(Axis::Vertical) == Some(0.0) { - frame = frame.stop(Axis::Vertical); + } + if vertical_amount != 0.0 { + frame = frame.relative_direction( + Axis::Vertical, + event.relative_direction(Axis::Vertical), + ); + frame = frame.value(Axis::Vertical, vertical_amount); + if let Some(discrete) = vertical_amount_discrete { + frame = frame.v120(Axis::Vertical, discrete as i32); } - let compositor = &mut backend.compositor; - let pointer = compositor.pointer.clone(); - pointer.axis(&mut compositor.state, frame); - pointer.frame(&mut compositor.state); } + if event.amount(Axis::Horizontal) == Some(0.0) { + frame = frame.stop(Axis::Horizontal); + } + if event.amount(Axis::Vertical) == Some(0.0) { + frame = frame.stop(Axis::Vertical); + } + let compositor = &mut backend.compositor; + let pointer = compositor.pointer.clone(); + pointer.axis(&mut compositor.state, frame); + pointer.frame(&mut compositor.state); } _ => {} }, @@ -383,7 +436,6 @@ fn redraw(backend: &mut WaylandBackend) -> Result<(), String> { .map_err(|error| format!("Failed to clear frame: {error:?}"))?; draw_render_elements(&mut frame, 1.0, &elements, &[damage]) .map_err(|error| format!("Failed to draw render elements: {error:?}"))?; - // We rely on the nested compositor to do the sync for us. let _ = frame .finish() .map_err(|error| format!("Failed to finish frame: {error:?}"))?; @@ -418,8 +470,6 @@ fn redraw(backend: &mut WaylandBackend) -> Result<(), String> { .map_err(|error| format!("Failed to flush clients: {error}"))?; } - // It is important that all events on the display have been dispatched and flushed to clients - // before swapping buffers because this operation may block. winit .submit(Some(&[damage])) .map_err(|error| format!("Failed to submit frame: {error}"))?;