| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Copyright (C) 2016 David Faure <david.faure@kdab.com> |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the test suite of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** 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 General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** 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-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QtGui> |
| #include <QtTest/QtTest> |
| |
| #include <QApplication> |
| #include <QX11Info> |
| |
| class tst_QX11Info : public QObject |
| { |
| Q_OBJECT |
| |
| private slots: |
| void staticFunctionsBeforeQApplication(); |
| void startupId(); |
| void isPlatformX11(); |
| void appTime(); |
| void peeker(); |
| }; |
| |
| void tst_QX11Info::staticFunctionsBeforeQApplication() |
| { |
| QVERIFY(!QApplication::instance()); |
| |
| // none of these functions should crash if QApplication hasn't |
| // been constructed |
| |
| Display *display = QX11Info::display(); |
| QCOMPARE(display, (Display *)0); |
| |
| #if 0 |
| const char *appClass = QX11Info::appClass(); |
| QCOMPARE(appClass, (const char *)0); |
| #endif |
| int appScreen = QX11Info::appScreen(); |
| QCOMPARE(appScreen, 0); |
| #if 0 |
| int appDepth = QX11Info::appDepth(); |
| QCOMPARE(appDepth, 32); |
| int appCells = QX11Info::appCells(); |
| QCOMPARE(appCells, 0); |
| Qt::HANDLE appColormap = QX11Info::appColormap(); |
| QCOMPARE(appColormap, static_cast<Qt::HANDLE>(0)); |
| void *appVisual = QX11Info::appVisual(); |
| QCOMPARE(appVisual, static_cast<void *>(0)); |
| #endif |
| unsigned long appRootWindow = QX11Info::appRootWindow(); |
| QCOMPARE(appRootWindow, static_cast<unsigned long>(0)); |
| |
| #if 0 |
| bool appDefaultColormap = QX11Info::appDefaultColormap(); |
| QCOMPARE(appDefaultColormap, true); |
| bool appDefaultVisual = QX11Info::appDefaultVisual(); |
| QCOMPARE(appDefaultVisual, true); |
| #endif |
| |
| int appDpiX = QX11Info::appDpiX(); |
| int appDpiY = QX11Info::appDpiY(); |
| QCOMPARE(appDpiX, 75); |
| QCOMPARE(appDpiY, 75); |
| |
| unsigned long appTime = QX11Info::appTime(); |
| unsigned long appUserTime = QX11Info::appUserTime(); |
| QCOMPARE(appTime, 0ul); |
| QCOMPARE(appUserTime, 0ul); |
| // setApp*Time do nothing without QApplication |
| QX11Info::setAppTime(1234); |
| QX11Info::setAppUserTime(5678); |
| appTime = QX11Info::appTime(); |
| appUserTime = QX11Info::appUserTime(); |
| QCOMPARE(appTime, 0ul); |
| QCOMPARE(appTime, 0ul); |
| |
| QX11Info::isCompositingManagerRunning(); |
| } |
| |
| static const char idFromEnv[] = "startupid_TIME123456"; |
| void initialize() |
| { |
| qputenv("DESKTOP_STARTUP_ID", idFromEnv); |
| } |
| Q_CONSTRUCTOR_FUNCTION(initialize) |
| |
| void tst_QX11Info::startupId() |
| { |
| int argc = 0; |
| QApplication app(argc, 0); |
| |
| // This relies on the fact that no widget was shown yet, |
| // so please make sure this method is always the first test. |
| QCOMPARE(QString(QX11Info::nextStartupId()), QString(idFromEnv)); |
| QWidget w; |
| w.show(); |
| QVERIFY(QX11Info::nextStartupId().isEmpty()); |
| |
| QByteArray idSecondWindow = "startupid2_TIME234567"; |
| QX11Info::setNextStartupId(idSecondWindow); |
| QCOMPARE(QX11Info::nextStartupId(), idSecondWindow); |
| |
| QWidget w2; |
| w2.show(); |
| QVERIFY(QX11Info::nextStartupId().isEmpty()); |
| } |
| |
| void tst_QX11Info::isPlatformX11() |
| { |
| int argc = 0; |
| QApplication app(argc, 0); |
| |
| QVERIFY(QX11Info::isPlatformX11()); |
| } |
| |
| void tst_QX11Info::appTime() |
| { |
| int argc = 0; |
| QApplication app(argc, 0); |
| |
| // No X11 event received yet |
| QCOMPARE(QX11Info::appTime(), 0ul); |
| QCOMPARE(QX11Info::appUserTime(), 0ul); |
| |
| // Trigger some X11 events |
| QWindow window; |
| window.show(); |
| QTest::qWait(100); |
| QVERIFY(QX11Info::appTime() > 0); |
| |
| unsigned long t0 = QX11Info::appTime(); |
| unsigned long t1 = t0 + 1; |
| QX11Info::setAppTime(t1); |
| QCOMPARE(QX11Info::appTime(), t1); |
| |
| unsigned long u0 = QX11Info::appUserTime(); |
| unsigned long u1 = u0 + 1; |
| QX11Info::setAppUserTime(u1); |
| 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" |