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"