Commit fb4bcf5b1d29
tarball6 files changed+226-20
@@ -0,0 +1,202 @@
1
+//! Pointer lock and confinement. A constraint is active while its surface2
+//! has both pointer and keyboard focus and the pointer is inside its region,3
+//! so a game that locks the mouse never traps it away from the window the4
+//! keyboard went to.5
+6
+use crate::idle::root_surface;7
+use crate::state::State;8
+use smithay::backend::input::InputTime;9
+use smithay::desktop::utils::bbox_from_surface_tree;10
+use smithay::input::pointer::PointerHandle;11
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;12
+use smithay::utils::{Logical, Point, Rectangle};13
+use smithay::wayland::compositor::{RectangleKind, RegionAttributes};14
+use smithay::wayland::pointer_constraints::{15
+ ConstraintRemove, PointerConstraint, PointerConstraintsHandler, with_pointer_constraint,16
+};17
+use smithay::wayland::seat::WaylandFocus;18
+19
+pub enum Constraint {20
+ Locked,21
+ Confined {22
+ surface: WlSurface,23
+ region: Option<RegionAttributes>,24
+ },25
+}26
+27
+impl State {28
+ pub fn pointer_constraint(&self) -> Option<Constraint> {29
+ let pointer = self.input.seat.get_pointer()?;30
+ let surface = pointer.current_focus()?;31
+ with_pointer_constraint(&surface, &pointer, |c| {32
+ let c = c.filter(|c| c.is_active())?;33
+ Some(match &*c {34
+ PointerConstraint::Locked(_) => Constraint::Locked,35
+ PointerConstraint::Confined(_) => Constraint::Confined {36
+ surface: surface.clone(),37
+ region: c.region().cloned(),38
+ },39
+ })40
+ })41
+ }42
+43
+ pub fn pointer_locked(&self) -> bool {44
+ matches!(self.pointer_constraint(), Some(Constraint::Locked))45
+ }46
+47
+ /// Where a motion from `from` to `to` ends up inside a confinement,48
+ /// at the nearest allowed point when `to` falls outside the region.49
+ pub fn confine(50
+ &self,51
+ surface: &WlSurface,52
+ region: Option<&RegionAttributes>,53
+ from: Point<f64, Logical>,54
+ to: Point<f64, Logical>,55
+ ) -> Point<f64, Logical> {56
+ let Some(origin) = self.origin_of(surface, from) else {57
+ return to;58
+ };59
+ let bounds = bbox_from_surface_tree(surface, (0, 0));60
+ let allowed = |p: Point<f64, Logical>| {61
+ bounds.to_f64().contains(p) && region.is_none_or(|r| r.contains(p.to_i32_floor()))62
+ };63
+ let local = to - origin;64
+ if allowed(local) {65
+ return to;66
+ }67
+ let rects: Vec<Rectangle<i32, Logical>> = match region {68
+ Some(r) => r69
+ .rects70
+ .iter()71
+ .filter(|(kind, _)| matches!(kind, RectangleKind::Add))72
+ .filter_map(|(_, rect)| rect.intersection(bounds))73
+ .collect(),74
+ None => vec![bounds],75
+ };76
+ let nearest = rects77
+ .iter()78
+ .map(|r| {79
+ let r = r.to_f64();80
+ Point::from((81
+ local82
+ .x83
+ .clamp(r.loc.x, (r.loc.x + r.size.w - 1.0).max(r.loc.x)),84
+ local85
+ .y86
+ .clamp(r.loc.y, (r.loc.y + r.size.h - 1.0).max(r.loc.y)),87
+ ))88
+ })89
+ .filter(|p| allowed(*p))90
+ .min_by(|a: &Point<f64, Logical>, b| {91
+ let d = |p: &Point<f64, Logical>| (p.x - local.x).hypot(p.y - local.y);92
+ d(a).total_cmp(&d(b))93
+ });94
+ nearest.map_or(from, |p| p + origin)95
+ }96
+97
+ fn origin_of(98
+ &self,99
+ surface: &WlSurface,100
+ at: Point<f64, Logical>,101
+ ) -> Option<Point<f64, Logical>> {102
+ self.surface_under(at)103
+ .filter(|(s, _)| s == surface)104
+ .map(|(_, origin)| origin)105
+ }106
+107
+ fn constraint_eligible(&self, surface: &WlSurface) -> bool {108
+ !self.services.lock.active()109
+ && self.input.drag.is_none()110
+ && self111
+ .input112
+ .seat113
+ .get_keyboard()114
+ .and_then(|k| k.current_focus())115
+ .and_then(|f| f.wl_surface().map(|s| root_surface(&s)))116
+ .is_some_and(|root| root == root_surface(surface))117
+ }118
+119
+ /// Activates the pointer focus's constraint once it may apply, and120
+ /// deactivates it when keyboard focus has moved off the surface.121
+ pub fn sync_pointer_constraint(&mut self) {122
+ let Some(pointer) = self.input.seat.get_pointer() else {123
+ return;124
+ };125
+ let Some(surface) = pointer.current_focus() else {126
+ return;127
+ };128
+ let state = with_pointer_constraint(&surface, &pointer, |c| {129
+ c.map(|c| (c.is_active(), c.region().cloned()))130
+ });131
+ let Some((active, region)) = state else {132
+ return;133
+ };134
+ let eligible = self.constraint_eligible(&surface);135
+ if active && !eligible {136
+ self.release_constraint(&surface, &pointer);137
+ } else if !active && eligible && self.pointer_in(&surface, region.as_ref()) {138
+ let locked = with_pointer_constraint(&surface, &pointer, |c| {139
+ c.inspect(|c| c.activate())140
+ .is_some_and(|c| matches!(&*c, PointerConstraint::Locked(_)))141
+ });142
+ if locked {143
+ self.input.hover = None;144
+ self.dirty.cursor = true;145
+ }146
+ }147
+ }148
+149
+ fn pointer_in(&self, surface: &WlSurface, region: Option<&RegionAttributes>) -> bool {150
+ let pos = self.input.pointer_pos;151
+ let Some(origin) = self.origin_of(surface, pos) else {152
+ return false;153
+ };154
+ region.is_none_or(|r| r.contains((pos - origin).to_i32_floor()))155
+ }156
+157
+ fn release_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle<State>) {158
+ let hint = with_pointer_constraint(surface, pointer, |c| {159
+ let c = c?;160
+ let hint = match &*c {161
+ PointerConstraint::Locked(l) => l.cursor_position_hint(),162
+ PointerConstraint::Confined(_) => None,163
+ };164
+ c.deactivate();165
+ hint166
+ });167
+ if let Some(hint) = hint {168
+ self.warp_to_hint(surface, hint);169
+ }170
+ }171
+172
+ fn warp_to_hint(&mut self, surface: &WlSurface, hint: Point<f64, Logical>) {173
+ let Some(origin) = self.origin_of(surface, self.input.pointer_pos) else {174
+ return;175
+ };176
+ let target = origin + hint;177
+ if self.origin_of(surface, target).is_some() {178
+ self.warp_pointer(target, InputTime::now());179
+ }180
+ }181
+}182
+183
+impl PointerConstraintsHandler for State {184
+ fn new_constraint(&mut self, _surface: &WlSurface, _pointer: &PointerHandle<Self>) {185
+ self.sync_pointer_constraint();186
+ }187
+188
+ fn remove_constraint(189
+ &mut self,190
+ surface: &WlSurface,191
+ _pointer: &PointerHandle<Self>,192
+ remove: ConstraintRemove,193
+ ) {194
+ if let ConstraintRemove::Destroyed(constraint) = remove195
+ && constraint.is_active()196
+ && let PointerConstraint::Locked(lock) = &constraint197
+ && let Some(hint) = lock.cursor_position_hint()198
+ {199
+ self.warp_to_hint(surface, hint);200
+ }201
+ }202
+}@@ -21,6 +21,7 @@ use smithay::wayland::image_capture_source::{
2121
};2222
use smithay::wayland::image_copy_capture::ImageCopyCaptureState;2323
use smithay::wayland::output::OutputManagerState;24
+use smithay::wayland::pointer_constraints::PointerConstraintsState;2425
use smithay::wayland::presentation::PresentationState;2526
use smithay::wayland::relative_pointer::RelativePointerManagerState;2627
use smithay::wayland::selection::data_device::DataDeviceState;@@ -72,6 +73,7 @@ pub struct Protocols {
7273
_fractional_scale: FractionalScaleManagerState,7374
_presentation: PresentationState,7475
_relative_pointer: RelativePointerManagerState,76
+ _pointer_constraints: PointerConstraintsState,7577
}7678
7779
impl Protocols {@@ -137,6 +139,7 @@ impl Protocols {
137139
_fractional_scale: FractionalScaleManagerState::new::<State>(dh),138140
_presentation: PresentationState::new::<State>(dh, Monotonic::ID as u32),139141
_relative_pointer: RelativePointerManagerState::new::<State>(dh),142
+ _pointer_constraints: PointerConstraintsState::new::<State>(dh),140143
}141144
}142145
}@@ -1,6 +1,7 @@
11
//! Input handling shared by every backend. Backends translate their events22
//! into these calls, with pointer positions in global logical coordinates.33
4
+use crate::constraints::Constraint;45
use crate::state::{Input, State};56
use crate::wm::Resizing;67
use monolith_layout::{Axis, Dir, Layout, MIN_FLOAT_SIZE, Rect, WindowId, clamp_float};@@ -41,7 +42,19 @@ impl State {
4142
}4243
Motion::Warp => Default::default(),4344
};44
- let pos = wanted;45
+ let constraint = self.pointer_constraint();46
+ if let Some(Constraint::Locked) = constraint {47
+ self.activity();48
+ let under = self.surface_under(self.input.pointer_pos);49
+ self.relative_motion(under, delta, unaccel, time);50
+ return;51
+ }52
+ let pos = match &constraint {53
+ Some(Constraint::Confined { surface, region }) => {54
+ self.confine(surface, region.as_ref(), self.input.pointer_pos, wanted)55
+ }56
+ _ => wanted,57
+ };4558
self.input.pointer_pos = pos;4659
self.follow_pointer_scale();4760
self.activity();@@ -73,6 +86,7 @@ impl State {
7386
self.input.cursor_status = CursorImageStatus::default_named();7487
self.dirty.redraw = true;7588
}89
+ self.sync_pointer_constraint();7690
}7791
7892
fn relative_motion(@@ -125,7 +139,8 @@ impl State {
125139
return;126140
}127141
}128
- if pressed == ButtonState::Pressed && !self.services.lock.active() {142
+ if pressed == ButtonState::Pressed && !self.services.lock.active() && !self.pointer_locked()143
+ {129144
focus_under_pointer(self);130145
if start_drag(self, button) {131146
return;@@ -297,6 +312,7 @@ impl State {
297312
|| !self.settings.config.general.mouse_follows_focus298313
|| self.backend.drm().is_none()299314
|| self.services.lock.active()315
+ || self.pointer_locked()300316
{301317
return;302318
}@@ -4,6 +4,7 @@ mod backend;
44
mod capture;55
mod commands;66
mod commit;7
+mod constraints;78
mod cursor;89
mod devices;910
mod drm;@@ -169,20 +169,3 @@ impl WaylandDndGrabHandler for State {
169169
}170170
}171171
}172
-173
-impl smithay::wayland::pointer_constraints::PointerConstraintsHandler for State {174
- fn new_constraint(175
- &mut self,176
- _surface: &WlSurface,177
- _pointer: &smithay::input::pointer::PointerHandle<Self>,178
- ) {179
- }180
-181
- fn cursor_position_hint(182
- &mut self,183
- _surface: &WlSurface,184
- _pointer: &smithay::input::pointer::PointerHandle<Self>,185
- _location: smithay::utils::Point<f64, smithay::utils::Logical>,186
- ) {187
- }188
-}@@ -468,8 +468,9 @@ impl State {
468468
if let Some(keyboard) = self.input.seat.get_keyboard()469469
&& keyboard.current_focus() != target470470
{471
- keyboard.set_focus(self, target, SERIAL_COUNTER.next_serial());471
+ keyboard.set_focus(self, target.clone(), SERIAL_COUNTER.next_serial());472472
}473
+ self.sync_pointer_constraint();473474
}474475
475476
pub fn reconfigure_keyboard(&mut self) {