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 "QtCore/qendian.h"
47 #include <qpa/qwindowsysteminterface_p.h>
48 #include <QDebug>
49 #include <cmath>
50 
51 #include <xcb/xinput.h>
52 
53 using qt_xcb_input_device_event_t = xcb_input_button_press_event_t;
54 
55 struct qt_xcb_input_event_mask_t {
56     xcb_input_event_mask_t header;
57     uint32_t               mask;
58 };
59 
xi2SelectStateEvents()60 void QXcbConnection::xi2SelectStateEvents()
61 {
62     // These state events do not depend on a specific X window, but are global
63     // for the X client's (application's) state.
64     qt_xcb_input_event_mask_t xiEventMask;
65     xiEventMask.header.deviceid = XCB_INPUT_DEVICE_ALL;
66     xiEventMask.header.mask_len = 1;
67     xiEventMask.mask = XCB_INPUT_XI_EVENT_MASK_HIERARCHY;
68     xiEventMask.mask |= XCB_INPUT_XI_EVENT_MASK_DEVICE_CHANGED;
69     xiEventMask.mask |= XCB_INPUT_XI_EVENT_MASK_PROPERTY;
70     xiEventMask.mask = qToLittleEndian(xiEventMask.mask);
71     xcb_input_xi_select_events(xcb_connection(), rootWindow(), 1, &xiEventMask.header);
72 }
73 
xi2SelectDeviceEvents(xcb_window_t window)74 void QXcbConnection::xi2SelectDeviceEvents(xcb_window_t window)
75 {
76     if (window == rootWindow())
77         return;
78 
79     uint32_t bitMask = XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS;
80     bitMask |= XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
81     bitMask |= XCB_INPUT_XI_EVENT_MASK_MOTION;
82     // There is a check for enter/leave events in plain xcb enter/leave event handler,
83     // core enter/leave events will be ignored in this case.
84     bitMask |= XCB_INPUT_XI_EVENT_MASK_ENTER;
85     bitMask |= XCB_INPUT_XI_EVENT_MASK_LEAVE;
86     if (isAtLeastXI22()) {
87         bitMask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN;
88         bitMask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE;
89         bitMask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_END;
90     }
91 
92     qt_xcb_input_event_mask_t mask;
93     mask.header.deviceid = XCB_INPUT_DEVICE_ALL_MASTER;
94     mask.header.mask_len = 1;
95     mask.mask = qToLittleEndian(bitMask);
96     xcb_void_cookie_t cookie =
97             xcb_input_xi_select_events_checked(xcb_connection(), window, 1, &mask.header);
98     xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
99     if (error) {
100         qCDebug(lcQpaXInput, "failed to select events, window %x, error code %d", window, error->error_code);
101         free(error);
102     } else {
103         QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
104     }
105 }
106 
fixed3232ToReal(xcb_input_fp3232_t val)107 static inline qreal fixed3232ToReal(xcb_input_fp3232_t val)
108 {
109     return qreal(val.integral) + qreal(val.frac) / (1ULL << 32);
110 }
111 
xi2SetupDevice(void * info,bool removeExisting)112 void QXcbConnection::xi2SetupDevice(void *info, bool removeExisting)
113 {
114     auto *deviceInfo = reinterpret_cast<xcb_input_xi_device_info_t *>(info);
115     if (removeExisting) {
116 #if QT_CONFIG(tabletevent)
117         for (int i = 0; i < m_tabletData.count(); ++i) {
118             if (m_tabletData.at(i).deviceId == deviceInfo->deviceid) {
119                 m_tabletData.remove(i);
120                 break;
121             }
122         }
123 #endif
124         m_scrollingDevices.remove(deviceInfo->deviceid);
125         m_touchDevices.remove(deviceInfo->deviceid);
126     }
127 
128     qCDebug(lcQpaXInputDevices) << "input device " << xcb_input_xi_device_info_name(deviceInfo) << "ID" << deviceInfo->deviceid;
129 #if QT_CONFIG(tabletevent)
130     TabletData tabletData;
131 #endif
132     ScrollingDevice scrollingDevice;
133     auto classes_it = xcb_input_xi_device_info_classes_iterator(deviceInfo);
134     for (; classes_it.rem; xcb_input_device_class_next(&classes_it)) {
135         xcb_input_device_class_t *classinfo = classes_it.data;
136         switch (classinfo->type) {
137         case XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR: {
138             auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classinfo);
139             const int valuatorAtom = qatom(vci->label);
140             qCDebug(lcQpaXInputDevices) << "   has valuator" << atomName(vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms);
141 #if QT_CONFIG(tabletevent)
142             if (valuatorAtom < QXcbAtom::NAtoms) {
143                 TabletData::ValuatorClassInfo info;
144                 info.minVal = fixed3232ToReal(vci->min);
145                 info.maxVal = fixed3232ToReal(vci->max);
146                 info.number = vci->number;
147                 tabletData.valuatorInfo[valuatorAtom] = info;
148             }
149 #endif // QT_CONFIG(tabletevent)
150             if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
151                 scrollingDevice.lastScrollPosition.setX(fixed3232ToReal(vci->value));
152             else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
153                 scrollingDevice.lastScrollPosition.setY(fixed3232ToReal(vci->value));
154             break;
155         }
156         case XCB_INPUT_DEVICE_CLASS_TYPE_SCROLL: {
157             auto *sci = reinterpret_cast<xcb_input_scroll_class_t *>(classinfo);
158             if (sci->scroll_type == XCB_INPUT_SCROLL_TYPE_VERTICAL) {
159                 scrollingDevice.orientations |= Qt::Vertical;
160                 scrollingDevice.verticalIndex = sci->number;
161                 scrollingDevice.verticalIncrement = fixed3232ToReal(sci->increment);
162             } else if (sci->scroll_type == XCB_INPUT_SCROLL_TYPE_HORIZONTAL) {
163                 scrollingDevice.orientations |= Qt::Horizontal;
164                 scrollingDevice.horizontalIndex = sci->number;
165                 scrollingDevice.horizontalIncrement = fixed3232ToReal(sci->increment);
166             }
167             break;
168         }
169         case XCB_INPUT_DEVICE_CLASS_TYPE_BUTTON: {
170             auto *bci = reinterpret_cast<xcb_input_button_class_t *>(classinfo);
171             xcb_atom_t *labels = nullptr;
172             if (bci->num_buttons >= 5) {
173                 labels = xcb_input_button_class_labels(bci);
174                 xcb_atom_t label4 = labels[3];
175                 xcb_atom_t label5 = labels[4];
176                 // Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on
177                 // button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons.
178                 if ((!label4 || qatom(label4) == QXcbAtom::ButtonWheelUp || qatom(label4) == QXcbAtom::ButtonWheelDown) &&
179                     (!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(label5) == QXcbAtom::ButtonWheelDown))
180                     scrollingDevice.legacyOrientations |= Qt::Vertical;
181             }
182             if (bci->num_buttons >= 7) {
183                 xcb_atom_t label6 = labels[5];
184                 xcb_atom_t label7 = labels[6];
185                 if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight))
186                     scrollingDevice.legacyOrientations |= Qt::Horizontal;
187             }
188             qCDebug(lcQpaXInputDevices, "   has %d buttons", bci->num_buttons);
189             break;
190         }
191         case XCB_INPUT_DEVICE_CLASS_TYPE_KEY:
192             qCDebug(lcQpaXInputDevices) << "   it's a keyboard";
193             break;
194         case XCB_INPUT_DEVICE_CLASS_TYPE_TOUCH:
195             // will be handled in populateTouchDevices()
196             break;
197         default:
198             qCDebug(lcQpaXInputDevices) << "   has class" << classinfo->type;
199             break;
200         }
201     }
202     bool isTablet = false;
203 #if QT_CONFIG(tabletevent)
204     // If we have found the valuators which we expect a tablet to have, it might be a tablet.
205     if (tabletData.valuatorInfo.contains(QXcbAtom::AbsX) &&
206             tabletData.valuatorInfo.contains(QXcbAtom::AbsY) &&
207             tabletData.valuatorInfo.contains(QXcbAtom::AbsPressure))
208         isTablet = true;
209 
210     // But we need to be careful not to take the touch and tablet-button devices as tablets.
211     QByteArray name = QByteArray(xcb_input_xi_device_info_name(deviceInfo),
212                                  xcb_input_xi_device_info_name_length(deviceInfo)).toLower();
213     QString dbgType = QLatin1String("UNKNOWN");
214     if (name.contains("eraser")) {
215         isTablet = true;
216         tabletData.pointerType = QTabletEvent::Eraser;
217         dbgType = QLatin1String("eraser");
218     } else if (name.contains("cursor") && !(name.contains("cursor controls") && name.contains("trackball"))) {
219         isTablet = true;
220         tabletData.pointerType = QTabletEvent::Cursor;
221         dbgType = QLatin1String("cursor");
222     } else if (name.contains("wacom") && name.contains("finger touch")) {
223         isTablet = false;
224     } else if ((name.contains("pen") || name.contains("stylus")) && isTablet) {
225         tabletData.pointerType = QTabletEvent::Pen;
226         dbgType = QLatin1String("pen");
227     } else if (name.contains("wacom") && isTablet && !name.contains("touch")) {
228         // combined device (evdev) rather than separate pen/eraser (wacom driver)
229         tabletData.pointerType = QTabletEvent::Pen;
230         dbgType = QLatin1String("pen");
231     } else if (name.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) {
232         // some "Genius" tablets
233         isTablet = true;
234         tabletData.pointerType = QTabletEvent::Pen;
235         dbgType = QLatin1String("pen");
236     } else if (name.contains("waltop") && name.contains("tablet")) {
237         // other "Genius" tablets
238         // WALTOP International Corp. Slim Tablet
239         isTablet = true;
240         tabletData.pointerType = QTabletEvent::Pen;
241         dbgType = QLatin1String("pen");
242     } else if (name.contains("uc-logic") && isTablet) {
243         tabletData.pointerType = QTabletEvent::Pen;
244         dbgType = QLatin1String("pen");
245     } else if (name.contains("ugee")) {
246         isTablet = true;
247         tabletData.pointerType = QTabletEvent::Pen;
248         dbgType = QLatin1String("pen");
249     } else {
250         isTablet = false;
251     }
252 
253     if (isTablet) {
254         tabletData.deviceId = deviceInfo->deviceid;
255         m_tabletData.append(tabletData);
256         qCDebug(lcQpaXInputDevices) << "   it's a tablet with pointer type" << dbgType;
257     }
258 #endif // QT_CONFIG(tabletevent)
259 
260     if (scrollingDevice.orientations || scrollingDevice.legacyOrientations) {
261         scrollingDevice.deviceId = deviceInfo->deviceid;
262         // Only use legacy wheel button events when we don't have real scroll valuators.
263         scrollingDevice.legacyOrientations &= ~scrollingDevice.orientations;
264         m_scrollingDevices.insert(scrollingDevice.deviceId, scrollingDevice);
265         qCDebug(lcQpaXInputDevices) << "   it's a scrolling device";
266     }
267 
268     if (!isTablet) {
269         TouchDeviceData *dev = populateTouchDevices(deviceInfo);
270         if (dev && lcQpaXInputDevices().isDebugEnabled()) {
271             if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen)
272                 qCDebug(lcQpaXInputDevices, "   it's a touchscreen with type %d capabilities 0x%X max touch points %d",
273                         dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(),
274                         dev->qtTouchDevice->maximumTouchPoints());
275             else if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad)
276                 qCDebug(lcQpaXInputDevices, "   it's a touchpad with type %d capabilities 0x%X max touch points %d size %f x %f",
277                         dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(),
278                         dev->qtTouchDevice->maximumTouchPoints(),
279                         dev->size.width(), dev->size.height());
280         }
281     }
282 
283 }
284 
xi2SetupDevices()285 void QXcbConnection::xi2SetupDevices()
286 {
287 #if QT_CONFIG(tabletevent)
288     m_tabletData.clear();
289 #endif
290     m_scrollingDevices.clear();
291     m_touchDevices.clear();
292     m_xiMasterPointerIds.clear();
293 
294     auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), XCB_INPUT_DEVICE_ALL);
295     if (!reply) {
296         qCDebug(lcQpaXInputDevices) << "failed to query devices";
297         return;
298     }
299 
300     auto it = xcb_input_xi_query_device_infos_iterator(reply.get());
301     for (; it.rem; xcb_input_xi_device_info_next(&it)) {
302         xcb_input_xi_device_info_t *deviceInfo = it.data;
303         if (deviceInfo->type == XCB_INPUT_DEVICE_TYPE_MASTER_POINTER) {
304             m_xiMasterPointerIds.append(deviceInfo->deviceid);
305             continue;
306         }
307         // only slave pointer devices are relevant here
308         if (deviceInfo->type == XCB_INPUT_DEVICE_TYPE_SLAVE_POINTER)
309             xi2SetupDevice(deviceInfo, false);
310     }
311 
312     if (m_xiMasterPointerIds.size() > 1)
313         qCDebug(lcQpaXInputDevices) << "multi-pointer X detected";
314 }
315 
316 /*! \internal
317 
318     Notes on QT_XCB_NO_XI2_MOUSE Handling:
319 
320     Here we don't select pointer button press/release and motion events on master devices, instead
321     we select these events directly on slave devices. This means that a master device will fallback
322     to sending core events for every XI_* event that is sent directly by a slave device. For more
323     details see "Event processing for attached slave devices" in XInput2 specification. To prevent
324     handling of the same event twice, we have checks for xi2MouseEventsDisabled() in XI2 event
325     handlers (but this is somewhat inconsistent in some situations). If the purpose for
326     QT_XCB_NO_XI2_MOUSE was so that an application using QAbstractNativeEventFilter would see core
327     mouse events before they are handled by Qt then QT_XCB_NO_XI2_MOUSE won't always work as
328     expected (e.g. we handle scroll event directly from a slave device event, before an application
329     has seen the fallback core event from a master device).
330 
331     The commit introducing QT_XCB_NO_XI2_MOUSE also states that setting this envvar "restores the
332     old behavior with broken grabbing". It did not elaborate why grabbing was not fixed for this
333     code path. The issue that this envvar tries to solve seem to be less important than broken
334     grabbing (broken apparently only for touch events). Thus, if you really want core mouse events
335     in your application and do not care about broken touch, then use QT_XCB_NO_XI2 (more on this
336     below) to disable the extension all together. The reason why grabbing might have not been fixed
337     is that calling XIGrabDevice with this code path for some reason always returns AlreadyGrabbed
338     (by debugging X server's code it appears that when we call XIGrabDevice, an X server first grabs
339     pointer via core pointer and then fails to do XI2 grab with AlreadyGrabbed; disclaimer - I did
340     not debug this in great detail). When we try supporting odd setups like QT_XCB_NO_XI2_MOUSE, we
341     are asking for trouble anyways.
342 
343     In conclusion, introduction of QT_XCB_NO_XI2_MOUSE causes more issues than solves - the above
344     mentioned inconsistencies, maintenance of this code path and that QT_XCB_NO_XI2_MOUSE replaces
345     less important issue with somewhat more important issue. It also makes us to use less optimal
346     code paths in certain situations (see xi2HandleHierarchyEvent). Using of QT_XCB_NO_XI2 has its
347     drawbacks too - no tablet and touch events. So the only real fix in this case is at an
348     application side (teach the application about xcb_ge_event_t events). Based on this,
349     QT_XCB_NO_XI2_MOUSE will be removed in ### Qt 6. It should not have existed in the first place,
350     native events seen by QAbstractNativeEventFilter is not really a public API, applications should
351     expect changes at this level and do ifdefs if something changes between Qt version.
352 */
xi2SelectDeviceEventsCompatibility(xcb_window_t window)353 void QXcbConnection::xi2SelectDeviceEventsCompatibility(xcb_window_t window)
354 {
355     if (window == rootWindow())
356         return;
357 
358     uint32_t mask = 0;
359 
360     if (isAtLeastXI22()) {
361         mask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN;
362         mask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE;
363         mask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_END;
364 
365         qt_xcb_input_event_mask_t xiMask;
366         xiMask.header.deviceid = XCB_INPUT_DEVICE_ALL_MASTER;
367         xiMask.header.mask_len = 1;
368         xiMask.mask = qToLittleEndian(mask);
369 
370         xcb_void_cookie_t cookie =
371                 xcb_input_xi_select_events_checked(xcb_connection(), window, 1, &xiMask.header);
372         xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
373         if (error) {
374             qCDebug(lcQpaXInput, "failed to select events, window %x, error code %d", window, error->error_code);
375             free(error);
376         } else {
377             QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
378         }
379     }
380 
381     mask = XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS;
382     mask |= XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
383     mask |= XCB_INPUT_XI_EVENT_MASK_MOTION;
384 
385 #if QT_CONFIG(tabletevent)
386     QSet<int> tabletDevices;
387     if (!m_tabletData.isEmpty()) {
388         const int nrTablets = m_tabletData.count();
389         QVector<qt_xcb_input_event_mask_t> xiEventMask(nrTablets);
390         for (int i = 0; i < nrTablets; ++i) {
391             int deviceId = m_tabletData.at(i).deviceId;
392             tabletDevices.insert(deviceId);
393             xiEventMask[i].header.deviceid = deviceId;
394             xiEventMask[i].header.mask_len = 1;
395             xiEventMask[i].mask = qToLittleEndian(mask);
396         }
397         xcb_input_xi_select_events(xcb_connection(), window, nrTablets, &(xiEventMask.data()->header));
398     }
399 #endif
400 
401     if (!m_scrollingDevices.isEmpty()) {
402         QVector<qt_xcb_input_event_mask_t> xiEventMask(m_scrollingDevices.size());
403         int i = 0;
404         for (const ScrollingDevice& scrollingDevice : qAsConst(m_scrollingDevices)) {
405 #if QT_CONFIG(tabletevent)
406             if (tabletDevices.contains(scrollingDevice.deviceId))
407                 continue; // All necessary events are already captured.
408 #endif
409             xiEventMask[i].header.deviceid = scrollingDevice.deviceId;
410             xiEventMask[i].header.mask_len = 1;
411             xiEventMask[i].mask = qToLittleEndian(mask);
412             i++;
413         }
414         xcb_input_xi_select_events(xcb_connection(), window, i, &(xiEventMask.data()->header));
415     }
416 }
417 
touchDeviceForId(int id)418 QXcbConnection::TouchDeviceData *QXcbConnection::touchDeviceForId(int id)
419 {
420     TouchDeviceData *dev = nullptr;
421     if (m_touchDevices.contains(id))
422         dev = &m_touchDevices[id];
423     return dev;
424 }
425 
populateTouchDevices(void * info)426 QXcbConnection::TouchDeviceData *QXcbConnection::populateTouchDevices(void *info)
427 {
428     auto *deviceinfo = reinterpret_cast<xcb_input_xi_device_info_t *>(info);
429     QTouchDevice::Capabilities caps;
430     int type = -1;
431     int maxTouchPoints = 1;
432     bool isTouchDevice = false;
433     bool hasRelativeCoords = false;
434     TouchDeviceData dev;
435     auto classes_it = xcb_input_xi_device_info_classes_iterator(deviceinfo);
436     for (; classes_it.rem; xcb_input_device_class_next(&classes_it)) {
437         xcb_input_device_class_t *classinfo = classes_it.data;
438         switch (classinfo->type) {
439         case XCB_INPUT_DEVICE_CLASS_TYPE_TOUCH: {
440             auto *tci = reinterpret_cast<xcb_input_touch_class_t *>(classinfo);
441             maxTouchPoints = tci->num_touches;
442             qCDebug(lcQpaXInputDevices, "   has touch class with mode %d", tci->mode);
443             switch (tci->mode) {
444             case XCB_INPUT_TOUCH_MODE_DEPENDENT:
445                 type = QTouchDevice::TouchPad;
446                 break;
447             case XCB_INPUT_TOUCH_MODE_DIRECT:
448                 type = QTouchDevice::TouchScreen;
449                 break;
450             }
451             break;
452         }
453         case XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR: {
454             auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classinfo);
455             const QXcbAtom::Atom valuatorAtom = qatom(vci->label);
456             if (valuatorAtom < QXcbAtom::NAtoms) {
457                 TouchDeviceData::ValuatorClassInfo info;
458                 info.min = fixed3232ToReal(vci->min);
459                 info.max = fixed3232ToReal(vci->max);
460                 info.number = vci->number;
461                 info.label = valuatorAtom;
462                 dev.valuatorInfo.append(info);
463             }
464             // Some devices (mice) report a resolution of 0; they will be excluded later,
465             // for now just prevent a division by zero
466             const int vciResolution = vci->resolution ? vci->resolution : 1;
467             if (valuatorAtom == QXcbAtom::AbsMTPositionX)
468                 caps |= QTouchDevice::Position | QTouchDevice::NormalizedPosition;
469             else if (valuatorAtom == QXcbAtom::AbsMTTouchMajor)
470                 caps |= QTouchDevice::Area;
471             else if (valuatorAtom == QXcbAtom::AbsMTOrientation)
472                 dev.providesTouchOrientation = true;
473             else if (valuatorAtom == QXcbAtom::AbsMTPressure || valuatorAtom == QXcbAtom::AbsPressure)
474                 caps |= QTouchDevice::Pressure;
475             else if (valuatorAtom == QXcbAtom::RelX) {
476                 hasRelativeCoords = true;
477                 dev.size.setWidth((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
478             } else if (valuatorAtom == QXcbAtom::RelY) {
479                 hasRelativeCoords = true;
480                 dev.size.setHeight((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
481             } else if (valuatorAtom == QXcbAtom::AbsX) {
482                 caps |= QTouchDevice::Position;
483                 dev.size.setWidth((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
484             } else if (valuatorAtom == QXcbAtom::AbsY) {
485                 caps |= QTouchDevice::Position;
486                 dev.size.setHeight((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
487             }
488             break;
489         }
490         default:
491             break;
492         }
493     }
494     if (type < 0 && caps && hasRelativeCoords) {
495         type = QTouchDevice::TouchPad;
496         if (dev.size.width() < 10 || dev.size.height() < 10 ||
497                 dev.size.width() > 10000 || dev.size.height() > 10000)
498             dev.size = QSizeF(130, 110);
499     }
500     if (!isAtLeastXI22() || type == QTouchDevice::TouchPad)
501         caps |= QTouchDevice::MouseEmulation;
502 
503     if (type >= QTouchDevice::TouchScreen && type <= QTouchDevice::TouchPad) {
504         dev.qtTouchDevice = new QTouchDevice;
505         dev.qtTouchDevice->setName(QString::fromUtf8(xcb_input_xi_device_info_name(deviceinfo),
506                                                      xcb_input_xi_device_info_name_length(deviceinfo)));
507         dev.qtTouchDevice->setType((QTouchDevice::DeviceType)type);
508         dev.qtTouchDevice->setCapabilities(caps);
509         dev.qtTouchDevice->setMaximumTouchPoints(maxTouchPoints);
510         if (caps != 0)
511             QWindowSystemInterface::registerTouchDevice(dev.qtTouchDevice);
512         m_touchDevices[deviceinfo->deviceid] = dev;
513         isTouchDevice = true;
514     }
515 
516     return isTouchDevice ? &m_touchDevices[deviceinfo->deviceid] : nullptr;
517 }
518 
fixed1616ToReal(xcb_input_fp1616_t val)519 static inline qreal fixed1616ToReal(xcb_input_fp1616_t val)
520 {
521     return qreal(val) / 0x10000;
522 }
523 
xi2HandleEvent(xcb_ge_event_t * event)524 void 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 = nullptr;
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 
xi2MouseEventsDisabled() const610 bool 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 
isTouchScreen(int id)618 bool QXcbConnection::isTouchScreen(int id)
619 {
620     auto device = touchDeviceForId(id);
621     return device && device->qtTouchDevice->type() == QTouchDevice::TouchScreen;
622 }
623 
xi2ProcessTouch(void * xiDevEvent,QXcbWindow * platformWindow)624 void 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.edges);
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 
startSystemMoveResizeForTouch(xcb_window_t window,int edges)790 bool QXcbConnection::startSystemMoveResizeForTouch(xcb_window_t window, int edges)
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             auto pointIt = deviceData.touchPoints.constBegin();
797             for (; pointIt != deviceData.touchPoints.constEnd(); ++pointIt) {
798                 Qt::TouchPointState state = pointIt.value().state;
799                 if (state == Qt::TouchPointMoved || state == Qt::TouchPointPressed || state == Qt::TouchPointStationary) {
800                     m_startSystemMoveResizeInfo.window = window;
801                     m_startSystemMoveResizeInfo.deviceid = devIt.key();
802                     m_startSystemMoveResizeInfo.pointid = pointIt.key();
803                     m_startSystemMoveResizeInfo.edges = edges;
804                     return true;
805                 }
806             }
807         }
808     }
809     return false;
810 }
811 
abortSystemMoveResizeForTouch()812 void QXcbConnection::abortSystemMoveResizeForTouch()
813 {
814     m_startSystemMoveResizeInfo.window = XCB_NONE;
815 }
816 
xi2SetMouseGrabEnabled(xcb_window_t w,bool grab)817 bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab)
818 {
819     bool ok = false;
820 
821     if (grab) { // grab
822         uint32_t mask = XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS
823                 | XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE
824                 | XCB_INPUT_XI_EVENT_MASK_MOTION
825                 | XCB_INPUT_XI_EVENT_MASK_ENTER
826                 | XCB_INPUT_XI_EVENT_MASK_LEAVE
827                 | XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN
828                 | XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE
829                 | XCB_INPUT_XI_EVENT_MASK_TOUCH_END;
830 
831         for (int id : qAsConst(m_xiMasterPointerIds)) {
832             xcb_generic_error_t *error = nullptr;
833             auto cookie = xcb_input_xi_grab_device(xcb_connection(), w, XCB_CURRENT_TIME, XCB_CURSOR_NONE, id,
834                                                    XCB_INPUT_GRAB_MODE_22_ASYNC, XCB_INPUT_GRAB_MODE_22_ASYNC,
835                                                    false, 1, &mask);
836             auto *reply = xcb_input_xi_grab_device_reply(xcb_connection(), cookie, &error);
837             if (error) {
838                 qCDebug(lcQpaXInput, "failed to grab events for device %d on window %x"
839                                      "(error code %d)", id, w, error->error_code);
840                 free(error);
841             } else {
842                 // Managed to grab at least one of master pointers, that should be enough
843                 // to properly dismiss windows that rely on mouse grabbing.
844                 ok = true;
845             }
846             free(reply);
847         }
848     } else { // ungrab
849         for (int id : qAsConst(m_xiMasterPointerIds)) {
850             auto cookie = xcb_input_xi_ungrab_device_checked(xcb_connection(), XCB_CURRENT_TIME, id);
851             xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
852             if (error) {
853                 qCDebug(lcQpaXInput, "XIUngrabDevice failed - id: %d (error code %d)", id, error->error_code);
854                 free(error);
855             }
856         }
857         // XIUngrabDevice does not seem to wait for a reply from X server (similar to
858         // xcb_ungrab_pointer). Ungrabbing won't fail, unless NoSuchExtension error
859         // has occurred due to a programming error somewhere else in the stack. That
860         // would mean that things will crash soon anyway.
861         ok = true;
862     }
863 
864     if (ok)
865         m_xiGrab = grab;
866 
867     return ok;
868 }
869 
xi2HandleHierarchyEvent(void * event)870 void QXcbConnection::xi2HandleHierarchyEvent(void *event)
871 {
872     auto *xiEvent = reinterpret_cast<xcb_input_hierarchy_event_t *>(event);
873     // We only care about hotplugged devices
874     if (!(xiEvent->flags & (XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED | XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED)))
875         return;
876 
877     xi2SetupDevices();
878 
879     if (xi2MouseEventsDisabled()) {
880         // In compatibility mode (a.k.a xi2MouseEventsDisabled() mode) we select events for
881         // each device separately. When a new device appears, we have to select events from
882         // this device on all event-listening windows. This is not needed when events are
883         // selected via XIAllDevices/XIAllMasterDevices (as in xi2SelectDeviceEvents()).
884         for (auto it = m_mapper.cbegin(), end = m_mapper.cend(); it != end; ++it)
885             xi2SelectDeviceEventsCompatibility(it.key());
886     }
887 }
888 
xi2HandleDeviceChangedEvent(void * event)889 void QXcbConnection::xi2HandleDeviceChangedEvent(void *event)
890 {
891     auto *xiEvent = reinterpret_cast<xcb_input_device_changed_event_t *>(event);
892     switch (xiEvent->reason) {
893     case XCB_INPUT_CHANGE_REASON_DEVICE_CHANGE: {
894         auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), xiEvent->sourceid);
895         if (!reply || reply->num_infos <= 0)
896             return;
897         auto it = xcb_input_xi_query_device_infos_iterator(reply.get());
898         xi2SetupDevice(it.data);
899         break;
900     }
901     case XCB_INPUT_CHANGE_REASON_SLAVE_SWITCH: {
902         if (ScrollingDevice *scrollingDevice = scrollingDeviceForId(xiEvent->sourceid))
903             xi2UpdateScrollingDevice(*scrollingDevice);
904         break;
905     }
906     default:
907         qCDebug(lcQpaXInputEvents, "unknown device-changed-event (device %d)", xiEvent->sourceid);
908         break;
909     }
910 }
911 
xi2UpdateScrollingDevice(ScrollingDevice & scrollingDevice)912 void QXcbConnection::xi2UpdateScrollingDevice(ScrollingDevice &scrollingDevice)
913 {
914     auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), scrollingDevice.deviceId);
915     if (!reply || reply->num_infos <= 0) {
916         qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", scrollingDevice.deviceId);
917         return;
918     }
919     QPointF lastScrollPosition;
920     if (lcQpaXInputEvents().isDebugEnabled())
921         lastScrollPosition = scrollingDevice.lastScrollPosition;
922 
923     xcb_input_xi_device_info_t *deviceInfo = xcb_input_xi_query_device_infos_iterator(reply.get()).data;
924     auto classes_it = xcb_input_xi_device_info_classes_iterator(deviceInfo);
925     for (; classes_it.rem; xcb_input_device_class_next(&classes_it)) {
926         xcb_input_device_class_t *classInfo = classes_it.data;
927         if (classInfo->type == XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR) {
928             auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classInfo);
929             const int valuatorAtom = qatom(vci->label);
930             if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
931                 scrollingDevice.lastScrollPosition.setX(fixed3232ToReal(vci->value));
932             else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
933                 scrollingDevice.lastScrollPosition.setY(fixed3232ToReal(vci->value));
934         }
935     }
936     if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled() && lastScrollPosition != scrollingDevice.lastScrollPosition))
937         qCDebug(lcQpaXInputEvents, "scrolling device %d moved from (%f, %f) to (%f, %f)", scrollingDevice.deviceId,
938                 lastScrollPosition.x(), lastScrollPosition.y(),
939                 scrollingDevice.lastScrollPosition.x(),
940                 scrollingDevice.lastScrollPosition.y());
941 }
942 
xi2UpdateScrollingDevices()943 void QXcbConnection::xi2UpdateScrollingDevices()
944 {
945     QHash<int, ScrollingDevice>::iterator it = m_scrollingDevices.begin();
946     const QHash<int, ScrollingDevice>::iterator end = m_scrollingDevices.end();
947     while (it != end) {
948         xi2UpdateScrollingDevice(it.value());
949         ++it;
950     }
951 }
952 
scrollingDeviceForId(int id)953 QXcbConnection::ScrollingDevice *QXcbConnection::scrollingDeviceForId(int id)
954 {
955     ScrollingDevice *dev = nullptr;
956     if (m_scrollingDevices.contains(id))
957         dev = &m_scrollingDevices[id];
958     return dev;
959 }
960 
xi2HandleScrollEvent(void * event,ScrollingDevice & scrollingDevice)961 void QXcbConnection::xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice)
962 {
963     auto *xiDeviceEvent = reinterpret_cast<qt_xcb_input_device_event_t *>(event);
964 
965     if (xiDeviceEvent->event_type == XCB_INPUT_MOTION && scrollingDevice.orientations) {
966         if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) {
967             QPoint rawDelta;
968             QPoint angleDelta;
969             double value;
970             if (scrollingDevice.orientations & Qt::Vertical) {
971                 if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.verticalIndex, &value)) {
972                     double delta = scrollingDevice.lastScrollPosition.y() - value;
973                     scrollingDevice.lastScrollPosition.setY(value);
974                     angleDelta.setY((delta / scrollingDevice.verticalIncrement) * 120);
975                     // With most drivers the increment is 1 for wheels.
976                     // For libinput it is hardcoded to a useless 15.
977                     // For a proper touchpad driver it should be in the same order of magnitude as 120
978                     if (scrollingDevice.verticalIncrement > 15)
979                         rawDelta.setY(delta);
980                     else if (scrollingDevice.verticalIncrement < -15)
981                         rawDelta.setY(-delta);
982                 }
983             }
984             if (scrollingDevice.orientations & Qt::Horizontal) {
985                 if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.horizontalIndex, &value)) {
986                     double delta = scrollingDevice.lastScrollPosition.x() - value;
987                     scrollingDevice.lastScrollPosition.setX(value);
988                     angleDelta.setX((delta / scrollingDevice.horizontalIncrement) * 120);
989                     // See comment under vertical
990                     if (scrollingDevice.horizontalIncrement > 15)
991                         rawDelta.setX(delta);
992                     else if (scrollingDevice.horizontalIncrement < -15)
993                         rawDelta.setX(-delta);
994                 }
995             }
996             if (!angleDelta.isNull()) {
997                 QPoint local(fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y));
998                 QPoint global(fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y));
999                 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(xiDeviceEvent->mods.effective);
1000                 if (modifiers & Qt::AltModifier) {
1001                     angleDelta = angleDelta.transposed();
1002                     rawDelta = rawDelta.transposed();
1003                 }
1004                 qCDebug(lcQpaXInputEvents) << "scroll wheel @ window pos" << local << "delta px" << rawDelta << "angle" << angleDelta;
1005                 QWindowSystemInterface::handleWheelEvent(platformWindow->window(), xiDeviceEvent->time, local, global, rawDelta, angleDelta, modifiers);
1006             }
1007         }
1008     } else if (xiDeviceEvent->event_type == XCB_INPUT_BUTTON_RELEASE && scrollingDevice.legacyOrientations) {
1009         if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) {
1010             QPoint angleDelta;
1011             if (scrollingDevice.legacyOrientations & Qt::Vertical) {
1012                 if (xiDeviceEvent->detail == 4)
1013                     angleDelta.setY(120);
1014                 else if (xiDeviceEvent->detail == 5)
1015                     angleDelta.setY(-120);
1016             }
1017             if (scrollingDevice.legacyOrientations & Qt::Horizontal) {
1018                 if (xiDeviceEvent->detail == 6)
1019                     angleDelta.setX(120);
1020                 else if (xiDeviceEvent->detail == 7)
1021                     angleDelta.setX(-120);
1022             }
1023             if (!angleDelta.isNull()) {
1024                 QPoint local(fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y));
1025                 QPoint global(fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y));
1026                 Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(xiDeviceEvent->mods.effective);
1027                 if (modifiers & Qt::AltModifier)
1028                     angleDelta = angleDelta.transposed();
1029                 qCDebug(lcQpaXInputEvents) << "scroll wheel (button" << xiDeviceEvent->detail << ") @ window pos" << local << "delta angle" << angleDelta;
1030                 QWindowSystemInterface::handleWheelEvent(platformWindow->window(), xiDeviceEvent->time, local, global, QPoint(), angleDelta, modifiers);
1031             }
1032         }
1033     }
1034 }
1035 
xi2ValuatorOffset(const unsigned char * maskPtr,int maskLen,int number)1036 static int xi2ValuatorOffset(const unsigned char *maskPtr, int maskLen, int number)
1037 {
1038     int offset = 0;
1039     for (int i = 0; i < maskLen; i++) {
1040         if (number < 8) {
1041             if ((maskPtr[i] & (1 << number)) == 0)
1042                 return -1;
1043         }
1044         for (int j = 0; j < 8; j++) {
1045             if (j == number)
1046                 return offset;
1047             if (maskPtr[i] & (1 << j))
1048                 offset++;
1049         }
1050         number -= 8;
1051     }
1052     return -1;
1053 }
1054 
xi2GetValuatorValueIfSet(const void * event,int valuatorNum,double * value)1055 bool QXcbConnection::xi2GetValuatorValueIfSet(const void *event, int valuatorNum, double *value)
1056 {
1057     auto *xideviceevent = static_cast<const qt_xcb_input_device_event_t *>(event);
1058     auto *buttonsMaskAddr = reinterpret_cast<const unsigned char *>(&xideviceevent[1]);
1059     auto *valuatorsMaskAddr = buttonsMaskAddr + xideviceevent->buttons_len * 4;
1060     auto *valuatorsValuesAddr = reinterpret_cast<const xcb_input_fp3232_t *>(valuatorsMaskAddr + xideviceevent->valuators_len * 4);
1061 
1062     int valuatorOffset = xi2ValuatorOffset(valuatorsMaskAddr, xideviceevent->valuators_len, valuatorNum);
1063     if (valuatorOffset < 0)
1064         return false;
1065 
1066     *value = valuatorsValuesAddr[valuatorOffset].integral;
1067     *value += ((double)valuatorsValuesAddr[valuatorOffset].frac / (1 << 16) / (1 << 16));
1068     return true;
1069 }
1070 
xiToQtMouseButton(uint32_t b)1071 Qt::MouseButton QXcbConnection::xiToQtMouseButton(uint32_t b)
1072 {
1073     switch (b) {
1074     case 1: return Qt::LeftButton;
1075     case 2: return Qt::MiddleButton;
1076     case 3: return Qt::RightButton;
1077     // 4-7 are for scrolling
1078     default: break;
1079     }
1080     if (b >= 8 && b <= Qt::MaxMouseButton)
1081         return static_cast<Qt::MouseButton>(Qt::BackButton << (b - 8));
1082     return Qt::NoButton;
1083 }
1084 
1085 #if QT_CONFIG(tabletevent)
toolIdToTabletDevice(quint32 toolId)1086 static QTabletEvent::TabletDevice toolIdToTabletDevice(quint32 toolId) {
1087     // keep in sync with wacom_intuos_inout() in Linux kernel driver wacom_wac.c
1088     switch (toolId) {
1089     case 0xd12:
1090     case 0x912:
1091     case 0x112:
1092     case 0x913: /* Intuos3 Airbrush */
1093     case 0x91b: /* Intuos3 Airbrush Eraser */
1094     case 0x902: /* Intuos4/5 13HD/24HD Airbrush */
1095     case 0x90a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
1096     case 0x100902: /* Intuos4/5 13HD/24HD Airbrush */
1097     case 0x10090a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
1098         return QTabletEvent::Airbrush;
1099     case 0x007: /* Mouse 4D and 2D */
1100     case 0x09c:
1101     case 0x094:
1102         return QTabletEvent::FourDMouse;
1103     case 0x017: /* Intuos3 2D Mouse */
1104     case 0x806: /* Intuos4 Mouse */
1105     case 0x096: /* Lens cursor */
1106     case 0x097: /* Intuos3 Lens cursor */
1107     case 0x006: /* Intuos4 Lens cursor */
1108         return QTabletEvent::Puck;
1109     case 0x885:    /* Intuos3 Art Pen (Marker Pen) */
1110     case 0x100804: /* Intuos4/5 13HD/24HD Art Pen */
1111     case 0x10080c: /* Intuos4/5 13HD/24HD Art Pen Eraser */
1112         return QTabletEvent::RotationStylus;
1113     case 0:
1114         return QTabletEvent::NoDevice;
1115     }
1116     return QTabletEvent::Stylus;  // Safe default assumption if nonzero
1117 }
1118 
toolName(QTabletEvent::TabletDevice tool)1119 static const char *toolName(QTabletEvent::TabletDevice tool) {
1120     static const QMetaObject *metaObject = qt_getEnumMetaObject(tool);
1121     static const QMetaEnum me = metaObject->enumerator(metaObject->indexOfEnumerator(qt_getEnumName(tool)));
1122     return me.valueToKey(tool);
1123 }
1124 
pointerTypeName(QTabletEvent::PointerType ptype)1125 static const char *pointerTypeName(QTabletEvent::PointerType ptype) {
1126     static const QMetaObject *metaObject = qt_getEnumMetaObject(ptype);
1127     static const QMetaEnum me = metaObject->enumerator(metaObject->indexOfEnumerator(qt_getEnumName(ptype)));
1128     return me.valueToKey(ptype);
1129 }
1130 
xi2HandleTabletEvent(const void * event,TabletData * tabletData)1131 bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletData)
1132 {
1133     bool handled = true;
1134     const auto *xiDeviceEvent = reinterpret_cast<const qt_xcb_input_device_event_t *>(event);
1135 
1136     switch (xiDeviceEvent->event_type) {
1137     case XCB_INPUT_BUTTON_PRESS: {
1138         Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail);
1139         tabletData->buttons |= b;
1140         xi2ReportTabletEvent(event, tabletData);
1141         break;
1142     }
1143     case XCB_INPUT_BUTTON_RELEASE: {
1144         Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail);
1145         tabletData->buttons ^= b;
1146         xi2ReportTabletEvent(event, tabletData);
1147         break;
1148     }
1149     case XCB_INPUT_MOTION:
1150         xi2ReportTabletEvent(event, tabletData);
1151         break;
1152     case XCB_INPUT_PROPERTY: {
1153         // This is the wacom driver's way of reporting tool proximity.
1154         // The evdev driver doesn't do it this way.
1155         const auto *ev = reinterpret_cast<const xcb_input_property_event_t *>(event);
1156         if (ev->what == XCB_INPUT_PROPERTY_FLAG_MODIFIED) {
1157             if (ev->property == atom(QXcbAtom::WacomSerialIDs)) {
1158                 enum WacomSerialIndex {
1159                     _WACSER_USB_ID = 0,
1160                     _WACSER_LAST_TOOL_SERIAL,
1161                     _WACSER_LAST_TOOL_ID,
1162                     _WACSER_TOOL_SERIAL,
1163                     _WACSER_TOOL_ID,
1164                     _WACSER_COUNT
1165                 };
1166 
1167                 auto reply = Q_XCB_REPLY(xcb_input_xi_get_property, xcb_connection(), tabletData->deviceId, 0,
1168                                          ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, 100);
1169                 if (reply) {
1170                     if (reply->type == atom(QXcbAtom::INTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) {
1171                         quint32 *ptr = reinterpret_cast<quint32 *>(xcb_input_xi_get_property_items(reply.get()));
1172                         quint32 tool = ptr[_WACSER_TOOL_ID];
1173                         // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
1174                         // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
1175                         if (!tool && ptr[_WACSER_TOOL_SERIAL])
1176                             tool = ptr[_WACSER_TOOL_SERIAL];
1177 
1178                         // The property change event informs us which tool is in proximity or which one left proximity.
1179                         if (tool) {
1180                             tabletData->inProximity = true;
1181                             tabletData->tool = toolIdToTabletDevice(tool);
1182                             tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_TOOL_SERIAL]);
1183                             QWindowSystemInterface::handleTabletEnterProximityEvent(ev->time,
1184                                 tabletData->tool, tabletData->pointerType, tabletData->serialId);
1185                         } else {
1186                             tabletData->inProximity = false;
1187                             tabletData->tool = toolIdToTabletDevice(ptr[_WACSER_LAST_TOOL_ID]);
1188                             // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
1189                             // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
1190                             if (!tabletData->tool)
1191                                 tabletData->tool = toolIdToTabletDevice(ptr[_WACSER_LAST_TOOL_SERIAL]);
1192                             tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_LAST_TOOL_SERIAL]);
1193                             QWindowSystemInterface::handleTabletLeaveProximityEvent(ev->time,
1194                                 tabletData->tool, tabletData->pointerType, tabletData->serialId);
1195                         }
1196                         // TODO maybe have a hash of tabletData->deviceId to device data so we can
1197                         // look up the tablet name here, and distinguish multiple tablets
1198                         if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
1199                             qCDebug(lcQpaXInputEvents, "XI2 proximity change on tablet %d (USB %x): last tool: %x id %x current tool: %x id %x %s",
1200                                     tabletData->deviceId, ptr[_WACSER_USB_ID], ptr[_WACSER_LAST_TOOL_SERIAL], ptr[_WACSER_LAST_TOOL_ID],
1201                                     ptr[_WACSER_TOOL_SERIAL], ptr[_WACSER_TOOL_ID], toolName(tabletData->tool));
1202                     }
1203                 }
1204             }
1205         }
1206         break;
1207     }
1208     default:
1209         handled = false;
1210         break;
1211     }
1212 
1213     return handled;
1214 }
1215 
scaleOneValuator(qreal normValue,qreal screenMin,qreal screenSize)1216 inline qreal scaleOneValuator(qreal normValue, qreal screenMin, qreal screenSize)
1217 {
1218     return screenMin + normValue * screenSize;
1219 }
1220 
xi2ReportTabletEvent(const void * event,TabletData * tabletData)1221 void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletData)
1222 {
1223     auto *ev = reinterpret_cast<const qt_xcb_input_device_event_t *>(event);
1224     QXcbWindow *xcbWindow = platformWindowFromId(ev->event);
1225     if (!xcbWindow)
1226         return;
1227     QWindow *window = xcbWindow->window();
1228     const Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(ev->mods.effective);
1229     QPointF local(fixed1616ToReal(ev->event_x), fixed1616ToReal(ev->event_y));
1230     QPointF global(fixed1616ToReal(ev->root_x), fixed1616ToReal(ev->root_y));
1231     double pressure = 0, rotation = 0, tangentialPressure = 0;
1232     int xTilt = 0, yTilt = 0;
1233     static const bool useValuators = !qEnvironmentVariableIsSet("QT_XCB_TABLET_LEGACY_COORDINATES");
1234 
1235     // Valuators' values are relative to the physical size of the current virtual
1236     // screen. Therefore we cannot use QScreen/QWindow geometry and should use
1237     // QPlatformWindow/QPlatformScreen instead.
1238     QRect physicalScreenArea;
1239     if (Q_LIKELY(useValuators)) {
1240         const QList<QPlatformScreen *> siblings = window->screen()->handle()->virtualSiblings();
1241         for (const QPlatformScreen *screen : siblings)
1242             physicalScreenArea |= screen->geometry();
1243     }
1244 
1245     for (QHash<int, TabletData::ValuatorClassInfo>::iterator it = tabletData->valuatorInfo.begin(),
1246             ite = tabletData->valuatorInfo.end(); it != ite; ++it) {
1247         int valuator = it.key();
1248         TabletData::ValuatorClassInfo &classInfo(it.value());
1249         xi2GetValuatorValueIfSet(event, classInfo.number, &classInfo.curVal);
1250         double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal);
1251         switch (valuator) {
1252         case QXcbAtom::AbsX:
1253             if (Q_LIKELY(useValuators)) {
1254                 const qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.x(), physicalScreenArea.width());
1255                 global.setX(value);
1256                 // mapFromGlobal is ok for nested/embedded windows, but works only with whole-number QPoint;
1257                 // so map it first, then add back the sub-pixel position
1258                 local.setX(window->mapFromGlobal(QPoint(int(value), 0)).x() + (value - int(value)));
1259             }
1260             break;
1261         case QXcbAtom::AbsY:
1262             if (Q_LIKELY(useValuators)) {
1263                 qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.y(), physicalScreenArea.height());
1264                 global.setY(value);
1265                 local.setY(window->mapFromGlobal(QPoint(0, int(value))).y() + (value - int(value)));
1266             }
1267             break;
1268         case QXcbAtom::AbsPressure:
1269             pressure = normalizedValue;
1270             break;
1271         case QXcbAtom::AbsTiltX:
1272             xTilt = classInfo.curVal;
1273             break;
1274         case QXcbAtom::AbsTiltY:
1275             yTilt = classInfo.curVal;
1276             break;
1277         case QXcbAtom::AbsWheel:
1278             switch (tabletData->tool) {
1279             case QTabletEvent::Airbrush:
1280                 tangentialPressure = normalizedValue * 2.0 - 1.0; // Convert 0..1 range to -1..+1 range
1281                 break;
1282             case QTabletEvent::RotationStylus:
1283                 rotation = normalizedValue * 360.0 - 180.0; // Convert 0..1 range to -180..+180 degrees
1284                 break;
1285             default:    // Other types of styli do not use this valuator
1286                 break;
1287             }
1288             break;
1289         default:
1290             break;
1291         }
1292     }
1293 
1294     if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
1295         qCDebug(lcQpaXInputEvents, "XI2 event on tablet %d with tool %s type %s seq %d detail %d time %d "
1296             "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",
1297             tabletData->deviceId, toolName(tabletData->tool), pointerTypeName(tabletData->pointerType),
1298             ev->sequence, ev->detail, ev->time,
1299             local.x(), local.y(), global.x(), global.y(),
1300             (int)tabletData->buttons, pressure, xTilt, yTilt, rotation, (int)modifiers);
1301 
1302     QWindowSystemInterface::handleTabletEvent(window, ev->time, local, global,
1303                                               tabletData->tool, tabletData->pointerType,
1304                                               tabletData->buttons, pressure,
1305                                               xTilt, yTilt, tangentialPressure,
1306                                               rotation, 0, tabletData->serialId, modifiers);
1307 }
1308 
tabletDataForDevice(int id)1309 QXcbConnection::TabletData *QXcbConnection::tabletDataForDevice(int id)
1310 {
1311     for (int i = 0; i < m_tabletData.count(); ++i) {
1312         if (m_tabletData.at(i).deviceId == id)
1313             return &m_tabletData[i];
1314     }
1315     return nullptr;
1316 }
1317 
1318 #endif // QT_CONFIG(tabletevent)
1319