summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGatis Paeglis <gatis.paeglis@qt.io>2017-07-26 14:54:23 +0200
committerGatis Paeglis <gatis.paeglis@qt.io>2017-08-29 14:18:29 +0000
commitec2c0f4db6350a304dcf901b9adbadc895864c14 (patch)
treedb8c6df6b3143b67f18feeab78a6d2cccd92b8f1
parent9749c5085dc21d2064177aa748f3a9395592a7cf (diff)
downloadqtx11extras-ec2c0f4db6350a304dcf901b9adbadc895864c14.tar.gz
[ChangeLog][QX11Info] Added new API to peek into the XCB event queue - peekEventQueue(). This enables porting certain Qt4-based applications to Qt5 (those using Xlib's event handling functions to peek into the X11 event queue). In Qt5 we set XCB to be the owner of the X11 event queue with XSetEventQueueOwner(dpy, XCBOwnsEventQueue), which means that only XCB event handling functions can be used to read events from the X server. XCB does not have an equivalent for Xlib's peeker API. For filtering out unwanted native events Qt5 provides QAbstractNativeEventFilter::nativeEventFilter(), but there isn't any API in Qt to just *peek* into the queue while the GUI thread is busy processing a long task. The peekEventQueue() function adds this capability via QX11Info. Manual and auto test included. Task-number: QTBUG-50358 Change-Id: Id31f797f7ff76d011ad7a55a9b6c13756aaaca60 Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
-rw-r--r--src/x11extras/qx11info_x11.cpp158
-rw-r--r--src/x11extras/qx11info_x11.h17
-rw-r--r--tests/auto/qx11info/qx11info.pro1
-rw-r--r--tests/auto/qx11info/tst_qx11info.cpp214
-rw-r--r--tests/manual/manual.pro3
-rw-r--r--tests/manual/peeker/main.cpp111
-rw-r--r--tests/manual/peeker/peeker.pro6
-rw-r--r--tests/tests.pro2
8 files changed, 509 insertions, 3 deletions
diff --git a/src/x11extras/qx11info_x11.cpp b/src/x11extras/qx11info_x11.cpp
index 31bc405..e64ba9c 100644
--- a/src/x11extras/qx11info_x11.cpp
+++ b/src/x11extras/qx11info_x11.cpp
@@ -403,4 +403,162 @@ bool QX11Info::isCompositingManagerRunning(int screen)
return native->nativeResourceForScreen(QByteArray("compositingEnabled"), scr);
}
+/*!
+ Returns a new peeker id or -1 if some interal error has occurred.
+ Each peeker id is associated with an index in the buffered native
+ event queue.
+
+ For more details see QX11Info::PeekOption and peekEventQueue().
+
+ \sa peekEventQueue(), removePeekerId()
+ \since 5.10
+*/
+qint32 QX11Info::generatePeekerId()
+{
+ if (!qApp)
+ return -1;
+ QPlatformNativeInterface *native = qApp->platformNativeInterface();
+ if (!native)
+ return -1;
+
+ typedef qint32 (*GeneratePeekerIdFunc)(void);
+ GeneratePeekerIdFunc generatepeekerid = reinterpret_cast<GeneratePeekerIdFunc>(
+ native->nativeResourceFunctionForIntegration("generatepeekerid"));
+ if (!generatepeekerid) {
+ qWarning("Internal error: QPA plugin doesn't implement generatePeekerId");
+ return -1;
+ }
+
+ return generatepeekerid();
+}
+
+/*!
+ Removes \a peekerId, which was earlier obtained via generatePeekerId().
+
+ Returns \c true on success or \c false if unknown peeker id was
+ provided or some interal error has occurred.
+
+ \sa generatePeekerId()
+ \since 5.10
+*/
+bool QX11Info::removePeekerId(qint32 peekerId)
+{
+ if (!qApp)
+ return false;
+ QPlatformNativeInterface *native = qApp->platformNativeInterface();
+ if (!native)
+ return false;
+
+ typedef bool (*RemovePeekerIdFunc)(qint32);
+ RemovePeekerIdFunc removePeekerId = reinterpret_cast<RemovePeekerIdFunc>(
+ native->nativeResourceFunctionForIntegration("removepeekerid"));
+ if (!removePeekerId) {
+ qWarning("Internal error: QPA plugin doesn't implement removePeekerId");
+ return false;
+ }
+
+ return removePeekerId(peekerId);
+}
+
+/*!
+ \enum QX11Info::PeekOption
+ \brief An enum to tune the behavior of QX11Info::peekEventQueue().
+
+ \value PeekDefault
+ Peek from the beginning of the buffered native event queue. A peeker
+ id is optional with PeekDefault. If a peeker id is provided to
+ peekEventQueue() when using PeekDefault, then peeking starts from
+ the beginning of the queue, not from the cached index; thus, this
+ can be used to manually reset a cached index to peek from the start
+ of the queue. When this operation completes, the associated index
+ will be updated to the new position in the queue.
+
+ \value PeekFromCachedIndex
+ QX11Info::peekEventQueue() can optimize the peeking algorithm by
+ skipping events that it already has seen in earlier calls to
+ peekEventQueue(). When control returns to the main event loop,
+ which causes the buffered native event queue to be flushed to Qt's
+ event queue, the cached indices are marked invalid and will be
+ reset on the next access. The same is true if the program
+ explicitly flushes the buffered native event queue by
+ QCoreApplication::processEvents().
+
+ \since 5.10
+*/
+
+/*!
+ \typedef QX11Info::PeekerCallback
+ Typedef for a pointer to a function with the following signature:
+
+ \code
+ bool (*PeekerCallback)(xcb_generic_event_t *event, void *peekerData);
+ \endcode
+
+ The \a event is a native XCB event.
+ The \a peekerData is a pointer to data, passed in via peekEventQueue().
+
+ Return \c true from this function to stop examining the buffered
+ native event queue or \c false to continue.
+
+ \note A non-capturing lambda can serve as a PeekerCallback.
+ \since 5.10
+*/
+
+/*!
+ \brief Peek into the buffered XCB event queue.
+
+ You can call peekEventQueue() periodically, when your program is busy
+ performing a long-running operation, to peek into the buffered native
+ event queue. The more time the long-running operation blocks the
+ program from returning control to the main event loop, the more
+ events will accumulate in the buffered XCB event queue. Once control
+ returns to the main event loop these events will be flushed to Qt's
+ event queue, which is a separate event queue from the queue this
+ function is peeking into.
+
+ \note It is usually better to run CPU-intensive operations in a
+ non-GUI thread, instead of blocking the main event loop.
+
+ The buffered XCB event queue is populated from a non-GUI thread and
+ therefore might be ahead of the current GUI state. To handle native
+ events as they are processed by the GUI thread, see
+ QAbstractNativeEventFilter::nativeEventFilter().
+
+ The \a peeker is a callback function as documented in PeekerCallback.
+ The \a peekerData can be used to pass in arbitrary data to the \a
+ peeker callback.
+ The \a option is an enum that tunes the behavior of peekEventQueue().
+ The \a peekerId is used to track an index in the queue, for more
+ details see QX11Info::PeekOption. There can be several indices,
+ each tracked individually by a peeker id obtained via generatePeekerId().
+
+ This function returns \c true when the peeker has stopped the event
+ proccesing by returning \c true from the callback. If there were no
+ events in the buffered native event queue to peek at or all the
+ events have been processed by the peeker, this function returns \c
+ false.
+
+ \sa generatePeekerId(), QAbstractNativeEventFilter::nativeEventFilter()
+ \since 5.10
+*/
+bool QX11Info::peekEventQueue(PeekerCallback peeker, void *peekerData, PeekOptions option,
+ qint32 peekerId)
+{
+ if (!peeker || !qApp)
+ return false;
+ QPlatformNativeInterface *native = qApp->platformNativeInterface();
+ if (!native)
+ return false;
+
+ typedef bool (*PeekEventQueueFunc)(PeekerCallback, void *, PeekOptions, qint32);
+ PeekEventQueueFunc peekeventqueue = reinterpret_cast<PeekEventQueueFunc>(
+ native->nativeResourceFunctionForIntegration("peekeventqueue"));
+ if (!peekeventqueue) {
+ qWarning("Internal error: QPA plugin doesn't implement peekEventQueue");
+ return false;
+ }
+
+ return peekeventqueue(peeker, peekerData, option, peekerId);
+}
+
QT_END_NAMESPACE
diff --git a/src/x11extras/qx11info_x11.h b/src/x11extras/qx11info_x11.h
index ab72686..c0bfbf8 100644
--- a/src/x11extras/qx11info_x11.h
+++ b/src/x11extras/qx11info_x11.h
@@ -43,14 +43,21 @@
#include <QtCore/qnamespace.h>
#include "QtX11Extras/qtx11extrasglobal.h"
+#include <xcb/xcb.h>
+
typedef struct _XDisplay Display;
-struct xcb_connection_t;
QT_BEGIN_NAMESPACE
class Q_X11EXTRAS_EXPORT QX11Info
{
public:
+ enum PeekOption {
+ PeekDefault = 0,
+ PeekFromCachedIndex = 1
+ };
+ Q_DECLARE_FLAGS(PeekOptions, PeekOption)
+
static bool isPlatformX11();
static int appDpiX(int screen=-1);
@@ -75,10 +82,18 @@ public:
static bool isCompositingManagerRunning(int screen = -1);
+ static qint32 generatePeekerId();
+ static bool removePeekerId(qint32 peekerId);
+ typedef bool (*PeekerCallback)(xcb_generic_event_t *event, void *peekerData);
+ static bool peekEventQueue(PeekerCallback peeker, void *peekerData = nullptr,
+ PeekOptions option = PeekDefault, qint32 peekerId = -1);
+
private:
QX11Info();
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QX11Info::PeekOptions)
+
QT_END_NAMESPACE
#endif // QX11INFO_X11_H
diff --git a/tests/auto/qx11info/qx11info.pro b/tests/auto/qx11info/qx11info.pro
index 9059baa..3a34ecd 100644
--- a/tests/auto/qx11info/qx11info.pro
+++ b/tests/auto/qx11info/qx11info.pro
@@ -2,3 +2,4 @@ CONFIG += testcase
TARGET = tst_qx11info
QT += x11extras widgets testlib
SOURCES += tst_qx11info.cpp
+LIBS += -lxcb
diff --git a/tests/auto/qx11info/tst_qx11info.cpp b/tests/auto/qx11info/tst_qx11info.cpp
index a27f443..e997832 100644
--- a/tests/auto/qx11info/tst_qx11info.cpp
+++ b/tests/auto/qx11info/tst_qx11info.cpp
@@ -27,7 +27,7 @@
**
****************************************************************************/
-
+#include <QtGui>
#include <QtTest/QtTest>
#include <QApplication>
@@ -42,6 +42,7 @@ private slots:
void startupId();
void isPlatformX11();
void appTime();
+ void peeker();
};
void tst_QX11Info::staticFunctionsBeforeQApplication()
@@ -162,6 +163,217 @@ void tst_QX11Info::appTime()
QCOMPARE(QX11Info::appUserTime(), u1);
}
+class PeekerTest : public QWindow
+{
+public:
+ PeekerTest()
+ {
+ setGeometry(100, 100, 400, 400);
+ m_peekerFirstId = QX11Info::generatePeekerId();
+ QVERIFY(m_peekerFirstId >= 0);
+ m_peekerSecondId = QX11Info::generatePeekerId();
+ QVERIFY(m_peekerSecondId == m_peekerFirstId + 1);
+ // Get X atoms
+ xcb_connection_t *c = QX11Info::connection();
+ const char *first = "QT_XCB_PEEKER_TEST_FIRST";
+ const char *second = "QT_XCB_PEEKER_TEST_SECOND";
+ xcb_intern_atom_reply_t *reply =
+ xcb_intern_atom_reply(c, xcb_intern_atom(c, false, strlen(first), first), 0);
+ QVERIFY2(reply != nullptr, first);
+ m_atomFirst = reply->atom;
+ free(reply);
+ reply = xcb_intern_atom_reply(c, xcb_intern_atom(c, false, strlen(second), second), 0);
+ QVERIFY2(reply != nullptr, second);
+ m_atomSecond = reply->atom;
+ free(reply);
+ }
+
+protected:
+ void triggerPropertyNotifyEvents()
+ {
+ xcb_window_t rootWindow = QX11Info::appRootWindow();
+ xcb_connection_t *connection = QX11Info::connection();
+ xcb_change_property(connection, XCB_PROP_MODE_APPEND, rootWindow, m_atomFirst,
+ XCB_ATOM_INTEGER, 32, 0, nullptr);
+ xcb_change_property(connection, XCB_PROP_MODE_APPEND, rootWindow, m_atomSecond,
+ XCB_ATOM_INTEGER, 32, 0, nullptr);
+ xcb_flush(connection);
+ }
+
+ static bool checkForPropertyNotifyByAtom(xcb_generic_event_t *event, void *data)
+ {
+ bool isPropertyNotify = (event->response_type & ~0x80) == XCB_PROPERTY_NOTIFY;
+ if (isPropertyNotify) {
+ xcb_property_notify_event_t *pn =
+ reinterpret_cast<xcb_property_notify_event_t *>(event);
+ xcb_atom_t *atom = static_cast<xcb_atom_t *>(data);
+ if (pn->atom == *atom)
+ return true;
+ }
+ return false;
+ }
+
+ bool sanityCheckPeeking(xcb_generic_event_t *event)
+ {
+ m_countWithCaching++;
+ bool isPropertyNotify = (event->response_type & ~0x80) == XCB_PROPERTY_NOTIFY;
+ if (isPropertyNotify) {
+ xcb_property_notify_event_t *pn =
+ reinterpret_cast<xcb_property_notify_event_t *>(event);
+ if (pn->atom == m_atomFirst) {
+ if (m_indexFirst == -1) {
+ m_indexFirst = m_countWithCaching;
+ // continue peeking, maybe the second event is already in the queue
+ return false;
+ }
+ m_foundFirstEventAgain = true;
+ // Return so we can fail the test with QVERIFY2, for more details
+ // see QTBUG-62354
+ return true;
+ }
+ // Let it peek to the end, even when the second event was found
+ if (pn->atom == m_atomSecond) {
+ m_indexSecond = m_countWithCaching;
+ if (m_indexFirst == -1) {
+ m_foundSecondBeforeFirst = true;
+ // Same as above, see QTBUG-62354
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ void exposeEvent(QExposeEvent *)
+ {
+ if (m_ignoreSubsequentExpose)
+ return;
+ // We don't want to execute this handler again in case there are more expose
+ // events after calling QCoreApplication::processEvents() later in this test
+ m_ignoreSubsequentExpose = true;
+
+ xcb_flush(QX11Info::connection());
+ bool found = QX11Info::peekEventQueue(checkForPropertyNotifyByAtom, &m_atomFirst);
+ QVERIFY2(!found, "Found m_atomFirst which should not be in the queue yet");
+ found = QX11Info::peekEventQueue(checkForPropertyNotifyByAtom, &m_atomSecond);
+ QVERIFY2(!found, "Found m_atomSecond which should not be in the queue yet");
+
+ triggerPropertyNotifyEvents();
+
+ bool earlyExit = false;
+ while (!earlyExit && (m_indexFirst == -1 || m_indexSecond == -1)) {
+
+ earlyExit = QX11Info::peekEventQueue([](xcb_generic_event_t *event, void *data) {
+ return static_cast<PeekerTest *>(data)->sanityCheckPeeking(event);
+ }, this, QX11Info::PeekFromCachedIndex, m_peekerFirstId);
+
+ if (m_countWithCaching == -1)
+ QVERIFY2(!earlyExit, "Unexpected return value for an empty queue");
+ }
+
+ QVERIFY2(!m_foundFirstEventAgain,
+ "Found the same notify event twice, maybe broken index cache?");
+ QVERIFY2(!m_foundSecondBeforeFirst, "Found second event before first");
+ QVERIFY2(!earlyExit,
+ "Unexpected return value for a peeker that always scans to the end of the queue");
+
+ found = QX11Info::peekEventQueue(checkForPropertyNotifyByAtom, &m_atomFirst,
+ QX11Info::PeekDefault, m_peekerFirstId);
+ QVERIFY2(found, "Failed to find m_atomFirst, when peeking from start and ignoring "
+ "the cached index associated with the passed in peeker id");
+ // The above call updated index cache for m_peekerFirstId to the position of
+ // event(m_atomFirst) + 1
+ found = QX11Info::peekEventQueue(checkForPropertyNotifyByAtom, &m_atomSecond,
+ QX11Info::PeekFromCachedIndex, m_peekerFirstId);
+ QVERIFY2(found, "Unexpectedly failed to find m_atomSecond");
+
+ QVERIFY(m_indexFirst <= m_countWithCaching);
+ QVERIFY(m_indexSecond <= m_countWithCaching);
+ QVERIFY(m_indexFirst < m_indexSecond);
+ QX11Info::peekEventQueue([](xcb_generic_event_t *, void *data) {
+ static_cast<PeekerTest *>(data)->m_countFromStart++;
+ return false;
+ }, this);
+ QVERIFY(m_countWithCaching <= m_countFromStart);
+ found = QX11Info::peekEventQueue(checkForPropertyNotifyByAtom, &m_atomFirst,
+ QX11Info::PeekFromCachedIndex, m_peekerSecondId);
+ QVERIFY2(found, "m_peekerSecondId failed to find the event");
+
+ // Remove peeker id from within the peeker while using it for peeking
+ QX11Info::peekEventQueue([](xcb_generic_event_t *, void *data) {
+ PeekerTest *obj = static_cast<PeekerTest *>(data);
+ QX11Info::removePeekerId(obj->m_peekerSecondId);
+ return true;
+ }, this, QX11Info::PeekFromCachedIndex, m_peekerSecondId);
+ // Check that it really has been removed from the cache
+ bool ok = QX11Info::peekEventQueue([](xcb_generic_event_t *, void *) {
+ return true;
+ }, nullptr, QX11Info::PeekFromCachedIndex, m_peekerSecondId);
+ QVERIFY2(!ok, "Unexpected return value when attempting to peek from cached "
+ "index when peeker id has been removed from the cache");
+
+ // Sanity check other input combinations
+ QVERIFY(!QX11Info::removePeekerId(-99));
+ ok = QX11Info::peekEventQueue([](xcb_generic_event_t *, void *) {
+ return true;
+ }, nullptr, QX11Info::PeekFromCachedIndex, -100);
+ QVERIFY2(!ok, "PeekFromCachedIndex with invalid peeker id unexpectedly succeeded");
+ ok = QX11Info::peekEventQueue([](xcb_generic_event_t *, void *) {
+ return true;
+ }, nullptr, QX11Info::PeekDefault, -100);
+ QVERIFY2(!ok, "PeekDefault with invalid peeker id unexpectedly succeeded");
+ ok = QX11Info::peekEventQueue([](xcb_generic_event_t *, void *) {
+ return true;
+ }, nullptr, QX11Info::PeekFromCachedIndex);
+ QVERIFY2(!ok, "PeekFromCachedIndex without peeker id unexpectedly succeeded");
+ ok = QX11Info::peekEventQueue([](xcb_generic_event_t *, void *) {
+ return true;
+ }, nullptr, QX11Info::PeekDefault);
+ QVERIFY2(ok, "PeekDefault without peeker id unexpectedly failed");
+
+ QCoreApplication::processEvents(); // Flush buffered events
+
+ found = QX11Info::peekEventQueue(checkForPropertyNotifyByAtom, &m_atomFirst);
+ QVERIFY2(!found, "Found m_atomFirst in the queue after flushing");
+ found = QX11Info::peekEventQueue(checkForPropertyNotifyByAtom, &m_atomSecond);
+ QVERIFY2(!found, "Found m_atomSecond in the queue after flushing");
+
+ QVERIFY(QX11Info::removePeekerId(m_peekerFirstId));
+ QVERIFY2(!QX11Info::removePeekerId(m_peekerFirstId),
+ "Removing the same peeker id twice unexpectedly succeeded");
+#if 0
+ qDebug() << "Buffered event queue size (caching) : " << m_countWithCaching + 1;
+ qDebug() << "Buffered event queue size (from start) : " << m_countFromStart + 1;
+ qDebug() << "PropertyNotify[FIRST] at : " << m_indexFirst;
+ qDebug() << "PropertyNotify[SECOND] at : " << m_indexSecond;
+#endif
+ }
+
+private:
+ xcb_atom_t m_atomFirst = -1;
+ xcb_atom_t m_atomSecond = -1;
+ qint32 m_peekerFirstId = -1;
+ qint32 m_peekerSecondId = -1;
+ qint32 m_indexFirst = -1;
+ qint32 m_indexSecond = -1;
+ bool m_foundFirstEventAgain = false;
+ qint32 m_countWithCaching = -1;
+ qint32 m_countFromStart = -1;
+ bool m_ignoreSubsequentExpose = false;
+ bool m_foundSecondBeforeFirst = false;
+};
+
+void tst_QX11Info::peeker()
+{
+ int argc = 0;
+ QGuiApplication app(argc, 0);
+
+ PeekerTest test;
+ test.show();
+
+ QVERIFY(QTest::qWaitForWindowExposed(&test));
+}
+
QTEST_APPLESS_MAIN(tst_QX11Info)
#include "tst_qx11info.moc"
diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro
new file mode 100644
index 0000000..1ef0dcd
--- /dev/null
+++ b/tests/manual/manual.pro
@@ -0,0 +1,3 @@
+TEMPLATE = subdirs
+
+SUBDIRS = peeker
diff --git a/tests/manual/peeker/main.cpp b/tests/manual/peeker/main.cpp
new file mode 100644
index 0000000..8c7b280
--- /dev/null
+++ b/tests/manual/peeker/main.cpp
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtGui>
+#include <QtX11Extras/QX11Info>
+
+class IntensivePainter : public QWindow
+{
+public:
+ IntensivePainter() : m_backingStore(new QBackingStore(this))
+ { setGeometry(100, 100, 900, 600); }
+
+protected:
+ bool interrupted() const { return m_stopPainting; }
+ void stopPainting() { m_stopPainting = true; }
+ void beginPaining() { m_stopPainting = false; }
+
+ void exposeEvent(QExposeEvent *)
+ {
+ bool useGreenColor = false;
+ QRect rect(0, 0, width(), height());
+ m_backingStore->resize(QSize(width(), height()));
+
+ qint32 peekerId = QX11Info::generatePeekerId();
+ if (peekerId == -1) {
+ qWarning() << "Internal error: QX11Info::generatePeekerId() returned -1";
+ exit(EXIT_FAILURE);
+ }
+
+ while (!interrupted()) { // Begin a long operation
+ m_backingStore->beginPaint(rect);
+ QPaintDevice *device = m_backingStore->paintDevice();
+ QPainter painter(device);
+ painter.fillRect(rect, useGreenColor ? Qt::green : Qt::red);
+ useGreenColor = !useGreenColor;
+ painter.drawText(rect, Qt::AlignCenter, QStringLiteral("Press any key to exit.\n"
+ "(make sure the window is in focus when testing)\n\n If key press does not "
+ "immediately exit the application,\n then that should be considered a bug/regression."
+ "\n\n A demo might appear frozen to some window managers\n and therefore "
+ "might get grayed out (e.g Unity), this is the expected behavior.\n On KWin, for "
+ "example, this demo does not get grayed out.\n Resizing the window on any system will "
+ "cause rendering artefacts, that is not a bug,\n but simply the way the test is "
+ "implemented."));
+ m_backingStore->endPaint();
+ m_backingStore->flush(rect);
+ QThread::msleep(500); // Reduce the speed of re-painting (blinking)
+
+ QX11Info::peekEventQueue([](xcb_generic_event_t *event, void *data) {
+ bool isKeyPress = (event->response_type & ~0x80) == XCB_KEY_PRESS;
+ if (isKeyPress) {
+ IntensivePainter *painter = static_cast<IntensivePainter *>(data);
+ painter->stopPainting();
+ }
+ return isKeyPress;
+ }, this, QX11Info::PeekOption::PeekFromCachedIndex, peekerId);
+ }
+
+ QX11Info::removePeekerId(peekerId);
+ exit(EXIT_SUCCESS);
+ }
+
+private:
+ QBackingStore *m_backingStore;
+ bool m_stopPainting = false;
+};
+
+int main(int argc, char** argv)
+{
+ QGuiApplication app(argc, argv);
+
+ IntensivePainter painterWindow;
+ painterWindow.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/peeker/peeker.pro b/tests/manual/peeker/peeker.pro
new file mode 100644
index 0000000..aef58cd
--- /dev/null
+++ b/tests/manual/peeker/peeker.pro
@@ -0,0 +1,6 @@
+QT += x11extras
+
+TARGET = peeker
+TEMPLATE = app
+
+SOURCES += main.cpp
diff --git a/tests/tests.pro b/tests/tests.pro
index d1a5e17..3febcf1 100644
--- a/tests/tests.pro
+++ b/tests/tests.pro
@@ -2,5 +2,5 @@ TEMPLATE = subdirs
QT_FOR_CONFIG += gui-private
qtConfig(xcb) {
- SUBDIRS += auto
+ SUBDIRS += auto manual
}