Warning, file /frameworks/kwindowsystem/autotests/kwindowinfox11test.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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"