Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qxcbconnection_xi2.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 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 "qxcbconnection.h"
5#include "qxcbkeyboard.h"
7#include "qxcbscreen.h"
8#include "qxcbwindow.h"
9#include "QtCore/qmetaobject.h"
10#include "QtCore/qmath.h"
11#include <QtGui/qpointingdevice.h>
12#include <QtGui/private/qpointingdevice_p.h>
13#include <qpa/qwindowsysteminterface_p.h>
14#include <QDebug>
15
16#include <xcb/xinput.h>
17
18#if QT_CONFIG(gestures)
19#define QT_XCB_HAS_TOUCHPAD_GESTURES (XCB_INPUT_MINOR_VERSION >= 4)
20#endif
21
22using namespace Qt::StringLiterals;
23
24using qt_xcb_input_device_event_t = xcb_input_button_press_event_t;
25#if QT_XCB_HAS_TOUCHPAD_GESTURES
26using qt_xcb_input_pinch_event_t = xcb_input_gesture_pinch_begin_event_t;
27using qt_xcb_input_swipe_event_t = xcb_input_gesture_swipe_begin_event_t;
28#endif
29
31 xcb_input_event_mask_t header;
32 alignas(4) uint8_t mask[8] = {}; // up to 2 units of 4 bytes
33};
34
35static inline void setXcbMask(uint8_t* mask, int bit)
36{
37 // note that XI protocol always uses little endian for masks over the wire
38 mask[bit >> 3] |= 1 << (bit & 7);
39}
40
42{
43 // These state events do not depend on a specific X window, but are global
44 // for the X client's (application's) state.
45 qt_xcb_input_event_mask_t xiEventMask;
46 xiEventMask.header.deviceid = XCB_INPUT_DEVICE_ALL;
47 xiEventMask.header.mask_len = 1;
48 setXcbMask(xiEventMask.mask, XCB_INPUT_HIERARCHY);
49 setXcbMask(xiEventMask.mask, XCB_INPUT_DEVICE_CHANGED);
50 setXcbMask(xiEventMask.mask, XCB_INPUT_PROPERTY);
51 xcb_input_xi_select_events(xcb_connection(), rootWindow(), 1, &xiEventMask.header);
52}
53
55{
56 if (window == rootWindow())
57 return;
58
60
61 setXcbMask(mask.mask, XCB_INPUT_BUTTON_PRESS);
62 setXcbMask(mask.mask, XCB_INPUT_BUTTON_RELEASE);
63 setXcbMask(mask.mask, XCB_INPUT_MOTION);
64 // There is a check for enter/leave events in plain xcb enter/leave event handler,
65 // core enter/leave events will be ignored in this case.
66 setXcbMask(mask.mask, XCB_INPUT_ENTER);
67 setXcbMask(mask.mask, XCB_INPUT_LEAVE);
68 if (isAtLeastXI22()) {
69 setXcbMask(mask.mask, XCB_INPUT_TOUCH_BEGIN);
70 setXcbMask(mask.mask, XCB_INPUT_TOUCH_UPDATE);
71 setXcbMask(mask.mask, XCB_INPUT_TOUCH_END);
72 }
73#if QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
74 if (isAtLeastXI24()) {
75 setXcbMask(mask.mask, XCB_INPUT_GESTURE_PINCH_BEGIN);
76 setXcbMask(mask.mask, XCB_INPUT_GESTURE_PINCH_UPDATE);
77 setXcbMask(mask.mask, XCB_INPUT_GESTURE_PINCH_END);
78 setXcbMask(mask.mask, XCB_INPUT_GESTURE_SWIPE_BEGIN);
79 setXcbMask(mask.mask, XCB_INPUT_GESTURE_SWIPE_UPDATE);
80 setXcbMask(mask.mask, XCB_INPUT_GESTURE_SWIPE_END);
81 }
82#endif // QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
83
84 mask.header.deviceid = XCB_INPUT_DEVICE_ALL;
85 mask.header.mask_len = 2;
86 xcb_void_cookie_t cookie =
87 xcb_input_xi_select_events_checked(xcb_connection(), window, 1, &mask.header);
88 xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
89 if (error) {
90 qCDebug(lcQpaXInput, "failed to select events, window %x, error code %d", window, error->error_code);
91 free(error);
92 } else {
94 }
95}
96
97static inline qreal fixed3232ToReal(xcb_input_fp3232_t val)
98{
99 return qreal(val.integral) + qreal(val.frac) / (1ULL << 32);
100}
101
102#if QT_CONFIG(tabletevent)
121static const QPointingDevice *tabletToolInstance(QPointingDevice *master, const QString &tabletName,
122 qint64 id, quint32 usbId, quint32 toolId, qint64 uniqueId,
124 QPointingDevice::Capabilities capsOverride = QInputDevice::Capability::None)
125{
128 QPointingDevice::Capabilities caps = QInputDevice::Capability::Position |
132 capsOverride;
133 int buttonCount = 3; // the tip, plus two barrel buttons
134 // keep in sync with wacom_intuos_inout() in Linux kernel driver wacom_wac.c
135 // TODO yeah really, there are many more now so this needs updating
136 switch (toolId) {
137 case 0xd12:
138 case 0x912:
139 case 0x112:
140 case 0x913: /* Intuos3 Airbrush */
141 case 0x902: /* Intuos4/5 13HD/24HD Airbrush */
142 case 0x100902: /* Intuos4/5 13HD/24HD Airbrush */
147 buttonCount = 2;
148 break;
149 case 0x91b: /* Intuos3 Airbrush Eraser */
150 case 0x90a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
151 case 0x10090a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
157 buttonCount = 2;
158 break;
159 case 0x007: /* Mouse 4D and 2D */
160 case 0x09c:
161 case 0x094:
162 // TODO set something to indicate a multi-dimensional capability:
163 // Capability::3D or 4D or QPointingDevice::setMaximumDimensions()?
165 buttonCount = 5; // TODO only if it's a 4D Mouse
166 break;
167 case 0x017: /* Intuos3 2D Mouse */
168 case 0x806: /* Intuos4 Mouse */
170 break;
171 case 0x096: /* Lens cursor */
172 case 0x097: /* Intuos3 Lens cursor */
173 case 0x006: /* Intuos4 Lens cursor */
175 break;
176 case 0x885: /* Intuos3 Art Pen (Marker Pen) */
177 case 0x100804: /* Intuos4/5 13HD/24HD Art Pen */
181 buttonCount = 1;
182 break;
183 case 0x10080c: /* Intuos4/5 13HD/24HD Art Pen Eraser */
188 buttonCount = 1;
189 break;
190 case 0:
192 break;
193 }
194 if (pointerTypeOverride != QPointingDevice::PointerType::Unknown)
195 pointerType = pointerTypeOverride;
198 caps, id);
199 if (!ret) {
200 ret = new QPointingDevice(tabletName, id, devType, pointerType, caps, 1, buttonCount,
201 master ? master->seatName() : QString(),
202 QPointingDeviceUniqueId::fromNumericId(uniqueId), master);
204 }
206 devPriv->busId = QString::number(usbId, 16);
207 devPriv->toolId = toolId;
208 if (master)
209 devPriv->seatName = master->seatName();
210 return ret;
211}
212
213static const char *toolName(QInputDevice::DeviceType tool) {
214 static const QMetaObject *metaObject = qt_getEnumMetaObject(tool);
215 static const QMetaEnum me = metaObject->enumerator(metaObject->indexOfEnumerator(qt_getEnumName(tool)));
216 return me.valueToKey(int(tool));
217}
218
219static const char *pointerTypeName(QPointingDevice::PointerType ptype) {
220 static const QMetaObject *metaObject = qt_getEnumMetaObject(ptype);
221 static const QMetaEnum me = metaObject->enumerator(metaObject->indexOfEnumerator(qt_getEnumName(ptype)));
222 return me.valueToKey(int(ptype));
223}
224#endif
225
226void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting, QPointingDevice *master)
227{
228 auto *deviceInfo = reinterpret_cast<xcb_input_xi_device_info_t *>(info);
229 if (removeExisting) {
230#if QT_CONFIG(tabletevent)
231 for (int i = 0; i < m_tabletData.size(); ++i) {
232 if (m_tabletData.at(i).deviceId == deviceInfo->deviceid) {
233 m_tabletData.remove(i);
234 break;
235 }
236 }
237#endif
238 m_touchDevices.remove(deviceInfo->deviceid);
239 }
240
241 const QByteArray nameRaw = QByteArray(xcb_input_xi_device_info_name(deviceInfo),
242 xcb_input_xi_device_info_name_length(deviceInfo));
243 const QString name = QString::fromUtf8(nameRaw);
244 m_xiSlavePointerIds.append(deviceInfo->deviceid);
245 qCDebug(lcQpaXInputDevices) << "input device " << name << "ID" << deviceInfo->deviceid;
246#if QT_CONFIG(tabletevent)
247 TabletData tabletData;
248#endif
249 QXcbScrollingDevicePrivate *scrollingDeviceP = nullptr;
250 bool used = false;
251 auto scrollingDevice = [&]() {
252 if (!scrollingDeviceP)
253 scrollingDeviceP = new QXcbScrollingDevicePrivate(name, deviceInfo->deviceid,
255 return scrollingDeviceP;
256 };
257
258 int buttonCount = 32;
259 auto classes_it = xcb_input_xi_device_info_classes_iterator(deviceInfo);
260 for (; classes_it.rem; xcb_input_device_class_next(&classes_it)) {
261 xcb_input_device_class_t *classinfo = classes_it.data;
262 switch (classinfo->type) {
263 case XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR: {
264 auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classinfo);
265 const int valuatorAtom = qatom(vci->label);
266 qCDebug(lcQpaXInputDevices) << " has valuator" << atomName(vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms);
267#if QT_CONFIG(tabletevent)
268 if (valuatorAtom < QXcbAtom::NAtoms) {
269 TabletData::ValuatorClassInfo info;
270 info.minVal = fixed3232ToReal(vci->min);
271 info.maxVal = fixed3232ToReal(vci->max);
272 info.number = vci->number;
273 tabletData.valuatorInfo[valuatorAtom] = info;
274 }
275#endif // QT_CONFIG(tabletevent)
276 if (valuatorAtom == QXcbAtom::AtomRelHorizScroll || valuatorAtom == QXcbAtom::AtomRelHorizWheel)
277 scrollingDevice()->lastScrollPosition.setX(fixed3232ToReal(vci->value));
278 else if (valuatorAtom == QXcbAtom::AtomRelVertScroll || valuatorAtom == QXcbAtom::AtomRelVertWheel)
279 scrollingDevice()->lastScrollPosition.setY(fixed3232ToReal(vci->value));
280 break;
281 }
282 case XCB_INPUT_DEVICE_CLASS_TYPE_SCROLL: {
283 auto *sci = reinterpret_cast<xcb_input_scroll_class_t *>(classinfo);
284 if (sci->scroll_type == XCB_INPUT_SCROLL_TYPE_VERTICAL) {
285 auto dev = scrollingDevice();
286 dev->orientations.setFlag(Qt::Vertical);
287 dev->verticalIndex = sci->number;
288 dev->verticalIncrement = fixed3232ToReal(sci->increment);
289 } else if (sci->scroll_type == XCB_INPUT_SCROLL_TYPE_HORIZONTAL) {
290 auto dev = scrollingDevice();
291 dev->orientations.setFlag(Qt::Horizontal);
292 dev->horizontalIndex = sci->number;
293 dev->horizontalIncrement = fixed3232ToReal(sci->increment);
294 }
295 break;
296 }
297 case XCB_INPUT_DEVICE_CLASS_TYPE_BUTTON: {
298 auto *bci = reinterpret_cast<xcb_input_button_class_t *>(classinfo);
299 xcb_atom_t *labels = nullptr;
300 if (bci->num_buttons >= 5) {
301 labels = xcb_input_button_class_labels(bci);
302 xcb_atom_t label4 = labels[3];
303 xcb_atom_t label5 = labels[4];
304 // Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on
305 // button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons.
306 if ((!label4 || qatom(label4) == QXcbAtom::AtomButtonWheelUp || qatom(label4) == QXcbAtom::AtomButtonWheelDown) &&
307 (!label5 || qatom(label5) == QXcbAtom::AtomButtonWheelUp || qatom(label5) == QXcbAtom::AtomButtonWheelDown))
308 scrollingDevice()->legacyOrientations |= Qt::Vertical;
309 }
310 if (bci->num_buttons >= 7) {
311 xcb_atom_t label6 = labels[5];
312 xcb_atom_t label7 = labels[6];
313 if ((!label6 || qatom(label6) == QXcbAtom::AtomButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::AtomButtonHorizWheelRight))
314 scrollingDevice()->legacyOrientations |= Qt::Horizontal;
315 }
316 buttonCount = bci->num_buttons;
317 qCDebug(lcQpaXInputDevices, " has %d buttons", bci->num_buttons);
318 break;
319 }
320 case XCB_INPUT_DEVICE_CLASS_TYPE_KEY:
321 qCDebug(lcQpaXInputDevices) << " it's a keyboard";
322 break;
323 case XCB_INPUT_DEVICE_CLASS_TYPE_TOUCH:
324#if QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
325 case XCB_INPUT_DEVICE_CLASS_TYPE_GESTURE:
326#endif // QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
327 // will be handled in populateTouchDevices()
328 break;
329 default:
330 qCDebug(lcQpaXInputDevices) << " has class" << classinfo->type;
331 break;
332 }
333 }
334 bool isTablet = false;
335#if QT_CONFIG(tabletevent)
336 // If we have found the valuators which we expect a tablet to have, it might be a tablet.
337 if (tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsX) &&
338 tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsY) &&
339 tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsPressure))
340 isTablet = true;
341
342 // But we need to be careful not to take the touch and tablet-button devices as tablets.
343 QByteArray nameLower = nameRaw.toLower();
344 QString dbgType = "UNKNOWN"_L1;
345 if (nameLower.contains("eraser")) {
346 isTablet = true;
347 tabletData.pointerType = QPointingDevice::PointerType::Eraser;
348 dbgType = "eraser"_L1;
349 } else if (nameLower.contains("cursor") && !(nameLower.contains("cursor controls") && nameLower.contains("trackball"))) {
350 isTablet = true;
351 tabletData.pointerType = QPointingDevice::PointerType::Cursor;
352 dbgType = "cursor"_L1;
353 } else if (nameLower.contains("wacom") && nameLower.contains("finger touch")) {
354 isTablet = false;
355 } else if ((nameLower.contains("pen") || nameLower.contains("stylus")) && isTablet) {
356 tabletData.pointerType = QPointingDevice::PointerType::Pen;
357 dbgType = "pen"_L1;
358 } else if (nameLower.contains("wacom") && isTablet && !nameLower.contains("touch")) {
359 // combined device (evdev) rather than separate pen/eraser (wacom driver)
360 tabletData.pointerType = QPointingDevice::PointerType::Pen;
361 dbgType = "pen"_L1;
362 } else if (nameLower.contains("aiptek") /* && device == QXcbAtom::AtomKEYBOARD */) {
363 // some "Genius" tablets
364 isTablet = true;
365 tabletData.pointerType = QPointingDevice::PointerType::Pen;
366 dbgType = "pen"_L1;
367 } else if (nameLower.contains("waltop") && nameLower.contains("tablet")) {
368 // other "Genius" tablets
369 // WALTOP International Corp. Slim Tablet
370 isTablet = true;
371 tabletData.pointerType = QPointingDevice::PointerType::Pen;
372 dbgType = "pen"_L1;
373 } else if (nameLower.contains("uc-logic") && isTablet) {
374 tabletData.pointerType = QPointingDevice::PointerType::Pen;
375 dbgType = "pen"_L1;
376 } else if (nameLower.contains("ugee")) {
377 isTablet = true;
378 tabletData.pointerType = QPointingDevice::PointerType::Pen;
379 dbgType = "pen"_L1;
380 } else {
381 isTablet = false;
382 }
383
384 if (isTablet) {
385 tabletData.deviceId = deviceInfo->deviceid;
386 tabletData.name = name;
387 m_tabletData.append(tabletData);
388 qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType;
389 QPointingDevice::Capabilities capsOverride = QInputDevice::Capability::None;
390 if (tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsTiltX))
391 capsOverride.setFlag(QInputDevice::Capability::XTilt);
392 if (tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsTiltY))
393 capsOverride.setFlag(QInputDevice::Capability::YTilt);
394 // TODO can we get USB ID?
395 Q_ASSERT(deviceInfo->deviceid == tabletData.deviceId);
396 const QPointingDevice *dev = tabletToolInstance(master,
397 tabletData.name, deviceInfo->deviceid, 0, 0, tabletData.serialId,
398 tabletData.pointerType, capsOverride);
399 Q_ASSERT(dev);
400 }
401#endif // QT_CONFIG(tabletevent)
402
403 if (scrollingDeviceP) {
404 // Only use legacy wheel button events when we don't have real scroll valuators.
405 scrollingDeviceP->legacyOrientations &= ~scrollingDeviceP->orientations;
406 qCDebug(lcQpaXInputDevices) << " it's a scrolling device";
407 }
408
409 if (!isTablet) {
410 TouchDeviceData *dev = populateTouchDevices(deviceInfo, scrollingDeviceP, &used);
411 if (dev && lcQpaXInputDevices().isDebugEnabled()) {
412 if (dev->qtTouchDevice->type() == QInputDevice::DeviceType::TouchScreen)
413 qCDebug(lcQpaXInputDevices, " it's a touchscreen with type %d capabilities 0x%X max touch points %d",
414 int(dev->qtTouchDevice->type()), qint32(dev->qtTouchDevice->capabilities()),
415 dev->qtTouchDevice->maximumPoints());
416 else if (dev->qtTouchDevice->type() == QInputDevice::DeviceType::TouchPad)
417 qCDebug(lcQpaXInputDevices, " it's a touchpad with type %d capabilities 0x%X max touch points %d size %f x %f",
418 int(dev->qtTouchDevice->type()), qint32(dev->qtTouchDevice->capabilities()),
419 dev->qtTouchDevice->maximumPoints(),
420 dev->size.width(), dev->size.height());
421 }
422 }
423
424 if (!QInputDevicePrivate::fromId(deviceInfo->deviceid)) {
425 qCDebug(lcQpaXInputDevices) << " it's a mouse";
427 if (scrollingDeviceP) {
428 scrollingDeviceP->capabilities |= caps;
429 scrollingDeviceP->buttonCount = buttonCount;
430 if (master)
431 scrollingDeviceP->seatName = master->seatName();
433 used = true;
434 } else {
436 name, deviceInfo->deviceid,
438 caps, 1, buttonCount, (master ? master->seatName() : QString()), QPointingDeviceUniqueId(), master));
439 }
440 }
441
442 if (!used && scrollingDeviceP) {
443 QXcbScrollingDevice *holder = new QXcbScrollingDevice(*scrollingDeviceP, master);
444 holder->deleteLater();
445 }
446}
447
455void QXcbConnection::xi2SetupDevices()
456{
457 m_xiMasterPointerIds.clear();
458
459 auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), XCB_INPUT_DEVICE_ALL);
460 if (!reply) {
461 qCDebug(lcQpaXInputDevices) << "failed to query devices";
462 return;
463 }
464
465 // Start with all known devices; remove the ones that still exist.
466 // Afterwards, previousDevices will be the list of those that we should delete.
467 QList<const QInputDevice *> previousDevices = QInputDevice::devices();
468 // Return true if the device with the given systemId is new;
469 // otherwise remove it from previousDevices and return false.
470 auto newOrKeep = [&previousDevices](qint64 systemId) {
471 // if nothing is removed from previousDevices, it's a new device
472 return !previousDevices.removeIf([systemId](const QInputDevice *dev) {
473 return dev->systemId() == systemId;
474 });
475 };
476
477 // XInput doesn't provide a way to identify "seats"; but each device has an attachment to another device.
478 // So we make up a seatId: master-keyboard-id << 16 | master-pointer-id.
479
480 auto it = xcb_input_xi_query_device_infos_iterator(reply.get());
481 for (; it.rem; xcb_input_xi_device_info_next(&it)) {
482 xcb_input_xi_device_info_t *deviceInfo = it.data;
483 switch (deviceInfo->type) {
484 case XCB_INPUT_DEVICE_TYPE_MASTER_KEYBOARD: {
485 if (newOrKeep(deviceInfo->deviceid)) {
486 auto dev = new QInputDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)),
487 deviceInfo->deviceid, QInputDevice::DeviceType::Keyboard,
488 QString::number(deviceInfo->deviceid << 16 | deviceInfo->attachment, 16), this);
490 }
491 } break;
492 case XCB_INPUT_DEVICE_TYPE_MASTER_POINTER: {
493 m_xiMasterPointerIds.append(deviceInfo->deviceid);
494 if (newOrKeep(deviceInfo->deviceid)) {
495 auto dev = new QXcbScrollingDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid,
497 32, QString::number(deviceInfo->attachment << 16 | deviceInfo->deviceid, 16), this);
499 }
500 continue;
501 } break;
502 default:
503 break;
504 }
505 }
506
507 it = xcb_input_xi_query_device_infos_iterator(reply.get());
508 for (; it.rem; xcb_input_xi_device_info_next(&it)) {
509 xcb_input_xi_device_info_t *deviceInfo = it.data;
510 switch (deviceInfo->type) {
511 case XCB_INPUT_DEVICE_TYPE_MASTER_KEYBOARD:
512 case XCB_INPUT_DEVICE_TYPE_MASTER_POINTER:
513 // already registered
514 break;
515 case XCB_INPUT_DEVICE_TYPE_SLAVE_POINTER: {
516 if (newOrKeep(deviceInfo->deviceid)) {
517 m_xiSlavePointerIds.append(deviceInfo->deviceid);
518 QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment));
519 Q_ASSERT(master);
520 xi2SetupSlavePointerDevice(deviceInfo, false, qobject_cast<QPointingDevice *>(master));
521 }
522 } break;
523 case XCB_INPUT_DEVICE_TYPE_SLAVE_KEYBOARD: {
524 if (newOrKeep(deviceInfo->deviceid)) {
525 QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment));
526 Q_ASSERT(master);
528 QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid,
529 QInputDevice::DeviceType::Keyboard, master->seatName(), master));
530 }
531 } break;
532 case XCB_INPUT_DEVICE_TYPE_FLOATING_SLAVE:
533 break;
534 }
535 }
536
537 // previousDevices is now the list of those that are no longer found
538 qCDebug(lcQpaXInputDevices) << "removed" << previousDevices;
539 for (auto it = previousDevices.constBegin(); it != previousDevices.constEnd(); ++it) {
540 const auto id = (*it)->systemId();
541 m_xiSlavePointerIds.removeAll(id);
542 m_touchDevices.remove(id);
543 }
544 qDeleteAll(previousDevices);
545
546 if (m_xiMasterPointerIds.size() > 1)
547 qCDebug(lcQpaXInputDevices) << "multi-pointer X detected";
548}
549
550QXcbConnection::TouchDeviceData *QXcbConnection::touchDeviceForId(int id)
551{
552 TouchDeviceData *dev = nullptr;
553 if (m_touchDevices.contains(id))
554 dev = &m_touchDevices[id];
555 return dev;
556}
557
558QXcbConnection::TouchDeviceData *QXcbConnection::populateTouchDevices(void *info, QXcbScrollingDevicePrivate *scrollingDeviceP, bool *used)
559{
560 auto *deviceInfo = reinterpret_cast<xcb_input_xi_device_info_t *>(info);
561 QPointingDevice::Capabilities caps;
563 int maxTouchPoints = 1;
564 bool isTouchDevice = false;
565 bool hasRelativeCoords = false;
566 TouchDeviceData dev;
567 auto classes_it = xcb_input_xi_device_info_classes_iterator(deviceInfo);
568 for (; classes_it.rem; xcb_input_device_class_next(&classes_it)) {
569 xcb_input_device_class_t *classinfo = classes_it.data;
570 switch (classinfo->type) {
571 case XCB_INPUT_DEVICE_CLASS_TYPE_TOUCH: {
572 auto *tci = reinterpret_cast<xcb_input_touch_class_t *>(classinfo);
573 maxTouchPoints = tci->num_touches;
574 qCDebug(lcQpaXInputDevices, " has touch class with mode %d", tci->mode);
575 switch (tci->mode) {
576 case XCB_INPUT_TOUCH_MODE_DEPENDENT:
578 break;
579 case XCB_INPUT_TOUCH_MODE_DIRECT:
581 break;
582 }
583 break;
584 }
585#if QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
586 case XCB_INPUT_DEVICE_CLASS_TYPE_GESTURE: {
587 // Note that gesture devices can only be touchpads (i.e. dependent devices in XInput
588 // naming convention). According to XI 2.4, the same device can't have touch and
589 // gesture device classes.
590 auto *gci = reinterpret_cast<xcb_input_gesture_class_t *>(classinfo);
591 maxTouchPoints = gci->num_touches;
592 qCDebug(lcQpaXInputDevices, " has gesture class");
594 break;
595 }
596#endif // QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
597 case XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR: {
598 auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classinfo);
599 const QXcbAtom::Atom valuatorAtom = qatom(vci->label);
600 if (valuatorAtom < QXcbAtom::NAtoms) {
601 TouchDeviceData::ValuatorClassInfo info;
602 info.min = fixed3232ToReal(vci->min);
603 info.max = fixed3232ToReal(vci->max);
604 info.number = vci->number;
605 info.label = valuatorAtom;
606 dev.valuatorInfo.append(info);
607 }
608 // Some devices (mice) report a resolution of 0; they will be excluded later,
609 // for now just prevent a division by zero
610 const int vciResolution = vci->resolution ? vci->resolution : 1;
611 if (valuatorAtom == QXcbAtom::AtomAbsMTPositionX)
613 else if (valuatorAtom == QXcbAtom::AtomAbsMTTouchMajor)
615 else if (valuatorAtom == QXcbAtom::AtomAbsMTOrientation)
616 dev.providesTouchOrientation = true;
617 else if (valuatorAtom == QXcbAtom::AtomAbsMTPressure || valuatorAtom == QXcbAtom::AtomAbsPressure)
619 else if (valuatorAtom == QXcbAtom::AtomRelX) {
620 hasRelativeCoords = true;
621 dev.size.setWidth((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
622 } else if (valuatorAtom == QXcbAtom::AtomRelY) {
623 hasRelativeCoords = true;
624 dev.size.setHeight((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
625 } else if (valuatorAtom == QXcbAtom::AtomAbsX) {
627 dev.size.setWidth((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
628 } else if (valuatorAtom == QXcbAtom::AtomAbsY) {
630 dev.size.setHeight((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
631 } else if (valuatorAtom == QXcbAtom::AtomRelVertWheel || valuatorAtom == QXcbAtom::AtomRelHorizWheel) {
633 }
634 break;
635 }
636 default:
637 break;
638 }
639 }
640 if (type == QInputDevice::DeviceType::Unknown && caps && hasRelativeCoords) {
642 if (dev.size.width() < 10 || dev.size.height() < 10 ||
643 dev.size.width() > 10000 || dev.size.height() > 10000)
644 dev.size = QSizeF(130, 110);
645 }
648
650 QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment));
651 Q_ASSERT(master);
652 if (scrollingDeviceP) {
653 // valuators were already discovered in QXcbConnection::xi2SetupSlavePointerDevice, so just finish initialization
654 scrollingDeviceP->deviceType = type;
655 scrollingDeviceP->pointerType = QPointingDevice::PointerType::Finger;
656 scrollingDeviceP->capabilities |= caps;
657 scrollingDeviceP->maximumTouchPoints = maxTouchPoints;
658 scrollingDeviceP->buttonCount = 3;
659 scrollingDeviceP->seatName = master->seatName();
660 dev.qtTouchDevice = new QXcbScrollingDevice(*scrollingDeviceP, master);
661 if (Q_UNLIKELY(!caps.testFlag(QInputDevice::Capability::Scroll)))
662 qCDebug(lcQpaXInputDevices) << "unexpectedly missing RelVert/HorizWheel atoms for touchpad with scroll capability" << dev.qtTouchDevice;
663 *used = true;
664 } else {
665 dev.qtTouchDevice = new QPointingDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo),
666 xcb_input_xi_device_info_name_length(deviceInfo)),
667 deviceInfo->deviceid,
668 type, QPointingDevice::PointerType::Finger, caps, maxTouchPoints, 0,
669 master->seatName(), QPointingDeviceUniqueId(), master);
670 }
671 if (caps != 0)
673 m_touchDevices[deviceInfo->deviceid] = dev;
674 isTouchDevice = true;
675 }
676
677 return isTouchDevice ? &m_touchDevices[deviceInfo->deviceid] : nullptr;
678}
679
680static inline qreal fixed1616ToReal(xcb_input_fp1616_t val)
681{
682 return qreal(val) / 0x10000;
683}
684
685void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
686{
687 auto *xiEvent = reinterpret_cast<qt_xcb_input_device_event_t *>(event);
688 setTime(xiEvent->time);
689 if (m_xiSlavePointerIds.contains(xiEvent->deviceid) && xiEvent->event_type != XCB_INPUT_PROPERTY) {
690 if (!m_duringSystemMoveResize)
691 return;
692 if (xiEvent->event == XCB_NONE)
693 return;
694
695 if (xiEvent->event_type == XCB_INPUT_BUTTON_RELEASE
696 && xiEvent->detail == XCB_BUTTON_INDEX_1 ) {
697 abortSystemMoveResize(xiEvent->event);
698 } else if (xiEvent->event_type == XCB_INPUT_TOUCH_END) {
699 abortSystemMoveResize(xiEvent->event);
700 return;
701 } else {
702 return;
703 }
704 }
705 int sourceDeviceId = xiEvent->deviceid; // may be the master id
706 qt_xcb_input_device_event_t *xiDeviceEvent = nullptr;
707 xcb_input_enter_event_t *xiEnterEvent = nullptr;
708 QXcbWindowEventListener *eventListener = nullptr;
709
710 switch (xiEvent->event_type) {
711 case XCB_INPUT_BUTTON_PRESS:
712 case XCB_INPUT_BUTTON_RELEASE:
713 case XCB_INPUT_MOTION:
714 case XCB_INPUT_TOUCH_BEGIN:
715 case XCB_INPUT_TOUCH_UPDATE:
716 case XCB_INPUT_TOUCH_END:
717 {
718 xiDeviceEvent = xiEvent;
719 eventListener = windowEventListenerFromId(xiDeviceEvent->event);
720 sourceDeviceId = xiDeviceEvent->sourceid; // use the actual device id instead of the master
721 break;
722 }
723#if QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
724 case XCB_INPUT_GESTURE_PINCH_BEGIN:
725 case XCB_INPUT_GESTURE_PINCH_UPDATE:
726 case XCB_INPUT_GESTURE_PINCH_END:
727 xi2HandleGesturePinchEvent(event);
728 return;
729 case XCB_INPUT_GESTURE_SWIPE_BEGIN:
730 case XCB_INPUT_GESTURE_SWIPE_UPDATE:
731 case XCB_INPUT_GESTURE_SWIPE_END:
732 xi2HandleGestureSwipeEvent(event);
733 return;
734#endif // QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
735 case XCB_INPUT_ENTER:
736 case XCB_INPUT_LEAVE: {
737 xiEnterEvent = reinterpret_cast<xcb_input_enter_event_t *>(event);
738 eventListener = windowEventListenerFromId(xiEnterEvent->event);
739 sourceDeviceId = xiEnterEvent->sourceid; // use the actual device id instead of the master
740 break;
741 }
742 case XCB_INPUT_HIERARCHY:
743 xi2HandleHierarchyEvent(event);
744 return;
745 case XCB_INPUT_DEVICE_CHANGED:
746 xi2HandleDeviceChangedEvent(event);
747 return;
748 default:
749 break;
750 }
751
752 if (eventListener) {
753 if (eventListener->handleNativeEvent(reinterpret_cast<xcb_generic_event_t *>(event)))
754 return;
755 }
756
757#if QT_CONFIG(tabletevent)
758 if (!xiEnterEvent) {
759 // TODO we need the UID here; tabletDataForDevice doesn't have enough to go on (?)
760 QXcbConnection::TabletData *tablet = tabletDataForDevice(sourceDeviceId);
761 if (tablet && xi2HandleTabletEvent(event, tablet))
762 return;
763 }
764#endif // QT_CONFIG(tabletevent)
765
766 if (auto device = QPointingDevicePrivate::pointingDeviceById(sourceDeviceId))
767 xi2HandleScrollEvent(event, device);
768 else
769 qCDebug(lcQpaXInputEvents) << "scroll event from unregistered device" << sourceDeviceId;
770
771 if (xiDeviceEvent) {
772 switch (xiDeviceEvent->event_type) {
773 case XCB_INPUT_BUTTON_PRESS:
774 case XCB_INPUT_BUTTON_RELEASE:
775 case XCB_INPUT_MOTION:
776 if (eventListener && !(xiDeviceEvent->flags & XCB_INPUT_POINTER_EVENT_FLAGS_POINTER_EMULATED))
777 eventListener->handleXIMouseEvent(event);
778 break;
779
780 case XCB_INPUT_TOUCH_BEGIN:
781 case XCB_INPUT_TOUCH_UPDATE:
782 case XCB_INPUT_TOUCH_END:
783 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
784 qCDebug(lcQpaXInputEvents, "XI2 touch event type %d seq %d detail %d pos %6.1f, %6.1f root pos %6.1f, %6.1f on window %x",
785 event->event_type, xiDeviceEvent->sequence, xiDeviceEvent->detail,
786 fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y),
787 fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y),xiDeviceEvent->event);
788 if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) {
789 xi2ProcessTouch(xiDeviceEvent, platformWindow);
790 } else { // When the window cannot be matched, delete it from touchPoints
791 if (TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid))
792 dev->touchPoints.remove((xiDeviceEvent->detail % INT_MAX));
793 }
794 break;
795 }
796 } else if (xiEnterEvent && eventListener) {
797 switch (xiEnterEvent->event_type) {
798 case XCB_INPUT_ENTER:
799 case XCB_INPUT_LEAVE:
800 eventListener->handleXIEnterLeave(event);
801 break;
802 }
803 }
804}
805
807{
808 auto device = touchDeviceForId(id);
809 return device && device->qtTouchDevice->type() == QInputDevice::DeviceType::TouchScreen;
810}
811
812void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindow)
813{
814 auto *xiDeviceEvent = reinterpret_cast<xcb_input_touch_begin_event_t *>(xiDevEvent);
815 TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid);
816 if (!dev) {
817 qCDebug(lcQpaXInputEvents) << "didn't find the dev for given sourceid - " << xiDeviceEvent->sourceid
818 << ", try to repopulate xi2 devices";
819 xi2SetupDevices();
820 dev = touchDeviceForId(xiDeviceEvent->sourceid);
821 if (!dev) {
822 qCDebug(lcQpaXInputEvents) << "still can't find the dev for it, give up.";
823 return;
824 }
825 }
826 const bool firstTouch = dev->touchPoints.isEmpty();
827 if (xiDeviceEvent->event_type == XCB_INPUT_TOUCH_BEGIN) {
829 tp.id = xiDeviceEvent->detail % INT_MAX;
831 tp.pressure = -1.0;
832 dev->touchPoints[tp.id] = tp;
833 }
834 QWindowSystemInterface::TouchPoint &touchPoint = dev->touchPoints[xiDeviceEvent->detail];
835 QXcbScreen* screen = platformWindow->xcbScreen();
836 qreal x = fixed1616ToReal(xiDeviceEvent->root_x);
837 qreal y = fixed1616ToReal(xiDeviceEvent->root_y);
838 qreal nx = -1.0, ny = -1.0;
839 qreal w = 0.0, h = 0.0;
840 bool majorAxisIsY = touchPoint.area.height() > touchPoint.area.width();
841 for (const TouchDeviceData::ValuatorClassInfo &vci : std::as_const(dev->valuatorInfo)) {
842 double value;
843 if (!xi2GetValuatorValueIfSet(xiDeviceEvent, vci.number, &value))
844 continue;
845 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
846 qCDebug(lcQpaXInputEvents, " valuator %20s value %lf from range %lf -> %lf",
847 atomName(atom(vci.label)).constData(), value, vci.min, vci.max);
848 if (value > vci.max)
849 value = vci.max;
850 if (value < vci.min)
851 value = vci.min;
852 qreal valuatorNormalized = (value - vci.min) / (vci.max - vci.min);
853 if (vci.label == QXcbAtom::AtomRelX) {
854 nx = valuatorNormalized;
855 } else if (vci.label == QXcbAtom::AtomRelY) {
856 ny = valuatorNormalized;
857 } else if (vci.label == QXcbAtom::AtomAbsX) {
858 nx = valuatorNormalized;
859 } else if (vci.label == QXcbAtom::AtomAbsY) {
860 ny = valuatorNormalized;
861 } else if (vci.label == QXcbAtom::AtomAbsMTPositionX) {
862 nx = valuatorNormalized;
863 } else if (vci.label == QXcbAtom::AtomAbsMTPositionY) {
864 ny = valuatorNormalized;
865 } else if (vci.label == QXcbAtom::AtomAbsMTTouchMajor) {
866 const qreal sw = screen->geometry().width();
867 const qreal sh = screen->geometry().height();
868 w = valuatorNormalized * qHypot(sw, sh);
869 } else if (vci.label == QXcbAtom::AtomAbsMTTouchMinor) {
870 const qreal sw = screen->geometry().width();
871 const qreal sh = screen->geometry().height();
872 h = valuatorNormalized * qHypot(sw, sh);
873 } else if (vci.label == QXcbAtom::AtomAbsMTOrientation) {
874 // Find the closest axis.
875 // 0 corresponds to the Y axis, vci.max to the X axis.
876 // Flipping over the Y axis and rotating by 180 degrees
877 // don't change the result, so normalize value to range
878 // [0, vci.max] first.
879 value = qAbs(value);
880 while (value > vci.max)
881 value -= 2 * vci.max;
882 value = qAbs(value);
883 majorAxisIsY = value < vci.max - value;
884 } else if (vci.label == QXcbAtom::AtomAbsMTPressure || vci.label == QXcbAtom::AtomAbsPressure) {
885 touchPoint.pressure = valuatorNormalized;
886 }
887
888 }
889 // If any value was not updated, use the last-known value.
890 if (nx == -1.0) {
891 x = touchPoint.area.center().x();
892 nx = x / screen->geometry().width();
893 }
894 if (ny == -1.0) {
895 y = touchPoint.area.center().y();
896 ny = y / screen->geometry().height();
897 }
898 if (xiDeviceEvent->event_type != XCB_INPUT_TOUCH_END) {
899 if (!dev->providesTouchOrientation) {
900 if (w == 0.0)
901 w = touchPoint.area.width();
902 h = w;
903 } else {
904 if (w == 0.0)
905 w = qMax(touchPoint.area.width(), touchPoint.area.height());
906 if (h == 0.0)
907 h = qMin(touchPoint.area.width(), touchPoint.area.height());
908 if (majorAxisIsY)
909 qSwap(w, h);
910 }
911 }
912
913 switch (xiDeviceEvent->event_type) {
914 case XCB_INPUT_TOUCH_BEGIN:
915 if (firstTouch) {
916 dev->firstPressedPosition = QPointF(x, y);
917 dev->firstPressedNormalPosition = QPointF(nx, ny);
918 }
919 dev->pointPressedPosition.insert(touchPoint.id, QPointF(x, y));
920
921 // Touches must be accepted when we are grabbing touch events. Otherwise the entire sequence
922 // will get replayed when the grab ends.
923 if (m_xiGrab) {
924 xcb_input_xi_allow_events(xcb_connection(), XCB_CURRENT_TIME, xiDeviceEvent->deviceid,
925 XCB_INPUT_EVENT_MODE_ACCEPT_TOUCH,
926 xiDeviceEvent->detail, xiDeviceEvent->event);
927 }
928 break;
929 case XCB_INPUT_TOUCH_UPDATE:
930 if (dev->qtTouchDevice->type() == QInputDevice::DeviceType::TouchPad && dev->pointPressedPosition.value(touchPoint.id) == QPointF(x, y)) {
931 qreal dx = (nx - dev->firstPressedNormalPosition.x()) *
932 dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
933 qreal dy = (ny - dev->firstPressedNormalPosition.y()) *
934 dev->size.height() * screen->geometry().height() / screen->physicalSize().height();
935 x = dev->firstPressedPosition.x() + dx;
936 y = dev->firstPressedPosition.y() + dy;
938 } else if (touchPoint.area.center() != QPoint(x, y)) {
940 if (dev->qtTouchDevice->type() == QInputDevice::DeviceType::TouchPad)
941 dev->pointPressedPosition[touchPoint.id] = QPointF(x, y);
942 }
943
944 if (dev->qtTouchDevice->type() == QInputDevice::DeviceType::TouchScreen &&
945 xiDeviceEvent->event == m_startSystemMoveResizeInfo.window &&
946 xiDeviceEvent->sourceid == m_startSystemMoveResizeInfo.deviceid &&
947 xiDeviceEvent->detail == m_startSystemMoveResizeInfo.pointid) {
948 QXcbWindow *window = platformWindowFromId(m_startSystemMoveResizeInfo.window);
949 if (window) {
950 xcb_input_xi_allow_events(xcb_connection(), XCB_CURRENT_TIME, xiDeviceEvent->deviceid,
951 XCB_INPUT_EVENT_MODE_REJECT_TOUCH,
952 xiDeviceEvent->detail, xiDeviceEvent->event);
953 window->doStartSystemMoveResize(QPoint(x, y), m_startSystemMoveResizeInfo.edges);
954 m_startSystemMoveResizeInfo.window = XCB_NONE;
955 }
956 }
957 break;
958 case XCB_INPUT_TOUCH_END:
960 if (dev->qtTouchDevice->type() == QInputDevice::DeviceType::TouchPad && dev->pointPressedPosition.value(touchPoint.id) == QPointF(x, y)) {
961 qreal dx = (nx - dev->firstPressedNormalPosition.x()) *
962 dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
963 qreal dy = (ny - dev->firstPressedNormalPosition.y()) *
964 dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
965 x = dev->firstPressedPosition.x() + dx;
966 y = dev->firstPressedPosition.y() + dy;
967 }
968 dev->pointPressedPosition.remove(touchPoint.id);
969 }
970 touchPoint.area = QRectF(x - w/2, y - h/2, w, h);
971 touchPoint.normalPosition = QPointF(nx, ny);
972
973 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
974 qCDebug(lcQpaXInputEvents) << " touchpoint " << touchPoint.id << " state " << touchPoint.state << " pos norm " << touchPoint.normalPosition <<
975 " area " << touchPoint.area << " pressure " << touchPoint.pressure;
976 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(xiDeviceEvent->mods.effective);
977 QWindowSystemInterface::handleTouchEvent(platformWindow->window(), xiDeviceEvent->time, dev->qtTouchDevice, dev->touchPoints.values(), modifiers);
978 if (touchPoint.state == QEventPoint::State::Released)
979 // If a touchpoint was released, we can forget it, because the ID won't be reused.
980 dev->touchPoints.remove(touchPoint.id);
981 else
982 // Make sure that we don't send TouchPointPressed/Moved in more than one QTouchEvent
983 // with this touch point if the next XI2 event is about a different touch point.
985}
986
988{
990 for (; devIt != m_touchDevices.constEnd(); ++devIt) {
991 TouchDeviceData deviceData = devIt.value();
992 if (deviceData.qtTouchDevice->type() == QInputDevice::DeviceType::TouchScreen) {
993 auto pointIt = deviceData.touchPoints.constBegin();
994 for (; pointIt != deviceData.touchPoints.constEnd(); ++pointIt) {
995 QEventPoint::State state = pointIt.value().state;
997 m_startSystemMoveResizeInfo.window = window;
998 m_startSystemMoveResizeInfo.deviceid = devIt.key();
999 m_startSystemMoveResizeInfo.pointid = pointIt.key();
1000 m_startSystemMoveResizeInfo.edges = edges;
1002 qCDebug(lcQpaXInputDevices) << "triggered system move or resize from touch";
1003 return true;
1004 }
1005 }
1006 }
1007 }
1008 return false;
1009}
1010
1012{
1013 qCDebug(lcQpaXInputDevices) << "sending client message NET_WM_MOVERESIZE_CANCEL to window: " << window;
1014 m_startSystemMoveResizeInfo.window = XCB_NONE;
1015
1016 const xcb_atom_t moveResize = connection()->atom(QXcbAtom::Atom_NET_WM_MOVERESIZE);
1017 xcb_client_message_event_t xev;
1018 xev.response_type = XCB_CLIENT_MESSAGE;
1019 xev.type = moveResize;
1020 xev.sequence = 0;
1021 xev.window = window;
1022 xev.format = 32;
1023 xev.data.data32[0] = 0;
1024 xev.data.data32[1] = 0;
1025 xev.data.data32[2] = 11; // _NET_WM_MOVERESIZE_CANCEL
1026 xev.data.data32[3] = 0;
1027 xev.data.data32[4] = 0;
1028 xcb_send_event(xcb_connection(), false, primaryScreen()->root(),
1029 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
1030 (const char *)&xev);
1031
1032 m_duringSystemMoveResize = false;
1033}
1034
1036{
1037 return m_duringSystemMoveResize;
1038}
1039
1041{
1042 m_duringSystemMoveResize = during;
1043}
1044
1045bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab)
1046{
1047 bool ok = false;
1048
1049 if (grab) { // grab
1050 uint8_t mask[8] = {};
1051 setXcbMask(mask, XCB_INPUT_BUTTON_PRESS);
1052 setXcbMask(mask, XCB_INPUT_BUTTON_RELEASE);
1053 setXcbMask(mask, XCB_INPUT_MOTION);
1054 setXcbMask(mask, XCB_INPUT_ENTER);
1055 setXcbMask(mask, XCB_INPUT_LEAVE);
1056 if (isAtLeastXI22()) {
1057 setXcbMask(mask, XCB_INPUT_TOUCH_BEGIN);
1058 setXcbMask(mask, XCB_INPUT_TOUCH_UPDATE);
1059 setXcbMask(mask, XCB_INPUT_TOUCH_END);
1060 }
1061#if QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
1062 if (isAtLeastXI24()) {
1063 setXcbMask(mask, XCB_INPUT_GESTURE_PINCH_BEGIN);
1064 setXcbMask(mask, XCB_INPUT_GESTURE_PINCH_UPDATE);
1065 setXcbMask(mask, XCB_INPUT_GESTURE_PINCH_END);
1066 setXcbMask(mask, XCB_INPUT_GESTURE_SWIPE_BEGIN);
1067 setXcbMask(mask, XCB_INPUT_GESTURE_SWIPE_UPDATE);
1068 setXcbMask(mask, XCB_INPUT_GESTURE_SWIPE_END);
1069 }
1070#endif // QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
1071
1072 for (int id : std::as_const(m_xiMasterPointerIds)) {
1073 xcb_generic_error_t *error = nullptr;
1074 auto cookie = xcb_input_xi_grab_device(xcb_connection(), w, XCB_CURRENT_TIME, XCB_CURSOR_NONE, id,
1075 XCB_INPUT_GRAB_MODE_22_ASYNC, XCB_INPUT_GRAB_MODE_22_ASYNC,
1076 false, 2, reinterpret_cast<uint32_t *>(mask));
1077 auto *reply = xcb_input_xi_grab_device_reply(xcb_connection(), cookie, &error);
1078 if (error) {
1079 qCDebug(lcQpaXInput, "failed to grab events for device %d on window %x"
1080 "(error code %d)", id, w, error->error_code);
1081 free(error);
1082 } else {
1083 // Managed to grab at least one of master pointers, that should be enough
1084 // to properly dismiss windows that rely on mouse grabbing.
1085 ok = true;
1086 }
1087 free(reply);
1088 }
1089 } else { // ungrab
1090 for (int id : std::as_const(m_xiMasterPointerIds)) {
1091 auto cookie = xcb_input_xi_ungrab_device_checked(xcb_connection(), XCB_CURRENT_TIME, id);
1092 xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
1093 if (error) {
1094 qCDebug(lcQpaXInput, "XIUngrabDevice failed - id: %d (error code %d)", id, error->error_code);
1095 free(error);
1096 }
1097 }
1098 // XIUngrabDevice does not seem to wait for a reply from X server (similar to
1099 // xcb_ungrab_pointer). Ungrabbing won't fail, unless NoSuchExtension error
1100 // has occurred due to a programming error somewhere else in the stack. That
1101 // would mean that things will crash soon anyway.
1102 ok = true;
1103 }
1104
1105 if (ok)
1106 m_xiGrab = grab;
1107
1108 return ok;
1109}
1110
1111void QXcbConnection::xi2HandleHierarchyEvent(void *event)
1112{
1113 auto *xiEvent = reinterpret_cast<xcb_input_hierarchy_event_t *>(event);
1114 // We care about hotplugged devices (slaves) and master devices.
1115 // We don't report anything for DEVICE_ENABLED or DEVICE_DISABLED
1116 // (but often that goes with adding or removal anyway).
1117 // We don't react to SLAVE_ATTACHED or SLAVE_DETACHED either.
1118 if (xiEvent->flags & (XCB_INPUT_HIERARCHY_MASK_MASTER_ADDED |
1119 XCB_INPUT_HIERARCHY_MASK_MASTER_REMOVED |
1120 XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED |
1121 XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED))
1122 xi2SetupDevices();
1123}
1124
1125#if QT_XCB_HAS_TOUCHPAD_GESTURES
1126void QXcbConnection::xi2HandleGesturePinchEvent(void *event)
1127{
1128 auto *xiEvent = reinterpret_cast<qt_xcb_input_pinch_event_t *>(event);
1129
1130 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) {
1131 qCDebug(lcQpaXInputEvents, "XI2 gesture event type %d seq %d fingers %d pos %6.1f, "
1132 "%6.1f root pos %6.1f, %6.1f delta_angle %6.1f scale %6.1f on window %x",
1133 xiEvent->event_type, xiEvent->sequence, xiEvent->detail,
1134 fixed1616ToReal(xiEvent->event_x), fixed1616ToReal(xiEvent->event_y),
1135 fixed1616ToReal(xiEvent->root_x), fixed1616ToReal(xiEvent->root_y),
1136 fixed1616ToReal(xiEvent->delta_angle), fixed1616ToReal(xiEvent->scale),
1137 xiEvent->event);
1138 }
1139 QXcbWindow *platformWindow = platformWindowFromId(xiEvent->event);
1140 if (!platformWindow)
1141 return;
1142
1143 setTime(xiEvent->time);
1144
1145 TouchDeviceData *dev = touchDeviceForId(xiEvent->sourceid);
1146 Q_ASSERT(dev);
1147
1148 uint32_t fingerCount = xiEvent->detail;
1149
1150 switch (xiEvent->event_type) {
1151 case XCB_INPUT_GESTURE_PINCH_BEGIN:
1152 // Gestures must be accepted when we are grabbing gesture events. Otherwise the entire
1153 // sequence will get replayed when the grab ends.
1154 if (m_xiGrab) {
1155 xcb_input_xi_allow_events(xcb_connection(), XCB_CURRENT_TIME, xiEvent->deviceid,
1156 XCB_INPUT_EVENT_MODE_ASYNC_DEVICE, 0, xiEvent->event);
1157 }
1158 m_lastPinchScale = 1.0;
1159 QWindowSystemInterface::handleGestureEvent(platformWindow->window(), xiEvent->time,
1160 dev->qtTouchDevice,
1162 platformWindow->lastPointerPosition(),
1163 platformWindow->lastPointerGlobalPosition(),
1164 fingerCount);
1165 break;
1166
1167 case XCB_INPUT_GESTURE_PINCH_UPDATE: {
1168 qreal rotationDelta = fixed1616ToReal(xiEvent->delta_angle);
1169 qreal scale = fixed1616ToReal(xiEvent->scale);
1170 qreal scaleDelta = scale - m_lastPinchScale;
1171 m_lastPinchScale = scale;
1172
1173 QPointF delta = QPointF(fixed1616ToReal(xiEvent->delta_x),
1174 fixed1616ToReal(xiEvent->delta_y));
1175
1176 if (!delta.isNull()) {
1178 platformWindow->window(), xiEvent->time, dev->qtTouchDevice,
1179 Qt::PanNativeGesture, 0, delta,
1180 platformWindow->lastPointerPosition(),
1181 platformWindow->lastPointerGlobalPosition(),
1182 fingerCount);
1183 }
1184 if (rotationDelta != 0) {
1186 platformWindow->window(), xiEvent->time, dev->qtTouchDevice,
1188 rotationDelta,
1189 platformWindow->lastPointerPosition(),
1190 platformWindow->lastPointerGlobalPosition(),
1191 fingerCount);
1192 }
1193 if (scaleDelta != 0) {
1195 platformWindow->window(), xiEvent->time, dev->qtTouchDevice,
1197 scaleDelta,
1198 platformWindow->lastPointerPosition(),
1199 platformWindow->lastPointerGlobalPosition(),
1200 fingerCount);
1201 }
1202 break;
1203 }
1204 case XCB_INPUT_GESTURE_PINCH_END:
1205 QWindowSystemInterface::handleGestureEvent(platformWindow->window(), xiEvent->time,
1206 dev->qtTouchDevice,
1208 platformWindow->lastPointerPosition(),
1209 platformWindow->lastPointerGlobalPosition(),
1210 fingerCount);
1211 break;
1212 }
1213}
1214
1215void QXcbConnection::xi2HandleGestureSwipeEvent(void *event)
1216{
1217 auto *xiEvent = reinterpret_cast<qt_xcb_input_swipe_event_t *>(event);
1218
1219 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) {
1220 qCDebug(lcQpaXInputEvents, "XI2 gesture event type %d seq %d detail %d pos %6.1f, %6.1f root pos %6.1f, %6.1f on window %x",
1221 xiEvent->event_type, xiEvent->sequence, xiEvent->detail,
1222 fixed1616ToReal(xiEvent->event_x), fixed1616ToReal(xiEvent->event_y),
1223 fixed1616ToReal(xiEvent->root_x), fixed1616ToReal(xiEvent->root_y),
1224 xiEvent->event);
1225 }
1226 QXcbWindow *platformWindow = platformWindowFromId(xiEvent->event);
1227 if (!platformWindow)
1228 return;
1229
1230 setTime(xiEvent->time);
1231
1232 TouchDeviceData *dev = touchDeviceForId(xiEvent->sourceid);
1233 Q_ASSERT(dev);
1234
1235 uint32_t fingerCount = xiEvent->detail;
1236
1237 switch (xiEvent->event_type) {
1238 case XCB_INPUT_GESTURE_SWIPE_BEGIN:
1239 // Gestures must be accepted when we are grabbing gesture events. Otherwise the entire
1240 // sequence will get replayed when the grab ends.
1241 if (m_xiGrab) {
1242 xcb_input_xi_allow_events(xcb_connection(), XCB_CURRENT_TIME, xiEvent->deviceid,
1243 XCB_INPUT_EVENT_MODE_ASYNC_DEVICE, 0, xiEvent->event);
1244 }
1245 QWindowSystemInterface::handleGestureEvent(platformWindow->window(), xiEvent->time,
1246 dev->qtTouchDevice,
1248 platformWindow->lastPointerPosition(),
1249 platformWindow->lastPointerGlobalPosition(),
1250 fingerCount);
1251 break;
1252 case XCB_INPUT_GESTURE_SWIPE_UPDATE: {
1253 QPointF delta = QPointF(fixed1616ToReal(xiEvent->delta_x),
1254 fixed1616ToReal(xiEvent->delta_y));
1255
1256 if (xiEvent->delta_x != 0 || xiEvent->delta_y != 0) {
1258 platformWindow->window(), xiEvent->time, dev->qtTouchDevice,
1259 Qt::PanNativeGesture, 0, delta,
1260 platformWindow->lastPointerPosition(),
1261 platformWindow->lastPointerGlobalPosition(),
1262 fingerCount);
1263 }
1264 break;
1265 }
1266 case XCB_INPUT_GESTURE_SWIPE_END:
1267 QWindowSystemInterface::handleGestureEvent(platformWindow->window(), xiEvent->time,
1268 dev->qtTouchDevice,
1270 platformWindow->lastPointerPosition(),
1271 platformWindow->lastPointerGlobalPosition(),
1272 fingerCount);
1273 break;
1274 }
1275}
1276
1277#else // QT_XCB_HAS_TOUCHPAD_GESTURES
1278void QXcbConnection::xi2HandleGesturePinchEvent(void*) {}
1279void QXcbConnection::xi2HandleGestureSwipeEvent(void*) {}
1280#endif
1281
1282void QXcbConnection::xi2HandleDeviceChangedEvent(void *event)
1283{
1284 auto *xiEvent = reinterpret_cast<xcb_input_device_changed_event_t *>(event);
1285 switch (xiEvent->reason) {
1286 case XCB_INPUT_CHANGE_REASON_DEVICE_CHANGE: {
1287 // Don't call xi2SetupSlavePointerDevice() again for an already-known device, and never for a master.
1288 if (m_xiMasterPointerIds.contains(xiEvent->deviceid) || m_xiSlavePointerIds.contains(xiEvent->deviceid))
1289 return;
1290 auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), xiEvent->sourceid);
1291 if (!reply || reply->num_infos <= 0)
1292 return;
1293 auto it = xcb_input_xi_query_device_infos_iterator(reply.get());
1294 xi2SetupSlavePointerDevice(it.data);
1295 break;
1296 }
1297 case XCB_INPUT_CHANGE_REASON_SLAVE_SWITCH: {
1298 if (auto *scrollingDevice = scrollingDeviceForId(xiEvent->sourceid))
1299 xi2UpdateScrollingDevice(scrollingDevice);
1300 break;
1301 }
1302 default:
1303 qCDebug(lcQpaXInputEvents, "unknown device-changed-event (device %d)", xiEvent->sourceid);
1304 break;
1305 }
1306}
1307
1308void QXcbConnection::xi2UpdateScrollingDevice(QInputDevice *dev)
1309{
1310 QXcbScrollingDevice *scrollDev = qobject_cast<QXcbScrollingDevice *>(dev);
1311 if (!scrollDev || !scrollDev->capabilities().testFlag(QInputDevice::Capability::Scroll))
1312 return;
1313 QXcbScrollingDevicePrivate *scrollingDevice = QXcbScrollingDevice::get(scrollDev);
1314
1315 auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), scrollingDevice->systemId);
1316 if (!reply || reply->num_infos <= 0) {
1317 qCDebug(lcQpaXInputDevices, "scrolling device %lld no longer present", scrollingDevice->systemId);
1318 return;
1319 }
1320 QPointF lastScrollPosition;
1321 if (lcQpaXInputEvents().isDebugEnabled())
1322 lastScrollPosition = scrollingDevice->lastScrollPosition;
1323
1324 xcb_input_xi_device_info_t *deviceInfo = xcb_input_xi_query_device_infos_iterator(reply.get()).data;
1325 auto classes_it = xcb_input_xi_device_info_classes_iterator(deviceInfo);
1326 for (; classes_it.rem; xcb_input_device_class_next(&classes_it)) {
1327 xcb_input_device_class_t *classInfo = classes_it.data;
1328 if (classInfo->type == XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR) {
1329 auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classInfo);
1330 const int valuatorAtom = qatom(vci->label);
1331 if (valuatorAtom == QXcbAtom::AtomRelHorizScroll || valuatorAtom == QXcbAtom::AtomRelHorizWheel)
1332 scrollingDevice->lastScrollPosition.setX(fixed3232ToReal(vci->value));
1333 else if (valuatorAtom == QXcbAtom::AtomRelVertScroll || valuatorAtom == QXcbAtom::AtomRelVertWheel)
1334 scrollingDevice->lastScrollPosition.setY(fixed3232ToReal(vci->value));
1335 }
1336 }
1337 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled() && lastScrollPosition != scrollingDevice->lastScrollPosition))
1338 qCDebug(lcQpaXInputEvents, "scrolling device %lld moved from (%f, %f) to (%f, %f)", scrollingDevice->systemId,
1339 lastScrollPosition.x(), lastScrollPosition.y(),
1340 scrollingDevice->lastScrollPosition.x(),
1341 scrollingDevice->lastScrollPosition.y());
1342}
1343
1345{
1346 const auto &devices = QInputDevice::devices();
1347 for (const QInputDevice *dev : devices) {
1349 xi2UpdateScrollingDevice(const_cast<QInputDevice *>(dev));
1350 }
1351}
1352
1353QXcbScrollingDevice *QXcbConnection::scrollingDeviceForId(int id)
1354{
1356 if (!dev|| !dev->capabilities().testFlag(QInputDevice::Capability::Scroll))
1357 return nullptr;
1358 return qobject_cast<QXcbScrollingDevice *>(const_cast<QPointingDevice *>(dev));
1359}
1360
1361void QXcbConnection::xi2HandleScrollEvent(void *event, const QPointingDevice *dev)
1362{
1363 auto *xiDeviceEvent = reinterpret_cast<qt_xcb_input_device_event_t *>(event);
1364
1365 const QXcbScrollingDevice *scrollDev = qobject_cast<const QXcbScrollingDevice *>(dev);
1366 if (!scrollDev || !scrollDev->capabilities().testFlag(QInputDevice::Capability::Scroll))
1367 return;
1368 const QXcbScrollingDevicePrivate *scrollingDevice = QXcbScrollingDevice::get(scrollDev);
1369
1370 if (xiDeviceEvent->event_type == XCB_INPUT_MOTION && scrollingDevice->orientations) {
1371 if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) {
1372 QPoint rawDelta;
1373 QPoint angleDelta;
1374 double value;
1375 if (scrollingDevice->orientations & Qt::Vertical) {
1376 if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice->verticalIndex, &value)) {
1377 double delta = scrollingDevice->lastScrollPosition.y() - value;
1378 scrollingDevice->lastScrollPosition.setY(value);
1379 angleDelta.setY((delta / scrollingDevice->verticalIncrement) * 120);
1380 // With most drivers the increment is 1 for wheels.
1381 // For libinput it is hardcoded to a useless 15.
1382 // For a proper touchpad driver it should be in the same order of magnitude as 120
1383 if (scrollingDevice->verticalIncrement > 15)
1384 rawDelta.setY(delta);
1385 else if (scrollingDevice->verticalIncrement < -15)
1386 rawDelta.setY(-delta);
1387 }
1388 }
1389 if (scrollingDevice->orientations & Qt::Horizontal) {
1390 if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice->horizontalIndex, &value)) {
1391 double delta = scrollingDevice->lastScrollPosition.x() - value;
1392 scrollingDevice->lastScrollPosition.setX(value);
1393 angleDelta.setX((delta / scrollingDevice->horizontalIncrement) * 120);
1394 // See comment under vertical
1395 if (scrollingDevice->horizontalIncrement > 15)
1396 rawDelta.setX(delta);
1397 else if (scrollingDevice->horizontalIncrement < -15)
1398 rawDelta.setX(-delta);
1399 }
1400 }
1401 if (!angleDelta.isNull()) {
1402 QPoint local(fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y));
1403 QPoint global(fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y));
1404 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(xiDeviceEvent->mods.effective);
1405 if (modifiers & Qt::AltModifier) {
1406 angleDelta = angleDelta.transposed();
1407 rawDelta = rawDelta.transposed();
1408 }
1409 qCDebug(lcQpaXInputEvents) << "scroll wheel from device" << scrollingDevice->systemId
1410 << "@ window pos" << local << "delta px" << rawDelta << "angle" << angleDelta;
1411 QWindowSystemInterface::handleWheelEvent(platformWindow->window(), xiDeviceEvent->time, dev,
1412 local, global, rawDelta, angleDelta, modifiers);
1413 }
1414 }
1415 } else if (xiDeviceEvent->event_type == XCB_INPUT_BUTTON_RELEASE && scrollingDevice->legacyOrientations) {
1416 if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) {
1417 QPoint angleDelta;
1418 if (scrollingDevice->legacyOrientations & Qt::Vertical) {
1419 if (xiDeviceEvent->detail == 4)
1420 angleDelta.setY(120);
1421 else if (xiDeviceEvent->detail == 5)
1422 angleDelta.setY(-120);
1423 }
1424 if (scrollingDevice->legacyOrientations & Qt::Horizontal) {
1425 if (xiDeviceEvent->detail == 6)
1426 angleDelta.setX(120);
1427 else if (xiDeviceEvent->detail == 7)
1428 angleDelta.setX(-120);
1429 }
1430 if (!angleDelta.isNull()) {
1431 QPoint local(fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y));
1432 QPoint global(fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y));
1433 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(xiDeviceEvent->mods.effective);
1435 angleDelta = angleDelta.transposed();
1436 qCDebug(lcQpaXInputEvents) << "scroll wheel (button" << xiDeviceEvent->detail << ") @ window pos" << local << "delta angle" << angleDelta;
1437 QWindowSystemInterface::handleWheelEvent(platformWindow->window(), xiDeviceEvent->time, dev,
1438 local, global, QPoint(), angleDelta, modifiers);
1439 }
1440 }
1441 }
1442}
1443
1444static int xi2ValuatorOffset(const unsigned char *maskPtr, int maskLen, int number)
1445{
1446 int offset = 0;
1447 for (int i = 0; i < maskLen; i++) {
1448 if (number < 8) {
1449 if ((maskPtr[i] & (1 << number)) == 0)
1450 return -1;
1451 }
1452 for (int j = 0; j < 8; j++) {
1453 if (j == number)
1454 return offset;
1455 if (maskPtr[i] & (1 << j))
1456 offset++;
1457 }
1458 number -= 8;
1459 }
1460 return -1;
1461}
1462
1463bool QXcbConnection::xi2GetValuatorValueIfSet(const void *event, int valuatorNum, double *value)
1464{
1465 auto *xideviceevent = static_cast<const qt_xcb_input_device_event_t *>(event);
1466 auto *buttonsMaskAddr = reinterpret_cast<const unsigned char *>(&xideviceevent[1]);
1467 auto *valuatorsMaskAddr = buttonsMaskAddr + xideviceevent->buttons_len * 4;
1468 auto *valuatorsValuesAddr = reinterpret_cast<const xcb_input_fp3232_t *>(valuatorsMaskAddr + xideviceevent->valuators_len * 4);
1469
1470 int valuatorOffset = xi2ValuatorOffset(valuatorsMaskAddr, xideviceevent->valuators_len, valuatorNum);
1471 if (valuatorOffset < 0)
1472 return false;
1473
1474 *value = valuatorsValuesAddr[valuatorOffset].integral;
1475 *value += ((double)valuatorsValuesAddr[valuatorOffset].frac / (1 << 16) / (1 << 16));
1476 return true;
1477}
1478
1480{
1481 switch (b) {
1482 case 1: return Qt::LeftButton;
1483 case 2: return Qt::MiddleButton;
1484 case 3: return Qt::RightButton;
1485 // 4-7 are for scrolling
1486 default: break;
1487 }
1488 if (b >= 8 && b <= Qt::MaxMouseButton)
1489 return static_cast<Qt::MouseButton>(Qt::BackButton << (b - 8));
1490 return Qt::NoButton;
1491}
1492
1493#if QT_CONFIG(tabletevent)
1494bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletData)
1495{
1496 bool handled = true;
1497 const auto *xiDeviceEvent = reinterpret_cast<const qt_xcb_input_device_event_t *>(event);
1498
1499 switch (xiDeviceEvent->event_type) {
1500 case XCB_INPUT_BUTTON_PRESS: {
1501 Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail);
1502 tabletData->buttons |= b;
1503 xi2ReportTabletEvent(event, tabletData);
1504 break;
1505 }
1506 case XCB_INPUT_BUTTON_RELEASE: {
1507 Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail);
1508 tabletData->buttons ^= b;
1509 xi2ReportTabletEvent(event, tabletData);
1510 break;
1511 }
1512 case XCB_INPUT_MOTION:
1513 xi2ReportTabletEvent(event, tabletData);
1514 break;
1515 case XCB_INPUT_PROPERTY: {
1516 // This is the wacom driver's way of reporting tool proximity.
1517 // The evdev driver doesn't do it this way.
1518 const auto *ev = reinterpret_cast<const xcb_input_property_event_t *>(event);
1519 if (ev->what == XCB_INPUT_PROPERTY_FLAG_MODIFIED) {
1520 if (ev->property == atom(QXcbAtom::AtomWacomSerialIDs)) {
1521 enum WacomSerialIndex {
1522 _WACSER_USB_ID = 0,
1523 _WACSER_LAST_TOOL_SERIAL,
1524 _WACSER_LAST_TOOL_ID,
1525 _WACSER_TOOL_SERIAL,
1526 _WACSER_TOOL_ID,
1527 _WACSER_COUNT
1528 };
1529
1530 auto reply = Q_XCB_REPLY(xcb_input_xi_get_property, xcb_connection(), tabletData->deviceId, 0,
1531 ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, 100);
1532 if (reply) {
1533 if (reply->type == atom(QXcbAtom::AtomINTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) {
1534 quint32 *ptr = reinterpret_cast<quint32 *>(xcb_input_xi_get_property_items(reply.get()));
1535 quint32 tool = ptr[_WACSER_TOOL_ID];
1536 // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
1537 // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
1538 if (!tool && ptr[_WACSER_TOOL_SERIAL])
1539 tool = ptr[_WACSER_TOOL_SERIAL];
1540
1541 QWindow *win = nullptr; // TODO QTBUG-111400 get the position somehow, then the window
1542 // The property change event informs us which tool is in proximity or which one left proximity.
1543 if (tool) {
1544 const QPointingDevice *dev = tabletToolInstance(nullptr, tabletData->name,
1545 tabletData->deviceId, ptr[_WACSER_USB_ID], tool,
1546 qint64(ptr[_WACSER_TOOL_SERIAL])); // TODO look up the master
1547 tabletData->inProximity = true;
1548 tabletData->tool = dev->type();
1549 tabletData->serialId = qint64(ptr[_WACSER_TOOL_SERIAL]);
1551 } else {
1552 tool = ptr[_WACSER_LAST_TOOL_ID];
1553 // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
1554 // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
1555 if (!tool)
1556 tool = ptr[_WACSER_LAST_TOOL_SERIAL];
1557 auto *dev = qobject_cast<const QPointingDevice *>(QInputDevicePrivate::fromId(tabletData->deviceId));
1558 Q_ASSERT(dev);
1559 tabletData->tool = dev->type();
1560 tabletData->inProximity = false;
1561 tabletData->serialId = qint64(ptr[_WACSER_LAST_TOOL_SERIAL]);
1563 }
1564 // TODO maybe have a hash of tabletData->deviceId to device data so we can
1565 // look up the tablet name here, and distinguish multiple tablets
1566 qCDebug(lcQpaXInputDevices, "XI2 proximity change on tablet %d %s (USB %x): last tool: %x id %x current tool: %x id %x %s",
1567 tabletData->deviceId, qPrintable(tabletData->name), ptr[_WACSER_USB_ID],
1568 ptr[_WACSER_LAST_TOOL_SERIAL], ptr[_WACSER_LAST_TOOL_ID],
1569 ptr[_WACSER_TOOL_SERIAL], ptr[_WACSER_TOOL_ID], toolName(tabletData->tool));
1570 }
1571 }
1572 }
1573 }
1574 break;
1575 }
1576 default:
1577 handled = false;
1578 break;
1579 }
1580
1581 return handled;
1582}
1583
1584inline qreal scaleOneValuator(qreal normValue, qreal screenMin, qreal screenSize)
1585{
1586 return screenMin + normValue * screenSize;
1587}
1588
1589// TODO QPointingDevice not TabletData
1590void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletData)
1591{
1592 auto *ev = reinterpret_cast<const qt_xcb_input_device_event_t *>(event);
1593 QXcbWindow *xcbWindow = platformWindowFromId(ev->event);
1594 if (!xcbWindow)
1595 return;
1596 QWindow *window = xcbWindow->window();
1597 const Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(ev->mods.effective);
1598 QPointF local(fixed1616ToReal(ev->event_x), fixed1616ToReal(ev->event_y));
1599 QPointF global(fixed1616ToReal(ev->root_x), fixed1616ToReal(ev->root_y));
1600 double pressure = 0, rotation = 0, tangentialPressure = 0;
1601 int xTilt = 0, yTilt = 0;
1602 static const bool useValuators = !qEnvironmentVariableIsSet("QT_XCB_TABLET_LEGACY_COORDINATES");
1604 QPointingDevice::PointerType(tabletData->pointerType),
1605 QPointingDeviceUniqueId::fromNumericId(tabletData->serialId));
1606
1607 // Valuators' values are relative to the physical size of the current virtual
1608 // screen. Therefore we cannot use QScreen/QWindow geometry and should use
1609 // QPlatformWindow/QPlatformScreen instead.
1610 QRect physicalScreenArea;
1611 if (Q_LIKELY(useValuators)) {
1612 const QList<QPlatformScreen *> siblings = window->screen()->handle()->virtualSiblings();
1613 for (const QPlatformScreen *screen : siblings)
1614 physicalScreenArea |= screen->geometry();
1615 }
1616
1617 for (QHash<int, TabletData::ValuatorClassInfo>::iterator it = tabletData->valuatorInfo.begin(),
1618 ite = tabletData->valuatorInfo.end(); it != ite; ++it) {
1619 int valuator = it.key();
1620 TabletData::ValuatorClassInfo &classInfo(it.value());
1621 xi2GetValuatorValueIfSet(event, classInfo.number, &classInfo.curVal);
1622 double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal);
1623 switch (valuator) {
1624 case QXcbAtom::AtomAbsX:
1625 if (Q_LIKELY(useValuators)) {
1626 const qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.x(), physicalScreenArea.width());
1627 global.setX(value);
1628 local.setX(xcbWindow->mapFromGlobalF(global).x());
1629 }
1630 break;
1631 case QXcbAtom::AtomAbsY:
1632 if (Q_LIKELY(useValuators)) {
1633 qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.y(), physicalScreenArea.height());
1634 global.setY(value);
1635 local.setY(xcbWindow->mapFromGlobalF(global).y());
1636 }
1637 break;
1639 pressure = normalizedValue;
1640 break;
1642 xTilt = classInfo.curVal;
1643 break;
1645 yTilt = classInfo.curVal;
1646 break;
1648 switch (tabletData->tool) {
1650 tangentialPressure = normalizedValue * 2.0 - 1.0; // Convert 0..1 range to -1..+1 range
1651 break;
1654 rotation = normalizedValue * 360.0 - 180.0; // Convert 0..1 range to -180..+180 degrees
1655 break;
1656 default: // Other types of styli do not use this valuator
1657 break;
1658 }
1659 break;
1660 default:
1661 break;
1662 }
1663 }
1664
1665 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
1666 qCDebug(lcQpaXInputEvents, "XI2 event on tablet %d with tool %s %llx type %s seq %d detail %d time %d "
1667 "pos %6.1f, %6.1f root pos %6.1f, %6.1f buttons 0x%x pressure %4.2lf tilt %d, %d rotation %6.2lf modifiers 0x%x",
1668 tabletData->deviceId, toolName(tabletData->tool), tabletData->serialId, pointerTypeName(tabletData->pointerType),
1669 ev->sequence, ev->detail, ev->time,
1670 local.x(), local.y(), global.x(), global.y(),
1671 (int)tabletData->buttons, pressure, xTilt, yTilt, rotation, (int)modifiers);
1672
1674 tabletData->buttons, pressure,
1675 xTilt, yTilt, tangentialPressure,
1676 rotation, 0, modifiers);
1677}
1678
1679QXcbConnection::TabletData *QXcbConnection::tabletDataForDevice(int id)
1680{
1681 for (int i = 0; i < m_tabletData.size(); ++i) {
1682 if (m_tabletData.at(i).deviceId == id)
1683 return &m_tabletData[i];
1684 }
1685 return nullptr;
1686}
1687
1688#endif // QT_CONFIG(tabletevent)
IOBluetoothDevice * device
\inmodule QtCore
Definition qbytearray.h:57
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
QByteArray toLower() const &
Definition qbytearray.h:254
State
Specifies the state of this event point.
Definition qeventpoint.h:48
\inmodule QtCore
Definition qhash.h:1145
\inmodule QtCore
Definition qhash.h:1103
bool remove(const Key &key)
Removes the item that has the key from the hash.
Definition qhash.h:958
const_iterator constEnd() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
Definition qhash.h:1219
const_iterator constBegin() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash.
Definition qhash.h:1215
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:1007
static const QInputDevice * fromId(qint64 systemId)
The QInputDevice class describes a device from which a QInputEvent originates.
Capabilities capabilities
DeviceType
This enum represents the type of device that generated a QPointerEvent.
DeviceType type
qint64 systemId
static QList< const QInputDevice * > devices()
Returns a list of all registered input devices (keyboards and pointing devices).
qsizetype size() const noexcept
Definition qlist.h:397
qsizetype removeAll(const AT &t)
Definition qlist.h:592
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
\inmodule QtCore
const char * valueToKey(int value) const
Returns the string that is used as the name of the given enumeration value, or \nullptr if value is n...
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
The QPlatformScreen class provides an abstraction for visual displays.
QWindow * window() const
Returns the window which belongs to the QPlatformWindow.
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:348
bool isNull() const noexcept
Returns true if both the x and y coordinates are set to 0.0 (ignoring the sign); otherwise returns fa...
Definition qpoint.h:338
\inmodule QtCore\reentrant
Definition qpoint.h:25
constexpr bool isNull() const noexcept
Returns true if both the x and y coordinates are set to 0, otherwise returns false.
Definition qpoint.h:125
constexpr QPoint transposed() const noexcept
Definition qpoint.h:39
constexpr void setY(int y) noexcept
Sets the y coordinate of this point to the given y coordinate.
Definition qpoint.h:145
constexpr void setX(int x) noexcept
Sets the x coordinate of this point to the given x coordinate.
Definition qpoint.h:140
static const QPointingDevice * tabletDevice(QInputDevice::DeviceType deviceType, QPointingDevice::PointerType pointerType, QPointingDeviceUniqueId uniqueId)
static const QPointingDevice * queryTabletDevice(QInputDevice::DeviceType deviceType, QPointingDevice::PointerType pointerType, QPointingDeviceUniqueId uniqueId, QInputDevice::Capabilities capabilities=QInputDevice::Capability::None, qint64 systemId=0)
static const QPointingDevice * pointingDeviceById(qint64 systemId)
static QPointingDevicePrivate * get(QPointingDevice *q)
QPointingDeviceUniqueId identifies a unique object, such as a tagged token or stylus,...
static QPointingDeviceUniqueId fromNumericId(qint64 id)
Constructs a unique pointer ID from numeric ID id.
The QPointingDevice class describes a device from which mouse, touch or tablet events originate.
PointerType
This enum represents what is interacting with the pointing device.
\inmodule QtCore\reentrant
Definition qrect.h:484
constexpr qreal height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:732
constexpr qreal width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:729
constexpr QPointF center() const noexcept
Returns the center point of the rectangle.
Definition qrect.h:699
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr int height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:239
constexpr int width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:236
QSizeF physicalSize
the screen's physical size (in millimeters)
Definition qscreen.h:50
QRect geometry
the screen's geometry in pixels
Definition qscreen.h:45
iterator begin()
Definition qset.h:136
const_iterator constBegin() const noexcept
Definition qset.h:139
const_iterator constEnd() const noexcept
Definition qset.h:143
\inmodule QtCore
Definition qsize.h:208
constexpr qreal width() const noexcept
Returns the width.
Definition qsize.h:332
constexpr qreal height() const noexcept
Returns the height.
Definition qsize.h:335
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8084
QString & append(QChar c)
Definition qstring.cpp:3252
static bool handleTouchEvent(QWindow *window, const QPointingDevice *device, const QList< struct TouchPoint > &points, Qt::KeyboardModifiers mods=Qt::NoModifier)
static bool handleGestureEventWithValueAndDelta(QWindow *window, ulong timestamp, const QPointingDevice *device, Qt::NativeGestureType type, qreal value, const QPointF &delta, const QPointF &local, const QPointF &global, int fingerCount=2)
static bool handleTabletEvent(QWindow *window, ulong timestamp, const QPointingDevice *device, const QPointF &local, const QPointF &global, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, Qt::KeyboardModifiers modifiers=Qt::NoModifier)
static bool handleGestureEventWithRealValue(QWindow *window, ulong timestamp, const QPointingDevice *device, Qt::NativeGestureType type, qreal value, const QPointF &local, const QPointF &global, int fingerCount=2)
static void registerInputDevice(const QInputDevice *device)
static bool handleGestureEvent(QWindow *window, ulong timestamp, const QPointingDevice *device, Qt::NativeGestureType type, const QPointF &local, const QPointF &global, int fingerCount=0)
static bool handleTabletEnterLeaveProximityEvent(QWindow *window, ulong timestamp, const QPointingDevice *device, bool inProximity, const QPointF &local=QPointF(), const QPointF &global=QPointF(), Qt::MouseButtons buttons={}, int xTilt=0, int yTilt=0, qreal tangentialPressure=0, qreal rotation=0, int z=0, Qt::KeyboardModifiers modifiers=Qt::NoModifier)
static bool handleWheelEvent(QWindow *window, const QPointF &local, const QPointF &global, QPoint pixelDelta, QPoint angleDelta, Qt::KeyboardModifiers mods=Qt::NoModifier, Qt::ScrollPhase phase=Qt::NoScrollPhase, Qt::MouseEventSource source=Qt::MouseEventNotSynthesized)
\inmodule QtGui
Definition qwindow.h:63
@ AtomAbsMTPositionX
Definition qxcbatom.h:172
@ AtomButtonHorizWheelLeft
Definition qxcbatom.h:170
@ AtomRelVertWheel
Definition qxcbatom.h:193
@ AtomINTEGER
Definition qxcbatom.h:191
@ AtomAbsMTOrientation
Definition qxcbatom.h:176
@ Atom_NET_WM_MOVERESIZE
Definition qxcbatom.h:73
@ AtomAbsPressure
Definition qxcbatom.h:185
@ AtomAbsMTTouchMinor
Definition qxcbatom.h:175
@ AtomAbsTiltX
Definition qxcbatom.h:186
@ AtomAbsWheel
Definition qxcbatom.h:188
@ AtomRelHorizScroll
Definition qxcbatom.h:194
@ AtomAbsMTPressure
Definition qxcbatom.h:177
@ AtomWacomSerialIDs
Definition qxcbatom.h:190
@ AtomRelHorizWheel
Definition qxcbatom.h:192
@ AtomButtonWheelUp
Definition qxcbatom.h:168
@ AtomButtonWheelDown
Definition qxcbatom.h:169
@ AtomAbsMTPositionY
Definition qxcbatom.h:173
@ AtomButtonHorizWheelRight
Definition qxcbatom.h:171
@ AtomAbsMTTouchMajor
Definition qxcbatom.h:174
@ AtomRelVertScroll
Definition qxcbatom.h:195
@ AtomAbsTiltY
Definition qxcbatom.h:187
QXcbAtom::Atom qatom(xcb_atom_t atom) const
QByteArray atomName(xcb_atom_t atom)
xcb_connection_t * xcb_connection() const
xcb_atom_t atom(QXcbAtom::Atom qatom) const
xcb_window_t rootWindow()
QXcbKeyboard * keyboard() const
bool isDuringSystemMoveResize() const
void setTime(xcb_timestamp_t t)
QXcbConnection * connection() const
bool isTouchScreen(int id)
bool startSystemMoveResizeForTouch(xcb_window_t window, int edges)
QXcbScreen * primaryScreen() const
void abortSystemMoveResize(xcb_window_t window)
void setDuringSystemMoveResize(bool during)
void xi2SelectDeviceEvents(xcb_window_t window)
QXcbWindowEventListener * windowEventListenerFromId(xcb_window_t id)
Qt::MouseButton xiToQtMouseButton(uint32_t b)
QXcbWindow * platformWindowFromId(xcb_window_t id)
bool xi2SetMouseGrabEnabled(xcb_window_t w, bool grab)
Qt::KeyboardModifiers translateModifiers(int s) const
static QXcbScrollingDevicePrivate * get(QXcbScrollingDevice *q)
QPoint lastPointerPosition() const
Definition qxcbwindow.h:143
QXcbScreen * xcbScreen() const
QPoint lastPointerGlobalPosition() const
Definition qxcbwindow.h:144
EGLImageKHR int int EGLuint64KHR * modifiers
qDeleteAll(list.begin(), list.end())
QSet< QString >::iterator it
else opt state
[0]
const char * classInfo(const QMetaObject *metaObject, const char *key)
char qt_getEnumMetaObject(const T &)
MouseButton
Definition qnamespace.h:56
@ LeftButton
Definition qnamespace.h:58
@ BackButton
Definition qnamespace.h:61
@ RightButton
Definition qnamespace.h:59
@ MaxMouseButton
Definition qnamespace.h:91
@ MiddleButton
Definition qnamespace.h:60
@ NoButton
Definition qnamespace.h:57
@ Horizontal
Definition qnamespace.h:99
@ Vertical
Definition qnamespace.h:100
@ AltModifier
@ RotateNativeGesture
@ ZoomNativeGesture
@ BeginNativeGesture
@ EndNativeGesture
@ PanNativeGesture
#define Q_UNLIKELY(x)
#define Q_LIKELY(x)
DBusConnection const char DBusError * error
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLDeviceEXT * devices
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
auto qHypot(qfloat16 x, qfloat16 y)
Definition qfloat16.h:443
static QByteArray normalizedValue(QAnyStringView value)
#define qCDebug(category,...)
return ret
static ControlElement< T > * ptr(QWidget *widget)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
GLboolean GLboolean GLboolean b
GLint GLint GLint GLint GLint x
[0]
GLfloat GLfloat GLfloat w
[0]
GLenum GLuint id
[7]
GLenum type
GLenum GLuint GLintptr offset
GLuint name
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
GLint y
GLfloat GLfloat GLfloat GLfloat h
struct _cl_event * event
GLbyte nx
GLuint GLfloat * val
GLfixed ny
GLenum GLenum GLenum GLenum GLenum scale
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define qPrintable(string)
Definition qstring.h:1531
QT_BEGIN_NAMESPACE constexpr void qSwap(T &value1, T &value2) noexcept(std::is_nothrow_swappable_v< T >)
Definition qswap.h:20
QScreen * screen
[1]
Definition main.cpp:29
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
unsigned int quint32
Definition qtypes.h:50
int qint32
Definition qtypes.h:49
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
static QPointingDevice::PointerType pointerType(unsigned currentCursor)
#define Q_XCB_REPLY(call,...)
static int xi2ValuatorOffset(const unsigned char *maskPtr, int maskLen, int number)
static void setXcbMask(uint8_t *mask, int bit)
static qreal fixed1616ToReal(xcb_input_fp1616_t val)
xcb_input_button_press_event_t qt_xcb_input_device_event_t
static qreal fixed3232ToReal(xcb_input_fp3232_t val)
QWidget * win
Definition settings.cpp:6
obj metaObject() -> className()
aWidget window() -> setWindowTitle("New Window Title")
[2]
QNetworkReply * reply
QHostInfo info
[0]
QJSValue global
bool contains(const AT &t) const noexcept
Definition qlist.h:45
\inmodule QtCore
xcb_input_event_mask_t header