From 2d0494eff498f0835a61c90b733533a4f96bb74f Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 13 Sep 2023 20:52:10 -0700 Subject: [PATCH] Implement pointer constraints; update to send `frame` manually --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/input/mod.rs | 199 +++++++++++++++----- src/shell/element/mod.rs | 7 + src/shell/element/stack.rs | 21 +++ src/shell/element/surface.rs | 8 + src/shell/element/window.rs | 10 + src/shell/focus/target.rs | 10 + src/shell/grabs/mod.rs | 7 + src/shell/grabs/moving.rs | 4 + src/shell/layout/floating/grabs/resize.rs | 4 + src/shell/layout/tiling/grabs/resize.rs | 5 + src/state.rs | 2 + src/utils/iced.rs | 2 + src/wayland/handlers/mod.rs | 1 + src/wayland/handlers/pointer_constraints.rs | 29 +++ 16 files changed, 267 insertions(+), 46 deletions(-) create mode 100644 src/wayland/handlers/pointer_constraints.rs diff --git a/Cargo.lock b/Cargo.lock index 0b53ed32..b75e9113 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3761,7 +3761,7 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/smithay//smithay?rev=58d5bdc#58d5bdca0d65b5f45408201770da0ce86b1fa057" +source = "git+https://github.com/smithay//smithay?rev=d3e1ef9#d3e1ef9584cb3eadec80e8961552447cb919d6aa" dependencies = [ "appendlist", "ash", diff --git a/Cargo.toml b/Cargo.toml index 6b6c5d8e..63b38964 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,4 +87,4 @@ debug = true lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/smithay//smithay", rev = "58d5bdc" } +smithay = { git = "https://github.com/smithay//smithay", rev = "d3e1ef9" } diff --git a/src/input/mod.rs b/src/input/mod.rs index 9af4c60d..0bf90fb4 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -41,7 +41,9 @@ use smithay::{ }, utils::{Logical, Point, Rectangle, Serial, SERIAL_COUNTER}, wayland::{ - keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, seat::WaylandFocus, + keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, + pointer_constraints::{with_pointer_constraint, PointerConstraint}, + seat::WaylandFocus, shell::wlr_layer::Layer as WlrLayer, }, xwayland::X11Surface, @@ -507,27 +509,71 @@ impl State { let current_output = seat.active_output(); let mut position = seat.get_pointer().unwrap().current_location(); - position += event.delta(); - let output = self + let relative_pos = self .common .shell - .outputs() - .find(|output| output.geometry().to_f64().contains(position)) - .cloned() - .unwrap_or(current_output.clone()); - if output != current_output { - for session in sessions_for_output(&self.common, ¤t_output) { - session.cursor_leave(&seat, InputType::Pointer); - } + .map_global_to_space(position, ¤t_output); + let overview = self.common.shell.overview_mode(); + let output_geometry = current_output.geometry(); + let workspace = self.common.shell.workspaces.active_mut(¤t_output); + let under = State::surface_under( + position, + relative_pos, + ¤t_output, + output_geometry, + &self.common.shell.override_redirect_windows, + overview.0.clone(), + workspace, + ); - for session in sessions_for_output(&self.common, &output) { - session.cursor_enter(&seat, InputType::Pointer); - } + let ptr = seat.get_pointer().unwrap(); - seat.set_active_output(&output); + let mut pointer_locked = false; + let mut pointer_confined = false; + let mut confine_region = None; + if let Some((surface, surface_loc)) = under + .as_ref() + .and_then(|(target, l)| Some((target.wl_surface()?, l))) + { + with_pointer_constraint(&surface, &ptr, |constraint| match constraint { + Some(constraint) if constraint.is_active() => { + // Constraint does not apply if not within region + if !constraint.region().map_or(true, |x| { + x.contains(ptr.current_location().to_i32_round() - *surface_loc) + }) { + return; + } + match &*constraint { + PointerConstraint::Locked(_locked) => { + pointer_locked = true; + } + PointerConstraint::Confined(confine) => { + pointer_confined = true; + confine_region = confine.region().cloned(); + } + } + } + _ => {} + }); } - let output_geometry = output.geometry(); + + ptr.relative_motion( + self, + under.clone(), + &RelativeMotionEvent { + delta: event.delta(), + delta_unaccel: event.delta_unaccel(), + utime: event.time(), + }, + ); + + if pointer_locked { + ptr.frame(self); + return; + } + + position += event.delta(); position.x = (output_geometry.loc.x as f64) .max(position.x) @@ -536,11 +582,17 @@ impl State { .max(position.y) .min((output_geometry.loc.y + output_geometry.size.h) as f64); - let serial = SERIAL_COUNTER.next_serial(); - let relative_pos = self.common.shell.map_global_to_space(position, &output); - let overview = self.common.shell.overview_mode(); + let output = self + .common + .shell + .outputs() + .find(|output| output.geometry().to_f64().contains(position)) + .cloned() + .unwrap_or(current_output.clone()); + let workspace = self.common.shell.workspaces.active_mut(&output); - let under = State::surface_under( + let output_geometry = output.geometry(); + let new_under = State::surface_under( position, relative_pos, &output, @@ -550,6 +602,75 @@ impl State { workspace, ); + // If confined, don't move pointer if it would go outside surface or region + if pointer_confined { + if let Some((surface, surface_loc)) = &under { + if new_under.as_ref().and_then(|(under, _)| under.wl_surface()) + != surface.wl_surface() + { + ptr.frame(self); + return; + } + if let PointerFocusTarget::Element(element) = surface { + //if !element.is_in_input_region(&(position.to_i32_round() - *surface_loc).to_f64()) { + if !element.is_in_input_region(&(position - surface_loc.to_f64())) { + ptr.frame(self); + return; + } + } + if let Some(region) = confine_region { + if !region.contains(position.to_i32_round() - *surface_loc) { + ptr.frame(self); + return; + } + } + } + } + + let serial = SERIAL_COUNTER.next_serial(); + ptr.motion( + self, + under, + &MotionEvent { + location: position, + serial, + time: event.time_msec(), + }, + ); + + // If pointer is now in a constraint region, activate it + if let Some((under, surface_location)) = + new_under.and_then(|(target, loc)| Some((target.wl_surface()?, loc))) + { + with_pointer_constraint(&under, &ptr, |constraint| match constraint { + Some(constraint) if !constraint.is_active() => { + let region = match &*constraint { + PointerConstraint::Locked(locked) => locked.region(), + PointerConstraint::Confined(confined) => confined.region(), + }; + let point = + ptr.current_location().to_i32_round() - surface_location; + if region.map_or(true, |region| region.contains(point)) { + constraint.activate(); + } + } + _ => {} + }); + } + ptr.frame(self); + + if output != current_output { + for session in sessions_for_output(&self.common, ¤t_output) { + session.cursor_leave(&seat, InputType::Pointer); + } + + for session in sessions_for_output(&self.common, &output) { + session.cursor_enter(&seat, InputType::Pointer); + } + + seat.set_active_output(&output); + } + for session in sessions_for_output(&self.common, &output) { if let Some((geometry, offset)) = seat.cursor_geometry( position.to_buffer( @@ -562,27 +683,7 @@ impl State { session.cursor_info(&seat, InputType::Pointer, geometry, offset); } } - let ptr = seat.get_pointer().unwrap(); - // Relative motion is sent first to ensure they're part of a `frame` - // TODO: Find more correct solution - ptr.relative_motion( - self, - under.clone(), - &RelativeMotionEvent { - delta: event.delta(), - delta_unaccel: event.delta_unaccel(), - utime: event.time(), - }, - ); - ptr.motion( - self, - under, - &MotionEvent { - location: position, - serial, - time: event.time_msec(), - }, - ); + #[cfg(feature = "debug")] if self.common.seats().position(|x| x == &seat).unwrap() == 0 { let location = if let Some(output) = self.common.shell.outputs.first() { @@ -632,7 +733,8 @@ impl State { session.cursor_info(&seat, InputType::Pointer, geometry, offset); } } - seat.get_pointer().unwrap().motion( + let ptr = seat.get_pointer().unwrap(); + ptr.motion( self, under, &MotionEvent { @@ -641,6 +743,7 @@ impl State { time: event.time_msec(), }, ); + ptr.frame(self); #[cfg(feature = "debug")] if self.common.seats().position(|x| x == &seat).unwrap() == 0 { let location = if let Some(output) = self.common.shell.outputs.first() { @@ -784,7 +887,8 @@ impl State { } } }; - seat.get_pointer().unwrap().button( + let ptr = seat.get_pointer().unwrap(); + ptr.button( self, &ButtonEvent { button, @@ -793,6 +897,7 @@ impl State { time: event.time_msec(), }, ); + ptr.frame(self); } } InputEvent::PointerAxis { event, .. } => { @@ -853,7 +958,9 @@ impl State { } else if event.source() == AxisSource::Finger { frame = frame.stop(Axis::Vertical); } - seat.get_pointer().unwrap().axis(self, frame); + let ptr = seat.get_pointer().unwrap(); + ptr.axis(self, frame); + ptr.frame(self); } } } @@ -1199,6 +1306,7 @@ impl State { time, }, ); + ptr.frame(self); } } } @@ -1229,6 +1337,7 @@ impl State { time, }, ); + ptr.frame(self); } } } @@ -1263,6 +1372,7 @@ impl State { time, }, ); + ptr.frame(self); } } } @@ -1298,6 +1408,7 @@ impl State { time, }, ); + ptr.frame(self); } } } diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index f649ea76..848723c0 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -906,6 +906,13 @@ impl PointerTarget for CosmicMapped { _ => {} } } + fn frame(&self, seat: &Seat, data: &mut State) { + match &self.element { + CosmicMappedInternal::Stack(s) => PointerTarget::frame(s, seat, data), + CosmicMappedInternal::Window(w) => PointerTarget::frame(w, seat, data), + _ => {} + } + } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { self.last_cursor_position.lock().unwrap().remove(&seat.id()); match &self.element { diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 913c2b4e..9449f99f 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -1169,6 +1169,27 @@ impl PointerTarget for CosmicStack { } } + fn frame(&self, seat: &Seat, data: &mut State) { + if let Some((location, serial, time)) = self + .0 + .with_program(|p| p.last_location.lock().unwrap().clone()) + { + self.pointer_leave_if_previous(seat, data, serial, time, location); + } + + match self.0.with_program(|p| p.current_focus()) { + Focus::Header => PointerTarget::frame(&self.0, seat, data), + Focus::Window => self.0.with_program(|p| { + PointerTarget::frame( + &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)], + seat, + data, + ) + }), + _ => {} + } + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { if let Some((location, serial, time)) = self .0 diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index d6506041..67b88b1b 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -757,6 +757,14 @@ impl PointerTarget for CosmicSurface { } } + fn frame(&self, seat: &Seat, data: &mut State) { + match self { + CosmicSurface::Wayland(window) => PointerTarget::frame(window, seat, data), + CosmicSurface::X11(surface) => PointerTarget::frame(surface, seat, data), + _ => unreachable!(), + } + } + fn leave( &self, seat: &Seat, diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 0d0174ed..8c7e7329 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -585,6 +585,16 @@ impl PointerTarget for CosmicWindow { } } + fn frame(&self, seat: &Seat, data: &mut State) { + match self.0.with_program(|p| p.current_focus()) { + Focus::Header => PointerTarget::frame(&self.0, seat, data), + Focus::Window => self + .0 + .with_program(|p| PointerTarget::frame(&p.window, seat, data)), + _ => {} + } + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { let previous = self.0.with_program(|p| { if let Some(sessions) = p.window.user_data().get::() { diff --git a/src/shell/focus/target.rs b/src/shell/focus/target.rs index 1dc16b2f..72d41ae7 100644 --- a/src/shell/focus/target.rs +++ b/src/shell/focus/target.rs @@ -190,6 +190,16 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::ResizeFork(f) => PointerTarget::axis(f, seat, data, frame), } } + fn frame(&self, seat: &Seat, data: &mut State) { + match self { + PointerFocusTarget::Element(w) => PointerTarget::frame(w, seat, data), + PointerFocusTarget::Fullscreen(w) => PointerTarget::frame(w, seat, data), + PointerFocusTarget::LayerSurface(l) => PointerTarget::frame(l, seat, data), + PointerFocusTarget::Popup(p) => PointerTarget::frame(p.wl_surface(), seat, data), + PointerFocusTarget::OverrideRedirect(s) => PointerTarget::frame(s, seat, data), + PointerFocusTarget::ResizeFork(f) => PointerTarget::frame(f, seat, data), + } + } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { match self { PointerFocusTarget::Element(w) => PointerTarget::leave(w, seat, data, serial, time), diff --git a/src/shell/grabs/mod.rs b/src/shell/grabs/mod.rs index f5f9fb7f..87a4a361 100644 --- a/src/shell/grabs/mod.rs +++ b/src/shell/grabs/mod.rs @@ -152,6 +152,13 @@ impl PointerGrab for ResizeGrab { } } + fn frame(&mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>) { + match self { + ResizeGrab::Floating(grab) => grab.frame(data, handle), + ResizeGrab::Tiling(grab) => grab.frame(data, handle), + } + } + fn gesture_swipe_begin( &mut self, data: &mut State, diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 441e711c..e8233b55 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -311,6 +311,10 @@ impl PointerGrab for MoveGrab { handle.axis(state, details); } + fn frame(&mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>) { + handle.frame(data) + } + fn gesture_swipe_begin( &mut self, data: &mut State, diff --git a/src/shell/layout/floating/grabs/resize.rs b/src/shell/layout/floating/grabs/resize.rs index 93e986aa..530c9a86 100644 --- a/src/shell/layout/floating/grabs/resize.rs +++ b/src/shell/layout/floating/grabs/resize.rs @@ -164,6 +164,10 @@ impl PointerGrab for ResizeSurfaceGrab { handle.axis(data, details) } + fn frame(&mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>) { + handle.frame(data) + } + fn gesture_swipe_begin( &mut self, data: &mut State, diff --git a/src/shell/layout/tiling/grabs/resize.rs b/src/shell/layout/tiling/grabs/resize.rs index 1c45eebc..d90b211b 100644 --- a/src/shell/layout/tiling/grabs/resize.rs +++ b/src/shell/layout/tiling/grabs/resize.rs @@ -102,6 +102,7 @@ impl PointerTarget for ResizeForkTarget { ) { } fn axis(&self, _seat: &Seat, _data: &mut State, _frame: AxisFrame) {} + fn frame(&self, _seat: &Seat, _data: &mut State) {} fn gesture_swipe_begin(&self, _: &Seat, _: &mut State, _: &GestureSwipeBeginEvent) {} fn gesture_swipe_update(&self, _: &Seat, _: &mut State, _: &GestureSwipeUpdateEvent) {} fn gesture_swipe_end(&self, _: &Seat, _: &mut State, _: &GestureSwipeEndEvent) {} @@ -234,6 +235,10 @@ impl PointerGrab for ResizeForkGrab { handle.axis(data, details) } + fn frame(&mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>) { + handle.frame(data) + } + fn gesture_swipe_begin( &mut self, data: &mut State, diff --git a/src/state.rs b/src/state.rs index 02a3c714..a92b6148 100644 --- a/src/state.rs +++ b/src/state.rs @@ -65,6 +65,7 @@ use smithay::{ fractional_scale::{with_fractional_scale, FractionalScaleManagerState}, keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, output::OutputManagerState, + pointer_constraints::PointerConstraintsState, pointer_gestures::PointerGesturesState, presentation::PresentationState, primary_selection::PrimarySelectionState, @@ -313,6 +314,7 @@ impl State { let kde_decoration_state = KdeDecorationState::new::(&dh, Mode::Client); let xdg_decoration_state = XdgDecorationState::new::(&dh); XWaylandKeyboardGrabState::new::(&dh); + PointerConstraintsState::new::(&dh); PointerGesturesState::new::(&dh); SecurityContextState::new::(&dh, client_has_security_context); diff --git a/src/utils/iced.rs b/src/utils/iced.rs index 34ee3f12..bd2a5a4c 100644 --- a/src/utils/iced.rs +++ b/src/utils/iced.rs @@ -461,6 +461,8 @@ impl PointerTarget for IcedEle let _ = internal.update(true); } + fn frame(&self, _seat: &Seat, _data: &mut crate::state::State) {} + fn leave( &self, _seat: &Seat, diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index d8eac626..8107ec71 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -10,6 +10,7 @@ pub mod keyboard_shortcuts_inhibit; pub mod layer_shell; pub mod output; pub mod output_configuration; +pub mod pointer_constraints; pub mod pointer_gestures; pub mod presentation; pub mod primary_selection; diff --git a/src/wayland/handlers/pointer_constraints.rs b/src/wayland/handlers/pointer_constraints.rs new file mode 100644 index 00000000..33d48d50 --- /dev/null +++ b/src/wayland/handlers/pointer_constraints.rs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::State; +use smithay::{ + delegate_pointer_constraints, + input::pointer::PointerHandle, + reexports::wayland_server::protocol::wl_surface::WlSurface, + wayland::{ + pointer_constraints::{with_pointer_constraint, PointerConstraintsHandler}, + seat::WaylandFocus, + }, +}; + +impl PointerConstraintsHandler for State { + fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle) { + // XXX region + if pointer + .current_focus() + .and_then(|x| x.wl_surface()) + .as_ref() + == Some(surface) + { + with_pointer_constraint(surface, pointer, |constraint| { + constraint.unwrap().activate(); + }); + } + } +} +delegate_pointer_constraints!(State);