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 
52 using qt_xcb_input_device_event_t = xcb_input_button_press_event_t;
53 
54 struct qt_xcb_input_event_mask_t {
55     xcb_input_event_mask_t header;
56     uint32_t               mask;
57 };
58 
xi2SelectStateEvents()59 void 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 
xi2SelectDeviceEvents(xcb_window_t window)72 void 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 
fixed3232ToReal(xcb_input_fp3232_t val)105 static inline qreal fixed3232ToReal(xcb_input_fp3232_t val)
106 {
107     return qreal(val.integral) + qreal(val.frac) / (1ULL << 32);
108 }
109 
xi2SetupDevice(void * info,bool removeExisting)110 void 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 = nullptr;
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 
xi2SetupDevices()283 void 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 */
xi2SelectDeviceEventsCompatibility(xcb_window_t window)351 void 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 
touchDeviceForId(int id)416 QXcbConnection::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 
populateTouchDevices(void * info)424 QXcbConnection::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(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 
fixed1616ToReal(xcb_input_fp1616_t val)517 static inline qreal fixed1616ToReal(xcb_input_fp1616_t val)
518 {
519     return qreal(val) / 0x10000;
520 }
521 
xi2HandleEvent(xcb_ge_event_t * event)522 void 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(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(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(sourceDeviceId);
568         if (tablet && xi2HandleTabletEvent(event, tablet))
569             return;
570     }
571 #endif // QT_CONFIG(tabletevent)
572 
573     if (ScrollingDevice *device = scrollingDeviceForId(sourceDeviceId))
574         xi2HandleScrollEvent(event, *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(xiDeviceEvent->event))
595                 xi2ProcessTouch(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 
xi2MouseEventsDisabled() const608 bool QXcbConnection::xi2MouseEventsDisabled() const
609 {
610     static bool xi2MouseDisabled = qEnvironmentVariableIsSet("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 
isTouchScreen(int id)616 bool QXcbConnection::isTouchScreen(int id)
617 {
618     auto device = touchDeviceForId(id);
619     return device && device->qtTouchDevice->type() == QTouchDevice::TouchScreen;
620 }
621 
xi2ProcessTouch(void * xiDevEvent,QXcbWindow * platformWindow)622 void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindow)
623 {
624     auto *xiDeviceEvent = reinterpret_cast<xcb_input_touch_begin_event_t *>(xiDevEvent);
625     TouchDeviceData *dev = touchDeviceForId(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(xiDeviceEvent->root_x);
638     qreal y = fixed1616ToReal(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(dev->valuatorInfo)) {
643         double value;
644         if (!xi2GetValuatorValueIfSet(xiDeviceEvent, vci.number, &value))
645             continue;
646         if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
647             qCDebug(lcQpaXInputEvents, "   valuator %20s value %lf from range %lf -> %lf",
648                     atomName(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(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(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(value);
681             while (value > vci.max)
682                 value -= 2 * vci.max;
683             value = qAbs(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(touchPoint.area.width(), touchPoint.area.height());
707             if (h == 0.0)
708                 h = qMin(touchPoint.area.width(), touchPoint.area.height());
709             if (majorAxisIsY)
710                 qSwap(w, 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(touchPoint.id, 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(xcb_connection(), XCB_CURRENT_TIME, xiDeviceEvent->deviceid,
726                                       XCB_INPUT_EVENT_MODE_ACCEPT_TOUCH,
727                                       xiDeviceEvent->detail, xiDeviceEvent->event);
728         }
729         break;
730     case XCB_INPUT_TOUCH_UPDATE:
731         if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad && dev->pointPressedPosition.value(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(m_startSystemMoveResizeInfo.window);
750             if (window) {
751                 xcb_input_xi_allow_events(xcb_connection(), XCB_CURRENT_TIME, xiDeviceEvent->deviceid,
752                                           XCB_INPUT_EVENT_MODE_REJECT_TOUCH,
753                                           xiDeviceEvent->detail, xiDeviceEvent->event);
754                 window->doStartSystemMoveResize(QPoint(x, y), 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(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(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(xiDeviceEvent->mods.effective);
778     QWindowSystemInterface::handleTouchEvent(platformWindow->window(), xiDeviceEvent->time, dev->qtTouchDevice, dev->touchPoints.values(), 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(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 
startSystemMoveResizeForTouch(xcb_window_t window,int edges)788 bool 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 
abortSystemMoveResizeForTouch()810 void QXcbConnection::abortSystemMoveResizeForTouch()
811 {
812     m_startSystemMoveResizeInfo.window = XCB_NONE;
813 }
814 
xi2SetMouseGrabEnabled(xcb_window_t w,bool grab)815 bool 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(m_xiMasterPointerIds)) {
830             xcb_generic_error_t *error = nullptr;
831             auto cookie = xcb_input_xi_grab_device(xcb_connection(), w, XCB_CURRENT_TIME, XCB_CURSOR_NONE, id,
832                                                    XCB_INPUT_GRAB_MODE_22_ASYNC, XCB_INPUT_GRAB_MODE_22_ASYNC,
833                                                    false, 1, &mask);
834             auto *reply = xcb_input_xi_grab_device_reply(xcb_connection(), cookie, &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(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(reply);
845         }
846     } else { // ungrab
847         for (int id : qAsConst(m_xiMasterPointerIds)) {
848             auto cookie = xcb_input_xi_ungrab_device_checked(xcb_connection(), XCB_CURRENT_TIME, id);
849             xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
850             if (error) {
851                 qCDebug(lcQpaXInput, "XIUngrabDevice failed - id: %d (error code %d)", id, error->error_code);
852                 free(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 
xi2HandleHierarchyEvent(void * event)868 void 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(it.key());
884     }
885 }
886 
xi2HandleDeviceChangedEvent(void * event)887 void 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(reply.get());
896         xi2SetupDevice(it.data);
897         break;
898     }
899     case XCB_INPUT_CHANGE_REASON_SLAVE_SWITCH: {
900         if (ScrollingDevice *scrollingDevice = scrollingDeviceForId(xiEvent->sourceid))
901             xi2UpdateScrollingDevice(*scrollingDevice);
902         break;
903     }
904     default:
905         qCDebug(lcQpaXInputEvents, "unknown device-changed-event (device %d)", xiEvent->sourceid);
906         break;
907     }
908 }
909 
xi2UpdateScrollingDevice(ScrollingDevice & scrollingDevice)910 void 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(reply.get()).data;
922     auto classes_it = xcb_input_xi_device_info_classes_iterator(deviceInfo);
923     for (; classes_it.rem; xcb_input_device_class_next(&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(vci->label);
928             if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
929                 scrollingDevice.lastScrollPosition.setX(fixed3232ToReal(vci->value));
930             else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
931                 scrollingDevice.lastScrollPosition.setY(fixed3232ToReal(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 
xi2UpdateScrollingDevices()941 void 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(it.value());
947         ++it;
948     }
949 }
950 
scrollingDeviceForId(int id)951 QXcbConnection::ScrollingDevice *QXcbConnection::scrollingDeviceForId(int id)
952 {
953     ScrollingDevice *dev = nullptr;
954     if (m_scrollingDevices.contains(id))
955         dev = &m_scrollingDevices[id];
956     return dev;
957 }
958 
xi2HandleScrollEvent(void * event,ScrollingDevice & scrollingDevice)959 void 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(xiDeviceEvent->event)) {
965             QPoint rawDelta;
966             QPoint angleDelta;
967             double value;
968             if (scrollingDevice.orientations & Qt::Vertical) {
969                 if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.verticalIndex, &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(xiDeviceEvent, scrollingDevice.horizontalIndex, &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(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y));
996                 QPoint global(fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y));
997                 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(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(platformWindow->window(), xiDeviceEvent->time, local, global, rawDelta, angleDelta, modifiers);
1004             }
1005         }
1006     } else if (xiDeviceEvent->event_type == XCB_INPUT_BUTTON_RELEASE && scrollingDevice.legacyOrientations) {
1007         if (QXcbWindow *platformWindow = platformWindowFromId(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(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y));
1023                 QPoint global(fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y));
1024                 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(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(platformWindow->window(), xiDeviceEvent->time, local, global, QPoint(), angleDelta, modifiers);
1029             }
1030         }
1031     }
1032 }
1033 
xi2ValuatorOffset(const unsigned char * maskPtr,int maskLen,int number)1034 static 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 
xi2GetValuatorValueIfSet(const void * event,int valuatorNum,double * value)1053 bool 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(valuatorsMaskAddr, xideviceevent->valuators_len, 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 
xiToQtMouseButton(uint32_t b)1069 Qt::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)
toolIdToTabletDevice(quint32 toolId)1084 static 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 
toolName(QTabletEvent::TabletDevice tool)1117 static const char *toolName(QTabletEvent::TabletDevice tool) {
1118     static const QMetaObject *metaObject = qt_getEnumMetaObject(tool);
1119     static const QMetaEnum me = metaObject->enumerator(metaObject->indexOfEnumerator(qt_getEnumName(tool)));
1120     return me.valueToKey(tool);
1121 }
1122 
pointerTypeName(QTabletEvent::PointerType ptype)1123 static const char *pointerTypeName(QTabletEvent::PointerType ptype) {
1124     static const QMetaObject *metaObject = qt_getEnumMetaObject(ptype);
1125     static const QMetaEnum me = metaObject->enumerator(metaObject->indexOfEnumerator(qt_getEnumName(ptype)));
1126     return me.valueToKey(ptype);
1127 }
1128 
xi2HandleTabletEvent(const void * event,TabletData * tabletData)1129 bool 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(xiDeviceEvent->detail);
1137         tabletData->buttons |= b;
1138         xi2ReportTabletEvent(event, tabletData);
1139         break;
1140     }
1141     case XCB_INPUT_BUTTON_RELEASE: {
1142         Qt::MouseButton b = xiToQtMouseButton(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(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(QXcbAtom::INTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) {
1169                         quint32 *ptr = reinterpret_cast<quint32 *>(xcb_input_xi_get_property_items(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(tool);
1180                             tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_TOOL_SERIAL]);
1181                             QWindowSystemInterface::handleTabletEnterProximityEvent(ev->time,
1182                                 tabletData->tool, tabletData->pointerType, tabletData->serialId);
1183                         } else {
1184                             tabletData->inProximity = false;
1185                             tabletData->tool = toolIdToTabletDevice(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(ptr[_WACSER_LAST_TOOL_SERIAL]);
1190                             tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_LAST_TOOL_SERIAL]);
1191                             QWindowSystemInterface::handleTabletLeaveProximityEvent(ev->time,
1192                                 tabletData->tool, tabletData->pointerType, 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 
scaleOneValuator(qreal normValue,qreal screenMin,qreal screenSize)1214 inline qreal scaleOneValuator(qreal normValue, qreal screenMin, qreal screenSize)
1215 {
1216     return screenMin + normValue * screenSize;
1217 }
1218 
xi2ReportTabletEvent(const void * event,TabletData * tabletData)1219 void 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(ev->event);
1223     if (!xcbWindow)
1224         return;
1225     QWindow *window = xcbWindow->window();
1226     const Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(ev->mods.effective);
1227     QPointF local(fixed1616ToReal(ev->event_x), fixed1616ToReal(ev->event_y));
1228     QPointF global(fixed1616ToReal(ev->root_x), fixed1616ToReal(ev->root_y));
1229     double pressure = 0, rotation = 0, tangentialPressure = 0;
1230     int xTilt = 0, yTilt = 0;
1231     static const bool useValuators = !qEnvironmentVariableIsSet("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, classInfo.number, &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(normalizedValue, physicalScreenArea.x(), 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(QPoint(int(value), 0)).x() + (value - int(value)));
1257             }
1258             break;
1259         case QXcbAtom::AbsY:
1260             if (Q_LIKELY(useValuators)) {
1261                 qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.y(), physicalScreenArea.height());
1262                 global.setY(value);
1263                 local.setY(window->mapFromGlobal(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, ev->time, local, global,
1301                                               tabletData->tool, tabletData->pointerType,
1302                                               tabletData->buttons, pressure,
1303                                               xTilt, yTilt, tangentialPressure,
1304                                               rotation, 0, tabletData->serialId, modifiers);
1305 }
1306 
tabletDataForDevice(int id)1307 QXcbConnection::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