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 qCDebug(lcQpaXInputEvents) << "scroll event from unregistered device" << sourceDeviceId;
770
771 if (xiDeviceEvent) {
772 switch (xiDeviceEvent->event_type) {
773 case XCB_INPUT_BUTTON_PRESS:
774 case XCB_INPUT_BUTTON_RELEASE:
775 case XCB_INPUT_MOTION:
776 if (eventListener && !(xiDeviceEvent->flags & XCB_INPUT_POINTER_EVENT_FLAGS_POINTER_EMULATED))
777 eventListener->handleXIMouseEvent(event);
778 break;
779
780 case XCB_INPUT_TOUCH_BEGIN:
781 case XCB_INPUT_TOUCH_UPDATE:
782 case XCB_INPUT_TOUCH_END:
783 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
784 qCDebug(lcQpaXInputEvents, "XI2 touch event type %d seq %d detail %d pos %6.1f, %6.1f root pos %6.1f, %6.1f on window %x",
785 event->event_type, xiDeviceEvent->sequence, xiDeviceEvent->detail,
786 fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y),
787 fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y),xiDeviceEvent->event);
788 if (QXcbWindow *platformWindow = platformWindowFromId(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 if (!dev) {
817 qCDebug(lcQpaXInputEvents) << "didn't find the dev for given sourceid - " << xiDeviceEvent->sourceid
818 << ", try to repopulate xi2 devices";
819 xi2SetupDevices();
820 dev = touchDeviceForId(id: xiDeviceEvent->sourceid);
821 if (!dev) {
822 qCDebug(lcQpaXInputEvents) << "still can't find the dev for it, give up.";
823 return;
824 }
825 }
826 const bool firstTouch = dev->touchPoints.isEmpty();
827 if (xiDeviceEvent->event_type == XCB_INPUT_TOUCH_BEGIN) {
828 QWindowSystemInterface::TouchPoint tp;
829 tp.id = xiDeviceEvent->detail % INT_MAX;
830 tp.state = QEventPoint::State::Pressed;
831 tp.pressure = -1.0;
832 dev->touchPoints[tp.id] = tp;
833 }
834 QWindowSystemInterface::TouchPoint &touchPoint = dev->touchPoints[xiDeviceEvent->detail];
835 QXcbScreen* screen = platformWindow->xcbScreen();
836 qreal x = fixed1616ToReal(val: xiDeviceEvent->root_x);
837 qreal y = fixed1616ToReal(val: xiDeviceEvent->root_y);
838 qreal nx = -1.0, ny = -1.0;
839 qreal w = 0.0, h = 0.0;
840 bool majorAxisIsY = touchPoint.area.height() > touchPoint.area.width();
841 for (const TouchDeviceData::ValuatorClassInfo &vci : std::as_const(t&: dev->valuatorInfo)) {
842 double value;
843 if (!xi2GetValuatorValueIfSet(event: xiDeviceEvent, valuatorNum: vci.number, value: &value))
844 continue;
845 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
846 qCDebug(lcQpaXInputEvents, " valuator %20s value %lf from range %lf -> %lf",
847 atomName(atom(vci.label)).constData(), value, vci.min, vci.max);
848 if (value > vci.max)
849 value = vci.max;
850 if (value < vci.min)
851 value = vci.min;
852 qreal valuatorNormalized = (value - vci.min) / (vci.max - vci.min);
853 if (vci.label == QXcbAtom::AtomRelX) {
854 nx = valuatorNormalized;
855 } else if (vci.label == QXcbAtom::AtomRelY) {
856 ny = valuatorNormalized;
857 } else if (vci.label == QXcbAtom::AtomAbsX) {
858 nx = valuatorNormalized;
859 } else if (vci.label == QXcbAtom::AtomAbsY) {
860 ny = valuatorNormalized;
861 } else if (vci.label == QXcbAtom::AtomAbsMTPositionX) {
862 nx = valuatorNormalized;
863 } else if (vci.label == QXcbAtom::AtomAbsMTPositionY) {
864 ny = valuatorNormalized;
865 } else if (vci.label == QXcbAtom::AtomAbsMTTouchMajor) {
866 const qreal sw = screen->geometry().width();
867 const qreal sh = screen->geometry().height();
868 w = valuatorNormalized * qHypot(x: sw, y: sh);
869 } else if (vci.label == QXcbAtom::AtomAbsMTTouchMinor) {
870 const qreal sw = screen->geometry().width();
871 const qreal sh = screen->geometry().height();
872 h = valuatorNormalized * qHypot(x: sw, y: sh);
873 } else if (vci.label == QXcbAtom::AtomAbsMTOrientation) {
874 // Find the closest axis.
875 // 0 corresponds to the Y axis, vci.max to the X axis.
876 // Flipping over the Y axis and rotating by 180 degrees
877 // don't change the result, so normalize value to range
878 // [0, vci.max] first.
879 value = qAbs(t: value);
880 while (value > vci.max)
881 value -= 2 * vci.max;
882 value = qAbs(t: value);
883 majorAxisIsY = value < vci.max - value;
884 } else if (vci.label == QXcbAtom::AtomAbsMTPressure || vci.label == QXcbAtom::AtomAbsPressure) {
885 touchPoint.pressure = valuatorNormalized;
886 }
887
888 }
889 // If any value was not updated, use the last-known value.
890 if (nx == -1.0) {
891 x = touchPoint.area.center().x();
892 nx = x / screen->geometry().width();
893 }
894 if (ny == -1.0) {
895 y = touchPoint.area.center().y();
896 ny = y / screen->geometry().height();
897 }
898 if (xiDeviceEvent->event_type != XCB_INPUT_TOUCH_END) {
899 if (!dev->providesTouchOrientation) {
900 if (w == 0.0)
901 w = touchPoint.area.width();
902 h = w;
903 } else {
904 if (w == 0.0)
905 w = qMax(a: touchPoint.area.width(), b: touchPoint.area.height());
906 if (h == 0.0)
907 h = qMin(a: touchPoint.area.width(), b: touchPoint.area.height());
908 if (majorAxisIsY)
909 qSwap(value1&: w, value2&: h);
910 }
911 }
912
913 switch (xiDeviceEvent->event_type) {
914 case XCB_INPUT_TOUCH_BEGIN:
915 if (firstTouch) {
916 dev->firstPressedPosition = QPointF(x, y);
917 dev->firstPressedNormalPosition = QPointF(nx, ny);
918 }
919 dev->pointPressedPosition.insert(key: touchPoint.id, value: QPointF(x, y));
920
921 // Touches must be accepted when we are grabbing touch events. Otherwise the entire sequence
922 // will get replayed when the grab ends.
923 if (m_xiGrab) {
924 xcb_input_xi_allow_events(c: xcb_connection(), XCB_CURRENT_TIME, deviceid: xiDeviceEvent->deviceid,
925 event_mode: XCB_INPUT_EVENT_MODE_ACCEPT_TOUCH,
926 touchid: xiDeviceEvent->detail, grab_window: xiDeviceEvent->event);
927 }
928 break;
929 case XCB_INPUT_TOUCH_UPDATE:
930 if (dev->qtTouchDevice->type() == QInputDevice::DeviceType::TouchPad && dev->pointPressedPosition.value(key: touchPoint.id) == QPointF(x, y)) {
931 qreal dx = (nx - dev->firstPressedNormalPosition.x()) *
932 dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
933 qreal dy = (ny - dev->firstPressedNormalPosition.y()) *
934 dev->size.height() * screen->geometry().height() / screen->physicalSize().height();
935 x = dev->firstPressedPosition.x() + dx;
936 y = dev->firstPressedPosition.y() + dy;
937 touchPoint.state = QEventPoint::State::Updated;
938 } else if (touchPoint.area.center() != QPoint(x, y)) {
939 touchPoint.state = QEventPoint::State::Updated;
940 if (dev->qtTouchDevice->type() == QInputDevice::DeviceType::TouchPad)
941 dev->pointPressedPosition[touchPoint.id] = QPointF(x, y);
942 }
943
944 if (dev->qtTouchDevice->type() == QInputDevice::DeviceType::TouchScreen &&
945 xiDeviceEvent->event == m_startSystemMoveResizeInfo.window &&
946 xiDeviceEvent->sourceid == m_startSystemMoveResizeInfo.deviceid &&
947 xiDeviceEvent->detail == m_startSystemMoveResizeInfo.pointid) {
948 QXcbWindow *window = platformWindowFromId(id: m_startSystemMoveResizeInfo.window);
949 if (window) {
950 xcb_input_xi_allow_events(c: xcb_connection(), XCB_CURRENT_TIME, deviceid: xiDeviceEvent->deviceid,
951 event_mode: XCB_INPUT_EVENT_MODE_REJECT_TOUCH,
952 touchid: xiDeviceEvent->detail, grab_window: xiDeviceEvent->event);
953 window->doStartSystemMoveResize(globalPos: QPoint(x, y), edges: m_startSystemMoveResizeInfo.edges);
954 m_startSystemMoveResizeInfo.window = XCB_NONE;
955 }
956 }
957 break;
958 case XCB_INPUT_TOUCH_END:
959 touchPoint.state = QEventPoint::State::Released;
960 if (dev->qtTouchDevice->type() == QInputDevice::DeviceType::TouchPad && dev->pointPressedPosition.value(key: touchPoint.id) == QPointF(x, y)) {
961 qreal dx = (nx - dev->firstPressedNormalPosition.x()) *
962 dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
963 qreal dy = (ny - dev->firstPressedNormalPosition.y()) *
964 dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
965 x = dev->firstPressedPosition.x() + dx;
966 y = dev->firstPressedPosition.y() + dy;
967 }
968 dev->pointPressedPosition.remove(key: touchPoint.id);
969 }
970 touchPoint.area = QRectF(x - w/2, y - h/2, w, h);
971 touchPoint.normalPosition = QPointF(nx, ny);
972
973 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
974 qCDebug(lcQpaXInputEvents) << " touchpoint " << touchPoint.id << " state " << touchPoint.state << " pos norm " << touchPoint.normalPosition <<
975 " area " << touchPoint.area << " pressure " << touchPoint.pressure;
976 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(s: xiDeviceEvent->mods.effective);
977 QWindowSystemInterface::handleTouchEvent(window: platformWindow->window(), timestamp: xiDeviceEvent->time, device: dev->qtTouchDevice, points: dev->touchPoints.values(), mods: modifiers);
978 if (touchPoint.state == QEventPoint::State::Released)
979 // If a touchpoint was released, we can forget it, because the ID won't be reused.
980 dev->touchPoints.remove(key: touchPoint.id);
981 else
982 // Make sure that we don't send TouchPointPressed/Moved in more than one QTouchEvent
983 // with this touch point if the next XI2 event is about a different touch point.
984 touchPoint.state = QEventPoint::State::Stationary;
985}
986
987bool QXcbConnection::startSystemMoveResizeForTouch(xcb_window_t window, int edges)
988{
989 QHash<int, TouchDeviceData>::const_iterator devIt = m_touchDevices.constBegin();
990 for (; devIt != m_touchDevices.constEnd(); ++devIt) {
991 TouchDeviceData deviceData = devIt.value();
992 if (deviceData.qtTouchDevice->type() == QInputDevice::DeviceType::TouchScreen) {
993 auto pointIt = deviceData.touchPoints.constBegin();
994 for (; pointIt != deviceData.touchPoints.constEnd(); ++pointIt) {
995 QEventPoint::State state = pointIt.value().state;
996 if (state == QEventPoint::State::Updated || state == QEventPoint::State::Pressed || state == QEventPoint::State::Stationary) {
997 m_startSystemMoveResizeInfo.window = window;
998 m_startSystemMoveResizeInfo.deviceid = devIt.key();
999 m_startSystemMoveResizeInfo.pointid = pointIt.key();
1000 m_startSystemMoveResizeInfo.edges = edges;
1001 setDuringSystemMoveResize(true);
1002 qCDebug(lcQpaXInputDevices) << "triggered system move or resize from touch";
1003 return true;
1004 }
1005 }
1006 }
1007 }
1008 return false;
1009}
1010
1011void QXcbConnection::abortSystemMoveResize(xcb_window_t window)
1012{
1013 qCDebug(lcQpaXInputDevices) << "sending client message NET_WM_MOVERESIZE_CANCEL to window: " << window;
1014 m_startSystemMoveResizeInfo.window = XCB_NONE;
1015
1016 const xcb_atom_t moveResize = connection()->atom(qatom: QXcbAtom::Atom_NET_WM_MOVERESIZE);
1017 xcb_client_message_event_t xev;
1018 xev.response_type = XCB_CLIENT_MESSAGE;
1019 xev.type = moveResize;
1020 xev.sequence = 0;
1021 xev.window = window;
1022 xev.format = 32;
1023 xev.data.data32[0] = 0;
1024 xev.data.data32[1] = 0;
1025 xev.data.data32[2] = 11; // _NET_WM_MOVERESIZE_CANCEL
1026 xev.data.data32[3] = 0;
1027 xev.data.data32[4] = 0;
1028 xcb_send_event(c: xcb_connection(), propagate: false, destination: primaryScreen()->root(),
1029 event_mask: XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
1030 event: (const char *)&xev);
1031
1032 m_duringSystemMoveResize = false;
1033}
1034
1035bool QXcbConnection::isDuringSystemMoveResize() const
1036{
1037 return m_duringSystemMoveResize;
1038}
1039
1040void QXcbConnection::setDuringSystemMoveResize(bool during)
1041{
1042 m_duringSystemMoveResize = during;
1043}
1044
1045bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab)
1046{
1047 bool ok = false;
1048
1049 if (grab) { // grab
1050 uint8_t mask[8] = {};
1051 setXcbMask(mask, XCB_INPUT_BUTTON_PRESS);
1052 setXcbMask(mask, XCB_INPUT_BUTTON_RELEASE);
1053 setXcbMask(mask, XCB_INPUT_MOTION);
1054 setXcbMask(mask, XCB_INPUT_ENTER);
1055 setXcbMask(mask, XCB_INPUT_LEAVE);
1056 if (isAtLeastXI22()) {
1057 setXcbMask(mask, XCB_INPUT_TOUCH_BEGIN);
1058 setXcbMask(mask, XCB_INPUT_TOUCH_UPDATE);
1059 setXcbMask(mask, XCB_INPUT_TOUCH_END);
1060 }
1061#if QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
1062 if (isAtLeastXI24()) {
1063 setXcbMask(mask, XCB_INPUT_GESTURE_PINCH_BEGIN);
1064 setXcbMask(mask, XCB_INPUT_GESTURE_PINCH_UPDATE);
1065 setXcbMask(mask, XCB_INPUT_GESTURE_PINCH_END);
1066 setXcbMask(mask, XCB_INPUT_GESTURE_SWIPE_BEGIN);
1067 setXcbMask(mask, XCB_INPUT_GESTURE_SWIPE_UPDATE);
1068 setXcbMask(mask, XCB_INPUT_GESTURE_SWIPE_END);
1069 }
1070#endif // QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
1071
1072 for (int id : std::as_const(t&: m_xiMasterPointerIds)) {
1073 xcb_generic_error_t *error = nullptr;
1074 auto cookie = xcb_input_xi_grab_device(c: xcb_connection(), window: w, XCB_CURRENT_TIME, cursor: XCB_CURSOR_NONE, deviceid: id,
1075 mode: XCB_INPUT_GRAB_MODE_22_ASYNC, paired_device_mode: XCB_INPUT_GRAB_MODE_22_ASYNC,
1076 owner_events: false, mask_len: 2, mask: reinterpret_cast<uint32_t *>(mask));
1077 auto *reply = xcb_input_xi_grab_device_reply(c: xcb_connection(), cookie, e: &error);
1078 if (error) {
1079 qCDebug(lcQpaXInput, "failed to grab events for device %d on window %x"
1080 "(error code %d)", id, w, error->error_code);
1081 free(ptr: error);
1082 } else {
1083 // Managed to grab at least one of master pointers, that should be enough
1084 // to properly dismiss windows that rely on mouse grabbing.
1085 ok = true;
1086 }
1087 free(ptr: reply);
1088 }
1089 } else { // ungrab
1090 for (int id : std::as_const(t&: m_xiMasterPointerIds)) {
1091 auto cookie = xcb_input_xi_ungrab_device_checked(c: xcb_connection(), XCB_CURRENT_TIME, deviceid: id);
1092 xcb_generic_error_t *error = xcb_request_check(c: xcb_connection(), cookie);
1093 if (error) {
1094 qCDebug(lcQpaXInput, "XIUngrabDevice failed - id: %d (error code %d)", id, error->error_code);
1095 free(ptr: error);
1096 }
1097 }
1098 // XIUngrabDevice does not seem to wait for a reply from X server (similar to
1099 // xcb_ungrab_pointer). Ungrabbing won't fail, unless NoSuchExtension error
1100 // has occurred due to a programming error somewhere else in the stack. That
1101 // would mean that things will crash soon anyway.
1102 ok = true;
1103 }
1104
1105 if (ok)
1106 m_xiGrab = grab;
1107
1108 return ok;
1109}
1110
1111void QXcbConnection::xi2HandleHierarchyEvent(void *event)
1112{
1113 auto *xiEvent = reinterpret_cast<xcb_input_hierarchy_event_t *>(event);
1114 // We care about hotplugged devices (slaves) and master devices.
1115 // We don't report anything for DEVICE_ENABLED or DEVICE_DISABLED
1116 // (but often that goes with adding or removal anyway).
1117 // We don't react to SLAVE_ATTACHED or SLAVE_DETACHED either.
1118 if (xiEvent->flags & (XCB_INPUT_HIERARCHY_MASK_MASTER_ADDED |
1119 XCB_INPUT_HIERARCHY_MASK_MASTER_REMOVED |
1120 XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED |
1121 XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED))
1122 xi2SetupDevices();
1123}
1124
1125#if QT_XCB_HAS_TOUCHPAD_GESTURES
1126void QXcbConnection::xi2HandleGesturePinchEvent(void *event)
1127{
1128 auto *xiEvent = reinterpret_cast<qt_xcb_input_pinch_event_t *>(event);
1129
1130 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) {
1131 qCDebug(lcQpaXInputEvents, "XI2 gesture event type %d seq %d fingers %d pos %6.1f, "
1132 "%6.1f root pos %6.1f, %6.1f delta_angle %6.1f scale %6.1f on window %x",
1133 xiEvent->event_type, xiEvent->sequence, xiEvent->detail,
1134 fixed1616ToReal(xiEvent->event_x), fixed1616ToReal(xiEvent->event_y),
1135 fixed1616ToReal(xiEvent->root_x), fixed1616ToReal(xiEvent->root_y),
1136 fixed1616ToReal(xiEvent->delta_angle), fixed1616ToReal(xiEvent->scale),
1137 xiEvent->event);
1138 }
1139 QXcbWindow *platformWindow = platformWindowFromId(id: xiEvent->event);
1140 if (!platformWindow)
1141 return;
1142
1143 setTime(xiEvent->time);
1144
1145 TouchDeviceData *dev = touchDeviceForId(id: xiEvent->sourceid);
1146 Q_ASSERT(dev);
1147
1148 uint32_t fingerCount = xiEvent->detail;
1149
1150 switch (xiEvent->event_type) {
1151 case XCB_INPUT_GESTURE_PINCH_BEGIN:
1152 // Gestures must be accepted when we are grabbing gesture events. Otherwise the entire
1153 // sequence will get replayed when the grab ends.
1154 if (m_xiGrab) {
1155 xcb_input_xi_allow_events(c: xcb_connection(), XCB_CURRENT_TIME, deviceid: xiEvent->deviceid,
1156 event_mode: XCB_INPUT_EVENT_MODE_ASYNC_DEVICE, touchid: 0, grab_window: xiEvent->event);
1157 }
1158 m_lastPinchScale = 1.0;
1159 QWindowSystemInterface::handleGestureEvent(window: platformWindow->window(), timestamp: xiEvent->time,
1160 device: dev->qtTouchDevice,
1161 type: Qt::BeginNativeGesture,
1162 local: platformWindow->lastPointerPosition(),
1163 global: platformWindow->lastPointerGlobalPosition(),
1164 fingerCount);
1165 break;
1166
1167 case XCB_INPUT_GESTURE_PINCH_UPDATE: {
1168 qreal rotationDelta = fixed1616ToReal(val: xiEvent->delta_angle);
1169 qreal scale = fixed1616ToReal(val: xiEvent->scale);
1170 qreal scaleDelta = scale - m_lastPinchScale;
1171 m_lastPinchScale = scale;
1172
1173 QPointF delta = QPointF(fixed1616ToReal(val: xiEvent->delta_x),
1174 fixed1616ToReal(val: xiEvent->delta_y));
1175
1176 if (!delta.isNull()) {
1177 QWindowSystemInterface::handleGestureEventWithValueAndDelta(
1178 window: platformWindow->window(), timestamp: xiEvent->time, device: dev->qtTouchDevice,
1179 type: Qt::PanNativeGesture, value: 0, delta,
1180 local: platformWindow->lastPointerPosition(),
1181 global: platformWindow->lastPointerGlobalPosition(),
1182 fingerCount);
1183 }
1184 if (rotationDelta != 0) {
1185 QWindowSystemInterface::handleGestureEventWithRealValue(
1186 window: platformWindow->window(), timestamp: xiEvent->time, device: dev->qtTouchDevice,
1187 type: Qt::RotateNativeGesture,
1188 value: rotationDelta,
1189 local: platformWindow->lastPointerPosition(),
1190 global: platformWindow->lastPointerGlobalPosition(),
1191 fingerCount);
1192 }
1193 if (scaleDelta != 0) {
1194 QWindowSystemInterface::handleGestureEventWithRealValue(
1195 window: platformWindow->window(), timestamp: xiEvent->time, device: dev->qtTouchDevice,
1196 type: Qt::ZoomNativeGesture,
1197 value: scaleDelta,
1198 local: platformWindow->lastPointerPosition(),
1199 global: platformWindow->lastPointerGlobalPosition(),
1200 fingerCount);
1201 }
1202 break;
1203 }
1204 case XCB_INPUT_GESTURE_PINCH_END:
1205 QWindowSystemInterface::handleGestureEvent(window: platformWindow->window(), timestamp: xiEvent->time,
1206 device: dev->qtTouchDevice,
1207 type: Qt::EndNativeGesture,
1208 local: platformWindow->lastPointerPosition(),
1209 global: platformWindow->lastPointerGlobalPosition(),
1210 fingerCount);
1211 break;
1212 }
1213}
1214
1215void QXcbConnection::xi2HandleGestureSwipeEvent(void *event)
1216{
1217 auto *xiEvent = reinterpret_cast<qt_xcb_input_swipe_event_t *>(event);
1218
1219 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) {
1220 qCDebug(lcQpaXInputEvents, "XI2 gesture event type %d seq %d detail %d pos %6.1f, %6.1f root pos %6.1f, %6.1f on window %x",
1221 xiEvent->event_type, xiEvent->sequence, xiEvent->detail,
1222 fixed1616ToReal(xiEvent->event_x), fixed1616ToReal(xiEvent->event_y),
1223 fixed1616ToReal(xiEvent->root_x), fixed1616ToReal(xiEvent->root_y),
1224 xiEvent->event);
1225 }
1226 QXcbWindow *platformWindow = platformWindowFromId(id: xiEvent->event);
1227 if (!platformWindow)
1228 return;
1229
1230 setTime(xiEvent->time);
1231
1232 TouchDeviceData *dev = touchDeviceForId(id: xiEvent->sourceid);
1233 Q_ASSERT(dev);
1234
1235 uint32_t fingerCount = xiEvent->detail;
1236
1237 switch (xiEvent->event_type) {
1238 case XCB_INPUT_GESTURE_SWIPE_BEGIN:
1239 // Gestures must be accepted when we are grabbing gesture events. Otherwise the entire
1240 // sequence will get replayed when the grab ends.
1241 if (m_xiGrab) {
1242 xcb_input_xi_allow_events(c: xcb_connection(), XCB_CURRENT_TIME, deviceid: xiEvent->deviceid,
1243 event_mode: XCB_INPUT_EVENT_MODE_ASYNC_DEVICE, touchid: 0, grab_window: xiEvent->event);
1244 }
1245 QWindowSystemInterface::handleGestureEvent(window: platformWindow->window(), timestamp: xiEvent->time,
1246 device: dev->qtTouchDevice,
1247 type: Qt::BeginNativeGesture,
1248 local: platformWindow->lastPointerPosition(),
1249 global: platformWindow->lastPointerGlobalPosition(),
1250 fingerCount);
1251 break;
1252 case XCB_INPUT_GESTURE_SWIPE_UPDATE: {
1253 QPointF delta = QPointF(fixed1616ToReal(val: xiEvent->delta_x),
1254 fixed1616ToReal(val: xiEvent->delta_y));
1255
1256 if (xiEvent->delta_x != 0 || xiEvent->delta_y != 0) {
1257 QWindowSystemInterface::handleGestureEventWithValueAndDelta(
1258 window: platformWindow->window(), timestamp: xiEvent->time, device: dev->qtTouchDevice,
1259 type: Qt::PanNativeGesture, value: 0, delta,
1260 local: platformWindow->lastPointerPosition(),
1261 global: platformWindow->lastPointerGlobalPosition(),
1262 fingerCount);
1263 }
1264 break;
1265 }
1266 case XCB_INPUT_GESTURE_SWIPE_END:
1267 QWindowSystemInterface::handleGestureEvent(window: platformWindow->window(), timestamp: xiEvent->time,
1268 device: dev->qtTouchDevice,
1269 type: Qt::EndNativeGesture,
1270 local: platformWindow->lastPointerPosition(),
1271 global: platformWindow->lastPointerGlobalPosition(),
1272 fingerCount);
1273 break;
1274 }
1275}
1276
1277#else // QT_XCB_HAS_TOUCHPAD_GESTURES
1278void QXcbConnection::xi2HandleGesturePinchEvent(void*) {}
1279void QXcbConnection::xi2HandleGestureSwipeEvent(void*) {}
1280#endif
1281
1282void QXcbConnection::xi2HandleDeviceChangedEvent(void *event)
1283{
1284 auto *xiEvent = reinterpret_cast<xcb_input_device_changed_event_t *>(event);
1285 switch (xiEvent->reason) {
1286 case XCB_INPUT_CHANGE_REASON_DEVICE_CHANGE: {
1287 // Don't call xi2SetupSlavePointerDevice() again for an already-known device, and never for a master.
1288 if (m_xiMasterPointerIds.contains(t: xiEvent->deviceid) || m_xiSlavePointerIds.contains(t: xiEvent->deviceid))
1289 return;
1290 auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), xiEvent->sourceid);
1291 if (!reply || reply->num_infos <= 0)
1292 return;
1293 auto it = xcb_input_xi_query_device_infos_iterator(R: reply.get());
1294 xi2SetupSlavePointerDevice(info: it.data);
1295 break;
1296 }
1297 case XCB_INPUT_CHANGE_REASON_SLAVE_SWITCH: {
1298 if (auto *scrollingDevice = scrollingDeviceForId(id: xiEvent->sourceid))
1299 xi2UpdateScrollingDevice(scrollingDevice);
1300 break;
1301 }
1302 default:
1303 qCDebug(lcQpaXInputEvents, "unknown device-changed-event (device %d)", xiEvent->sourceid);
1304 break;
1305 }
1306}
1307
1308void QXcbConnection::xi2UpdateScrollingDevice(QInputDevice *dev)
1309{
1310 QXcbScrollingDevice *scrollDev = qobject_cast<QXcbScrollingDevice *>(object: dev);
1311 if (!scrollDev || !scrollDev->capabilities().testFlag(flag: QInputDevice::Capability::Scroll))
1312 return;
1313 QXcbScrollingDevicePrivate *scrollingDevice = QXcbScrollingDevice::get(q: scrollDev);
1314
1315 auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), scrollingDevice->systemId);
1316 if (!reply || reply->num_infos <= 0) {
1317 qCDebug(lcQpaXInputDevices, "scrolling device %lld no longer present", scrollingDevice->systemId);
1318 return;
1319 }
1320 QPointF lastScrollPosition;
1321 if (lcQpaXInputEvents().isDebugEnabled())
1322 lastScrollPosition = scrollingDevice->lastScrollPosition;
1323
1324 xcb_input_xi_device_info_t *deviceInfo = xcb_input_xi_query_device_infos_iterator(R: reply.get()).data;
1325 auto classes_it = xcb_input_xi_device_info_classes_iterator(R: deviceInfo);
1326 for (; classes_it.rem; xcb_input_device_class_next(i: &classes_it)) {
1327 xcb_input_device_class_t *classInfo = classes_it.data;
1328 if (classInfo->type == XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR) {
1329 auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classInfo);
1330 const int valuatorAtom = qatom(atom: vci->label);
1331 if (valuatorAtom == QXcbAtom::AtomRelHorizScroll || valuatorAtom == QXcbAtom::AtomRelHorizWheel)
1332 scrollingDevice->lastScrollPosition.setX(fixed3232ToReal(val: vci->value));
1333 else if (valuatorAtom == QXcbAtom::AtomRelVertScroll || valuatorAtom == QXcbAtom::AtomRelVertWheel)
1334 scrollingDevice->lastScrollPosition.setY(fixed3232ToReal(val: vci->value));
1335 }
1336 }
1337 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled() && lastScrollPosition != scrollingDevice->lastScrollPosition))
1338 qCDebug(lcQpaXInputEvents, "scrolling device %lld moved from (%f, %f) to (%f, %f)", scrollingDevice->systemId,
1339 lastScrollPosition.x(), lastScrollPosition.y(),
1340 scrollingDevice->lastScrollPosition.x(),
1341 scrollingDevice->lastScrollPosition.y());
1342}
1343
1344void QXcbConnection::xi2UpdateScrollingDevices()
1345{
1346 const auto &devices = QInputDevice::devices();
1347 for (const QInputDevice *dev : devices) {
1348 if (dev->capabilities().testFlag(flag: QInputDevice::Capability::Scroll))
1349 xi2UpdateScrollingDevice(dev: const_cast<QInputDevice *>(dev));
1350 }
1351}
1352
1353QXcbScrollingDevice *QXcbConnection::scrollingDeviceForId(int id)
1354{
1355 const QPointingDevice *dev = QPointingDevicePrivate::pointingDeviceById(systemId: id);
1356 if (!dev|| !dev->capabilities().testFlag(flag: QInputDevice::Capability::Scroll))
1357 return nullptr;
1358 return qobject_cast<QXcbScrollingDevice *>(object: const_cast<QPointingDevice *>(dev));
1359}
1360
1361void QXcbConnection::xi2HandleScrollEvent(void *event, const QPointingDevice *dev)
1362{
1363 auto *xiDeviceEvent = reinterpret_cast<qt_xcb_input_device_event_t *>(event);
1364
1365 const QXcbScrollingDevice *scrollDev = qobject_cast<const QXcbScrollingDevice *>(object: dev);
1366 if (!scrollDev || !scrollDev->capabilities().testFlag(flag: QInputDevice::Capability::Scroll))
1367 return;
1368 const QXcbScrollingDevicePrivate *scrollingDevice = QXcbScrollingDevice::get(q: scrollDev);
1369
1370 if (xiDeviceEvent->event_type == XCB_INPUT_MOTION && scrollingDevice->orientations) {
1371 if (QXcbWindow *platformWindow = platformWindowFromId(id: xiDeviceEvent->event)) {
1372 QPoint rawDelta;
1373 QPoint angleDelta;
1374 double value;
1375 if (scrollingDevice->orientations & Qt::Vertical) {
1376 if (xi2GetValuatorValueIfSet(event: xiDeviceEvent, valuatorNum: scrollingDevice->verticalIndex, value: &value)) {
1377 double delta = scrollingDevice->lastScrollPosition.y() - value;
1378 scrollingDevice->lastScrollPosition.setY(value);
1379 angleDelta.setY((delta / scrollingDevice->verticalIncrement) * 120);
1380 // With most drivers the increment is 1 for wheels.
1381 // For libinput it is hardcoded to a useless 15.
1382 // For a proper touchpad driver it should be in the same order of magnitude as 120
1383 if (scrollingDevice->verticalIncrement > 15)
1384 rawDelta.setY(delta);
1385 else if (scrollingDevice->verticalIncrement < -15)
1386 rawDelta.setY(-delta);
1387 }
1388 }
1389 if (scrollingDevice->orientations & Qt::Horizontal) {
1390 if (xi2GetValuatorValueIfSet(event: xiDeviceEvent, valuatorNum: scrollingDevice->horizontalIndex, value: &value)) {
1391 double delta = scrollingDevice->lastScrollPosition.x() - value;
1392 scrollingDevice->lastScrollPosition.setX(value);
1393 angleDelta.setX((delta / scrollingDevice->horizontalIncrement) * 120);
1394 // See comment under vertical
1395 if (scrollingDevice->horizontalIncrement > 15)
1396 rawDelta.setX(delta);
1397 else if (scrollingDevice->horizontalIncrement < -15)
1398 rawDelta.setX(-delta);
1399 }
1400 }
1401 if (!angleDelta.isNull()) {
1402 QPoint local(fixed1616ToReal(val: xiDeviceEvent->event_x), fixed1616ToReal(val: xiDeviceEvent->event_y));
1403 QPoint global(fixed1616ToReal(val: xiDeviceEvent->root_x), fixed1616ToReal(val: xiDeviceEvent->root_y));
1404 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(s: xiDeviceEvent->mods.effective);
1405 if (modifiers & Qt::AltModifier) {
1406 angleDelta = angleDelta.transposed();
1407 rawDelta = rawDelta.transposed();
1408 }
1409 qCDebug(lcQpaXInputEvents) << "scroll wheel from device" << scrollingDevice->systemId
1410 << "@ window pos" << local << "delta px" << rawDelta << "angle" << angleDelta;
1411 QWindowSystemInterface::handleWheelEvent(window: platformWindow->window(), timestamp: xiDeviceEvent->time, device: dev,
1412 local, global, pixelDelta: rawDelta, angleDelta, mods: modifiers);
1413 }
1414 }
1415 } else if (xiDeviceEvent->event_type == XCB_INPUT_BUTTON_RELEASE && scrollingDevice->legacyOrientations) {
1416 if (QXcbWindow *platformWindow = platformWindowFromId(id: xiDeviceEvent->event)) {
1417 QPoint angleDelta;
1418 if (scrollingDevice->legacyOrientations & Qt::Vertical) {
1419 if (xiDeviceEvent->detail == 4)
1420 angleDelta.setY(120);
1421 else if (xiDeviceEvent->detail == 5)
1422 angleDelta.setY(-120);
1423 }
1424 if (scrollingDevice->legacyOrientations & Qt::Horizontal) {
1425 if (xiDeviceEvent->detail == 6)
1426 angleDelta.setX(120);
1427 else if (xiDeviceEvent->detail == 7)
1428 angleDelta.setX(-120);
1429 }
1430 if (!angleDelta.isNull()) {
1431 QPoint local(fixed1616ToReal(val: xiDeviceEvent->event_x), fixed1616ToReal(val: xiDeviceEvent->event_y));
1432 QPoint global(fixed1616ToReal(val: xiDeviceEvent->root_x), fixed1616ToReal(val: xiDeviceEvent->root_y));
1433 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(s: xiDeviceEvent->mods.effective);
1434 if (modifiers & Qt::AltModifier)
1435 angleDelta = angleDelta.transposed();
1436 qCDebug(lcQpaXInputEvents) << "scroll wheel (button" << xiDeviceEvent->detail << ") @ window pos" << local << "delta angle" << angleDelta;
1437 QWindowSystemInterface::handleWheelEvent(window: platformWindow->window(), timestamp: xiDeviceEvent->time, device: dev,
1438 local, global, pixelDelta: QPoint(), angleDelta, mods: modifiers);
1439 }
1440 }
1441 }
1442}
1443
1444static int xi2ValuatorOffset(const unsigned char *maskPtr, int maskLen, int number)
1445{
1446 int offset = 0;
1447 for (int i = 0; i < maskLen; i++) {
1448 if (number < 8) {
1449 if ((maskPtr[i] & (1 << number)) == 0)
1450 return -1;
1451 }
1452 for (int j = 0; j < 8; j++) {
1453 if (j == number)
1454 return offset;
1455 if (maskPtr[i] & (1 << j))
1456 offset++;
1457 }
1458 number -= 8;
1459 }
1460 return -1;
1461}
1462
1463bool QXcbConnection::xi2GetValuatorValueIfSet(const void *event, int valuatorNum, double *value)
1464{
1465 auto *xideviceevent = static_cast<const qt_xcb_input_device_event_t *>(event);
1466 auto *buttonsMaskAddr = reinterpret_cast<const unsigned char *>(&xideviceevent[1]);
1467 auto *valuatorsMaskAddr = buttonsMaskAddr + xideviceevent->buttons_len * 4;
1468 auto *valuatorsValuesAddr = reinterpret_cast<const xcb_input_fp3232_t *>(valuatorsMaskAddr + xideviceevent->valuators_len * 4);
1469
1470 int valuatorOffset = xi2ValuatorOffset(maskPtr: valuatorsMaskAddr, maskLen: xideviceevent->valuators_len, number: valuatorNum);
1471 if (valuatorOffset < 0)
1472 return false;
1473
1474 *value = valuatorsValuesAddr[valuatorOffset].integral;
1475 *value += ((double)valuatorsValuesAddr[valuatorOffset].frac / (1 << 16) / (1 << 16));
1476 return true;
1477}
1478
1479Qt::MouseButton QXcbConnection::xiToQtMouseButton(uint32_t b)
1480{
1481 switch (b) {
1482 case 1: return Qt::LeftButton;
1483 case 2: return Qt::MiddleButton;
1484 case 3: return Qt::RightButton;
1485 // 4-7 are for scrolling
1486 default: break;
1487 }
1488 if (b >= 8 && b <= Qt::MaxMouseButton)
1489 return static_cast<Qt::MouseButton>(Qt::BackButton << (b - 8));
1490 return Qt::NoButton;
1491}
1492
1493#if QT_CONFIG(tabletevent)
1494bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletData)
1495{
1496 bool handled = true;
1497 const auto *xiDeviceEvent = reinterpret_cast<const qt_xcb_input_device_event_t *>(event);
1498
1499 switch (xiDeviceEvent->event_type) {
1500 case XCB_INPUT_BUTTON_PRESS: {
1501 Qt::MouseButton b = xiToQtMouseButton(b: xiDeviceEvent->detail);
1502 tabletData->buttons |= b;
1503 xi2ReportTabletEvent(event, tabletData);
1504 break;
1505 }
1506 case XCB_INPUT_BUTTON_RELEASE: {
1507 Qt::MouseButton b = xiToQtMouseButton(b: xiDeviceEvent->detail);
1508 tabletData->buttons ^= b;
1509 xi2ReportTabletEvent(event, tabletData);
1510 break;
1511 }
1512 case XCB_INPUT_MOTION:
1513 xi2ReportTabletEvent(event, tabletData);
1514 break;
1515 case XCB_INPUT_PROPERTY: {
1516 // This is the wacom driver's way of reporting tool proximity.
1517 // The evdev driver doesn't do it this way.
1518 const auto *ev = reinterpret_cast<const xcb_input_property_event_t *>(event);
1519 if (ev->what == XCB_INPUT_PROPERTY_FLAG_MODIFIED) {
1520 if (ev->property == atom(qatom: QXcbAtom::AtomWacomSerialIDs)) {
1521 enum WacomSerialIndex {
1522 _WACSER_USB_ID = 0,
1523 _WACSER_LAST_TOOL_SERIAL,
1524 _WACSER_LAST_TOOL_ID,
1525 _WACSER_TOOL_SERIAL,
1526 _WACSER_TOOL_ID,
1527 _WACSER_COUNT
1528 };
1529
1530 auto reply = Q_XCB_REPLY(xcb_input_xi_get_property, xcb_connection(), tabletData->deviceId, 0,
1531 ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, 100);
1532 if (reply) {
1533 if (reply->type == atom(qatom: QXcbAtom::AtomINTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) {
1534 quint32 *ptr = reinterpret_cast<quint32 *>(xcb_input_xi_get_property_items(R: reply.get()));
1535 quint32 tool = ptr[_WACSER_TOOL_ID];
1536 // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
1537 // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
1538 if (!tool && ptr[_WACSER_TOOL_SERIAL])
1539 tool = ptr[_WACSER_TOOL_SERIAL];
1540
1541 QWindow *win = nullptr; // TODO QTBUG-111400 get the position somehow, then the window
1542 // The property change event informs us which tool is in proximity or which one left proximity.
1543 if (tool) {
1544 const QPointingDevice *dev = tabletToolInstance(master: nullptr, tabletName: tabletData->name,
1545 id: tabletData->deviceId, usbId: ptr[_WACSER_USB_ID], toolId: tool,
1546 uniqueId: qint64(ptr[_WACSER_TOOL_SERIAL])); // TODO look up the master
1547 tabletData->inProximity = true;
1548 tabletData->tool = dev->type();
1549 tabletData->serialId = qint64(ptr[_WACSER_TOOL_SERIAL]);
1550 QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window: win, timestamp: ev->time, device: dev, inProximity: true); // enter
1551 } else {
1552 tool = ptr[_WACSER_LAST_TOOL_ID];
1553 // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
1554 // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
1555 if (!tool)
1556 tool = ptr[_WACSER_LAST_TOOL_SERIAL];
1557 auto *dev = qobject_cast<const QPointingDevice *>(object: QInputDevicePrivate::fromId(systemId: tabletData->deviceId));
1558 Q_ASSERT(dev);
1559 tabletData->tool = dev->type();
1560 tabletData->inProximity = false;
1561 tabletData->serialId = qint64(ptr[_WACSER_LAST_TOOL_SERIAL]);
1562 QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window: win, timestamp: ev->time, device: dev, inProximity: false); // leave
1563 }
1564 // TODO maybe have a hash of tabletData->deviceId to device data so we can
1565 // look up the tablet name here, and distinguish multiple tablets
1566 qCDebug(lcQpaXInputDevices, "XI2 proximity change on tablet %d %s (USB %x): last tool: %x id %x current tool: %x id %x %s",
1567 tabletData->deviceId, qPrintable(tabletData->name), ptr[_WACSER_USB_ID],
1568 ptr[_WACSER_LAST_TOOL_SERIAL], ptr[_WACSER_LAST_TOOL_ID],
1569 ptr[_WACSER_TOOL_SERIAL], ptr[_WACSER_TOOL_ID], toolName(tabletData->tool));
1570 }
1571 }
1572 }
1573 }
1574 break;
1575 }
1576 default:
1577 handled = false;
1578 break;
1579 }
1580
1581 return handled;
1582}
1583
1584inline qreal scaleOneValuator(qreal normValue, qreal screenMin, qreal screenSize)
1585{
1586 return screenMin + normValue * screenSize;
1587}
1588
1589// TODO QPointingDevice not TabletData
1590void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletData)
1591{
1592 auto *ev = reinterpret_cast<const qt_xcb_input_device_event_t *>(event);
1593 QXcbWindow *xcbWindow = platformWindowFromId(id: ev->event);
1594 if (!xcbWindow)
1595 return;
1596 QWindow *window = xcbWindow->window();
1597 const Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(s: ev->mods.effective);
1598 QPointF local(fixed1616ToReal(val: ev->event_x), fixed1616ToReal(val: ev->event_y));
1599 QPointF global(fixed1616ToReal(val: ev->root_x), fixed1616ToReal(val: ev->root_y));
1600 double pressure = 0, rotation = 0, tangentialPressure = 0;
1601 int xTilt = 0, yTilt = 0;
1602 static const bool useValuators = !qEnvironmentVariableIsSet(varName: "QT_XCB_TABLET_LEGACY_COORDINATES");
1603 const QPointingDevice *dev = QPointingDevicePrivate::tabletDevice(deviceType: QInputDevice::DeviceType(tabletData->tool),
1604 pointerType: QPointingDevice::PointerType(tabletData->pointerType),
1605 uniqueId: QPointingDeviceUniqueId::fromNumericId(id: tabletData->serialId));
1606
1607 // Valuators' values are relative to the physical size of the current virtual
1608 // screen. Therefore we cannot use QScreen/QWindow geometry and should use
1609 // QPlatformWindow/QPlatformScreen instead.
1610 QRect physicalScreenArea;
1611 if (Q_LIKELY(useValuators)) {
1612 const QList<QPlatformScreen *> siblings = window->screen()->handle()->virtualSiblings();
1613 for (const QPlatformScreen *screen : siblings)
1614 physicalScreenArea |= screen->geometry();
1615 }
1616
1617 for (QHash<int, TabletData::ValuatorClassInfo>::iterator it = tabletData->valuatorInfo.begin(),
1618 ite = tabletData->valuatorInfo.end(); it != ite; ++it) {
1619 int valuator = it.key();
1620 TabletData::ValuatorClassInfo &classInfo(it.value());
1621 xi2GetValuatorValueIfSet(event, valuatorNum: classInfo.number, value: &classInfo.curVal);
1622 double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal);
1623 switch (valuator) {
1624 case QXcbAtom::AtomAbsX:
1625 if (Q_LIKELY(useValuators)) {
1626 const qreal value = scaleOneValuator(normValue: normalizedValue, screenMin: physicalScreenArea.x(), screenSize: physicalScreenArea.width());
1627 global.setX(value);
1628 local.setX(xcbWindow->mapFromGlobalF(pos: global).x());
1629 }
1630 break;
1631 case QXcbAtom::AtomAbsY:
1632 if (Q_LIKELY(useValuators)) {
1633 qreal value = scaleOneValuator(normValue: normalizedValue, screenMin: physicalScreenArea.y(), screenSize: physicalScreenArea.height());
1634 global.setY(value);
1635 local.setY(xcbWindow->mapFromGlobalF(pos: global).y());
1636 }
1637 break;
1638 case QXcbAtom::AtomAbsPressure:
1639 pressure = normalizedValue;
1640 break;
1641 case QXcbAtom::AtomAbsTiltX:
1642 xTilt = classInfo.curVal;
1643 break;
1644 case QXcbAtom::AtomAbsTiltY:
1645 yTilt = classInfo.curVal;
1646 break;
1647 case QXcbAtom::AtomAbsWheel:
1648 switch (tabletData->tool) {
1649 case QInputDevice::DeviceType::Airbrush:
1650 tangentialPressure = normalizedValue * 2.0 - 1.0; // Convert 0..1 range to -1..+1 range
1651 break;
1652 case QInputDevice::DeviceType::Stylus:
1653 if (dev->capabilities().testFlag(flag: QInputDevice::Capability::Rotation))
1654 rotation = normalizedValue * 360.0 - 180.0; // Convert 0..1 range to -180..+180 degrees
1655 break;
1656 default: // Other types of styli do not use this valuator
1657 break;
1658 }
1659 break;
1660 default:
1661 break;
1662 }
1663 }
1664
1665 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
1666 qCDebug(lcQpaXInputEvents, "XI2 event on tablet %d with tool %s %llx type %s seq %d detail %d time %d "
1667 "pos %6.1f, %6.1f root pos %6.1f, %6.1f buttons 0x%x pressure %4.2lf tilt %d, %d rotation %6.2lf modifiers 0x%x",
1668 tabletData->deviceId, toolName(tabletData->tool), tabletData->serialId, pointerTypeName(tabletData->pointerType),
1669 ev->sequence, ev->detail, ev->time,
1670 local.x(), local.y(), global.x(), global.y(),
1671 (int)tabletData->buttons, pressure, xTilt, yTilt, rotation, (int)modifiers);
1672
1673 QWindowSystemInterface::handleTabletEvent(window, timestamp: ev->time, device: dev, local, global,
1674 buttons: tabletData->buttons, pressure,
1675 xTilt, yTilt, tangentialPressure,
1676 rotation, z: 0, modifiers);
1677}
1678
1679QXcbConnection::TabletData *QXcbConnection::tabletDataForDevice(int id)
1680{
1681 for (int i = 0; i < m_tabletData.size(); ++i) {
1682 if (m_tabletData.at(i).deviceId == id)
1683 return &m_tabletData[i];
1684 }
1685 return nullptr;
1686}
1687
1688#endif // QT_CONFIG(tabletevent)
1689

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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