summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.p.agocs@nokia.com>2012-06-05 16:04:02 +0300
committerQt by Nokia <qt-info@nokia.com>2012-06-28 12:35:14 +0200
commit26c85a5f79875a25367527462e8d0864021078ab (patch)
treefce112bb5ed906eeff1ded77c0863315a66f595b /src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
parentba892305960b37846ee49a90c5684aa8dbfd3d72 (diff)
Basic tablet support in xcb through XI2
The Maemo-specific function have been renamed a bit to prevent them clashing with the more generic stuff. Task-number: QTBUG-25865 Change-Id: Id55693159e15d5a0c679546eb48308feb48acac9 Reviewed-by: Samuel Rødal <samuel.rodal@nokia.com>
Diffstat (limited to 'src/plugins/platforms/xcb/qxcbconnection_xi2.cpp')
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection_xi2.cpp310
1 files changed, 310 insertions, 0 deletions
diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
new file mode 100644
index 0000000000..026fd8c5af
--- /dev/null
+++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
@@ -0,0 +1,310 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qxcbconnection.h"
+#include "qxcbwindow.h"
+#include <QtGui/QWindowSystemInterface>
+
+#ifdef XCB_USE_XINPUT2
+
+#include <X11/extensions/XInput2.h>
+#include <X11/extensions/XI2proto.h>
+
+#ifndef QT_NO_TABLETEVENT
+static inline bool q_xi2_is_tablet(XIDeviceInfo *dev)
+{
+ QByteArray name(dev->name);
+ name = name.toLower();
+ // Cannot just check for "wacom" because that would also pick up the touch and tablet-button devices.
+ return name.contains("stylus") || name.contains("eraser");
+}
+#endif // QT_NO_TABLETEVENT
+
+void QXcbConnection::initializeXInput2()
+{
+ Display *xDisplay = static_cast<Display *>(m_xlib_display);
+ if (XQueryExtension(xDisplay, "XInputExtension", &m_xiOpCode, &m_xiEventBase, &m_xiErrorBase)) {
+ int xiMajor = 2;
+ m_xi2Minor = 2; // try 2.2 first, needed for TouchBegin/Update/End
+ if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) {
+ m_xi2Minor = 0; // for tablet support 2.0 is enough
+ m_xi2Enabled = XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) != BadRequest;
+ } else {
+ m_xi2Enabled = true;
+ }
+
+ if (m_xi2Enabled) {
+#ifndef QT_NO_TABLETEVENT
+ // Tablet support: Figure out the stylus-related devices. We will
+ // only select events on this device. Press, motion, etc. events
+ // must never be selected for _all_ devices as that would render
+ // the standard XCB_MOTION_NOTIFY and similar handlers useless and
+ // we have no intention to infect all the pure xcb code with
+ // Xlib-based XI2.
+ xi2SetupTabletDevices();
+#endif // QT_NO_TABLETEVENT
+ }
+ }
+}
+
+void QXcbConnection::finalizeXInput2()
+{
+}
+
+void QXcbConnection::xi2Select(xcb_window_t window)
+{
+ if (!m_xi2Enabled)
+ return;
+
+#ifndef QT_NO_TABLETEVENT
+ // Tablets.
+ if (!m_tabletData.isEmpty()) {
+ Display *xDisplay = static_cast<Display *>(m_xlib_display);
+ QVector<XIEventMask> xiEventMask(m_tabletData.count());
+ unsigned int bitMask = 0;
+ unsigned char *xiBitMask = reinterpret_cast<unsigned char *>(&bitMask);
+ bitMask |= XI_ButtonPressMask;
+ bitMask |= XI_ButtonReleaseMask;
+ bitMask |= XI_MotionMask;
+ bitMask |= XI_PropertyEventMask;
+ for (int i = 0; i < m_tabletData.count(); ++i) {
+ xiEventMask[i].deviceid = m_tabletData.at(i).deviceId;
+ xiEventMask[i].mask_len = sizeof(bitMask);
+ xiEventMask[i].mask = xiBitMask;
+ }
+ XISelectEvents(xDisplay, window, xiEventMask.data(), m_tabletData.count());
+ }
+#endif // QT_NO_TABLETEVENT
+}
+
+void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
+{
+ if (xi2PrepareXIGenericDeviceEvent(event, m_xiOpCode)) {
+ xXIGenericDeviceEvent *xiEvent = reinterpret_cast<xXIGenericDeviceEvent *>(event);
+#ifndef QT_NO_TABLETEVENT
+ for (int i = 0; i < m_tabletData.count(); ++i) {
+ if (m_tabletData.at(i).deviceId == xiEvent->deviceid) {
+ xi2HandleTabletEvent(xiEvent, &m_tabletData[i]);
+ return;
+ }
+ }
+#endif // QT_NO_TABLETEVENT
+ }
+}
+
+#ifndef QT_NO_TABLETEVENT
+void QXcbConnection::xi2QueryTabletData(void *dev, TabletData *tabletData)
+{
+ XIDeviceInfo *device = static_cast<XIDeviceInfo *>(dev);
+ tabletData->deviceId = device->deviceid;
+
+ tabletData->pointerType = QTabletEvent::Pen;
+ if (QByteArray(device->name).toLower().contains("eraser"))
+ tabletData->pointerType = QTabletEvent::Eraser;
+
+ for (int i = 0; i < device->num_classes; ++i) {
+ switch (device->classes[i]->type) {
+ case XIValuatorClass: {
+ XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(device->classes[i]);
+ int val = 0;
+ if (vci->label == atom(QXcbAtom::AbsX))
+ val = QXcbAtom::AbsX;
+ else if (vci->label == atom(QXcbAtom::AbsY))
+ val = QXcbAtom::AbsY;
+ else if (vci->label == atom(QXcbAtom::AbsPressure))
+ val = QXcbAtom::AbsPressure;
+ else if (vci->label == atom(QXcbAtom::AbsTiltX))
+ val = QXcbAtom::AbsTiltX;
+ else if (vci->label == atom(QXcbAtom::AbsTiltY))
+ val = QXcbAtom::AbsTiltY;
+ else if (vci->label == atom(QXcbAtom::AbsWheel))
+ val = QXcbAtom::AbsWheel;
+ else if (vci->label == atom(QXcbAtom::AbsDistance))
+ val = QXcbAtom::AbsDistance;
+ if (val) {
+ TabletData::ValuatorClassInfo info;
+ info.minVal = vci->min;
+ info.maxVal = vci->max;
+ info.number = vci->number;
+ tabletData->valuatorInfo[val] = info;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void QXcbConnection::xi2SetupTabletDevices()
+{
+ Display *xDisplay = static_cast<Display *>(m_xlib_display);
+ m_tabletData.clear();
+ int deviceCount = 0;
+ XIDeviceInfo *devices = XIQueryDevice(xDisplay, XIAllDevices, &deviceCount);
+ if (devices) {
+ for (int i = 0; i < deviceCount; ++i) {
+ int unused = 0;
+ XIDeviceInfo *dev = XIQueryDevice(xDisplay, devices[i].deviceid, &unused);
+ if (dev) {
+ if (q_xi2_is_tablet(dev)) {
+ TabletData tabletData;
+ xi2QueryTabletData(dev, &tabletData);
+ m_tabletData.append(tabletData);
+ }
+ XIFreeDeviceInfo(dev);
+ }
+ }
+ XIFreeDeviceInfo(devices);
+ }
+}
+
+void QXcbConnection::xi2HandleTabletEvent(void *event, TabletData *tabletData)
+{
+ Display *xDisplay = static_cast<Display *>(m_xlib_display);
+ xXIGenericDeviceEvent *xiEvent = static_cast<xXIGenericDeviceEvent *>(event);
+ switch (xiEvent->evtype) {
+ case XI_ButtonPress: // stylus down
+ if (reinterpret_cast<xXIDeviceEvent *>(event)->detail == 1) { // ignore the physical buttons on the stylus
+ tabletData->down = true;
+ xi2ReportTabletEvent(*tabletData, xiEvent);
+ }
+ break;
+ case XI_ButtonRelease: // stylus up
+ if (reinterpret_cast<xXIDeviceEvent *>(event)->detail == 1) {
+ tabletData->down = false;
+ xi2ReportTabletEvent(*tabletData, xiEvent);
+ }
+ break;
+ case XI_Motion:
+ // Report TabletMove only when the stylus is touching the tablet.
+ // No possiblity to report proximity motion (no suitable Qt event exists yet).
+ if (tabletData->down)
+ xi2ReportTabletEvent(*tabletData, xiEvent);
+ break;
+ case XI_PropertyEvent: {
+ xXIPropertyEvent *ev = reinterpret_cast<xXIPropertyEvent *>(event);
+ if (ev->what == XIPropertyModified) {
+ if (ev->property == atom(QXcbAtom::WacomSerialIDs)) {
+ Atom propType;
+ int propFormat;
+ unsigned long numItems, bytesAfter;
+ unsigned char *data;
+ if (XIGetProperty(xDisplay, tabletData->deviceId, ev->property, 0, 100,
+ 0, AnyPropertyType, &propType, &propFormat,
+ &numItems, &bytesAfter, &data) == Success) {
+ if (propType == atom(QXcbAtom::INTEGER) && propFormat == 32) {
+ int *ptr = reinterpret_cast<int *>(data);
+ for (unsigned long i = 0; i < numItems; ++i)
+ tabletData->serialId |= qint64(ptr[i]) << (i * 32);
+ }
+ XFree(data);
+ }
+ // With recent-enough X drivers this property change event seems to come always
+ // when entering and leaving proximity. Due to the lack of other options hook up
+ // the enter/leave events to it.
+ if (tabletData->inProximity) {
+ tabletData->inProximity = false;
+ QWindowSystemInterface::handleTabletLeaveProximityEvent(QTabletEvent::Stylus,
+ tabletData->pointerType,
+ tabletData->serialId);
+ } else {
+ tabletData->inProximity = true;
+ QWindowSystemInterface::handleTabletEnterProximityEvent(QTabletEvent::Stylus,
+ tabletData->pointerType,
+ tabletData->serialId);
+ }
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void QXcbConnection::xi2ReportTabletEvent(const TabletData &tabletData, void *event)
+{
+ xXIDeviceEvent *ev = reinterpret_cast<xXIDeviceEvent *>(event);
+ QXcbWindow *xcbWindow = platformWindowFromId(ev->event);
+ if (!xcbWindow)
+ return;
+ QWindow *window = xcbWindow->window();
+ const double scale = 65536.0;
+ QPointF local(ev->event_x / scale, ev->event_y / scale);
+ QPointF global(ev->root_x / scale, ev->root_y / scale);
+ double pressure = 0, rotation = 0;
+ int xTilt = 0, yTilt = 0;
+
+ for (QHash<int, TabletData::ValuatorClassInfo>::const_iterator it = tabletData.valuatorInfo.constBegin(),
+ ite = tabletData.valuatorInfo.constEnd(); it != ite; ++it) {
+ int valuator = it.key();
+ const TabletData::ValuatorClassInfo &classInfo(it.value());
+ double value;
+ if (xi2GetValuatorValueIfSet(event, classInfo.number, &value)) {
+ double normalizedValue = (value - classInfo.minVal) / double(classInfo.maxVal - classInfo.minVal);
+ switch (valuator) {
+ case QXcbAtom::AbsPressure:
+ pressure = normalizedValue;
+ break;
+ case QXcbAtom::AbsTiltX:
+ xTilt = value;
+ break;
+ case QXcbAtom::AbsTiltY:
+ yTilt = value;
+ break;
+ case QXcbAtom::AbsWheel:
+ rotation = value / 64.0;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ QWindowSystemInterface::handleTabletEvent(window, tabletData.down, local, global,
+ QTabletEvent::Stylus, tabletData.pointerType,
+ pressure, xTilt, yTilt, 0,
+ rotation, 0, tabletData.serialId);
+}
+#endif // QT_NO_TABLETEVENT
+
+#endif // XCB_USE_XINPUT2