File indexing completed on 2024-04-14 03:56:53

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