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(xcb_connection(), rootWindow(), 1, &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(xcb_connection(), window, 1, &mask.header);
96 xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
97 if (error) {
98 qCDebug(lcQpaXInput, "failed to select events, window %x, error code %d", window, error->error_code);
99 free(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(deviceInfo->deviceid);
123 m_touchDevices.remove(deviceInfo->deviceid);
124 }
125
126 qCDebug(lcQpaXInputDevices) << "input device " << xcb_input_xi_device_info_name(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(deviceInfo);
132 for (; classes_it.rem; xcb_input_device_class_next(&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(vci->label);
138 qCDebug(lcQpaXInputDevices) << " has valuator" << atomName(vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms);
139#if QT_CONFIG(tabletevent)
140 if (valuatorAtom < QXcbAtom::NAtoms) {
141 TabletData::ValuatorClassInfo info;
142 info.minVal = fixed3232ToReal(vci->min);
143 info.maxVal = fixed3232ToReal(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(vci->value));
150 else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
151 scrollingDevice.lastScrollPosition.setY(fixed3232ToReal(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(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(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 = 0;
170 if (bci->num_buttons >= 5) {
171 labels = xcb_input_button_class_labels(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(label4) == QXcbAtom::ButtonWheelUp || qatom(label4) == QXcbAtom::ButtonWheelDown) &&
177 (!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(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(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(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(QXcbAtom::AbsX) &&
204 tabletData.valuatorInfo.contains(QXcbAtom::AbsY) &&
205 tabletData.valuatorInfo.contains(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(deviceInfo),
210 xcb_input_xi_device_info_name_length(deviceInfo)).toLower();
211 QString dbgType = QLatin1String("UNKNOWN");
212 if (name.contains("eraser")) {
213 isTablet = true;
214 tabletData.pointerType = QTabletEvent::Eraser;
215 dbgType = QLatin1String("eraser");
216 } else if (name.contains("cursor") && !(name.contains("cursor controls") && name.contains("trackball"))) {
217 isTablet = true;
218 tabletData.pointerType = QTabletEvent::Cursor;
219 dbgType = QLatin1String("cursor");
220 } else if (name.contains("wacom") && name.contains("finger touch")) {
221 isTablet = false;
222 } else if ((name.contains("pen") || name.contains("stylus")) && isTablet) {
223 tabletData.pointerType = QTabletEvent::Pen;
224 dbgType = QLatin1String("pen");
225 } else if (name.contains("wacom") && isTablet && !name.contains("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("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("waltop") && name.contains("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("uc-logic") && isTablet) {
241 tabletData.pointerType = QTabletEvent::Pen;
242 dbgType = QLatin1String("pen");
243 } else if (name.contains("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(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(scrollingDevice.deviceId, scrollingDevice);
263 qCDebug(lcQpaXInputDevices) << " it's a scrolling device";
264 }
265
266 if (!isTablet) {
267 TouchDeviceData *dev = populateTouchDevices(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(reply.get());
299 for (; it.rem; xcb_input_xi_device_info_next(&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(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(deviceInfo, 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(xcb_connection(), window, 1, &xiMask.header);
370 xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
371 if (error) {
372 qCDebug(lcQpaXInput, "failed to select events, window %x, error code %d", window, error->error_code);
373 free(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(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(xcb_connection(), window, nrTablets, &(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(m_scrollingDevices)) {
403#if QT_CONFIG(tabletevent)
404 if (tabletDevices.contains(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(xcb_connection(), window, i, &(xiEventMask.data()->header));
413 }
414}
415
416QXcbConnection::TouchDeviceData *QXcbConnection::touchDeviceForId(int id)
417{
418 TouchDeviceData *dev = nullptr;
419 if (m_touchDevices.contains(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 = 0;
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(deviceinfo);
434 for (; classes_it.rem; xcb_input_device_class_next(&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(vci->label);
454 if (valuatorAtom < QXcbAtom::NAtoms) {
455 TouchDeviceData::ValuatorClassInfo info;
456 info.min = fixed3232ToReal(vci->min);
457 info.max = fixed3232ToReal(vci->max);
458 info.number = vci->number;
459 info.label = valuatorAtom;
460 dev.valuatorInfo.append(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(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
476 } else if (valuatorAtom == QXcbAtom::RelY) {
477 hasRelativeCoords = true;
478 dev.size.setHeight((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
479 } else if (valuatorAtom == QXcbAtom::AbsX) {
480 caps |= QTouchDevice::Position;
481 dev.size.setWidth((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
482 } else if (valuatorAtom == QXcbAtom::AbsY) {
483 caps |= QTouchDevice::Position;
484 dev.size.setHeight((fixed3232ToReal(vci->max) - fixed3232ToReal(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(xcb_input_xi_device_info_name(deviceinfo),
504 xcb_input_xi_device_info_name_length(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(dev.qtTouchDevice);
510 m_touchDevices[deviceinfo->deviceid] = dev;
511 isTouchDevice = true;
512 }
513
514 return isTouchDevice ? &m_touchDevices[deviceinfo->deviceid] : nullptr;
515}
516
517#if QT_CONFIG(tabletevent)
518static inline qreal fixed1616ToReal(xcb_input_fp1616_t val)
519{
520 return qreal(val) / 0x10000;
521}
522#endif // QT_CONFIG(tabletevent)
523
524void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
525{
526 auto *xiEvent = reinterpret_cast<qt_xcb_input_device_event_t *>(event);
527 int sourceDeviceId = xiEvent->deviceid; // may be the master id
528 qt_xcb_input_device_event_t *xiDeviceEvent = nullptr;
529 xcb_input_enter_event_t *xiEnterEvent = nullptr;
530 QXcbWindowEventListener *eventListener = 0;
531
532 switch (xiEvent->event_type) {
533 case XCB_INPUT_BUTTON_PRESS:
534 case XCB_INPUT_BUTTON_RELEASE:
535 case XCB_INPUT_MOTION:
536 case XCB_INPUT_TOUCH_BEGIN:
537 case XCB_INPUT_TOUCH_UPDATE:
538 case XCB_INPUT_TOUCH_END:
539 {
540 xiDeviceEvent = xiEvent;
541 eventListener = windowEventListenerFromId(xiDeviceEvent->event);
542 sourceDeviceId = xiDeviceEvent->sourceid; // use the actual device id instead of the master
543 break;
544 }
545 case XCB_INPUT_ENTER:
546 case XCB_INPUT_LEAVE: {
547 xiEnterEvent = reinterpret_cast<xcb_input_enter_event_t *>(event);
548 eventListener = windowEventListenerFromId(xiEnterEvent->event);
549 sourceDeviceId = xiEnterEvent->sourceid; // use the actual device id instead of the master
550 break;
551 }
552 case XCB_INPUT_HIERARCHY:
553 xi2HandleHierarchyEvent(event);
554 return;
555 case XCB_INPUT_DEVICE_CHANGED:
556 xi2HandleDeviceChangedEvent(event);
557 return;
558 default:
559 break;
560 }
561
562 if (eventListener) {
563 if (eventListener->handleNativeEvent(reinterpret_cast<xcb_generic_event_t *>(event)))
564 return;
565 }
566
567#if QT_CONFIG(tabletevent)
568 if (!xiEnterEvent) {
569 QXcbConnection::TabletData *tablet = tabletDataForDevice(sourceDeviceId);
570 if (tablet && xi2HandleTabletEvent(event, tablet))
571 return;
572 }
573#endif // QT_CONFIG(tabletevent)
574
575 if (ScrollingDevice *device = scrollingDeviceForId(sourceDeviceId))
576 xi2HandleScrollEvent(event, *device);
577
578 if (xiDeviceEvent) {
579 switch (xiDeviceEvent->event_type) {
580 case XCB_INPUT_BUTTON_PRESS:
581 case XCB_INPUT_BUTTON_RELEASE:
582 case XCB_INPUT_MOTION:
583 if (!xi2MouseEventsDisabled() && eventListener &&
584 !(xiDeviceEvent->flags & XCB_INPUT_POINTER_EVENT_FLAGS_POINTER_EMULATED))
585 eventListener->handleXIMouseEvent(event);
586 break;
587
588 case XCB_INPUT_TOUCH_BEGIN:
589 case XCB_INPUT_TOUCH_UPDATE:
590 case XCB_INPUT_TOUCH_END:
591 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
592 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",
593 event->event_type, xiDeviceEvent->sequence, xiDeviceEvent->detail,
594 fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y),
595 fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y),xiDeviceEvent->event);
596 if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event))
597 xi2ProcessTouch(xiDeviceEvent, platformWindow);
598 break;
599 }
600 } else if (xiEnterEvent && !xi2MouseEventsDisabled() && eventListener) {
601 switch (xiEnterEvent->event_type) {
602 case XCB_INPUT_ENTER:
603 case XCB_INPUT_LEAVE:
604 eventListener->handleXIEnterLeave(event);
605 break;
606 }
607 }
608}
609
610bool QXcbConnection::xi2MouseEventsDisabled() const
611{
612 static bool xi2MouseDisabled = qEnvironmentVariableIsSet("QT_XCB_NO_XI2_MOUSE");
613 // FIXME: Don't use XInput2 mouse events when Xinerama extension
614 // is enabled, because it causes problems with multi-monitor setup.
615 return xi2MouseDisabled || hasXinerama();
616}
617
618bool QXcbConnection::isTouchScreen(int id)
619{
620 auto device = touchDeviceForId(id);
621 return device && device->qtTouchDevice->type() == QTouchDevice::TouchScreen;
622}
623
624void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindow)
625{
626 auto *xiDeviceEvent = reinterpret_cast<xcb_input_touch_begin_event_t *>(xiDevEvent);
627 TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid);
628 Q_ASSERT(dev);
629 const bool firstTouch = dev->touchPoints.isEmpty();
630 if (xiDeviceEvent->event_type == XCB_INPUT_TOUCH_BEGIN) {
631 QWindowSystemInterface::TouchPoint tp;
632 tp.id = xiDeviceEvent->detail % INT_MAX;
633 tp.state = Qt::TouchPointPressed;
634 tp.pressure = -1.0;
635 dev->touchPoints[tp.id] = tp;
636 }
637 QWindowSystemInterface::TouchPoint &touchPoint = dev->touchPoints[xiDeviceEvent->detail];
638 QXcbScreen* screen = platformWindow->xcbScreen();
639 qreal x = fixed1616ToReal(xiDeviceEvent->root_x);
640 qreal y = fixed1616ToReal(xiDeviceEvent->root_y);
641 qreal nx = -1.0, ny = -1.0;
642 qreal w = 0.0, h = 0.0;
643 bool majorAxisIsY = touchPoint.area.height() > touchPoint.area.width();
644 for (const TouchDeviceData::ValuatorClassInfo &vci : qAsConst(dev->valuatorInfo)) {
645 double value;
646 if (!xi2GetValuatorValueIfSet(xiDeviceEvent, vci.number, &value))
647 continue;
648 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
649 qCDebug(lcQpaXInputEvents, " valuator %20s value %lf from range %lf -> %lf",
650 atomName(vci.label).constData(), value, vci.min, vci.max);
651 if (value > vci.max)
652 value = vci.max;
653 if (value < vci.min)
654 value = vci.min;
655 qreal valuatorNormalized = (value - vci.min) / (vci.max - vci.min);
656 if (vci.label == QXcbAtom::RelX) {
657 nx = valuatorNormalized;
658 } else if (vci.label == QXcbAtom::RelY) {
659 ny = valuatorNormalized;
660 } else if (vci.label == QXcbAtom::AbsX) {
661 nx = valuatorNormalized;
662 } else if (vci.label == QXcbAtom::AbsY) {
663 ny = valuatorNormalized;
664 } else if (vci.label == QXcbAtom::AbsMTPositionX) {
665 nx = valuatorNormalized;
666 } else if (vci.label == QXcbAtom::AbsMTPositionY) {
667 ny = valuatorNormalized;
668 } else if (vci.label == QXcbAtom::AbsMTTouchMajor) {
669 const qreal sw = screen->geometry().width();
670 const qreal sh = screen->geometry().height();
671 w = valuatorNormalized * std::sqrt(sw * sw + sh * sh);
672 } else if (vci.label == QXcbAtom::AbsMTTouchMinor) {
673 const qreal sw = screen->geometry().width();
674 const qreal sh = screen->geometry().height();
675 h = valuatorNormalized * std::sqrt(sw * sw + sh * sh);
676 } else if (vci.label == QXcbAtom::AbsMTOrientation) {
677 // Find the closest axis.
678 // 0 corresponds to the Y axis, vci.max to the X axis.
679 // Flipping over the Y axis and rotating by 180 degrees
680 // don't change the result, so normalize value to range
681 // [0, vci.max] first.
682 value = qAbs(value);
683 while (value > vci.max)
684 value -= 2 * vci.max;
685 value = qAbs(value);
686 majorAxisIsY = value < vci.max - value;
687 } else if (vci.label == QXcbAtom::AbsMTPressure || vci.label == QXcbAtom::AbsPressure) {
688 touchPoint.pressure = valuatorNormalized;
689 }
690
691 }
692 // If any value was not updated, use the last-known value.
693 if (nx == -1.0) {
694 x = touchPoint.area.center().x();
695 nx = x / screen->geometry().width();
696 }
697 if (ny == -1.0) {
698 y = touchPoint.area.center().y();
699 ny = y / screen->geometry().height();
700 }
701 if (xiDeviceEvent->event_type != XCB_INPUT_TOUCH_END) {
702 if (!dev->providesTouchOrientation) {
703 if (w == 0.0)
704 w = touchPoint.area.width();
705 h = w;
706 } else {
707 if (w == 0.0)
708 w = qMax(touchPoint.area.width(), touchPoint.area.height());
709 if (h == 0.0)
710 h = qMin(touchPoint.area.width(), touchPoint.area.height());
711 if (majorAxisIsY)
712 qSwap(w, h);
713 }
714 }
715
716 switch (xiDeviceEvent->event_type) {
717 case XCB_INPUT_TOUCH_BEGIN:
718 if (firstTouch) {
719 dev->firstPressedPosition = QPointF(x, y);
720 dev->firstPressedNormalPosition = QPointF(nx, ny);
721 }
722 dev->pointPressedPosition.insert(touchPoint.id, QPointF(x, y));
723
724 // Touches must be accepted when we are grabbing touch events. Otherwise the entire sequence
725 // will get replayed when the grab ends.
726 if (m_xiGrab) {
727 xcb_input_xi_allow_events(xcb_connection(), XCB_CURRENT_TIME, xiDeviceEvent->deviceid,
728 XCB_INPUT_EVENT_MODE_ACCEPT_TOUCH,
729 xiDeviceEvent->detail, xiDeviceEvent->event);
730 }
731 break;
732 case XCB_INPUT_TOUCH_UPDATE:
733 if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad && dev->pointPressedPosition.value(touchPoint.id) == QPointF(x, y)) {
734 qreal dx = (nx - dev->firstPressedNormalPosition.x()) *
735 dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
736 qreal dy = (ny - dev->firstPressedNormalPosition.y()) *
737 dev->size.height() * screen->geometry().height() / screen->physicalSize().height();
738 x = dev->firstPressedPosition.x() + dx;
739 y = dev->firstPressedPosition.y() + dy;
740 touchPoint.state = Qt::TouchPointMoved;
741 } else if (touchPoint.area.center() != QPoint(x, y)) {
742 touchPoint.state = Qt::TouchPointMoved;
743 if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad)
744 dev->pointPressedPosition[touchPoint.id] = QPointF(x, y);
745 }
746
747 if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen &&
748 xiDeviceEvent->event == m_startSystemMoveResizeInfo.window &&
749 xiDeviceEvent->sourceid == m_startSystemMoveResizeInfo.deviceid &&
750 xiDeviceEvent->detail == m_startSystemMoveResizeInfo.pointid) {
751 QXcbWindow *window = platformWindowFromId(m_startSystemMoveResizeInfo.window);
752 if (window) {
753 xcb_input_xi_allow_events(xcb_connection(), XCB_CURRENT_TIME, xiDeviceEvent->deviceid,
754 XCB_INPUT_EVENT_MODE_REJECT_TOUCH,
755 xiDeviceEvent->detail, xiDeviceEvent->event);
756 window->doStartSystemMoveResize(QPoint(x, y), m_startSystemMoveResizeInfo.corner);
757 m_startSystemMoveResizeInfo.window = XCB_NONE;
758 }
759 }
760 break;
761 case XCB_INPUT_TOUCH_END:
762 touchPoint.state = Qt::TouchPointReleased;
763 if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad && dev->pointPressedPosition.value(touchPoint.id) == QPointF(x, y)) {
764 qreal dx = (nx - dev->firstPressedNormalPosition.x()) *
765 dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
766 qreal dy = (ny - dev->firstPressedNormalPosition.y()) *
767 dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
768 x = dev->firstPressedPosition.x() + dx;
769 y = dev->firstPressedPosition.y() + dy;
770 }
771 dev->pointPressedPosition.remove(touchPoint.id);
772 }
773 touchPoint.area = QRectF(x - w/2, y - h/2, w, h);
774 touchPoint.normalPosition = QPointF(nx, ny);
775
776 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
777 qCDebug(lcQpaXInputEvents) << " touchpoint " << touchPoint.id << " state " << touchPoint.state << " pos norm " << touchPoint.normalPosition <<
778 " area " << touchPoint.area << " pressure " << touchPoint.pressure;
779 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(xiDeviceEvent->mods.effective);
780 QWindowSystemInterface::handleTouchEvent(platformWindow->window(), xiDeviceEvent->time, dev->qtTouchDevice, dev->touchPoints.values(), modifiers);
781 if (touchPoint.state == Qt::TouchPointReleased)
782 // If a touchpoint was released, we can forget it, because the ID won't be reused.
783 dev->touchPoints.remove(touchPoint.id);
784 else
785 // Make sure that we don't send TouchPointPressed/Moved in more than one QTouchEvent
786 // with this touch point if the next XI2 event is about a different touch point.
787 touchPoint.state = Qt::TouchPointStationary;
788}
789
790bool QXcbConnection::startSystemMoveResizeForTouchBegin(xcb_window_t window, const QPoint &point, int corner)
791{
792 QHash<int, TouchDeviceData>::const_iterator devIt = m_touchDevices.constBegin();
793 for (; devIt != m_touchDevices.constEnd(); ++devIt) {
794 TouchDeviceData deviceData = devIt.value();
795 if (deviceData.qtTouchDevice->type() == QTouchDevice::TouchScreen) {
796 QHash<int, QPointF>::const_iterator pointIt = deviceData.pointPressedPosition.constBegin();
797 for (; pointIt != deviceData.pointPressedPosition.constEnd(); ++pointIt) {
798 if (pointIt.value().toPoint() == point) {
799 m_startSystemMoveResizeInfo.window = window;
800 m_startSystemMoveResizeInfo.deviceid = devIt.key();
801 m_startSystemMoveResizeInfo.pointid = pointIt.key();
802 m_startSystemMoveResizeInfo.corner = corner;
803 return true;
804 }
805 }
806 }
807 }
808 return false;
809}
810
811void QXcbConnection::abortSystemMoveResizeForTouch()
812{
813 m_startSystemMoveResizeInfo.window = XCB_NONE;
814}
815
816bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab)
817{
818 bool ok = false;
819
820 if (grab) { // grab
821 uint32_t mask = XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS
822 | XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE
823 | XCB_INPUT_XI_EVENT_MASK_MOTION
824 | XCB_INPUT_XI_EVENT_MASK_ENTER
825 | XCB_INPUT_XI_EVENT_MASK_LEAVE
826 | XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN
827 | XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE
828 | XCB_INPUT_XI_EVENT_MASK_TOUCH_END;
829
830 for (int id : qAsConst(m_xiMasterPointerIds)) {
831 xcb_generic_error_t *error = nullptr;
832 auto cookie = xcb_input_xi_grab_device(xcb_connection(), w, XCB_CURRENT_TIME, XCB_CURSOR_NONE, id,
833 XCB_INPUT_GRAB_MODE_22_ASYNC, XCB_INPUT_GRAB_MODE_22_ASYNC,
834 false, 1, &mask);
835 auto *reply = xcb_input_xi_grab_device_reply(xcb_connection(), cookie, &error);
836 if (error) {
837 qCDebug(lcQpaXInput, "failed to grab events for device %d on window %x"
838 "(error code %d)", id, w, error->error_code);
839 free(error);
840 } else {
841 // Managed to grab at least one of master pointers, that should be enough
842 // to properly dismiss windows that rely on mouse grabbing.
843 ok = true;
844 }
845 free(reply);
846 }
847 } else { // ungrab
848 for (int id : qAsConst(m_xiMasterPointerIds)) {
849 auto cookie = xcb_input_xi_ungrab_device_checked(xcb_connection(), XCB_CURRENT_TIME, id);
850 xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
851 if (error) {
852 qCDebug(lcQpaXInput, "XIUngrabDevice failed - id: %d (error code %d)", id, error->error_code);
853 free(error);
854 }
855 }
856 // XIUngrabDevice does not seem to wait for a reply from X server (similar to
857 // xcb_ungrab_pointer). Ungrabbing won't fail, unless NoSuchExtension error
858 // has occurred due to a programming error somewhere else in the stack. That
859 // would mean that things will crash soon anyway.
860 ok = true;
861 }
862
863 if (ok)
864 m_xiGrab = grab;
865
866 return ok;
867}
868
869void QXcbConnection::xi2HandleHierarchyEvent(void *event)
870{
871 auto *xiEvent = reinterpret_cast<xcb_input_hierarchy_event_t *>(event);
872 // We only care about hotplugged devices
873 if (!(xiEvent->flags & (XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED | XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED)))
874 return;
875
876 xi2SetupDevices();
877
878 if (xi2MouseEventsDisabled()) {
879 // In compatibility mode (a.k.a xi2MouseEventsDisabled() mode) we select events for
880 // each device separately. When a new device appears, we have to select events from
881 // this device on all event-listening windows. This is not needed when events are
882 // selected via XIAllDevices/XIAllMasterDevices (as in xi2SelectDeviceEvents()).
883 for (auto it = m_mapper.cbegin(), end = m_mapper.cend(); it != end; ++it)
884 xi2SelectDeviceEventsCompatibility(it.key());
885 }
886}
887
888void QXcbConnection::xi2HandleDeviceChangedEvent(void *event)
889{
890 auto *xiEvent = reinterpret_cast<xcb_input_device_changed_event_t *>(event);
891 switch (xiEvent->reason) {
892 case XCB_INPUT_CHANGE_REASON_DEVICE_CHANGE: {
893 auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), xiEvent->sourceid);
894 if (!reply || reply->num_infos <= 0)
895 return;
896 auto it = xcb_input_xi_query_device_infos_iterator(reply.get());
897 xi2SetupDevice(it.data);
898 break;
899 }
900 case XCB_INPUT_CHANGE_REASON_SLAVE_SWITCH: {
901 if (ScrollingDevice *scrollingDevice = scrollingDeviceForId(xiEvent->sourceid))
902 xi2UpdateScrollingDevice(*scrollingDevice);
903 break;
904 }
905 default:
906 qCDebug(lcQpaXInputEvents, "unknown device-changed-event (device %d)", xiEvent->sourceid);
907 break;
908 }
909}
910
911void QXcbConnection::xi2UpdateScrollingDevice(ScrollingDevice &scrollingDevice)
912{
913 auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), scrollingDevice.deviceId);
914 if (!reply || reply->num_infos <= 0) {
915 qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", scrollingDevice.deviceId);
916 return;
917 }
918 QPointF lastScrollPosition;
919 if (lcQpaXInputEvents().isDebugEnabled())
920 lastScrollPosition = scrollingDevice.lastScrollPosition;
921
922 xcb_input_xi_device_info_t *deviceInfo = xcb_input_xi_query_device_infos_iterator(reply.get()).data;
923 auto classes_it = xcb_input_xi_device_info_classes_iterator(deviceInfo);
924 for (; classes_it.rem; xcb_input_device_class_next(&classes_it)) {
925 xcb_input_device_class_t *classInfo = classes_it.data;
926 if (classInfo->type == XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR) {
927 auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classInfo);
928 const int valuatorAtom = qatom(vci->label);
929 if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
930 scrollingDevice.lastScrollPosition.setX(fixed3232ToReal(vci->value));
931 else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
932 scrollingDevice.lastScrollPosition.setY(fixed3232ToReal(vci->value));
933 }
934 }
935 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled() && lastScrollPosition != scrollingDevice.lastScrollPosition))
936 qCDebug(lcQpaXInputEvents, "scrolling device %d moved from (%f, %f) to (%f, %f)", scrollingDevice.deviceId,
937 lastScrollPosition.x(), lastScrollPosition.y(),
938 scrollingDevice.lastScrollPosition.x(),
939 scrollingDevice.lastScrollPosition.y());
940}
941
942void QXcbConnection::xi2UpdateScrollingDevices()
943{
944 QHash<int, ScrollingDevice>::iterator it = m_scrollingDevices.begin();
945 const QHash<int, ScrollingDevice>::iterator end = m_scrollingDevices.end();
946 while (it != end) {
947 xi2UpdateScrollingDevice(it.value());
948 ++it;
949 }
950}
951
952QXcbConnection::ScrollingDevice *QXcbConnection::scrollingDeviceForId(int id)
953{
954 ScrollingDevice *dev = nullptr;
955 if (m_scrollingDevices.contains(id))
956 dev = &m_scrollingDevices[id];
957 return dev;
958}
959
960void QXcbConnection::xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice)
961{
962 auto *xiDeviceEvent = reinterpret_cast<qt_xcb_input_device_event_t *>(event);
963
964 if (xiDeviceEvent->event_type == XCB_INPUT_MOTION && scrollingDevice.orientations) {
965 if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) {
966 QPoint rawDelta;
967 QPoint angleDelta;
968 double value;
969 if (scrollingDevice.orientations & Qt::Vertical) {
970 if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.verticalIndex, &value)) {
971 double delta = scrollingDevice.lastScrollPosition.y() - value;
972 scrollingDevice.lastScrollPosition.setY(value);
973 angleDelta.setY((delta / scrollingDevice.verticalIncrement) * 120);
974 // With most drivers the increment is 1 for wheels.
975 // For libinput it is hardcoded to a useless 15.
976 // For a proper touchpad driver it should be in the same order of magnitude as 120
977 if (scrollingDevice.verticalIncrement > 15)
978 rawDelta.setY(delta);
979 else if (scrollingDevice.verticalIncrement < -15)
980 rawDelta.setY(-delta);
981 }
982 }
983 if (scrollingDevice.orientations & Qt::Horizontal) {
984 if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.horizontalIndex, &value)) {
985 double delta = scrollingDevice.lastScrollPosition.x() - value;
986 scrollingDevice.lastScrollPosition.setX(value);
987 angleDelta.setX((delta / scrollingDevice.horizontalIncrement) * 120);
988 // See comment under vertical
989 if (scrollingDevice.horizontalIncrement > 15)
990 rawDelta.setX(delta);
991 else if (scrollingDevice.horizontalIncrement < -15)
992 rawDelta.setX(-delta);
993 }
994 }
995 if (!angleDelta.isNull()) {
996 QPoint local(fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y));
997 QPoint global(fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y));
998 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(xiDeviceEvent->mods.effective);
999 if (modifiers & Qt::AltModifier) {
1000 std::swap(angleDelta.rx(), angleDelta.ry());
1001 std::swap(rawDelta.rx(), rawDelta.ry());
1002 }
1003 qCDebug(lcQpaXInputEvents) << "scroll wheel @ window pos" << local << "delta px" << rawDelta << "angle" << angleDelta;
1004 QWindowSystemInterface::handleWheelEvent(platformWindow->window(), xiDeviceEvent->time, local, global, rawDelta, angleDelta, modifiers);
1005 }
1006 }
1007 } else if (xiDeviceEvent->event_type == XCB_INPUT_BUTTON_RELEASE && scrollingDevice.legacyOrientations) {
1008 if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) {
1009 QPoint angleDelta;
1010 if (scrollingDevice.legacyOrientations & Qt::Vertical) {
1011 if (xiDeviceEvent->detail == 4)
1012 angleDelta.setY(120);
1013 else if (xiDeviceEvent->detail == 5)
1014 angleDelta.setY(-120);
1015 }
1016 if (scrollingDevice.legacyOrientations & Qt::Horizontal) {
1017 if (xiDeviceEvent->detail == 6)
1018 angleDelta.setX(120);
1019 else if (xiDeviceEvent->detail == 7)
1020 angleDelta.setX(-120);
1021 }
1022 if (!angleDelta.isNull()) {
1023 QPoint local(fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y));
1024 QPoint global(fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y));
1025 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(xiDeviceEvent->mods.effective);
1026 if (modifiers & Qt::AltModifier)
1027 std::swap(angleDelta.rx(), angleDelta.ry());
1028 qCDebug(lcQpaXInputEvents) << "scroll wheel (button" << xiDeviceEvent->detail << ") @ window pos" << local << "delta angle" << angleDelta;
1029 QWindowSystemInterface::handleWheelEvent(platformWindow->window(), xiDeviceEvent->time, local, global, QPoint(), angleDelta, modifiers);
1030 }
1031 }
1032 }
1033}
1034
1035static int xi2ValuatorOffset(const unsigned char *maskPtr, int maskLen, int number)
1036{
1037 int offset = 0;
1038 for (int i = 0; i < maskLen; i++) {
1039 if (number < 8) {
1040 if ((maskPtr[i] & (1 << number)) == 0)
1041 return -1;
1042 }
1043 for (int j = 0; j < 8; j++) {
1044 if (j == number)
1045 return offset;
1046 if (maskPtr[i] & (1 << j))
1047 offset++;
1048 }
1049 number -= 8;
1050 }
1051 return -1;
1052}
1053
1054bool QXcbConnection::xi2GetValuatorValueIfSet(const void *event, int valuatorNum, double *value)
1055{
1056 auto *xideviceevent = static_cast<const qt_xcb_input_device_event_t *>(event);
1057 auto *buttonsMaskAddr = reinterpret_cast<const unsigned char *>(&xideviceevent[1]);
1058 auto *valuatorsMaskAddr = buttonsMaskAddr + xideviceevent->buttons_len * 4;
1059 auto *valuatorsValuesAddr = reinterpret_cast<const xcb_input_fp3232_t *>(valuatorsMaskAddr + xideviceevent->valuators_len * 4);
1060
1061 int valuatorOffset = xi2ValuatorOffset(valuatorsMaskAddr, xideviceevent->valuators_len, valuatorNum);
1062 if (valuatorOffset < 0)
1063 return false;
1064
1065 *value = valuatorsValuesAddr[valuatorOffset].integral;
1066 *value += ((double)valuatorsValuesAddr[valuatorOffset].frac / (1 << 16) / (1 << 16));
1067 return true;
1068}
1069
1070Qt::MouseButton QXcbConnection::xiToQtMouseButton(uint32_t b)
1071{
1072 switch (b) {
1073 case 1: return Qt::LeftButton;
1074 case 2: return Qt::MiddleButton;
1075 case 3: return Qt::RightButton;
1076 // 4-7 are for scrolling
1077 default: break;
1078 }
1079 if (b >= 8 && b <= Qt::MaxMouseButton)
1080 return static_cast<Qt::MouseButton>(Qt::BackButton << (b - 8));
1081 return Qt::NoButton;
1082}
1083
1084#if QT_CONFIG(tabletevent)
1085static QTabletEvent::TabletDevice toolIdToTabletDevice(quint32 toolId) {
1086 // keep in sync with wacom_intuos_inout() in Linux kernel driver wacom_wac.c
1087 switch (toolId) {
1088 case 0xd12:
1089 case 0x912:
1090 case 0x112:
1091 case 0x913: /* Intuos3 Airbrush */
1092 case 0x91b: /* Intuos3 Airbrush Eraser */
1093 case 0x902: /* Intuos4/5 13HD/24HD Airbrush */
1094 case 0x90a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
1095 case 0x100902: /* Intuos4/5 13HD/24HD Airbrush */
1096 case 0x10090a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
1097 return QTabletEvent::Airbrush;
1098 case 0x007: /* Mouse 4D and 2D */
1099 case 0x09c:
1100 case 0x094:
1101 return QTabletEvent::FourDMouse;
1102 case 0x017: /* Intuos3 2D Mouse */
1103 case 0x806: /* Intuos4 Mouse */
1104 case 0x096: /* Lens cursor */
1105 case 0x097: /* Intuos3 Lens cursor */
1106 case 0x006: /* Intuos4 Lens cursor */
1107 return QTabletEvent::Puck;
1108 case 0x885: /* Intuos3 Art Pen (Marker Pen) */
1109 case 0x100804: /* Intuos4/5 13HD/24HD Art Pen */
1110 case 0x10080c: /* Intuos4/5 13HD/24HD Art Pen Eraser */
1111 return QTabletEvent::RotationStylus;
1112 case 0:
1113 return QTabletEvent::NoDevice;
1114 }
1115 return QTabletEvent::Stylus; // Safe default assumption if nonzero
1116}
1117
1118static const char *toolName(QTabletEvent::TabletDevice tool) {
1119 static const QMetaObject *metaObject = qt_getEnumMetaObject(tool);
1120 static const QMetaEnum me = metaObject->enumerator(metaObject->indexOfEnumerator(qt_getEnumName(tool)));
1121 return me.valueToKey(tool);
1122}
1123
1124static const char *pointerTypeName(QTabletEvent::PointerType ptype) {
1125 static const QMetaObject *metaObject = qt_getEnumMetaObject(ptype);
1126 static const QMetaEnum me = metaObject->enumerator(metaObject->indexOfEnumerator(qt_getEnumName(ptype)));
1127 return me.valueToKey(ptype);
1128}
1129
1130bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletData)
1131{
1132 bool handled = true;
1133 const auto *xiDeviceEvent = reinterpret_cast<const qt_xcb_input_device_event_t *>(event);
1134
1135 switch (xiDeviceEvent->event_type) {
1136 case XCB_INPUT_BUTTON_PRESS: {
1137 Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail);
1138 tabletData->buttons |= b;
1139 xi2ReportTabletEvent(event, tabletData);
1140 break;
1141 }
1142 case XCB_INPUT_BUTTON_RELEASE: {
1143 Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail);
1144 tabletData->buttons ^= b;
1145 xi2ReportTabletEvent(event, tabletData);
1146 break;
1147 }
1148 case XCB_INPUT_MOTION:
1149 xi2ReportTabletEvent(event, tabletData);
1150 break;
1151 case XCB_INPUT_PROPERTY: {
1152 // This is the wacom driver's way of reporting tool proximity.
1153 // The evdev driver doesn't do it this way.
1154 const auto *ev = reinterpret_cast<const xcb_input_property_event_t *>(event);
1155 if (ev->what == XCB_INPUT_PROPERTY_FLAG_MODIFIED) {
1156 if (ev->property == atom(QXcbAtom::WacomSerialIDs)) {
1157 enum WacomSerialIndex {
1158 _WACSER_USB_ID = 0,
1159 _WACSER_LAST_TOOL_SERIAL,
1160 _WACSER_LAST_TOOL_ID,
1161 _WACSER_TOOL_SERIAL,
1162 _WACSER_TOOL_ID,
1163 _WACSER_COUNT
1164 };
1165
1166 auto reply = Q_XCB_REPLY(xcb_input_xi_get_property, xcb_connection(), tabletData->deviceId, 0,
1167 ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, 100);
1168 if (reply) {
1169 if (reply->type == atom(QXcbAtom::INTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) {
1170 quint32 *ptr = reinterpret_cast<quint32 *>(xcb_input_xi_get_property_items(reply.get()));
1171 quint32 tool = ptr[_WACSER_TOOL_ID];
1172 // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
1173 // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
1174 if (!tool && ptr[_WACSER_TOOL_SERIAL])
1175 tool = ptr[_WACSER_TOOL_SERIAL];
1176
1177 // The property change event informs us which tool is in proximity or which one left proximity.
1178 if (tool) {
1179 tabletData->inProximity = true;
1180 tabletData->tool = toolIdToTabletDevice(tool);
1181 tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_TOOL_SERIAL]);
1182 QWindowSystemInterface::handleTabletEnterProximityEvent(ev->time,
1183 tabletData->tool, tabletData->pointerType, tabletData->serialId);
1184 } else {
1185 tabletData->inProximity = false;
1186 tabletData->tool = toolIdToTabletDevice(ptr[_WACSER_LAST_TOOL_ID]);
1187 // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
1188 // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
1189 if (!tabletData->tool)
1190 tabletData->tool = toolIdToTabletDevice(ptr[_WACSER_LAST_TOOL_SERIAL]);
1191 tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_LAST_TOOL_SERIAL]);
1192 QWindowSystemInterface::handleTabletLeaveProximityEvent(ev->time,
1193 tabletData->tool, tabletData->pointerType, tabletData->serialId);
1194 }
1195 // TODO maybe have a hash of tabletData->deviceId to device data so we can
1196 // look up the tablet name here, and distinguish multiple tablets
1197 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
1198 qCDebug(lcQpaXInputEvents, "XI2 proximity change on tablet %d (USB %x): last tool: %x id %x current tool: %x id %x %s",
1199 tabletData->deviceId, ptr[_WACSER_USB_ID], ptr[_WACSER_LAST_TOOL_SERIAL], ptr[_WACSER_LAST_TOOL_ID],
1200 ptr[_WACSER_TOOL_SERIAL], ptr[_WACSER_TOOL_ID], toolName(tabletData->tool));
1201 }
1202 }
1203 }
1204 }
1205 break;
1206 }
1207 default:
1208 handled = false;
1209 break;
1210 }
1211
1212 return handled;
1213}
1214
1215inline qreal scaleOneValuator(qreal normValue, qreal screenMin, qreal screenSize)
1216{
1217 return screenMin + normValue * screenSize;
1218}
1219
1220void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletData)
1221{
1222 auto *ev = reinterpret_cast<const qt_xcb_input_device_event_t *>(event);
1223 QXcbWindow *xcbWindow = platformWindowFromId(ev->event);
1224 if (!xcbWindow)
1225 return;
1226 QWindow *window = xcbWindow->window();
1227 const Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(ev->mods.effective);
1228 QPointF local(fixed1616ToReal(ev->event_x), fixed1616ToReal(ev->event_y));
1229 QPointF global(fixed1616ToReal(ev->root_x), fixed1616ToReal(ev->root_y));
1230 double pressure = 0, rotation = 0, tangentialPressure = 0;
1231 int xTilt = 0, yTilt = 0;
1232 static const bool useValuators = !qEnvironmentVariableIsSet("QT_XCB_TABLET_LEGACY_COORDINATES");
1233
1234 // Valuators' values are relative to the physical size of the current virtual
1235 // screen. Therefore we cannot use QScreen/QWindow geometry and should use
1236 // QPlatformWindow/QPlatformScreen instead.
1237 QRect physicalScreenArea;
1238 if (Q_LIKELY(useValuators)) {
1239 const QList<QPlatformScreen *> siblings = window->screen()->handle()->virtualSiblings();
1240 for (const QPlatformScreen *screen : siblings)
1241 physicalScreenArea |= screen->geometry();
1242 }
1243
1244 for (QHash<int, TabletData::ValuatorClassInfo>::iterator it = tabletData->valuatorInfo.begin(),
1245 ite = tabletData->valuatorInfo.end(); it != ite; ++it) {
1246 int valuator = it.key();
1247 TabletData::ValuatorClassInfo &classInfo(it.value());
1248 xi2GetValuatorValueIfSet(event, classInfo.number, &classInfo.curVal);
1249 double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal);
1250 switch (valuator) {
1251 case QXcbAtom::AbsX:
1252 if (Q_LIKELY(useValuators)) {
1253 const qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.x(), physicalScreenArea.width());
1254 global.setX(value);
1255 local.setX(value - window->handle()->geometry().x());
1256 }
1257 break;
1258 case QXcbAtom::AbsY:
1259 if (Q_LIKELY(useValuators)) {
1260 qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.y(), physicalScreenArea.height());
1261 global.setY(value);
1262 local.setY(value - window->handle()->geometry().y());
1263 }
1264 break;
1265 case QXcbAtom::AbsPressure:
1266 pressure = normalizedValue;
1267 break;
1268 case QXcbAtom::AbsTiltX:
1269 xTilt = classInfo.curVal;
1270 break;
1271 case QXcbAtom::AbsTiltY:
1272 yTilt = classInfo.curVal;
1273 break;
1274 case QXcbAtom::AbsWheel:
1275 switch (tabletData->tool) {
1276 case QTabletEvent::Airbrush:
1277 tangentialPressure = normalizedValue * 2.0 - 1.0; // Convert 0..1 range to -1..+1 range
1278 break;
1279 case QTabletEvent::RotationStylus:
1280 rotation = normalizedValue * 360.0 - 180.0; // Convert 0..1 range to -180..+180 degrees
1281 break;
1282 default: // Other types of styli do not use this valuator
1283 break;
1284 }
1285 break;
1286 default:
1287 break;
1288 }
1289 }
1290
1291 if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
1292 qCDebug(lcQpaXInputEvents, "XI2 event on tablet %d with tool %s type %s seq %d detail %d time %d "
1293 "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",
1294 tabletData->deviceId, toolName(tabletData->tool), pointerTypeName(tabletData->pointerType),
1295 ev->sequence, ev->detail, ev->time,
1296 local.x(), local.y(), global.x(), global.y(),
1297 (int)tabletData->buttons, pressure, xTilt, yTilt, rotation, (int)modifiers);
1298
1299 QWindowSystemInterface::handleTabletEvent(window, ev->time, local, global,
1300 tabletData->tool, tabletData->pointerType,
1301 tabletData->buttons, pressure,
1302 xTilt, yTilt, tangentialPressure,
1303 rotation, 0, tabletData->serialId, modifiers);
1304}
1305
1306QXcbConnection::TabletData *QXcbConnection::tabletDataForDevice(int id)
1307{
1308 for (int i = 0; i < m_tabletData.count(); ++i) {
1309 if (m_tabletData.at(i).deviceId == id)
1310 return &m_tabletData[i];
1311 }
1312 return nullptr;
1313}
1314
1315#endif // QT_CONFIG(tabletevent)
1316