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