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

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