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

source code of qtbase/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp