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