mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-29 20:34:46 +01:00
Add pointer constraints
Needs more work and testing
This commit is contained in:
parent
4eb29c49dc
commit
1543b64fb8
4 changed files with 215 additions and 92 deletions
|
@ -487,7 +487,8 @@ impl window_service_server::WindowService for WindowService {
|
|||
else {
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
|
@ -524,7 +525,8 @@ impl window_service_server::WindowService for WindowService {
|
|||
else {
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
|
|
|
@ -8,13 +8,17 @@ use std::{mem, os::fd::OwnedFd, time::Duration};
|
|||
use smithay::{
|
||||
backend::renderer::utils::{self, with_renderer_surface_state},
|
||||
delegate_compositor, delegate_data_control, delegate_data_device, delegate_fractional_scale,
|
||||
delegate_layer_shell, delegate_output, delegate_presentation, delegate_primary_selection,
|
||||
delegate_relative_pointer, delegate_seat, delegate_shm, delegate_viewporter,
|
||||
delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_presentation,
|
||||
delegate_primary_selection, delegate_relative_pointer, delegate_seat, delegate_shm,
|
||||
delegate_viewporter,
|
||||
desktop::{
|
||||
self, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output,
|
||||
utils::surface_primary_scanout_output, PopupKind, WindowSurfaceType,
|
||||
},
|
||||
input::{pointer::CursorImageStatus, Seat, SeatHandler, SeatState},
|
||||
input::{
|
||||
pointer::{CursorImageStatus, PointerHandle},
|
||||
Seat, SeatHandler, SeatState,
|
||||
},
|
||||
output::Output,
|
||||
reexports::{
|
||||
calloop::Interest,
|
||||
|
@ -27,7 +31,7 @@ use smithay::{
|
|||
Client, Resource,
|
||||
},
|
||||
},
|
||||
utils::{Logical, Rectangle, SERIAL_COUNTER},
|
||||
utils::{Logical, Point, Rectangle, SERIAL_COUNTER},
|
||||
wayland::{
|
||||
buffer::BufferHandler,
|
||||
compositor::{
|
||||
|
@ -37,6 +41,7 @@ use smithay::{
|
|||
dmabuf,
|
||||
fractional_scale::{self, FractionalScaleHandler},
|
||||
output::OutputHandler,
|
||||
pointer_constraints::{with_pointer_constraint, PointerConstraintsHandler},
|
||||
seat::WaylandFocus,
|
||||
selection::{
|
||||
data_device::{
|
||||
|
@ -638,6 +643,14 @@ impl GammaControlHandler for 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 {
|
||||
fn position_popup(&self, popup: &PopupSurface) {
|
||||
trace!("State::position_popup");
|
||||
|
@ -691,4 +704,32 @@ impl Pinnacle {
|
|||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
249
src/input.rs
249
src/input.rs
|
@ -6,7 +6,7 @@ use std::{collections::HashMap, mem::Discriminant, time::Duration};
|
|||
|
||||
use crate::{
|
||||
focus::{keyboard::KeyboardFocusTarget, pointer::PointerFocusTarget},
|
||||
state::WithState,
|
||||
state::{Pinnacle, WithState},
|
||||
window::WindowElement,
|
||||
};
|
||||
use pinnacle_api_defs::pinnacle::input::v0alpha1::{
|
||||
|
@ -23,10 +23,15 @@ use smithay::{
|
|||
keyboard::{keysyms, FilterResult, ModifiersState},
|
||||
pointer::{AxisFrame, ButtonEvent, MotionEvent, RelativeMotionEvent},
|
||||
},
|
||||
reexports::input::{self, Led},
|
||||
utils::{IsAlive, Logical, Point, SERIAL_COUNTER},
|
||||
reexports::{
|
||||
input::{self, Led},
|
||||
wayland_server::protocol::wl_surface::WlSurface,
|
||||
},
|
||||
utils::{IsAlive, Logical, Point, Rectangle, SERIAL_COUNTER},
|
||||
wayland::{
|
||||
compositor,
|
||||
compositor::{self, RegionAttributes},
|
||||
pointer_constraints::{with_pointer_constraint, PointerConstraint},
|
||||
seat::WaylandFocus,
|
||||
shell::wlr_layer::{self, KeyboardInteractivity, LayerSurfaceCachedState},
|
||||
},
|
||||
};
|
||||
|
@ -144,23 +149,7 @@ enum KeyAction {
|
|||
ReloadConfig,
|
||||
}
|
||||
|
||||
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),
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
impl Pinnacle {
|
||||
/// Get the [`PointerFocusTarget`] under `point` along with its origin in the global space.
|
||||
pub fn pointer_focus_target_under<P>(
|
||||
&self,
|
||||
|
@ -171,16 +160,14 @@ impl State {
|
|||
{
|
||||
let point: Point<f64, Logical> = point.into();
|
||||
|
||||
let output = self.pinnacle.space.outputs().find(|op| {
|
||||
self.pinnacle
|
||||
.space
|
||||
let output = self.space.outputs().find(|op| {
|
||||
self.space
|
||||
.output_geometry(op)
|
||||
.expect("called output_geometry on unmapped output (this shouldn't happen here)")
|
||||
.contains(point.to_i32_round())
|
||||
})?;
|
||||
|
||||
let output_geo = self
|
||||
.pinnacle
|
||||
.space
|
||||
.output_geometry(output)
|
||||
.expect("called output_geometry on unmapped output");
|
||||
|
@ -188,7 +175,6 @@ impl State {
|
|||
let mut fullscreen_and_up_split_at = 0;
|
||||
|
||||
for (i, win) in self
|
||||
.pinnacle
|
||||
.space
|
||||
.elements()
|
||||
.rev()
|
||||
|
@ -226,7 +212,6 @@ impl State {
|
|||
|windows: &[&WindowElement]| -> Option<(PointerFocusTarget, Point<i32, Logical>)> {
|
||||
windows.iter().find_map(|win| {
|
||||
let loc = self
|
||||
.pinnacle
|
||||
.space
|
||||
.element_location(win)
|
||||
.expect("called elem loc on unmapped win")
|
||||
|
@ -250,7 +235,6 @@ impl State {
|
|||
.or_else(|| {
|
||||
window_under(
|
||||
&self
|
||||
.pinnacle
|
||||
.space
|
||||
.elements()
|
||||
.rev()
|
||||
|
@ -263,7 +247,6 @@ impl State {
|
|||
.or_else(|| {
|
||||
window_under(
|
||||
&self
|
||||
.pinnacle
|
||||
.space
|
||||
.elements()
|
||||
.rev()
|
||||
|
@ -274,6 +257,24 @@ impl State {
|
|||
})
|
||||
.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.
|
||||
pub fn update_pointer_focus(&mut self) {
|
||||
|
@ -282,7 +283,7 @@ impl State {
|
|||
};
|
||||
|
||||
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
|
||||
.current_focus()
|
||||
|
@ -465,16 +466,12 @@ impl State {
|
|||
}
|
||||
|
||||
fn pointer_button<I: InputBackend>(&mut self, event: I::PointerButtonEvent) {
|
||||
let pointer = self
|
||||
.pinnacle
|
||||
.seat
|
||||
.get_pointer()
|
||||
.expect("Seat has no pointer"); // FIXME: handle err
|
||||
let keyboard = self
|
||||
.pinnacle
|
||||
.seat
|
||||
.get_keyboard()
|
||||
.expect("Seat has no keyboard"); // FIXME: handle err
|
||||
let Some(pointer) = self.pinnacle.seat.get_pointer() else {
|
||||
return;
|
||||
};
|
||||
let Some(keyboard) = self.pinnacle.seat.get_keyboard() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
|
||||
|
@ -501,16 +498,8 @@ impl State {
|
|||
return;
|
||||
}
|
||||
|
||||
// If the button was clicked, focus on the window below if exists, else
|
||||
// unfocus on windows.
|
||||
if button_state == ButtonState::Pressed {
|
||||
if let Some((focus, _)) = self.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((focus, _)) = self.pinnacle.pointer_focus_target_under(pointer_loc) {
|
||||
if let Some(window) = focus.window_for(self) {
|
||||
self.pinnacle.raise_window(window.clone(), true);
|
||||
if let Some(output) = window.output(&self.pinnacle) {
|
||||
|
@ -603,39 +592,6 @@ impl State {
|
|||
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.
|
||||
///
|
||||
/// This *should* only be generated on the winit backend.
|
||||
|
@ -667,7 +623,7 @@ impl State {
|
|||
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(
|
||||
self,
|
||||
|
@ -689,11 +645,107 @@ impl State {
|
|||
};
|
||||
|
||||
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();
|
||||
|
||||
// clamp to screen limits
|
||||
// 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
|
||||
.pinnacle
|
||||
|
@ -705,8 +757,6 @@ impl State {
|
|||
self.pinnacle.output_focus_stack.set_focus(output);
|
||||
}
|
||||
|
||||
let surface_under = self.pointer_focus_target_under(pointer_loc);
|
||||
|
||||
pointer.motion(
|
||||
self,
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ use smithay::{
|
|||
dmabuf::DmabufFeedback,
|
||||
fractional_scale::FractionalScaleManagerState,
|
||||
output::OutputManagerState,
|
||||
pointer_constraints::PointerConstraintsState,
|
||||
relative_pointer::RelativePointerManagerState,
|
||||
selection::{
|
||||
data_device::DataDeviceState, primary_selection::PrimarySelectionState,
|
||||
|
@ -82,6 +83,7 @@ pub struct Pinnacle {
|
|||
pub screencopy_manager_state: ScreencopyManagerState,
|
||||
pub gamma_control_manager_state: GammaControlManagerState,
|
||||
pub relative_pointer_manager_state: RelativePointerManagerState,
|
||||
pub pointer_constraints_state: PointerConstraintsState,
|
||||
|
||||
/// The state of key and mousebinds along with libinput settings
|
||||
pub input_state: InputState,
|
||||
|
@ -280,6 +282,7 @@ impl State {
|
|||
relative_pointer_manager_state: RelativePointerManagerState::new::<Self>(
|
||||
&display_handle,
|
||||
),
|
||||
pointer_constraints_state: PointerConstraintsState::new::<Self>(&display_handle),
|
||||
|
||||
input_state: InputState::new(),
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue