diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index cc23d1ff884..217ee4a4af2 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -967,6 +967,11 @@ qt_internal_extend_target(Gui CONDITION UNIX AND (QT_FEATURE_xcb OR NOT UIKIT) platform/unix/qgenericunixservices.cpp platform/unix/qgenericunixservices_p.h ) +qt_internal_extend_target(Gui CONDITION UNIX AND (QT_FEATURE_xcb) + SOURCES + platform/unix/qtx11extras.cpp platform/unix/qtx11extras_p.h +) + qt_internal_extend_target(Gui CONDITION TARGET Qt::DBus AND UNIX AND (QT_FEATURE_xcb OR NOT UIKIT) LIBRARIES Qt::DBus diff --git a/src/gui/platform/unix/qtx11extras.cpp b/src/gui/platform/unix/qtx11extras.cpp new file mode 100644 index 00000000000..09bcb81bae6 --- /dev/null +++ b/src/gui/platform/unix/qtx11extras.cpp @@ -0,0 +1,548 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2016 Richard Moore +** Copyright (C) 2016 David Faure +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui 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 "qtx11extras_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static QScreen *findScreenForVirtualDesktop(int virtualDesktopNumber) +{ + const auto screens = QGuiApplication::screens(); + for (QScreen *screen : screens) { + auto *qxcbScreen = dynamic_cast(screen->handle()); + if (qxcbScreen && qxcbScreen->virtualDesktopNumber() == virtualDesktopNumber) + return screen; + } + return nullptr; +} + +/*! + \class QX11Info + \inmodule QtGui + \since 6.2 + \internal + + \brief Provides information about the X display configuration. + + The class provides two APIs: a set of non-static functions that + provide information about a specific widget or pixmap, and a set + of static functions that provide the default information for the + application. + + \warning This class is only available on X11. For querying + per-screen information in a portable way, use QDesktopWidget. +*/ + +/*! + Constructs an empty QX11Info object. +*/ +QX11Info::QX11Info() +{ +} + +/*! + Returns true if the application is currently running on X11. + + \since 6.2 + */ +bool QX11Info::isPlatformX11() +{ + return QGuiApplication::platformName() == QLatin1String("xcb"); +} + +/*! + Returns the horizontal resolution of the given \a screen in terms of the + number of dots per inch. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. + + \sa appDpiY() +*/ +int QX11Info::appDpiX(int screen) +{ + if (screen == -1) { + const QScreen *scr = QGuiApplication::primaryScreen(); + if (!scr) + return 75; + return qRound(scr->logicalDotsPerInchX()); + } + + QScreen *scr = findScreenForVirtualDesktop(screen); + if (!scr) + return 0; + + return scr->logicalDotsPerInchX(); +} + +/*! + Returns the vertical resolution of the given \a screen in terms of the + number of dots per inch. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. + + \sa appDpiX() +*/ +int QX11Info::appDpiY(int screen) +{ + if (screen == -1) { + const QScreen *scr = QGuiApplication::primaryScreen(); + if (!scr) + return 75; + return qRound(scr->logicalDotsPerInchY()); + } + + QScreen *scr = findScreenForVirtualDesktop(screen); + if (!scr) + return 0; + + return scr->logicalDotsPerInchY(); +} + +/*! + Returns a handle for the applications root window on the given \a screen. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. +*/ +quint32 QX11Info::appRootWindow(int screen) +{ + if (!qApp) + return 0; + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return 0; + QScreen *scr = screen == -1 ? QGuiApplication::primaryScreen() : findScreenForVirtualDesktop(screen); + if (!scr) + return 0; + return static_cast(reinterpret_cast(native->nativeResourceForScreen(QByteArrayLiteral("rootwindow"), scr))); +} + +/*! + Returns the number of the screen where the application is being + displayed. + + This method refers to screens in the original X11 meaning with a + different DISPLAY environment variable per screen. + This information is only useful if your application needs to know + on which X screen it is running. + + In a typical multi-head configuration, multiple physical monitors + are combined in one X11 screen. This means this method returns the + same number for each of the physical monitors. In such a setup you + are interested in the monitor information as provided by the X11 + RandR extension. This is available through QDesktopWidget and QScreen. + + \sa display() +*/ +int QX11Info::appScreen() +{ + if (!qApp) + return 0; + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return 0; + return reinterpret_cast(native->nativeResourceForIntegration(QByteArrayLiteral("x11screen"))); +} + +/*! + Returns the X11 time. + + \sa setAppTime(), appUserTime() +*/ +quint32 QX11Info::appTime() +{ + if (!qApp) + return 0; + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return 0; + QScreen* screen = QGuiApplication::primaryScreen(); + return static_cast(reinterpret_cast(native->nativeResourceForScreen("apptime", screen))); +} + +/*! + Returns the X11 user time. + + \sa setAppUserTime(), appTime() +*/ +quint32 QX11Info::appUserTime() +{ + if (!qApp) + return 0; + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return 0; + QScreen* screen = QGuiApplication::primaryScreen(); + return static_cast(reinterpret_cast(native->nativeResourceForScreen("appusertime", screen))); +} + +/*! + Sets the X11 time to the value specified by \a time. + + \sa appTime(), setAppUserTime() +*/ +void QX11Info::setAppTime(quint32 time) +{ + if (!qApp) + return; + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return; + typedef void (*SetAppTimeFunc)(QScreen *, xcb_timestamp_t); + QScreen* screen = QGuiApplication::primaryScreen(); + SetAppTimeFunc func = reinterpret_cast(reinterpret_cast(native->nativeResourceFunctionForScreen("setapptime"))); + if (func) + func(screen, time); + else + qWarning("Internal error: QPA plugin doesn't implement setAppTime"); +} + +/*! + Sets the X11 user time as specified by \a time. + + \sa appUserTime(), setAppTime() +*/ +void QX11Info::setAppUserTime(quint32 time) +{ + if (!qApp) + return; + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return; + typedef void (*SetAppUserTimeFunc)(QScreen *, xcb_timestamp_t); + QScreen* screen = QGuiApplication::primaryScreen(); + SetAppUserTimeFunc func = reinterpret_cast(reinterpret_cast(native->nativeResourceFunctionForScreen("setappusertime"))); + if (func) + func(screen, time); + else + qWarning("Internal error: QPA plugin doesn't implement setAppUserTime"); +} + +/*! + Fetches the current X11 time stamp from the X Server. + + This method creates a property notify event and blocks till it is + received back from the X Server. +*/ +quint32 QX11Info::getTimestamp() +{ + if (!qApp) + return 0; + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return 0; + QScreen* screen = QGuiApplication::primaryScreen(); + return static_cast(reinterpret_cast(native->nativeResourceForScreen("gettimestamp", screen))); +} + +/*! + Returns the startup ID that will be used for the next window to be shown by this process. + + After the next window is shown, the next startup ID will be empty. + + http://standards.freedesktop.org/startup-notification-spec/startup-notification-latest.txt + + \sa setNextStartupId() +*/ +QByteArray QX11Info::nextStartupId() +{ + if (!qApp) + return QByteArray(); + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return QByteArray(); + return static_cast(native->nativeResourceForIntegration("startupid")); +} + +/*! + Sets the next startup ID to \a id. + + This is the startup ID that will be used for the next window to be shown by this process. + + The startup ID of the first window comes from the environment variable DESKTOP_STARTUP_ID. + This method is useful for subsequent windows, when the request comes from another process + (e.g. via DBus). + + \sa nextStartupId() +*/ +void QX11Info::setNextStartupId(const QByteArray &id) +{ + if (!qApp) + return; + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return; + typedef void (*SetStartupIdFunc)(const char*); + SetStartupIdFunc func = reinterpret_cast(reinterpret_cast(native->nativeResourceFunctionForIntegration("setstartupid"))); + if (func) + func(id.constData()); + else + qWarning("Internal error: QPA plugin doesn't implement setStartupId"); +} + +/*! + Returns the default display for the application. + + \sa appScreen() +*/ +Display *QX11Info::display() +{ + if (!qApp) + return nullptr; + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return nullptr; + + void *display = native->nativeResourceForIntegration(QByteArray("display")); + return reinterpret_cast(display); +} + +/*! + Returns the default XCB connection for the application. + + \sa display() +*/ +xcb_connection_t *QX11Info::connection() +{ + if (!qApp) + return nullptr; + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return nullptr; + + void *connection = native->nativeResourceForIntegration(QByteArray("connection")); + return reinterpret_cast(connection); +} + +/*! + Returns true if there is a compositing manager running for the connection + attached to \a screen. + + If \a screen equals -1, the application's primary screen is used. +*/ +bool QX11Info::isCompositingManagerRunning(int screen) +{ + if (!qApp) + return false; + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return false; + + QScreen *scr = screen == -1 ? QGuiApplication::primaryScreen() : findScreenForVirtualDesktop(screen); + if (!scr) { + qWarning() << "isCompositingManagerRunning: Could not find screen number" << screen; + return false; + } + + 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() +*/ +qint32 QX11Info::generatePeekerId() +{ + if (!qApp) + return -1; + QPlatformNativeInterface *native = qApp->platformNativeInterface(); + if (!native) + return -1; + + typedef qint32 (*GeneratePeekerIdFunc)(void); + GeneratePeekerIdFunc generatepeekerid = reinterpret_cast( + reinterpret_cast(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() +*/ +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( + reinterpret_cast(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(). +*/ + +/*! + \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. +*/ + +/*! + \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() +*/ +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( + reinterpret_cast(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/gui/platform/unix/qtx11extras_p.h b/src/gui/platform/unix/qtx11extras_p.h new file mode 100644 index 00000000000..85b26b1462a --- /dev/null +++ b/src/gui/platform/unix/qtx11extras_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui 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$ +** +****************************************************************************/ + +#ifndef QTX11EXTRAS_P_H +#define QTX11EXTRAS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +typedef struct _XDisplay Display; + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QX11Info +{ +public: + enum PeekOption { + PeekDefault = 0, + PeekFromCachedIndex = 1 + }; + Q_DECLARE_FLAGS(PeekOptions, PeekOption) + + static bool isPlatformX11(); + + static int appDpiX(int screen=-1); + static int appDpiY(int screen=-1); + + static quint32 appRootWindow(int screen=-1); + static int appScreen(); + + static quint32 appTime(); + static quint32 appUserTime(); + + static void setAppTime(quint32 time); + static void setAppUserTime(quint32 time); + + static quint32 getTimestamp(); + + static QByteArray nextStartupId(); + static void setNextStartupId(const QByteArray &id); + + static Display *display(); + static xcb_connection_t *connection(); + + 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 // QTX11EXTRAS_P_H + diff --git a/tests/auto/gui/CMakeLists.txt b/tests/auto/gui/CMakeLists.txt index ad6bb07001a..7ba124952db 100644 --- a/tests/auto/gui/CMakeLists.txt +++ b/tests/auto/gui/CMakeLists.txt @@ -17,3 +17,4 @@ endif() if(QT_FEATURE_vulkan AND NOT UIKIT) add_subdirectory(qvulkan) endif() +add_subdirectory(platform) diff --git a/tests/auto/gui/platform/CMakeLists.txt b/tests/auto/gui/platform/CMakeLists.txt new file mode 100644 index 00000000000..fc2d330fd75 --- /dev/null +++ b/tests/auto/gui/platform/CMakeLists.txt @@ -0,0 +1,3 @@ +if(QT_FEATURE_xcb) + add_subdirectory(qx11info) +endif() diff --git a/tests/auto/gui/platform/qx11info/CMakeLists.txt b/tests/auto/gui/platform/qx11info/CMakeLists.txt new file mode 100644 index 00000000000..c4ce1f91c82 --- /dev/null +++ b/tests/auto/gui/platform/qx11info/CMakeLists.txt @@ -0,0 +1,7 @@ +qt_internal_add_test(tst_qx11info + SOURCES + tst_qx11info.cpp + PUBLIC_LIBRARIES + Qt::GuiPrivate + XCB::XCB +) diff --git a/tests/auto/gui/platform/qx11info/tst_qx11info.cpp b/tests/auto/gui/platform/qx11info/tst_qx11info.cpp new file mode 100644 index 00000000000..0cd11c87acd --- /dev/null +++ b/tests/auto/gui/platform/qx11info/tst_qx11info.cpp @@ -0,0 +1,390 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 David Faure +** 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 +#include + +#include + +class tst_QX11Info : public QObject +{ + Q_OBJECT + +private slots: + void staticFunctionsBeforeQGuiApplication(); + void startupId(); + void isPlatformX11(); + void appTime(); + void peeker(); + void isCompositingManagerRunning(); +}; + +void tst_QX11Info::staticFunctionsBeforeQGuiApplication() +{ + QVERIFY(!QGuiApplication::instance()); + + // none of these functions should crash if QGuiApplication 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(0)); + void *appVisual = QX11Info::appVisual(); + QCOMPARE(appVisual, static_cast(0)); +#endif + unsigned long appRootWindow = QX11Info::appRootWindow(); + QCOMPARE(appRootWindow, static_cast(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 QGuiApplication + 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; + QGuiApplication 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)); + QWindow w; + w.show(); + QVERIFY(QX11Info::nextStartupId().isEmpty()); + + QByteArray idSecondWindow = "startupid2_TIME234567"; + QX11Info::setNextStartupId(idSecondWindow); + QCOMPARE(QX11Info::nextStartupId(), idSecondWindow); + + QWindow w2; + w2.show(); + QVERIFY(QX11Info::nextStartupId().isEmpty()); +} + +void tst_QX11Info::isPlatformX11() +{ + int argc = 0; + QGuiApplication app(argc, 0); + + QVERIFY(QX11Info::isPlatformX11()); +} + +void tst_QX11Info::appTime() +{ + int argc = 0; + QGuiApplication 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(event); + xcb_atom_t *atom = static_cast(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(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 *) override + { + 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(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(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(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)); +} + +void tst_QX11Info::isCompositingManagerRunning() +{ + int argc = 0; + QGuiApplication app(argc, 0); + const bool b = QX11Info::isCompositingManagerRunning(); + Q_UNUSED(b); + const bool b2 = QX11Info::isCompositingManagerRunning(0); + Q_UNUSED(b2); + // just check that it didn't crash (QTBUG-91913) +} + +QTEST_APPLESS_MAIN(tst_QX11Info) + +#include "tst_qx11info.moc"