| 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 | #include "qwaylandscreen_p.h" |
| 9 | #include "qwaylandbuffer_p.h" |
| 10 | #include "qwaylandcursorsurface_p.h" |
| 11 | #include "qwaylandcursor_p.h" |
| 12 | |
| 13 | #include <QtGui/private/qguiapplication_p.h> |
| 14 | #include <QtGui/private/qpointingdevice_p.h> |
| 15 | #include <QtGui/qpa/qplatformtheme.h> |
| 16 | #include <QtGui/qpa/qwindowsysteminterface_p.h> |
| 17 | |
| 18 | #include <wayland-cursor.h> |
| 19 | |
| 20 | QT_BEGIN_NAMESPACE |
| 21 | |
| 22 | namespace QtWaylandClient { |
| 23 | |
| 24 | using namespace Qt::StringLiterals; |
| 25 | |
| 26 | #if QT_CONFIG(cursor) |
| 27 | int QWaylandTabletToolV2::idealCursorScale() const |
| 28 | { |
| 29 | if (m_tabletSeat->seat()->mQDisplay->compositor()->version() < 3) { |
| 30 | return 1; |
| 31 | } |
| 32 | |
| 33 | if (auto *s = mCursor.surface.data()) { |
| 34 | if (s->outputScale() > 0) |
| 35 | return s->outputScale(); |
| 36 | } |
| 37 | |
| 38 | return m_tabletSeat->seat()->mCursor.fallbackOutputScale; |
| 39 | } |
| 40 | |
| 41 | void QWaylandTabletToolV2::updateCursorTheme() |
| 42 | { |
| 43 | QString cursorThemeName; |
| 44 | QSize cursorSize; |
| 45 | |
| 46 | if (const QPlatformTheme *platformTheme = QGuiApplicationPrivate::platformTheme()) { |
| 47 | cursorThemeName = platformTheme->themeHint(hint: QPlatformTheme::MouseCursorTheme).toString(); |
| 48 | cursorSize = platformTheme->themeHint(hint: QPlatformTheme::MouseCursorSize).toSize(); |
| 49 | } |
| 50 | |
| 51 | if (cursorThemeName.isEmpty()) |
| 52 | cursorThemeName = QStringLiteral("default" ); |
| 53 | if (cursorSize.isEmpty()) |
| 54 | cursorSize = QSize(24, 24); |
| 55 | |
| 56 | int scale = idealCursorScale(); |
| 57 | int pixelSize = cursorSize.width() * scale; |
| 58 | auto *display = m_tabletSeat->seat()->mQDisplay; |
| 59 | mCursor.theme = display->loadCursorTheme(name: cursorThemeName, pixelSize); |
| 60 | |
| 61 | if (!mCursor.theme) |
| 62 | return; // A warning has already been printed in loadCursorTheme |
| 63 | |
| 64 | if (auto *arrow = mCursor.theme->cursor(shape: Qt::ArrowCursor)) { |
| 65 | int arrowPixelSize = qMax(a: arrow->images[0]->width, |
| 66 | b: arrow->images[0]->height); // Not all cursor themes are square |
| 67 | while (scale > 1 && arrowPixelSize / scale < cursorSize.width()) |
| 68 | --scale; |
| 69 | } else { |
| 70 | qCWarning(lcQpaWayland) << "Cursor theme does not support the arrow cursor" ; |
| 71 | } |
| 72 | mCursor.themeBufferScale = scale; |
| 73 | } |
| 74 | |
| 75 | void QWaylandTabletToolV2::updateCursor() |
| 76 | { |
| 77 | if (mEnterSerial == 0) |
| 78 | return; |
| 79 | |
| 80 | auto shape = m_tabletSeat->seat()->mCursor.shape; |
| 81 | |
| 82 | if (shape == Qt::BlankCursor) { |
| 83 | if (mCursor.surface) |
| 84 | mCursor.surface->reset(); |
| 85 | set_cursor(mEnterSerial, nullptr, 0, 0); |
| 86 | return; |
| 87 | } |
| 88 | |
| 89 | if (shape == Qt::BitmapCursor) { |
| 90 | auto buffer = m_tabletSeat->seat()->mCursor.bitmapBuffer; |
| 91 | if (!buffer) { |
| 92 | qCWarning(lcQpaWayland) << "No buffer for bitmap cursor, can't set cursor" ; |
| 93 | return; |
| 94 | } |
| 95 | auto hotspot = m_tabletSeat->seat()->mCursor.hotspot; |
| 96 | int bufferScale = m_tabletSeat->seat()->mCursor.bitmapScale; |
| 97 | getOrCreateCursorSurface()->update(buffer: buffer->buffer(), hotspot, size: buffer->size(), bufferScale); |
| 98 | return; |
| 99 | } |
| 100 | |
| 101 | if (mCursor.shape) { |
| 102 | if (mCursor.surface) { |
| 103 | mCursor.surface->reset(); |
| 104 | } |
| 105 | mCursor.shape->setShape(serial: mEnterSerial, shape); |
| 106 | return; |
| 107 | } |
| 108 | |
| 109 | if (!mCursor.theme || idealCursorScale() != mCursor.themeBufferScale) |
| 110 | updateCursorTheme(); |
| 111 | |
| 112 | if (!mCursor.theme) |
| 113 | return; |
| 114 | |
| 115 | // Set from shape using theme |
| 116 | QElapsedTimer &timer = m_tabletSeat->seat()->mCursor.animationTimer; |
| 117 | const uint time = timer.isValid() ? timer.elapsed() : 0; |
| 118 | |
| 119 | if (struct ::wl_cursor *waylandCursor = mCursor.theme->cursor(shape)) { |
| 120 | uint duration = 0; |
| 121 | int frame = wl_cursor_frame_and_duration(cursor: waylandCursor, time, duration: &duration); |
| 122 | ::wl_cursor_image *image = waylandCursor->images[frame]; |
| 123 | |
| 124 | struct wl_buffer *buffer = wl_cursor_image_get_buffer(image); |
| 125 | if (!buffer) { |
| 126 | qCWarning(lcQpaWayland) << "Could not find buffer for cursor" << shape; |
| 127 | return; |
| 128 | } |
| 129 | int bufferScale = mCursor.themeBufferScale; |
| 130 | QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale; |
| 131 | QSize size = QSize(image->width, image->height) / bufferScale; |
| 132 | bool animated = duration > 0; |
| 133 | if (animated) { |
| 134 | mCursor.gotFrameCallback = false; |
| 135 | mCursor.gotTimerCallback = false; |
| 136 | mCursor.frameTimer.start(msec: duration); |
| 137 | } |
| 138 | getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale, animated); |
| 139 | return; |
| 140 | } |
| 141 | |
| 142 | qCWarning(lcQpaWayland) << "Unable to change to cursor" << shape; |
| 143 | } |
| 144 | |
| 145 | CursorSurface<QWaylandTabletToolV2> *QWaylandTabletToolV2::getOrCreateCursorSurface() |
| 146 | { |
| 147 | if (!mCursor.surface) |
| 148 | mCursor.surface.reset( |
| 149 | other: new CursorSurface<QWaylandTabletToolV2>(this, m_tabletSeat->seat()->mQDisplay)); |
| 150 | return mCursor.surface.get(); |
| 151 | } |
| 152 | |
| 153 | void QWaylandTabletToolV2::cursorTimerCallback() |
| 154 | { |
| 155 | mCursor.gotTimerCallback = true; |
| 156 | if (mCursor.gotFrameCallback) |
| 157 | updateCursor(); |
| 158 | } |
| 159 | |
| 160 | void QWaylandTabletToolV2::cursorFrameCallback() |
| 161 | { |
| 162 | mCursor.gotFrameCallback = true; |
| 163 | if (mCursor.gotTimerCallback) |
| 164 | updateCursor(); |
| 165 | } |
| 166 | |
| 167 | #endif // QT_CONFIG(cursor) |
| 168 | |
| 169 | QWaylandTabletManagerV2::QWaylandTabletManagerV2(QWaylandDisplay *display, uint id, uint version) |
| 170 | : zwp_tablet_manager_v2(display->wl_registry(), id, qMin(version, uint(1))) |
| 171 | { |
| 172 | qCDebug(lcQpaInputDevices, "new tablet manager: ID %d version %d" , id, version); |
| 173 | } |
| 174 | |
| 175 | QWaylandTabletManagerV2::~QWaylandTabletManagerV2() |
| 176 | { |
| 177 | destroy(); |
| 178 | } |
| 179 | |
| 180 | QWaylandTabletSeatV2::QWaylandTabletSeatV2(QWaylandTabletManagerV2 *manager, QWaylandInputDevice *seat) |
| 181 | : QtWayland::zwp_tablet_seat_v2(manager->get_tablet_seat(seat->wl_seat())) |
| 182 | , m_seat(seat) |
| 183 | { |
| 184 | qCDebug(lcQpaInputDevices) << "new tablet seat" << seat->seatname() << "id" << seat->id(); |
| 185 | } |
| 186 | |
| 187 | QWaylandTabletSeatV2::~QWaylandTabletSeatV2() |
| 188 | { |
| 189 | qDeleteAll(c: m_tablets); |
| 190 | qDeleteAll(c: m_tools); |
| 191 | qDeleteAll(c: m_deadTools); |
| 192 | qDeleteAll(c: m_pads); |
| 193 | destroy(); |
| 194 | } |
| 195 | |
| 196 | void QWaylandTabletSeatV2::zwp_tablet_seat_v2_tablet_added(zwp_tablet_v2 *id) |
| 197 | { |
| 198 | auto *tablet = new QWaylandTabletV2(id, m_seat->seatname()); |
| 199 | qCDebug(lcQpaInputDevices) << "seat" << this << id << "has tablet" << tablet; |
| 200 | tablet->setParent(this); |
| 201 | m_tablets.push_back(tablet); |
| 202 | connect(tablet, &QWaylandTabletV2::destroyed, this, [this, tablet] { m_tablets.removeOne(tablet); }); |
| 203 | } |
| 204 | |
| 205 | void QWaylandTabletSeatV2::zwp_tablet_seat_v2_tool_added(zwp_tablet_tool_v2 *id) |
| 206 | { |
| 207 | qDeleteAll(c: m_deadTools); |
| 208 | auto *tool = new QWaylandTabletToolV2(this, id); |
| 209 | if (m_tablets.size() == 1) { |
| 210 | tool->setParent(m_tablets.first()); |
| 211 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(tool); |
| 212 | d->name = m_tablets.first()->name() + u" stylus" ; |
| 213 | } else { |
| 214 | qCDebug(lcQpaInputDevices) << "seat" << this << "has tool" << tool << "for one of these tablets:" << m_tablets; |
| 215 | // TODO identify which tablet if there are several; then tool->setParent(tablet) |
| 216 | } |
| 217 | m_tools.push_back(tool); |
| 218 | connect(tool, &QWaylandTabletToolV2::destroyed, this, [this, tool] { |
| 219 | m_tools.removeOne(tool); |
| 220 | m_deadTools.removeOne(tool); |
| 221 | }); |
| 222 | } |
| 223 | |
| 224 | void QWaylandTabletSeatV2::zwp_tablet_seat_v2_pad_added(zwp_tablet_pad_v2 *id) |
| 225 | { |
| 226 | auto *pad = new QWaylandTabletPadV2(id); |
| 227 | if (m_tablets.size() == 1) { |
| 228 | pad->setParent(m_tablets.first()); |
| 229 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(pad); |
| 230 | d->name = m_tablets.first()->name() + u" touchpad" ; |
| 231 | } else { |
| 232 | qCDebug(lcQpaInputDevices) << "seat" << this << "has touchpad" << pad << "for one of these tablets:" << m_tablets; |
| 233 | // TODO identify which tablet if there are several |
| 234 | } |
| 235 | m_pads.push_back(pad); |
| 236 | connect(pad, &QWaylandTabletPadV2::destroyed, this, [this, pad] { m_pads.removeOne(pad); }); |
| 237 | } |
| 238 | |
| 239 | QWaylandTabletV2::QWaylandTabletV2(::zwp_tablet_v2 *tablet, const QString &seatName) |
| 240 | : QPointingDevice(u"unknown"_s , -1, DeviceType::Stylus, PointerType::Pen, |
| 241 | Capability::Position | Capability::Hover, |
| 242 | 1, 1, seatName) |
| 243 | , QtWayland::zwp_tablet_v2(tablet) |
| 244 | { |
| 245 | qCDebug(lcQpaInputDevices) << "new tablet on seat" << seatName; |
| 246 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(q: this); |
| 247 | d->seatName = seatName; |
| 248 | } |
| 249 | |
| 250 | QWaylandTabletV2::~QWaylandTabletV2() |
| 251 | { |
| 252 | destroy(); |
| 253 | } |
| 254 | |
| 255 | void QWaylandTabletV2::zwp_tablet_v2_name(const QString &name) |
| 256 | { |
| 257 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(q: this); |
| 258 | d->name = name; |
| 259 | } |
| 260 | |
| 261 | void QWaylandTabletV2::zwp_tablet_v2_id(uint32_t vid, uint32_t pid) |
| 262 | { |
| 263 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(q: this); |
| 264 | d->systemId = (quint64(vid) << 32) | pid; |
| 265 | qCDebug(lcQpaInputDevices) << "vid" << vid << "pid" << pid << "stored as systemId in" << this; |
| 266 | } |
| 267 | |
| 268 | void QWaylandTabletV2::zwp_tablet_v2_path(const QString &path) |
| 269 | { |
| 270 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(q: this); |
| 271 | d->busId = path; |
| 272 | } |
| 273 | |
| 274 | void QWaylandTabletV2::zwp_tablet_v2_done() |
| 275 | { |
| 276 | QWindowSystemInterface::registerInputDevice(device: this); |
| 277 | } |
| 278 | |
| 279 | void QWaylandTabletSeatV2::updateCursor() |
| 280 | { |
| 281 | for (auto tool : m_tools) |
| 282 | tool->updateCursor(); |
| 283 | } |
| 284 | |
| 285 | void QWaylandTabletSeatV2::toolRemoved(QWaylandTabletToolV2 *tool) |
| 286 | { |
| 287 | m_tools.removeOne(t: tool); |
| 288 | m_deadTools.append(t: tool); |
| 289 | } |
| 290 | |
| 291 | void QWaylandTabletV2::zwp_tablet_v2_removed() |
| 292 | { |
| 293 | deleteLater(); |
| 294 | } |
| 295 | |
| 296 | QWaylandTabletToolV2::QWaylandTabletToolV2(QWaylandTabletSeatV2 *tabletSeat, ::zwp_tablet_tool_v2 *tool) |
| 297 | : QPointingDevice(u"tool"_s , -1, DeviceType::Stylus, PointerType::Pen, |
| 298 | Capability::Position | Capability::Hover, |
| 299 | 1, 1, tabletSeat->seat()->seatname()) |
| 300 | , QtWayland::zwp_tablet_tool_v2(tool) |
| 301 | , m_tabletSeat(tabletSeat) |
| 302 | { |
| 303 | // TODO get the number of buttons somehow? |
| 304 | |
| 305 | #if QT_CONFIG(cursor) |
| 306 | if (auto cursorShapeManager = m_tabletSeat->seat()->mQDisplay->cursorShapeManager()) { |
| 307 | mCursor.shape.reset( |
| 308 | other: new QWaylandCursorShape(cursorShapeManager->get_tablet_tool_v2(object()))); |
| 309 | } |
| 310 | |
| 311 | mCursor.frameTimer.setSingleShot(true); |
| 312 | mCursor.frameTimer.callOnTimeout(args: this, args: [&]() { cursorTimerCallback(); }); |
| 313 | #endif |
| 314 | } |
| 315 | |
| 316 | QWaylandTabletToolV2::~QWaylandTabletToolV2() |
| 317 | { |
| 318 | destroy(); |
| 319 | } |
| 320 | |
| 321 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_type(uint32_t tool_type) |
| 322 | { |
| 323 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); |
| 324 | |
| 325 | switch (tool_type) { |
| 326 | case type_airbrush: |
| 327 | case type_brush: |
| 328 | case type_pencil: |
| 329 | case type_pen: |
| 330 | d->pointerType = QPointingDevice::PointerType::Pen; |
| 331 | break; |
| 332 | case type_eraser: |
| 333 | d->pointerType = QPointingDevice::PointerType::Eraser; |
| 334 | break; |
| 335 | case type_mouse: |
| 336 | case type_lens: |
| 337 | d->pointerType = QPointingDevice::PointerType::Cursor; |
| 338 | break; |
| 339 | case type_finger: |
| 340 | d->pointerType = QPointingDevice::PointerType::Unknown; |
| 341 | break; |
| 342 | } |
| 343 | |
| 344 | switch (tool_type) { |
| 345 | case type::type_airbrush: |
| 346 | d->deviceType = QInputDevice::DeviceType::Airbrush; |
| 347 | d->capabilities.setFlag(flag: QInputDevice::Capability::TangentialPressure); |
| 348 | break; |
| 349 | case type::type_brush: |
| 350 | case type::type_pencil: |
| 351 | case type::type_pen: |
| 352 | case type::type_eraser: |
| 353 | d->deviceType = QInputDevice::DeviceType::Stylus; |
| 354 | break; |
| 355 | case type::type_lens: |
| 356 | d->deviceType = QInputDevice::DeviceType::Puck; |
| 357 | break; |
| 358 | case type::type_mouse: |
| 359 | case type::type_finger: |
| 360 | d->deviceType = QInputDevice::DeviceType::Unknown; |
| 361 | break; |
| 362 | } |
| 363 | } |
| 364 | |
| 365 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_hardware_serial(uint32_t hardware_serial_hi, uint32_t hardware_serial_lo) |
| 366 | { |
| 367 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); |
| 368 | d->uniqueId = QPointingDeviceUniqueId::fromNumericId(id: (quint64(hardware_serial_hi) << 32) + hardware_serial_lo); |
| 369 | } |
| 370 | |
| 371 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_hardware_id_wacom(uint32_t hardware_id_hi, uint32_t hardware_id_lo) |
| 372 | { |
| 373 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); |
| 374 | d->systemId = (quint64(hardware_id_hi) << 32) + hardware_id_lo; |
| 375 | } |
| 376 | |
| 377 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_capability(uint32_t capability) |
| 378 | { |
| 379 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); |
| 380 | switch (capability) { |
| 381 | case capability_tilt: |
| 382 | // no distinction... we have to assume it has both axes |
| 383 | d->capabilities.setFlag(flag: QInputDevice::Capability::XTilt); |
| 384 | d->capabilities.setFlag(flag: QInputDevice::Capability::YTilt); |
| 385 | break; |
| 386 | case capability_pressure: |
| 387 | d->capabilities.setFlag(flag: QInputDevice::Capability::Pressure); |
| 388 | break; |
| 389 | case capability_distance: |
| 390 | d->capabilities.setFlag(flag: QInputDevice::Capability::ZPosition); |
| 391 | break; |
| 392 | case capability_rotation: |
| 393 | d->capabilities.setFlag(flag: QInputDevice::Capability::Rotation); |
| 394 | break; |
| 395 | case capability_slider: |
| 396 | // nothing to represent that so far |
| 397 | break; |
| 398 | case capability_wheel: |
| 399 | d->capabilities.setFlag(flag: QInputDevice::Capability::Scroll); |
| 400 | d->capabilities.setFlag(flag: QInputDevice::Capability::PixelScroll); |
| 401 | break; |
| 402 | } |
| 403 | qCDebug(lcQpaInputDevices) << capability << "->" << this; |
| 404 | } |
| 405 | |
| 406 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_done() |
| 407 | { |
| 408 | QWindowSystemInterface::registerInputDevice(this); |
| 409 | } |
| 410 | |
| 411 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_removed() |
| 412 | { |
| 413 | m_tabletSeat->toolRemoved(tool: this); |
| 414 | } |
| 415 | |
| 416 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_proximity_in(uint32_t serial, zwp_tablet_v2 *tablet, wl_surface *surface) |
| 417 | { |
| 418 | Q_UNUSED(tablet); |
| 419 | |
| 420 | m_tabletSeat->seat()->mSerial = serial; |
| 421 | mEnterSerial = serial; |
| 422 | |
| 423 | if (Q_UNLIKELY(!surface)) { |
| 424 | qCDebug(lcQpaWayland) << "Ignoring zwp_tablet_tool_v2_proximity_v2 with no surface" ; |
| 425 | return; |
| 426 | } |
| 427 | m_pending.enteredSurface = true; |
| 428 | m_pending.proximitySurface = QWaylandSurface::fromWlSurface(surface); |
| 429 | |
| 430 | #if QT_CONFIG(cursor) |
| 431 | // Depends on mEnterSerial being updated |
| 432 | updateCursor(); |
| 433 | #endif |
| 434 | } |
| 435 | |
| 436 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_proximity_out() |
| 437 | { |
| 438 | m_pending.enteredSurface = false; |
| 439 | m_pending.proximitySurface = nullptr; |
| 440 | } |
| 441 | |
| 442 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_down(uint32_t serial) |
| 443 | { |
| 444 | m_pending.down = true; |
| 445 | |
| 446 | m_tabletSeat->seat()->mSerial = serial; |
| 447 | |
| 448 | if (m_pending.proximitySurface) { |
| 449 | if (QWaylandWindow *window = m_pending.proximitySurface->waylandWindow()) { |
| 450 | QWaylandInputDevice *seat = m_tabletSeat->seat(); |
| 451 | seat->display()->setLastInputDevice(device: seat, serial, window); |
| 452 | } |
| 453 | } |
| 454 | } |
| 455 | |
| 456 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_up() |
| 457 | { |
| 458 | m_pending.down = false; |
| 459 | } |
| 460 | |
| 461 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_motion(wl_fixed_t x, wl_fixed_t y) |
| 462 | { |
| 463 | m_pending.surfacePosition = QPointF(wl_fixed_to_double(x), wl_fixed_to_double(y)); |
| 464 | } |
| 465 | |
| 466 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_pressure(uint32_t pressure) |
| 467 | { |
| 468 | const int maxPressure = 65535; |
| 469 | m_pending.pressure = qreal(pressure)/maxPressure; |
| 470 | } |
| 471 | |
| 472 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_distance(uint32_t distance) |
| 473 | { |
| 474 | m_pending.distance = distance; |
| 475 | } |
| 476 | |
| 477 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_tilt(wl_fixed_t tilt_x, wl_fixed_t tilt_y) |
| 478 | { |
| 479 | m_pending.xTilt = wl_fixed_to_double(tilt_x); |
| 480 | m_pending.yTilt = wl_fixed_to_double(tilt_y); |
| 481 | } |
| 482 | |
| 483 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_rotation(wl_fixed_t degrees) |
| 484 | { |
| 485 | m_pending.rotation = wl_fixed_to_double(degrees); |
| 486 | } |
| 487 | |
| 488 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_slider(int32_t position) |
| 489 | { |
| 490 | m_pending.slider = qreal(position) / 65535; |
| 491 | } |
| 492 | |
| 493 | static Qt::MouseButton mouseButtonFromTablet(uint button) |
| 494 | { |
| 495 | switch (button) { |
| 496 | case 0x110: return Qt::MouseButton::LeftButton; // BTN_LEFT |
| 497 | case 0x14b: return Qt::MouseButton::MiddleButton; // BTN_STYLUS |
| 498 | case 0x14c: return Qt::MouseButton::RightButton; // BTN_STYLUS2 |
| 499 | default: |
| 500 | return Qt::NoButton; |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_button(uint32_t serial, uint32_t button, uint32_t state) |
| 505 | { |
| 506 | m_tabletSeat->seat()->mSerial = serial; |
| 507 | |
| 508 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(this); |
| 509 | Qt::MouseButton mouseButton = mouseButtonFromTablet(button); |
| 510 | if (state == button_state_pressed) |
| 511 | m_pending.buttons |= mouseButton; |
| 512 | else |
| 513 | m_pending.buttons &= ~mouseButton; |
| 514 | // ideally we'd get button count when the tool is discovered; seems to be a shortcoming in tablet-unstable-v2 |
| 515 | // but if we get events from buttons we didn't know existed, increase it |
| 516 | if (mouseButton == Qt::RightButton) |
| 517 | d->buttonCount = qMax(a: d->buttonCount, b: 2); |
| 518 | else if (mouseButton == Qt::MiddleButton) |
| 519 | d->buttonCount = qMax(a: d->buttonCount, b: 3); |
| 520 | } |
| 521 | |
| 522 | void QWaylandTabletToolV2::zwp_tablet_tool_v2_frame(uint32_t time) |
| 523 | { |
| 524 | if (!m_pending.proximitySurface) { |
| 525 | if (m_applied.enteredSurface) { |
| 526 | // leaving proximity |
| 527 | QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(nullptr, this, false); |
| 528 | m_pending = State(); // Don't leave pressure etc. lying around when we enter the next surface |
| 529 | m_applied = State(); |
| 530 | } else { |
| 531 | qCWarning(lcQpaWayland) << "Can't send tablet event with no proximity surface, ignoring" ; |
| 532 | } |
| 533 | return; |
| 534 | } |
| 535 | |
| 536 | QWaylandWindow *waylandWindow = QWaylandWindow::fromWlSurface(surface: m_pending.proximitySurface->object()); |
| 537 | QWindow *window = waylandWindow->window(); |
| 538 | |
| 539 | if (!m_applied.proximitySurface) { |
| 540 | // TODO get position etc. as below |
| 541 | QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, this, true); |
| 542 | m_applied.proximitySurface = m_pending.proximitySurface; |
| 543 | } |
| 544 | |
| 545 | if (!(m_pending == m_applied)) { |
| 546 | ulong timestamp = time; |
| 547 | const QPointF localPosition = waylandWindow->mapFromWlSurface(surfacePosition: m_pending.surfacePosition); |
| 548 | |
| 549 | const QPointF globalPosition = waylandWindow->mapToGlobalF(localPosition); |
| 550 | |
| 551 | Qt::MouseButtons buttons = m_pending.down ? Qt::MouseButton::LeftButton : Qt::MouseButton::NoButton; |
| 552 | buttons |= m_pending.buttons; |
| 553 | qreal pressure = m_pending.pressure; |
| 554 | qreal xTilt = m_pending.xTilt; |
| 555 | qreal yTilt = m_pending.yTilt; |
| 556 | qreal tangentialPressure = m_pending.slider; |
| 557 | qreal rotation = m_pending.rotation; |
| 558 | int z = int(m_pending.distance); |
| 559 | |
| 560 | // do not use localPosition here since that is in Qt window coordinates |
| 561 | // but we need surface coordinates to include the decoration |
| 562 | bool decorationHandledEvent = waylandWindow->handleTabletEventDecoration( |
| 563 | inputDevice: m_tabletSeat->seat(), local: m_pending.surfacePosition, |
| 564 | global: window->mapToGlobal(pos: m_pending.surfacePosition), buttons, |
| 565 | modifiers: m_tabletSeat->seat()->modifiers()); |
| 566 | |
| 567 | if (!decorationHandledEvent) { |
| 568 | QWindowSystemInterface::handleTabletEvent(window, timestamp, this, localPosition, globalPosition, |
| 569 | buttons, pressure, |
| 570 | xTilt, yTilt, tangentialPressure, rotation, z, |
| 571 | m_tabletSeat->seat()->modifiers()); |
| 572 | } |
| 573 | } |
| 574 | |
| 575 | m_applied = m_pending; |
| 576 | } |
| 577 | |
| 578 | // TODO: delete when upgrading to c++20 |
| 579 | bool QWaylandTabletToolV2::State::operator==(const QWaylandTabletToolV2::State &o) const { |
| 580 | return |
| 581 | down == o.down && |
| 582 | proximitySurface.data() == o.proximitySurface.data() && |
| 583 | enteredSurface == o.enteredSurface && |
| 584 | surfacePosition == o.surfacePosition && |
| 585 | distance == o.distance && |
| 586 | pressure == o.pressure && |
| 587 | rotation == o.rotation && |
| 588 | xTilt == o.xTilt && |
| 589 | yTilt == o.yTilt && |
| 590 | slider == o.slider && |
| 591 | buttons == o.buttons; |
| 592 | } |
| 593 | |
| 594 | QWaylandTabletPadV2::QWaylandTabletPadV2(::zwp_tablet_pad_v2 *pad) |
| 595 | : QPointingDevice(u"tablet touchpad"_s , -1, DeviceType::TouchPad, PointerType::Finger, |
| 596 | Capability::Position, |
| 597 | 1, 1) |
| 598 | , QtWayland::zwp_tablet_pad_v2(pad) |
| 599 | { |
| 600 | } |
| 601 | |
| 602 | QWaylandTabletPadV2::~QWaylandTabletPadV2() |
| 603 | { |
| 604 | destroy(); |
| 605 | } |
| 606 | |
| 607 | void QWaylandTabletPadV2::zwp_tablet_pad_v2_path(const QString &path) |
| 608 | { |
| 609 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(q: this); |
| 610 | d->busId = path; |
| 611 | } |
| 612 | |
| 613 | void QWaylandTabletPadV2::zwp_tablet_pad_v2_buttons(uint32_t buttons) |
| 614 | { |
| 615 | QPointingDevicePrivate *d = QPointingDevicePrivate::get(q: this); |
| 616 | d->buttonCount = buttons; |
| 617 | } |
| 618 | |
| 619 | void QWaylandTabletPadV2::zwp_tablet_pad_v2_group(zwp_tablet_pad_group_v2 *pad_group) |
| 620 | { |
| 621 | // As of writing Qt does not handle tablet pads group and the controls on it |
| 622 | // This proxy is server created so it is just deleted here to not leak it |
| 623 | zwp_tablet_pad_group_v2_destroy(pad_group); |
| 624 | } |
| 625 | |
| 626 | void QWaylandTabletPadV2::zwp_tablet_pad_v2_done() |
| 627 | { |
| 628 | QWindowSystemInterface::registerInputDevice(device: this); |
| 629 | } |
| 630 | |
| 631 | void QWaylandTabletPadV2::zwp_tablet_pad_v2_removed() |
| 632 | { |
| 633 | delete this; |
| 634 | } |
| 635 | |
| 636 | } // namespace QtWaylandClient |
| 637 | |
| 638 | QT_END_NAMESPACE |
| 639 | |
| 640 | #include "moc_qwaylandtabletv2_p.cpp" |
| 641 | |