File indexing completed on 2024-04-14 14:29:40

0001 /*
0002     SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
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 <QScreen>
0014 #include <QSignalSpy>
0015 #include <QSysInfo>
0016 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0017 #include <private/qtx11extras_p.h>
0018 #else
0019 #include <QX11Info>
0020 #endif
0021 #include <qtest_widgets.h>
0022 
0023 #include <xcb/xcb_icccm.h>
0024 
0025 #include <unistd.h>
0026 
0027 Q_DECLARE_METATYPE(WId)
0028 Q_DECLARE_METATYPE(NET::State)
0029 Q_DECLARE_METATYPE(NET::States)
0030 Q_DECLARE_METATYPE(NET::WindowType)
0031 Q_DECLARE_METATYPE(NET::WindowTypeMask)
0032 Q_DECLARE_METATYPE(NET::WindowTypes)
0033 Q_DECLARE_METATYPE(NET::Properties)
0034 Q_DECLARE_METATYPE(NET::Properties2)
0035 
0036 class KWindowInfoX11Test : public QObject
0037 {
0038     Q_OBJECT
0039 private Q_SLOTS:
0040     void initTestCase();
0041     void init();
0042     void cleanup();
0043 
0044     void testState_data();
0045     void testState();
0046 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 101)
0047     void testDemandsAttention();
0048 #endif
0049     void testMinimized();
0050     void testMappingState();
0051     void testWindowType_data();
0052     void testWindowType();
0053     void testDesktop();
0054     void testActivities();
0055     void testWindowClass();
0056     void testWindowRole();
0057     void testClientMachine();
0058     void testName();
0059     void testTransientFor();
0060     void testGroupLeader();
0061     void testExtendedStrut();
0062     void testGeometry();
0063     void testDesktopFileName();
0064     void testPid();
0065 
0066     // actionSupported is not tested as it's too window manager specific
0067     // we could write a test against KWin's behavior, but that would fail on
0068     // build.kde.org as we use OpenBox there.
0069 
0070 private:
0071     void showWidget(QWidget *widget);
0072     bool waitForWindow(QSignalSpy &spy, WId winId, NET::Properties property, NET::Properties2 properties2 = NET::Properties2()) const;
0073     bool verifyMinimized(WId window) const;
0074 
0075     std::unique_ptr<QWidget> window;
0076 };
0077 
0078 void KWindowInfoX11Test::initTestCase()
0079 {
0080     QCoreApplication::setAttribute(Qt::AA_ForceRasterWidgets);
0081     qRegisterMetaType<NET::Properties>();
0082     qRegisterMetaType<NET::Properties2>();
0083 }
0084 
0085 bool KWindowInfoX11Test::waitForWindow(QSignalSpy &spy, WId winId, NET::Properties property, NET::Properties2 property2) const
0086 {
0087     // we need to wait, window manager has to react and update the property.
0088     bool foundOurWindow = false;
0089     for (int i = 0; i < 10; ++i) {
0090         spy.wait(50);
0091         if (spy.isEmpty()) {
0092             continue;
0093         }
0094         for (auto it = spy.constBegin(); it != spy.constEnd(); ++it) {
0095             if (it->first().value<WId>() != winId) {
0096                 continue;
0097             }
0098             if (property != NET::Properties()) {
0099                 if (it->at(1).value<NET::Properties>() != property) {
0100                     continue;
0101                 }
0102             }
0103             if (property2 != NET::Properties2()) {
0104                 if (it->at(2).value<NET::Properties2>() != property2) {
0105                     continue;
0106                 }
0107             }
0108             foundOurWindow = true;
0109             break;
0110         }
0111         if (foundOurWindow) {
0112             break;
0113         }
0114         spy.clear();
0115     }
0116     return foundOurWindow;
0117 }
0118 
0119 bool KWindowInfoX11Test::verifyMinimized(WId window) const
0120 {
0121     KWindowInfo info(window, NET::WMState | NET::XAWMState);
0122     return info.isMinimized();
0123 }
0124 
0125 void KWindowInfoX11Test::init()
0126 {
0127     // create the window and ensure it has been managed
0128     window.reset(new QWidget());
0129     showWidget(window.get());
0130 }
0131 
0132 void KWindowInfoX11Test::showWidget(QWidget *window)
0133 {
0134     qRegisterMetaType<WId>("WId");
0135     QSignalSpy spy(KX11Extras::self(), &KX11Extras::windowAdded);
0136     window->show();
0137     bool foundOurWindow = false;
0138     for (int i = 0; i < 50; ++i) {
0139         spy.wait(50);
0140         if (spy.isEmpty()) {
0141             continue;
0142         }
0143         for (auto it = spy.constBegin(); it != spy.constEnd(); ++it) {
0144             if (it->isEmpty()) {
0145                 continue;
0146             }
0147             if (it->first().value<WId>() == window->winId()) {
0148                 foundOurWindow = true;
0149                 break;
0150             }
0151         }
0152         if (foundOurWindow) {
0153             break;
0154         }
0155         spy.clear();
0156     }
0157 }
0158 
0159 void KWindowInfoX11Test::cleanup()
0160 {
0161     // we hide the window and wait till it is gone so that we have a clean state in next test
0162     if (window && window->isVisible()) {
0163         WId id = window->winId();
0164         QSignalSpy spy(KX11Extras::self(), &KX11Extras::windowRemoved);
0165         window->hide();
0166         bool foundOurWindow = false;
0167         for (int i = 0; i < 50; ++i) {
0168             spy.wait(50);
0169             if (spy.isEmpty()) {
0170                 continue;
0171             }
0172             for (auto it = spy.constBegin(); it != spy.constEnd(); ++it) {
0173                 if (it->first().value<WId>() == id) {
0174                     foundOurWindow = true;
0175                     break;
0176                 }
0177             }
0178             if (foundOurWindow) {
0179                 break;
0180             }
0181             spy.clear();
0182         }
0183     }
0184     window.reset();
0185 }
0186 
0187 void KWindowInfoX11Test::testState_data()
0188 {
0189     QTest::addColumn<NET::States>("state");
0190 
0191     QTest::newRow("max") << NET::States(NET::Max);
0192     QTest::newRow("maxHoriz") << NET::States(NET::MaxHoriz);
0193     QTest::newRow("shaded") << NET::States(NET::Shaded);
0194     QTest::newRow("skipTaskbar") << NET::States(NET::SkipTaskbar);
0195     QTest::newRow("skipPager") << NET::States(NET::SkipPager);
0196     QTest::newRow("keep above") << NET::States(NET::KeepAbove);
0197     QTest::newRow("keep below") << NET::States(NET::KeepBelow);
0198     QTest::newRow("fullscreen") << NET::States(NET::FullScreen);
0199 
0200     NETRootInfo info(QX11Info::connection(), NET::Supported);
0201     if (info.isSupported(NET::SkipSwitcher)) {
0202         QTest::newRow("skipSwitcher") << NET::States(NET::SkipSwitcher);
0203     }
0204 
0205     // NOTE: modal, sticky and hidden cannot be tested with this variant
0206     // demands attention is not tested as that's already part of the first run adjustments
0207 }
0208 
0209 void KWindowInfoX11Test::testState()
0210 {
0211     QFETCH(NET::States, state);
0212     QX11Info::getTimestamp();
0213 
0214     KWindowInfo info(window->winId(), NET::WMState);
0215     QVERIFY(info.valid());
0216     // all states except demands attention
0217     for (int i = 0; i < 12; ++i) {
0218         QVERIFY(!info.hasState(NET::States(1 << i)));
0219     }
0220 
0221     QSignalSpy spy(KX11Extras::self(), &KX11Extras::windowChanged);
0222     QVERIFY(spy.isValid());
0223     // now we have a clean window and can do fun stuff
0224     KWindowSystem::setState(window->winId(), state);
0225 
0226     QVERIFY(waitForWindow(spy, window->winId(), NET::WMState));
0227 
0228     KWindowInfo info3(window->winId(), NET::WMState);
0229     QVERIFY(info3.valid());
0230     QCOMPARE(int(info3.state()), int(state));
0231     QVERIFY(info3.hasState(state));
0232 }
0233 
0234 // This struct is defined here to avoid a dependency on xcb-icccm
0235 struct kde_wm_hints {
0236     uint32_t flags;
0237     uint32_t input;
0238     int32_t initial_state;
0239     xcb_pixmap_t icon_pixmap;
0240     xcb_window_t icon_window;
0241     int32_t icon_x;
0242     int32_t icon_y;
0243     xcb_pixmap_t icon_mask;
0244     xcb_window_t window_group;
0245 };
0246 
0247 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 101)
0248 void KWindowInfoX11Test::testDemandsAttention()
0249 {
0250     QSignalSpy activeWindowSpy(KX11Extras::self(), &KX11Extras::activeWindowChanged);
0251     QVERIFY(activeWindowSpy.isValid());
0252     if (KX11Extras::activeWindow() != window->winId()) {
0253         // we force activate as KWin's focus stealing prevention might kick in
0254         KX11Extras::forceActiveWindow(window->winId());
0255         QVERIFY(activeWindowSpy.wait());
0256         QCOMPARE(activeWindowSpy.first().first().toULongLong(), window->winId());
0257         activeWindowSpy.clear();
0258     }
0259 
0260     // need a second window for proper interaction
0261     QWidget win2;
0262     showWidget(&win2);
0263     KX11Extras::forceActiveWindow(win2.winId());
0264     if (activeWindowSpy.isEmpty()) {
0265         QVERIFY(activeWindowSpy.wait());
0266     }
0267 
0268     KWindowInfo info(window->winId(), NET::WMState);
0269     QVERIFY(info.valid());
0270     QVERIFY(!info.hasState(NET::DemandsAttention));
0271 
0272     QSignalSpy spy(KX11Extras::self(), &KX11Extras::windowChanged);
0273     QVERIFY(spy.isValid());
0274     // now we have a clean window and can do fun stuff
0275     KWindowSystem::demandAttention(window->winId());
0276 
0277     QVERIFY(waitForWindow(spy, window->winId(), NET::WMState));
0278 
0279     KWindowInfo info2(window->winId(), NET::WMState);
0280     QVERIFY(info2.valid());
0281     QCOMPARE(info2.state(), NET::DemandsAttention);
0282     QVERIFY(info2.hasState(NET::DemandsAttention));
0283 
0284     // now activate win1, that should remove demands attention
0285     spy.clear();
0286     KX11Extras::forceActiveWindow(window->winId());
0287     QTest::qWait(200);
0288     QVERIFY(waitForWindow(spy, window->winId(), NET::WMState));
0289 
0290     KWindowInfo info3(window->winId(), NET::WMState);
0291     QVERIFY(info3.valid());
0292     QVERIFY(!info3.hasState(NET::DemandsAttention));
0293 
0294     // we should be able to demand attention on win2
0295     spy.clear();
0296     KWindowSystem::demandAttention(win2.winId());
0297     xcb_flush(QX11Info::connection());
0298     QVERIFY(waitForWindow(spy, win2.winId(), NET::WMState));
0299     KWindowInfo info4(win2.winId(), NET::WMState);
0300     QVERIFY(info4.valid());
0301     QCOMPARE(info4.state(), NET::DemandsAttention);
0302     QVERIFY(info4.hasState(NET::DemandsAttention));
0303     // and to remove demand attention on win2
0304     spy.clear();
0305     QTest::qWait(200);
0306     KWindowSystem::demandAttention(win2.winId(), false);
0307     xcb_flush(QX11Info::connection());
0308     QVERIFY(waitForWindow(spy, win2.winId(), NET::WMState));
0309     KWindowInfo info5(win2.winId(), NET::WMState);
0310     QVERIFY(info5.valid());
0311     QVERIFY(!info5.hasState(NET::DemandsAttention));
0312 
0313     // WM2Urgency should be mapped to state NET::DemandsAttention
0314     kde_wm_hints hints;
0315     hints.flags = (1 << 8);
0316     hints.icon_mask = XCB_PIXMAP_NONE;
0317     hints.icon_pixmap = XCB_PIXMAP_NONE;
0318     hints.icon_window = XCB_WINDOW_NONE;
0319     hints.input = 0;
0320     hints.window_group = XCB_WINDOW_NONE;
0321     hints.icon_x = 0;
0322     hints.icon_y = 0;
0323     hints.initial_state = 0;
0324     xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, win2.winId(), XCB_ATOM_WM_HINTS, XCB_ATOM_WM_HINTS, 32, 9, &hints);
0325     xcb_flush(QX11Info::connection());
0326 
0327     // window managers map urgency to demands attention
0328     spy.clear();
0329     QVERIFY(waitForWindow(spy, win2.winId(), NET::WMState));
0330     QTest::qWait(100);
0331 
0332     // a window info with NET::WM2Urgency should show demands attention
0333     KWindowInfo urgencyInfo(win2.winId(), NET::WMState);
0334     QVERIFY(urgencyInfo.valid());
0335     QVERIFY(urgencyInfo.hasState(NET::DemandsAttention));
0336 
0337     // remove urgency again
0338     hints.flags = 0;
0339     xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, win2.winId(), XCB_ATOM_WM_HINTS, XCB_ATOM_WM_HINTS, 32, 9, &hints);
0340     xcb_flush(QX11Info::connection());
0341     // TODO: test whether it gets removed again. At least KWin does not remove demands attention
0342 
0343     // prevent openbox crash (see https://bugzilla.icculus.org/show_bug.cgi?id=6315 )
0344     QTest::qWait(600);
0345 }
0346 #endif
0347 
0348 void KWindowInfoX11Test::testMinimized()
0349 {
0350     // should not be minimized, now
0351     QVERIFY(!verifyMinimized(window->winId()));
0352 
0353     window->showMinimized();
0354     // TODO: improve by using signalspy?
0355     QTest::qWait(100);
0356 
0357     // should be minimized, now
0358     QVERIFY(verifyMinimized(window->winId()));
0359 
0360     // back to normal
0361     window->showNormal();
0362     // TODO: improve by using signalspy?
0363     QTest::qWait(100);
0364 
0365     // should no longer be minimized
0366     QVERIFY(!verifyMinimized(window->winId()));
0367 }
0368 
0369 void KWindowInfoX11Test::testMappingState()
0370 {
0371     KWindowInfo info(window->winId(), NET::XAWMState);
0372     QCOMPARE(info.mappingState(), NET::Visible);
0373 
0374     window->showMinimized();
0375     // TODO: improve by using signalspy?
0376     QTest::qWait(100);
0377     KWindowInfo info2(window->winId(), NET::XAWMState);
0378     QCOMPARE(info2.mappingState(), NET::Iconic);
0379 
0380     window->hide();
0381     // TODO: improve by using signalspy?
0382     QTest::qWait(100);
0383     KWindowInfo info3(window->winId(), NET::XAWMState);
0384     QCOMPARE(info3.mappingState(), NET::Withdrawn);
0385 }
0386 
0387 void KWindowInfoX11Test::testWindowType_data()
0388 {
0389     QTest::addColumn<NET::WindowTypeMask>("mask");
0390     QTest::addColumn<NET::WindowType>("type");
0391     QTest::addColumn<NET::WindowType>("expectedType");
0392 
0393     // clang-format off
0394     QTest::newRow("desktop")            << NET::DesktopMask      << NET::Desktop      << NET::Desktop;
0395     QTest::newRow("dock")               << NET::DockMask         << NET::Dock         << NET::Dock;
0396     QTest::newRow("toolbar")            << NET::ToolbarMask      << NET::Toolbar      << NET::Toolbar;
0397     QTest::newRow("menu")               << NET::MenuMask         << NET::Menu         << NET::Menu;
0398     QTest::newRow("dialog")             << NET::DialogMask       << NET::Dialog       << NET::Dialog;
0399     QTest::newRow("override")           << NET::OverrideMask     << NET::Override     << NET::Override;
0400     QTest::newRow("override as normal") << NET::NormalMask       << NET::Override     << NET::Normal;
0401     QTest::newRow("topmenu")            << NET::TopMenuMask      << NET::TopMenu      << NET::TopMenu;
0402     QTest::newRow("topmenu as dock")    << NET::DockMask         << NET::TopMenu      << NET::Dock;
0403     QTest::newRow("utility")            << NET::UtilityMask      << NET::Utility      << NET::Utility;
0404     QTest::newRow("utility as dialog")  << NET::DialogMask       << NET::Utility      << NET::Dialog;
0405     QTest::newRow("splash")             << NET::SplashMask       << NET::Splash       << NET::Splash;
0406     QTest::newRow("splash as dock")     << NET::DockMask         << NET::Splash       << NET::Dock;
0407     QTest::newRow("dropdownmenu")       << NET::DropdownMenuMask << NET::DropdownMenu << NET::DropdownMenu;
0408     QTest::newRow("popupmenu")          << NET::PopupMenuMask    << NET::PopupMenu    << NET::PopupMenu;
0409     QTest::newRow("popupmenu as menu")  << NET::MenuMask         << NET::Menu         << NET::Menu;
0410     QTest::newRow("tooltip")            << NET::TooltipMask      << NET::Tooltip      << NET::Tooltip;
0411     QTest::newRow("notification")       << NET::NotificationMask << NET::Notification << NET::Notification;
0412     QTest::newRow("ComboBox")           << NET::ComboBoxMask     << NET::ComboBox     << NET::ComboBox;
0413     QTest::newRow("DNDIcon")            << NET::DNDIconMask      << NET::DNDIcon      << NET::DNDIcon;
0414     QTest::newRow("OnScreenDisplay")    << NET::OnScreenDisplayMask << NET::OnScreenDisplay << NET::OnScreenDisplay;
0415     QTest::newRow("CriticalNotification") << NET::CriticalNotificationMask << NET::CriticalNotification << NET::CriticalNotification;
0416     QTest::newRow("AppletPopup")        << NET::AppletPopupMask  << NET::AppletPopup  << NET::AppletPopup;
0417 
0418     // incorrect masks
0419     QTest::newRow("desktop-unknown")      << NET::NormalMask << NET::Desktop      << NET::Unknown;
0420     QTest::newRow("dock-unknown")         << NET::NormalMask << NET::Dock         << NET::Unknown;
0421     QTest::newRow("toolbar-unknown")      << NET::NormalMask << NET::Toolbar      << NET::Unknown;
0422     QTest::newRow("menu-unknown")         << NET::NormalMask << NET::Menu         << NET::Unknown;
0423     QTest::newRow("dialog-unknown")       << NET::NormalMask << NET::Dialog       << NET::Unknown;
0424     QTest::newRow("override-unknown")     << NET::DialogMask << NET::Override     << NET::Unknown;
0425     QTest::newRow("topmenu-unknown")      << NET::NormalMask << NET::TopMenu      << NET::Unknown;
0426     QTest::newRow("utility-unknown")      << NET::NormalMask << NET::Utility      << NET::Unknown;
0427     QTest::newRow("splash-unknown")       << NET::NormalMask << NET::Splash       << NET::Unknown;
0428     QTest::newRow("dropdownmenu-unknown") << NET::NormalMask << NET::DropdownMenu << NET::Unknown;
0429     QTest::newRow("popupmenu-unknown")    << NET::NormalMask << NET::PopupMenu    << NET::Unknown;
0430     QTest::newRow("tooltip-unknown")      << NET::NormalMask << NET::Tooltip      << NET::Unknown;
0431     QTest::newRow("notification-unknown") << NET::NormalMask << NET::Notification << NET::Unknown;
0432     QTest::newRow("ComboBox-unknown")     << NET::NormalMask << NET::ComboBox     << NET::Unknown;
0433     QTest::newRow("DNDIcon-unknown")      << NET::NormalMask << NET::DNDIcon      << NET::Unknown;
0434     QTest::newRow("OnScreenDisplay-unknown") << NET::NormalMask << NET::OnScreenDisplay << NET::Unknown;
0435     QTest::newRow("CriticalNotification-unknown") << NET::NormalMask << NET::CriticalNotification << NET::Unknown;
0436     QTest::newRow("AppletPopup-unknown")  << NET::NormalMask << NET::AppletPopup  << NET::Unknown;
0437     // clang-format on
0438 }
0439 
0440 void KWindowInfoX11Test::testWindowType()
0441 {
0442     KWindowInfo info(window->winId(), NET::WMWindowType);
0443     QCOMPARE(info.windowType(NET::NormalMask), NET::Normal);
0444 
0445     QFETCH(NET::WindowTypeMask, mask);
0446     QFETCH(NET::WindowType, type);
0447     QFETCH(NET::WindowType, expectedType);
0448 
0449     KWindowSystem::setType(window->winId(), type);
0450     // setWindowType just changes an xproperty, so a roundtrip waiting for another property ensures we are updated
0451     QX11Info::getTimestamp();
0452     KWindowInfo info2(window->winId(), NET::WMWindowType);
0453     QCOMPARE(info2.windowType(mask), expectedType);
0454 }
0455 
0456 void KWindowInfoX11Test::testDesktop()
0457 {
0458     if (KX11Extras::numberOfDesktops() < 2) {
0459         QSKIP("We need at least two virtual desktops to perform proper virtual desktop testing");
0460     }
0461     KWindowInfo info(window->winId(), NET::WMDesktop);
0462     QVERIFY(info.isOnCurrentDesktop());
0463     QVERIFY(!info.onAllDesktops());
0464     QCOMPARE(info.desktop(), KX11Extras::currentDesktop());
0465     for (int i = 1; i < KX11Extras::numberOfDesktops(); i++) {
0466         if (i == KX11Extras::currentDesktop()) {
0467             QVERIFY(info.isOnDesktop(i));
0468         } else {
0469             QVERIFY(!info.isOnDesktop(i));
0470         }
0471     }
0472 
0473     // set on all desktop
0474     QSignalSpy spy(KX11Extras::self(), &KX11Extras::windowChanged);
0475     QVERIFY(spy.isValid());
0476     KX11Extras::setOnAllDesktops(window->winId(), true);
0477     QVERIFY(waitForWindow(spy, window->winId(), NET::WMDesktop));
0478 
0479     KWindowInfo info2(window->winId(), NET::WMDesktop);
0480     QVERIFY(info2.isOnCurrentDesktop());
0481     QVERIFY(info2.onAllDesktops());
0482     QCOMPARE(info2.desktop(), int(NET::OnAllDesktops));
0483     for (int i = 1; i < KX11Extras::numberOfDesktops(); i++) {
0484         QVERIFY(info2.isOnDesktop(i));
0485     }
0486 
0487     const int desktop = (KX11Extras::currentDesktop() % KX11Extras::numberOfDesktops()) + 1;
0488     spy.clear();
0489     KX11Extras::setOnDesktop(window->winId(), desktop);
0490     QX11Info::getTimestamp();
0491     QVERIFY(waitForWindow(spy, window->winId(), NET::WMDesktop));
0492 
0493     KWindowInfo info3(window->winId(), NET::WMDesktop);
0494     QVERIFY(!info3.isOnCurrentDesktop());
0495     QVERIFY(!info3.onAllDesktops());
0496     QCOMPARE(info3.desktop(), desktop);
0497     for (int i = 1; i < KX11Extras::numberOfDesktops(); i++) {
0498         if (i == desktop) {
0499             QVERIFY(info3.isOnDesktop(i));
0500         } else {
0501             QVERIFY(!info3.isOnDesktop(i));
0502         }
0503     }
0504 }
0505 
0506 void KWindowInfoX11Test::testActivities()
0507 {
0508     NETRootInfo rootInfo(QX11Info::connection(), NET::Supported | NET::SupportingWMCheck);
0509 
0510     QSignalSpy spyReal(KX11Extras::self(), &KX11Extras::windowChanged);
0511     QVERIFY(spyReal.isValid());
0512 
0513     KWindowInfo info(window->winId(), NET::Properties(), NET::WM2Activities);
0514     QVERIFY(info.valid());
0515 
0516     QStringList startingActivities = info.activities();
0517 
0518     // The window is either on a specific activity when created,
0519     // or on all of them (aka startingActivities is empty or contains
0520     // just one element)
0521     QVERIFY(startingActivities.size() <= 1);
0522 
0523     // Window on all activities
0524     KX11Extras::self()->setOnActivities(window->winId(), QStringList());
0525 
0526     QVERIFY(waitForWindow(spyReal, window->winId(), NET::Properties(), NET::WM2Activities));
0527 
0528     KWindowInfo info2(window->winId(), NET::Properties(), NET::WM2Activities);
0529 
0530     QVERIFY(info2.activities().size() == 0);
0531 
0532     // Window on a specific activity
0533     KX11Extras::self()->setOnActivities(window->winId(), QStringList() << "test-activity");
0534 
0535     QVERIFY(waitForWindow(spyReal, window->winId(), NET::Properties(), NET::WM2Activities));
0536 
0537     KWindowInfo info3(window->winId(), NET::Properties(), NET::WM2Activities);
0538 
0539     QVERIFY(info3.activities().size() == 1);
0540     QVERIFY(info3.activities()[0] == "test-activity");
0541 
0542     // Window on two specific activities
0543     KX11Extras::self()->setOnActivities(window->winId(), QStringList{"test-activity", "test-activity2"});
0544 
0545     QVERIFY(waitForWindow(spyReal, window->winId(), NET::Properties(), NET::WM2Activities));
0546 
0547     KWindowInfo info4(window->winId(), NET::Properties(), NET::WM2Activities);
0548 
0549     QCOMPARE(info4.activities().size(), 2);
0550     QVERIFY(info4.activities()[0] == "test-activity");
0551     QVERIFY(info4.activities()[1] == "test-activity2");
0552 
0553     // Window on the starting activity
0554     KX11Extras::self()->setOnActivities(window->winId(), startingActivities);
0555 
0556     QVERIFY(waitForWindow(spyReal, window->winId(), NET::Properties(), NET::WM2Activities));
0557 
0558     KWindowInfo info5(window->winId(), NET::Properties(), NET::WM2Activities);
0559 
0560     QVERIFY(info5.activities() == startingActivities);
0561 }
0562 
0563 void KWindowInfoX11Test::testWindowClass()
0564 {
0565     KWindowInfo info(window->winId(), NET::Properties(), NET::WM2WindowClass);
0566     QCOMPARE(info.windowClassName(), QByteArrayLiteral("kwindowinfox11test"));
0567     QCOMPARE(info.windowClassClass(), QByteArrayLiteral("kwindowinfox11test"));
0568 
0569     // window class needs to be changed using xcb
0570     xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, window->winId(), XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, 7, "foo\0bar");
0571     xcb_flush(QX11Info::connection());
0572 
0573     // it's just a property change so we can easily refresh
0574     QX11Info::getTimestamp();
0575 
0576     KWindowInfo info2(window->winId(), NET::Properties(), NET::WM2WindowClass);
0577     QCOMPARE(info2.windowClassName(), QByteArrayLiteral("foo"));
0578     QCOMPARE(info2.windowClassClass(), QByteArrayLiteral("bar"));
0579 }
0580 
0581 void KWindowInfoX11Test::testWindowRole()
0582 {
0583     KWindowInfo info(window->winId(), NET::Properties(), NET::WM2WindowRole);
0584     QVERIFY(info.windowRole().isNull());
0585 
0586     // window role needs to be changed using xcb
0587     KXUtils::Atom atom(QX11Info::connection(), QByteArrayLiteral("WM_WINDOW_ROLE"));
0588     xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, window->winId(), atom, XCB_ATOM_STRING, 8, 3, "bar");
0589     xcb_flush(QX11Info::connection());
0590 
0591     // it's just a property change so we can easily refresh
0592     QX11Info::getTimestamp();
0593 
0594     KWindowInfo info2(window->winId(), NET::Properties(), NET::WM2WindowRole);
0595     QCOMPARE(info2.windowRole(), QByteArrayLiteral("bar"));
0596 }
0597 
0598 void KWindowInfoX11Test::testClientMachine()
0599 {
0600     const QByteArray oldHostName = QSysInfo::machineHostName().toLocal8Bit();
0601 
0602     KWindowInfo info(window->winId(), NET::Properties(), NET::WM2ClientMachine);
0603     QCOMPARE(info.clientMachine(), oldHostName);
0604 
0605     // client machine needs to be set through xcb
0606     const QByteArray newHostName = oldHostName + "2";
0607     xcb_change_property(QX11Info::connection(),
0608                         XCB_PROP_MODE_REPLACE,
0609                         window->winId(),
0610                         XCB_ATOM_WM_CLIENT_MACHINE,
0611                         XCB_ATOM_STRING,
0612                         8,
0613                         newHostName.count(),
0614                         newHostName.data());
0615     xcb_flush(QX11Info::connection());
0616 
0617     // it's just a property change so we can easily refresh
0618     QX11Info::getTimestamp();
0619 
0620     KWindowInfo info2(window->winId(), NET::Properties(), NET::WM2ClientMachine);
0621     QCOMPARE(info2.clientMachine(), newHostName);
0622 }
0623 
0624 void KWindowInfoX11Test::testName()
0625 {
0626     // clang-format off
0627     KWindowInfo info(window->winId(), NET::WMName | NET::WMVisibleName | NET::WMIconName | NET::WMVisibleIconName | NET::WMState | NET::XAWMState);
0628     QCOMPARE(info.name(),                     QStringLiteral("kwindowinfox11test"));
0629     QCOMPARE(info.visibleName(),              QStringLiteral("kwindowinfox11test"));
0630     QCOMPARE(info.visibleNameWithState(),     QStringLiteral("kwindowinfox11test"));
0631     QCOMPARE(info.iconName(),                 QStringLiteral("kwindowinfox11test"));
0632     QCOMPARE(info.visibleIconName(),          QStringLiteral("kwindowinfox11test"));
0633     QCOMPARE(info.visibleIconNameWithState(), QStringLiteral("kwindowinfox11test"));
0634 
0635     window->showMinimized();
0636     // TODO: improve by using signalspy?
0637     QTest::qWait(100);
0638     // should be minimized, now
0639     QVERIFY(verifyMinimized(window->winId()));
0640 
0641     // that should have changed the visible name
0642     KWindowInfo info2(window->winId(), NET::WMName | NET::WMVisibleName | NET::WMIconName | NET::WMVisibleIconName | NET::WMState | NET::XAWMState);
0643     QCOMPARE(info2.name(),                     QStringLiteral("kwindowinfox11test"));
0644     QCOMPARE(info2.visibleName(),              QStringLiteral("kwindowinfox11test"));
0645     QCOMPARE(info2.visibleNameWithState(),     QStringLiteral("(kwindowinfox11test)"));
0646     QCOMPARE(info2.iconName(),                 QStringLiteral("kwindowinfox11test"));
0647     QCOMPARE(info2.visibleIconName(),          QStringLiteral("kwindowinfox11test"));
0648     QCOMPARE(info2.visibleIconNameWithState(), QStringLiteral("(kwindowinfox11test)"));
0649 
0650     NETRootInfo rootInfo(QX11Info::connection(), NET::Supported | NET::SupportingWMCheck);
0651     if (qstrcmp(rootInfo.wmName(), "Openbox") == 0) {
0652         QSKIP("setting name test fails on openbox");
0653     }
0654 
0655     // create a low level NETWinInfo to manipulate the name
0656     NETWinInfo winInfo(QX11Info::connection(), window->winId(), QX11Info::appRootWindow(), NET::WMName, NET::Properties2());
0657     winInfo.setName("foobar");
0658 
0659     QX11Info::getTimestamp();
0660 
0661     KWindowInfo info3(window->winId(), NET::WMName | NET::WMVisibleName | NET::WMIconName | NET::WMVisibleIconName | NET::WMState | NET::XAWMState);
0662     QCOMPARE(info3.name(),                     QStringLiteral("foobar"));
0663     QCOMPARE(info3.visibleName(),              QStringLiteral("foobar"));
0664     QCOMPARE(info3.visibleNameWithState(),     QStringLiteral("(foobar)"));
0665     QCOMPARE(info3.iconName(),                 QStringLiteral("foobar"));
0666     QCOMPARE(info3.visibleIconName(),          QStringLiteral("foobar"));
0667     QCOMPARE(info3.visibleIconNameWithState(), QStringLiteral("(foobar)"));
0668     // clang-format on
0669 }
0670 
0671 void KWindowInfoX11Test::testTransientFor()
0672 {
0673     KWindowInfo info(window->winId(), NET::Properties(), NET::WM2TransientFor);
0674     QCOMPARE(info.transientFor(), WId(0));
0675 
0676     // let's create a second window
0677     std::unique_ptr<QWidget> window2(new QWidget());
0678     window2->show();
0679     QVERIFY(QTest::qWaitForWindowExposed(window2.get()));
0680 
0681     // update the transient for of window1 to window2
0682     const uint32_t id = window2->winId();
0683     xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, window->winId(), XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32, 1, &id);
0684     xcb_flush(QX11Info::connection());
0685 
0686     KWindowInfo info2(window->winId(), NET::Properties(), NET::WM2TransientFor);
0687     QCOMPARE(info2.transientFor(), window2->winId());
0688 }
0689 
0690 void KWindowInfoX11Test::testGroupLeader()
0691 {
0692     // WM_CLIENT_LEADER is set by default
0693     KWindowInfo info1(window->winId(), NET::Properties(), NET::WM2GroupLeader);
0694     QVERIFY(info1.groupLeader() != XCB_WINDOW_NONE);
0695 
0696     xcb_connection_t *connection = QX11Info::connection();
0697     xcb_window_t rootWindow = QX11Info::appRootWindow();
0698 
0699     xcb_window_t leader = xcb_generate_id(connection);
0700     xcb_create_window(connection, XCB_COPY_FROM_PARENT, leader, rootWindow, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0701 
0702     xcb_icccm_wm_hints_t hints = {};
0703     hints.flags = XCB_ICCCM_WM_HINT_WINDOW_GROUP;
0704     hints.window_group = leader;
0705     xcb_icccm_set_wm_hints(connection, leader, &hints);
0706     xcb_icccm_set_wm_hints(connection, window->winId(), &hints);
0707 
0708     KWindowInfo info2(window->winId(), NET::Properties(), NET::WM2GroupLeader);
0709     QCOMPARE(info2.groupLeader(), leader);
0710 }
0711 
0712 void KWindowInfoX11Test::testExtendedStrut()
0713 {
0714     KWindowInfo info(window->winId(), NET::Properties(), NET::WM2ExtendedStrut);
0715     NETExtendedStrut strut = info.extendedStrut();
0716     QCOMPARE(strut.bottom_end, 0);
0717     QCOMPARE(strut.bottom_start, 0);
0718     QCOMPARE(strut.bottom_width, 0);
0719     QCOMPARE(strut.left_end, 0);
0720     QCOMPARE(strut.left_start, 0);
0721     QCOMPARE(strut.left_width, 0);
0722     QCOMPARE(strut.right_end, 0);
0723     QCOMPARE(strut.right_start, 0);
0724     QCOMPARE(strut.right_width, 0);
0725     QCOMPARE(strut.top_end, 0);
0726     QCOMPARE(strut.top_start, 0);
0727     QCOMPARE(strut.top_width, 0);
0728 
0729     KX11Extras::setExtendedStrut(window->winId(), 10, 20, 30, 40, 5, 15, 25, 35, 2, 12, 22, 32);
0730 
0731     // it's just an xprop, so one roundtrip is good enough
0732     QX11Info::getTimestamp();
0733 
0734     KWindowInfo info2(window->winId(), NET::Properties(), NET::WM2ExtendedStrut);
0735     strut = info2.extendedStrut();
0736     QCOMPARE(strut.bottom_end, 32);
0737     QCOMPARE(strut.bottom_start, 22);
0738     QCOMPARE(strut.bottom_width, 12);
0739     QCOMPARE(strut.left_end, 30);
0740     QCOMPARE(strut.left_start, 20);
0741     QCOMPARE(strut.left_width, 10);
0742     QCOMPARE(strut.right_end, 15);
0743     QCOMPARE(strut.right_start, 5);
0744     QCOMPARE(strut.right_width, 40);
0745     QCOMPARE(strut.top_end, 2);
0746     QCOMPARE(strut.top_start, 35);
0747     QCOMPARE(strut.top_width, 25);
0748 }
0749 
0750 void KWindowInfoX11Test::testGeometry()
0751 {
0752     KWindowInfo info(window->winId(), NET::WMGeometry | NET::WMFrameExtents);
0753     QCOMPARE(info.geometry().size(), window->geometry().size());
0754     QCOMPARE(info.frameGeometry().size(), window->frameGeometry().size());
0755 
0756     QSignalSpy spy(KX11Extras::self(), &KX11Extras::windowChanged);
0757     QVERIFY(spy.isValid());
0758 
0759     // this is tricky, KWin is smart and doesn't allow all geometries we pass in
0760     // setting to center of screen should work, though
0761     QRect geo(window->windowHandle()->screen()->geometry().center() - QPoint(window->width() / 2 - 5, window->height() / 2 - 5),
0762               window->size() + QSize(10, 10));
0763     window->setGeometry(geo);
0764     waitForWindow(spy, window->winId(), NET::WMGeometry);
0765 
0766     KWindowInfo info2(window->winId(), NET::WMGeometry | NET::WMFrameExtents);
0767     QCOMPARE(info2.geometry(), window->geometry());
0768     QCOMPARE(info2.geometry(), geo);
0769     QCOMPARE(info2.frameGeometry(), window->frameGeometry());
0770 }
0771 
0772 void KWindowInfoX11Test::testDesktopFileName()
0773 {
0774     KWindowInfo info(window->winId(), NET::Properties(), NET::WM2DesktopFileName);
0775     QVERIFY(info.valid());
0776     QCOMPARE(info.desktopFileName(), QByteArray());
0777 
0778     QSignalSpy spy(KX11Extras::self(), &KX11Extras::windowChanged);
0779     QVERIFY(spy.isValid());
0780 
0781     // create a NETWinInfo to set the desktop file name
0782     NETWinInfo netInfo(QX11Info::connection(), window->winId(), QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
0783     netInfo.setDesktopFileName("org.kde.foo");
0784     xcb_flush(QX11Info::connection());
0785 
0786     // it's just a property change so we can easily refresh
0787     QX11Info::getTimestamp();
0788     QTRY_COMPARE(spy.count(), 1);
0789     QCOMPARE(spy.first().at(0).value<WId>(), window->winId());
0790     QCOMPARE(spy.first().at(2).value<NET::Properties2>(), NET::Properties2(NET::WM2DesktopFileName));
0791 
0792     KWindowInfo info2(window->winId(), NET::Properties(), NET::WM2DesktopFileName);
0793     QVERIFY(info2.valid());
0794     QCOMPARE(info2.desktopFileName(), QByteArrayLiteral("org.kde.foo"));
0795 }
0796 
0797 void KWindowInfoX11Test::testPid()
0798 {
0799     KWindowInfo info(window->winId(), NET::WMPid);
0800     QVERIFY(info.valid());
0801     QCOMPARE(info.pid(), getpid());
0802 }
0803 
0804 QTEST_MAIN(KWindowInfoX11Test)
0805 
0806 #include "kwindowinfox11test.moc"