1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qwaylandtabletv2_p.h"
5#include "qwaylandinputdevice_p.h"
6#include "qwaylanddisplay_p.h"
7#include "qwaylandsurface_p.h"
8
9QT_BEGIN_NAMESPACE
10
11namespace QtWaylandClient {
12
13QWaylandTabletManagerV2::QWaylandTabletManagerV2(QWaylandDisplay *display, uint id, uint version)
14 : zwp_tablet_manager_v2(display->wl_registry(), id, qMin(version, uint(1)))
15{
16 // Create tabletSeats for all seats.
17 // This only works if we get the manager after all seats
18 const auto seats = display->inputDevices();
19 for (auto *seat : seats)
20 createTabletSeat(seat);
21}
22
23QWaylandTabletSeatV2 *QWaylandTabletManagerV2::createTabletSeat(QWaylandInputDevice *seat)
24{
25 return new QWaylandTabletSeatV2(this, seat);
26}
27
28QWaylandTabletSeatV2::QWaylandTabletSeatV2(QWaylandTabletManagerV2 *manager, QWaylandInputDevice *seat)
29 : QtWayland::zwp_tablet_seat_v2(manager->get_tablet_seat(seat->wl_seat()))
30 , m_seat(seat)
31{
32}
33
34QWaylandTabletSeatV2::~QWaylandTabletSeatV2()
35{
36 for (auto *tablet : m_tablets)
37 tablet->destroy();
38 for (auto *tool : m_tools)
39 tool->destroy();
40 for (auto *pad : m_pads)
41 pad->destroy();
42 qDeleteAll(c: m_tablets);
43 qDeleteAll(c: m_tools);
44 qDeleteAll(c: m_pads);
45 destroy();
46}
47
48void QWaylandTabletSeatV2::zwp_tablet_seat_v2_tablet_added(zwp_tablet_v2 *id)
49{
50 auto *tablet = new QWaylandTabletV2(id);
51 m_tablets.push_back(tablet);
52 connect(tablet, &QWaylandTabletV2::destroyed, this, [this, tablet] { m_tablets.removeOne(tablet); });
53}
54
55void QWaylandTabletSeatV2::zwp_tablet_seat_v2_tool_added(zwp_tablet_tool_v2 *id)
56{
57 auto *tool = new QWaylandTabletToolV2(this, id);
58 m_tools.push_back(tool);
59 connect(tool, &QWaylandTabletToolV2::destroyed, this, [this, tool] { m_tools.removeOne(tool); });
60}
61
62void QWaylandTabletSeatV2::zwp_tablet_seat_v2_pad_added(zwp_tablet_pad_v2 *id)
63{
64 auto *pad = new QWaylandTabletPadV2(id);
65 m_pads.push_back(pad);
66 connect(pad, &QWaylandTabletPadV2::destroyed, this, [this, pad] { m_pads.removeOne(pad); });
67}
68
69QWaylandTabletV2::QWaylandTabletV2(::zwp_tablet_v2 *tablet)
70 : QtWayland::zwp_tablet_v2(tablet)
71{
72}
73
74void QWaylandTabletV2::zwp_tablet_v2_removed()
75{
76 destroy();
77 delete this;
78}
79
80QWaylandTabletToolV2::QWaylandTabletToolV2(QWaylandTabletSeatV2 *tabletSeat, ::zwp_tablet_tool_v2 *tool)
81 : QtWayland::zwp_tablet_tool_v2(tool)
82 , m_tabletSeat(tabletSeat)
83{
84}
85
86void QWaylandTabletToolV2::zwp_tablet_tool_v2_type(uint32_t tool_type)
87{
88 m_toolType = type(tool_type);
89}
90
91void QWaylandTabletToolV2::zwp_tablet_tool_v2_hardware_serial(uint32_t hardware_serial_hi, uint32_t hardware_serial_lo)
92{
93 m_uid = (quint64(hardware_serial_hi) << 32) + hardware_serial_lo;
94}
95
96void QWaylandTabletToolV2::zwp_tablet_tool_v2_capability(uint32_t capability)
97{
98 if (capability == capability_rotation)
99 m_hasRotation = true;
100}
101
102void QWaylandTabletToolV2::zwp_tablet_tool_v2_done()
103{
104 switch (m_toolType) {
105 case type::type_airbrush:
106 case type::type_brush:
107 case type::type_pencil:
108 case type::type_pen:
109 m_pointerType = QPointingDevice::PointerType::Pen;
110 break;
111 case type::type_eraser:
112 m_pointerType = QPointingDevice::PointerType::Eraser;
113 break;
114 case type::type_mouse:
115 case type::type_lens:
116 m_pointerType = QPointingDevice::PointerType::Cursor;
117 break;
118 case type::type_finger:
119 m_pointerType = QPointingDevice::PointerType::Unknown;
120 break;
121 }
122 switch (m_toolType) {
123 case type::type_airbrush:
124 m_tabletDevice = QInputDevice::DeviceType::Airbrush;
125 break;
126 case type::type_brush:
127 case type::type_pencil:
128 case type::type_pen:
129 case type::type_eraser:
130 m_tabletDevice = QInputDevice::DeviceType::Stylus;
131 break;
132 case type::type_lens:
133 m_tabletDevice = QInputDevice::DeviceType::Puck;
134 break;
135 case type::type_mouse:
136 case type::type_finger:
137 m_tabletDevice = QInputDevice::DeviceType::Unknown;
138 break;
139 }
140}
141
142void QWaylandTabletToolV2::zwp_tablet_tool_v2_removed()
143{
144 destroy();
145 delete this;
146}
147
148void QWaylandTabletToolV2::zwp_tablet_tool_v2_proximity_in(uint32_t serial, zwp_tablet_v2 *tablet, wl_surface *surface)
149{
150 Q_UNUSED(tablet);
151 Q_UNUSED(serial);
152 if (Q_UNLIKELY(!surface)) {
153 qCDebug(lcQpaWayland) << "Ignoring zwp_tablet_tool_v2_proximity_v2 with no surface";
154 return;
155 }
156 m_pending.enteredSurface = true;
157 m_pending.proximitySurface = QWaylandSurface::fromWlSurface(surface);
158}
159
160void QWaylandTabletToolV2::zwp_tablet_tool_v2_proximity_out()
161{
162 m_pending.enteredSurface = false;
163 m_pending.proximitySurface = nullptr;
164}
165
166void QWaylandTabletToolV2::zwp_tablet_tool_v2_down(uint32_t serial)
167{
168 m_pending.down = true;
169
170 if (m_pending.proximitySurface) {
171 if (QWaylandWindow *window = m_pending.proximitySurface->waylandWindow()) {
172 QWaylandInputDevice *seat = m_tabletSeat->seat();
173 seat->display()->setLastInputDevice(device: seat, serial, window);
174 }
175 }
176}
177
178void QWaylandTabletToolV2::zwp_tablet_tool_v2_up()
179{
180 m_pending.down = false;
181}
182
183void QWaylandTabletToolV2::zwp_tablet_tool_v2_motion(wl_fixed_t x, wl_fixed_t y)
184{
185 m_pending.surfacePosition = QPointF(wl_fixed_to_double(x), wl_fixed_to_double(y));
186}
187
188void QWaylandTabletToolV2::zwp_tablet_tool_v2_pressure(uint32_t pressure)
189{
190 const int maxPressure = 65535;
191 m_pending.pressure = qreal(pressure)/maxPressure;
192}
193
194void QWaylandTabletToolV2::zwp_tablet_tool_v2_distance(uint32_t distance)
195{
196 m_pending.distance = distance;
197}
198
199void QWaylandTabletToolV2::zwp_tablet_tool_v2_tilt(wl_fixed_t tilt_x, wl_fixed_t tilt_y)
200{
201 m_pending.xTilt = wl_fixed_to_double(tilt_x);
202 m_pending.yTilt = wl_fixed_to_double(tilt_y);
203}
204
205void QWaylandTabletToolV2::zwp_tablet_tool_v2_rotation(wl_fixed_t degrees)
206{
207 m_pending.rotation = wl_fixed_to_double(degrees);
208}
209
210void QWaylandTabletToolV2::zwp_tablet_tool_v2_slider(int32_t position)
211{
212 m_pending.slider = qreal(position) / 65535;
213}
214
215static Qt::MouseButton mouseButtonFromTablet(uint button)
216{
217 switch (button) {
218 case 0x110: return Qt::MouseButton::LeftButton; // BTN_LEFT
219 case 0x14b: return Qt::MouseButton::MiddleButton; // BTN_STYLUS
220 case 0x14c: return Qt::MouseButton::RightButton; // BTN_STYLUS2
221 default:
222 return Qt::NoButton;
223 }
224}
225
226void QWaylandTabletToolV2::zwp_tablet_tool_v2_button(uint32_t serial, uint32_t button, uint32_t state)
227{
228 Q_UNUSED(serial);
229 Qt::MouseButton mouseButton = mouseButtonFromTablet(button);
230 if (state == button_state_pressed)
231 m_pending.buttons |= mouseButton;
232 else
233 m_pending.buttons &= ~mouseButton;
234}
235
236void QWaylandTabletToolV2::zwp_tablet_tool_v2_frame(uint32_t time)
237{
238 if (m_pending.proximitySurface && !m_applied.proximitySurface) {
239 QWindowSystemInterface::handleTabletEnterProximityEvent(deviceType: int(m_tabletDevice), pointerType: int(m_pointerType), uid: m_uid);
240 m_applied.proximitySurface = m_pending.proximitySurface;
241 }
242
243 if (!(m_pending == m_applied) && m_pending.proximitySurface) {
244 if (!m_pending.proximitySurface) {
245 qCWarning(lcQpaWayland) << "Can't send tablet event with no proximity surface, ignoring";
246 return;
247 }
248 QWaylandWindow *waylandWindow = QWaylandWindow::fromWlSurface(surface: m_pending.proximitySurface->object());
249 QWindow *window = waylandWindow->window();
250 ulong timestamp = time;
251 const QPointF localPosition = waylandWindow->mapFromWlSurface(surfacePosition: m_pending.surfacePosition);
252
253 QPointF delta = localPosition - localPosition.toPoint();
254 QPointF globalPosition = window->mapToGlobal(pos: localPosition.toPoint());
255 globalPosition += delta;
256
257 Qt::MouseButtons buttons = m_pending.down ? Qt::MouseButton::LeftButton : Qt::MouseButton::NoButton;
258 buttons |= m_pending.buttons;
259 qreal pressure = m_pending.pressure;
260 int xTilt = int(m_pending.xTilt);
261 int yTilt = int(m_pending.yTilt);
262 qreal tangentialPressure = m_pending.slider;
263 qreal rotation = m_pending.rotation;
264 int z = int(m_pending.distance);
265 QWindowSystemInterface::handleTabletEvent(window, timestamp, local: localPosition, global: globalPosition,
266 device: int(m_tabletDevice), pointerType: int(m_pointerType), buttons, pressure,
267 xTilt, yTilt, tangentialPressure, rotation, z, uid: m_uid);
268 }
269
270 if (!m_pending.proximitySurface && m_applied.enteredSurface) {
271 QWindowSystemInterface::handleTabletLeaveProximityEvent(deviceType: int(m_tabletDevice), pointerType: int(m_pointerType), uid: m_uid);
272 m_pending = State(); // Don't leave pressure etc. lying around when we enter the next surface
273 }
274
275 m_applied = m_pending;
276}
277
278// TODO: delete when upgrading to c++20
279bool QWaylandTabletToolV2::State::operator==(const QWaylandTabletToolV2::State &o) const {
280 return
281 down == o.down &&
282 proximitySurface.data() == o.proximitySurface.data() &&
283 enteredSurface == o.enteredSurface &&
284 surfacePosition == o.surfacePosition &&
285 distance == o.distance &&
286 pressure == o.pressure &&
287 rotation == o.rotation &&
288 xTilt == o.xTilt &&
289 yTilt == o.yTilt &&
290 slider == o.slider &&
291 buttons == o.buttons;
292}
293
294QWaylandTabletPadV2::QWaylandTabletPadV2(::zwp_tablet_pad_v2 *pad)
295 : QtWayland::zwp_tablet_pad_v2(pad)
296{
297}
298
299void QWaylandTabletPadV2::zwp_tablet_pad_v2_removed()
300{
301 destroy();
302 delete this;
303}
304
305} // namespace QtWaylandClient
306
307QT_END_NAMESPACE
308
309#include "moc_qwaylandtabletv2_p.cpp"
310

source code of qtwayland/src/client/qwaylandtabletv2.cpp