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