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