// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. //! Wayland pointer → ltk dispatch. //! //! Each `wl_pointer` event (motion / press / release / axis) translates //! into a call on the surface's [`GestureState`](super::gesture::GestureState) //! plus the follow-up side-effects that can only live at the //! [`AppData`] level (pending-message push, surface redraw, dirty-cache //! invalidation, window move). The pointer handler adds three things //! touch does not have: //! //! * **Hover tracking** — motion updates `SurfaceState::hovered_idx` //! before delegating the motion to the gesture machine, since the //! hovered widget drives visual state independent of whether a press //! is active. //! * **Title-bar interaction** — a press inside the client-side title //! bar either closes the window (close button hit) or initiates a //! compositor-driven window move. Neither path goes through the //! gesture machine. //! * **Wheel / axis scroll** — routed directly into the per-viewport //! `scroll_offsets` map; axis events do not have a press/release //! lifecycle so they bypass the state machine entirely. use smithay_client_toolkit::seat::pointer::{ PointerEvent, PointerEventKind, PointerHandler }; use smithay_client_toolkit::reexports::client:: { protocol::wl_pointer::WlPointer, Connection, QueueHandle, }; use crate::app::App; use crate::event_loop::{ AppData, SurfaceFocus }; pub( crate ) mod enter; pub( crate ) mod motion; pub( crate ) mod press; pub( crate ) mod release; pub( crate ) mod scroll; pub( crate ) mod helpers; impl PointerHandler for AppData { fn pointer_frame( &mut self, conn: &Connection, qh: &QueueHandle, pointer: &WlPointer, events: &[PointerEvent], ) { for event in events { if self.is_secondary_surface( &event.surface ) { continue; } let focus = self.focus_for_surface( &event.surface ) .unwrap_or( SurfaceFocus::Main ); self.pointer_focus = focus; self.report_mouse_button( focus, event ); match event.kind { PointerEventKind::Enter { .. } => self.on_pointer_enter( conn, qh, pointer, event ), PointerEventKind::Leave { .. } => self.app.on_pointer_hover( focus.target(), None ), PointerEventKind::Motion { .. } => self.on_pointer_motion( conn, qh, pointer, event ), PointerEventKind::Press { button: 0x110, .. } => self.on_pointer_left_press( conn, qh, pointer, event ), PointerEventKind::Press { button: 0x111, .. } => self.on_pointer_right_click( conn, qh, pointer, event ), PointerEventKind::Press { button: 0x112, .. } => self.on_pointer_middle_press( qh, event ), // BTN_SIDE / BTN_BACK: the mouse's back button. PointerEventKind::Press { button: 0x113 | 0x116, .. } => self.handle_back( focus, qh ), PointerEventKind::Release { button: 0x110, .. } => self.on_pointer_release( conn, qh, pointer, event ), PointerEventKind::Axis { .. } => self.on_pointer_axis( conn, qh, pointer, event ), _ => {} } } } } impl AppData { /// Hand a press or release of the left, middle or right button to [`App::on_mouse_button`]. fn report_mouse_button( &mut self, focus: SurfaceFocus, event: &PointerEvent ) { let ( code, pressed ) = match event.kind { PointerEventKind::Press { button, .. } => ( button, true ), PointerEventKind::Release { button, .. } => ( button, false ), _ => return, }; let button = match code { 0x110 => crate::app::MouseButton::Left, 0x111 => crate::app::MouseButton::Right, 0x112 => crate::app::MouseButton::Middle, _ => return, }; let pos = self.surface( focus ).to_physical( event.position.0, event.position.1 ); self.app.on_mouse_button( &crate::app::MouseButtonEvent { button, x: pos.x, y: pos.y, pressed, ctrl: self.ctrl_pressed, shift: self.shift_pressed, alt: self.alt_pressed, } ); } }