1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qxcbconnection.h"
41#include "qxcbkeyboard.h"
42#include "qxcbscreen.h"
43#include "qxcbwindow.h"
44#include "qtouchdevice.h"
45#include "QtCore/qmetaobject.h"
46#include <qpa/qwindowsysteminterface_p.h>
47#include <QDebug>
48#include <cmath>
49
50#include <xcb/xinput.h>
51
52using qt_xcb_input_device_event_t = xcb_input_button_press_event_t;
53
54struct qt_xcb_input_event_mask_t {
55 xcb_input_event_mask_t header;
56 uint32_t mask;
57};
58
59void QXcbConnection::xi2SelectStateEvents()
60{
61 // These state events do not depend on a specific X window, but are global
62 // for the X client's (application's) state.
63 qt_xcb_input_event_mask_t xiEventMask;
64 xiEventMask.header.deviceid = XCB_INPUT_DEVICE_ALL;
65 xiEventMask.header.mask_len = 1;
66 xiEventMask.mask = XCB_INPUT_XI_EVENT_MASK_HIERARCHY;
67 xiEventMask.mask |= XCB_INPUT_XI_EVENT_MASK_DEVICE_CHANGED;
68 xiEventMask.mask |= XCB_INPUT_XI_EVENT_MASK_PROPERTY;
69 xcb_input_xi_select_events(c: xcb_connection(), window: rootWindow(), num_mask: 1, masks: &xiEventMask.header);
70}
71
72void QXcbConnection::xi2SelectDeviceEvents(xcb_window_t window)
73{
74 if (window == rootWindow())
75 return;
76
77 uint32_t bitMask = XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS;
78 bitMask |= XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
79 bitMask |= XCB_INPUT_XI_EVENT_MASK_MOTION;
80 // There is a check for enter/leave events in plain xcb enter/leave event handler,
81 // core enter/leave events will be ignored in this case.
82 bitMask |= XCB_INPUT_XI_EVENT_MASK_ENTER;
83 bitMask |= XCB_INPUT_XI_EVENT_MASK_LEAVE;
84 if (isAtLeastXI22()) {
85 bitMask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN;
86 bitMask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE;
87 bitMask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_END;
88 }
89
90 qt_xcb_input_event_mask_t mask;
91 mask.header.deviceid = XCB_INPUT_DEVICE_ALL_MASTER;
92 mask.header.mask_len = 1;
93 mask.mask = bitMask;
94 xcb_void_cookie_t cookie =
95 xcb_input_xi_select_events_checked(c: xcb_connection(), window, num_mask: 1, masks: &mask.header);
96 xcb_generic_error_t *error = xcb_request_check(c: xcb_connection(), cookie);
97 if (error) {
98 qCDebug(lcQpaXInput, "failed to select events, window %x, error code %d", window, error->error_code);
99 free(ptr: error);
100 } else {
101 QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
102 }
103}
104
105static inline qreal fixed3232ToReal(xcb_input_fp3232_t val)
106{
107 return qreal(val.integral) + qreal(val.frac) / (1ULL << 32);
108}
109
110void QXcbConnection::xi2SetupDevice(void *info, bool removeExisting)
111{
112 auto *deviceInfo = reinterpret_cast<xcb_input_xi_device_info_t *>(info);
113 if (removeExisting) {
114#if QT_CONFIG(tabletevent)
115 for (int i = 0; i < m_tabletData.count(); ++i) {
116 if (m_tabletData.at(i).deviceId == deviceInfo->deviceid) {
117 m_tabletData.remove(i);
118 break;
119 }
120 }
121#endif
122 m_scrollingDevices.remove(akey: deviceInfo->deviceid);
123 m_touchDevices.remove(akey: deviceInfo->deviceid);
124 }
125
126 qCDebug(lcQpaXInputDevices) << "input device " << xcb_input_xi_device_info_name(R: deviceInfo) << "ID" << deviceInfo->deviceid;
127#if QT_CONFIG(tabletevent)
128 TabletData tabletData;
129#endif
130 ScrollingDevice scrollingDevice;
131 auto classes_it = xcb_input_xi_device_info_classes_iterator(R: deviceInfo);
132 for (; classes_it.rem; xcb_input_device_class_next(i: &classes_it)) {
133 xcb_input_device_class_t *classinfo = classes_it.data;
134 switch (classinfo->type) {
135 case XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR: {
136 auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classinfo);
137 const int valuatorAtom = qatom(atom: vci->label);
138 qCDebug(lcQpaXInputDevices) << " has valuator" << atomName(atom: vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms);
139#if QT_CONFIG(tabletevent)
140 if (valuatorAtom < QXcbAtom::NAtoms) {
141 TabletData::ValuatorClassInfo info;
142 info.minVal = fixed3232ToReal(val: vci->min);
143 info.maxVal = fixed3232ToReal(val: vci->max);
144 info.number = vci->number;
145 tabletData.valuatorInfo[valuatorAtom] = info;
146 }
147#endif // QT_CONFIG(tabletevent)
148 if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
149 scrollingDevice.lastScrollPosition.setX(fixed3232ToReal(val: vci->value));
150 else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
151 scrollingDevice.lastScrollPosition.setY(fixed3232ToReal(val: vci->value));
152 break;
153 }
154 case XCB_INPUT_DEVICE_CLASS_TYPE_SCROLL: {
155 auto *sci = reinterpret_cast<xcb_input_scroll_class_t *>(classinfo);
156 if (sci->scroll_type == XCB_INPUT_SCROLL_TYPE_VERTICAL) {
157 scrollingDevice.orientations |= Qt::Vertical;
158 scrollingDevice.verticalIndex = sci->number;
159 scrollingDevice.verticalIncrement = fixed3232ToReal(val: sci->increment);
160 } else if (sci->scroll_type == XCB_INPUT_SCROLL_TYPE_HORIZONTAL) {
161 scrollingDevice.orientations |= Qt::Horizontal;
162 scrollingDevice.horizontalIndex = sci->number;
163 scrollingDevice.horizontalIncrement = fixed3232ToReal(val: sci->increment);
164 }
165 break;
166 }
167 case XCB_INPUT_DEVICE_CLASS_TYPE_BUTTON: {
168 auto *bci = reinterpret_cast<xcb_input_button_class_t *>(classinfo);
169 xcb_atom_t *labels = nullptr;
170 if (bci->num_buttons >= 5) {
171 labels = xcb_input_button_class_labels(R: bci);
172 xcb_atom_t label4 = labels[3];
173 xcb_atom_t label5 = labels[4];
174 // Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on
175 // button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons.
176 if ((!label4 || qatom(atom: label4) == QXcbAtom::ButtonWheelUp || qatom(atom: label4) == QXcbAtom::ButtonWheelDown) &&
177 (!label5 || qatom(atom: label5) == QXcbAtom::ButtonWheelUp || qatom(atom: label5) == QXcbAtom::ButtonWheelDown))
178 scrollingDevice.legacyOrientations |= Qt::Vertical;
179 }
180 if (bci->num_buttons >= 7) {
181 xcb_atom_t label6 = labels[5];
182 xcb_atom_t label7 = labels[6];
183 if ((!label6 || qatom(atom: label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(atom: label7) == QXcbAtom::ButtonHorizWheelRight))
184 scrollingDevice.legacyOrientations |= Qt::Horizontal;
185 }
186 qCDebug(lcQpaXInputDevices, " has %d buttons", bci->num_buttons);
187 break;
188 }
189 case XCB_INPUT_DEVICE_CLASS_TYPE_KEY:
190 qCDebug(lcQpaXInputDevices) << " it's a keyboard";
191 break;
192 case XCB_INPUT_DEVICE_CLASS_TYPE_TOUCH:
193 // will be handled in populateTouchDevices()
194 break;
195 default:
196 qCDebug(lcQpaXInputDevices) << " has class" << classinfo->type;
197 break;
198 }
199 }
200 bool isTablet = false;
201#if QT_CONFIG(tabletevent)
202 // If we have found the valuators which we expect a tablet to have, it might be a tablet.
203 if (tabletData.valuatorInfo.contains(akey: QXcbAtom::AbsX) &&
204 tabletData.valuatorInfo.contains(akey: QXcbAtom::AbsY) &&
205 tabletData.valuatorInfo.contains(akey: QXcbAtom::AbsPressure))
206 isTablet = true;
207
208 // But we need to be careful not to take the touch and tablet-button devices as tablets.
209 QByteArray name = QByteArray(xcb_input_xi_device_info_name(R: deviceInfo),
210 xcb_input_xi_device_info_name_length(R: deviceInfo)).toLower();
211 QString dbgType = QLatin1String("UNKNOWN");
212 if (name.contains(c: "eraser")) {
213 isTablet = true;
214 tabletData.pointerType = QTabletEvent::Eraser;
215 dbgType = QLatin1String("eraser");
216 } else if (name.contains(c: "cursor") && !(name.contains(c: "cursor controls") && name.contains(c: "trackball"))) {
217 isTablet = true;
218 tabletData.pointerType = QTabletEvent::Cursor;
219 dbgType = QLatin1String("cursor");
220 } else if (name.contains(c: "wacom") && name.contains(c: "finger touch")) {
221 isTablet = false;
222 } else if ((name.contains(c: "pen") || name.contains(c: "stylus")) && isTablet) {
223 tabletData.pointerType = QTabletEvent::Pen;
224 dbgType = QLatin1String("pen");
225 } else if (name.contains(c: "wacom") && isTablet && !name.contains(c: "touch")) {
226 // combined device (evdev) rather than separate pen/eraser (wacom driver)
227 tabletData.pointerType = QTabletEvent::Pen;
228 dbgType = QLatin1String("pen");
229 } else if (name.contains(c: "aiptek") /* && device == QXcbAtom::KEYBOARD */) {
230 // some "Genius" tablets
231 isTablet = true;
232 tabletData.pointerType = QTabletEvent::Pen;
233 dbgType = QLatin1String("pen");
234 } else if (name.contains(c: "waltop") && name.contains(c: "tablet")) {
235 // other "Genius" tablets
236 // WALTOP International Corp. Slim Tablet
237 isTablet = true;
238 tabletData.pointerType = QTabletEvent::Pen;
239 dbgType = QLatin1String("pen");
240 } else if (name.contains(c: "uc-logic") && isTablet) {
241 tabletData.pointerType = QTabletEvent::Pen;
242 dbgType = QLatin1String("pen");
243 } else if (name.contains(c: "ugee")) {
244 isTablet = true;
245 tabletData.pointerType = QTabletEvent::Pen;
246 dbgType = QLatin1String("pen");
247 } else {
248 isTablet = false;
249 }
250
251 if (isTablet) {
252 tabletData.deviceId = deviceInfo->deviceid;
253 m_tabletData.append(t: tabletData);
254 qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType;
255 }
256#endif // QT_CONFIG(tabletevent)
257
258 if (scrollingDevice.orientations || scrollingDevice.legacyOrientations) {
259 scrollingDevice.deviceId = deviceInfo->deviceid;
260 // Only use legacy wheel button events when we don't have real scroll valuators.
261 scrollingDevice.legacyOrientations &= ~scrollingDevice.orientations;
262 m_scrollingDevices.insert(akey: scrollingDevice.deviceId, avalue: scrollingDevice);
263 qCDebug(lcQpaXInputDevices) << " it's a scrolling device";
264 }
265
266 if (!isTablet) {
267 TouchDeviceData *dev = populateTouchDevices(info: deviceInfo);
268 if (dev && lcQpaXInputDevices().isDebugEnabled()) {
269 if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen)
270 qCDebug(lcQpaXInputDevices, " it's a touchscreen with type %d capabilities 0x%X max touch points %d",
271 dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(),
272 dev->qtTouchDevice->maximumTouchPoints());
273 else if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad)
274 qCDebug(lcQpaXInputDevices, " it's a touchpad with type %d capabilities 0x%X max touch points %d size %f x %f",
275 dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(),
276 dev->qtTouchDevice->maximumTouchPoints(),
277 dev->size.width(), dev->size.height());
278 }
279 }
280
281}
282
283void QXcbConnection::xi2SetupDevices()
284{
285#if QT_CONFIG(tabletevent)
286 m_tabletData.clear();
287#endif
288 m_scrollingDevices.clear();
289 m_touchDevices.clear();
290 m_xiMasterPointerIds.clear();
291
292 auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), XCB_INPUT_DEVICE_ALL);
293 if (!reply) {
294 qCDebug(lcQpaXInputDevices) << "failed to query devices";
295 return;
296 }
297
298 auto it = xcb_input_xi_query_device_infos_iterator(R: reply.get());
299 for (; it.rem; xcb_input_xi_device_info_next(i: &it)) {
300 xcb_input_xi_device_info_t *deviceInfo = it.data;
301 if (deviceInfo->type == XCB_INPUT_DEVICE_TYPE_MASTER_POINTER) {
302 m_xiMasterPointerIds.append(t: deviceInfo->deviceid);
303 continue;
304 }
305 // only slave pointer devices are relevant here
306 if (deviceInfo->type == XCB_INPUT_DEVICE_TYPE_SLAVE_POINTER)
307 xi2SetupDevice(info: deviceInfo, removeExisting: false);
308 }
309
310 if (m_xiMasterPointerIds.size() > 1)
311 qCDebug(lcQpaXInputDevices) << "multi-pointer X detected";
312}
313
314/*! \internal
315
316 Notes on QT_XCB_NO_XI2_MOUSE Handling:
317
318 Here we don't select pointer button press/release and motion events on master devices, instead
319 we select these events directly on slave devices. This means that a master device will fallback
320 to sending core events for every XI_* event that is sent directly by a slave device. For more
321 details see "Event processing for attached slave devices" in XInput2 specification. To prevent
322 handling of the same event twice, we have checks for xi2MouseEventsDisabled() in XI2 event
323 handlers (but this is somewhat inconsistent in some situations). If the purpose for
324 QT_XCB_NO_XI2_MOUSE was so that an application using QAbstractNativeEventFilter would see core
325 mouse events before they are handled by Qt then QT_XCB_NO_XI2_MOUSE won't always work as
326 expected (e.g. we handle scroll event directly from a slave device event, before an application
327 has seen the fallback core event from a master device).
328
329 The commit introducing QT_XCB_NO_XI2_MOUSE also states that setting this envvar "restores the
330 old behavior with broken grabbing". It did not elaborate why grabbing was not fixed for this
331 code path. The issue that this envvar tries to solve seem to be less important than broken
332 grabbing (broken apparently only for touch events). Thus, if you really want core mouse events
333 in your application and do not care about broken touch, then use QT_XCB_NO_XI2 (more on this
334 below) to disable the extension all together. The reason why grabbing might have not been fixed
335 is that calling XIGrabDevice with this code path for some reason always returns AlreadyGrabbed
336 (by debugging X server's code it appears that when we call XIGrabDevice, an X server first grabs
337 pointer via core pointer and then fails to do XI2 grab with AlreadyGrabbed; disclaimer - I did
338 not debug this in great detail). When we try supporting odd setups like QT_XCB_NO_XI2_MOUSE, we
339 are asking for trouble anyways.
340
341 In conclusion, introduction of QT_XCB_NO_XI2_MOUSE causes more issues than solves - the above
342 mentioned inconsistencies, maintenance of this code path and that QT_XCB_NO_XI2_MOUSE replaces
343 less important issue with somewhat more important issue. It also makes us to use less optimal
344 code paths in certain situations (see xi2HandleHierarchyEvent). Using of QT_XCB_NO_XI2 has its
345 drawbacks too - no tablet and touch events. So the only real fix in this case is at an
346 application side (teach the application about xcb_ge_event_t events). Based on this,
347 QT_XCB_NO_XI2_MOUSE will be removed in ### Qt 6. It should not have existed in the first place,
348 native events seen by QAbstractNativeEventFilter is not really a public API, applications should
349 expect changes at this level and do ifdefs if something changes between Qt version.
350*/
351void QXcbConnection::xi2SelectDeviceEventsCompatibility(xcb_window_t window)
352{
353 if (window == rootWindow())
354 return;
355
356 uint32_t mask = 0;
357
358 if (isAtLeastXI22()) {
359 mask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN;
360 mask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE;
361 mask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_END;
362
363 qt_xcb_input_event_mask_t xiMask;
364 xiMask.header.deviceid = XCB_INPUT_DEVICE_ALL_MASTER;
365 xiMask.header.mask_len = 1;
366 xiMask.mask = mask;
367
368 xcb_void_cookie_t cookie =
369 xcb_input_xi_select_events_checked(c: xcb_connection(), window, num_mask: 1, masks: &xiMask.header);
370 xcb_generic_error_t *error = xcb_request_check(c: xcb_connection(), cookie);
371 if (error) {
372 qCDebug(lcQpaXInput, "failed to select events, window %x, error code %d", window, error->error_code);
373 free(ptr: error);
374 } else {
375 QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
376 }
377 }
378
379 mask = XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS;
380 mask |= XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
381 mask |= XCB_INPUT_XI_EVENT_MASK_MOTION;
382
383#if QT_CONFIG(tabletevent)
384 QSet<int> tabletDevices;
385 if (!m_tabletData.isEmpty()) {
386 const int nrTablets = m_tabletData.count();
387 QVector<qt_xcb_input_event_mask_t> xiEventMask(nrTablets);
388 for (int i = 0; i < nrTablets; ++i) {
389 int deviceId = m_tabletData.at(i).deviceId;
390 tabletDevices.insert(value: deviceId);
391 xiEventMask[i].header.deviceid = deviceId;
392 xiEventMask[i].header.mask_len = 1;
393 xiEventMask[i].mask = mask;
394 }
395 xcb_input_xi_select_events(c: xcb_connection(), window, num_mask: nrTablets, masks: &(xiEventMask.data()->header));
396 }
397#endif
398
399 if (!m_scrollingDevices.isEmpty()) {
400 QVector<qt_xcb_input_event_mask_t> xiEventMask(m_scrollingDevices.size());
401 int i = 0;
402 for (const ScrollingDevice& scrollingDevice : qAsConst(t&: m_scrollingDevices)) {
403#if QT_CONFIG(tabletevent)
404 if (tabletDevices.contains(value: scrollingDevice.deviceId))
405 continue; // All necessary events are already captured.
406#endif
407 xiEventMask[i].header.deviceid = scrollingDevice.deviceId;
408 xiEventMask[i].header.mask_len = 1;
409 xiEventMask[i].mask = mask;
410 i++;
411 }
412 xcb_input_xi_select_events(c: xcb_connection(), window, num_mask: i, masks: &(xiEventMask.data()->header));
413 }
414}
415
416QXcbConnection::TouchDeviceData *QXcbConnection::touchDeviceForId(int id)
417{
418 TouchDeviceData *dev = nullptr;
419 if (m_touchDevices.contains(akey: id))
420 dev = &m_touchDevices[id];
421 return dev;
422}
423
424QXcbConnection::TouchDeviceData *QXcbConnection::populateTouchDevices(void *info)
425{
426 auto *deviceinfo = reinterpret_cast<xcb_input_xi_device_info_t *>(info);
427 QTouchDevice::Capabilities caps;
428 int type = -1;
429 int maxTouchPoints = 1;
430 bool isTouchDevice = false;
431 bool hasRelativeCoords = false;
432 TouchDeviceData dev;
433 auto classes_it = xcb_input_xi_device_info_classes_iterator(R: deviceinfo);
434 for (; classes_it.rem; xcb_input_device_class_next(i: &classes_it)) {
435 xcb_input_device_class_t *classinfo = classes_it.data;
436 switch (classinfo->type) {
437 case XCB_INPUT_DEVICE_CLASS_TYPE_TOUCH: {
438 auto *tci = reinterpret_cast<xcb_input_touch_class_t *>(classinfo);
439 maxTouchPoints = tci->num_touches;
440 qCDebug(lcQpaXInputDevices, " has touch class with mode %d", tci->mode);
441 switch (tci->mode) {
442 case XCB_INPUT_TOUCH_MODE_DEPENDENT:
443 type = QTouchDevice::TouchPad;
444 break;
445 case XCB_INPUT_TOUCH_MODE_DIRECT:
446 type = QTouchDevice::TouchScreen;
447 break;
448 }
449 break;
450 }
451 case XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR: {
452 auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classinfo);
453 const QXcbAtom::Atom valuatorAtom = qatom(atom: vci->label);
454 if (valuatorAtom < QXcbAtom::NAtoms) {
455 TouchDeviceData::ValuatorClassInfo info;
456 info.min = fixed3232ToReal(val: vci->min);
457 info.max = fixed3232ToReal(val: vci->max);
458 info.number = vci->number;
459 info.label = valuatorAtom;
460 dev.valuatorInfo.append(t: info);
461 }
462 // Some devices (mice) report a resolution of 0; they will be excluded later,
463 // for now just prevent a division by zero
464 const int vciResolution = vci->resolution ? vci->resolution : 1;
465 if (valuatorAtom == QXcbAtom::AbsMTPositionX)
466 caps |= QTouchDevice::Position | QTouchDevice::NormalizedPosition;
467 else if (valuatorAtom == QXcbAtom::AbsMTTouchMajor)
468 caps |= QTouchDevice::Area;
469 else if (valuatorAtom == QXcbAtom::AbsMTOrientation)
470 dev.providesTouchOrientation = true;
471 else if (valuatorAtom == QXcbAtom::AbsMTPressure || valuatorAtom == QXcbAtom::AbsPressure)
472 caps |= QTouchDevice::Pressure;
473 else if (valuatorAtom == QXcbAtom::RelX) {
474 hasRelativeCoords = true;
475 dev.size.setWidth((fixed3232ToReal(val: vci->max) - fixed3232ToReal(val: vci->min)) * 1000.0 / vciResolution);
476 } else if (valuatorAtom == QXcbAtom::RelY) {
477 hasRelativeCoords = true;
478 dev.size.setHeight((fixed3232ToReal(val: vci->max) - fixed3232ToReal(val: vci->min)) * 1000.0 / vciResolution);
479 } else if (valuatorAtom == QXcbAtom::AbsX) {
480 caps |= QTouchDevice::Position;
481 dev.size.setWidth((fixed3232ToReal(val: vci->max) - fixed3232ToReal(val: vci->min)) * 1000.0 / vciResolution);
482 } else if (valuatorAtom == QXcbAtom::AbsY) {
483 caps |= QTouchDevice::Position;
484 dev.size.setHeight((fixed3232ToReal(val: vci->max) - fixed3232ToReal(val: vci->min)) * 1000.0 / vciResolution);
485 }
486 break;
487 }
488 default:
489 break;
490 }
491 }
492 if (type < 0 && caps && hasRelativeCoords) {
493 type = QTouchDevice::TouchPad;
494 if (dev.size.width() < 10 || dev.size.height() < 10 ||
495 dev.size.width() > 10000 || dev.size.height() > 10000)
496 dev.size = QSizeF(130, 110);
497 }
498 if (!isAtLeastXI22() || type == QTouchDevice::TouchPad)
499 caps |= QTouchDevice::MouseEmulation;
500
501 if (type >= QTouchDevice::TouchScreen && type <= QTouchDevice::TouchPad) {
502 dev.qtTouchDevice = new QTouchDevice;
503 dev.qtTouchDevice->setName(QString::fromUtf8(str: xcb_input_xi_device_info_name(R: deviceinfo),
504 size: xcb_input_xi_device_info_name_length(R: deviceinfo)));
505 dev.qtTouchDevice->setType((QTouchDevice::DeviceType)type);
506 dev.qtTouchDevice->setCapabilities(caps);
507 dev.qtTouchDevice->setMaximumTouchPoints(maxTouchPoints);
508 if (caps != 0)
509 QWindowSystemInterface::registerTouchDevice(device: dev.qtTouchDevice);
510 m_touchDevices[deviceinfo->deviceid] = dev;
511 isTouchDevice = true;
512 }
513
514 return isTouchDevice ? &m_touchDevices[deviceinfo->deviceid] : nullptr;
515}
516
517static inline qreal fixed1616ToReal(xcb_input_fp1616_t val)
518{
519 return qreal(val) / 0x10000;
520}
521
522void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
523{
524 auto *xiEvent = reinterpret_cast<qt_xcb_input_device_event_t *>(event);
525 int sourceDeviceId = xiEvent->deviceid; // may be the master id
526 qt_xcb_input_device_event_t *xiDeviceEvent = nullptr;
527 xcb_input_enter_event_t *xiEnterEvent = nullptr;
528 QXcbWindowEventListener *eventListener = nullptr;
529
530 switch (xiEvent->event_type) {
531 case XCB_INPUT_BUTTON_PRESS:
532 case XCB_INPUT_BUTTON_RELEASE:
533 case XCB_INPUT_MOTION:
534 case XCB_INPUT_TOUCH_BEGIN:
535 case XCB_INPUT_TOUCH_UPDATE:
536 case XCB_INPUT_TOUCH_END:
537 {
538 xiDeviceEvent = xiEvent;
539 eventListener = windowEventListenerFromId(id: xiDeviceEvent->event);
540 sourceDeviceId = xiDeviceEvent->sourceid; // use the actual device id instead of the master
541 break;
542 }
543 case XCB_INPUT_ENTER:
544 case XCB_INPUT_LEAVE: {
545 xiEnterEvent = reinterpret_cast<xcb_input_enter_event_t *>(event);
546 eventListener = windowEventListenerFromId(id: xiEnterEvent->event);
547 sourceDeviceId = xiEnterEvent->sourceid; // use the actual device id instead of the master
548 break;
549 }
550 case XCB_INPUT_HIERARCHY:
551 xi2HandleHierarchyEvent(event);
552 return;
553 case XCB_INPUT_DEVICE_CHANGED:
554 xi2HandleDeviceChangedEvent(event);
555 return;
556 default:
557 break;
558 }
559
560 if (eventListener) {
561 if (eventListener->handleNativeEvent(reinterpret_cast<xcb_generic_event_t *>(event)))
562 return;
563 }
564
565#if QT_CONFIG(tabletevent)
566 if (!xiEnterEvent) {
567 QXcbConnection::TabletData *tablet = tabletDataForDevice(id: sourceDeviceId);
568 if (tablet && xi2HandleTabletEvent(event, tabletData: tablet))
569 return;
570 }
571#endif // QT_CONFIG(tabletevent)
572
573 if (ScrollingDevice *device = scrollingDeviceForId(id: sourceDeviceId))
574 xi2HandleScrollEvent(event, scrollingDevice&: *device);
575
576 if (xiDeviceEvent) {
577 switch (xiDeviceEvent->event_type) {
578 case XCB_INPUT_BUTTON_PRESS:
579 case XCB_INPUT_BUTTON_RELEASE:
580 case XCB_INPUT_MOTION:
581 if (!xi2MouseEventsDisabled() && eventListener &&
582 !(xiDeviceEvent->flags & XCB_INPUT_POINTER_EVENT_FLAGS_POINTER_EMULATED))
583 eventListener->handleXIMouseEvent(event);
584 break;
585
586 case XCB_INPUT_TOUCH_BEGIN:
587 case XCB_INPUT_TOUCH_UPDATE:
588 case XCB_INPUT_TOUCH_END:
589 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
590 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",
591 event->event_type, xiDeviceEvent->sequence, xiDeviceEvent->detail,
592 fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y),
593 fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y),xiDeviceEvent->event);
594 if (QXcbWindow *platformWindow = platformWindowFromId(id: xiDeviceEvent->event))
595 xi2ProcessTouch(xiDevEvent: xiDeviceEvent, platformWindow);
596 break;
597 }
598 } else if (xiEnterEvent && !xi2MouseEventsDisabled() && eventListener) {
599 switch (xiEnterEvent->event_type) {
600 case XCB_INPUT_ENTER:
601 case XCB_INPUT_LEAVE:
602 eventListener->handleXIEnterLeave(event);
603 break;
604 }
605 }
606}
607
608bool QXcbConnection::xi2MouseEventsDisabled() const
609{
610 static bool xi2MouseDisabled = qEnvironmentVariableIsSet(varName: "QT_XCB_NO_XI2_MOUSE");
611 // FIXME: Don't use XInput2 mouse events when Xinerama extension
612 // is enabled, because it causes problems with multi-monitor setup.
613 return xi2MouseDisabled || hasXinerama();
614}
615
616bool QXcbConnection::isTouchScreen(int id)
617{
618 auto device = touchDeviceForId(id);
619 return device && device->qtTouchDevice->type() == QTouchDevice::TouchScreen;
620}
621
622void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindow)
623{
624 auto *xiDeviceEvent = reinterpret_cast<xcb_input_touch_begin_event_t *>(xiDevEvent);
625 TouchDeviceData *dev = touchDeviceForId(id: xiDeviceEvent->sourceid);
626 Q_ASSERT(dev);
627 const bool firstTouch = dev->touchPoints.isEmpty();
628 if (xiDeviceEvent->event_type == XCB_INPUT_TOUCH_BEGIN) {
629 QWindowSystemInterface::TouchPoint tp;
630 tp.id = xiDeviceEvent->detail % INT_MAX;
631 tp.state = Qt::TouchPointPressed;
632 tp.pressure = -1.0;
633 dev->touchPoints[tp.id] = tp;
634 }
635 QWindowSystemInterface::TouchPoint &touchPoint = dev->touchPoints[xiDeviceEvent->detail];
636 QXcbScreen* screen = platformWindow->xcbScreen();
637 qreal x = fixed1616ToReal(val: xiDeviceEvent->root_x);
638 qreal y = fixed1616ToReal(val: xiDeviceEvent->root_y);
639 qreal nx = -1.0, ny = -1.0;
640 qreal w = 0.0, h = 0.0;
641 bool majorAxisIsY = touchPoint.area.height() > touchPoint.area.width();
642 for (const TouchDeviceData::ValuatorClassInfo &vci : qAsConst(t&: dev->valuatorInfo)) {
643 double value;
644 if (!xi2GetValuatorValueIfSet(event: xiDeviceEvent, valuatorNum: vci.number, value: &value))
645 continue;
646 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
647 qCDebug(lcQpaXInputEvents, " valuator %20s value %lf from range %lf -> %lf",
648 atomName(atom(vci.label)).constData(), value, vci.min, vci.max);
649 if (value > vci.max)
650 value = vci.max;
651 if (value < vci.min)
652 value = vci.min;
653 qreal valuatorNormalized = (value - vci.min) / (vci.max - vci.min);
654 if (vci.label == QXcbAtom::RelX) {
655 nx = valuatorNormalized;
656 } else if (vci.label == QXcbAtom::RelY) {
657 ny = valuatorNormalized;
658 } else if (vci.label == QXcbAtom::AbsX) {
659 nx = valuatorNormalized;
660 } else if (vci.label == QXcbAtom::AbsY) {
661 ny = valuatorNormalized;
662 } else if (vci.label == QXcbAtom::AbsMTPositionX) {
663 nx = valuatorNormalized;
664 } else if (vci.label == QXcbAtom::AbsMTPositionY) {
665 ny = valuatorNormalized;
666 } else if (vci.label == QXcbAtom::AbsMTTouchMajor) {
667 const qreal sw = screen->geometry().width();
668 const qreal sh = screen->geometry().height();
669 w = valuatorNormalized * std::sqrt(x: sw * sw + sh * sh);
670 } else if (vci.label == QXcbAtom::AbsMTTouchMinor) {
671 const qreal sw = screen->geometry().width();
672 const qreal sh = screen->geometry().height();
673 h = valuatorNormalized * std::sqrt(x: sw * sw + sh * sh);
674 } else if (vci.label == QXcbAtom::AbsMTOrientation) {
675 // Find the closest axis.
676 // 0 corresponds to the Y axis, vci.max to the X axis.
677 // Flipping over the Y axis and rotating by 180 degrees
678 // don't change the result, so normalize value to range
679 // [0, vci.max] first.
680 value = qAbs(t: value);
681 while (value > vci.max)
682 value -= 2 * vci.max;
683 value = qAbs(t: value);
684 majorAxisIsY = value < vci.max - value;
685 } else if (vci.label == QXcbAtom::AbsMTPressure || vci.label == QXcbAtom::AbsPressure) {
686 touchPoint.pressure = valuatorNormalized;
687 }
688
689 }
690 // If any value was not updated, use the last-known value.
691 if (nx == -1.0) {
692 x = touchPoint.area.center().x();
693 nx = x / screen->geometry().width();
694 }
695 if (ny == -1.0) {
696 y = touchPoint.area.center().y();
697 ny = y / screen->geometry().height();
698 }
699 if (xiDeviceEvent->event_type != XCB_INPUT_TOUCH_END) {
700 if (!dev->providesTouchOrientation) {
701 if (w == 0.0)
702 w = touchPoint.area.width();
703 h = w;
704 } else {
705 if (w == 0.0)
706 w = qMax(a: touchPoint.area.width(), b: touchPoint.area.height());
707 if (h == 0.0)
708 h = qMin(a: touchPoint.area.width(), b: touchPoint.area.height());
709 if (majorAxisIsY)
710 qSwap(value1&: w, value2&: h);
711 }
712 }
713
714 switch (xiDeviceEvent->event_type) {
715 case XCB_INPUT_TOUCH_BEGIN:
716 if (firstTouch) {
717 dev->firstPressedPosition = QPointF(x, y);
718 dev->firstPressedNormalPosition = QPointF(nx, ny);
719 }
720 dev->pointPressedPosition.insert(akey: touchPoint.id, avalue: QPointF(x, y));
721
722 // Touches must be accepted when we are grabbing touch events. Otherwise the entire sequence
723 // will get replayed when the grab ends.
724 if (m_xiGrab) {
725 xcb_input_xi_allow_events(c: xcb_connection(), XCB_CURRENT_TIME, deviceid: xiDeviceEvent->deviceid,
726 event_mode: XCB_INPUT_EVENT_MODE_ACCEPT_TOUCH,
727 touchid: xiDeviceEvent->detail, grab_window: xiDeviceEvent->event);
728 }
729 break;
730 case XCB_INPUT_TOUCH_UPDATE:
731 if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad && dev->pointPressedPosition.value(akey: touchPoint.id) == QPointF(x, y)) {
732 qreal dx = (nx - dev->firstPressedNormalPosition.x()) *
733 dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
734 qreal dy = (ny - dev->firstPressedNormalPosition.y()) *
735 dev->size.height() * screen->geometry().height() / screen->physicalSize().height();
736 x = dev->firstPressedPosition.x() + dx;
737 y = dev->firstPressedPosition.y() + dy;
738 touchPoint.state = Qt::TouchPointMoved;
739 } else if (touchPoint.area.center() != QPoint(x, y)) {
740 touchPoint.state = Qt::TouchPointMoved;
741 if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad)
742 dev->pointPressedPosition[touchPoint.id] = QPointF(x, y);
743 }
744
745 if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen &&
746 xiDeviceEvent->event == m_startSystemMoveResizeInfo.window &&
747 xiDeviceEvent->sourceid == m_startSystemMoveResizeInfo.deviceid &&
748 xiDeviceEvent->detail == m_startSystemMoveResizeInfo.pointid) {
749 QXcbWindow *window = platformWindowFromId(id: m_startSystemMoveResizeInfo.window);
750 if (window) {
751 xcb_input_xi_allow_events(c: xcb_connection(), XCB_CURRENT_TIME, deviceid: xiDeviceEvent->deviceid,
752 event_mode: XCB_INPUT_EVENT_MODE_REJECT_TOUCH,
753 touchid: xiDeviceEvent->detail, grab_window: xiDeviceEvent->event);
754 window->doStartSystemMoveResize(globalPos: QPoint(x, y), edges: m_startSystemMoveResizeInfo.edges);
755 m_startSystemMoveResizeInfo.window = XCB_NONE;
756 }
757 }
758 break;
759 case XCB_INPUT_TOUCH_END:
760 touchPoint.state = Qt::TouchPointReleased;
761 if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad && dev->pointPressedPosition.value(akey: touchPoint.id) == QPointF(x, y)) {
762 qreal dx = (nx - dev->firstPressedNormalPosition.x()) *
763 dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
764 qreal dy = (ny - dev->firstPressedNormalPosition.y()) *
765 dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
766 x = dev->firstPressedPosition.x() + dx;
767 y = dev->firstPressedPosition.y() + dy;
768 }
769 dev->pointPressedPosition.remove(akey: touchPoint.id);
770 }
771 touchPoint.area = QRectF(x - w/2, y - h/2, w, h);
772 touchPoint.normalPosition = QPointF(nx, ny);
773
774 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
775 qCDebug(lcQpaXInputEvents) << " touchpoint " << touchPoint.id << " state " << touchPoint.state << " pos norm " << touchPoint.normalPosition <<
776 " area " << touchPoint.area << " pressure " << touchPoint.pressure;
777 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(s: xiDeviceEvent->mods.effective);
778 QWindowSystemInterface::handleTouchEvent(window: platformWindow->window(), timestamp: xiDeviceEvent->time, device: dev->qtTouchDevice, points: dev->touchPoints.values(), mods: modifiers);
779 if (touchPoint.state == Qt::TouchPointReleased)
780 // If a touchpoint was released, we can forget it, because the ID won't be reused.
781 dev->touchPoints.remove(akey: touchPoint.id);
782 else
783 // Make sure that we don't send TouchPointPressed/Moved in more than one QTouchEvent
784 // with this touch point if the next XI2 event is about a different touch point.
785 touchPoint.state = Qt::TouchPointStationary;
786}
787
788bool QXcbConnection::startSystemMoveResizeForTouch(xcb_window_t window, int edges)
789{
790 QHash<int, TouchDeviceData>::const_iterator devIt = m_touchDevices.constBegin();
791 for (; devIt != m_touchDevices.constEnd(); ++devIt) {
792 TouchDeviceData deviceData = devIt.value();
793 if (deviceData.qtTouchDevice->type() == QTouchDevice::TouchScreen) {
794 auto pointIt = deviceData.touchPoints.constBegin();
795 for (; pointIt != deviceData.touchPoints.constEnd(); ++pointIt) {
796 Qt::TouchPointState state = pointIt.value().state;
797 if (state == Qt::TouchPointMoved || state == Qt::TouchPointPressed || state == Qt::TouchPointStationary) {
798 m_startSystemMoveResizeInfo.window = window;
799 m_startSystemMoveResizeInfo.deviceid = devIt.key();
800 m_startSystemMoveResizeInfo.pointid = pointIt.key();
801 m_startSystemMoveResizeInfo.edges = edges;
802 return true;
803 }
804 }
805 }
806 }
807 return false;
808}
809
810void QXcbConnection::abortSystemMoveResizeForTouch()
811{
812 m_startSystemMoveResizeInfo.window = XCB_NONE;
813}
814
815bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab)
816{
817 bool ok = false;
818
819 if (grab) { // grab
820 uint32_t mask = XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS
821 | XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE
822 | XCB_INPUT_XI_EVENT_MASK_MOTION
823 | XCB_INPUT_XI_EVENT_MASK_ENTER
824 | XCB_INPUT_XI_EVENT_MASK_LEAVE
825 | XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN
826 | XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE
827 | XCB_INPUT_XI_EVENT_MASK_TOUCH_END;
828
829 for (int id : qAsConst(t&: m_xiMasterPointerIds)) {
830 xcb_generic_error_t *error = nullptr;
831 auto cookie = xcb_input_xi_grab_device(c: xcb_connection(), window: w, XCB_CURRENT_TIME, cursor: XCB_CURSOR_NONE, deviceid: id,
832 mode: XCB_INPUT_GRAB_MODE_22_ASYNC, paired_device_mode: XCB_INPUT_GRAB_MODE_22_ASYNC,
833 owner_events: false, mask_len: 1, mask: &mask);
834 auto *reply = xcb_input_xi_grab_device_reply(c: xcb_connection(), cookie, e: &error);
835 if (error) {
836 qCDebug(lcQpaXInput, "failed to grab events for device %d on window %x"
837 "(error code %d)", id, w, error->error_code);
838 free(ptr: error);
839 } else {
840 // Managed to grab at least one of master pointers, that should be enough
841 // to properly dismiss windows that rely on mouse grabbing.
842 ok = true;
843 }
844 free(ptr: reply);
845 }
846 } else { // ungrab
847 for (int id : qAsConst(t&: m_xiMasterPointerIds)) {
848 auto cookie = xcb_input_xi_ungrab_device_checked(c: xcb_connection(), XCB_CURRENT_TIME, deviceid: id);
849 xcb_generic_error_t *error = xcb_request_check(c: xcb_connection(), cookie);
850 if (error) {
851 qCDebug(lcQpaXInput, "XIUngrabDevice failed - id: %d (error code %d)", id, error->error_code);
852 free(ptr: error);
853 }
854 }
855 // XIUngrabDevice does not seem to wait for a reply from X server (similar to
856 // xcb_ungrab_pointer). Ungrabbing won't fail, unless NoSuchExtension error
857 // has occurred due to a programming error somewhere else in the stack. That
858 // would mean that things will crash soon anyway.
859 ok = true;
860 }
861
862 if (ok)
863 m_xiGrab = grab;
864
865 return ok;
866}
867
868void QXcbConnection::xi2HandleHierarchyEvent(void *event)
869{
870 auto *xiEvent = reinterpret_cast<xcb_input_hierarchy_event_t *>(event);
871 // We only care about hotplugged devices
872 if (!(xiEvent->flags & (XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED | XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED)))
873 return;
874
875 xi2SetupDevices();
876
877 if (xi2MouseEventsDisabled()) {
878 // In compatibility mode (a.k.a xi2MouseEventsDisabled() mode) we select events for
879 // each device separately. When a new device appears, we have to select events from
880 // this device on all event-listening windows. This is not needed when events are
881 // selected via XIAllDevices/XIAllMasterDevices (as in xi2SelectDeviceEvents()).
882 for (auto it = m_mapper.cbegin(), end = m_mapper.cend(); it != end; ++it)
883 xi2SelectDeviceEventsCompatibility(window: it.key());
884 }
885}
886
887void QXcbConnection::xi2HandleDeviceChangedEvent(void *event)
888{
889 auto *xiEvent = reinterpret_cast<xcb_input_device_changed_event_t *>(event);
890 switch (xiEvent->reason) {
891 case XCB_INPUT_CHANGE_REASON_DEVICE_CHANGE: {
892 auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), xiEvent->sourceid);
893 if (!reply || reply->num_infos <= 0)
894 return;
895 auto it = xcb_input_xi_query_device_infos_iterator(R: reply.get());
896 xi2SetupDevice(info: it.data);
897 break;
898 }
899 case XCB_INPUT_CHANGE_REASON_SLAVE_SWITCH: {
900 if (ScrollingDevice *scrollingDevice = scrollingDeviceForId(id: xiEvent->sourceid))
901 xi2UpdateScrollingDevice(scrollingDevice&: *scrollingDevice);
902 break;
903 }
904 default:
905 qCDebug(lcQpaXInputEvents, "unknown device-changed-event (device %d)", xiEvent->sourceid);
906 break;
907 }
908}
909
910void QXcbConnection::xi2UpdateScrollingDevice(ScrollingDevice &scrollingDevice)
911{
912 auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), scrollingDevice.deviceId);
913 if (!reply || reply->num_infos <= 0) {
914 qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", scrollingDevice.deviceId);
915 return;
916 }
917 QPointF lastScrollPosition;
918 if (lcQpaXInputEvents().isDebugEnabled())
919 lastScrollPosition = scrollingDevice.lastScrollPosition;
920
921 xcb_input_xi_device_info_t *deviceInfo = xcb_input_xi_query_device_infos_iterator(R: reply.get()).data;
922 auto classes_it = xcb_input_xi_device_info_classes_iterator(R: deviceInfo);
923 for (; classes_it.rem; xcb_input_device_class_next(i: &classes_it)) {
924 xcb_input_device_class_t *classInfo = classes_it.data;
925 if (classInfo->type == XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR) {
926 auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classInfo);
927 const int valuatorAtom = qatom(atom: vci->label);
928 if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
929 scrollingDevice.lastScrollPosition.setX(fixed3232ToReal(val: vci->value));
930 else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
931 scrollingDevice.lastScrollPosition.setY(fixed3232ToReal(val: vci->value));
932 }
933 }
934 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled() && lastScrollPosition != scrollingDevice.lastScrollPosition))
935 qCDebug(lcQpaXInputEvents, "scrolling device %d moved from (%f, %f) to (%f, %f)", scrollingDevice.deviceId,
936 lastScrollPosition.x(), lastScrollPosition.y(),
937 scrollingDevice.lastScrollPosition.x(),
938 scrollingDevice.lastScrollPosition.y());
939}
940
941void QXcbConnection::xi2UpdateScrollingDevices()
942{
943 QHash<int, ScrollingDevice>::iterator it = m_scrollingDevices.begin();
944 const QHash<int, ScrollingDevice>::iterator end = m_scrollingDevices.end();
945 while (it != end) {
946 xi2UpdateScrollingDevice(scrollingDevice&: it.value());
947 ++it;
948 }
949}
950
951QXcbConnection::ScrollingDevice *QXcbConnection::scrollingDeviceForId(int id)
952{
953 ScrollingDevice *dev = nullptr;
954 if (m_scrollingDevices.contains(akey: id))
955 dev = &m_scrollingDevices[id];
956 return dev;
957}
958
959void QXcbConnection::xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice)
960{
961 auto *xiDeviceEvent = reinterpret_cast<qt_xcb_input_device_event_t *>(event);
962
963 if (xiDeviceEvent->event_type == XCB_INPUT_MOTION && scrollingDevice.orientations) {
964 if (QXcbWindow *platformWindow = platformWindowFromId(id: xiDeviceEvent->event)) {
965 QPoint rawDelta;
966 QPoint angleDelta;
967 double value;
968 if (scrollingDevice.orientations & Qt::Vertical) {
969 if (xi2GetValuatorValueIfSet(event: xiDeviceEvent, valuatorNum: scrollingDevice.verticalIndex, value: &value)) {
970 double delta = scrollingDevice.lastScrollPosition.y() - value;
971 scrollingDevice.lastScrollPosition.setY(value);
972 angleDelta.setY((delta / scrollingDevice.verticalIncrement) * 120);
973 // With most drivers the increment is 1 for wheels.
974 // For libinput it is hardcoded to a useless 15.
975 // For a proper touchpad driver it should be in the same order of magnitude as 120
976 if (scrollingDevice.verticalIncrement > 15)
977 rawDelta.setY(delta);
978 else if (scrollingDevice.verticalIncrement < -15)
979 rawDelta.setY(-delta);
980 }
981 }
982 if (scrollingDevice.orientations & Qt::Horizontal) {
983 if (xi2GetValuatorValueIfSet(event: xiDeviceEvent, valuatorNum: scrollingDevice.horizontalIndex, value: &value)) {
984 double delta = scrollingDevice.lastScrollPosition.x() - value;
985 scrollingDevice.lastScrollPosition.setX(value);
986 angleDelta.setX((delta / scrollingDevice.horizontalIncrement) * 120);
987 // See comment under vertical
988 if (scrollingDevice.horizontalIncrement > 15)
989 rawDelta.setX(delta);
990 else if (scrollingDevice.horizontalIncrement < -15)
991 rawDelta.setX(-delta);
992 }
993 }
994 if (!angleDelta.isNull()) {
995 QPoint local(fixed1616ToReal(val: xiDeviceEvent->event_x), fixed1616ToReal(val: xiDeviceEvent->event_y));
996 QPoint global(fixed1616ToReal(val: xiDeviceEvent->root_x), fixed1616ToReal(val: xiDeviceEvent->root_y));
997 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(s: xiDeviceEvent->mods.effective);
998 if (modifiers & Qt::AltModifier) {
999 angleDelta = angleDelta.transposed();
1000 rawDelta = rawDelta.transposed();
1001 }
1002 qCDebug(lcQpaXInputEvents) << "scroll wheel @ window pos" << local << "delta px" << rawDelta << "angle" << angleDelta;
1003 QWindowSystemInterface::handleWheelEvent(window: platformWindow->window(), timestamp: xiDeviceEvent->time, local, global, pixelDelta: rawDelta, angleDelta, mods: modifiers);
1004 }
1005 }
1006 } else if (xiDeviceEvent->event_type == XCB_INPUT_BUTTON_RELEASE && scrollingDevice.legacyOrientations) {
1007 if (QXcbWindow *platformWindow = platformWindowFromId(id: xiDeviceEvent->event)) {
1008 QPoint angleDelta;
1009 if (scrollingDevice.legacyOrientations & Qt::Vertical) {
1010 if (xiDeviceEvent->detail == 4)
1011 angleDelta.setY(120);
1012 else if (xiDeviceEvent->detail == 5)
1013 angleDelta.setY(-120);
1014 }
1015 if (scrollingDevice.legacyOrientations & Qt::Horizontal) {
1016 if (xiDeviceEvent->detail == 6)
1017 angleDelta.setX(120);
1018 else if (xiDeviceEvent->detail == 7)
1019 angleDelta.setX(-120);
1020 }
1021 if (!angleDelta.isNull()) {
1022 QPoint local(fixed1616ToReal(val: xiDeviceEvent->event_x), fixed1616ToReal(val: xiDeviceEvent->event_y));
1023 QPoint global(fixed1616ToReal(val: xiDeviceEvent->root_x), fixed1616ToReal(val: xiDeviceEvent->root_y));
1024 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(s: xiDeviceEvent->mods.effective);
1025 if (modifiers & Qt::AltModifier)
1026 angleDelta = angleDelta.transposed();
1027 qCDebug(lcQpaXInputEvents) << "scroll wheel (button" << xiDeviceEvent->detail << ") @ window pos" << local << "delta angle" << angleDelta;
1028 QWindowSystemInterface::handleWheelEvent(window: platformWindow->window(), timestamp: xiDeviceEvent->time, local, global, pixelDelta: QPoint(), angleDelta, mods: modifiers);
1029 }
1030 }
1031 }
1032}
1033
1034static int xi2ValuatorOffset(const unsigned char *maskPtr, int maskLen, int number)
1035{
1036 int offset = 0;
1037 for (int i = 0; i < maskLen; i++) {
1038 if (number < 8) {
1039 if ((maskPtr[i] & (1 << number)) == 0)
1040 return -1;
1041 }
1042 for (int j = 0; j < 8; j++) {
1043 if (j == number)
1044 return offset;
1045 if (maskPtr[i] & (1 << j))
1046 offset++;
1047 }
1048 number -= 8;
1049 }
1050 return -1;
1051}
1052
1053bool QXcbConnection::xi2GetValuatorValueIfSet(const void *event, int valuatorNum, double *value)
1054{
1055 auto *xideviceevent = static_cast<const qt_xcb_input_device_event_t *>(event);
1056 auto *buttonsMaskAddr = reinterpret_cast<const unsigned char *>(&xideviceevent[1]);
1057 auto *valuatorsMaskAddr = buttonsMaskAddr + xideviceevent->buttons_len * 4;
1058 auto *valuatorsValuesAddr = reinterpret_cast<const xcb_input_fp3232_t *>(valuatorsMaskAddr + xideviceevent->valuators_len * 4);
1059
1060 int valuatorOffset = xi2ValuatorOffset(maskPtr: valuatorsMaskAddr, maskLen: xideviceevent->valuators_len, number: valuatorNum);
1061 if (valuatorOffset < 0)
1062 return false;
1063
1064 *value = valuatorsValuesAddr[valuatorOffset].integral;
1065 *value += ((double)valuatorsValuesAddr[valuatorOffset].frac / (1 << 16) / (1 << 16));
1066 return true;
1067}
1068
1069Qt::MouseButton QXcbConnection::xiToQtMouseButton(uint32_t b)
1070{
1071 switch (b) {
1072 case 1: return Qt::LeftButton;
1073 case 2: return Qt::MiddleButton;
1074 case 3: return Qt::RightButton;
1075 // 4-7 are for scrolling
1076 default: break;
1077 }
1078 if (b >= 8 && b <= Qt::MaxMouseButton)
1079 return static_cast<Qt::MouseButton>(Qt::BackButton << (b - 8));
1080 return Qt::NoButton;
1081}
1082
1083#if QT_CONFIG(tabletevent)
1084static QTabletEvent::TabletDevice toolIdToTabletDevice(quint32 toolId) {
1085 // keep in sync with wacom_intuos_inout() in Linux kernel driver wacom_wac.c
1086 switch (toolId) {
1087 case 0xd12:
1088 case 0x912:
1089 case 0x112:
1090 case 0x913: /* Intuos3 Airbrush */
1091 case 0x91b: /* Intuos3 Airbrush Eraser */
1092 case 0x902: /* Intuos4/5 13HD/24HD Airbrush */
1093 case 0x90a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
1094 case 0x100902: /* Intuos4/5 13HD/24HD Airbrush */
1095 case 0x10090a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
1096 return QTabletEvent::Airbrush;
1097 case 0x007: /* Mouse 4D and 2D */
1098 case 0x09c:
1099 case 0x094:
1100 return QTabletEvent::FourDMouse;
1101 case 0x017: /* Intuos3 2D Mouse */
1102 case 0x806: /* Intuos4 Mouse */
1103 case 0x096: /* Lens cursor */
1104 case 0x097: /* Intuos3 Lens cursor */
1105 case 0x006: /* Intuos4 Lens cursor */
1106 return QTabletEvent::Puck;
1107 case 0x885: /* Intuos3 Art Pen (Marker Pen) */
1108 case 0x100804: /* Intuos4/5 13HD/24HD Art Pen */
1109 case 0x10080c: /* Intuos4/5 13HD/24HD Art Pen Eraser */
1110 return QTabletEvent::RotationStylus;
1111 case 0:
1112 return QTabletEvent::NoDevice;
1113 }
1114 return QTabletEvent::Stylus; // Safe default assumption if nonzero
1115}
1116
1117static const char *toolName(QTabletEvent::TabletDevice tool) {
1118 static const QMetaObject *metaObject = qt_getEnumMetaObject(tool);
1119 static const QMetaEnum me = metaObject->enumerator(index: metaObject->indexOfEnumerator(name: qt_getEnumName(tool)));
1120 return me.valueToKey(value: tool);
1121}
1122
1123static const char *pointerTypeName(QTabletEvent::PointerType ptype) {
1124 static const QMetaObject *metaObject = qt_getEnumMetaObject(ptype);
1125 static const QMetaEnum me = metaObject->enumerator(index: metaObject->indexOfEnumerator(name: qt_getEnumName(ptype)));
1126 return me.valueToKey(value: ptype);
1127}
1128
1129bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletData)
1130{
1131 bool handled = true;
1132 const auto *xiDeviceEvent = reinterpret_cast<const qt_xcb_input_device_event_t *>(event);
1133
1134 switch (xiDeviceEvent->event_type) {
1135 case XCB_INPUT_BUTTON_PRESS: {
1136 Qt::MouseButton b = xiToQtMouseButton(b: xiDeviceEvent->detail);
1137 tabletData->buttons |= b;
1138 xi2ReportTabletEvent(event, tabletData);
1139 break;
1140 }
1141 case XCB_INPUT_BUTTON_RELEASE: {
1142 Qt::MouseButton b = xiToQtMouseButton(b: xiDeviceEvent->detail);
1143 tabletData->buttons ^= b;
1144 xi2ReportTabletEvent(event, tabletData);
1145 break;
1146 }
1147 case XCB_INPUT_MOTION:
1148 xi2ReportTabletEvent(event, tabletData);
1149 break;
1150 case XCB_INPUT_PROPERTY: {
1151 // This is the wacom driver's way of reporting tool proximity.
1152 // The evdev driver doesn't do it this way.
1153 const auto *ev = reinterpret_cast<const xcb_input_property_event_t *>(event);
1154 if (ev->what == XCB_INPUT_PROPERTY_FLAG_MODIFIED) {
1155 if (ev->property == atom(qatom: QXcbAtom::WacomSerialIDs)) {
1156 enum WacomSerialIndex {
1157 _WACSER_USB_ID = 0,
1158 _WACSER_LAST_TOOL_SERIAL,
1159 _WACSER_LAST_TOOL_ID,
1160 _WACSER_TOOL_SERIAL,
1161 _WACSER_TOOL_ID,
1162 _WACSER_COUNT
1163 };
1164
1165 auto reply = Q_XCB_REPLY(xcb_input_xi_get_property, xcb_connection(), tabletData->deviceId, 0,
1166 ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, 100);
1167 if (reply) {
1168 if (reply->type == atom(qatom: QXcbAtom::INTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) {
1169 quint32 *ptr = reinterpret_cast<quint32 *>(xcb_input_xi_get_property_items(R: reply.get()));
1170 quint32 tool = ptr[_WACSER_TOOL_ID];
1171 // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
1172 // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
1173 if (!tool && ptr[_WACSER_TOOL_SERIAL])
1174 tool = ptr[_WACSER_TOOL_SERIAL];
1175
1176 // The property change event informs us which tool is in proximity or which one left proximity.
1177 if (tool) {
1178 tabletData->inProximity = true;
1179 tabletData->tool = toolIdToTabletDevice(toolId: tool);
1180 tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_TOOL_SERIAL]);
1181 QWindowSystemInterface::handleTabletEnterProximityEvent(timestamp: ev->time,
1182 device: tabletData->tool, pointerType: tabletData->pointerType, uid: tabletData->serialId);
1183 } else {
1184 tabletData->inProximity = false;
1185 tabletData->tool = toolIdToTabletDevice(toolId: ptr[_WACSER_LAST_TOOL_ID]);
1186 // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
1187 // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
1188 if (!tabletData->tool)
1189 tabletData->tool = toolIdToTabletDevice(toolId: ptr[_WACSER_LAST_TOOL_SERIAL]);
1190 tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_LAST_TOOL_SERIAL]);
1191 QWindowSystemInterface::handleTabletLeaveProximityEvent(timestamp: ev->time,
1192 device: tabletData->tool, pointerType: tabletData->pointerType, uid: tabletData->serialId);
1193 }
1194 // TODO maybe have a hash of tabletData->deviceId to device data so we can
1195 // look up the tablet name here, and distinguish multiple tablets
1196 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
1197 qCDebug(lcQpaXInputEvents, "XI2 proximity change on tablet %d (USB %x): last tool: %x id %x current tool: %x id %x %s",
1198 tabletData->deviceId, ptr[_WACSER_USB_ID], ptr[_WACSER_LAST_TOOL_SERIAL], ptr[_WACSER_LAST_TOOL_ID],
1199 ptr[_WACSER_TOOL_SERIAL], ptr[_WACSER_TOOL_ID], toolName(tabletData->tool));
1200 }
1201 }
1202 }
1203 }
1204 break;
1205 }
1206 default:
1207 handled = false;
1208 break;
1209 }
1210
1211 return handled;
1212}
1213
1214inline qreal scaleOneValuator(qreal normValue, qreal screenMin, qreal screenSize)
1215{
1216 return screenMin + normValue * screenSize;
1217}
1218
1219void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletData)
1220{
1221 auto *ev = reinterpret_cast<const qt_xcb_input_device_event_t *>(event);
1222 QXcbWindow *xcbWindow = platformWindowFromId(id: ev->event);
1223 if (!xcbWindow)
1224 return;
1225 QWindow *window = xcbWindow->window();
1226 const Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(s: ev->mods.effective);
1227 QPointF local(fixed1616ToReal(val: ev->event_x), fixed1616ToReal(val: ev->event_y));
1228 QPointF global(fixed1616ToReal(val: ev->root_x), fixed1616ToReal(val: ev->root_y));
1229 double pressure = 0, rotation = 0, tangentialPressure = 0;
1230 int xTilt = 0, yTilt = 0;
1231 static const bool useValuators = !qEnvironmentVariableIsSet(varName: "QT_XCB_TABLET_LEGACY_COORDINATES");
1232
1233 // Valuators' values are relative to the physical size of the current virtual
1234 // screen. Therefore we cannot use QScreen/QWindow geometry and should use
1235 // QPlatformWindow/QPlatformScreen instead.
1236 QRect physicalScreenArea;
1237 if (Q_LIKELY(useValuators)) {
1238 const QList<QPlatformScreen *> siblings = window->screen()->handle()->virtualSiblings();
1239 for (const QPlatformScreen *screen : siblings)
1240 physicalScreenArea |= screen->geometry();
1241 }
1242
1243 for (QHash<int, TabletData::ValuatorClassInfo>::iterator it = tabletData->valuatorInfo.begin(),
1244 ite = tabletData->valuatorInfo.end(); it != ite; ++it) {
1245 int valuator = it.key();
1246 TabletData::ValuatorClassInfo &classInfo(it.value());
1247 xi2GetValuatorValueIfSet(event, valuatorNum: classInfo.number, value: &classInfo.curVal);
1248 double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal);
1249 switch (valuator) {
1250 case QXcbAtom::AbsX:
1251 if (Q_LIKELY(useValuators)) {
1252 const qreal value = scaleOneValuator(normValue: normalizedValue, screenMin: physicalScreenArea.x(), screenSize: physicalScreenArea.width());
1253 global.setX(value);
1254 // mapFromGlobal is ok for nested/embedded windows, but works only with whole-number QPoint;
1255 // so map it first, then add back the sub-pixel position
1256 local.setX(window->mapFromGlobal(pos: QPoint(int(value), 0)).x() + (value - int(value)));
1257 }
1258 break;
1259 case QXcbAtom::AbsY:
1260 if (Q_LIKELY(useValuators)) {
1261 qreal value = scaleOneValuator(normValue: normalizedValue, screenMin: physicalScreenArea.y(), screenSize: physicalScreenArea.height());
1262 global.setY(value);
1263 local.setY(window->mapFromGlobal(pos: QPoint(0, int(value))).y() + (value - int(value)));
1264 }
1265 break;
1266 case QXcbAtom::AbsPressure:
1267 pressure = normalizedValue;
1268 break;
1269 case QXcbAtom::AbsTiltX:
1270 xTilt = classInfo.curVal;
1271 break;
1272 case QXcbAtom::AbsTiltY:
1273 yTilt = classInfo.curVal;
1274 break;
1275 case QXcbAtom::AbsWheel:
1276 switch (tabletData->tool) {
1277 case QTabletEvent::Airbrush:
1278 tangentialPressure = normalizedValue * 2.0 - 1.0; // Convert 0..1 range to -1..+1 range
1279 break;
1280 case QTabletEvent::RotationStylus:
1281 rotation = normalizedValue * 360.0 - 180.0; // Convert 0..1 range to -180..+180 degrees
1282 break;
1283 default: // Other types of styli do not use this valuator
1284 break;
1285 }
1286 break;
1287 default:
1288 break;
1289 }
1290 }
1291
1292 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
1293 qCDebug(lcQpaXInputEvents, "XI2 event on tablet %d with tool %s type %s seq %d detail %d time %d "
1294 "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",
1295 tabletData->deviceId, toolName(tabletData->tool), pointerTypeName(tabletData->pointerType),
1296 ev->sequence, ev->detail, ev->time,
1297 local.x(), local.y(), global.x(), global.y(),
1298 (int)tabletData->buttons, pressure, xTilt, yTilt, rotation, (int)modifiers);
1299
1300 QWindowSystemInterface::handleTabletEvent(window, timestamp: ev->time, local, global,
1301 device: tabletData->tool, pointerType: tabletData->pointerType,
1302 buttons: tabletData->buttons, pressure,
1303 xTilt, yTilt, tangentialPressure,
1304 rotation, z: 0, uid: tabletData->serialId, modifiers);
1305}
1306
1307QXcbConnection::TabletData *QXcbConnection::tabletDataForDevice(int id)
1308{
1309 for (int i = 0; i < m_tabletData.count(); ++i) {
1310 if (m_tabletData.at(i).deviceId == id)
1311 return &m_tabletData[i];
1312 }
1313 return nullptr;
1314}
1315
1316#endif // QT_CONFIG(tabletevent)
1317

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