diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -247,9 +247,8 @@ find_package(X11) if(X11_FOUND) - find_package(Qt5 ${MIN_QT_VERSION} REQUIRED NO_MODULE COMPONENTS - X11Extras - ) + find_package(Qt5 ${MIN_QT_VERSION} REQUIRED NO_MODULE COMPONENTS X11Extras) + find_package(XCB REQUIRED COMPONENTS XCB) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) else() diff --git a/krita/main.cc b/krita/main.cc --- a/krita/main.cc +++ b/krita/main.cc @@ -146,6 +146,16 @@ app.setSplashScreen(splash); + +#if defined Q_OS_WIN + KisTabletSupportWin::init(); + app.installNativeEventFilter(new KisTabletSupportWin()); +#elif defined HAVE_X11 + KisTabletSupportXcb::init(); + // TODO: who owns the filter object? + app.installNativeEventFilter(new KisTabletSupportXcb()); +#endif + if (!app.start(args)) { return 1; } diff --git a/krita/sketch/main.cpp b/krita/sketch/main.cpp --- a/krita/sketch/main.cpp +++ b/krita/sketch/main.cpp @@ -161,9 +161,9 @@ KisTabletSupportWin::init(); app.installNativeEventFilter(new KisTabletSupportWin()); #elif defined HAVE_X11 - KisTabletSupportX11::init(); + KisTabletSupportXcb::init(); // TODO: who owns the filter object? - app.installNativeEventFilter(new KisTabletSupportX11()); + app.installNativeEventFilter(new KisTabletSupportXcb()); #endif app.start(); diff --git a/krita/ui/CMakeLists.txt b/krita/ui/CMakeLists.txt --- a/krita/ui/CMakeLists.txt +++ b/krita/ui/CMakeLists.txt @@ -357,6 +357,7 @@ set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_x11.cpp + input/wintab/kis_xcb_connection.cpp ) endif() endif() @@ -453,7 +454,7 @@ endif() if (NOT WIN32 AND NOT APPLE) - target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB}) + target_link_libraries(kritaui XCB::XCB ${X11_X11_LIB} ${X11_Xinput_LIB}) endif() if(APPLE) diff --git a/krita/ui/input/kis_input_manager.cpp b/krita/ui/input/kis_input_manager.cpp --- a/krita/ui/input/kis_input_manager.cpp +++ b/krita/ui/input/kis_input_manager.cpp @@ -160,7 +160,8 @@ if (object != d->eventsReceiver) return false; - // If we have saved an event, take care of it now. + // If we have saved an event, take care of it now. (XCB should take care of this!) +#if !defined(HAVE_X11) if (d->eventEater.savedEvent) { auto savedEvent = d->eventEater.savedEvent; d->eventEater.savedEvent = 0; @@ -170,9 +171,8 @@ } delete savedEvent; } - if (d->eventEater.eventFilter(object, event)) return false; - +#endif foreach (QPointer filter, d->priorityEventFilter) { if (filter.isNull()) { diff --git a/krita/ui/input/kis_input_manager_p.h b/krita/ui/input/kis_input_manager_p.h --- a/krita/ui/input/kis_input_manager_p.h +++ b/krita/ui/input/kis_input_manager_p.h @@ -109,6 +109,7 @@ int eatOneMouseStroke; }; +#if !defined(HAVE_X11) class EventEater { public: @@ -126,9 +127,10 @@ bool hungry{false}; // Continue eating mouse strokes bool peckish{false}; // Eat a single mouse press event }; - - CanvasSwitcher canvasSwitcher; EventEater eventEater; +#endif + + CanvasSwitcher canvasSwitcher; bool focusOnEnter = true; bool containsPointer = true; diff --git a/krita/ui/input/kis_input_manager_p.cpp b/krita/ui/input/kis_input_manager_p.cpp --- a/krita/ui/input/kis_input_manager_p.cpp +++ b/krita/ui/input/kis_input_manager_p.cpp @@ -36,6 +36,8 @@ /** * This hungry class EventEater encapsulates event masking logic. * + * It should not be necessary if we handle our own events. Currently this is done on X11 only. + * * Its basic role is to kill synthetic mouseMove events sent by Xorg or Qt after * tablet events. Those events are sent in order to allow widgets that haven't * implemented tablet specific functionality to seamlessly behave as if one were @@ -65,6 +67,7 @@ * hand, that would be simply preposterous. */ +#if !defined(HAVE_X11) bool KisInputManager::Private::EventEater::eventFilter(QObject* target, QEvent* event ) { if ((hungry && (event->type() == QEvent::MouseMove || @@ -119,20 +122,24 @@ } // This would be a solution if we had reliable proximity events. SIGH -// void eatOneMousePress() +// void KisInputManager::Private::eatOneMousePress() // { // peckish = true; // } bool KisInputManager::Private::EventEater::isActive() { return hungry; } - +#endif bool KisInputManager::Private::ignoreQtCursorEvents() { +#if !defined(HAVE_X11) return eventEater.isActive(); +#else + return false; +#endif } KisInputManager::Private::Private(KisInputManager *qq) @@ -417,12 +424,16 @@ void KisInputManager::Private::blockMouseEvents() { +#if !defined(HAVE_X11) eventEater.activate(); +#endif } void KisInputManager::Private::allowMouseEvents() { +#if !defined(HAVE_X11) eventEater.deactivate(); +#endif } bool KisInputManager::Private::handleCompressedTabletEvent(QObject *object, QTabletEvent *tevent) diff --git a/krita/ui/input/kis_tablet_debugger.cpp b/krita/ui/input/kis_tablet_debugger.cpp --- a/krita/ui/input/kis_tablet_debugger.cpp +++ b/krita/ui/input/kis_tablet_debugger.cpp @@ -58,7 +58,7 @@ { s << "btn: " << button(ev) << " "; s << "btns: " << buttons(ev) << " "; - s << "pos: " << qSetFieldWidth(4) << ev.x() << qSetFieldWidth(0) << "," << qSetFieldWidth(4) << ev.y() << qSetFieldWidth(0) << " "; + s << "lpos: " << qSetFieldWidth(4) << ev.x() << qSetFieldWidth(0) << "," << qSetFieldWidth(4) << ev.y() << qSetFieldWidth(0) << " "; s << "gpos: " << qSetFieldWidth(4) << ev.globalX() << qSetFieldWidth(0) << "," << qSetFieldWidth(4) << ev.globalY() << qSetFieldWidth(0) << " "; } diff --git a/krita/ui/input/wintab/kis_tablet_support.h b/krita/ui/input/wintab/kis_tablet_support.h --- a/krita/ui/input/wintab/kis_tablet_support.h +++ b/krita/ui/input/wintab/kis_tablet_support.h @@ -36,6 +36,7 @@ #include "kis_incremental_average.h" #endif +#undef None // thanks, XInput.h struct QTabletDeviceData { diff --git a/krita/ui/input/wintab/kis_tablet_support_x11.h b/krita/ui/input/wintab/kis_tablet_support_x11.h --- a/krita/ui/input/wintab/kis_tablet_support_x11.h +++ b/krita/ui/input/wintab/kis_tablet_support_x11.h @@ -22,9 +22,10 @@ #include #include -class KRITAUI_EXPORT KisTabletSupportX11 : public QAbstractNativeEventFilter +class KRITAUI_EXPORT KisTabletSupportXcb : public QAbstractNativeEventFilter { public: + ~KisTabletSupportXcb(); static void init(); virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result); }; diff --git a/krita/ui/input/wintab/kis_tablet_support_x11.cpp b/krita/ui/input/wintab/kis_tablet_support_x11.cpp --- a/krita/ui/input/wintab/kis_tablet_support_x11.cpp +++ b/krita/ui/input/wintab/kis_tablet_support_x11.cpp @@ -24,14 +24,17 @@ #include #include "kis_debug.h" -#include "kis_config.h" -#include #include "kis_tablet_support.h" +#include "kis_xcb_connection.h" #include "wacom-properties.h" +#include -#include -#include +// Note: XInput 2.2 is required +#include +#include +#include +#include /** * This is an analog of a Qt's variable qt_tabletChokeMouse. It is @@ -48,125 +51,23 @@ */ bool kis_haveEvdevTablets = false; -// link Xinput statically -#define XINPUT_LOAD(symbol) symbol -typedef int (*PtrXCloseDevice)(Display *, XDevice *); -typedef XDeviceInfo* (*PtrXListInputDevices)(Display *, int *); -typedef XDevice* (*PtrXOpenDevice)(Display *, XID); -typedef void (*PtrXFreeDeviceList)(XDeviceInfo *); -typedef int (*PtrXSelectExtensionEvent)(Display *, Window, XEventClass *, int); +KisXcbConnection *KIS_XCB = 0; -struct KisX11Data -{ - Display *display; - - bool use_xinput; - int xinput_major; - int xinput_eventbase; - int xinput_errorbase; - - PtrXCloseDevice ptrXCloseDevice; - PtrXListInputDevices ptrXListInputDevices; - PtrXOpenDevice ptrXOpenDevice; - PtrXFreeDeviceList ptrXFreeDeviceList; - PtrXSelectExtensionEvent ptrXSelectExtensionEvent; - - /* Warning: if you modify this list, modify the names of atoms in kis_x11_atomnames as well! */ - enum X11Atom { - XWacomStylus, - XWacomCursor, - XWacomEraser, - - XTabletStylus, - XTabletEraser, - - XInputTablet, - - XInputKeyboard, - - AxisLabels, - ATOM, - - AbsX, - AbsY, - AbsPressure, - AbsTiltX, - AbsTiltY, - - WacomTouch, - - AiptekStylus, - - NPredefinedAtoms, - NAtoms = NPredefinedAtoms - }; - Atom atoms[NAtoms]; -}; - -/* Warning: if you modify this string, modify the list of atoms in KisX11Data as well! */ -static const char kis_x11_atomnames[] = { - // Wacom old. (before version 0.10) - "Wacom Stylus\0" - "Wacom Cursor\0" - "Wacom Eraser\0" +#define KIS_ATOM(x) KIS_XCB->m_allAtoms[KisXcbAtom::x] +#define KIS_X11 KIS_XCB - // Tablet - "STYLUS\0" - "ERASER\0" - - // XInput tablet device - "TABLET\0" - - // Really "nice" Aiptek devices reporting they are a keyboard - "KEYBOARD\0" - - // Evdev property that report the assignment of axes - "Axis Labels\0" - "ATOM\0" - - // Types of axes reported by evdev - "Abs X\0" - "Abs Y\0" - "Abs Pressure\0" - "Abs Tilt X\0" - "Abs Tilt Y\0" - - // Touch capabilities reported by Wacom Intuos tablets - "TOUCH\0" - - // Aiptek drivers (e.g. Hyperpen 12000U) reports non-standard type string - "Stylus\0" - -}; - -KisX11Data *kis_x11Data = 0; - -#define KIS_ATOM(x) kis_x11Data->atoms[KisX11Data::x] -#define KIS_X11 kis_x11Data - -static void kis_x11_create_intern_atoms() +void KisTabletSupportXcb::init() { - const char *names[KisX11Data::NAtoms]; - const char *ptr = kis_x11_atomnames; + // TODO: free this structure on exit + dbgInput << "Creating xcb event handler."; + KIS_XCB = new KisXcbConnection; +} - int i = 0; - while (*ptr) { - names[i++] = ptr; - while (*ptr) - ++ptr; - ++ptr; - } - - Q_ASSERT(i == KisX11Data::NPredefinedAtoms); - Q_ASSERT(i == KisX11Data::NAtoms); - -#if defined(XlibSpecificationRelease) && (XlibSpecificationRelease >= 6) - XInternAtoms(KIS_X11->display, (char **)names, i, False, KIS_X11->atoms); -#else - for (i = 0; i < KisX11Data::NAtoms; ++i) - KIS_X11->atoms[i] = XInternAtom(KIS_X11->display, (char *)names[i], False); -#endif +KisTabletSupportXcb::~KisTabletSupportXcb() +{ + delete KIS_XCB; + KIS_XCB = 0; } void QTabletDeviceData::SavedAxesData::tryFetchAxesMapping(XDevice *dev) @@ -189,9 +90,9 @@ if (result == Success && propertyType > 0) { - if (KisTabletDebugger::instance()->initializationDebugEnabled()) { - dbgKrita << "# Building tablet axes remapping table:"; - } + //if (KisTabletDebugger::instance()->initializationDebugEnabled()) { + dbgInput << "# Building tablet axes remapping table:"; + //} QVector axesMap(nitems, Unused); @@ -215,251 +116,19 @@ axesMap[axisIndex] = mappedIndex; - if (KisTabletDebugger::instance()->initializationDebugEnabled()) { - dbgKrita << XGetAtomName(KIS_X11->display, currentAxisName) + //if (KisTabletDebugger::instance()->initializationDebugEnabled()) { + dbgInput << XGetAtomName(KIS_X11->display, currentAxisName) << axisIndex << "->" << mappedIndex; - } + // } } this->setAxesMap(axesMap); } } -void kis_x11_init_tablet() -{ - KisConfig cfg; - bool disableTouchOnCanvas = cfg.disableTouchOnCanvas(); - - // TODO: free this structure on exit - KIS_X11 = new KisX11Data; - KIS_X11->display = QX11Info::display(); - - kis_x11_create_intern_atoms(); - - // XInputExtension - KIS_X11->use_xinput = false; - KIS_X11->xinput_major = 0; - KIS_X11->xinput_eventbase = 0; - KIS_X11->xinput_errorbase = 0; - - // See if Xinput is supported on the connected display - KIS_X11->ptrXCloseDevice = 0; - KIS_X11->ptrXListInputDevices = 0; - KIS_X11->ptrXOpenDevice = 0; - KIS_X11->ptrXFreeDeviceList = 0; - KIS_X11->ptrXSelectExtensionEvent = 0; - KIS_X11->use_xinput = XQueryExtension(KIS_X11->display, "XInputExtension", &KIS_X11->xinput_major, - &KIS_X11->xinput_eventbase, &KIS_X11->xinput_errorbase); - if (KIS_X11->use_xinput) { - KIS_X11->ptrXCloseDevice = XINPUT_LOAD(XCloseDevice); - KIS_X11->ptrXListInputDevices = XINPUT_LOAD(XListInputDevices); - KIS_X11->ptrXOpenDevice = XINPUT_LOAD(XOpenDevice); - KIS_X11->ptrXFreeDeviceList = XINPUT_LOAD(XFreeDeviceList); - KIS_X11->ptrXSelectExtensionEvent = XINPUT_LOAD(XSelectExtensionEvent); - } - - if (KIS_X11->use_xinput) { - int ndev, - i, - j; - bool gotStylus, - gotEraser; - XDeviceInfo *devices = 0, *devs; - XInputClassInfo *ip; - XAnyClassPtr any; - XValuatorInfoPtr v; - XAxisInfoPtr a; - XDevice *dev = 0; - - bool needCheckIfItIsReallyATablet; - bool touchWacomTabletWorkaround; - - if (KIS_X11->ptrXListInputDevices) { - devices = KIS_X11->ptrXListInputDevices(KIS_X11->display, &ndev); - if (!devices) - qWarning("QApplication: Failed to get list of tablet devices"); - } - if (!devices) - ndev = -1; - - QTabletEvent::TabletDevice deviceType; - for (devs = devices, i = 0; i < ndev && devs; i++, devs++) { - dev = 0; - deviceType = QTabletEvent::NoDevice; - gotStylus = false; - gotEraser = false; - needCheckIfItIsReallyATablet = false; - touchWacomTabletWorkaround = false; - -#if defined(Q_OS_IRIX) -#else - - - if (devs->type == KIS_ATOM(XWacomStylus) || devs->type == KIS_ATOM(XTabletStylus) ||devs->type == KIS_ATOM(XInputTablet)) { - if (devs->type == KIS_ATOM(XInputTablet)) { - kis_haveEvdevTablets = true; - } - deviceType = QTabletEvent::Stylus; - gotStylus = true; - } else if (devs->type == KIS_ATOM(XWacomEraser) || devs->type == KIS_ATOM(XTabletEraser)) { - deviceType = QTabletEvent::XFreeEraser; - gotEraser = true; - } else if ((devs->type == KIS_ATOM(XInputKeyboard) || - devs->type == KIS_ATOM(AiptekStylus)) - && QString(devs->name) == "Aiptek") { - /** - * Some really "nice" tablets (more precisely, - * Genius G-Pen 510 (aiptek driver)) report that - * they are a "keyboard". Well, we cannot convince - * them that they are not, so just check if this - * "keyboard" has motion and proximity events. If - * it looks like a duck... :) - */ - kis_haveEvdevTablets = true; - deviceType = QTabletEvent::Stylus; - gotStylus = true; - needCheckIfItIsReallyATablet = true; - } else if (disableTouchOnCanvas && - devs->type == KIS_ATOM(WacomTouch) && - QString(devs->name).contains("Wacom")) { - - kis_haveEvdevTablets = true; - deviceType = QTabletEvent::Stylus; - gotStylus = true; - touchWacomTabletWorkaround = true; - } - -#endif - if (deviceType == QTabletEvent::NoDevice) - continue; - - if (gotStylus || gotEraser) { - if (KIS_X11->ptrXOpenDevice) - dev = KIS_X11->ptrXOpenDevice(KIS_X11->display, devs->id); - - if (!dev) - continue; - - QTabletDeviceData device_data; - device_data.deviceType = deviceType; - device_data.eventCount = 0; - device_data.device = dev; - device_data.xinput_motion = -1; - device_data.xinput_key_press = -1; - device_data.xinput_key_release = -1; - device_data.xinput_button_press = -1; - device_data.xinput_button_release = -1; - device_data.xinput_proximity_in = -1; - device_data.xinput_proximity_out = -1; - device_data.isTouchWacomTablet = touchWacomTabletWorkaround; - //device_data.widgetToGetPress = 0; - - if (dev->num_classes > 0) { - for (ip = dev->classes, j = 0; j < dev->num_classes; - ip++, j++) { - switch (ip->input_class) { - case KeyClass: - DeviceKeyPress(dev, device_data.xinput_key_press, - device_data.eventList[device_data.eventCount]); - if (device_data.eventList[device_data.eventCount]) - ++device_data.eventCount; - DeviceKeyRelease(dev, device_data.xinput_key_release, - device_data.eventList[device_data.eventCount]); - if (device_data.eventList[device_data.eventCount]) - ++device_data.eventCount; - break; - case ButtonClass: - DeviceButtonPress(dev, device_data.xinput_button_press, - device_data.eventList[device_data.eventCount]); - if (device_data.eventList[device_data.eventCount]) - ++device_data.eventCount; - DeviceButtonRelease(dev, device_data.xinput_button_release, - device_data.eventList[device_data.eventCount]); - if (device_data.eventList[device_data.eventCount]) - ++device_data.eventCount; - break; - case ValuatorClass: - // I'm only going to be interested in motion when the - // stylus is already down anyway! - DeviceMotionNotify(dev, device_data.xinput_motion, - device_data.eventList[device_data.eventCount]); - if (device_data.eventList[device_data.eventCount]) - ++device_data.eventCount; - ProximityIn(dev, device_data.xinput_proximity_in, device_data.eventList[device_data.eventCount]); - if (device_data.eventList[device_data.eventCount]) - ++device_data.eventCount; - ProximityOut(dev, device_data.xinput_proximity_out, device_data.eventList[device_data.eventCount]); - if (device_data.eventList[device_data.eventCount]) - ++device_data.eventCount; - default: - break; - } - } - } - - if (needCheckIfItIsReallyATablet && - (device_data.xinput_motion == -1 || - device_data.xinput_proximity_in == -1 || - device_data.xinput_proximity_out == -1)) { - continue; - } - - if (KisTabletDebugger::instance()->initializationDebugEnabled()) { - dbgKrita << "###################################"; - dbgKrita << "# Adding a tablet device:" << devs->name; - dbgKrita << "Device Type:" << KisTabletDebugger::tabletDeviceToString(deviceType); - } - - device_data.savedAxesData.tryFetchAxesMapping(dev); - - // get the min/max value for pressure! - any = (XAnyClassPtr) (devs->inputclassinfo); - for (j = 0; j < devs->num_classes; j++) { - if (any->c_class == ValuatorClass) { - v = (XValuatorInfoPtr) any; - a = (XAxisInfoPtr) ((char *) v + - sizeof (XValuatorInfo)); -#if defined (Q_OS_IRIX) -#else - device_data.minX = a[0].min_value; - device_data.maxX = a[0].max_value; - device_data.minY = a[1].min_value; - device_data.maxY = a[1].max_value; - device_data.minPressure = a[2].min_value; - device_data.maxPressure = a[2].max_value; - device_data.minTanPressure = 0; - device_data.maxTanPressure = 0; - device_data.minZ = 0; - device_data.maxZ = 0; - device_data.minRotation = a[5].min_value; - device_data.maxRotation = a[5].max_value; - - if (KisTabletDebugger::instance()->initializationDebugEnabled()) { - dbgKrita << "# Axes limits data"; - dbgKrita << "X: " << device_data.minX << device_data.maxX; - dbgKrita << "Y: " << device_data.minY << device_data.maxY; - dbgKrita << "Z: " << device_data.minZ << device_data.maxZ; - dbgKrita << "Pressure:" << device_data.minPressure << device_data.maxPressure; - dbgKrita << "Rotation:" << device_data.minRotation << device_data.maxRotation; - dbgKrita << "T. Pres: " << device_data.minTanPressure << device_data.maxTanPressure; - } - -#endif - - // got the max pressure no need to go further... - break; - } - any = (XAnyClassPtr) ((char *) any + any->length); - } // end of for loop - - qt_tablet_devices()->append(device_data); - } // if (gotStylus || gotEraser) - } - if (KIS_X11->ptrXFreeDeviceList) - KIS_X11->ptrXFreeDeviceList(devices); - } -} +// Not working - commented out +/* void fetchWacomToolId(qint64 &serialId, qint64 &deviceId, QTabletDeviceData *tablet) { XDevice *dev = static_cast(tablet->device); @@ -487,26 +156,7 @@ serialId = l[3]; // serial id of the stylus in proximity deviceId = l[4]; // device if of the stylus in proximity } - -static Qt::MouseButtons translateMouseButtons(int s) -{ - Qt::MouseButtons ret = 0; - if (s & Button1Mask) - ret |= Qt::LeftButton; - if (s & Button2Mask) - ret |= Qt::MidButton; - if (s & Button3Mask) - ret |= Qt::RightButton; - return ret; -} - -static Qt::MouseButton translateMouseButton(int b) -{ - return b == Button1 ? Qt::LeftButton : - b == Button2 ? Qt::MidButton : - b == Button3 ? Qt::RightButton : - Qt::LeftButton /* fallback */; -} +*/ QTabletEvent::TabletDevice parseWacomDeviceId(quint32 deviceId) { @@ -557,13 +207,9 @@ return device; } -bool translateXinputEvent(const XEvent *ev, QTabletDeviceData *tablet, QWidget *defaultWidget) +bool old_translateXinputEvent(const XEvent *ev, QTabletDeviceData *tablet, QWidget *defaultWidget) { Q_ASSERT(defaultWidget); - -#if defined (Q_OS_IRIX) -#endif - Q_ASSERT(tablet != 0); QWidget *w = defaultWidget; @@ -585,18 +231,14 @@ modifiers = QApplication::queryKeyboardModifiers(); -#if !defined (Q_OS_IRIX) XID device_id = 0; -#endif if (ev->type == tablet->xinput_motion) { motion = reinterpret_cast(ev); t = KisTabletEvent::TabletMoveEx; global = QPoint(motion->x_root, motion->y_root); curr = QPoint(motion->x, motion->y); -#if !defined (Q_OS_IRIX) device_id = motion->deviceid; -#endif } else if (ev->type == tablet->xinput_button_press || ev->type == tablet->xinput_button_release) { if (ev->type == tablet->xinput_button_press) { t = KisTabletEvent::TabletPressEx; @@ -606,18 +248,14 @@ button = (XDeviceButtonEvent*)ev; global = QPoint(button->x_root, button->y_root); curr = QPoint(button->x, button->y); -#if !defined (Q_OS_IRIX) device_id = button->deviceid; -#endif } else { qFatal("Unknown event type! Probably, 'proximity', " "but we don't handle it here, so this is a bug"); } qint64 wacomSerialId = 0; qint64 wacomDeviceId = 0; -#if defined (Q_OS_IRIX) -#else // We've been passed in data for a tablet device that handles this type // of event, but it isn't necessarily the tablet device that originated // the event. Use the device id to find the originating device if we @@ -645,7 +283,7 @@ */ if (tablet->isTouchWacomTablet) return false; - fetchWacomToolId(wacomSerialId, wacomDeviceId, tablet); + // fetchWacomToolId(wacomSerialId, wacomDeviceId, tablet); if (wacomDeviceId && deviceType == QTabletEvent::Stylus) { deviceType = parseWacomDeviceId(wacomDeviceId); @@ -690,8 +328,6 @@ (tablet->maxRotation - tablet->minRotation) + 0.5, 1.0) * 360.0; } -#endif - if (tablet->widgetToGetPress) { w = tablet->widgetToGetPress; } else { @@ -713,10 +349,10 @@ Qt::MouseButtons qtbuttons; if (motion) { - qtbuttons = translateMouseButtons(motion->state); + // qtbuttons = translateMouseButtons(motion->state); } else if (button) { - qtbuttons = translateMouseButtons(button->state); - qtbutton = translateMouseButton(button->button); + // qtbuttons = translateMouseButtons(button->state); + // qtbutton = translateMouseButton(button->button); } KisTabletEvent e(t, curr, global, hiRes, @@ -727,14 +363,11 @@ e.ignore(); QApplication::sendEvent(w, &e); + kis_tabletChokeMouse = true; return e.isAccepted(); } -void KisTabletSupportX11::init() -{ - kis_x11_init_tablet(); -} void evdevEventsActivationWorkaround(WId window) { @@ -759,26 +392,42 @@ } } -bool KisTabletSupportX11::nativeEventFilter(const QByteArray &/*eventType*/, void *ev, long * /*unused_on_X11*/) +bool KisTabletSupportXcb::nativeEventFilter(const QByteArray &/*eventType*/, void *message, long * /*unused_on_X11*/) { - XEvent *event = static_cast(ev); + xcb_generic_event_t *event = static_cast(message); + uint response_type = event->response_type & ~0x80; // hmm - // Eat the choked mouse event... + // This only works when XI2.2 is not in use. + // Otherwise all XI events are sent as XCB_GE_GENERIC. if (kis_tabletChokeMouse && - (event->type == ButtonRelease || - event->type == ButtonPress || - event->type == MotionNotify)) { + (response_type == XCB_BUTTON_RELEASE || + response_type == XCB_BUTTON_PRESS || + response_type == XCB_MOTION_NOTIFY)) { kis_tabletChokeMouse = false; // Mhom-mhom... return true; } - if (kis_haveEvdevTablets && event->type == EnterNotify) { - evdevEventsActivationWorkaround((WId)event->xany.window); + // if (kis_haveEvdevTablets && response_type == EnterNotify) { + // evdevEventsActivationWorkaround((WId)event->xany.window); + // } + + if (response_type == XCB_GE_GENERIC) { + bool handled = KIS_XCB->xi2HandleEvent(reinterpret_cast(message)); + kis_tabletChokeMouse = handled; + return handled; } + return false; +} + + +bool old_handleNativeEvent(XEvent* event) +{ + + // Old event handler code QTabletDeviceDataList *tablets = qt_tablet_devices(); for (int i = 0; i < tablets->size(); ++i) { QTabletDeviceData &tab = tablets->operator [](i); @@ -796,7 +445,7 @@ widget = QWidget::find((WId)event->xany.window); } - bool retval = widget ? translateXinputEvent(event, &tab, widget) : false; + bool retval = widget ? old_translateXinputEvent(event, &tab, widget) : false; if (retval) { /** diff --git a/krita/ui/input/wintab/kis_xcb_connection.h b/krita/ui/input/wintab/kis_xcb_connection.h new file mode 100644 --- /dev/null +++ b/krita/ui/input/wintab/kis_xcb_connection.h @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2013 Dmitry Kazakov + * Copyright (c) 2015 The Qt Company Ltd. http://www.qt.io/licensing/ + * Copyright (c) 2015 Michael Abrahams + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +// Note: XInput 2.2 is required +#include +#include +#include + + +namespace KisXcbAtom { + /* Warning: if you modify this list, modify the names of atoms in kis_x11_atomnames as well! */ + enum Atom { + XWacomStylus, + XWacomCursor, + XWacomEraser, + + XTabletStylus, + XTabletEraser, + + XInputTablet, + + XInputKeyboard, + + AxisLabels, + ATOM, + + AbsX, + AbsY, + AbsPressure, + AbsTiltX, + AbsTiltY, + AbsWheel, + AbsDistance, + + + WacomSerialIDs, + INTEGER, + + WacomTouch, + + AiptekStylus, + + NPredefinedAtoms, + NAtoms = NPredefinedAtoms + }; +} + + +/* Warning: if you modify this string, modify the list of atoms in KisX11Data as well! */ +static const char kis_xcb_atomnames[] = { + // Wacom old. (before version 0.10) + "Wacom Stylus\0" + "Wacom Cursor\0" + "Wacom Eraser\0" + + // Tablet + "STYLUS\0" + "ERASER\0" + + // XInput tablet device + "TABLET\0" + + // Really "nice" Aiptek devices reporting they are a keyboard + "KEYBOARD\0" + + // Evdev property that report the assignment of axes + "Axis Labels\0" + "ATOM\0" + + // Types of axes reported by evdev + "Abs X\0" + "Abs Y\0" + "Abs Pressure\0" + "Abs Tilt X\0" + "Abs Tilt Y\0" + "Abs Wheel\0" + "Abs Distance\0" + + // Wacom driver oddity + "Wacom Serial IDs\0" + "INTEGER\0" + + // Touch capabilities reported by Wacom Intuos tablets + "TOUCH\0" + + // Aiptek drivers (e.g. Hyperpen 12000U) reports non-standard type string + "Stylus\0" + +}; + +class QWidget; + +// from QXcbConnection +struct KisXcbConnection +{ + + KisXcbConnection(); + ~KisXcbConnection(); + + struct TabletData { + TabletData() : deviceId(0), pointerType(QTabletEvent::UnknownPointer), + tool(QTabletEvent::Stylus), buttons(0), serialId(0), inProximity(false) { } + int deviceId; + QTabletEvent::PointerType pointerType; + QTabletEvent::TabletDevice tool; + Qt::MouseButtons buttons; + qint64 serialId; + bool inProximity; + struct ValuatorClassInfo { + ValuatorClassInfo() : minVal(0.), maxVal(0.), curVal(0.) { } + double minVal; + double maxVal; + double curVal; + int number; + }; + QHash valuatorInfo; + QWidget* widgetToGetPress{0}; + }; + + void init_tablet(); + void xi2SetupDevices(); + bool xi2HandleEvent(xcb_ge_event_t *event); + void initializeAllAtoms(); + bool xi2HandleTabletEvent(void *event, TabletData &tabletData); + bool xi2ReportTabletEvent(TabletData &tabletData, void *event, QEvent::Type type); + // void handleXIMouseEvent(xcb_ge_event_t *event); /* not implemented */ + bool handleWacomProximityEvent(TabletData &tabletData, void * event); + QByteArray atomName(xcb_atom_t atom); + + KisXcbAtom::Atom kis_atom(xcb_atom_t xatom) const; + inline xcb_atom_t atom(KisXcbAtom::Atom atom) const { return m_allAtoms[atom]; } + + Display *display; + xcb_connection_t *connection; + xcb_atom_t m_allAtoms[KisXcbAtom::NAtoms]; + + int m_xiOpCode; + + // XXX: these seem unused + bool use_xinput; + int m_xi2Minor; + int m_xi2Major; + int m_xiEventBase; + int m_xiErrorBase; + + + + QVector m_tabletData; + + + // QList m_screens; + // QRect nativeGeometry() const { + // // TODO: Perform the inverse of this: + // const int dpr = int(devicePixelRatio()); // we may override m_devicePixelRatio + // m_geometry = QRect(xGeometry.topLeft(), xGeometry.size()/dpr); + // m_nativeGeometry = QRect(xGeometry.topLeft(), xGeometry.size()); + // m_availableGeometry = QRect(mapFromNative(xAvailableGeometry.topLeft()), xAvailableGeometry.size()/dpr); + // } + +}; diff --git a/krita/ui/input/wintab/kis_xcb_connection.cpp b/krita/ui/input/wintab/kis_xcb_connection.cpp new file mode 100644 --- /dev/null +++ b/krita/ui/input/wintab/kis_xcb_connection.cpp @@ -0,0 +1,802 @@ +/* + * Copyright (c) 2013 Dmitry Kazakov + * Copyright (c) 2015 The Qt Company Ltd. http://www.qt.io/licensing/ + * Copyright (c) 2015 Michael Abrahams + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kis_xcb_connection.h" +#include "kis_config.h" +#include "kis_debug.h" +#include + +#include +#include +#include + +// Note: XInput 2.2 is required +#include +#include +#include +#include +#include + +// Defined in kis_tablet_support_x11.cpp +extern bool kis_tabletChokeMouse; + +KisXcbConnection::KisXcbConnection() +{ + // We will mix Xlib and XCB calls in this code. + display = QX11Info::display(); + connection = QX11Info::connection(); + initializeAllAtoms(); + init_tablet(); +} + + + +KisXcbConnection::~KisXcbConnection() +{ + // From finalizeXInput2(). Uncomment when touch support is added. + // foreach (XInput2TouchDeviceData *dev, m_touchDevices) { + // if (dev->xiDeviceInfo) + // XIFreeDeviceInfo(dev->xiDeviceInfo); + // delete dev; + // } +} + + +KisXcbAtom::Atom KisXcbConnection::kis_atom(xcb_atom_t xatom) const +{ + return static_cast(std::find(m_allAtoms, m_allAtoms + KisXcbAtom::NAtoms, xatom) - m_allAtoms); +} + + +void KisXcbConnection::initializeAllAtoms() +{ + const char *names[KisXcbAtom::NAtoms]; + const char *ptr = kis_xcb_atomnames; + + int i = 0; + while (*ptr) { + names[i++] = ptr; + while (*ptr) + ++ptr; + ++ptr; + } + + Q_ASSERT(i == KisXcbAtom::NPredefinedAtoms); + Q_ASSERT(i == KisXcbAtom::NAtoms); + + xcb_intern_atom_cookie_t cookies[KisXcbAtom::NAtoms]; + + for (i = 0; i < KisXcbAtom::NAtoms; ++i) + cookies[i] = xcb_intern_atom(connection, false, strlen(names[i]), names[i]); + + for (i = 0; i < KisXcbAtom::NAtoms; ++i) { + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, cookies[i], 0); + m_allAtoms[i] = reply->atom; + dbgInput << "Atom" << names[i] << "has value" << reply->atom; + free(reply); + } +} + + + +void KisXcbConnection::init_tablet() +{ + // KisConfig cfg; + // bool disableTouchOnCanvas = cfg.disableTouchOnCanvas(); + + + if (XQueryExtension(display, "XInputExtension", + &m_xiOpCode, &m_xiEventBase, &m_xiErrorBase)) { + int xiMajor = 2; + m_xi2Minor = 2; // try 2.2 first, needed for TouchBegin/Update/End + if (XIQueryVersion(display, &xiMajor, &m_xi2Minor) == BadRequest) { + errInput << "FATAL: XInput 2.2 not supported."; + } + xi2SetupDevices(); + } +} + + +void KisXcbConnection::xi2SetupDevices() +{ + int deviceCount = 0; + XIDeviceInfo *devices = XIQueryDevice(display, XIAllDevices, &deviceCount); + if (!devices) + dbgInput << "QApplication: Failed to get list of devices"; + + bool gotStylus, gotEraser; + + bool needCheckIfItIsReallyATablet; + bool touchWacomTabletWorkaround; + QTabletEvent::TabletDevice deviceType; + m_tabletData.clear(); + + + for (int i = 0; i < deviceCount; ++i) { + // Only non-master pointing devices are relevant here. + if (devices[i].use != XISlavePointer) + continue; + dbgInput << "input device "<< devices[i].name; + TabletData tabletData; + // ScrollingDevice scrollingDevice; + + deviceType = QTabletEvent::NoDevice; + gotStylus = false; + gotEraser = false; + needCheckIfItIsReallyATablet = false; + touchWacomTabletWorkaround = false; // XXX: Look into this + + // Inspect valuators for this device + for (int c = 0; c < devices[i].num_classes; ++c) { + switch (devices[i].classes[c]->type) { + case XIValuatorClass: { + XIValuatorClassInfo *vci = reinterpret_cast(devices[i].classes[c]); + const int valuatorAtom = kis_atom(vci->label); + dbgInput << " has valuator" << atomName(vci->label) << \ + "Atom number" << vci->label << "recognized?" << (valuatorAtom < KisXcbAtom::NAtoms); + if (valuatorAtom < KisXcbAtom::NAtoms) { + TabletData::ValuatorClassInfo info; + info.minVal = vci->min; + info.maxVal = vci->max; + info.number = vci->number; + tabletData.valuatorInfo[valuatorAtom] = info; + } + break; + } + case XIButtonClass: { + XIButtonClassInfo *bci = reinterpret_cast(devices[i].classes[c]); + + /* From prior code + DeviceButtonPress(dev, device_data.xinput_button_press, + device_data.eventList[device_data.eventCount]); + if (device_data.eventList[device_data.eventCount]) + ++device_data.eventCount; + DeviceButtonRelease(dev, device_data.xinput_button_release, + device_data.eventList[device_data.eventCount]); + if (device_data.eventList[device_data.eventCount]) + ++device_data.eventCount; + break; + */ + + + // This doesn't seem useful + /* if (bci->num_buttons >= 5) { + Atom label4 = bci->labels[3]; + Atom label5 = bci->labels[4]; + // Some drivers have no labels on the wheel buttons, some + // have no label on just one and some have no label on + // button 4 and the wrong one on button 5. So we just check + // that they are not labelled with unrelated buttons. + if ((!label4 || + kis_atom(label4) == KisXcbAtom::ButtonWheelUp || + kis_atom(label4) == KisXcbAtom::ButtonWheelDown) && + (!label5 || + kis_atom(label5) == KisXcbAtom::ButtonWheelUp || + kis_atom(label5) == KisXcbAtom::ButtonWheelDown)) + scrollingDevice.legacyOrientations |= Qt::Vertical; + } + if (bci->num_buttons >= 7) { + Atom label6 = bci->labels[5]; + Atom label7 = bci->labels[6]; + if ((!label6 || kis_atom(label6) == KisXcbAtom::ButtonHorizWheelLeft) && + (!label7 || kis_atom(label7) == KisXcbAtom::ButtonHorizWheelRight)) + scrollingDevice.legacyOrientations |= Qt::Horizontal; + } + */ + dbgInput << " has" << bci->num_buttons << "buttons"; + break; + } + case XIKeyClass: + dbgInput << " posesses keys?"; + break; + case XITouchClass: + // will be handled in touchDeviceForId() + // TODO: Look into this. + break; + default: + dbgInput << " has class" << devices[i].classes[c]->type; + break; + } + } + + // If we have found the valuators which we expect a tablet to have, it + // might be a tablet. No promises! + bool isTablet = false; + if (tabletData.valuatorInfo.contains(KisXcbAtom::AbsX) && + tabletData.valuatorInfo.contains(KisXcbAtom::AbsY) && + tabletData.valuatorInfo.contains(KisXcbAtom::AbsPressure)) + isTablet = true; + + // But we need to be careful not to take the touch and tablet-button devices as tablets. + QByteArray name = QByteArray(devices[i].name).toLower(); + QString dbgType = QLatin1String("UNKNOWN"); + if (name.contains("eraser")) { + isTablet = true; + gotEraser = true; + tabletData.pointerType = QTabletEvent::Eraser; + dbgType = QLatin1String("eraser"); + } else if (name.contains("cursor")) { + isTablet = true; + tabletData.pointerType = QTabletEvent::Cursor; + dbgType = QLatin1String("cursor"); + } else if ((name.contains("pen") || name.contains("stylus")) && isTablet) { + tabletData.pointerType = QTabletEvent::Pen; + gotStylus = true; + dbgType = QLatin1String("pen"); + } else if (name.contains("wacom") && isTablet && !name.contains("touch")) { + // combined device (evdev) rather than separate pen/eraser (wacom driver) + tabletData.pointerType = QTabletEvent::Pen; + gotStylus = true; + dbgType = QLatin1String("pen"); + touchWacomTabletWorkaround = true; + } else if (name.contains("aiptek") /* && device == KisXcbAtom::KEYBOARD */) { + // some "Genius" tablets + isTablet = true; + tabletData.pointerType = QTabletEvent::Pen; + dbgType = QLatin1String("pen"); + needCheckIfItIsReallyATablet = true; + gotStylus = true; + } else { + isTablet = false; + } + + if (isTablet) { + tabletData.deviceId = devices[i].deviceid; + m_tabletData.append(tabletData); + } else { + // Who cares! + continue; + } + + dbgInput << "###################################"; + dbgInput << "# Adding a tablet device:" << devices[i].name; + dbgInput << "Device Type:" << KisTabletDebugger::tabletDeviceToString(deviceType); + + /* + dbgInput << "# Axes limits data"; + dbgInput << "X: " << device_data.minX << device_data.maxX; + dbgInput << "Y: " << device_data.minY << device_data.maxY; + dbgInput << "Z: " << device_data.minZ << device_data.maxZ; + dbgInput << "Pressure:" << device_data.minPressure << device_data.maxPressure; + dbgInput << "Rotation:" << device_data.minRotation << device_data.maxRotation; + dbgInput << "T. Pres: " << device_data.minTanPressure << device_data.maxTanPressure; + */ + + // TODO: what was this? + // device_data.savedAxesData.tryFetchAxesMapping(dev); + + } // Loop over devices + XIFreeDeviceInfo(devices); +} + +namespace { + Qt::MouseButton xiToQtMouseButton(uint32_t b) + { + switch (b) { + case 1: return Qt::LeftButton; + case 2: return Qt::MiddleButton; + case 3: return Qt::RightButton; + // 4-7 are for scrolling + default: break; + } + if (b >= 8 && b <= Qt::MaxMouseButton) + return static_cast(Qt::BackButton << (b - 8)); + return Qt::NoButton; + } + + + Qt::MouseButtons translateMouseButtons(int s) + { + Qt::MouseButtons ret = 0; + if (s & XCB_BUTTON_MASK_1) + ret |= Qt::LeftButton; + if (s & XCB_BUTTON_MASK_2) + ret |= Qt::MidButton; + if (s & XCB_BUTTON_MASK_3) + ret |= Qt::RightButton; + return ret; + } + + QTabletEvent::TabletDevice toolIdToTabletDevice(quint32 toolId) { + // keep in sync with wacom_intuos_inout() in Linux kernel driver wacom_wac.c + switch (toolId) { + case 0xd12: + case 0x912: + case 0x112: + case 0x913: /* Intuos3 Airbrush */ + case 0x91b: /* Intuos3 Airbrush Eraser */ + case 0x902: /* Intuos4/5 13HD/24HD Airbrush */ + case 0x90a: /* Intuos4/5 13HD/24HD Airbrush Eraser */ + case 0x100902: /* Intuos4/5 13HD/24HD Airbrush */ + case 0x10090a: /* Intuos4/5 13HD/24HD Airbrush Eraser */ + return QTabletEvent::Airbrush; + case 0x007: /* Mouse 4D and 2D */ + case 0x09c: + case 0x094: + return QTabletEvent::FourDMouse; + case 0x017: /* Intuos3 2D Mouse */ + case 0x806: /* Intuos4 Mouse */ + case 0x096: /* Lens cursor */ + case 0x097: /* Intuos3 Lens cursor */ + case 0x006: /* Intuos4 Lens cursor */ + return QTabletEvent::Puck; + case 0x885: /* Intuos3 Art Pen (Marker Pen) */ + case 0x100804: /* Intuos4/5 13HD/24HD Art Pen */ + case 0x10080c: /* Intuos4/5 13HD/24HD Art Pen Eraser */ + return QTabletEvent::RotationStylus; + case 0: + return QTabletEvent::NoDevice; + } + return QTabletEvent::Stylus; // Safe default assumption if nonzero + } + + + int xi2ValuatorOffset(unsigned char *maskPtr, int maskLen, int number) + { + int offset = 0; + for (int i = 0; i < maskLen; i++) { + if (number < 8) { + if ((maskPtr[i] & (1 << number)) == 0) + return -1; + } + for (int j = 0; j < 8; j++) { + if (j == number) + return offset; + if (maskPtr[i] & (1 << j)) + offset++; + } + number -= 8; + } + return -1; + } + + + bool xi2GetValuatorValueIfSet(void *event, int valuatorNum, double *value) + { + xXIDeviceEvent *xideviceevent = static_cast(event); + unsigned char *buttonsMaskAddr = (unsigned char*)&xideviceevent[1]; + unsigned char *valuatorsMaskAddr = buttonsMaskAddr + xideviceevent->buttons_len * 4; + FP3232 *valuatorsValuesAddr = (FP3232*)(valuatorsMaskAddr + xideviceevent->valuators_len * 4); + + int valuatorOffset = xi2ValuatorOffset(valuatorsMaskAddr, xideviceevent->valuators_len, valuatorNum); + if (valuatorOffset < 0) + return false; + + *value = valuatorsValuesAddr[valuatorOffset].integral; + *value += ((double)valuatorsValuesAddr[valuatorOffset].frac / (1 << 16) / (1 << 16)); + return true; + } + + void sendProximityEvent(KisXcbConnection::TabletData &tabletData, QEvent::Type type) + { + QPointF emptyPos; + qreal zero = 0.0; + QTabletEvent e(type, emptyPos, emptyPos, tabletData.tool, tabletData.pointerType, + zero, 0, 0, zero, zero, 0, (Qt::KeyboardModifiers)0, + tabletData.serialId, (Qt::MouseButton)0, tabletData.buttons); + qApp->sendEvent(qApp->activeWindow(), &e); + } + +/** + * + */ + // Starting from the xcb version 1.9.3 struct xcb_ge_event_t has changed: + // - "pad0" became "extension" + // - "pad1" and "pad" became "pad0" + // New and old version of this struct share the following fields: + // NOTE: API might change again in the next release of xcb in which case this comment will + // need to be updated to reflect the reality. + typedef struct qt_xcb_ge_event_t { + uint8_t response_type; + uint8_t extension; + uint16_t sequence; + uint32_t length; + uint16_t event_type; + } qt_xcb_ge_event_t; + + bool xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *ev, int opCode) + { + qt_xcb_ge_event_t *event = (qt_xcb_ge_event_t *)ev; + // xGenericEvent has "extension" on the second byte, the same is true for xcb_ge_event_t starting from + // the xcb version 1.9.3, prior to that it was called "pad0". + if (event->extension == opCode) { + // xcb event structs contain stuff that wasn't on the wire, the full_sequence field + // adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes. + // Move this data back to have the same layout in memory as it was on the wire + // and allow casting, overwriting the full_sequence field. + memmove((char*) event + 32, (char*) event + 36, event->length * 4); + return true; + } + return false; + } +/** + * + */ + +} + +bool KisXcbConnection::xi2HandleTabletEvent(void *event, TabletData &tabletData) +{ + bool handled = true; + xXIGenericDeviceEvent *xiEvent = static_cast(event); + xXIDeviceEvent *xiDeviceEvent = reinterpret_cast(xiEvent); + + switch (xiEvent->evtype) { + case XI_ButtonPress: { + Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail); + tabletData.buttons |= b; + return xi2ReportTabletEvent(tabletData, xiEvent, QEvent::TabletPress); + break; + } + case XI_ButtonRelease: { + Qt::MouseButton b = xiToQtMouseButton(xiDeviceEvent->detail); + tabletData.buttons ^= b; + return xi2ReportTabletEvent(tabletData, xiEvent, QEvent::TabletRelease); + break; + } + case XI_Motion: + // Report TabletMove only when the stylus is touching the tablet or any button is pressed. + // TODO: report proximity (hover) motion (no suitable Qt event exists yet). + // if (tabletData.buttons != Qt::NoButton) + return xi2ReportTabletEvent(tabletData, xiEvent, QEvent::TabletMove); + break; + case XI_PropertyEvent: { + // This is the wacom driver's way of reporting tool proximity. + // The evdev driver doesn't do it this way. + // Note: this was inline in Qt source but split up here, because it is gross. + xXIPropertyEvent *ev = reinterpret_cast(event); + if (ev->what == XIPropertyModified) { + if (ev->property == kis_atom(KisXcbAtom::WacomSerialIDs)) { + handled = handleWacomProximityEvent(tabletData, ev); + } + } + break; + } + default: + handled = false; + break; + } + + // Synthesize mouse events since otherwise there are no mouse events from + // the pen on the XI 2.2+ path. + // TODO: not implemented. + // if (xi2MouseEvents() && eventListener) + // eventListener->handleXIMouseEvent(reinterpret_cast(event)); + + + return handled; +} + + +bool KisXcbConnection::handleWacomProximityEvent(TabletData &tabletData, void *event) +{ + xXIPropertyEvent *ev = reinterpret_cast(event); + enum WacomSerialIndex { + _WACSER_USB_ID = 0, + _WACSER_LAST_TOOL_SERIAL, + _WACSER_LAST_TOOL_ID, + _WACSER_TOOL_SERIAL, + _WACSER_TOOL_ID, + _WACSER_COUNT + }; + Atom propType; + int propFormat; + unsigned long numItems, bytesAfter; + unsigned char *data; + bool handled = false; + if (XIGetProperty(display, tabletData.deviceId, ev->property, 0, 100, + 0, AnyPropertyType, &propType, &propFormat, + &numItems, &bytesAfter, &data) == Success) { + if (propType == atom(KisXcbAtom::INTEGER) && propFormat == 32 && numItems == _WACSER_COUNT) { + handled = true; + + quint32 *ptr = reinterpret_cast(data); + quint32 tool = ptr[_WACSER_TOOL_ID]; + // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/ + // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1 + if (!tool && ptr[_WACSER_TOOL_SERIAL]) + tool = ptr[_WACSER_TOOL_SERIAL]; + + // The property change event informs us which tool is in proximity or which one left proximity. + if (tool) { + tabletData.inProximity = true; + tabletData.tool = toolIdToTabletDevice(tool); + tabletData.serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_TOOL_SERIAL]); + sendProximityEvent(tabletData, QEvent::TabletEnterProximity); + } else { + tabletData.inProximity = false; + tabletData.tool = toolIdToTabletDevice(ptr[_WACSER_LAST_TOOL_ID]); + // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/ + // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1 + if (!tabletData.tool) + tabletData.tool = toolIdToTabletDevice(ptr[_WACSER_LAST_TOOL_SERIAL]); + tabletData.serialId = qint64(ptr[_WACSER_USB_ID]) << 32 | qint64(ptr[_WACSER_LAST_TOOL_SERIAL]); + sendProximityEvent(tabletData, QEvent::TabletLeaveProximity); + } + + // Qt had more informative debug output + dbgInput << QString("XI2 proximity change on tablet %d (USB %x):"\ + "last tool: %x id %x current tool: %x id %x TabletDevice %d")\ + .arg(tabletData.deviceId).arg(ptr[_WACSER_USB_ID]).arg(ptr[_WACSER_LAST_TOOL_SERIAL]) \ + .arg(ptr[_WACSER_LAST_TOOL_ID]).arg(ptr[_WACSER_TOOL_SERIAL]).arg(ptr[_WACSER_TOOL_ID])\ + .arg(tabletData.tool); + } + XFree(data); + } + return handled; +} + +bool KisXcbConnection::xi2ReportTabletEvent(TabletData &tabletData, void *event, QEvent::Type type) +{ + xXIDeviceEvent *ev = reinterpret_cast(event); + // QXcbWindow *xcbWindow = platformWindowFromId(ev->event); + // if (!xcbWindow) + // return; + // QWindow *window = xcbWindow->window(); + // QWindow *window = QWindow::fromWinId(reply->atom) + double pressure = 0, rotation = 0, tangentialPressure = 0; + int xTilt = 0, yTilt = 0; + Qt::KeyboardModifiers keyState = QApplication::queryKeyboardModifiers(); + + // Note: this uses the buggy computation + // 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); + + + // Raw valuator: Not implemented yet + // QPointF global(0.0, 0.0); + // QRegion area(m_screens.at(0)->nativeGeometry()); + // for (int i = 1; i < m_screens.count(); ++i) + // screenArea += m_screens.at(i)->nativeGeometry(); + + // HMM. + QPointF global(0.0, 0.0); + QRect screenArea = qApp->desktop()->rect(); + + for (QHash::iterator it = tabletData.valuatorInfo.begin(), + ite = tabletData.valuatorInfo.end(); it != ite; ++it) { + int valuator = it.key(); + TabletData::ValuatorClassInfo &classInfo(it.value()); + xi2GetValuatorValueIfSet(event, classInfo.number, &classInfo.curVal); + double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal); + switch (valuator) { + case KisXcbAtom::AbsX: + // global.rx() = screenArea.boundingRect().width() * normalizedValue; + global.rx() = screenArea.width() * normalizedValue; + break; + case KisXcbAtom::AbsY: + // global.ry() = screenArea.boundingRect().height() * normalizedValue; + global.ry() = screenArea.height() * normalizedValue; + break; + case KisXcbAtom::AbsPressure: + pressure = normalizedValue; + break; + case KisXcbAtom::AbsTiltX: + xTilt = classInfo.curVal; + break; + case KisXcbAtom::AbsTiltY: + yTilt = classInfo.curVal; + break; + case KisXcbAtom::AbsWheel: + switch (tabletData.tool) { + case QTabletEvent::Airbrush: + tangentialPressure = normalizedValue * 2.0 - 1.0; // Convert 0..1 range to -1..+1 range + break; + case QTabletEvent::RotationStylus: + rotation = normalizedValue * 360.0 - 180.0; // Convert 0..1 range to -180..+180 degrees + break; + default: // Other types of styli do not use this valuator + break; + } + break; + default: + break; + } + } + + + // Global valuator method + QPointF local = global - QPointF((ev->root_x >> 16) - (ev->event_x >> 16), + (ev->root_y >> 16) - (ev->event_y >> 16)); + + // From older translateXinputEvent() + + QWidget *w = 0; + if (tabletData.widgetToGetPress) { + w = tabletData.widgetToGetPress; + } + if (!w) { + w = QApplication::activePopupWidget(); + } + if (!w) { + w = QApplication::activeModalWidget(); + } + if (!w) { + w = QWidget::find((WId)ev->event); //XXX: No idea what I'm doing here.. + } + if (!w) { + w = qApp->activeWindow(); + } + + if (w) { + QWidget *child = w->childAt(local.toPoint()); + if (child) + w = child; + } + + if (type == QEvent::TabletPress) { + tabletData.widgetToGetPress = w; + } else if (type == QEvent::TabletRelease && tabletData.widgetToGetPress) { + local = w->mapFromGlobal(global.toPoint()); + tabletData.widgetToGetPress = 0; + } + + // TODO: this is redundant (see xi2HandleTabletEvent) + Qt::MouseButton qtbutton = Qt::NoButton; + if (type != QEvent::TabletMove) { + qtbutton = xiToQtMouseButton(ev->detail); + } + + // XXX: should we do something like this? + // local = w->mapFromGlobal(global); + + QTabletEvent e(type, local, global, tabletData.tool, tabletData.pointerType, + pressure, xTilt, yTilt, tangentialPressure, rotation, 0, + keyState, tabletData.serialId, qtbutton, tabletData.buttons); + e.ignore(); + qApp->sendEvent(w, &e); + // dbgInput << "Custom QTabletEvent sent to widget:" << w->objectName() << " Accepted?" << e.isAccepted(); + // QString prefix = QStringLiteral(""); + // dbgInput << KisTabletDebugger::instance()->eventToString(e, prefix); + // return e.isAccepted(); + return true; +} + + + +bool KisXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) +{ + + if (xi2PrepareXIGenericDeviceEvent(event, m_xiOpCode)) { + xXIGenericDeviceEvent *xiEvent = reinterpret_cast(event); + int sourceDeviceId = xiEvent->deviceid; // may be the master id + xXIDeviceEvent *xiDeviceEvent = 0; + + switch (xiEvent->evtype) { + case XI_ButtonPress: + case XI_ButtonRelease: + case XI_Motion: + case XI_TouchBegin: + case XI_TouchUpdate: + case XI_TouchEnd: + { + xiDeviceEvent = reinterpret_cast(event); + sourceDeviceId = xiDeviceEvent->sourceid; // use the actual device id instead of the master + break; + } + default: + break; + } + + for (int i = 0; i < m_tabletData.count(); ++i) { + if (m_tabletData.at(i).deviceId == sourceDeviceId) { + if (xi2HandleTabletEvent(xiEvent, m_tabletData[i])) { + // dbgInput << "Slurped an event. Don't use this one, Qt!"; + return true; + } + } + } + + // Handle non-tablet XInput2.2 events. + if (xiDeviceEvent) { + switch (xiDeviceEvent->evtype) { + case XI_ButtonPress: + case XI_ButtonRelease: + case XI_Motion: + // We don't really care about processing stuff here, we just want to block processing + if (kis_tabletChokeMouse) { + kis_tabletChokeMouse = false; + return true; + } + break; + + // XXX: Uncomment when restoring touch events. + // case XI_TouchBegin: + // case XI_TouchUpdate: + // case XI_TouchEnd: + // qCDebug(lcQpaXInput, "XI2 touch event type %d seq %d detail %d pos %6.1f, %6.1f root pos %6.1f, %6.1f on window %x",event->event_type, xiDeviceEvent->sequenceNumber, xiDeviceEvent->detail,fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y),fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y),xiDeviceEvent->event); + // if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) + // xi2ProcessTouch(xiDeviceEvent, platformWindow); + // break; + + } + } + } + return false; +}; + + +// static inline int fixed1616ToInt(FP1616 val) +// { +// return int((qreal(val >> 16)) + (val & 0xFFFF) / (qreal)0xFFFF); +// } + +// // With XI 2.2+ press/release/motion comes here instead of the above handlers. +// void KisXcbConnection::handleXIMouseEvent(xcb_ge_event_t *event) +// { +// xXIDeviceEvent *ev = reinterpret_cast(event); +// const Qt::KeyboardModifiers modifiers = connection->keyboard()->translateModifiers(ev->mods.effective_mods); +// const int event_x = fixed1616ToInt(ev->event_x); +// const int event_y = fixed1616ToInt(ev->event_y); +// const int root_x = fixed1616ToInt(ev->root_x); +// const int root_y = fixed1616ToInt(ev->root_y); + +// connection->keyboard()->updateXKBStateFromXI(&ev->mods, &ev->group); + +// const Qt::MouseButton button = conn->xiToQtMouseButton(ev->detail); + +// if (ev->buttons_len > 0) { +// unsigned char *buttonMask = (unsigned char *) &ev[1]; +// for (int i = 1; i <= 15; ++i) +// conn->setButton(conn->translateMouseButton(i), XIMaskIsSet(buttonMask, i)); +// } + +// switch (ev->evtype) { +// case XI_ButtonPress: +// dbgInput << "XI2 mouse press, " << ppvar(button) << ppVar(ev->time); +// conn->setButton(button, true); +// handleButtonPressEvent(event_x, event_y, root_x, root_y, ev->detail, modifiers, ev->time); +// break; +// case XI_ButtonRelease: +// dbgInput << "XI2 mouse release, " << ppvar(button) << ppVar(ev->time); +// conn->setButton(button, false); +// handleButtonReleaseEvent(event_x, event_y, root_x, root_y, ev->detail, modifiers, ev->time); +// break; +// case XI_Motion: +// dbgInput << "XI2 mouse motion, " << ppvar(button) << ppVar(ev->time); +// handleMotionNotifyEvent(event_x, event_y, root_x, root_y, modifiers, ev->time); +// break; +// default: +// qWarning() << "Unrecognized XI2 mouse event" << ev->evtype; +// break; +// } +// } + + +QByteArray KisXcbConnection::atomName(xcb_atom_t atom) +{ + if (!atom) + return QByteArray(); + + xcb_generic_error_t *error = 0; + xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name(connection, atom); + xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(connection, cookie, &error); + if (error) { + qWarning() << "QXcbConnection::atomName: bad Atom" << atom; + free(error); + } + if (reply) { + QByteArray result(xcb_get_atom_name_name(reply), xcb_get_atom_name_name_length(reply)); + free(reply); + return result; + } + return QByteArray(); +}