1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
use std::ops::Deref as _;
use std::sync::Mutex;
use std::{cell::RefCell, rc::Rc};

use crate::backend::input::{ButtonState, TabletToolCapabilitys, TabletToolDescriptor, TabletToolType};
use crate::utils::{Logical, Point};
use crate::wayland::seat::{CursorImageAttributes, CursorImageStatus};
use wayland_protocols::unstable::tablet::v2::server::{
    zwp_tablet_seat_v2::ZwpTabletSeatV2,
    zwp_tablet_tool_v2::{self, ZwpTabletToolV2},
};
use wayland_server::protocol::wl_surface::WlSurface;
use wayland_server::Filter;

use crate::wayland::{compositor, Serial};

use super::tablet::TabletHandle;

static CURSOR_IMAGE_ROLE: &str = "cursor_image";

#[derive(Debug, Default)]
struct TabletTool {
    instances: Vec<ZwpTabletToolV2>,
    focus: Option<WlSurface>,

    is_down: bool,

    pending_pressure: Option<f64>,
    pending_distance: Option<f64>,
    pending_tilt: Option<(f64, f64)>,
    pending_slider: Option<f64>,
    pending_rotation: Option<f64>,
    pending_wheel: Option<(f64, i32)>,
}

impl TabletTool {
    fn proximity_in(
        &mut self,
        loc: Point<f64, Logical>,
        (focus, sloc): (WlSurface, Point<i32, Logical>),
        tablet: &TabletHandle,
        serial: Serial,
        time: u32,
    ) {
        let wl_tool = self
            .instances
            .iter()
            .find(|i| i.as_ref().same_client_as(focus.as_ref()));

        if let Some(wl_tool) = wl_tool {
            tablet.with_focused_tablet(&focus, |wl_tablet| {
                wl_tool.proximity_in(serial.into(), wl_tablet, &focus);
                // proximity_in has to be followed by motion event (required by protocol)
                let srel_loc = loc - sloc.to_f64();
                wl_tool.motion(srel_loc.x, srel_loc.y);
                wl_tool.frame(time);
            });
        }

        self.focus = Some(focus.clone());
    }

    fn proximity_out(&mut self, time: u32) {
        if let Some(ref focus) = self.focus {
            let wl_tool = self
                .instances
                .iter()
                .find(|i| i.as_ref().same_client_as(focus.as_ref()));

            if let Some(wl_tool) = wl_tool {
                if self.is_down {
                    wl_tool.up();
                    self.is_down = false;
                }
                wl_tool.proximity_out();
                wl_tool.frame(time);
            }
        }

        self.focus = None;
    }

    fn tip_down(&mut self, serial: Serial, time: u32) {
        if let Some(ref focus) = self.focus {
            if let Some(wl_tool) = self
                .instances
                .iter()
                .find(|i| i.as_ref().same_client_as(focus.as_ref()))
            {
                if !self.is_down {
                    wl_tool.down(serial.into());
                    wl_tool.frame(time);
                }
            }
        }

        self.is_down = true;
    }

    fn tip_up(&mut self, time: u32) {
        if let Some(ref focus) = self.focus {
            if let Some(wl_tool) = self
                .instances
                .iter()
                .find(|i| i.as_ref().same_client_as(focus.as_ref()))
            {
                if self.is_down {
                    wl_tool.up();
                    wl_tool.frame(time);
                }
            }
        }

        self.is_down = false;
    }

    fn motion(
        &mut self,
        pos: Point<f64, Logical>,
        focus: Option<(WlSurface, Point<i32, Logical>)>,
        tablet: &TabletHandle,
        serial: Serial,
        time: u32,
    ) {
        match (focus, self.focus.as_ref()) {
            (Some(focus), Some(prev_focus)) => {
                if &focus.0 == prev_focus {
                    if let Some(wl_tool) = self
                        .instances
                        .iter()
                        .find(|i| i.as_ref().same_client_as(focus.0.as_ref()))
                    {
                        let srel_loc = pos - focus.1.to_f64();
                        wl_tool.motion(srel_loc.x, srel_loc.y);

                        if let Some(pressure) = self.pending_pressure.take() {
                            wl_tool.pressure((pressure * 65535.0).round() as u32);
                        }

                        if let Some(distance) = self.pending_distance.take() {
                            wl_tool.distance((distance * 65535.0).round() as u32);
                        }

                        if let Some((x, y)) = self.pending_tilt.take() {
                            wl_tool.tilt(x, y);
                        }

                        if let Some(slider) = self.pending_slider.take() {
                            wl_tool.slider((slider * 65535.0).round() as i32);
                        }

                        if let Some(rotation) = self.pending_rotation.take() {
                            wl_tool.rotation(rotation);
                        }

                        if let Some((degrees, clicks)) = self.pending_wheel.take() {
                            wl_tool.wheel(degrees, clicks)
                        }

                        wl_tool.frame(time);
                    }
                } else {
                    // If surface has changed

                    // Unfocus previous surface
                    self.proximity_out(time);
                    // Focuss a new one
                    self.proximity_in(pos, focus, tablet, serial, time)
                }
            }
            // New surface in focus
            (Some(focus), None) => self.proximity_in(pos, focus, tablet, serial, time),
            // No surface in focus
            (None, _) => self.proximity_out(time),
        }
    }

    fn pressure(&mut self, pressure: f64) {
        self.pending_pressure = Some(pressure);
    }

    fn distance(&mut self, distance: f64) {
        self.pending_distance = Some(distance);
    }

    fn tilt(&mut self, tilt: (f64, f64)) {
        self.pending_tilt = Some(tilt);
    }

    fn rotation(&mut self, rotation: f64) {
        self.pending_rotation = Some(rotation);
    }

    fn slider_position(&mut self, slider: f64) {
        self.pending_slider = Some(slider);
    }

    fn wheel(&mut self, degrees: f64, clicks: i32) {
        self.pending_wheel = Some((degrees, clicks));
    }

    /// Sent whenever a button on the tool is pressed or released.
    fn button(&self, button: u32, state: ButtonState, serial: Serial, time: u32) {
        if let Some(ref focus) = self.focus {
            if let Some(wl_tool) = self
                .instances
                .iter()
                .find(|i| i.as_ref().same_client_as(focus.as_ref()))
            {
                wl_tool.button(serial.into(), button, state.into());
                wl_tool.frame(time);
            }
        }
    }
}

impl Drop for TabletTool {
    fn drop(&mut self) {
        for instance in self.instances.iter() {
            // This event is sent when the tool is removed from the system and will send no further events.
            instance.removed();
        }
    }
}

/// Handle to a tablet tool device
///
/// TabletTool represents a physical tool that has been, or is currently in use with a tablet in seat.
///
/// A TabletTool relation to a physical tool depends on the tablet's ability to report serial numbers. If the tablet supports this capability, then the object represents a specific physical tool and can be identified even when used on multiple tablets.
#[derive(Debug, Default, Clone)]
pub struct TabletToolHandle {
    inner: Rc<RefCell<TabletTool>>,
}

impl TabletToolHandle {
    pub(super) fn new_instance<F>(&mut self, seat: &ZwpTabletSeatV2, tool: &TabletToolDescriptor, mut cb: F)
    where
        F: FnMut(&TabletToolDescriptor, CursorImageStatus) + 'static,
    {
        if let Some(client) = seat.as_ref().client() {
            let wl_tool = client
                .create_resource::<ZwpTabletToolV2>(seat.as_ref().version())
                .unwrap();

            let desc = tool.clone();
            let inner = self.inner.clone();
            wl_tool.quick_assign(move |tool, req, _| {
                use wayland_protocols::unstable::tablet::v2::server::zwp_tablet_tool_v2::Request;
                match req {
                    Request::SetCursor {
                        surface,
                        hotspot_x,
                        hotspot_y,
                        ..
                    } => {
                        let inner = inner.borrow();

                        if let Some(ref focus) = inner.focus {
                            if focus.as_ref().same_client_as(&tool.as_ref()) {
                                if let Some(surface) = surface {
                                    // tolerate re-using the same surface
                                    if compositor::give_role(&surface, CURSOR_IMAGE_ROLE).is_err()
                                        && compositor::get_role(&surface) != Some(CURSOR_IMAGE_ROLE)
                                    {
                                        tool.as_ref().post_error(
                                            zwp_tablet_tool_v2::Error::Role as u32,
                                            "Given wl_surface has another role.".into(),
                                        );
                                        return;
                                    }

                                    compositor::with_states(&surface, |states| {
                                        states.data_map.insert_if_missing_threadsafe(|| {
                                            Mutex::new(CursorImageAttributes {
                                                hotspot: (0, 0).into(),
                                            })
                                        });
                                        states
                                            .data_map
                                            .get::<Mutex<CursorImageAttributes>>()
                                            .unwrap()
                                            .lock()
                                            .unwrap()
                                            .hotspot = (hotspot_x, hotspot_y).into();
                                    })
                                    .unwrap();

                                    cb(&desc, CursorImageStatus::Image(surface));
                                } else {
                                    cb(&desc, CursorImageStatus::Hidden);
                                };
                            }
                        }
                    }
                    Request::Destroy => {
                        // Handled by our destructor
                    }
                    _ => {}
                }
            });

            let inner = self.inner.clone();
            wl_tool.assign_destructor(Filter::new(move |instance: ZwpTabletToolV2, _, _| {
                inner
                    .borrow_mut()
                    .instances
                    .retain(|i| !i.as_ref().equals(&instance.as_ref()));
            }));

            seat.tool_added(&wl_tool);

            wl_tool._type(tool.tool_type.into());

            let high: u32 = (tool.hardware_serial >> 16) as u32;
            let low: u32 = tool.hardware_serial as u32;

            wl_tool.hardware_serial(high, low);

            let high: u32 = (tool.hardware_id_wacom >> 16) as u32;
            let low: u32 = tool.hardware_id_wacom as u32;
            wl_tool.hardware_id_wacom(high, low);

            if tool.capabilitys.contains(TabletToolCapabilitys::PRESSURE) {
                wl_tool.capability(zwp_tablet_tool_v2::Capability::Pressure);
            }

            if tool.capabilitys.contains(TabletToolCapabilitys::DISTANCE) {
                wl_tool.capability(zwp_tablet_tool_v2::Capability::Distance);
            }

            if tool.capabilitys.contains(TabletToolCapabilitys::TILT) {
                wl_tool.capability(zwp_tablet_tool_v2::Capability::Tilt);
            }

            if tool.capabilitys.contains(TabletToolCapabilitys::SLIDER) {
                wl_tool.capability(zwp_tablet_tool_v2::Capability::Slider);
            }

            if tool.capabilitys.contains(TabletToolCapabilitys::ROTATION) {
                wl_tool.capability(zwp_tablet_tool_v2::Capability::Rotation);
            }

            if tool.capabilitys.contains(TabletToolCapabilitys::WHEEL) {
                wl_tool.capability(zwp_tablet_tool_v2::Capability::Wheel);
            }

            wl_tool.done();
            self.inner.borrow_mut().instances.push(wl_tool.deref().clone());
        }
    }

    /// Notify that this tool is focused on a certain surface.
    ///
    /// You provide the location of the tool, in the form of:
    ///
    /// - The coordinates of the tool in the global compositor space
    /// - The surface on top of which the tool is, and the coordinates of its
    ///   origin in the global compositor space.
    pub fn proximity_in(
        &self,
        pos: Point<f64, Logical>,
        focus: (WlSurface, Point<i32, Logical>),
        tablet: &TabletHandle,
        serial: Serial,
        time: u32,
    ) {
        self.inner
            .borrow_mut()
            .proximity_in(pos, focus, tablet, serial, time)
    }

    /// Notify that this tool has left proximity.
    pub fn proximity_out(&self, time: u32) {
        self.inner.borrow_mut().proximity_out(time);
    }

    /// Tablet tool is making contact
    pub fn tip_down(&self, serial: Serial, time: u32) {
        self.inner.borrow_mut().tip_down(serial, time);
    }

    /// Tablet tool is no longer making contact
    pub fn tip_up(&self, time: u32) {
        self.inner.borrow_mut().tip_up(time);
    }

    /// Notify that the tool moved
    ///
    /// You provide the new location of the tool, in the form of:
    ///
    /// - The coordinates of the tool in the global compositor space
    /// - The surface on top of which the tool is, and the coordinates of its
    ///   origin in the global compositor space (or `None` of the pointer is not
    ///   on top of a client surface).
    ///
    /// This will internally take care of notifying the appropriate client objects
    /// of proximity_in/proximity_out events.
    pub fn motion(
        &self,
        pos: Point<f64, Logical>,
        focus: Option<(WlSurface, Point<i32, Logical>)>,
        tablet: &TabletHandle,
        serial: Serial,
        time: u32,
    ) {
        self.inner.borrow_mut().motion(pos, focus, tablet, serial, time)
    }

    /// Queue tool pressure update
    ///
    /// It will be sent alongside next motion event
    pub fn pressure(&self, pressure: f64) {
        self.inner.borrow_mut().pressure(pressure);
    }

    /// Queue tool distance update
    ///
    /// It will be sent alongside next motion event
    pub fn distance(&self, distance: f64) {
        self.inner.borrow_mut().distance(distance);
    }

    /// Queue tool tilt update
    ///
    /// It will be sent alongside next motion event
    pub fn tilt(&self, tilt: (f64, f64)) {
        self.inner.borrow_mut().tilt(tilt);
    }

    /// Queue tool rotation update
    ///
    /// It will be sent alongside next motion event
    pub fn rotation(&self, rotation: f64) {
        self.inner.borrow_mut().rotation(rotation);
    }

    /// Queue tool slider update
    ///
    /// It will be sent alongside next motion event
    pub fn slider_position(&self, slider: f64) {
        self.inner.borrow_mut().slider_position(slider);
    }

    /// Queue tool wheel update
    ///
    /// It will be sent alongside next motion event
    pub fn wheel(&self, degrees: f64, clicks: i32) {
        self.inner.borrow_mut().wheel(degrees, clicks);
    }

    /// Button on the tool was pressed or released
    pub fn button(&self, button: u32, state: ButtonState, serial: Serial, time: u32) {
        self.inner.borrow().button(button, state, serial, time);
    }
}

impl From<TabletToolType> for zwp_tablet_tool_v2::Type {
    fn from(from: TabletToolType) -> zwp_tablet_tool_v2::Type {
        match from {
            TabletToolType::Pen => zwp_tablet_tool_v2::Type::Pen,
            TabletToolType::Eraser => zwp_tablet_tool_v2::Type::Eraser,
            TabletToolType::Brush => zwp_tablet_tool_v2::Type::Brush,
            TabletToolType::Pencil => zwp_tablet_tool_v2::Type::Pencil,
            TabletToolType::Airbrush => zwp_tablet_tool_v2::Type::Airbrush,
            TabletToolType::Mouse => zwp_tablet_tool_v2::Type::Mouse,
            TabletToolType::Lens => zwp_tablet_tool_v2::Type::Lens,
            _ => zwp_tablet_tool_v2::Type::Pen,
        }
    }
}

impl From<ButtonState> for zwp_tablet_tool_v2::ButtonState {
    fn from(from: ButtonState) -> zwp_tablet_tool_v2::ButtonState {
        match from {
            ButtonState::Pressed => zwp_tablet_tool_v2::ButtonState::Pressed,
            ButtonState::Released => zwp_tablet_tool_v2::ButtonState::Released,
        }
    }
}