Add pointer constraints

Needs more work and testing
This commit is contained in:
Ottatop 2024-04-27 17:02:19 -05:00
parent 4eb29c49dc
commit 1543b64fb8
4 changed files with 215 additions and 92 deletions

View file

@ -487,7 +487,8 @@ impl window_service_server::WindowService for WindowService {
else { else {
return; return;
}; };
let Some((pointer_focus, _)) = state.pointer_focus_target_under(pointer_location) let Some((pointer_focus, _)) =
state.pinnacle.pointer_focus_target_under(pointer_location)
else { else {
return; return;
}; };
@ -524,7 +525,8 @@ impl window_service_server::WindowService for WindowService {
else { else {
return; return;
}; };
let Some((pointer_focus, window_loc)) = state.pointer_focus_target_under(pointer_loc) let Some((pointer_focus, window_loc)) =
state.pinnacle.pointer_focus_target_under(pointer_loc)
else { else {
return; return;
}; };

View file

@ -8,13 +8,17 @@ use std::{mem, os::fd::OwnedFd, time::Duration};
use smithay::{ use smithay::{
backend::renderer::utils::{self, with_renderer_surface_state}, backend::renderer::utils::{self, with_renderer_surface_state},
delegate_compositor, delegate_data_control, delegate_data_device, delegate_fractional_scale, delegate_compositor, delegate_data_control, delegate_data_device, delegate_fractional_scale,
delegate_layer_shell, delegate_output, delegate_presentation, delegate_primary_selection, delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_presentation,
delegate_relative_pointer, delegate_seat, delegate_shm, delegate_viewporter, delegate_primary_selection, delegate_relative_pointer, delegate_seat, delegate_shm,
delegate_viewporter,
desktop::{ desktop::{
self, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, self, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output,
utils::surface_primary_scanout_output, PopupKind, WindowSurfaceType, utils::surface_primary_scanout_output, PopupKind, WindowSurfaceType,
}, },
input::{pointer::CursorImageStatus, Seat, SeatHandler, SeatState}, input::{
pointer::{CursorImageStatus, PointerHandle},
Seat, SeatHandler, SeatState,
},
output::Output, output::Output,
reexports::{ reexports::{
calloop::Interest, calloop::Interest,
@ -27,7 +31,7 @@ use smithay::{
Client, Resource, Client, Resource,
}, },
}, },
utils::{Logical, Rectangle, SERIAL_COUNTER}, utils::{Logical, Point, Rectangle, SERIAL_COUNTER},
wayland::{ wayland::{
buffer::BufferHandler, buffer::BufferHandler,
compositor::{ compositor::{
@ -37,6 +41,7 @@ use smithay::{
dmabuf, dmabuf,
fractional_scale::{self, FractionalScaleHandler}, fractional_scale::{self, FractionalScaleHandler},
output::OutputHandler, output::OutputHandler,
pointer_constraints::{with_pointer_constraint, PointerConstraintsHandler},
seat::WaylandFocus, seat::WaylandFocus,
selection::{ selection::{
data_device::{ data_device::{
@ -638,6 +643,14 @@ impl GammaControlHandler for State {
} }
delegate_gamma_control!(State); delegate_gamma_control!(State);
impl PointerConstraintsHandler for State {
fn new_constraint(&mut self, _surface: &WlSurface, pointer: &PointerHandle<Self>) {
self.pinnacle
.maybe_activate_pointer_constraint(pointer.current_location());
}
}
delegate_pointer_constraints!(State);
impl Pinnacle { impl Pinnacle {
fn position_popup(&self, popup: &PopupSurface) { fn position_popup(&self, popup: &PopupSurface) {
trace!("State::position_popup"); trace!("State::position_popup");
@ -691,4 +704,32 @@ impl Pinnacle {
state.positioner = positioner; state.positioner = positioner;
}); });
} }
// From Niri
/// Attempt to activate any pointer constraint on the pointer focus at `new_pos`.
pub fn maybe_activate_pointer_constraint(&self, new_pos: Point<f64, Logical>) {
let Some((surface, surface_loc)) = self.pointer_focus_target_under(new_pos) else {
return;
};
let Some(pointer) = self.seat.get_pointer() else {
return;
};
let Some(surface) = surface.wl_surface() else { return };
with_pointer_constraint(&surface, &pointer, |constraint| {
let Some(constraint) = constraint else { return };
if !constraint.is_active() {
return;
}
// Constraint does not apply if not within region.
if let Some(region) = constraint.region() {
let new_pos_within_surface = new_pos.to_i32_round() - surface_loc;
if !region.contains(new_pos_within_surface) {
return;
}
}
constraint.activate();
});
}
} }

View file

@ -6,7 +6,7 @@ use std::{collections::HashMap, mem::Discriminant, time::Duration};
use crate::{ use crate::{
focus::{keyboard::KeyboardFocusTarget, pointer::PointerFocusTarget}, focus::{keyboard::KeyboardFocusTarget, pointer::PointerFocusTarget},
state::WithState, state::{Pinnacle, WithState},
window::WindowElement, window::WindowElement,
}; };
use pinnacle_api_defs::pinnacle::input::v0alpha1::{ use pinnacle_api_defs::pinnacle::input::v0alpha1::{
@ -23,10 +23,15 @@ use smithay::{
keyboard::{keysyms, FilterResult, ModifiersState}, keyboard::{keysyms, FilterResult, ModifiersState},
pointer::{AxisFrame, ButtonEvent, MotionEvent, RelativeMotionEvent}, pointer::{AxisFrame, ButtonEvent, MotionEvent, RelativeMotionEvent},
}, },
reexports::input::{self, Led}, reexports::{
utils::{IsAlive, Logical, Point, SERIAL_COUNTER}, input::{self, Led},
wayland_server::protocol::wl_surface::WlSurface,
},
utils::{IsAlive, Logical, Point, Rectangle, SERIAL_COUNTER},
wayland::{ wayland::{
compositor, compositor::{self, RegionAttributes},
pointer_constraints::{with_pointer_constraint, PointerConstraint},
seat::WaylandFocus,
shell::wlr_layer::{self, KeyboardInteractivity, LayerSurfaceCachedState}, shell::wlr_layer::{self, KeyboardInteractivity, LayerSurfaceCachedState},
}, },
}; };
@ -144,23 +149,7 @@ enum KeyAction {
ReloadConfig, ReloadConfig,
} }
impl State { impl Pinnacle {
pub fn process_input_event<B: InputBackend>(&mut self, event: InputEvent<B>) {
match event {
// TODO: rest of input events
// InputEvent::DeviceAdded { device } => todo!(),
// InputEvent::DeviceRemoved { device } => todo!(),
InputEvent::Keyboard { event } => self.keyboard::<B>(event),
InputEvent::PointerMotion { event } => self.pointer_motion::<B>(event),
InputEvent::PointerMotionAbsolute { event } => self.pointer_motion_absolute::<B>(event),
InputEvent::PointerButton { event } => self.pointer_button::<B>(event),
InputEvent::PointerAxis { event } => self.pointer_axis::<B>(event),
_ => (),
}
}
/// Get the [`PointerFocusTarget`] under `point` along with its origin in the global space. /// Get the [`PointerFocusTarget`] under `point` along with its origin in the global space.
pub fn pointer_focus_target_under<P>( pub fn pointer_focus_target_under<P>(
&self, &self,
@ -171,16 +160,14 @@ impl State {
{ {
let point: Point<f64, Logical> = point.into(); let point: Point<f64, Logical> = point.into();
let output = self.pinnacle.space.outputs().find(|op| { let output = self.space.outputs().find(|op| {
self.pinnacle self.space
.space
.output_geometry(op) .output_geometry(op)
.expect("called output_geometry on unmapped output (this shouldn't happen here)") .expect("called output_geometry on unmapped output (this shouldn't happen here)")
.contains(point.to_i32_round()) .contains(point.to_i32_round())
})?; })?;
let output_geo = self let output_geo = self
.pinnacle
.space .space
.output_geometry(output) .output_geometry(output)
.expect("called output_geometry on unmapped output"); .expect("called output_geometry on unmapped output");
@ -188,7 +175,6 @@ impl State {
let mut fullscreen_and_up_split_at = 0; let mut fullscreen_and_up_split_at = 0;
for (i, win) in self for (i, win) in self
.pinnacle
.space .space
.elements() .elements()
.rev() .rev()
@ -226,7 +212,6 @@ impl State {
|windows: &[&WindowElement]| -> Option<(PointerFocusTarget, Point<i32, Logical>)> { |windows: &[&WindowElement]| -> Option<(PointerFocusTarget, Point<i32, Logical>)> {
windows.iter().find_map(|win| { windows.iter().find_map(|win| {
let loc = self let loc = self
.pinnacle
.space .space
.element_location(win) .element_location(win)
.expect("called elem loc on unmapped win") .expect("called elem loc on unmapped win")
@ -250,7 +235,6 @@ impl State {
.or_else(|| { .or_else(|| {
window_under( window_under(
&self &self
.pinnacle
.space .space
.elements() .elements()
.rev() .rev()
@ -263,7 +247,6 @@ impl State {
.or_else(|| { .or_else(|| {
window_under( window_under(
&self &self
.pinnacle
.space .space
.elements() .elements()
.rev() .rev()
@ -274,6 +257,24 @@ impl State {
}) })
.or_else(|| layer_under(&[wlr_layer::Layer::Bottom, wlr_layer::Layer::Background])) .or_else(|| layer_under(&[wlr_layer::Layer::Bottom, wlr_layer::Layer::Background]))
} }
}
impl State {
pub fn process_input_event<B: InputBackend>(&mut self, event: InputEvent<B>) {
match event {
// TODO: rest of input events
// InputEvent::DeviceAdded { device } => todo!(),
// InputEvent::DeviceRemoved { device } => todo!(),
InputEvent::Keyboard { event } => self.keyboard::<B>(event),
InputEvent::PointerMotion { event } => self.pointer_motion::<B>(event),
InputEvent::PointerMotionAbsolute { event } => self.pointer_motion_absolute::<B>(event),
InputEvent::PointerButton { event } => self.pointer_button::<B>(event),
InputEvent::PointerAxis { event } => self.pointer_axis::<B>(event),
_ => (),
}
}
/// Update the pointer focus if it's different from the previous one. /// Update the pointer focus if it's different from the previous one.
pub fn update_pointer_focus(&mut self) { pub fn update_pointer_focus(&mut self) {
@ -282,7 +283,7 @@ impl State {
}; };
let location = pointer.current_location(); let location = pointer.current_location();
let surface_under = self.pointer_focus_target_under(location); let surface_under = self.pinnacle.pointer_focus_target_under(location);
if pointer if pointer
.current_focus() .current_focus()
@ -465,16 +466,12 @@ impl State {
} }
fn pointer_button<I: InputBackend>(&mut self, event: I::PointerButtonEvent) { fn pointer_button<I: InputBackend>(&mut self, event: I::PointerButtonEvent) {
let pointer = self let Some(pointer) = self.pinnacle.seat.get_pointer() else {
.pinnacle return;
.seat };
.get_pointer() let Some(keyboard) = self.pinnacle.seat.get_keyboard() else {
.expect("Seat has no pointer"); // FIXME: handle err return;
let keyboard = self };
.pinnacle
.seat
.get_keyboard()
.expect("Seat has no keyboard"); // FIXME: handle err
let serial = SERIAL_COUNTER.next_serial(); let serial = SERIAL_COUNTER.next_serial();
@ -501,16 +498,8 @@ impl State {
return; return;
} }
// If the button was clicked, focus on the window below if exists, else
// unfocus on windows.
if button_state == ButtonState::Pressed { if button_state == ButtonState::Pressed {
if let Some((focus, _)) = self.pointer_focus_target_under(pointer_loc) { if let Some((focus, _)) = self.pinnacle.pointer_focus_target_under(pointer_loc) {
// NOTE: *Do not* set keyboard focus to an override redirect window. This leads
// | to wonky things like right-click menus not correctly getting pointer
// | clicks or showing up at all.
// TODO: use update_keyboard_focus from anvil
if let Some(window) = focus.window_for(self) { if let Some(window) = focus.window_for(self) {
self.pinnacle.raise_window(window.clone(), true); self.pinnacle.raise_window(window.clone(), true);
if let Some(output) = window.output(&self.pinnacle) { if let Some(output) = window.output(&self.pinnacle) {
@ -603,39 +592,6 @@ impl State {
pointer.frame(self); pointer.frame(self);
} }
/// Clamp pointer coordinates inside outputs.
///
/// This returns the nearest point inside an output.
fn clamp_coords(&self, pos: Point<f64, Logical>) -> Point<f64, Logical> {
if self.pinnacle.space.outputs().next().is_none() {
return pos;
}
let (pos_x, pos_y) = pos.into();
let nearest_points = self.pinnacle.space.outputs().map(|op| {
let size = self
.pinnacle
.space
.output_geometry(op)
.expect("called output_geometry on unmapped output")
.size;
let loc = op.current_location();
let pos_x = pos_x.clamp(loc.x as f64, (loc.x + size.w) as f64);
let pos_y = pos_y.clamp(loc.y as f64, (loc.y + size.h) as f64);
(pos_x, pos_y)
});
let nearest_point = nearest_points.min_by(|(x1, y1), (x2, y2)| {
f64::total_cmp(
&((pos_x - x1).powi(2) + (pos_y - y1).powi(2)).sqrt(),
&((pos_x - x2).powi(2) + (pos_y - y2).powi(2)).sqrt(),
)
});
nearest_point.map(|point| point.into()).unwrap_or(pos)
}
/// Handle an absolute pointer motion event. /// Handle an absolute pointer motion event.
/// ///
/// This *should* only be generated on the winit backend. /// This *should* only be generated on the winit backend.
@ -667,7 +623,7 @@ impl State {
self.pinnacle.output_focus_stack.set_focus(output); self.pinnacle.output_focus_stack.set_focus(output);
} }
let pointer_focus = self.pointer_focus_target_under(pointer_loc); let pointer_focus = self.pinnacle.pointer_focus_target_under(pointer_loc);
pointer.motion( pointer.motion(
self, self,
@ -689,11 +645,107 @@ impl State {
}; };
let mut pointer_loc = pointer.current_location(); let mut pointer_loc = pointer.current_location();
let mut pointer_confined_to: Option<(
WlSurface,
Point<i32, Logical>,
Option<RegionAttributes>,
)> = None;
// TODO: possibly cache the current pointer focus and location?
if let Some((surface, surface_loc)) = self.pinnacle.pointer_focus_target_under(pointer_loc)
{
if let Some(wl_surface) = surface.wl_surface() {
let mut pointer_locked_to: Option<Option<Point<f64, Logical>>> = None;
with_pointer_constraint(&wl_surface, &pointer, |constraint| {
let Some(constraint) = constraint else { return };
if !constraint.is_active() {
return;
}
let pointer_loc_relative_to_surf = surface_loc - pointer_loc.to_i32_round();
// Constraint does not apply if not within region.
if let Some(region) = constraint.region() {
if !region.contains(pointer_loc_relative_to_surf) {
return;
}
}
match &*constraint {
PointerConstraint::Confined(confined) => {
pointer_confined_to =
Some((wl_surface.clone(), surface_loc, confined.region().cloned()));
}
PointerConstraint::Locked(locked) => {
pointer_locked_to = Some(
locked
.cursor_position_hint()
.map(|hint| hint + surface_loc.to_f64()),
);
}
}
});
if let Some(lock_loc) = pointer_locked_to {
if let Some(lock_loc) = lock_loc {
if pointer_loc != lock_loc {}
}
pointer.relative_motion(
self,
Some((surface, surface_loc)),
&RelativeMotionEvent {
delta: event.delta(),
delta_unaccel: event.delta_unaccel(),
utime: event.time(),
},
);
pointer.frame(self);
return;
}
}
}
pointer_loc += event.delta(); pointer_loc += event.delta();
// clamp to screen limits // clamp to screen limits
// this event is never generated by winit // this event is never generated by winit
pointer_loc = self.clamp_coords(pointer_loc); let output_locs = self
.pinnacle
.space
.outputs()
.flat_map(|op| self.pinnacle.space.output_geometry(op));
pointer_loc = clamp_coords_inside_rects(pointer_loc, output_locs);
let surface_under = self.pinnacle.pointer_focus_target_under(pointer_loc);
if let Some((surf, surf_loc, Some(region))) = pointer_confined_to {
let mut prevent = false;
// compute closest point
let mut region_rects = Vec::<Rectangle<i32, Logical>>::new();
// TODO: ADD UNIT TEST
for (kind, mut rect) in region.rects {
rect.loc = surf_loc - rect.loc;
match kind {
compositor::RectangleKind::Add => {
region_rects.push(rect);
}
compositor::RectangleKind::Subtract => {
region_rects =
Rectangle::subtract_rects_many_in_place(region_rects, [rect]);
}
}
}
pointer_loc = clamp_coords_inside_rects(pointer_loc, region_rects);
}
if let Some(output) = self if let Some(output) = self
.pinnacle .pinnacle
@ -705,8 +757,6 @@ impl State {
self.pinnacle.output_focus_stack.set_focus(output); self.pinnacle.output_focus_stack.set_focus(output);
} }
let surface_under = self.pointer_focus_target_under(pointer_loc);
pointer.motion( pointer.motion(
self, self,
surface_under.clone(), surface_under.clone(),
@ -734,3 +784,30 @@ impl State {
} }
} }
} }
/// Clamp the given point within the given rects.
///
/// This returns the nearest point inside the rects.
fn clamp_coords_inside_rects(
pos: Point<f64, Logical>,
rects: impl IntoIterator<Item = Rectangle<i32, Logical>>,
) -> Point<f64, Logical> {
let (pos_x, pos_y) = pos.into();
let nearest_points = rects.into_iter().map(|rect| {
let loc = rect.loc;
let size = rect.size;
let pos_x = pos_x.clamp(loc.x as f64, (loc.x + size.w) as f64);
let pos_y = pos_y.clamp(loc.y as f64, (loc.y + size.h) as f64);
(pos_x, pos_y)
});
let nearest_point = nearest_points.min_by(|(x1, y1), (x2, y2)| {
f64::total_cmp(
&((pos_x - x1).powi(2) + (pos_y - y1).powi(2)).sqrt(),
&((pos_x - x2).powi(2) + (pos_y - y2).powi(2)).sqrt(),
)
});
nearest_point.map(|point| point.into()).unwrap_or(pos)
}

View file

@ -30,6 +30,7 @@ use smithay::{
dmabuf::DmabufFeedback, dmabuf::DmabufFeedback,
fractional_scale::FractionalScaleManagerState, fractional_scale::FractionalScaleManagerState,
output::OutputManagerState, output::OutputManagerState,
pointer_constraints::PointerConstraintsState,
relative_pointer::RelativePointerManagerState, relative_pointer::RelativePointerManagerState,
selection::{ selection::{
data_device::DataDeviceState, primary_selection::PrimarySelectionState, data_device::DataDeviceState, primary_selection::PrimarySelectionState,
@ -82,6 +83,7 @@ pub struct Pinnacle {
pub screencopy_manager_state: ScreencopyManagerState, pub screencopy_manager_state: ScreencopyManagerState,
pub gamma_control_manager_state: GammaControlManagerState, pub gamma_control_manager_state: GammaControlManagerState,
pub relative_pointer_manager_state: RelativePointerManagerState, pub relative_pointer_manager_state: RelativePointerManagerState,
pub pointer_constraints_state: PointerConstraintsState,
/// The state of key and mousebinds along with libinput settings /// The state of key and mousebinds along with libinput settings
pub input_state: InputState, pub input_state: InputState,
@ -280,6 +282,7 @@ impl State {
relative_pointer_manager_state: RelativePointerManagerState::new::<Self>( relative_pointer_manager_state: RelativePointerManagerState::new::<Self>(
&display_handle, &display_handle,
), ),
pointer_constraints_state: PointerConstraintsState::new::<Self>(&display_handle),
input_state: InputState::new(), input_state: InputState::new(),