Add PLUGIN_CLASS_NAME to qtbase plugins
[profile/ivi/qtbase.git] / src / plugins / platforms / xcb / qxcbconnection_xi2.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
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 Digia.  For licensing terms and
14 ** conditions see http://qt.digia.com/licensing.  For further information
15 ** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights.  These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file.  Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qxcbconnection.h"
43 #include "qxcbscreen.h"
44 #include "qxcbwindow.h"
45 #include "qtouchdevice.h"
46 #include <qpa/qwindowsysteminterface.h>
47 //#define XI2_TOUCH_DEBUG
48 #ifdef XI2_TOUCH_DEBUG
49 #include <QDebug>
50 #endif
51
52 #ifdef XCB_USE_XINPUT2
53
54 #include <X11/extensions/XInput2.h>
55 #include <X11/extensions/XI2proto.h>
56 #define FINGER_MAX_WIDTH_MM 10
57
58 struct XInput2DeviceData {
59     XInput2DeviceData()
60     : xiDeviceInfo(0)
61     , qtTouchDevice(0)
62     {
63     }
64     XIDeviceInfo *xiDeviceInfo;
65     QTouchDevice *qtTouchDevice;
66 };
67
68 #ifndef QT_NO_TABLETEVENT
69 static inline bool q_xi2_is_tablet(XIDeviceInfo *dev)
70 {
71     QByteArray name(dev->name);
72     name = name.toLower();
73     // Cannot just check for "wacom" because that would also pick up the touch and tablet-button devices.
74     return name.contains("stylus") || name.contains("eraser");
75 }
76 #endif // QT_NO_TABLETEVENT
77
78 void QXcbConnection::initializeXInput2()
79 {
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 = 0; // for tablet support 2.0 is enough
86             m_xi2Enabled = XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) != BadRequest;
87         } else {
88             m_xi2Enabled = true;
89         }
90         if (m_xi2Enabled) {
91 #ifndef QT_NO_TABLETEVENT
92             // Tablet support: Find the stylus-related devices.
93             xi2SetupTabletDevices();
94 #endif // QT_NO_TABLETEVENT
95         }
96     }
97 }
98
99 void QXcbConnection::finalizeXInput2()
100 {
101 }
102
103 void QXcbConnection::xi2Select(xcb_window_t window)
104 {
105     if (!m_xi2Enabled)
106         return;
107
108     Display *xDisplay = static_cast<Display *>(m_xlib_display);
109     unsigned int bitMask = 0;
110     unsigned char *xiBitMask = reinterpret_cast<unsigned char *>(&bitMask);
111
112 #ifdef XCB_USE_XINPUT22
113     // Select touch events on all master devices indiscriminately.
114     bitMask |= XI_TouchBeginMask;
115     bitMask |= XI_TouchUpdateMask;
116     bitMask |= XI_TouchEndMask;
117     XIEventMask mask;
118     mask.deviceid = XIAllMasterDevices;
119     mask.mask_len = sizeof(bitMask);
120     mask.mask = xiBitMask;
121     XISelectEvents(xDisplay, window, &mask, 1);
122 #endif
123
124 #ifndef QT_NO_TABLETEVENT
125     // For each tablet, select some additional event types.
126     // Press, motion, etc. events must never be selected for _all_ devices
127     // as that would render the standard XCB_MOTION_NOTIFY and
128     // similar handlers useless and we have no intention to infect
129     // all the pure xcb code with Xlib-based XI2.
130     if (!m_tabletData.isEmpty()) {
131         QVector<XIEventMask> xiEventMask(m_tabletData.count());
132         bitMask |= XI_ButtonPressMask;
133         bitMask |= XI_ButtonReleaseMask;
134         bitMask |= XI_MotionMask;
135         bitMask |= XI_PropertyEventMask;
136         for (int i = 0; i < m_tabletData.count(); ++i) {
137             xiEventMask[i].deviceid = m_tabletData.at(i).deviceId;
138             xiEventMask[i].mask_len = sizeof(bitMask);
139             xiEventMask[i].mask = xiBitMask;
140         }
141         XISelectEvents(xDisplay, window, xiEventMask.data(), m_tabletData.count());
142     }
143 #endif // QT_NO_TABLETEVENT
144 }
145
146 XInput2DeviceData *QXcbConnection::deviceForId(int id)
147 {
148     XInput2DeviceData *dev = m_touchDevices[id];
149     if (!dev) {
150         int unused = 0;
151         QTouchDevice::Capabilities caps = 0;
152         dev = new XInput2DeviceData;
153         dev->xiDeviceInfo = XIQueryDevice(static_cast<Display *>(m_xlib_display), id, &unused);
154         dev->qtTouchDevice = new QTouchDevice;
155         for (int i = 0; i < dev->xiDeviceInfo->num_classes; ++i) {
156             XIAnyClassInfo *classinfo = dev->xiDeviceInfo->classes[i];
157             switch (classinfo->type) {
158 #ifdef XCB_USE_XINPUT22
159             case XITouchClass: {
160                 XITouchClassInfo *tci = reinterpret_cast<XITouchClassInfo *>(classinfo);
161                 switch (tci->mode) {
162                 case XIModeRelative:
163                     dev->qtTouchDevice->setType(QTouchDevice::TouchPad);
164                     break;
165                 case XIModeAbsolute:
166                     dev->qtTouchDevice->setType(QTouchDevice::TouchScreen);
167                     break;
168                 }
169             } break;
170 #endif
171             case XIValuatorClass: {
172                 XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo);
173                 if (vci->label == atom(QXcbAtom::AbsMTPositionX))
174                     caps |= QTouchDevice::Position | QTouchDevice::NormalizedPosition;
175                 else if (vci->label == atom(QXcbAtom::AbsMTTouchMajor))
176                     caps |= QTouchDevice::Area;
177                 else if (vci->label == atom(QXcbAtom::AbsMTPressure) || vci->label == atom(QXcbAtom::AbsPressure))
178                     caps |= QTouchDevice::Pressure;
179             } break;
180             }
181         }
182         dev->qtTouchDevice->setCapabilities(caps);
183         dev->qtTouchDevice->setName(dev->xiDeviceInfo->name);
184         if (caps != 0)
185             QWindowSystemInterface::registerTouchDevice(dev->qtTouchDevice);
186 #ifdef XI2_TOUCH_DEBUG
187         qDebug("registered new device %s with %d classes and %d max touch points",
188             dev->xiDeviceInfo->name, dev->xiDeviceInfo->num_classes, dev->qtTouchDevice->maxTouchPoints());
189 #endif
190         m_touchDevices[id] = dev;
191     }
192     return dev;
193 }
194
195 #ifdef XCB_USE_XINPUT22
196 static qreal fixed1616ToReal(FP1616 val)
197 {
198     return (qreal(val >> 16)) + (val & 0xFF) / (qreal)0xFF;
199 }
200
201 static qreal valuatorNormalized(double value, XIValuatorClassInfo *vci)
202 {
203     if (value > vci->max)
204         value = vci->max;
205     if (value < vci->min)
206         value = vci->min;
207     return (value - vci->min) / (vci->max - vci->min);
208 }
209 #endif
210
211 void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
212 {
213     if (xi2PrepareXIGenericDeviceEvent(event, m_xiOpCode)) {
214         xXIGenericDeviceEvent *xiEvent = reinterpret_cast<xXIGenericDeviceEvent *>(event);
215
216 #ifndef QT_NO_TABLETEVENT
217         for (int i = 0; i < m_tabletData.count(); ++i) {
218             if (m_tabletData.at(i).deviceId == xiEvent->deviceid) {
219                 if (xi2HandleTabletEvent(xiEvent, &m_tabletData[i]))
220                     return;
221             }
222         }
223 #endif // QT_NO_TABLETEVENT
224
225 #ifdef XCB_USE_XINPUT22
226         if (xiEvent->evtype == XI_TouchBegin || xiEvent->evtype == XI_TouchUpdate || xiEvent->evtype == XI_TouchEnd) {
227             xXIDeviceEvent* xiDeviceEvent = reinterpret_cast<xXIDeviceEvent *>(event);
228 #ifdef XI2_TOUCH_DEBUG
229             qDebug("XI2 event type %d seq %d detail %d pos 0x%X,0x%X %f,%f root pos %f,%f",
230                 event->event_type, xiEvent->sequenceNumber, xiDeviceEvent->detail,
231                 xiDeviceEvent->event_x, xiDeviceEvent->event_y,
232                 fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y),
233                 fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y) );
234 #endif
235
236             if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) {
237                 XInput2DeviceData *dev = deviceForId(xiEvent->deviceid);
238                 if (xiEvent->evtype == XI_TouchBegin) {
239                     QWindowSystemInterface::TouchPoint tp;
240                     tp.id = xiDeviceEvent->detail % INT_MAX;
241                     tp.state = Qt::TouchPointPressed;
242                     tp.pressure = -1.0;
243                     m_touchPoints[tp.id] = tp;
244                 }
245                 QWindowSystemInterface::TouchPoint &touchPoint = m_touchPoints[xiDeviceEvent->detail];
246                 qreal x = fixed1616ToReal(xiDeviceEvent->root_x);
247                 qreal y = fixed1616ToReal(xiDeviceEvent->root_y);
248                 qreal nx = -1.0, ny = -1.0, w = 0.0, h = 0.0;
249                 QXcbScreen* screen = m_screens.at(0);
250                 for (int i = 0; i < dev->xiDeviceInfo->num_classes; ++i) {
251                     XIAnyClassInfo *classinfo = dev->xiDeviceInfo->classes[i];
252                     if (classinfo->type == XIValuatorClass) {
253                         XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo);
254                         int n = vci->number;
255                         double value;
256                         if (!xi2GetValuatorValueIfSet(xiDeviceEvent, n, &value))
257                             continue;
258 #ifdef XI2_TOUCH_DEBUG
259                         qDebug("   valuator class label %d value %lf from range %lf -> %lf name %s",
260                             vci->label, value, vci->min, vci->max, XGetAtomName(static_cast<Display *>(m_xlib_display), vci->label) );
261 #endif
262                         if (vci->label == atom(QXcbAtom::AbsMTPositionX)) {
263                             nx = valuatorNormalized(value, vci);
264                         } else if (vci->label == atom(QXcbAtom::AbsMTPositionY)) {
265                             ny = valuatorNormalized(value, vci);
266                         } else if (vci->label == atom(QXcbAtom::AbsMTTouchMajor)) {
267                             // Convert the value within its range as a fraction of a finger's max (contact patch)
268                             //  width in mm, and from there to pixels depending on screen resolution
269                             w = valuatorNormalized(value, vci) * FINGER_MAX_WIDTH_MM *
270                                 screen->geometry().width() / screen->physicalSize().width();
271                         } else if (vci->label == atom(QXcbAtom::AbsMTTouchMinor)) {
272                             h = valuatorNormalized(value, vci) * FINGER_MAX_WIDTH_MM *
273                                 screen->geometry().height() / screen->physicalSize().height();
274                         } else if (vci->label == atom(QXcbAtom::AbsMTPressure) ||
275                                    vci->label == atom(QXcbAtom::AbsPressure)) {
276                             touchPoint.pressure = valuatorNormalized(value, vci);
277                         }
278                     }
279                 }
280                 // If any value was not updated, use the last-known value.
281                 if (nx == -1.0) {
282                     x = touchPoint.area.center().x();
283                     nx = x / screen->geometry().width();
284                 }
285                 if (ny == -1.0) {
286                     y = touchPoint.area.center().y();
287                     ny = y / screen->geometry().height();
288                 }
289                 if (xiEvent->evtype != XI_TouchEnd) {
290                     if (w == 0.0)
291                         w = touchPoint.area.width();
292                     if (h == 0.0)
293                         h = touchPoint.area.height();
294                 }
295
296                 switch (xiEvent->evtype) {
297                 case XI_TouchUpdate:
298                     if (touchPoint.area.center() != QPoint(x, y))
299                         touchPoint.state = Qt::TouchPointMoved;
300                     else
301                         touchPoint.state = Qt::TouchPointStationary;
302                     break;
303                 case XI_TouchEnd:
304                     touchPoint.state = Qt::TouchPointReleased;
305                 }
306                 touchPoint.area = QRectF(x - w/2, y - h/2, w, h);
307                 touchPoint.normalPosition = QPointF(nx, ny);
308
309 #ifdef XI2_TOUCH_DEBUG
310                 qDebug() << "   tp "  << touchPoint.id << " state " << touchPoint.state << " pos norm " << touchPoint.normalPosition <<
311                     " area " << touchPoint.area << " pressure " << touchPoint.pressure;
312 #endif
313                 QWindowSystemInterface::handleTouchEvent(platformWindow->window(), xiEvent->time, dev->qtTouchDevice, m_touchPoints.values());
314                 // If a touchpoint was released, we can forget it, because the ID won't be reused.
315                 if (touchPoint.state == Qt::TouchPointReleased)
316                     m_touchPoints.remove(touchPoint.id);
317             }
318         }
319 #endif
320     }
321 }
322
323 #ifndef QT_NO_TABLETEVENT
324 void QXcbConnection::xi2QueryTabletData(void *dev, TabletData *tabletData)
325 {
326     XIDeviceInfo *device = static_cast<XIDeviceInfo *>(dev);
327     tabletData->deviceId = device->deviceid;
328
329     tabletData->pointerType = QTabletEvent::Pen;
330     if (QByteArray(device->name).toLower().contains("eraser"))
331         tabletData->pointerType = QTabletEvent::Eraser;
332
333     for (int i = 0; i < device->num_classes; ++i) {
334         switch (device->classes[i]->type) {
335         case XIValuatorClass: {
336             XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(device->classes[i]);
337             int val = 0;
338             if (vci->label == atom(QXcbAtom::AbsX))
339                 val = QXcbAtom::AbsX;
340             else if (vci->label == atom(QXcbAtom::AbsY))
341                 val = QXcbAtom::AbsY;
342             else if (vci->label == atom(QXcbAtom::AbsPressure))
343                 val = QXcbAtom::AbsPressure;
344             else if (vci->label == atom(QXcbAtom::AbsTiltX))
345                 val = QXcbAtom::AbsTiltX;
346             else if (vci->label == atom(QXcbAtom::AbsTiltY))
347                 val = QXcbAtom::AbsTiltY;
348             else if (vci->label == atom(QXcbAtom::AbsWheel))
349                 val = QXcbAtom::AbsWheel;
350             else if (vci->label == atom(QXcbAtom::AbsDistance))
351                 val = QXcbAtom::AbsDistance;
352             if (val) {
353                 TabletData::ValuatorClassInfo info;
354                 info.minVal = vci->min;
355                 info.maxVal = vci->max;
356                 info.number = vci->number;
357                 tabletData->valuatorInfo[val] = info;
358             }
359         }
360             break;
361         default:
362             break;
363         }
364     }
365 }
366
367 void QXcbConnection::xi2SetupTabletDevices()
368 {
369     Display *xDisplay = static_cast<Display *>(m_xlib_display);
370     m_tabletData.clear();
371     int deviceCount = 0;
372     XIDeviceInfo *devices = XIQueryDevice(xDisplay, XIAllDevices, &deviceCount);
373     if (devices) {
374         for (int i = 0; i < deviceCount; ++i) {
375             int unused = 0;
376             XIDeviceInfo *dev = XIQueryDevice(xDisplay, devices[i].deviceid, &unused);
377             if (dev) {
378                 if (q_xi2_is_tablet(dev)) {
379                     TabletData tabletData;
380                     xi2QueryTabletData(dev, &tabletData);
381 #ifdef XI2_TOUCH_DEBUG
382                     qDebug() << "found tablet" << dev->name;
383 #endif
384                     m_tabletData.append(tabletData);
385                 }
386                 XIFreeDeviceInfo(dev);
387             }
388         }
389         XIFreeDeviceInfo(devices);
390     }
391 }
392
393 bool QXcbConnection::xi2HandleTabletEvent(void *event, TabletData *tabletData)
394 {
395     bool handled = true;
396     Display *xDisplay = static_cast<Display *>(m_xlib_display);
397     xXIGenericDeviceEvent *xiEvent = static_cast<xXIGenericDeviceEvent *>(event);
398     switch (xiEvent->evtype) {
399     case XI_ButtonPress: // stylus down
400         if (reinterpret_cast<xXIDeviceEvent *>(event)->detail == 1) { // ignore the physical buttons on the stylus
401             tabletData->down = true;
402             xi2ReportTabletEvent(*tabletData, xiEvent);
403         }
404         break;
405     case XI_ButtonRelease: // stylus up
406         if (reinterpret_cast<xXIDeviceEvent *>(event)->detail == 1) {
407             tabletData->down = false;
408             xi2ReportTabletEvent(*tabletData, xiEvent);
409         }
410         break;
411     case XI_Motion:
412         // Report TabletMove only when the stylus is touching the tablet.
413         // No possibility to report proximity motion (no suitable Qt event exists yet).
414         if (tabletData->down)
415             xi2ReportTabletEvent(*tabletData, xiEvent);
416         break;
417     case XI_PropertyEvent: {
418         xXIPropertyEvent *ev = reinterpret_cast<xXIPropertyEvent *>(event);
419         if (ev->what == XIPropertyModified) {
420             if (ev->property == atom(QXcbAtom::WacomSerialIDs)) {
421                 Atom propType;
422                 int propFormat;
423                 unsigned long numItems, bytesAfter;
424                 unsigned char *data;
425                 if (XIGetProperty(xDisplay, tabletData->deviceId, ev->property, 0, 100,
426                                   0, AnyPropertyType, &propType, &propFormat,
427                                   &numItems, &bytesAfter, &data) == Success) {
428                     if (propType == atom(QXcbAtom::INTEGER) && propFormat == 32) {
429                         int *ptr = reinterpret_cast<int *>(data);
430                         for (unsigned long i = 0; i < numItems; ++i)
431                             tabletData->serialId |= qint64(ptr[i]) << (i * 32);
432                     }
433                     XFree(data);
434                 }
435                 // With recent-enough X drivers this property change event seems to come always
436                 // when entering and leaving proximity. Due to the lack of other options hook up
437                 // the enter/leave events to it.
438                 if (tabletData->inProximity) {
439                     tabletData->inProximity = false;
440                     QWindowSystemInterface::handleTabletLeaveProximityEvent(QTabletEvent::Stylus,
441                                                                             tabletData->pointerType,
442                                                                             tabletData->serialId);
443                 } else {
444                     tabletData->inProximity = true;
445                     QWindowSystemInterface::handleTabletEnterProximityEvent(QTabletEvent::Stylus,
446                                                                             tabletData->pointerType,
447                                                                             tabletData->serialId);
448                 }
449             }
450         }
451         break;
452     }
453     default:
454         handled = false;
455         break;
456     }
457     return handled;
458 }
459
460 void QXcbConnection::xi2ReportTabletEvent(const TabletData &tabletData, void *event)
461 {
462     xXIDeviceEvent *ev = reinterpret_cast<xXIDeviceEvent *>(event);
463     QXcbWindow *xcbWindow = platformWindowFromId(ev->event);
464     if (!xcbWindow)
465         return;
466     QWindow *window = xcbWindow->window();
467     const double scale = 65536.0;
468     QPointF local(ev->event_x / scale, ev->event_y / scale);
469     QPointF global(ev->root_x / scale, ev->root_y / scale);
470     double pressure = 0, rotation = 0;
471     int xTilt = 0, yTilt = 0;
472
473     for (QHash<int, TabletData::ValuatorClassInfo>::const_iterator it = tabletData.valuatorInfo.constBegin(),
474             ite = tabletData.valuatorInfo.constEnd(); it != ite; ++it) {
475         int valuator = it.key();
476         const TabletData::ValuatorClassInfo &classInfo(it.value());
477         double value;
478         if (xi2GetValuatorValueIfSet(event, classInfo.number, &value)) {
479             double normalizedValue = (value - classInfo.minVal) / double(classInfo.maxVal - classInfo.minVal);
480             switch (valuator) {
481             case QXcbAtom::AbsPressure:
482                 pressure = normalizedValue;
483                 break;
484             case QXcbAtom::AbsTiltX:
485                 xTilt = value;
486                 break;
487             case QXcbAtom::AbsTiltY:
488                 yTilt = value;
489                 break;
490             case QXcbAtom::AbsWheel:
491                 rotation = value / 64.0;
492                 break;
493             default:
494                 break;
495             }
496         }
497     }
498
499     QWindowSystemInterface::handleTabletEvent(window, tabletData.down, local, global,
500                                               QTabletEvent::Stylus, tabletData.pointerType,
501                                               pressure, xTilt, yTilt, 0,
502                                               rotation, 0, tabletData.serialId);
503 }
504 #endif // QT_NO_TABLETEVENT
505
506 #endif // XCB_USE_XINPUT2