From 255abc1e5a3397bf14e9854388881cd80f0bd2e3 Mon Sep 17 00:00:00 2001 From: Gatis Paeglis Date: Fri, 4 Aug 2017 12:10:12 +0200 Subject: xcb: add support for peeking into the XCB event queue This will be used by the Qt X11 Extras module. Task-number: QTBUG-50358 Change-Id: Ie095cd211c393ea6d78660b4d53cac28b435a3b2 Reviewed-by: Edward Welbourne --- src/plugins/platforms/xcb/qxcbconnection.cpp | 93 +++++++++++++++++++++++ src/plugins/platforms/xcb/qxcbconnection.h | 15 ++++ src/plugins/platforms/xcb/qxcbnativeinterface.cpp | 31 +++++++- src/plugins/platforms/xcb/qxcbnativeinterface.h | 14 +++- 4 files changed, 149 insertions(+), 4 deletions(-) diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp index 6f62589924..23bb5c6410 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection.cpp @@ -110,6 +110,8 @@ Q_LOGGING_CATEGORY(lcQpaXInputDevices, "qt.qpa.input.devices") Q_LOGGING_CATEGORY(lcQpaXInputEvents, "qt.qpa.input.events") Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen") Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events") +Q_LOGGING_CATEGORY(lcQpaXcb, "qt.qpa.xcb") // for general (uncategorized) XCB logging +Q_LOGGING_CATEGORY(lcQpaPeeker, "qt.qpa.peeker") // this event type was added in libxcb 1.10, // but we support also older version @@ -1227,6 +1229,95 @@ void QXcbConnection::addPeekFunc(PeekFunc f) m_peekFuncs.append(f); } +qint32 QXcbConnection::generatePeekerId() +{ + qint32 peekerId = m_peekerIdSource++; + m_peekerToCachedIndex.insert(peekerId, 0); + return peekerId; +} + +bool QXcbConnection::removePeekerId(qint32 peekerId) +{ + if (!m_peekerToCachedIndex.contains(peekerId)) { + qCWarning(lcQpaXcb, "failed to remove unknown peeker id: %d", peekerId); + return false; + } + m_peekerToCachedIndex.remove(peekerId); + if (m_peekerToCachedIndex.isEmpty()) { + m_peekerIdSource = 0; // Once the hash becomes empty, we can start reusing IDs + m_peekerIndexCacheDirty = false; + } + return true; +} + +bool QXcbConnection::peekEventQueue(PeekerCallback peeker, void *peekerData, + PeekOptions option, qint32 peekerId) +{ + bool peekerIdProvided = peekerId != -1; + if (peekerIdProvided && !m_peekerToCachedIndex.contains(peekerId)) { + qCWarning(lcQpaXcb, "failed to find index for unknown peeker id: %d", peekerId); + return false; + } + + bool peekFromCachedIndex = option.testFlag(PeekOption::PeekFromCachedIndex); + if (peekFromCachedIndex && !peekerIdProvided) { + qCWarning(lcQpaXcb, "PeekOption::PeekFromCachedIndex requires peeker id"); + return false; + } + + if (peekerIdProvided && m_peekerIndexCacheDirty) { + // When the main event loop has flushed the buffered XCB events into the window + // system event queue, the cached indices are not valid anymore and need reset. + auto it = m_peekerToCachedIndex.begin(); + while (it != m_peekerToCachedIndex.constEnd()) { + (*it) = 0; + ++it; + } + m_peekerIndexCacheDirty = false; + } + + qint32 peekerIndex = peekFromCachedIndex ? m_peekerToCachedIndex.value(peekerId) : 0; + qint32 startingIndex = peekerIndex; + bool result = false; + m_mainEventLoopFlushedQueue = false; + + QXcbEventArray *eventqueue = m_reader->lock(); + + if (Q_UNLIKELY(lcQpaPeeker().isDebugEnabled())) { + qCDebug(lcQpaPeeker, "[%d] peeker index: %d | mode: %s | queue size: %d", peekerId, + peekerIndex, peekFromCachedIndex ? "cache" : "start", eventqueue->size()); + } + while (peekerIndex < eventqueue->size() && !result && !m_mainEventLoopFlushedQueue) { + xcb_generic_event_t *event = eventqueue->at(peekerIndex++); + if (!event) + continue; + if (Q_UNLIKELY(lcQpaPeeker().isDebugEnabled())) { + QString debug = QString((QLatin1String("[%1] peeking at index: %2"))) + .arg(peekerId).arg(peekerIndex - 1); + printXcbEvent(lcQpaPeeker(), debug.toLatin1(), event); + } + // A peeker may call QCoreApplication::processEvents(), which has two implications: + // 1) We need to make the lock available for QXcbConnection::processXcbEvents(), + // otherwise we will deadlock; + // 2) QXcbConnection::processXcbEvents() will flush the queue we are currently + // looping through; + m_reader->unlock(); + result = peeker(event, peekerData); + m_reader->lock(); + } + + m_reader->unlock(); + + if (peekerIdProvided && peekerIndex != startingIndex && !m_mainEventLoopFlushedQueue) { + auto it = m_peekerToCachedIndex.find(peekerId); + // Make sure that a peeker callback did not remove the peeker id + if (it != m_peekerToCachedIndex.constEnd()) + (*it) = peekerIndex; + } + + return result; +} + QXcbEventReader::QXcbEventReader(QXcbConnection *connection) : m_connection(connection) { @@ -1673,6 +1764,8 @@ void QXcbConnection::processXcbEvents() m_reader->unlock(); + m_peekerIndexCacheDirty = m_mainEventLoopFlushedQueue = true; + // Indicate with a null event that the event the callbacks are waiting for // is not in the queue currently. for (PeekFunc f : qAsConst(m_peekFuncs)) diff --git a/src/plugins/platforms/xcb/qxcbconnection.h b/src/plugins/platforms/xcb/qxcbconnection.h index a049fc5036..a37ce5f5a6 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.h +++ b/src/plugins/platforms/xcb/qxcbconnection.h @@ -90,6 +90,8 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaXInputDevices) Q_DECLARE_LOGGING_CATEGORY(lcQpaXInputEvents) Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen) Q_DECLARE_LOGGING_CATEGORY(lcQpaEvents) +Q_DECLARE_LOGGING_CATEGORY(lcQpaXcb) +Q_DECLARE_LOGGING_CATEGORY(lcQpaPeeker) class QXcbVirtualDesktop; class QXcbScreen; @@ -444,6 +446,15 @@ public: typedef bool (*PeekFunc)(QXcbConnection *, xcb_generic_event_t *); void addPeekFunc(PeekFunc f); + // Peek at all queued events + qint32 generatePeekerId(); + bool removePeekerId(qint32 peekerId); + enum PeekOption { PeekDefault = 0, PeekFromCachedIndex = 1 }; // see qx11info_x11.h + Q_DECLARE_FLAGS(PeekOptions, PeekOption) + typedef bool (*PeekerCallback)(xcb_generic_event_t *event, void *peekerData); + bool peekEventQueue(PeekerCallback peeker, void *peekerData = nullptr, + PeekOptions option = PeekDefault, qint32 peekerId = -1); + inline xcb_timestamp_t time() const { return m_time; } inline void setTime(xcb_timestamp_t t) { if (t > m_time) m_time = t; } @@ -692,6 +703,10 @@ private: xcb_window_t m_qtSelectionOwner = 0; + bool m_mainEventLoopFlushedQueue = false; + qint32 m_peekerIdSource = 0; + bool m_peekerIndexCacheDirty = false; + QHash m_peekerToCachedIndex; friend class QXcbEventReader; }; #if QT_CONFIG(xinput2) diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp index 76ee8f3fb4..caa9499c45 100644 --- a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp +++ b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp @@ -83,7 +83,10 @@ static int resourceType(const QByteArray &key) QByteArrayLiteral("subpixeltype"), QByteArrayLiteral("antialiasingenabled"), QByteArrayLiteral("atspibus"), QByteArrayLiteral("compositingenabled"), - QByteArrayLiteral("vksurface") + QByteArrayLiteral("vksurface"), + QByteArrayLiteral("generatepeekerid"), + QByteArrayLiteral("removepeekerid"), + QByteArrayLiteral("peekeventqueue") }; const QByteArray *end = names + sizeof(names) / sizeof(names[0]); const QByteArray *result = std::find(names, end, key); @@ -304,6 +307,13 @@ QPlatformNativeInterface::NativeResourceForIntegrationFunction QXcbNativeInterfa if (lowerCaseResource == "setstartupid") return NativeResourceForIntegrationFunction(setStartupId); + if (lowerCaseResource == "generatepeekerid") + return NativeResourceForIntegrationFunction(generatePeekerId); + if (lowerCaseResource == "removepeekerid") + return NativeResourceForIntegrationFunction(removePeekerId); + if (lowerCaseResource == "peekeventqueue") + return NativeResourceForIntegrationFunction(peekEventQueue); + return 0; } @@ -482,6 +492,25 @@ void QXcbNativeInterface::setAppUserTime(QScreen* screen, xcb_timestamp_t time) } } +qint32 QXcbNativeInterface::generatePeekerId() +{ + QXcbIntegration *integration = QXcbIntegration::instance(); + return integration->defaultConnection()->generatePeekerId(); +} + +bool QXcbNativeInterface::removePeekerId(qint32 peekerId) +{ + QXcbIntegration *integration = QXcbIntegration::instance(); + return integration->defaultConnection()->removePeekerId(peekerId); +} + +bool QXcbNativeInterface::peekEventQueue(QXcbConnection::PeekerCallback peeker, void *peekerData, + QXcbConnection::PeekOptions option, qint32 peekerId) +{ + QXcbIntegration *integration = QXcbIntegration::instance(); + return integration->defaultConnection()->peekEventQueue(peeker, peekerData, option, peekerId); +} + void QXcbNativeInterface::setStartupId(const char *data) { QByteArray startupId(data); diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.h b/src/plugins/platforms/xcb/qxcbnativeinterface.h index b9f1ebcdc6..fb0db727aa 100644 --- a/src/plugins/platforms/xcb/qxcbnativeinterface.h +++ b/src/plugins/platforms/xcb/qxcbnativeinterface.h @@ -46,12 +46,11 @@ #include #include "qxcbexport.h" +#include "qxcbconnection.h" QT_BEGIN_NAMESPACE -class QWidget; class QXcbScreen; -class QXcbConnection; class QXcbNativeInterfaceHandler; class Q_XCB_EXPORT QXcbNativeInterface : public QPlatformNativeInterface @@ -74,7 +73,10 @@ public: ScreenAntialiasingEnabled, AtspiBus, CompositingEnabled, - VkSurface + VkSurface, + GeneratePeekerId, + RemovePeekerId, + PeekEventQueue }; QXcbNativeInterface(); @@ -114,6 +116,12 @@ public: static void setAppTime(QScreen *screen, xcb_timestamp_t time); static void setAppUserTime(QScreen *screen, xcb_timestamp_t time); + static qint32 generatePeekerId(); + static bool removePeekerId(qint32 peekerId); + static bool peekEventQueue(QXcbConnection::PeekerCallback peeker, void *peekerData = nullptr, + QXcbConnection::PeekOptions option = QXcbConnection::PeekDefault, + qint32 peekerId = -1); + Q_INVOKABLE bool systemTrayAvailable(const QScreen *screen) const; Q_INVOKABLE void setParentRelativeBackPixmap(QWindow *window); Q_INVOKABLE bool systrayVisualHasAlphaChannel(); -- cgit v1.2.3