File indexing completed on 2024-03-24 15:40:21

0001 /*
0002     SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "kwindowsystem.h"
0008 #include "kx11extras.h"
0009 #include "nettesthelper.h"
0010 #include "netwm.h"
0011 
0012 #include <QSignalSpy>
0013 #include <QWidget>
0014 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0015 #include <private/qtx11extras_p.h>
0016 #else
0017 #include <QX11Info>
0018 #endif
0019 
0020 #include <qtest_widgets.h>
0021 Q_DECLARE_METATYPE(WId)
0022 Q_DECLARE_METATYPE(NET::Properties)
0023 Q_DECLARE_METATYPE(NET::Properties2)
0024 Q_DECLARE_METATYPE(const unsigned long *)
0025 
0026 class KWindowSystemX11Test : public QObject
0027 {
0028     Q_OBJECT
0029 private Q_SLOTS:
0030     void initTestCase();
0031     // needs to be first test, would fail if run after others (X11)
0032     void testActiveWindowChanged();
0033     void testWindowAdded();
0034     void testWindowRemoved();
0035     void testDesktopChanged();
0036     void testNumberOfDesktopsChanged();
0037     void testDesktopNamesChanged();
0038     void testShowingDesktopChanged();
0039     void testSetShowingDesktop();
0040     void testWorkAreaChanged();
0041     void testWindowTitleChanged();
0042     void testMinimizeWindow();
0043     void testPlatformX11();
0044 };
0045 
0046 void KWindowSystemX11Test::initTestCase()
0047 {
0048     QCoreApplication::setAttribute(Qt::AA_ForceRasterWidgets);
0049 }
0050 
0051 void KWindowSystemX11Test::testActiveWindowChanged()
0052 {
0053     qRegisterMetaType<WId>("WId");
0054     QSignalSpy spy(KX11Extras::self(), &KX11Extras::activeWindowChanged);
0055 
0056     std::unique_ptr<QWidget> widget(new QWidget);
0057     widget->show();
0058 
0059     QVERIFY(spy.wait());
0060     QCOMPARE(spy.count(), 1);
0061     QCOMPARE(spy.first().at(0).toULongLong(), widget->winId());
0062     QCOMPARE(KX11Extras::activeWindow(), widget->winId());
0063 }
0064 
0065 void KWindowSystemX11Test::testWindowAdded()
0066 {
0067     qRegisterMetaType<WId>("WId");
0068     QSignalSpy spy(KX11Extras::self(), &KX11Extras::windowAdded);
0069     QSignalSpy stackingOrderSpy(KX11Extras::self(), &KX11Extras::stackingOrderChanged);
0070     std::unique_ptr<QWidget> widget(new QWidget);
0071     widget->show();
0072     QVERIFY(QTest::qWaitForWindowExposed(widget.get()));
0073     QVERIFY(spy.count() > 0);
0074     bool hasWId = false;
0075     for (auto it = spy.constBegin(); it != spy.constEnd(); ++it) {
0076         if ((*it).isEmpty()) {
0077             continue;
0078         }
0079         QCOMPARE((*it).count(), 1);
0080         hasWId = (*it).at(0).toULongLong() == widget->winId();
0081         if (hasWId) {
0082             break;
0083         }
0084     }
0085     QVERIFY(hasWId);
0086     QVERIFY(KX11Extras::hasWId(widget->winId()));
0087     QVERIFY(!stackingOrderSpy.isEmpty());
0088 }
0089 
0090 void KWindowSystemX11Test::testWindowRemoved()
0091 {
0092     qRegisterMetaType<WId>("WId");
0093     std::unique_ptr<QWidget> widget(new QWidget);
0094     widget->show();
0095     QVERIFY(QTest::qWaitForWindowExposed(widget.get()));
0096     QVERIFY(KX11Extras::hasWId(widget->winId()));
0097 
0098     QSignalSpy spy(KX11Extras::self(), &KX11Extras::windowRemoved);
0099     widget->hide();
0100     spy.wait(1000);
0101     QCOMPARE(spy.first().at(0).toULongLong(), widget->winId());
0102     QVERIFY(!KX11Extras::hasWId(widget->winId()));
0103 }
0104 
0105 void KWindowSystemX11Test::testDesktopChanged()
0106 {
0107     // This test requires a running NETWM-compliant window manager
0108     if (KX11Extras::numberOfDesktops() == 1) {
0109         QSKIP("At least two virtual desktops are required to test desktop changed");
0110     }
0111     const int current = KX11Extras::currentDesktop();
0112 
0113     QSignalSpy spy(KX11Extras::self(), &KX11Extras::currentDesktopChanged);
0114     int newDesktop = current + 1;
0115     if (newDesktop > KX11Extras::numberOfDesktops()) {
0116         newDesktop = 1;
0117     }
0118     KX11Extras::setCurrentDesktop(newDesktop);
0119     QVERIFY(spy.wait());
0120     QCOMPARE(KX11Extras::currentDesktop(), newDesktop);
0121     QCOMPARE(spy.count(), 1);
0122     QCOMPARE(spy.first().at(0).toInt(), newDesktop);
0123     spy.clear();
0124 
0125     // setting to current desktop should not change anything
0126     KX11Extras::setCurrentDesktop(newDesktop);
0127 
0128     // set back for clean state
0129     KX11Extras::setCurrentDesktop(current);
0130     QVERIFY(spy.wait());
0131     QCOMPARE(KX11Extras::currentDesktop(), current);
0132     QCOMPARE(spy.count(), 1);
0133     QCOMPARE(spy.first().at(0).toInt(), current);
0134 }
0135 
0136 void KWindowSystemX11Test::testNumberOfDesktopsChanged()
0137 {
0138     // This test requires a running NETWM-compliant window manager
0139     const int oldNumber = KX11Extras::numberOfDesktops();
0140     QSignalSpy spy(KX11Extras::self(), &KX11Extras::numberOfDesktopsChanged);
0141 
0142     // KWin has arbitrary max number of 20 desktops, so don't fail the test if we use +1
0143     const int newNumber = oldNumber < 20 ? oldNumber + 1 : oldNumber - 1;
0144 
0145     NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops, NET::Properties2());
0146     info.setNumberOfDesktops(newNumber);
0147 
0148     QVERIFY(spy.wait());
0149     QCOMPARE(KX11Extras::numberOfDesktops(), newNumber);
0150     QCOMPARE(spy.count(), 1);
0151     QCOMPARE(spy.first().at(0).toInt(), newNumber);
0152     spy.clear();
0153 
0154     // setting to same number should not change
0155     info.setNumberOfDesktops(newNumber);
0156 
0157     // set back for clean state
0158     info.setNumberOfDesktops(oldNumber);
0159     QVERIFY(spy.wait());
0160     QCOMPARE(KX11Extras::numberOfDesktops(), oldNumber);
0161     QCOMPARE(spy.count(), 1);
0162     QCOMPARE(spy.first().at(0).toInt(), oldNumber);
0163 }
0164 
0165 void KWindowSystemX11Test::testDesktopNamesChanged()
0166 {
0167     // This test requires a running NETWM-compliant window manager
0168     const QString origName = KX11Extras::desktopName(KX11Extras::currentDesktop());
0169     QSignalSpy spy(KX11Extras::self(), &KX11Extras::desktopNamesChanged);
0170 
0171     const QString testName = QStringLiteral("testFooBar");
0172 
0173     KX11Extras::setDesktopName(KX11Extras::currentDesktop(), testName);
0174     QVERIFY(spy.wait());
0175     QCOMPARE(KX11Extras::desktopName(KX11Extras::currentDesktop()), testName);
0176     QCOMPARE(spy.count(), 1);
0177     spy.clear();
0178 
0179     QX11Info::setAppTime(QX11Info::getTimestamp());
0180 
0181     // setting back to clean state
0182     KX11Extras::setDesktopName(KX11Extras::currentDesktop(), origName);
0183     QVERIFY(spy.wait());
0184     QCOMPARE(KX11Extras::desktopName(KX11Extras::currentDesktop()), origName);
0185     QCOMPARE(spy.count(), 1);
0186 }
0187 
0188 void KWindowSystemX11Test::testShowingDesktopChanged()
0189 {
0190     QX11Info::setAppTime(QX11Info::getTimestamp());
0191     const bool showingDesktop = KWindowSystem::showingDesktop();
0192 
0193     QSignalSpy spy(KWindowSystem::self(), &KWindowSystem::showingDesktopChanged);
0194 
0195     NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::WM2ShowingDesktop);
0196     info.setShowingDesktop(!showingDesktop);
0197 
0198     QVERIFY(spy.wait());
0199     QCOMPARE(spy.count(), 1);
0200     QCOMPARE(spy.first().at(0).toBool(), !showingDesktop);
0201     QCOMPARE(KWindowSystem::showingDesktop(), !showingDesktop);
0202     spy.clear();
0203 
0204     QX11Info::setAppTime(QX11Info::getTimestamp());
0205 
0206     // setting again should not change
0207     info.setShowingDesktop(!showingDesktop);
0208 
0209     // setting back to clean state
0210     info.setShowingDesktop(showingDesktop);
0211     QVERIFY(spy.wait(100));
0212     QCOMPARE(spy.count(), 1);
0213     QCOMPARE(spy.first().at(0).toBool(), showingDesktop);
0214     QCOMPARE(KWindowSystem::showingDesktop(), showingDesktop);
0215 }
0216 
0217 void KWindowSystemX11Test::testSetShowingDesktop()
0218 {
0219     QSignalSpy spy(KWindowSystem::self(), &KWindowSystem::showingDesktopChanged);
0220     const bool showingDesktop = KWindowSystem::showingDesktop();
0221 
0222     // setting the same state shouldn't change it
0223     QX11Info::setAppTime(QX11Info::getTimestamp());
0224     KWindowSystem::setShowingDesktop(showingDesktop);
0225     QCOMPARE(spy.wait(), false); // spy.wait() waits for 5s
0226     QCOMPARE(KWindowSystem::showingDesktop(), showingDesktop);
0227     spy.clear();
0228 
0229     // set opposite state
0230     QX11Info::setAppTime(QX11Info::getTimestamp());
0231     KWindowSystem::setShowingDesktop(!showingDesktop);
0232     QVERIFY(spy.wait());
0233     QCOMPARE(KWindowSystem::showingDesktop(), !showingDesktop);
0234     spy.clear();
0235 
0236     // setting back to clean state
0237     QX11Info::setAppTime(QX11Info::getTimestamp());
0238     KWindowSystem::setShowingDesktop(showingDesktop);
0239     QVERIFY(spy.wait());
0240     QCOMPARE(KWindowSystem::showingDesktop(), showingDesktop);
0241     spy.clear();
0242 }
0243 
0244 void KWindowSystemX11Test::testWorkAreaChanged()
0245 {
0246     // if there are multiple screens this test can fail as workarea is not multi screen aware
0247     QSignalSpy spy(KX11Extras::self(), &KX11Extras::workAreaChanged);
0248     QSignalSpy strutSpy(KX11Extras::self(), &KX11Extras::strutChanged);
0249 
0250     QWidget widget;
0251     widget.setGeometry(0, 0, 100, 10);
0252     widget.show();
0253 
0254     KX11Extras::setExtendedStrut(widget.winId(), 10, 0, 10, 0, 0, 0, 100, 0, 100, 0, 0, 0);
0255     QVERIFY(spy.wait());
0256     QVERIFY(!spy.isEmpty());
0257     QVERIFY(!strutSpy.isEmpty());
0258 }
0259 
0260 void KWindowSystemX11Test::testWindowTitleChanged()
0261 {
0262     qRegisterMetaType<WId>("WId");
0263     qRegisterMetaType<NET::Properties>("NET::Properties");
0264     qRegisterMetaType<NET::Properties2>("NET::Properties2");
0265     qRegisterMetaType<const unsigned long *>("const ulong*");
0266     QWidget widget;
0267     widget.setWindowTitle(QStringLiteral("foo"));
0268     widget.show();
0269     QVERIFY(QTest::qWaitForWindowExposed(&widget));
0270 
0271     // wait till the window is mapped, etc.
0272     QTest::qWait(200);
0273 
0274     QSignalSpy propertiesChangedSpy(KX11Extras::self(), &KX11Extras::windowChanged);
0275     QVERIFY(propertiesChangedSpy.isValid());
0276 
0277 #if KWINDOWSYSTEM_ENABLE_DEPRECATED_SINCE(5, 0)
0278     QSignalSpy propertyChangedSpy(KWindowSystem::self(), qOverload<WId, uint>(&KWindowSystem::windowChanged));
0279     QVERIFY(propertyChangedSpy.isValid());
0280 #endif
0281 
0282 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 80)
0283     QSignalSpy windowChangedSpy(KWindowSystem::self(), qOverload<WId>(&KWindowSystem::windowChanged));
0284     QVERIFY(windowChangedSpy.isValid());
0285 #endif
0286 
0287     widget.setWindowTitle(QStringLiteral("bar"));
0288     QX11Info::setAppTime(QX11Info::getTimestamp());
0289 
0290     int counter = 0;
0291     bool gotWMName = false;
0292     while (propertiesChangedSpy.wait() && counter < 10) {
0293         for (auto it = propertiesChangedSpy.constBegin(); it != propertiesChangedSpy.constEnd(); ++it) {
0294             if ((*it).isEmpty()) {
0295                 continue;
0296             }
0297             if ((*it).at(0).toULongLong() == widget.winId()) {
0298                 NET::Properties props = (*it).at(1).value<NET::Properties>();
0299                 if (props.testFlag(NET::WMName)) {
0300                     gotWMName = true;
0301                 }
0302             }
0303         }
0304         if (gotWMName) {
0305             break;
0306         }
0307         propertiesChangedSpy.clear();
0308         counter++;
0309     }
0310     QVERIFY(gotWMName);
0311 
0312 #if KWINDOWSYSTEM_ENABLE_DEPRECATED_SINCE(5, 0)
0313     gotWMName = false;
0314     QCOMPARE(propertyChangedSpy.isEmpty(), false);
0315     for (auto it = propertyChangedSpy.constBegin(); it != propertyChangedSpy.constEnd(); ++it) {
0316         if ((*it).isEmpty()) {
0317             continue;
0318         }
0319         if ((*it).at(0).toULongLong() == widget.winId()) {
0320             unsigned int props = (*it).at(1).value<unsigned int>();
0321             if (props & NET::WMName) {
0322                 gotWMName = true;
0323             }
0324         }
0325         if (gotWMName) {
0326             break;
0327         }
0328     }
0329     QVERIFY(gotWMName);
0330 #endif
0331 
0332 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 80)
0333     QCOMPARE(windowChangedSpy.isEmpty(), false);
0334     bool gotWindow = false;
0335     for (auto it = windowChangedSpy.constBegin(); it != windowChangedSpy.constEnd(); ++it) {
0336         if ((*it).isEmpty()) {
0337             continue;
0338         }
0339         if ((*it).at(0).toULongLong() == widget.winId()) {
0340             gotWindow = true;
0341         }
0342         if (gotWindow) {
0343             break;
0344         }
0345     }
0346     QVERIFY(gotWindow);
0347 #endif
0348 
0349     // now let's verify the info in KWindowInfo
0350     // we wait a little bit more as openbox is updating the visible name
0351     QTest::qWait(500);
0352     KWindowInfo info(widget.winId(), NET::WMName | NET::WMVisibleName | NET::WMVisibleIconName | NET::WMIconName, NET::Properties2());
0353     QVERIFY(info.valid());
0354     const QString expectedName = QStringLiteral("bar");
0355     QCOMPARE(info.name(), expectedName);
0356     QCOMPARE(info.visibleName(), expectedName);
0357     QCOMPARE(info.visibleIconName(), expectedName);
0358     QCOMPARE(info.iconName(), expectedName);
0359 }
0360 
0361 void KWindowSystemX11Test::testMinimizeWindow()
0362 {
0363     NETRootInfo rootInfo(QX11Info::connection(), NET::Supported | NET::SupportingWMCheck);
0364     if (qstrcmp(rootInfo.wmName(), "Openbox") != 0 && qstrcmp(rootInfo.wmName(), "KWin") != 0) {
0365         QSKIP("Test minimize window might not be supported on the used window manager.");
0366     }
0367     QWidget widget;
0368     widget.show();
0369     QVERIFY(QTest::qWaitForWindowExposed(&widget));
0370 
0371     KWindowInfo info(widget.winId(), NET::WMState | NET::XAWMState);
0372     QVERIFY(!info.isMinimized());
0373 
0374     KX11Extras::minimizeWindow(widget.winId());
0375     // create a roundtrip, updating minimized state is done by the window manager and wait a short time
0376     QX11Info::setAppTime(QX11Info::getTimestamp());
0377     QTest::qWait(200);
0378 
0379     KWindowInfo info2(widget.winId(), NET::WMState | NET::XAWMState);
0380     QVERIFY(info2.isMinimized());
0381 
0382     KX11Extras::unminimizeWindow(widget.winId());
0383     // create a roundtrip, updating minimized state is done by the window manager and wait a short time
0384     QX11Info::setAppTime(QX11Info::getTimestamp());
0385     QTest::qWait(200);
0386 
0387     KWindowInfo info3(widget.winId(), NET::WMState | NET::XAWMState);
0388     QVERIFY(!info3.isMinimized());
0389 }
0390 
0391 void KWindowSystemX11Test::testPlatformX11()
0392 {
0393     QCOMPARE(KWindowSystem::platform(), KWindowSystem::Platform::X11);
0394     QCOMPARE(KWindowSystem::isPlatformX11(), true);
0395     QCOMPARE(KWindowSystem::isPlatformWayland(), false);
0396 }
0397 
0398 QTEST_MAIN(KWindowSystemX11Test)
0399 
0400 #include "kwindowsystemx11test.moc"