From 208c2128cdc062847d4a51f71b5c4d55add04709 Mon Sep 17 00:00:00 2001 From: Skygrango Date: Wed, 13 May 2026 20:08:21 +0800 Subject: [PATCH] wayland: implement new_constraint, remove_constraint and cursor_position_hint for PointerConstraintsHandler new_constraint: check if the surface has pointer focus, keyboard focus, and if the pointer is within toplevel geometry or constraint region. If these conditions are met, then enable the constraint. remove_constraint: apply last cursor position hint that saved in seat. cursor_position_hint: save the cursor position hints provided by the client to the seat. --- src/input/mod.rs | 21 ++-- src/shell/mod.rs | 29 ++--- src/shell/seats.rs | 30 ++++- src/wayland/handlers/pointer_constraints.rs | 118 +++++++++++++++++--- 4 files changed, 156 insertions(+), 42 deletions(-) diff --git a/src/input/mod.rs b/src/input/mod.rs index 747ad172..cf08a721 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -505,12 +505,12 @@ impl State { let new_under = State::surface_under(pos, &output, shell) .map(|(target, pos)| (target, pos.as_logical())); - //We should only check size, not the surface, so that we can move the mouse over the OSD in confined mode - // if new_under.as_ref().and_then(|(under, _)| under.wl_surface()) - // != surface.wl_surface() - // { - // return (false, None); - // } + // TODO: We might need a solution that allows constraints to bypass the surface without affecting the constraints themselves + if new_under.as_ref().and_then(|(under, _)| under.wl_surface()) + != surface.wl_surface() + { + return (false, None); + } match surface { PointerFocusTarget::WlSurface { surface, .. } => { @@ -2375,11 +2375,10 @@ impl State { pointer: &PointerHandle, mut location: Point, ) { - if let Some(client) = surface.client() { - let scale = self.client_compositor_state(&client).client_scale(); - location.x /= scale; - location.y /= scale; - } + let Some(client) = surface.client() else { + return; + }; + location = location.downscale(self.client_compositor_state(&client).client_scale()); let point_and_output = { let shell = self.common.shell.read(); diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 061be6b4..66c3791f 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -737,22 +737,23 @@ impl WorkspaceSet { surface: &WlSurface, ) -> Option<(Rectangle, Point)> { let mut root = surface.clone(); - loop { - while let Some(parent) = get_parent(&root) { + + while let Some(parent) = get_parent(&root) { + root = parent; + } + + while smithay::wayland::compositor::get_role(&root) == Some(XDG_POPUP_ROLE) { + let parent = with_states(&root, |states| { + states + .data_map + .get::() + .and_then(|m| m.lock().unwrap().parent.as_ref().cloned()) + }); + if let Some(parent) = parent { root = parent; + } else { + break; } - if smithay::wayland::compositor::get_role(&root) == Some(XDG_POPUP_ROLE) - && let Some(parent) = with_states(&root, |states| { - states - .data_map - .get::() - .and_then(|m| m.lock().unwrap().parent.as_ref().cloned()) - }) - { - root = parent; - continue; - } - break; } self.sticky_layer diff --git a/src/shell/seats.rs b/src/shell/seats.rs index a4f14364..5273e97f 100644 --- a/src/shell/seats.rs +++ b/src/shell/seats.rs @@ -17,8 +17,11 @@ use smithay::{ pointer::{CursorImageAttributes, CursorImageStatus}, }, output::Output, - reexports::{input::Device as InputDevice, wayland_server::DisplayHandle}, - utils::{Buffer, IsAlive, Monotonic, Point, Rectangle, Serial, Time, Transform}, + reexports::{ + input::Device as InputDevice, + wayland_server::{DisplayHandle, protocol::wl_surface::WlSurface}, + }, + utils::{Buffer, IsAlive, Logical, Monotonic, Point, Rectangle, Serial, Time, Transform}, wayland::compositor::with_states, }; use tracing::warn; @@ -178,6 +181,9 @@ struct ActiveOutput(pub Mutex); /// The output which currently has keyboard focus struct FocusedOutput(pub Mutex>); +#[derive(Default)] +pub struct PointerConstraintHint(pub Mutex)>>); + #[derive(Default)] pub struct LastModifierChange(pub Mutex>); @@ -201,6 +207,7 @@ pub fn create_seat( userdata.insert_if_missing_threadsafe(CursorState::default); userdata.insert_if_missing_threadsafe(|| ActiveOutput(Mutex::new(output.clone()))); userdata.insert_if_missing_threadsafe(|| FocusedOutput(Mutex::new(None))); + userdata.insert_if_missing_threadsafe(PointerConstraintHint::default); userdata.insert_if_missing_threadsafe(|| Mutex::new(CursorImageStatus::default_named())); // A lot of clients bind keyboard and pointer unconditionally once on launch.. @@ -252,6 +259,8 @@ pub trait SeatExt { fn supressed_buttons(&self) -> &SupressedButtons; fn modifiers_shortcut_queue(&self) -> &ModifiersShortcutQueue; fn last_modifier_change(&self) -> Option; + fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point)>; + fn set_pointer_constraint_hint(&self, hint: Option<(WlSurface, Point)>); fn cursor_geometry( &self, @@ -337,6 +346,23 @@ impl SeatExt for Seat { .unwrap() } + fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point)> { + let lock = self.user_data().get::().unwrap(); + let mut hint = lock.0.lock().unwrap(); + // Check if alive + if let Some((ref surface, _)) = *hint + && !surface.alive() + { + *hint = None; + } + hint.clone() + } + + fn set_pointer_constraint_hint(&self, hint: Option<(WlSurface, Point)>) { + let lock = self.user_data().get::().unwrap(); + *lock.0.lock().unwrap() = hint; + } + fn cursor_geometry( &self, loc: impl Into>, diff --git a/src/wayland/handlers/pointer_constraints.rs b/src/wayland/handlers/pointer_constraints.rs index 71e4a4d1..e17c1961 100644 --- a/src/wayland/handlers/pointer_constraints.rs +++ b/src/wayland/handlers/pointer_constraints.rs @@ -1,35 +1,123 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::state::State; +use crate::{shell::CosmicSurface, state::State, utils::prelude::*}; use smithay::{ input::pointer::PointerHandle, reexports::wayland_server::protocol::wl_surface::WlSurface, utils::{Logical, Point}, - wayland::{ - pointer_constraints::{PointerConstraintsHandler, with_pointer_constraint}, - seat::WaylandFocus, - }, + wayland::{pointer_constraints::PointerConstraintsHandler, seat::WaylandFocus}, }; +pub use smithay::wayland::pointer_constraints::{PointerConstraintRef, with_pointer_constraint}; + impl PointerConstraintsHandler for State { fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle) { - // XXX region - if pointer - .current_focus() - .is_some_and(|x| x.wl_surface().as_deref() == Some(surface)) - { + let seat = self + .common + .shell + .read() + .seats + .iter() + .find(|s| s.get_pointer().as_ref() == Some(pointer)) + .cloned(); + + let (is_under, is_focused, surface_location) = if let Some(seat) = seat { + seat.set_pointer_constraint_hint(None); + let current_output = seat.active_output(); + let position = seat.get_pointer().unwrap().current_location().as_global(); + let shell = self.common.shell.read(); + let under = State::surface_under(position, ¤t_output, &shell); + let mut surface_location = None; + + let is_under = if let Some((target, target_loc)) = under + && let Some(under_surface) = target.wl_surface() + { + if *under_surface == *surface { + surface_location = Some(target_loc); + true + } else { + CosmicSurface::surface_tree_offset(surface, &under_surface).is_some_and( + |offset| { + surface_location = Some(target_loc - offset.to_f64().as_global()); + true + }, + ) + } + } else { + false + }; + + let is_focused = seat + .get_keyboard() + .and_then(|k| k.current_focus()) + .is_some_and(|f| f.has_surface(&shell, surface)); + + (is_under, is_focused, surface_location) + } else { + (false, false, None) + }; + + if is_focused && is_under { with_pointer_constraint(surface, pointer, |constraint| { - constraint.unwrap().activate(); + if let Some(constraint) = constraint { + if let Some(region) = constraint.region() { + if let Some(surface_location) = surface_location + && let position = pointer.current_location() + && let point = (position - surface_location.as_logical()).to_i32_round() + && region.contains(point) + { + constraint.activate(); + } + } else { + constraint.activate(); + } + } }); } } + fn remove_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle) { + if with_pointer_constraint(surface, pointer, |constraint| constraint.is_none()) { + let seat = self + .common + .shell + .read() + .seats + .iter() + .find(|s| s.get_pointer().as_ref() == Some(pointer)) + .cloned(); + + if let Some(seat) = seat + && let Some((hint_surface, hint_location)) = seat.pointer_constraint_hint() + && hint_surface == *surface + { + self.apply_cursor_hint(surface, pointer, hint_location); + seat.set_pointer_constraint_hint(None); + } + } + } + fn cursor_position_hint( &mut self, - _surface: &WlSurface, - _pointer: &PointerHandle, - _location: Point, + surface: &WlSurface, + pointer: &PointerHandle, + location: Point, ) { - // TODO + if with_pointer_constraint(surface, pointer, |constraint| { + constraint.is_some_and(|c| c.is_active()) + }) { + let seat = self + .common + .shell + .read() + .seats + .iter() + .find(|s| s.get_pointer().as_ref() == Some(pointer)) + .cloned(); + + if let Some(seat) = seat { + seat.set_pointer_constraint_hint(Some((surface.clone(), location))); + } + } } }