Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/tablet_tool.rs (13.3K)
1 use crate::ffi;
2 use crate::tablet::Tablet;
3 use crate::seat::Seat;
4 use crate::server::{WlListener, wl_listener_remove, wl_signal_add};
5
6 #[derive(Clone, Copy, Debug, PartialEq)]
7 pub enum TabletToolMode {
8 Passthrough,
9 Down {
10 lx: f64,
11 ly: f64,
12 sx: f64,
13 sy: f64,
14 },
15 }
16
17 pub struct TabletTool {
18 pub wp_tool: *mut ffi::wlr_tablet_v2_tablet_tool,
19 pub wlr_cursor: *mut ffi::wlr_cursor,
20 pub mode: TabletToolMode,
21 pub tilt_x: f64,
22 pub tilt_y: f64,
23
24 pub destroy_listener: ffi::wl_listener,
25 pub set_cursor_listener: ffi::wl_listener,
26 }
27
28 impl TabletTool {
29 pub unsafe fn get(
30 wlr_seat: *mut ffi::wlr_seat,
31 wlr_tool: *mut ffi::wlr_tablet_tool,
32 ) -> Result<*mut Self, &'static str> {
33 let data_ptr = (*wlr_tool).data as *mut Self;
34 if !data_ptr.is_null() {
35 Ok(data_ptr)
36 } else {
37 Self::create(wlr_seat, wlr_tool)
38 }
39 }
40
41 pub unsafe fn create(
42 wlr_seat: *mut ffi::wlr_seat,
43 wlr_tool: *mut ffi::wlr_tablet_tool,
44 ) -> Result<*mut Self, &'static str> {
45 let wlr_cursor = ffi::wlr_cursor_create();
46 if wlr_cursor.is_null() {
47 return Err("Failed to create wlr_cursor");
48 }
49
50 let seat = ffi::river_wlr_seat_get_data(wlr_seat) as *mut Seat;
51 if seat.is_null() {
52 ffi::wlr_cursor_destroy(wlr_cursor);
53 return Err("Seat data is null");
54 }
55 let server = (*seat).server;
56 let output_layout = (*server).om.output_layout;
57
58 ffi::wlr_cursor_attach_output_layout(wlr_cursor, output_layout);
59
60 let tablet_manager = (*server).input_manager.tablet_manager;
61 let wp_tool = ffi::wlr_tablet_tool_create(tablet_manager, wlr_seat, wlr_tool);
62 if wp_tool.is_null() {
63 ffi::wlr_cursor_destroy(wlr_cursor);
64 return Err("Failed to create wp_tool");
65 }
66
67 let tool = Box::into_raw(Box::new(Self {
68 wp_tool,
69 wlr_cursor,
70 mode: TabletToolMode::Passthrough,
71 tilt_x: 0.0,
72 tilt_y: 0.0,
73 destroy_listener: std::mem::zeroed(),
74 set_cursor_listener: std::mem::zeroed(),
75 }));
76
77 (*wlr_tool).data = tool as *mut _;
78
79 let destroy_listener_ptr = &mut (*tool).destroy_listener as *mut ffi::wl_listener as *mut WlListener;
80 (*destroy_listener_ptr).notify = Some(handle_destroy);
81 wl_signal_add(&mut (*wlr_tool).events.destroy, &mut (*tool).destroy_listener);
82
83 let set_cursor_listener_ptr = &mut (*tool).set_cursor_listener as *mut ffi::wl_listener as *mut WlListener;
84 (*set_cursor_listener_ptr).notify = Some(handle_set_cursor);
85 let set_cursor_signal = ffi::river_wlr_tablet_v2_tablet_tool_get_set_cursor_signal(wp_tool);
86 wl_signal_add(set_cursor_signal, &mut (*tool).set_cursor_listener);
87
88 Ok(tool)
89 }
90
91 pub unsafe fn cursor_x(&self) -> f64 {
92 ffi::river_wlr_cursor_get_x(self.wlr_cursor)
93 }
94
95 pub unsafe fn cursor_y(&self) -> f64 {
96 ffi::river_wlr_cursor_get_y(self.wlr_cursor)
97 }
98
99 pub unsafe fn allow_set_cursor(
100 &self,
101 seat_client: *mut ffi::wlr_seat_client,
102 serial: u32,
103 ) -> bool {
104 let focused_surface = ffi::river_wlr_tablet_v2_tablet_tool_get_focused_surface(self.wp_tool);
105 if focused_surface.is_null() {
106 log::debug!("client tried to set cursor without focus");
107 return false;
108 }
109
110 let surface_resource = ffi::river_wlr_surface_get_resource(focused_surface);
111 if surface_resource.is_null() {
112 return false;
113 }
114
115 let surface_client = ffi::wl_resource_get_client(surface_resource);
116 let seat_client_ptr = ffi::river_wlr_seat_client_get_client(seat_client);
117
118 if surface_client != seat_client_ptr {
119 log::debug!("client tried to set cursor without focus");
120 return false;
121 }
122
123 let proximity_serial = ffi::river_wlr_tablet_v2_tablet_tool_get_proximity_serial(self.wp_tool);
124 if serial != proximity_serial {
125 log::debug!("focused client tried to set cursor with incorrect serial");
126 return false;
127 }
128
129 true
130 }
131
132 pub unsafe fn attach(&mut self, tablet: *mut Tablet) {
133 let dev = (*(*tablet).device).wlr_device;
134 ffi::wlr_cursor_attach_input_device(self.wlr_cursor, dev);
135 ffi::wlr_cursor_map_input_to_output(self.wlr_cursor, dev, (*(*tablet).device).config.map_to_output);
136 ffi::wlr_cursor_map_input_to_region(self.wlr_cursor, dev, &mut (*(*tablet).device).config.map_to_rectangle as *mut _);
137 }
138
139 pub unsafe fn detach(&mut self, tablet: *mut Tablet) {
140 let dev = (*(*tablet).device).wlr_device;
141 ffi::wlr_cursor_detach_input_device(self.wlr_cursor, dev);
142 }
143
144 pub unsafe fn axis(&mut self, tablet: *mut Tablet, event: *mut ffi::wlr_tablet_tool_axis_event) {
145 self.attach(tablet);
146
147 let updated = (*event).updated_axes;
148 let has_x = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_X) != 0;
149 let has_y = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_Y) != 0;
150
151 let wlr_tool = ffi::river_wlr_tablet_v2_tablet_tool_get_wlr_tool(self.wp_tool);
152 let tool_type = (*wlr_tool).type_;
153
154 if has_x || has_y {
155 match tool_type {
156 ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_PEN |
157 ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_ERASER |
158 ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_BRUSH |
159 ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_PENCIL |
160 ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_AIRBRUSH |
161 ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_TOTEM => {
162 let warp_x = if has_x { (*event).x } else { f64::NAN };
163 let warp_y = if has_y { (*event).y } else { f64::NAN };
164 ffi::wlr_cursor_warp_absolute(self.wlr_cursor, (*(*tablet).device).wlr_device, warp_x, warp_y);
165 }
166 ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_LENS |
167 ffi::wlr_tablet_tool_type_WLR_TABLET_TOOL_TYPE_MOUSE => {
168 ffi::wlr_cursor_move(self.wlr_cursor, (*(*tablet).device).wlr_device, (*event).dx, (*event).dy);
169 }
170 _ => {}
171 }
172
173 match self.mode {
174 TabletToolMode::Passthrough => {
175 self.passthrough(tablet);
176 }
177 TabletToolMode::Down { lx, ly, sx, sy } => {
178 let dx = self.cursor_x() - lx;
179 let dy = self.cursor_y() - ly;
180 ffi::wlr_tablet_v2_tablet_tool_notify_motion(self.wp_tool, sx + dx, sy + dy);
181 }
182 }
183 }
184
185 let has_dist = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_DISTANCE) != 0;
186 if has_dist {
187 ffi::wlr_tablet_v2_tablet_tool_notify_distance(self.wp_tool, (*event).distance);
188 }
189
190 let has_press = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_PRESSURE) != 0;
191 if has_press {
192 ffi::wlr_tablet_v2_tablet_tool_notify_pressure(self.wp_tool, (*event).pressure);
193 }
194
195 let has_tilt_x = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_TILT_X) != 0;
196 let has_tilt_y = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_TILT_Y) != 0;
197 if has_tilt_x || has_tilt_y {
198 if has_tilt_x {
199 self.tilt_x = (*event).tilt_x;
200 }
201 if has_tilt_y {
202 self.tilt_y = (*event).tilt_y;
203 }
204 ffi::wlr_tablet_v2_tablet_tool_notify_tilt(self.wp_tool, self.tilt_x, self.tilt_y);
205 }
206
207 let has_rot = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_ROTATION) != 0;
208 if has_rot {
209 ffi::wlr_tablet_v2_tablet_tool_notify_rotation(self.wp_tool, (*event).rotation);
210 }
211
212 let has_slider = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_SLIDER) != 0;
213 if has_slider {
214 ffi::wlr_tablet_v2_tablet_tool_notify_slider(self.wp_tool, (*event).slider);
215 }
216
217 let has_wheel = (updated & ffi::wlr_tablet_tool_axes_WLR_TABLET_TOOL_AXIS_WHEEL) != 0;
218 if has_wheel {
219 ffi::wlr_tablet_v2_tablet_tool_notify_wheel(self.wp_tool, (*event).wheel_delta, 0);
220 }
221
222 self.detach(tablet);
223 }
224
225 pub unsafe fn proximity(
226 &mut self,
227 tablet: *mut Tablet,
228 event: *mut ffi::wlr_tablet_tool_proximity_event,
229 ) {
230 let state = (*event).state;
231 if state == ffi::wlr_tablet_tool_proximity_state_WLR_TABLET_TOOL_PROXIMITY_IN {
232 self.attach(tablet);
233
234 ffi::wlr_cursor_warp_absolute(
235 self.wlr_cursor,
236 (*(*tablet).device).wlr_device,
237 (*event).x,
238 (*event).y,
239 );
240
241 let xcursor_manager = (*(*(*tablet).device).seat).cursor.xcursor_manager;
242 ffi::wlr_cursor_set_xcursor(
243 self.wlr_cursor,
244 xcursor_manager,
245 b"pencil\0".as_ptr() as *const _,
246 );
247
248 self.passthrough(tablet);
249 self.detach(tablet);
250 } else {
251 ffi::wlr_tablet_v2_tablet_tool_notify_proximity_out(self.wp_tool);
252 ffi::wlr_cursor_unset_image(self.wlr_cursor);
253 }
254 }
255
256 pub unsafe fn tip(&mut self, tablet: *mut Tablet, event: *mut ffi::wlr_tablet_tool_tip_event) {
257 let state = (*event).state;
258 let is_down = ffi::river_wlr_tablet_v2_tablet_tool_get_is_down(self.wp_tool);
259 if state == ffi::wlr_tablet_tool_tip_state_WLR_TABLET_TOOL_TIP_DOWN {
260 if is_down {
261 return;
262 }
263 ffi::wlr_send_tablet_v2_tablet_tool_down(self.wp_tool);
264
265 let server = (*(*(*tablet).device).seat).server;
266 let lx = self.cursor_x();
267 let ly = self.cursor_y();
268 if let Some(result) = (*server).scene.at(lx, ly) {
269 if !result.surface.is_null() {
270 self.mode = TabletToolMode::Down {
271 lx,
272 ly,
273 sx: result.sx,
274 sy: result.sy,
275 };
276 }
277 }
278 } else {
279 if !is_down {
280 return;
281 }
282 ffi::wlr_send_tablet_v2_tablet_tool_up(self.wp_tool);
283 self.maybe_exit_down(tablet);
284 }
285 }
286
287 pub unsafe fn button(
288 &mut self,
289 tablet: *mut Tablet,
290 event: *mut ffi::wlr_tablet_tool_button_event,
291 ) {
292 ffi::wlr_tablet_v2_tablet_tool_notify_button(
293 self.wp_tool,
294 (*event).button,
295 (*event).state,
296 );
297 self.maybe_exit_down(tablet);
298 }
299
300 pub unsafe fn maybe_exit_down(&mut self, tablet: *mut Tablet) {
301 let is_down = ffi::river_wlr_tablet_v2_tablet_tool_get_is_down(self.wp_tool);
302 let num_buttons = ffi::river_wlr_tablet_v2_tablet_tool_get_num_buttons(self.wp_tool);
303 if !matches!(self.mode, TabletToolMode::Down { .. }) || is_down || num_buttons > 0 {
304 return;
305 }
306 self.mode = TabletToolMode::Passthrough;
307 self.passthrough(tablet);
308 }
309
310 pub unsafe fn passthrough(&mut self, tablet: *mut Tablet) {
311 let server = (*(*(*tablet).device).seat).server;
312 let lx = self.cursor_x();
313 let ly = self.cursor_y();
314
315 if let Some(result) = (*server).scene.at(lx, ly) {
316 if matches!(result.data, crate::scene_node_data::SceneNodeDataVal::LockSurface(_)) {
317 assert!((*server).lock_manager.state != crate::lock_manager::LockState::Unlocked);
318 } else {
319 assert!((*server).lock_manager.state != crate::lock_manager::LockState::Locked);
320 }
321
322 if !result.surface.is_null() {
323 ffi::wlr_send_tablet_v2_tablet_tool_proximity_in(self.wp_tool, (*tablet).wp_tablet, result.surface);
324 ffi::wlr_tablet_v2_tablet_tool_notify_motion(self.wp_tool, result.sx, result.sy);
325 return;
326 }
327 } else {
328 let xcursor_manager = (*(*(*tablet).device).seat).cursor.xcursor_manager;
329 ffi::wlr_cursor_set_xcursor(self.wlr_cursor, xcursor_manager, b"pencil\0".as_ptr() as *const _);
330 }
331
332 ffi::wlr_tablet_v2_tablet_tool_notify_proximity_out(self.wp_tool);
333 }
334 }
335
336 unsafe extern "C" fn handle_destroy(
337 listener: *mut ffi::wl_listener,
338 _data: *mut std::ffi::c_void,
339 ) {
340 let tool_ptr = crate::container_of!(listener, TabletTool, destroy_listener) as *mut TabletTool;
341 let tool = &mut *tool_ptr;
342
343 ffi::wlr_cursor_destroy(tool.wlr_cursor);
344
345 wl_listener_remove(&mut tool.destroy_listener);
346 wl_listener_remove(&mut tool.set_cursor_listener);
347
348 let _boxed = Box::from_raw(tool_ptr);
349 }
350
351 unsafe extern "C" fn handle_set_cursor(
352 listener: *mut ffi::wl_listener,
353 data: *mut std::ffi::c_void,
354 ) {
355 let tool = &mut *crate::container_of!(listener, TabletTool, set_cursor_listener);
356 let event = data as *mut ffi::wlr_tablet_v2_event_cursor;
357
358 if tool.allow_set_cursor((*event).seat_client, (*event).serial) {
359 ffi::wlr_cursor_set_surface(
360 tool.wlr_cursor,
361 (*event).surface,
362 (*event).hotspot_x,
363 (*event).hotspot_y,
364 );
365 }
366 }