Files
ltk/src/input/pointer/mod.rs
T
admin 3edd822918
CI / build + test (push) Canceled after 0s
CI / cargo audit (push) Canceled after 0s
Toplevel events name the window, apps hear the pointer hover a surface and where their blurred cards were laid out, and rows can align to the bottom
A shell could not tell the windows of an app apart, follow the mouse over one of its overlays, or tell its compositor where the translucent cards of a surface ended up. crustace's floating dock needs all three, to activate and close single windows, to magnify the icons under the pointer and to have forge blur behind its pill and its Quick Settings cards.
`ToplevelEvent::Opened` carries the handle's `identifier`, the compositor's token for that one window, and its `title`, both from `ForeignToplevelInfo`; `toplevel_opened` builds the event for the new and the updated handle. Matches on `Opened { id, app_id }` have to add the fields or `..`.
`App::on_pointer_hover( SurfaceTarget, Option<( x, y )> )` is called on every mouse motion over a surface, in its physical pixels, and with `None` from the pointer's `leave`, which ltk did not handle before. `SurfaceFocus::target` gives the `SurfaceTarget` of a focus.
`Container::blur_behind()` marks a container whose rect and largest corner radius, capped at half its shorter side, the layout pass records in surface coordinates through the `DrawCtx::origin` offset of the sub-canvases, clipped by the viewports it sits in. `commit_draw_ctx` keeps them in `FrameState`, and after a frame where they changed `draw_frame` hands them to `App::on_blur_regions( SurfaceTarget, &[ ( Rect, f32 ) ] )`, for the main surface and each overlay.
`Row::align_bottom()` pins the children to the bottom edge, so taller siblings grow upward. `docs/architecture.md` lists `on_pointer_hover` among the input hooks.
Not done. `on_blur_regions`, `blur_behind` and `align_bottom` are not in the documentation, and there are no tests for them. The regions are reported after the frame is committed, so a compositor that receives them over IPC is one frame behind a surface whose cards move.
2026-10-15 14:49:25 +02:00

108 lines
4.0 KiB
Rust

// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
//! 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<A: App> PointerHandler for AppData<A>
{
fn pointer_frame(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
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<A: App> AppData<A>
{
/// 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,
} );
}
}