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