~gabriel1984sibiu/minitube/qt5.6

« back to all changes in this revision

Viewing changes to src/plugins/platforms/xcb/qxcbconnection_xi2.cpp

  • Committer: Grevutiu Gabriel
  • Date: 2017-06-13 08:43:17 UTC
  • Revision ID: gabriel1984sibiu@gmail.com-20170613084317-ek0zqe0u9g3ocvi8
OriginalĀ upstreamĀ code

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 <qpa/qwindowsysteminterface_p.h>
 
46
#include <QDebug>
 
47
#include <cmath>
 
48
 
 
49
#ifdef XCB_USE_XINPUT2
 
50
 
 
51
#include <X11/extensions/XInput2.h>
 
52
#include <X11/extensions/XI2proto.h>
 
53
 
 
54
struct XInput2TouchDeviceData {
 
55
    XInput2TouchDeviceData()
 
56
    : xiDeviceInfo(0)
 
57
    , qtTouchDevice(0)
 
58
    , providesTouchOrientation(false)
 
59
    {
 
60
    }
 
61
    XIDeviceInfo *xiDeviceInfo;
 
62
    QTouchDevice *qtTouchDevice;
 
63
    QHash<int, QWindowSystemInterface::TouchPoint> touchPoints;
 
64
 
 
65
    // Stuff that is relevant only for touchpads
 
66
    QHash<int, QPointF> pointPressedPosition; // in screen coordinates where each point was pressed
 
67
    QPointF firstPressedPosition;        // in screen coordinates where the first point was pressed
 
68
    QPointF firstPressedNormalPosition;  // device coordinates (0 to 1, 0 to 1) where the first point was pressed
 
69
    QSizeF size;                         // device size in mm
 
70
    bool providesTouchOrientation;
 
71
};
 
72
 
 
73
void QXcbConnection::initializeXInput2()
 
74
{
 
75
    // TODO Qt 6 (or perhaps earlier): remove these redundant env variables
 
76
    if (qEnvironmentVariableIsSet("QT_XCB_DEBUG_XINPUT"))
 
77
        const_cast<QLoggingCategory&>(lcQpaXInput()).setEnabled(QtDebugMsg, true);
 
78
    if (qEnvironmentVariableIsSet("QT_XCB_DEBUG_XINPUT_DEVICES"))
 
79
        const_cast<QLoggingCategory&>(lcQpaXInputDevices()).setEnabled(QtDebugMsg, true);
 
80
    Display *xDisplay = static_cast<Display *>(m_xlib_display);
 
81
    if (XQueryExtension(xDisplay, "XInputExtension", &m_xiOpCode, &m_xiEventBase, &m_xiErrorBase)) {
 
82
        int xiMajor = 2;
 
83
        m_xi2Minor = 2; // try 2.2 first, needed for TouchBegin/Update/End
 
84
        if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) {
 
85
            m_xi2Minor = 1; // for smooth scrolling 2.1 is enough
 
86
            if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) {
 
87
                m_xi2Minor = 0; // for tablet support 2.0 is enough
 
88
                m_xi2Enabled = XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) != BadRequest;
 
89
            } else
 
90
                m_xi2Enabled = true;
 
91
        } else
 
92
            m_xi2Enabled = true;
 
93
        if (m_xi2Enabled) {
 
94
#ifdef XCB_USE_XINPUT22
 
95
            qCDebug(lcQpaXInputDevices, "XInput version %d.%d is available and Qt supports 2.2 or greater", xiMajor, m_xi2Minor);
 
96
#else
 
97
            qCDebug(lcQpaXInputDevices, "XInput version %d.%d is available and Qt supports 2.0", xiMajor, m_xi2Minor);
 
98
#endif
 
99
        }
 
100
 
 
101
        xi2SetupDevices();
 
102
    }
 
103
}
 
104
 
 
105
void QXcbConnection::xi2SetupDevices()
 
106
{
 
107
#ifndef QT_NO_TABLETEVENT
 
108
    m_tabletData.clear();
 
109
#endif
 
110
    m_scrollingDevices.clear();
 
111
 
 
112
    if (!m_xi2Enabled)
 
113
        return;
 
114
 
 
115
    Display *xDisplay = static_cast<Display *>(m_xlib_display);
 
116
    int deviceCount = 0;
 
117
    XIDeviceInfo *devices = XIQueryDevice(xDisplay, XIAllDevices, &deviceCount);
 
118
    for (int i = 0; i < deviceCount; ++i) {
 
119
        // Only non-master pointing devices are relevant here.
 
120
        if (devices[i].use != XISlavePointer)
 
121
            continue;
 
122
        qCDebug(lcQpaXInputDevices) << "input device " << devices[i].name << "ID" << devices[i].deviceid;
 
123
#ifndef QT_NO_TABLETEVENT
 
124
        TabletData tabletData;
 
125
#endif
 
126
        ScrollingDevice scrollingDevice;
 
127
        for (int c = 0; c < devices[i].num_classes; ++c) {
 
128
            switch (devices[i].classes[c]->type) {
 
129
            case XIValuatorClass: {
 
130
                XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(devices[i].classes[c]);
 
131
                const int valuatorAtom = qatom(vci->label);
 
132
                qCDebug(lcQpaXInputDevices) << "   has valuator" << atomName(vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms);
 
133
#ifndef QT_NO_TABLETEVENT
 
134
                if (valuatorAtom < QXcbAtom::NAtoms) {
 
135
                    TabletData::ValuatorClassInfo info;
 
136
                    info.minVal = vci->min;
 
137
                    info.maxVal = vci->max;
 
138
                    info.number = vci->number;
 
139
                    tabletData.valuatorInfo[valuatorAtom] = info;
 
140
                }
 
141
#endif // QT_NO_TABLETEVENT
 
142
                if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
 
143
                    scrollingDevice.lastScrollPosition.setX(vci->value);
 
144
                else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
 
145
                    scrollingDevice.lastScrollPosition.setY(vci->value);
 
146
                break;
 
147
            }
 
148
#ifdef XCB_USE_XINPUT21
 
149
            case XIScrollClass: {
 
150
                XIScrollClassInfo *sci = reinterpret_cast<XIScrollClassInfo *>(devices[i].classes[c]);
 
151
                if (sci->scroll_type == XIScrollTypeVertical) {
 
152
                    scrollingDevice.orientations |= Qt::Vertical;
 
153
                    scrollingDevice.verticalIndex = sci->number;
 
154
                    scrollingDevice.verticalIncrement = sci->increment;
 
155
                }
 
156
                else if (sci->scroll_type == XIScrollTypeHorizontal) {
 
157
                    scrollingDevice.orientations |= Qt::Horizontal;
 
158
                    scrollingDevice.horizontalIndex = sci->number;
 
159
                    scrollingDevice.horizontalIncrement = sci->increment;
 
160
                }
 
161
                break;
 
162
            }
 
163
            case XIButtonClass: {
 
164
                XIButtonClassInfo *bci = reinterpret_cast<XIButtonClassInfo *>(devices[i].classes[c]);
 
165
                if (bci->num_buttons >= 5) {
 
166
                    Atom label4 = bci->labels[3];
 
167
                    Atom label5 = bci->labels[4];
 
168
                    // Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on
 
169
                    // button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons.
 
170
                    if ((!label4 || qatom(label4) == QXcbAtom::ButtonWheelUp || qatom(label4) == QXcbAtom::ButtonWheelDown) &&
 
171
                        (!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(label5) == QXcbAtom::ButtonWheelDown))
 
172
                        scrollingDevice.legacyOrientations |= Qt::Vertical;
 
173
                }
 
174
                if (bci->num_buttons >= 7) {
 
175
                    Atom label6 = bci->labels[5];
 
176
                    Atom label7 = bci->labels[6];
 
177
                    if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight))
 
178
                        scrollingDevice.legacyOrientations |= Qt::Horizontal;
 
179
                }
 
180
                qCDebug(lcQpaXInputDevices, "   has %d buttons", bci->num_buttons);
 
181
                break;
 
182
            }
 
183
#endif
 
184
            case XIKeyClass:
 
185
                qCDebug(lcQpaXInputDevices) << "   it's a keyboard";
 
186
                break;
 
187
#ifdef XCB_USE_XINPUT22
 
188
            case XITouchClass:
 
189
                // will be handled in deviceForId()
 
190
                break;
 
191
#endif
 
192
            default:
 
193
                qCDebug(lcQpaXInputDevices) << "   has class" << devices[i].classes[c]->type;
 
194
                break;
 
195
            }
 
196
        }
 
197
        bool isTablet = false;
 
198
#ifndef QT_NO_TABLETEVENT
 
199
        // If we have found the valuators which we expect a tablet to have, it might be a tablet.
 
200
        if (tabletData.valuatorInfo.contains(QXcbAtom::AbsX) &&
 
201
                tabletData.valuatorInfo.contains(QXcbAtom::AbsY) &&
 
202
                tabletData.valuatorInfo.contains(QXcbAtom::AbsPressure))
 
203
            isTablet = true;
 
204
 
 
205
        // But we need to be careful not to take the touch and tablet-button devices as tablets.
 
206
        QByteArray name = QByteArray(devices[i].name).toLower();
 
207
        QString dbgType = QLatin1String("UNKNOWN");
 
208
        if (name.contains("eraser")) {
 
209
            isTablet = true;
 
210
            tabletData.pointerType = QTabletEvent::Eraser;
 
211
            dbgType = QLatin1String("eraser");
 
212
        } else if (name.contains("cursor")) {
 
213
            isTablet = true;
 
214
            tabletData.pointerType = QTabletEvent::Cursor;
 
215
            dbgType = QLatin1String("cursor");
 
216
        } else if ((name.contains("pen") || name.contains("stylus")) && isTablet) {
 
217
            tabletData.pointerType = QTabletEvent::Pen;
 
218
            dbgType = QLatin1String("pen");
 
219
        } else if (name.contains("wacom") && isTablet && !name.contains("touch")) {
 
220
            // combined device (evdev) rather than separate pen/eraser (wacom driver)
 
221
            tabletData.pointerType = QTabletEvent::Pen;
 
222
            dbgType = QLatin1String("pen");
 
223
        } else if (name.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) {
 
224
            // some "Genius" tablets
 
225
            isTablet = true;
 
226
            tabletData.pointerType = QTabletEvent::Pen;
 
227
            dbgType = QLatin1String("pen");
 
228
        } else if (name.contains("waltop") && name.contains("tablet")) {
 
229
            // other "Genius" tablets
 
230
            // WALTOP International Corp. Slim Tablet
 
231
            isTablet = true;
 
232
            tabletData.pointerType = QTabletEvent::Pen;
 
233
            dbgType = QLatin1String("pen");
 
234
        } else {
 
235
            isTablet = false;
 
236
        }
 
237
 
 
238
        if (isTablet) {
 
239
            tabletData.deviceId = devices[i].deviceid;
 
240
            m_tabletData.append(tabletData);
 
241
            qCDebug(lcQpaXInputDevices) << "   it's a tablet with pointer type" << dbgType;
 
242
        }
 
243
#endif // QT_NO_TABLETEVENT
 
244
 
 
245
#ifdef XCB_USE_XINPUT21
 
246
        if (scrollingDevice.orientations || scrollingDevice.legacyOrientations) {
 
247
            scrollingDevice.deviceId = devices[i].deviceid;
 
248
            // Only use legacy wheel button events when we don't have real scroll valuators.
 
249
            scrollingDevice.legacyOrientations &= ~scrollingDevice.orientations;
 
250
            m_scrollingDevices.insert(scrollingDevice.deviceId, scrollingDevice);
 
251
            qCDebug(lcQpaXInputDevices) << "   it's a scrolling device";
 
252
        }
 
253
#endif
 
254
 
 
255
        if (!isTablet) {
 
256
            // touchDeviceForId populates XInput2DeviceData the first time it is called
 
257
            // with a new deviceId. On subsequent calls it will return the cached object.
 
258
            XInput2TouchDeviceData *dev = touchDeviceForId(devices[i].deviceid);
 
259
            if (dev && lcQpaXInputDevices().isDebugEnabled()) {
 
260
                if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen)
 
261
                    qCDebug(lcQpaXInputDevices, "   it's a touchscreen with type %d capabilities 0x%X max touch points %d",
 
262
                            dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(),
 
263
                            dev->qtTouchDevice->maximumTouchPoints());
 
264
                else if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad)
 
265
                    qCDebug(lcQpaXInputDevices, "   it's a touchpad with type %d capabilities 0x%X max touch points %d size %f x %f",
 
266
                            dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(),
 
267
                            dev->qtTouchDevice->maximumTouchPoints(),
 
268
                            dev->size.width(), dev->size.height());
 
269
            }
 
270
        }
 
271
    }
 
272
    XIFreeDeviceInfo(devices);
 
273
}
 
274
 
 
275
void QXcbConnection::finalizeXInput2()
 
276
{
 
277
    for (XInput2TouchDeviceData *dev : qAsConst(m_touchDevices)) {
 
278
        if (dev->xiDeviceInfo)
 
279
            XIFreeDeviceInfo(dev->xiDeviceInfo);
 
280
        delete dev;
 
281
    }
 
282
}
 
283
 
 
284
void QXcbConnection::xi2Select(xcb_window_t window)
 
285
{
 
286
    if (!m_xi2Enabled || window == rootWindow())
 
287
        return;
 
288
 
 
289
    Display *xDisplay = static_cast<Display *>(m_xlib_display);
 
290
    unsigned int bitMask = 0;
 
291
    unsigned char *xiBitMask = reinterpret_cast<unsigned char *>(&bitMask);
 
292
 
 
293
#ifdef XCB_USE_XINPUT22
 
294
    if (isAtLeastXI22()) {
 
295
        bitMask |= XI_TouchBeginMask;
 
296
        bitMask |= XI_TouchUpdateMask;
 
297
        bitMask |= XI_TouchEndMask;
 
298
        bitMask |= XI_PropertyEventMask; // for tablets
 
299
        if (xi2MouseEvents()) {
 
300
            // We want both mouse and touch through XI2 if touch is supported (>= 2.2).
 
301
            // The plain xcb press and motion events will not be delivered after this.
 
302
            bitMask |= XI_ButtonPressMask;
 
303
            bitMask |= XI_ButtonReleaseMask;
 
304
            bitMask |= XI_MotionMask;
 
305
 
 
306
            // There is a check for enter/leave events in plain xcb enter/leave event handler
 
307
            bitMask |= XI_EnterMask;
 
308
            bitMask |= XI_LeaveMask;
 
309
 
 
310
            qCDebug(lcQpaXInput, "XInput 2.2: Selecting press/release/motion events in addition to touch");
 
311
        }
 
312
        XIEventMask mask;
 
313
        mask.mask_len = sizeof(bitMask);
 
314
        mask.mask = xiBitMask;
 
315
        // When xi2MouseEvents() is true (the default), pointer emulation for touch and tablet
 
316
        // events will get disabled. This is preferable, as Qt Quick handles touch events
 
317
        // directly, while for other applications QtGui synthesizes mouse events.
 
318
        mask.deviceid = XIAllMasterDevices;
 
319
        Status result = XISelectEvents(xDisplay, window, &mask, 1);
 
320
        if (result == Success)
 
321
            QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
 
322
        else
 
323
            qCDebug(lcQpaXInput, "XInput 2.2: failed to select pointer/touch events, window %x, result %d", window, result);
 
324
    }
 
325
 
 
326
    const bool pointerSelected = isAtLeastXI22() && xi2MouseEvents();
 
327
#else
 
328
    const bool pointerSelected = false;
 
329
#endif // XCB_USE_XINPUT22
 
330
 
 
331
    QSet<int> tabletDevices;
 
332
#ifndef QT_NO_TABLETEVENT
 
333
    if (!m_tabletData.isEmpty()) {
 
334
        unsigned int tabletBitMask;
 
335
        unsigned char *xiTabletBitMask = reinterpret_cast<unsigned char *>(&tabletBitMask);
 
336
        QVector<XIEventMask> xiEventMask(m_tabletData.count());
 
337
        tabletBitMask = XI_PropertyEventMask;
 
338
        if (!pointerSelected)
 
339
            tabletBitMask |= XI_ButtonPressMask | XI_ButtonReleaseMask | XI_MotionMask;
 
340
        for (int i = 0; i < m_tabletData.count(); ++i) {
 
341
            int deviceId = m_tabletData.at(i).deviceId;
 
342
            tabletDevices.insert(deviceId);
 
343
            xiEventMask[i].deviceid = deviceId;
 
344
            xiEventMask[i].mask_len = sizeof(tabletBitMask);
 
345
            xiEventMask[i].mask = xiTabletBitMask;
 
346
        }
 
347
        XISelectEvents(xDisplay, window, xiEventMask.data(), m_tabletData.count());
 
348
    }
 
349
#endif // QT_NO_TABLETEVENT
 
350
 
 
351
#ifdef XCB_USE_XINPUT21
 
352
    // Enable each scroll device
 
353
    if (!m_scrollingDevices.isEmpty() && !pointerSelected) {
 
354
        // Only when XI2 mouse events are not enabled, otherwise motion and release are selected already.
 
355
        QVector<XIEventMask> xiEventMask(m_scrollingDevices.size());
 
356
        unsigned int scrollBitMask;
 
357
        unsigned char *xiScrollBitMask = reinterpret_cast<unsigned char *>(&scrollBitMask);
 
358
 
 
359
        scrollBitMask = XI_MotionMask;
 
360
        scrollBitMask |= XI_ButtonReleaseMask;
 
361
        int i=0;
 
362
        for (const ScrollingDevice& scrollingDevice : qAsConst(m_scrollingDevices)) {
 
363
            if (tabletDevices.contains(scrollingDevice.deviceId))
 
364
                continue; // All necessary events are already captured.
 
365
            xiEventMask[i].deviceid = scrollingDevice.deviceId;
 
366
            xiEventMask[i].mask_len = sizeof(scrollBitMask);
 
367
            xiEventMask[i].mask = xiScrollBitMask;
 
368
            i++;
 
369
        }
 
370
        XISelectEvents(xDisplay, window, xiEventMask.data(), i);
 
371
    }
 
372
#else
 
373
    Q_UNUSED(xiBitMask);
 
374
#endif
 
375
 
 
376
    {
 
377
        // Listen for hotplug events
 
378
        XIEventMask xiEventMask;
 
379
        bitMask = XI_HierarchyChangedMask;
 
380
        bitMask |= XI_DeviceChangedMask;
 
381
        xiEventMask.deviceid = XIAllDevices;
 
382
        xiEventMask.mask_len = sizeof(bitMask);
 
383
        xiEventMask.mask = xiBitMask;
 
384
        XISelectEvents(xDisplay, window, &xiEventMask, 1);
 
385
    }
 
386
}
 
387
 
 
388
XInput2TouchDeviceData *QXcbConnection::touchDeviceForId(int id)
 
389
{
 
390
    XInput2TouchDeviceData *dev = Q_NULLPTR;
 
391
    QHash<int, XInput2TouchDeviceData*>::const_iterator devIt = m_touchDevices.constFind(id);
 
392
    if (devIt != m_touchDevices.cend()) {
 
393
        dev = devIt.value();
 
394
    } else {
 
395
        int nrDevices = 0;
 
396
        QTouchDevice::Capabilities caps = 0;
 
397
        dev = new XInput2TouchDeviceData;
 
398
        dev->xiDeviceInfo = XIQueryDevice(static_cast<Display *>(m_xlib_display), id, &nrDevices);
 
399
        if (nrDevices <= 0) {
 
400
            delete dev;
 
401
            return 0;
 
402
        }
 
403
        int type = -1;
 
404
        int maxTouchPoints = 1;
 
405
        bool hasRelativeCoords = false;
 
406
        for (int i = 0; i < dev->xiDeviceInfo->num_classes; ++i) {
 
407
            XIAnyClassInfo *classinfo = dev->xiDeviceInfo->classes[i];
 
408
            switch (classinfo->type) {
 
409
#ifdef XCB_USE_XINPUT22
 
410
            case XITouchClass: {
 
411
                XITouchClassInfo *tci = reinterpret_cast<XITouchClassInfo *>(classinfo);
 
412
                maxTouchPoints = tci->num_touches;
 
413
                qCDebug(lcQpaXInputDevices, "   has touch class with mode %d", tci->mode);
 
414
                switch (tci->mode) {
 
415
                case XIDependentTouch:
 
416
                    type = QTouchDevice::TouchPad;
 
417
                    break;
 
418
                case XIDirectTouch:
 
419
                    type = QTouchDevice::TouchScreen;
 
420
                    break;
 
421
                }
 
422
                break;
 
423
            }
 
424
#endif // XCB_USE_XINPUT22
 
425
            case XIValuatorClass: {
 
426
                XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo);
 
427
                // Some devices (mice) report a resolution of 0; they will be excluded later,
 
428
                // for now just prevent a division by zero
 
429
                const int vciResolution = vci->resolution ? vci->resolution : 1;
 
430
                if (vci->label == atom(QXcbAtom::AbsMTPositionX))
 
431
                    caps |= QTouchDevice::Position | QTouchDevice::NormalizedPosition;
 
432
                else if (vci->label == atom(QXcbAtom::AbsMTTouchMajor))
 
433
                    caps |= QTouchDevice::Area;
 
434
                else if (vci->label == atom(QXcbAtom::AbsMTOrientation))
 
435
                    dev->providesTouchOrientation = true;
 
436
                else if (vci->label == atom(QXcbAtom::AbsMTPressure) || vci->label == atom(QXcbAtom::AbsPressure))
 
437
                    caps |= QTouchDevice::Pressure;
 
438
                else if (vci->label == atom(QXcbAtom::RelX)) {
 
439
                    hasRelativeCoords = true;
 
440
                    dev->size.setWidth((vci->max - vci->min) * 1000.0 / vciResolution);
 
441
                } else if (vci->label == atom(QXcbAtom::RelY)) {
 
442
                    hasRelativeCoords = true;
 
443
                    dev->size.setHeight((vci->max - vci->min) * 1000.0 / vciResolution);
 
444
                } else if (vci->label == atom(QXcbAtom::AbsX)) {
 
445
                    caps |= QTouchDevice::Position;
 
446
                    dev->size.setHeight((vci->max - vci->min) * 1000.0 / vciResolution);
 
447
                } else if (vci->label == atom(QXcbAtom::AbsY)) {
 
448
                    caps |= QTouchDevice::Position;
 
449
                    dev->size.setWidth((vci->max - vci->min) * 1000.0 / vciResolution);
 
450
                }
 
451
                break;
 
452
            }
 
453
            default:
 
454
                break;
 
455
            }
 
456
        }
 
457
        if (type < 0 && caps && hasRelativeCoords) {
 
458
            type = QTouchDevice::TouchPad;
 
459
            if (dev->size.width() < 10 || dev->size.height() < 10 ||
 
460
                    dev->size.width() > 10000 || dev->size.height() > 10000)
 
461
                dev->size = QSizeF(130, 110);
 
462
        }
 
463
        if (!isAtLeastXI22() || type == QTouchDevice::TouchPad)
 
464
            caps |= QTouchDevice::MouseEmulation;
 
465
 
 
466
        if (type >= QTouchDevice::TouchScreen && type <= QTouchDevice::TouchPad) {
 
467
            dev->qtTouchDevice = new QTouchDevice;
 
468
            dev->qtTouchDevice->setName(QString::fromUtf8(dev->xiDeviceInfo->name));
 
469
            dev->qtTouchDevice->setType((QTouchDevice::DeviceType)type);
 
470
            dev->qtTouchDevice->setCapabilities(caps);
 
471
            dev->qtTouchDevice->setMaximumTouchPoints(maxTouchPoints);
 
472
            if (caps != 0)
 
473
                QWindowSystemInterface::registerTouchDevice(dev->qtTouchDevice);
 
474
            m_touchDevices[id] = dev;
 
475
        } else {
 
476
            XIFreeDeviceInfo(dev->xiDeviceInfo);
 
477
            delete dev;
 
478
            dev = 0;
 
479
        }
 
480
    }
 
481
    return dev;
 
482
}
 
483
 
 
484
#if defined(XCB_USE_XINPUT21) || !defined(QT_NO_TABLETEVENT)
 
485
static inline qreal fixed1616ToReal(FP1616 val)
 
486
{
 
487
    return qreal(val) / 0x10000;
 
488
}
 
489
#endif // defined(XCB_USE_XINPUT21) || !defined(QT_NO_TABLETEVENT)
 
490
 
 
491
void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
 
492
{
 
493
    xi2PrepareXIGenericDeviceEvent(event);
 
494
    xXIGenericDeviceEvent *xiEvent = reinterpret_cast<xXIGenericDeviceEvent *>(event);
 
495
    int sourceDeviceId = xiEvent->deviceid; // may be the master id
 
496
    xXIDeviceEvent *xiDeviceEvent = 0;
 
497
    xXIEnterEvent *xiEnterEvent = 0;
 
498
    QXcbWindowEventListener *eventListener = 0;
 
499
 
 
500
    switch (xiEvent->evtype) {
 
501
    case XI_ButtonPress:
 
502
    case XI_ButtonRelease:
 
503
    case XI_Motion:
 
504
#ifdef XCB_USE_XINPUT22
 
505
    case XI_TouchBegin:
 
506
    case XI_TouchUpdate:
 
507
    case XI_TouchEnd:
 
508
#endif
 
509
    {
 
510
        xiDeviceEvent = reinterpret_cast<xXIDeviceEvent *>(event);
 
511
        eventListener = windowEventListenerFromId(xiDeviceEvent->event);
 
512
        sourceDeviceId = xiDeviceEvent->sourceid; // use the actual device id instead of the master
 
513
        break;
 
514
    }
 
515
    case XI_Enter:
 
516
    case XI_Leave: {
 
517
        xiEnterEvent = reinterpret_cast<xXIEnterEvent *>(event);
 
518
        eventListener = windowEventListenerFromId(xiEnterEvent->event);
 
519
        sourceDeviceId = xiEnterEvent->sourceid; // use the actual device id instead of the master
 
520
        break;
 
521
    }
 
522
    case XI_HierarchyChanged:
 
523
        xi2HandleHierachyEvent(xiEvent);
 
524
        return;
 
525
    case XI_DeviceChanged:
 
526
        xi2HandleDeviceChangedEvent(xiEvent);
 
527
        return;
 
528
    default:
 
529
        break;
 
530
    }
 
531
 
 
532
    if (eventListener) {
 
533
        long result = 0;
 
534
        if (eventListener->handleGenericEvent(reinterpret_cast<xcb_generic_event_t *>(event), &result))
 
535
            return;
 
536
    }
 
537
 
 
538
#ifndef QT_NO_TABLETEVENT
 
539
    if (!xiEnterEvent) {
 
540
        QXcbConnection::TabletData *tablet = tabletDataForDevice(sourceDeviceId);
 
541
        if (tablet && xi2HandleTabletEvent(xiEvent, tablet))
 
542
            return;
 
543
    }
 
544
#endif // QT_NO_TABLETEVENT
 
545
 
 
546
#ifdef XCB_USE_XINPUT21
 
547
    QHash<int, ScrollingDevice>::iterator device = m_scrollingDevices.find(sourceDeviceId);
 
548
    if (device != m_scrollingDevices.end())
 
549
        xi2HandleScrollEvent(xiEvent, device.value());
 
550
#endif // XCB_USE_XINPUT21
 
551
 
 
552
#ifdef XCB_USE_XINPUT22
 
553
    if (xiDeviceEvent) {
 
554
        switch (xiDeviceEvent->evtype) {
 
555
        case XI_ButtonPress:
 
556
        case XI_ButtonRelease:
 
557
        case XI_Motion:
 
558
            if (xi2MouseEvents() && eventListener && !(xiDeviceEvent->flags & XIPointerEmulated))
 
559
                eventListener->handleXIMouseEvent(event);
 
560
            break;
 
561
 
 
562
        case XI_TouchBegin:
 
563
        case XI_TouchUpdate:
 
564
        case XI_TouchEnd:
 
565
            if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
 
566
                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",
 
567
                        event->event_type, xiDeviceEvent->sequenceNumber, xiDeviceEvent->detail,
 
568
                        fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y),
 
569
                        fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y),xiDeviceEvent->event);
 
570
            if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event))
 
571
                xi2ProcessTouch(xiDeviceEvent, platformWindow);
 
572
            break;
 
573
        }
 
574
    } else if (xiEnterEvent && xi2MouseEvents() && eventListener) {
 
575
        switch (xiEnterEvent->evtype) {
 
576
        case XI_Enter:
 
577
        case XI_Leave:
 
578
            eventListener->handleXIEnterLeave(event);
 
579
            break;
 
580
        }
 
581
    }
 
582
#endif // XCB_USE_XINPUT22
 
583
}
 
584
 
 
585
#ifdef XCB_USE_XINPUT22
 
586
static qreal valuatorNormalized(double value, XIValuatorClassInfo *vci)
 
587
{
 
588
    if (value > vci->max)
 
589
        value = vci->max;
 
590
    if (value < vci->min)
 
591
        value = vci->min;
 
592
    return (value - vci->min) / (vci->max - vci->min);
 
593
}
 
594
 
 
595
void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindow)
 
596
{
 
597
    xXIDeviceEvent *xiDeviceEvent = static_cast<xXIDeviceEvent *>(xiDevEvent);
 
598
    XInput2TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid);
 
599
    Q_ASSERT(dev);
 
600
    const bool firstTouch = dev->touchPoints.isEmpty();
 
601
    if (xiDeviceEvent->evtype == XI_TouchBegin) {
 
602
        QWindowSystemInterface::TouchPoint tp;
 
603
        tp.id = xiDeviceEvent->detail % INT_MAX;
 
604
        tp.state = Qt::TouchPointPressed;
 
605
        tp.pressure = -1.0;
 
606
        dev->touchPoints[tp.id] = tp;
 
607
    }
 
608
    QWindowSystemInterface::TouchPoint &touchPoint = dev->touchPoints[xiDeviceEvent->detail];
 
609
    QXcbScreen* screen = platformWindow->xcbScreen();
 
610
    qreal x = fixed1616ToReal(xiDeviceEvent->root_x);
 
611
    qreal y = fixed1616ToReal(xiDeviceEvent->root_y);
 
612
    qreal nx = -1.0, ny = -1.0;
 
613
    qreal w = 0.0, h = 0.0;
 
614
    bool majorAxisIsY = touchPoint.area.height() > touchPoint.area.width();
 
615
    for (int i = 0; i < dev->xiDeviceInfo->num_classes; ++i) {
 
616
        XIAnyClassInfo *classinfo = dev->xiDeviceInfo->classes[i];
 
617
        if (classinfo->type == XIValuatorClass) {
 
618
            XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo);
 
619
            int n = vci->number;
 
620
            double value;
 
621
            if (!xi2GetValuatorValueIfSet(xiDeviceEvent, n, &value))
 
622
                continue;
 
623
            if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
 
624
                qCDebug(lcQpaXInputEvents, "   valuator %20s value %lf from range %lf -> %lf",
 
625
                        atomName(vci->label).constData(), value, vci->min, vci->max );
 
626
            if (vci->label == atom(QXcbAtom::RelX)) {
 
627
                nx = valuatorNormalized(value, vci);
 
628
            } else if (vci->label == atom(QXcbAtom::RelY)) {
 
629
                ny = valuatorNormalized(value, vci);
 
630
            } else if (vci->label == atom(QXcbAtom::AbsX)) {
 
631
                nx = valuatorNormalized(value, vci);
 
632
            } else if (vci->label == atom(QXcbAtom::AbsY)) {
 
633
                ny = valuatorNormalized(value, vci);
 
634
            } else if (vci->label == atom(QXcbAtom::AbsMTPositionX)) {
 
635
                nx = valuatorNormalized(value, vci);
 
636
            } else if (vci->label == atom(QXcbAtom::AbsMTPositionY)) {
 
637
                ny = valuatorNormalized(value, vci);
 
638
            } else if (vci->label == atom(QXcbAtom::AbsMTTouchMajor)) {
 
639
                const qreal sw = screen->geometry().width();
 
640
                const qreal sh = screen->geometry().height();
 
641
                w = valuatorNormalized(value, vci) * std::sqrt(sw * sw + sh * sh);
 
642
            } else if (vci->label == atom(QXcbAtom::AbsMTTouchMinor)) {
 
643
                const qreal sw = screen->geometry().width();
 
644
                const qreal sh = screen->geometry().height();
 
645
                h = valuatorNormalized(value, vci) * std::sqrt(sw * sw + sh * sh);
 
646
            } else if (vci->label == atom(QXcbAtom::AbsMTOrientation)) {
 
647
                // Find the closest axis.
 
648
                // 0 corresponds to the Y axis, vci->max to the X axis.
 
649
                // Flipping over the Y axis and rotating by 180 degrees
 
650
                // don't change the result, so normalize value to range
 
651
                // [0, vci->max] first.
 
652
                value = qAbs(value);
 
653
                while (value > vci->max)
 
654
                    value -= 2 * vci->max;
 
655
                value = qAbs(value);
 
656
                majorAxisIsY = value < vci->max - value;
 
657
            } else if (vci->label == atom(QXcbAtom::AbsMTPressure) ||
 
658
                       vci->label == atom(QXcbAtom::AbsPressure)) {
 
659
                touchPoint.pressure = valuatorNormalized(value, vci);
 
660
            }
 
661
        }
 
662
    }
 
663
    // If any value was not updated, use the last-known value.
 
664
    if (nx == -1.0) {
 
665
        x = touchPoint.area.center().x();
 
666
        nx = x / screen->geometry().width();
 
667
    }
 
668
    if (ny == -1.0) {
 
669
        y = touchPoint.area.center().y();
 
670
        ny = y / screen->geometry().height();
 
671
    }
 
672
    if (xiDeviceEvent->evtype != XI_TouchEnd) {
 
673
        if (!dev->providesTouchOrientation) {
 
674
            if (w == 0.0)
 
675
                w = touchPoint.area.width();
 
676
            h = w;
 
677
        } else {
 
678
            if (w == 0.0)
 
679
                w = qMax(touchPoint.area.width(), touchPoint.area.height());
 
680
            if (h == 0.0)
 
681
                h = qMin(touchPoint.area.width(), touchPoint.area.height());
 
682
            if (majorAxisIsY)
 
683
                qSwap(w, h);
 
684
        }
 
685
    }
 
686
 
 
687
    switch (xiDeviceEvent->evtype) {
 
688
    case XI_TouchBegin:
 
689
        if (firstTouch) {
 
690
            dev->firstPressedPosition = QPointF(x, y);
 
691
            dev->firstPressedNormalPosition = QPointF(nx, ny);
 
692
        }
 
693
        dev->pointPressedPosition.insert(touchPoint.id, QPointF(x, y));
 
694
 
 
695
        // Touches must be accepted when we are grabbing touch events. Otherwise the entire sequence
 
696
        // will get replayed when the grab ends.
 
697
        if (m_xiGrab) {
 
698
            // XIAllowTouchEvents deadlocks with libXi < 1.7.4 (this has nothing to do with the XI2 versions like 2.2)
 
699
            // http://lists.x.org/archives/xorg-devel/2014-July/043059.html
 
700
#ifndef LIBXI_MAJOR
 
701
            static bool allowTouchWarningShown = false;
 
702
            if (!allowTouchWarningShown) {
 
703
                allowTouchWarningShown = true;
 
704
                qWarning("Skipping XIAllowTouchEvents() because it was not possible to detect libXi version at build time."
 
705
                         " Minimum libXi version required is 1.7.4."
 
706
                         " Expect issues with touch behavior.");
 
707
            }
 
708
#elif LIBXI_MAJOR == 1 && (LIBXI_MINOR < 7 || (LIBXI_MINOR == 7 && LIBXI_PATCH < 4))
 
709
            static bool allowTouchWarningShown = false;
 
710
            if (!allowTouchWarningShown) {
 
711
                allowTouchWarningShown = true;
 
712
                qWarning("Skipping XIAllowTouchEvents() due to not having libXi >= 1.7.4."
 
713
                         " libXi version at build time was %d.%d.%d."
 
714
                         " Expect issues with touch behavior.", LIBXI_MAJOR, LIBXI_MINOR, LIBXI_PATCH);
 
715
            }
 
716
#else
 
717
            XIAllowTouchEvents(static_cast<Display *>(m_xlib_display), xiDeviceEvent->deviceid,
 
718
                               xiDeviceEvent->detail, xiDeviceEvent->event, XIAcceptTouch);
 
719
#endif
 
720
        }
 
721
        break;
 
722
    case XI_TouchUpdate:
 
723
        if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad && dev->pointPressedPosition.value(touchPoint.id) == QPointF(x, y)) {
 
724
            qreal dx = (nx - dev->firstPressedNormalPosition.x()) *
 
725
                dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
 
726
            qreal dy = (ny - dev->firstPressedNormalPosition.y()) *
 
727
                dev->size.height() * screen->geometry().height() / screen->physicalSize().height();
 
728
            x = dev->firstPressedPosition.x() + dx;
 
729
            y = dev->firstPressedPosition.y() + dy;
 
730
            touchPoint.state = Qt::TouchPointMoved;
 
731
        } else if (touchPoint.area.center() != QPoint(x, y)) {
 
732
            touchPoint.state = Qt::TouchPointMoved;
 
733
            dev->pointPressedPosition[touchPoint.id] = QPointF(x, y);
 
734
        }
 
735
        break;
 
736
    case XI_TouchEnd:
 
737
        touchPoint.state = Qt::TouchPointReleased;
 
738
        if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad && dev->pointPressedPosition.value(touchPoint.id) == QPointF(x, y)) {
 
739
            qreal dx = (nx - dev->firstPressedNormalPosition.x()) *
 
740
                dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
 
741
            qreal dy = (ny - dev->firstPressedNormalPosition.y()) *
 
742
                dev->size.width() * screen->geometry().width() / screen->physicalSize().width();
 
743
            x = dev->firstPressedPosition.x() + dx;
 
744
            y = dev->firstPressedPosition.y() + dy;
 
745
        }
 
746
        dev->pointPressedPosition.remove(touchPoint.id);
 
747
    }
 
748
    touchPoint.area = QRectF(x - w/2, y - h/2, w, h);
 
749
    touchPoint.normalPosition = QPointF(nx, ny);
 
750
 
 
751
    if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
 
752
        qCDebug(lcQpaXInputEvents) << "   touchpoint "  << touchPoint.id << " state " << touchPoint.state << " pos norm " << touchPoint.normalPosition <<
 
753
            " area " << touchPoint.area << " pressure " << touchPoint.pressure;
 
754
    QWindowSystemInterface::handleTouchEvent(platformWindow->window(), xiDeviceEvent->time, dev->qtTouchDevice, dev->touchPoints.values());
 
755
    if (touchPoint.state == Qt::TouchPointReleased)
 
756
        // If a touchpoint was released, we can forget it, because the ID won't be reused.
 
757
        dev->touchPoints.remove(touchPoint.id);
 
758
    else
 
759
        // Make sure that we don't send TouchPointPressed/Moved in more than one QTouchEvent
 
760
        // with this touch point if the next XI2 event is about a different touch point.
 
761
        touchPoint.state = Qt::TouchPointStationary;
 
762
}
 
763
 
 
764
bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab)
 
765
{
 
766
    if (grab && !canGrab())
 
767
        return false;
 
768
 
 
769
    int num_devices = 0;
 
770
    Display *xDisplay = static_cast<Display *>(xlib_display());
 
771
    XIDeviceInfo *info = XIQueryDevice(xDisplay, XIAllMasterDevices, &num_devices);
 
772
    if (!info)
 
773
        return false;
 
774
 
 
775
    XIEventMask evmask;
 
776
    unsigned char mask[XIMaskLen(XI_LASTEVENT)];
 
777
    evmask.mask = mask;
 
778
    evmask.mask_len = sizeof(mask);
 
779
    memset(mask, 0, sizeof(mask));
 
780
    evmask.deviceid = XIAllMasterDevices;
 
781
 
 
782
    XISetMask(mask, XI_ButtonPress);
 
783
    XISetMask(mask, XI_ButtonRelease);
 
784
    XISetMask(mask, XI_Motion);
 
785
    XISetMask(mask, XI_Enter);
 
786
    XISetMask(mask, XI_Leave);
 
787
    XISetMask(mask, XI_TouchBegin);
 
788
    XISetMask(mask, XI_TouchUpdate);
 
789
    XISetMask(mask, XI_TouchEnd);
 
790
 
 
791
    bool grabbed = true;
 
792
    for (int i = 0; i < num_devices; i++) {
 
793
        int id = info[i].deviceid, n = 0;
 
794
        XIDeviceInfo *deviceInfo = XIQueryDevice(xDisplay, id, &n);
 
795
        if (deviceInfo) {
 
796
            const bool grabbable = deviceInfo->use != XIMasterKeyboard;
 
797
            XIFreeDeviceInfo(deviceInfo);
 
798
            if (!grabbable)
 
799
                continue;
 
800
        }
 
801
        if (!grab) {
 
802
            Status result = XIUngrabDevice(xDisplay, id, CurrentTime);
 
803
            if (result != Success) {
 
804
                grabbed = false;
 
805
                qCDebug(lcQpaXInput, "XInput 2.2: failed to ungrab events for device %d (result %d)", id, result);
 
806
            }
 
807
        } else {
 
808
            Status result = XIGrabDevice(xDisplay, id, w, CurrentTime, None, XIGrabModeAsync,
 
809
                                         XIGrabModeAsync, False, &evmask);
 
810
            if (result != Success) {
 
811
                grabbed = false;
 
812
                qCDebug(lcQpaXInput, "XInput 2.2: failed to grab events for device %d on window %x (result %d)", id, w, result);
 
813
            }
 
814
        }
 
815
    }
 
816
 
 
817
    XIFreeDeviceInfo(info);
 
818
 
 
819
    m_xiGrab = grabbed;
 
820
 
 
821
    return grabbed;
 
822
}
 
823
#endif // XCB_USE_XINPUT22
 
824
 
 
825
void QXcbConnection::xi2HandleHierachyEvent(void *event)
 
826
{
 
827
    xXIHierarchyEvent *xiEvent = reinterpret_cast<xXIHierarchyEvent *>(event);
 
828
    // We only care about hotplugged devices
 
829
    if (!(xiEvent->flags & (XISlaveRemoved | XISlaveAdded)))
 
830
        return;
 
831
    xi2SetupDevices();
 
832
    // Reselect events for all event-listening windows.
 
833
    for (auto it = m_mapper.cbegin(), end = m_mapper.cend(); it != end; ++it)
 
834
        xi2Select(it.key());
 
835
}
 
836
 
 
837
void QXcbConnection::xi2HandleDeviceChangedEvent(void *event)
 
838
{
 
839
    xXIDeviceChangedEvent *xiEvent = reinterpret_cast<xXIDeviceChangedEvent *>(event);
 
840
 
 
841
    // ### If a slave device changes (XIDeviceChange), we should probably run setup on it again.
 
842
    if (xiEvent->reason != XISlaveSwitch)
 
843
        return;
 
844
 
 
845
#ifdef XCB_USE_XINPUT21
 
846
    // This code handles broken scrolling device drivers that reset absolute positions
 
847
    // when they are made active. Whenever a new slave device is made active the
 
848
    // primary pointer sends a DeviceChanged event with XISlaveSwitch, and the new
 
849
    // active slave in sourceid.
 
850
 
 
851
    QHash<int, ScrollingDevice>::iterator device = m_scrollingDevices.find(xiEvent->sourceid);
 
852
    if (device == m_scrollingDevices.end())
 
853
        return;
 
854
 
 
855
    int nrDevices = 0;
 
856
    XIDeviceInfo* xiDeviceInfo = XIQueryDevice(static_cast<Display *>(m_xlib_display), xiEvent->sourceid, &nrDevices);
 
857
    if (nrDevices <= 0) {
 
858
        qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", xiEvent->sourceid);
 
859
        return;
 
860
    }
 
861
    updateScrollingDevice(*device, xiDeviceInfo->num_classes, xiDeviceInfo->classes);
 
862
    XIFreeDeviceInfo(xiDeviceInfo);
 
863
#endif
 
864
}
 
865
 
 
866
void QXcbConnection::updateScrollingDevice(ScrollingDevice &scrollingDevice, int num_classes, void *classInfo)
 
867
{
 
868
#ifdef XCB_USE_XINPUT21
 
869
    XIAnyClassInfo **classes = reinterpret_cast<XIAnyClassInfo**>(classInfo);
 
870
    QPointF lastScrollPosition;
 
871
    if (lcQpaXInput().isDebugEnabled())
 
872
        lastScrollPosition = scrollingDevice.lastScrollPosition;
 
873
    for (int c = 0; c < num_classes; ++c) {
 
874
        if (classes[c]->type == XIValuatorClass) {
 
875
            XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classes[c]);
 
876
            const int valuatorAtom = qatom(vci->label);
 
877
            if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
 
878
                scrollingDevice.lastScrollPosition.setX(vci->value);
 
879
            else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
 
880
                scrollingDevice.lastScrollPosition.setY(vci->value);
 
881
        }
 
882
    }
 
883
    if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled() && lastScrollPosition != scrollingDevice.lastScrollPosition))
 
884
        qCDebug(lcQpaXInputEvents, "scrolling device %d moved from (%f, %f) to (%f, %f)", scrollingDevice.deviceId,
 
885
                lastScrollPosition.x(), lastScrollPosition.y(),
 
886
                scrollingDevice.lastScrollPosition.x(),
 
887
                scrollingDevice.lastScrollPosition.y());
 
888
#else
 
889
    Q_UNUSED(scrollingDevice);
 
890
    Q_UNUSED(num_classes);
 
891
    Q_UNUSED(classInfo);
 
892
#endif
 
893
}
 
894
 
 
895
#ifdef XCB_USE_XINPUT21
 
896
void QXcbConnection::handleEnterEvent()
 
897
{
 
898
    QHash<int, ScrollingDevice>::iterator it = m_scrollingDevices.begin();
 
899
    const QHash<int, ScrollingDevice>::iterator end = m_scrollingDevices.end();
 
900
    while (it != end) {
 
901
        ScrollingDevice& scrollingDevice = it.value();
 
902
        int nrDevices = 0;
 
903
        XIDeviceInfo* xiDeviceInfo = XIQueryDevice(static_cast<Display *>(m_xlib_display), scrollingDevice.deviceId, &nrDevices);
 
904
        if (nrDevices <= 0) {
 
905
            qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", scrollingDevice.deviceId);
 
906
            it = m_scrollingDevices.erase(it);
 
907
            continue;
 
908
        }
 
909
        updateScrollingDevice(scrollingDevice, xiDeviceInfo->num_classes, xiDeviceInfo->classes);
 
910
        XIFreeDeviceInfo(xiDeviceInfo);
 
911
        ++it;
 
912
    }
 
913
}
 
914
#endif
 
915
 
 
916
void QXcbConnection::xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice)
 
917
{
 
918
#ifdef XCB_USE_XINPUT21
 
919
    xXIGenericDeviceEvent *xiEvent = reinterpret_cast<xXIGenericDeviceEvent *>(event);
 
920
 
 
921
    if (xiEvent->evtype == XI_Motion && scrollingDevice.orientations) {
 
922
        xXIDeviceEvent* xiDeviceEvent = reinterpret_cast<xXIDeviceEvent *>(event);
 
923
        if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) {
 
924
            QPoint rawDelta;
 
925
            QPoint angleDelta;
 
926
            double value;
 
927
            if (scrollingDevice.orientations & Qt::Vertical) {
 
928
                if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.verticalIndex, &value)) {
 
929
                    double delta = scrollingDevice.lastScrollPosition.y() - value;
 
930
                    scrollingDevice.lastScrollPosition.setY(value);
 
931
                    angleDelta.setY((delta / scrollingDevice.verticalIncrement) * 120);
 
932
                    // We do not set "pixel" delta if it is only measured in ticks.
 
933
                    if (scrollingDevice.verticalIncrement > 1)
 
934
                        rawDelta.setY(delta);
 
935
                    else if (scrollingDevice.verticalIncrement < -1)
 
936
                        rawDelta.setY(-delta);
 
937
                }
 
938
            }
 
939
            if (scrollingDevice.orientations & Qt::Horizontal) {
 
940
                if (xi2GetValuatorValueIfSet(xiDeviceEvent, scrollingDevice.horizontalIndex, &value)) {
 
941
                    double delta = scrollingDevice.lastScrollPosition.x() - value;
 
942
                    scrollingDevice.lastScrollPosition.setX(value);
 
943
                    angleDelta.setX((delta / scrollingDevice.horizontalIncrement) * 120);
 
944
                    // We do not set "pixel" delta if it is only measured in ticks.
 
945
                    if (scrollingDevice.horizontalIncrement > 1)
 
946
                        rawDelta.setX(delta);
 
947
                    else if (scrollingDevice.horizontalIncrement < -1)
 
948
                        rawDelta.setX(-delta);
 
949
                }
 
950
            }
 
951
            if (!angleDelta.isNull()) {
 
952
                QPoint local(fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y));
 
953
                QPoint global(fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y));
 
954
                Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(xiDeviceEvent->mods.effective_mods);
 
955
                if (modifiers & Qt::AltModifier) {
 
956
                    std::swap(angleDelta.rx(), angleDelta.ry());
 
957
                    std::swap(rawDelta.rx(), rawDelta.ry());
 
958
                }
 
959
                QWindowSystemInterface::handleWheelEvent(platformWindow->window(), xiEvent->time, local, global, rawDelta, angleDelta, modifiers);
 
960
            }
 
961
        }
 
962
    } else if (xiEvent->evtype == XI_ButtonRelease && scrollingDevice.legacyOrientations) {
 
963
        xXIDeviceEvent* xiDeviceEvent = reinterpret_cast<xXIDeviceEvent *>(event);
 
964
        if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) {
 
965
            QPoint angleDelta;
 
966
            if (scrollingDevice.legacyOrientations & Qt::Vertical) {
 
967
                if (xiDeviceEvent->detail == 4)
 
968
                    angleDelta.setY(120);
 
969
                else if (xiDeviceEvent->detail == 5)
 
970
                    angleDelta.setY(-120);
 
971
            }
 
972
            if (scrollingDevice.legacyOrientations & Qt::Horizontal) {
 
973
                if (xiDeviceEvent->detail == 6)
 
974
                    angleDelta.setX(120);
 
975
                else if (xiDeviceEvent->detail == 7)
 
976
                    angleDelta.setX(-120);
 
977
            }
 
978
            if (!angleDelta.isNull()) {
 
979
                QPoint local(fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y));
 
980
                QPoint global(fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y));
 
981
                Qt::KeyboardModifiers modifiers = keyboard()->translateModifiers(xiDeviceEvent->mods.effective_mods);
 
982
                if (modifiers & Qt::AltModifier)
 
983
                    std::swap(angleDelta.rx(), angleDelta.ry());
 
984
                QWindowSystemInterface::handleWheelEvent(platformWindow->window(), xiEvent->time, local, global, QPoint(), angleDelta, modifiers);
 
985
            }
 
986
        }
 
987
    }
 
988
#else
 
989
    Q_UNUSED(event);
 
990
    Q_UNUSED(scrollingDevice);
 
991
#endif // XCB_USE_XINPUT21
 
992
}
 
993
 
 
994
Qt::MouseButton QXcbConnection::xiToQtMouseButton(uint32_t b)
 
995
{
 
996
    switch (b) {
 
997
    case 1: return Qt::LeftButton;
 
998
    case 2: return Qt::MiddleButton;
 
999
    case 3: return Qt::RightButton;
 
1000
    // 4-7 are for scrolling
 
1001
    default: break;
 
1002
    }
 
1003
    if (b >= 8 && b <= Qt::MaxMouseButton)
 
1004
        return static_cast<Qt::MouseButton>(Qt::BackButton << (b - 8));
 
1005
    return Qt::NoButton;
 
1006
}
 
1007
 
 
1008
static QTabletEvent::TabletDevice toolIdToTabletDevice(quint32 toolId) {
 
1009
    // keep in sync with wacom_intuos_inout() in Linux kernel driver wacom_wac.c
 
1010
    switch (toolId) {
 
1011
    case 0xd12:
 
1012
    case 0x912:
 
1013
    case 0x112:
 
1014
    case 0x913: /* Intuos3 Airbrush */
 
1015
    case 0x91b: /* Intuos3 Airbrush Eraser */
 
1016
    case 0x902: /* Intuos4/5 13HD/24HD Airbrush */
 
1017
    case 0x90a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
 
1018
    case 0x100902: /* Intuos4/5 13HD/24HD Airbrush */
 
1019
    case 0x10090a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
 
1020
        return QTabletEvent::Airbrush;
 
1021
    case 0x007: /* Mouse 4D and 2D */
 
1022
    case 0x09c:
 
1023
    case 0x094:
 
1024
        return QTabletEvent::FourDMouse;
 
1025
    case 0x017: /* Intuos3 2D Mouse */
 
1026
    case 0x806: /* Intuos4 Mouse */
 
1027
    case 0x096: /* Lens cursor */
 
1028
    case 0x097: /* Intuos3 Lens cursor */
 
1029
    case 0x006: /* Intuos4 Lens cursor */
 
1030
        return QTabletEvent::Puck;
 
1031
    case 0x885:    /* Intuos3 Art Pen (Marker Pen) */
 
1032
    case 0x100804: /* Intuos4/5 13HD/24HD Art Pen */
 
1033
    case 0x10080c: /* Intuos4/5 13HD/24HD Art Pen Eraser */
 
1034
        return QTabletEvent::RotationStylus;
 
1035
    case 0:
 
1036
        return QTabletEvent::NoDevice;
 
1037
    }
 
1038
    return QTabletEvent::Stylus;  // Safe default assumption if nonzero
 
1039
}
 
1040
 
 
1041
#ifndef QT_NO_TABLETEVENT
 
1042
bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletData)
 
1043
{
 
1044
    bool handled = true;
 
1045
    Display *xDisplay = static_cast<Display *>(m_xlib_display);
 
1046
    const xXIGenericDeviceEvent *xiEvent = static_cast<const xXIGenericDeviceEvent *>(event);
 
1047
    const xXIDeviceEvent *xiDeviceEvent = reinterpret_cast<const xXIDeviceEvent *>(xiEvent);
 
1048
 
 
1049
    switch (xiEvent->evtype) {
 
1050
    case XI_ButtonPress: {
 
1051
        Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail);
 
1052
        tabletData->buttons |= b;
 
1053
        xi2ReportTabletEvent(xiEvent, tabletData);
 
1054
        break;
 
1055
    }
 
1056
    case XI_ButtonRelease: {
 
1057
        Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail);
 
1058
        tabletData->buttons ^= b;
 
1059
        xi2ReportTabletEvent(xiEvent, tabletData);
 
1060
        break;
 
1061
    }
 
1062
    case XI_Motion:
 
1063
        // Report TabletMove only when the stylus is touching the tablet or any button is pressed.
 
1064
        // TODO: report proximity (hover) motion (no suitable Qt event exists yet).
 
1065
        if (tabletData->buttons != Qt::NoButton)
 
1066
            xi2ReportTabletEvent(xiEvent, tabletData);
 
1067
        break;
 
1068
    case XI_PropertyEvent: {
 
1069
        // This is the wacom driver's way of reporting tool proximity.
 
1070
        // The evdev driver doesn't do it this way.
 
1071
        const xXIPropertyEvent *ev = reinterpret_cast<const xXIPropertyEvent *>(event);
 
1072
        if (ev->what == XIPropertyModified) {
 
1073
            if (ev->property == atom(QXcbAtom::WacomSerialIDs)) {
 
1074
                enum WacomSerialIndex {
 
1075
                    _WACSER_USB_ID = 0,
 
1076
                    _WACSER_LAST_TOOL_SERIAL,
 
1077
                    _WACSER_LAST_TOOL_ID,
 
1078
                    _WACSER_TOOL_SERIAL,
 
1079
                    _WACSER_TOOL_ID,
 
1080
                    _WACSER_COUNT
 
1081
                };
 
1082
                Atom propType;
 
1083
                int propFormat;
 
1084
                unsigned long numItems, bytesAfter;
 
1085
                unsigned char *data;
 
1086
                if (XIGetProperty(xDisplay, tabletData->deviceId, ev->property, 0, 100,
 
1087
                                  0, AnyPropertyType, &propType, &propFormat,
 
1088
                                  &numItems, &bytesAfter, &data) == Success) {
 
1089
                    if (propType == atom(QXcbAtom::INTEGER) && propFormat == 32 && numItems == _WACSER_COUNT) {
 
1090
                        quint32 *ptr = reinterpret_cast<quint32 *>(data);
 
1091
                        quint32 tool = ptr[_WACSER_TOOL_ID];
 
1092
                        // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
 
1093
                        // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
 
1094
                        if (!tool && ptr[_WACSER_TOOL_SERIAL])
 
1095
                            tool = ptr[_WACSER_TOOL_SERIAL];
 
1096
 
 
1097
                        // The property change event informs us which tool is in proximity or which one left proximity.
 
1098
                        if (tool) {
 
1099
                            tabletData->inProximity = true;
 
1100
                            tabletData->tool = toolIdToTabletDevice(tool);
 
1101
                            tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_TOOL_SERIAL]);
 
1102
                            QWindowSystemInterface::handleTabletEnterProximityEvent(ev->time,
 
1103
                                tabletData->tool, tabletData->pointerType, tabletData->serialId);
 
1104
                        } else {
 
1105
                            tabletData->inProximity = false;
 
1106
                            tabletData->tool = toolIdToTabletDevice(ptr[_WACSER_LAST_TOOL_ID]);
 
1107
                            // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
 
1108
                            // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
 
1109
                            if (!tabletData->tool)
 
1110
                                tabletData->tool = toolIdToTabletDevice(ptr[_WACSER_LAST_TOOL_SERIAL]);
 
1111
                            tabletData->serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_LAST_TOOL_SERIAL]);
 
1112
                            QWindowSystemInterface::handleTabletLeaveProximityEvent(ev->time,
 
1113
                                tabletData->tool, tabletData->pointerType, tabletData->serialId);
 
1114
                        }
 
1115
                        // TODO maybe have a hash of tabletData->deviceId to device data so we can
 
1116
                        // look up the tablet name here, and distinguish multiple tablets
 
1117
                        if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
 
1118
                            qCDebug(lcQpaXInputEvents, "XI2 proximity change on tablet %d (USB %x): last tool: %x id %x current tool: %x id %x TabletDevice %d",
 
1119
                                    tabletData->deviceId, ptr[_WACSER_USB_ID], ptr[_WACSER_LAST_TOOL_SERIAL], ptr[_WACSER_LAST_TOOL_ID],
 
1120
                                    ptr[_WACSER_TOOL_SERIAL], ptr[_WACSER_TOOL_ID], tabletData->tool);
 
1121
                    }
 
1122
                    XFree(data);
 
1123
                }
 
1124
            }
 
1125
        }
 
1126
        break;
 
1127
    }
 
1128
    default:
 
1129
        handled = false;
 
1130
        break;
 
1131
    }
 
1132
 
 
1133
    return handled;
 
1134
}
 
1135
 
 
1136
void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletData)
 
1137
{
 
1138
    const xXIDeviceEvent *ev = reinterpret_cast<const xXIDeviceEvent *>(event);
 
1139
    QXcbWindow *xcbWindow = platformWindowFromId(ev->event);
 
1140
    if (!xcbWindow)
 
1141
        return;
 
1142
    QWindow *window = xcbWindow->window();
 
1143
    const double scale = 65536.0;
 
1144
    QPointF local(ev->event_x / scale, ev->event_y / scale);
 
1145
    QPointF global(ev->root_x / scale, ev->root_y / scale);
 
1146
    double pressure = 0, rotation = 0, tangentialPressure = 0;
 
1147
    int xTilt = 0, yTilt = 0;
 
1148
 
 
1149
    for (QHash<int, TabletData::ValuatorClassInfo>::iterator it = tabletData->valuatorInfo.begin(),
 
1150
            ite = tabletData->valuatorInfo.end(); it != ite; ++it) {
 
1151
        int valuator = it.key();
 
1152
        TabletData::ValuatorClassInfo &classInfo(it.value());
 
1153
        xi2GetValuatorValueIfSet(event, classInfo.number, &classInfo.curVal);
 
1154
        double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal);
 
1155
        switch (valuator) {
 
1156
        case QXcbAtom::AbsPressure:
 
1157
            pressure = normalizedValue;
 
1158
            break;
 
1159
        case QXcbAtom::AbsTiltX:
 
1160
            xTilt = classInfo.curVal;
 
1161
            break;
 
1162
        case QXcbAtom::AbsTiltY:
 
1163
            yTilt = classInfo.curVal;
 
1164
            break;
 
1165
        case QXcbAtom::AbsWheel:
 
1166
            switch (tabletData->tool) {
 
1167
            case QTabletEvent::Airbrush:
 
1168
                tangentialPressure = normalizedValue * 2.0 - 1.0; // Convert 0..1 range to -1..+1 range
 
1169
                break;
 
1170
            case QTabletEvent::RotationStylus:
 
1171
                rotation = normalizedValue * 360.0 - 180.0; // Convert 0..1 range to -180..+180 degrees
 
1172
                break;
 
1173
            default:    // Other types of styli do not use this valuator
 
1174
                break;
 
1175
            }
 
1176
            break;
 
1177
        default:
 
1178
            break;
 
1179
        }
 
1180
    }
 
1181
 
 
1182
    if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled()))
 
1183
        qCDebug(lcQpaXInputEvents, "XI2 event on tablet %d with tool %d type %d seq %d detail %d time %d "
 
1184
            "pos %6.1f, %6.1f root pos %6.1f, %6.1f buttons 0x%x pressure %4.2lf tilt %d, %d rotation %6.2lf",
 
1185
            tabletData->deviceId, tabletData->tool, ev->evtype, ev->sequenceNumber, ev->detail, ev->time,
 
1186
            fixed1616ToReal(ev->event_x), fixed1616ToReal(ev->event_y),
 
1187
            fixed1616ToReal(ev->root_x), fixed1616ToReal(ev->root_y),
 
1188
            (int)tabletData->buttons, pressure, xTilt, yTilt, rotation);
 
1189
 
 
1190
    QWindowSystemInterface::handleTabletEvent(window, ev->time, local, global,
 
1191
                                              tabletData->tool, tabletData->pointerType,
 
1192
                                              tabletData->buttons, pressure,
 
1193
                                              xTilt, yTilt, tangentialPressure,
 
1194
                                              rotation, 0, tabletData->serialId);
 
1195
}
 
1196
 
 
1197
QXcbConnection::TabletData *QXcbConnection::tabletDataForDevice(int id)
 
1198
{
 
1199
    for (int i = 0; i < m_tabletData.count(); ++i) {
 
1200
        if (m_tabletData.at(i).deviceId == id)
 
1201
            return &m_tabletData[i];
 
1202
    }
 
1203
    return Q_NULLPTR;
 
1204
}
 
1205
 
 
1206
#endif // QT_NO_TABLETEVENT
 
1207
 
 
1208
#endif // XCB_USE_XINPUT2