Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/pointer_constraint.rs (9.8K)
1 use crate::ffi;
2 use crate::seat::{Seat, Focus};
3 use crate::server::{WlListener, wl_listener_remove, wl_signal_add};
4
5 #[derive(Clone, Copy)]
6 pub enum PointerConstraintState {
7 Inactive,
8 Active {
9 node: *mut ffi::wlr_scene_node,
10 sx: f64,
11 sy: f64,
12 },
13 }
14
15 pub struct PointerConstraint {
16 pub wlr_constraint: *mut ffi::wlr_pointer_constraint_v1,
17 pub state: PointerConstraintState,
18 pub destroy_listener: ffi::wl_listener,
19 pub commit_listener: ffi::wl_listener,
20 pub node_destroy_listener: ffi::wl_listener,
21 }
22
23 impl PointerConstraint {
24 pub unsafe fn create(wlr_constraint: *mut ffi::wlr_pointer_constraint_v1) -> *mut Self {
25 let constraint = Box::into_raw(Box::new(Self {
26 wlr_constraint,
27 state: PointerConstraintState::Inactive,
28 destroy_listener: std::mem::zeroed(),
29 commit_listener: std::mem::zeroed(),
30 node_destroy_listener: std::mem::zeroed(),
31 }));
32
33 (*wlr_constraint).data = constraint as *mut _;
34
35 let destroy_listener_ptr = &mut (*constraint).destroy_listener as *mut ffi::wl_listener as *mut WlListener;
36 (*destroy_listener_ptr).notify = Some(handle_destroy);
37
38 let commit_listener_ptr = &mut (*constraint).commit_listener as *mut ffi::wl_listener as *mut WlListener;
39 (*commit_listener_ptr).notify = Some(handle_commit);
40
41 let node_destroy_listener_ptr = &mut (*constraint).node_destroy_listener as *mut ffi::wl_listener as *mut WlListener;
42 (*node_destroy_listener_ptr).notify = Some(handle_node_destroy);
43
44 wl_signal_add(&mut (*wlr_constraint).events.destroy, &mut (*constraint).destroy_listener);
45 let commit_signal = ffi::river_wlr_surface_get_commit_signal((*wlr_constraint).surface);
46 wl_signal_add(commit_signal, &mut (*constraint).commit_listener);
47
48 let seat = river_wlr_seat_get_data_safe((*wlr_constraint).seat);
49 if !seat.is_null() {
50 if let Focus::LayerSurface(surface) = (*seat).focused {
51 if surface == (*wlr_constraint).surface {
52 (*seat).cursor.constraint = constraint;
53 (*constraint).maybe_activate();
54 }
55 }
56 }
57
58 constraint
59 }
60
61 pub unsafe fn maybe_activate(&mut self) {
62 let seat = river_wlr_seat_get_data_safe((*self.wlr_constraint).seat);
63 if seat.is_null() {
64 return;
65 }
66
67 if (*seat).cursor.constraint != self {
68 return;
69 }
70
71 if matches!(self.state, PointerConstraintState::Active { .. }) {
72 return;
73 }
74
75 // Retrieve cursor coordinates
76 let cx = (*seat).cursor.x();
77 let cy = (*seat).cursor.y();
78
79 let server = (*seat).server;
80 if let Some(result) = (*server).scene.at(cx, cy) {
81 if result.surface != (*self.wlr_constraint).surface {
82 return;
83 }
84
85 let sx = result.sx;
86 let sy = result.sy;
87
88 // Check if within the pixman region
89 if ffi::pixman_region32_contains_point(&mut (*self.wlr_constraint).region, sx as i32, sy as i32, std::ptr::null_mut()) == 0 {
90 return;
91 }
92
93 self.state = PointerConstraintState::Active {
94 node: result.node,
95 sx,
96 sy,
97 };
98
99 let destroy_signal = ffi::river_scene_node_get_destroy_signal(result.node);
100 wl_signal_add(destroy_signal, &mut self.node_destroy_listener);
101
102 log::info!("activating pointer constraint");
103 ffi::wlr_pointer_constraint_v1_send_activated(self.wlr_constraint);
104 }
105 }
106
107 pub unsafe fn deactivate(&mut self) {
108 if !matches!(self.state, PointerConstraintState::Active { .. }) {
109 return;
110 }
111
112 self.warp_to_hint_if_set();
113
114 self.state = PointerConstraintState::Inactive;
115 wl_listener_remove(&mut self.node_destroy_listener);
116 ffi::wlr_pointer_constraint_v1_send_deactivated(self.wlr_constraint);
117 }
118
119 pub unsafe fn update_state(&mut self) {
120 self.maybe_activate();
121
122 if let PointerConstraintState::Active { node, sx, sy } = self.state {
123 let seat = river_wlr_seat_get_data_safe((*self.wlr_constraint).seat);
124 if seat.is_null() {
125 return;
126 }
127
128 let mut lx = 0;
129 let mut ly = 0;
130 // Get coordinates of scene node relative to layout
131 // In wlroots, scene node coords are retrieved
132 if !wlr_scene_node_coords_safe(node, &mut lx, &mut ly) {
133 log::info!("deactivating pointer constraint, scene node disabled");
134 self.deactivate();
135 return;
136 }
137
138 let warp_lx = lx as f64 + sx;
139 let warp_ly = ly as f64 + sy;
140
141 if !ffi::wlr_cursor_warp((*seat).cursor.wlr_cursor, std::ptr::null_mut(), warp_lx, warp_ly) {
142 log::info!("deactivating pointer constraint, could not warp cursor");
143 self.deactivate();
144 return;
145 }
146
147 if ffi::pixman_region32_contains_point(&mut (*self.wlr_constraint).region, sx as i32, sy as i32, std::ptr::null_mut()) == 0 {
148 log::info!("deactivating pointer constraint, cursor outside region despite warp");
149 self.deactivate();
150 }
151 }
152 }
153
154 pub unsafe fn confine(&mut self, dx: &mut f64, dy: &mut f64) {
155 if let PointerConstraintState::Active { sx, sy, node } = self.state {
156 let mut new_sx = 0.0;
157 let mut new_sy = 0.0;
158
159 // Call wlroots / pixman confine helper
160 // river does: wlr.region.confine(region, sx, sy, sx + dx, sy + dy, &new_sx, &new_sy)
161 // If the region contains the line segment or we can confine it.
162 // Let's implement confinement using wlroots wlr_region_confine (or similar, or custom simple confinement).
163 // Actually, wlroots has: wlr_region_confine(struct pixman_region32 *region, double sx1, double sy1, double sx2, double sy2, double *ak_sx, double *ak_sy)
164 // Let's call it:
165 if ffi::wlr_region_confine(&mut (*self.wlr_constraint).region, sx, sy, sx + *dx, sy + *dy, &mut new_sx, &mut new_sy) {
166 *dx = new_sx - sx;
167 *dy = new_sy - sy;
168
169 self.state = PointerConstraintState::Active {
170 node,
171 sx: new_sx,
172 sy: new_sy,
173 };
174 }
175 }
176 }
177
178 unsafe fn warp_to_hint_if_set(&mut self) {
179 let seat = river_wlr_seat_get_data_safe((*self.wlr_constraint).seat);
180 if seat.is_null() {
181 return;
182 }
183
184 if (*self.wlr_constraint).current.cursor_hint.enabled {
185 if let PointerConstraintState::Active { node, .. } = self.state {
186 let mut lx = 0;
187 let mut ly = 0;
188 wlr_scene_node_coords_safe(node, &mut lx, &mut ly);
189
190 let sx = (*self.wlr_constraint).current.cursor_hint.x;
191 let sy = (*self.wlr_constraint).current.cursor_hint.y;
192 ffi::wlr_cursor_warp((*seat).cursor.wlr_cursor, std::ptr::null_mut(), lx as f64 + sx, ly as f64 + sy);
193 ffi::wlr_seat_pointer_warp((*seat).wlr_seat, sx, sy);
194 }
195 }
196 }
197 }
198
199 unsafe fn river_wlr_seat_get_data_safe(wlr_seat: *mut ffi::wlr_seat) -> *mut Seat {
200 if wlr_seat.is_null() {
201 std::ptr::null_mut()
202 } else {
203 ffi::river_wlr_seat_get_data(wlr_seat) as *mut Seat
204 }
205 }
206
207 unsafe fn wlr_scene_node_coords_safe(node: *mut ffi::wlr_scene_node, x: *mut i32, y: *mut i32) -> bool {
208 if node.is_null() {
209 false
210 } else {
211 ffi::wlr_scene_node_coords(node, x, y)
212 }
213 }
214
215 unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
216 let constraint_ptr = crate::container_of!(listener, PointerConstraint, destroy_listener) as *mut PointerConstraint;
217 let constraint = &mut *constraint_ptr;
218 let seat = river_wlr_seat_get_data_safe((*constraint.wlr_constraint).seat);
219
220 if matches!(constraint.state, PointerConstraintState::Active { .. }) {
221 constraint.warp_to_hint_if_set();
222 wl_listener_remove(&mut constraint.node_destroy_listener);
223 }
224
225 wl_listener_remove(&mut constraint.destroy_listener);
226 wl_listener_remove(&mut constraint.commit_listener);
227
228 if !seat.is_null() && (*seat).cursor.constraint == constraint_ptr {
229 (*seat).cursor.constraint = std::ptr::null_mut();
230 }
231
232 let _boxed = Box::from_raw(constraint_ptr);
233 }
234
235 unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
236 let constraint_ptr = crate::container_of!(listener, PointerConstraint, commit_listener) as *mut PointerConstraint;
237 let constraint = &mut *constraint_ptr;
238 let seat = river_wlr_seat_get_data_safe((*constraint.wlr_constraint).seat);
239
240 match constraint.state {
241 PointerConstraintState::Active { sx, sy, .. } => {
242 if ffi::pixman_region32_contains_point(&mut (*constraint.wlr_constraint).region, sx as i32, sy as i32, std::ptr::null_mut()) == 0 {
243 log::info!("deactivating pointer constraint, input region change left pointer outside");
244 constraint.deactivate();
245 }
246 }
247 PointerConstraintState::Inactive => {
248 if !seat.is_null() && (*seat).cursor.constraint == constraint_ptr {
249 constraint.maybe_activate();
250 }
251 }
252 }
253 }
254
255 unsafe extern "C" fn handle_node_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
256 let constraint_ptr = crate::container_of!(listener, PointerConstraint, node_destroy_listener) as *mut PointerConstraint;
257 log::info!("deactivating pointer constraint, scene node destroyed");
258 (*constraint_ptr).deactivate();
259 }