diff --git a/src/platformtheme/kdeplatformtheme.cpp b/src/platformtheme/kdeplatformtheme.cpp index 5939ba633b9e5fd2f67faa824f21c9f907f18e45..27bb36d91f325882d13c96ae924c187d15bca804 100644 --- a/src/platformtheme/kdeplatformtheme.cpp +++ b/src/platformtheme/kdeplatformtheme.cpp @@ -55,6 +55,195 @@ #include "qdbusmenubar_p.h" +#if !defined(QT_NO_GESTURES) && HAVE_X11 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +// #define TAPANDHOLD_DEBUG + +class KdePlasmaThemeEventFilter : public QObject +{ + Q_OBJECT +#if !defined(QT_NO_GESTURES) && HAVE_X11 + using XQueryPointerFnc = Bool (*)(Display*, Window, Window*, Window*, int*, int*, int*, int*, unsigned int*); + XQueryPointerFnc XQueryPointer = NULL; +#endif + +public: + KdePlasmaThemeEventFilter(QObject *parent=nullptr) + : QObject(parent) + { +#if !defined(QT_NO_GESTURES) && HAVE_X11 + QLibrary xlib(QStringLiteral("libX11"), this); + XQueryPointer = (XQueryPointerFnc) xlib.resolve("XQueryPointer"); + if (!XQueryPointer) { + qCritical() << "Failed to XQueryPointer():" << xlib.errorString(); + } +#endif + } + virtual ~KdePlasmaThemeEventFilter() + { + } + +#if !defined(QT_NO_GESTURES) && HAVE_X11 + inline bool handleGestureForObject(const QObject *obj) const + { + // this function is called with an that is or inherits a QWidget + const QPushButton *btn = qobject_cast(obj); + const QToolButton *tbtn = qobject_cast(obj); + if (tbtn) { + return !tbtn->menu(); + } else if (btn) { + return !btn->menu(); + } else { + return (qobject_cast(obj) || qobject_cast(obj) +// || obj->inherits("QTabBar") || obj->inherits("QTabWidget") + || qobject_cast(obj) + || qobject_cast(obj) + || qobject_cast(obj) + // this catches items in directory lists and the like + || obj->objectName() == QStringLiteral("qt_scrollarea_viewport") + || obj->inherits("KateViewInternal")); + // Konsole windows can be found as obj->inherits("Konsole::TerminalDisplay") but + // for some reason Konsole doesn't respond to synthetic ContextMenu events + } + } + + // checks if (only) the left mouse button is pressed + bool pressedLeftMouseButton() + { + if (KWindowSystem::isPlatformX11() && XQueryPointer) { + Display *display = QX11Info::display(); + Window dumWindow; + int dumInt; + unsigned int mask; + XQueryPointer(display, DefaultRootWindow(display), &dumWindow, &dumWindow, + &dumInt, &dumInt, &dumInt, &dumInt, &mask); + return (mask & Button1Mask) == Button1Mask; + } + return false; + } + + bool eventFilter(QObject *obj, QEvent *event) override + { + static QVariant qTrue(true), qFalse(false); + switch (event->type()) { + case QEvent::MouseButtonPress: { + QMouseEvent *me = dynamic_cast(event); + if (me->button() == Qt::LeftButton && me->modifiers() == Qt::NoModifier) { + QWidget *w = qobject_cast(obj); + if (w && handleGestureForObject(obj)) { + QVariant isGrabbed = obj->property("OurTaHGestureActive"); + if (!(isGrabbed.isValid() && isGrabbed.toBool())) { + // ideally we'd check first - if we could. + // storing all grabbed QObjects is potentially dangerous since we won't + // know when they go stale. + w->grabGesture(Qt::TapAndHoldGesture); + // accept this event but resend it so that the 1st mousepress + // can also trigger a tap-and-hold! + obj->setProperty("OurTaHGestureActive", qTrue); +#ifdef TAPANDHOLD_DEBUG + if (qEnvironmentVariableIsSet("TAPANDHOLD_CONTEXTMENU_DEBUG")) { + qWarning() << "event=" << event << "grabbing obj=" << obj << "parent=" << obj->parent(); + } +#endif + if (!m_grabbing.contains(obj)) { + QMouseEvent relay(*me); + me->accept(); + m_grabbing.insert(obj); + int ret = QCoreApplication::sendEvent(obj, &relay); + m_grabbing.remove(obj); + return ret; + } + } + } +#ifdef TAPANDHOLD_DEBUG + else if (w && qEnvironmentVariableIsSet("TAPANDHOLD_CONTEXTMENU_DEBUG")) { + qWarning() << "event=" << event << "obj=" << obj << "parent=" << obj->parent(); + } +#endif + } + // NB: don't "eat" the event if no action was taken! + break; + } + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: { + QVariant isGrabbed = obj->property("OurTaHGestureActive"); + if (isGrabbed.isValid() && isGrabbed.toBool()) { +#ifdef TAPANDHOLD_DEBUG + qWarning() << "event=" << event << "obj=" << obj << "parent=" << obj->parent() + << "grabbed=" << obj->property("OurTaHGestureActive"); +#endif + obj->setProperty("OurTaHGestureActive", qFalse); + } + break; + } + case QEvent::Gesture: { + QGestureEvent *gEvent = static_cast(event); + if (QTapAndHoldGesture *heldTap = static_cast(gEvent->gesture(Qt::TapAndHoldGesture))) { + if (heldTap->state() == Qt::GestureFinished) { + QVariant isGrabbed = obj->property("OurTaHGestureActive"); + if (isGrabbed.isValid() && isGrabbed.toBool() && pressedLeftMouseButton()) { + QWidget *w = qobject_cast(obj); + // user clicked and held a button, send it a simulated ContextMenuEvent + // but send a simulated buttonrelease event first. + QPoint localPos = w->mapFromGlobal(heldTap->position().toPoint()); + QContextMenuEvent ce(QContextMenuEvent::Mouse, localPos, heldTap->hotSpot().toPoint()); + // don't send a ButtonRelease event to Q*Buttons because we don't want to trigger them + if (QPushButton *btn = qobject_cast(obj)) { + btn->setDown(false); + obj->setProperty("OurTaHGestureActive", qFalse); + } else if (QToolButton *tbtn = qobject_cast(obj)) { + tbtn->setDown(false); + obj->setProperty("OurTaHGestureActive", qFalse); + } else { + QMouseEvent me(QEvent::MouseButtonRelease, localPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); +#ifdef TAPANDHOLD_DEBUG + qWarning() << "Sending" << &me; +#endif + // we'll be unsetting OurTaHGestureActive in the MouseButtonRelease handler above + QCoreApplication::sendEvent(obj, &me); + } + qWarning() << "Sending" << &ce << "to" << obj << "because of" << gEvent << "isGrabbed=" << isGrabbed; + bool ret = QCoreApplication::sendEvent(obj, &ce); + gEvent->accept(); + qWarning() << "\tsendEvent" << &ce << "returned" << ret; + return true; + } + } + } + break; + } +#ifdef TAPANDHOLD_DEBUG + case QEvent::ContextMenu: + if (qEnvironmentVariableIsSet("TAPANDHOLD_CONTEXTMENU_DEBUG")) { + qWarning() << "event=" << event << "obj=" << obj << "parent=" << obj->parent() + << "grabbed=" << obj->property("OurTaHGestureActive"); + } + break; +#endif + default: + break; + } + return false; + } + QSet m_grabbing; +#endif +}; + static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME"); static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH"); @@ -90,6 +279,9 @@ KdePlatformTheme::KdePlatformTheme() m_x11Integration->init(); } #endif +#if !defined(QT_NO_GESTURES) && HAVE_X11 + qApp->installEventFilter(new KdePlasmaThemeEventFilter); +#endif QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, false); setQtQuickControlsTheme(); @@ -393,3 +585,5 @@ void KdePlatformTheme::setQtQuickControlsTheme() } QQuickStyle::setStyle(QLatin1String("org.kde.desktop")); } + +#include "kdeplatformtheme.moc"