File indexing completed on 2024-04-21 15:05:29

0001 /*
0002     SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "nettesthelper.h"
0008 #include <netwm.h>
0009 
0010 #include <QProcess>
0011 #include <QStandardPaths>
0012 #include <qtest_widgets.h>
0013 
0014 // system
0015 #include <unistd.h>
0016 
0017 using Property = UniqueCPointer<xcb_get_property_reply_t>;
0018 
0019 Q_DECLARE_METATYPE(NET::Orientation)
0020 Q_DECLARE_METATYPE(NET::DesktopLayoutCorner)
0021 
0022 static const char *s_wmName = "netrootinfotest";
0023 
0024 class NetRootInfoTestWM : public QObject
0025 {
0026     Q_OBJECT
0027 private Q_SLOTS:
0028     void initTestCase();
0029     void cleanupTestCase();
0030     void init();
0031     void cleanup();
0032 
0033     void testCtor();
0034     void testSupported();
0035     void testClientList();
0036     void testClientListStacking();
0037     void testNumberOfDesktops();
0038     void testCurrentDesktop();
0039     void testDesktopNames();
0040     void testDesktopLayout_data();
0041     void testDesktopLayout();
0042     void testDesktopGeometry();
0043     void testDesktopViewports();
0044     void testShowingDesktop_data();
0045     void testShowingDesktop();
0046     void testWorkArea();
0047     void testActiveWindow();
0048     void testVirtualRoots();
0049     void testDontCrashMapViewports();
0050 
0051 private:
0052     void waitForPropertyChange(NETRootInfo *info, xcb_atom_t atom, NET::Property prop, NET::Property2 prop2 = NET::Property2(0));
0053     xcb_connection_t *connection()
0054     {
0055         return m_connection;
0056     }
0057     xcb_connection_t *m_connection;
0058     QVector<xcb_connection_t *> m_connections;
0059     std::unique_ptr<QProcess> m_xvfb;
0060     xcb_window_t m_supportWindow;
0061     xcb_window_t m_rootWindow;
0062 };
0063 
0064 void NetRootInfoTestWM::cleanupTestCase()
0065 {
0066     while (!m_connections.isEmpty()) {
0067         xcb_disconnect(m_connections.takeFirst());
0068     }
0069 }
0070 
0071 void NetRootInfoTestWM::initTestCase()
0072 {
0073 }
0074 
0075 void NetRootInfoTestWM::init()
0076 {
0077     // first reset just to be sure
0078     m_connection = nullptr;
0079     m_supportWindow = XCB_WINDOW_NONE;
0080     // start Xvfb
0081     const QString xfvbExec = QStandardPaths::findExecutable(QStringLiteral("Xvfb"));
0082     QVERIFY(!xfvbExec.isEmpty());
0083 
0084     m_xvfb.reset(new QProcess);
0085     // use pipe to pass fd to Xvfb to get back the display id
0086     int pipeFds[2];
0087     QVERIFY(pipe(pipeFds) == 0);
0088     m_xvfb->start(xfvbExec, QStringList{QStringLiteral("-displayfd"), QString::number(pipeFds[1])});
0089     QVERIFY(m_xvfb->waitForStarted());
0090     QCOMPARE(m_xvfb->state(), QProcess::Running);
0091 
0092     // reads from pipe, closes write side
0093     close(pipeFds[1]);
0094 
0095     QFile readPipe;
0096     QVERIFY(readPipe.open(pipeFds[0], QIODevice::ReadOnly, QFileDevice::AutoCloseHandle));
0097     QByteArray displayNumber = readPipe.readLine();
0098     readPipe.close();
0099 
0100     displayNumber.prepend(QByteArray(":"));
0101     displayNumber.remove(displayNumber.size() - 1, 1);
0102 
0103     // create X connection
0104     int screen = 0;
0105     m_connection = xcb_connect(displayNumber.constData(), &screen);
0106     QVERIFY(m_connection);
0107     QVERIFY(!xcb_connection_has_error(m_connection));
0108     m_rootWindow = KXUtils::rootWindow(m_connection, screen);
0109     uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE};
0110     xcb_change_window_attributes(m_connection, m_rootWindow, XCB_CW_EVENT_MASK, values);
0111 
0112     // create support window
0113     values[0] = true;
0114     m_supportWindow = xcb_generate_id(m_connection);
0115     xcb_create_window(m_connection,
0116                       XCB_COPY_FROM_PARENT,
0117                       m_supportWindow,
0118                       m_rootWindow,
0119                       0,
0120                       0,
0121                       1,
0122                       1,
0123                       0,
0124                       XCB_COPY_FROM_PARENT,
0125                       XCB_COPY_FROM_PARENT,
0126                       XCB_CW_OVERRIDE_REDIRECT,
0127                       values);
0128     const uint32_t lowerValues[] = {XCB_STACK_MODE_BELOW};
0129     // we need to do the lower window with a roundtrip, otherwise NETRootInfo is not functioning
0130     UniqueCPointer<xcb_generic_error_t> error(
0131         xcb_request_check(m_connection, xcb_configure_window_checked(m_connection, m_supportWindow, XCB_CONFIG_WINDOW_STACK_MODE, lowerValues)));
0132     QVERIFY(!error);
0133 }
0134 
0135 void NetRootInfoTestWM::cleanup()
0136 {
0137     // destroy support window
0138     xcb_destroy_window(connection(), m_supportWindow);
0139     m_supportWindow = XCB_WINDOW_NONE;
0140 
0141     // close connection
0142     // delay till clenupTestCase as otherwise xcb reuses the same memory address
0143     m_connections << connection();
0144     // kill Xvfb
0145     m_xvfb->terminate();
0146     m_xvfb->waitForFinished();
0147     m_xvfb.reset();
0148 }
0149 
0150 void NetRootInfoTestWM::waitForPropertyChange(NETRootInfo *info, xcb_atom_t atom, NET::Property prop, NET::Property2 prop2)
0151 {
0152     while (true) {
0153         UniqueCPointer<xcb_generic_event_t> event(xcb_wait_for_event(connection()));
0154         if (!event) {
0155             break;
0156         }
0157         if ((event->response_type & ~0x80) != XCB_PROPERTY_NOTIFY) {
0158             continue;
0159         }
0160         xcb_property_notify_event_t *pe = reinterpret_cast<xcb_property_notify_event_t *>(event.get());
0161         if (pe->window != m_rootWindow) {
0162             continue;
0163         }
0164         if (pe->atom != atom) {
0165             continue;
0166         }
0167         NET::Properties dirty;
0168         NET::Properties2 dirty2;
0169         info->event(event.get(), &dirty, &dirty2);
0170         if (prop != 0) {
0171             QVERIFY(dirty & prop);
0172         }
0173         if (prop2 != 0) {
0174             QVERIFY(dirty2 & prop2);
0175         }
0176         if (!prop) {
0177             QCOMPARE(dirty, NET::Properties());
0178         }
0179         if (!prop2) {
0180             QCOMPARE(dirty2, NET::Properties2());
0181         }
0182         break;
0183     }
0184 }
0185 
0186 void NetRootInfoTestWM::testCtor()
0187 {
0188     QVERIFY(connection());
0189     NETRootInfo
0190         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0191     QCOMPARE(rootInfo.xcbConnection(), connection());
0192     QCOMPARE(rootInfo.rootWindow(), m_rootWindow);
0193     QCOMPARE(rootInfo.supportWindow(), m_supportWindow);
0194     QCOMPARE(rootInfo.wmName(), s_wmName);
0195     QCOMPARE(rootInfo.supportedProperties(), NET::WMAllProperties);
0196     QCOMPARE(rootInfo.supportedProperties2(), NET::WM2AllProperties);
0197     QCOMPARE(rootInfo.supportedActions(), NET::Actions(~0u));
0198     QCOMPARE(rootInfo.supportedStates(), NET::States(~0u));
0199     QCOMPARE(rootInfo.supportedWindowTypes(), NET::AllTypesMask);
0200 
0201     QCOMPARE(rootInfo.passedProperties(), NET::WMAllProperties);
0202     QCOMPARE(rootInfo.passedProperties2(), NET::WM2AllProperties);
0203     QCOMPARE(rootInfo.passedActions(), NET::Actions(~0u));
0204     QCOMPARE(rootInfo.passedStates(), NET::States(~0u));
0205     QCOMPARE(rootInfo.passedWindowTypes(), NET::AllTypesMask);
0206 }
0207 
0208 void NetRootInfoTestWM::testSupported()
0209 {
0210     KXUtils::Atom supported(connection(), QByteArrayLiteral("_NET_SUPPORTED"));
0211     KXUtils::Atom wmCheck(connection(), QByteArrayLiteral("_NET_SUPPORTING_WM_CHECK"));
0212     KXUtils::Atom wmName(connection(), QByteArrayLiteral("_NET_WM_NAME"));
0213     KXUtils::Atom utf8String(connection(), QByteArrayLiteral("UTF8_STRING"));
0214     QVERIFY(connection());
0215     NETRootInfo
0216         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0217     int count = 0;
0218     for (int i = 0; i < 34; ++i) {
0219         if (i == 12) {
0220             continue;
0221         }
0222         QVERIFY(rootInfo.isSupported(NET::Property(1 << i)));
0223         count++;
0224     }
0225     for (int i = 0; i < 22; ++i) {
0226         QVERIFY(rootInfo.isSupported(NET::Property2(1 << i)));
0227         count++;
0228     }
0229 
0230     QVERIFY(rootInfo.isSupported(NET::WM2GTKShowWindowMenu));
0231     count++;
0232 
0233     for (int i = 0; i < 17; ++i) {
0234         QVERIFY(rootInfo.isSupported(NET::WindowTypeMask(1 << i)));
0235         count++;
0236     }
0237     for (int i = 0; i < 13; ++i) {
0238         QVERIFY(rootInfo.isSupported(NET::State(1 << i)));
0239         count++;
0240     }
0241     for (int i = 0; i < 10; ++i) {
0242         QVERIFY(rootInfo.isSupported(NET::Action(1 << i)));
0243         count++;
0244     }
0245     // NET::WMFrameExtents has two properties
0246     count += 1;
0247     // XAWState, WMGeometry, WM2TransientFor, WM2GroupLeader, WM2WindowClass, WM2WindowRole, WM2ClientMachine
0248     count -= 7;
0249     // WM2BlockCompositing has 3 properties
0250     count += 2;
0251     // Add _GTK_FRAME_EXTENTS
0252     ++count;
0253 
0254     QVERIFY(supported != XCB_ATOM_NONE);
0255     QVERIFY(utf8String != XCB_ATOM_NONE);
0256     QVERIFY(wmCheck != XCB_ATOM_NONE);
0257     QVERIFY(wmName != XCB_ATOM_NONE);
0258 
0259     // we should have got some events
0260     waitForPropertyChange(&rootInfo, supported, NET::Supported);
0261     waitForPropertyChange(&rootInfo, wmCheck, NET::SupportingWMCheck);
0262 
0263     // get the cookies of the things to check
0264     xcb_get_property_cookie_t supportedCookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), supported, XCB_ATOM_ATOM, 0, 101);
0265     xcb_get_property_cookie_t wmCheckRootCookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), wmCheck, XCB_ATOM_WINDOW, 0, 1);
0266     xcb_get_property_cookie_t wmCheckSupportWinCookie = xcb_get_property_unchecked(connection(), false, m_supportWindow, wmCheck, XCB_ATOM_WINDOW, 0, 1);
0267     xcb_get_property_cookie_t wmNameCookie = xcb_get_property_unchecked(connection(), false, m_supportWindow, wmName, utf8String, 0, 16);
0268 
0269     Property supportedReply(xcb_get_property_reply(connection(), supportedCookie, nullptr));
0270     QVERIFY(supportedReply);
0271     QCOMPARE(supportedReply->format, uint8_t(32));
0272     QCOMPARE(supportedReply->value_len, uint32_t(count));
0273     // TODO: check that the correct atoms are set?
0274     Property wmCheckRootReply(xcb_get_property_reply(connection(), wmCheckRootCookie, nullptr));
0275     QVERIFY(wmCheckRootReply);
0276     QCOMPARE(wmCheckRootReply->format, uint8_t(32));
0277     QCOMPARE(wmCheckRootReply->value_len, uint32_t(1));
0278     QCOMPARE(reinterpret_cast<xcb_window_t *>(xcb_get_property_value(wmCheckRootReply.get()))[0], m_supportWindow);
0279 
0280     Property wmCheckSupportReply(xcb_get_property_reply(connection(), wmCheckSupportWinCookie, nullptr));
0281     QVERIFY(wmCheckSupportReply);
0282     QCOMPARE(wmCheckSupportReply->format, uint8_t(32));
0283     QCOMPARE(wmCheckSupportReply->value_len, uint32_t(1));
0284     QCOMPARE(reinterpret_cast<xcb_window_t *>(xcb_get_property_value(wmCheckSupportReply.get()))[0], m_supportWindow);
0285 
0286     Property wmNameReply(xcb_get_property_reply(connection(), wmNameCookie, nullptr));
0287     QVERIFY(wmNameReply);
0288     QCOMPARE(wmNameReply->format, uint8_t(8));
0289     QCOMPARE(wmNameReply->value_len, uint32_t(15));
0290     QCOMPARE(reinterpret_cast<const char *>(xcb_get_property_value(wmNameReply.get())), s_wmName);
0291 
0292     // disable some supported
0293     rootInfo.setSupported(NET::WMFrameExtents, false);
0294     rootInfo.setSupported(NET::WM2KDETemporaryRules, false);
0295     rootInfo.setSupported(NET::ActionChangeDesktop, false);
0296     rootInfo.setSupported(NET::FullScreen, false);
0297     QVERIFY(rootInfo.isSupported(NET::ToolbarMask));
0298     QVERIFY(rootInfo.isSupported(NET::OnScreenDisplayMask));
0299     QVERIFY(rootInfo.isSupported(NET::DockMask));
0300     rootInfo.setSupported(NET::ToolbarMask, false);
0301     rootInfo.setSupported(NET::OnScreenDisplayMask, false);
0302 
0303     QVERIFY(!rootInfo.isSupported(NET::WMFrameExtents));
0304     QVERIFY(!rootInfo.isSupported(NET::WM2KDETemporaryRules));
0305     QVERIFY(!rootInfo.isSupported(NET::ActionChangeDesktop));
0306     QVERIFY(!rootInfo.isSupported(NET::FullScreen));
0307     QVERIFY(!rootInfo.isSupported(NET::ToolbarMask));
0308     QVERIFY(!rootInfo.isSupported(NET::OnScreenDisplayMask));
0309     QVERIFY(rootInfo.isSupported(NET::DockMask));
0310 
0311     // lets get supported again
0312     supportedCookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), supported, XCB_ATOM_ATOM, 0, 90);
0313     supportedReply.reset(xcb_get_property_reply(connection(), supportedCookie, nullptr));
0314     QVERIFY(supportedReply);
0315     QCOMPARE(supportedReply->format, uint8_t(32));
0316     QCOMPARE(supportedReply->value_len, uint32_t(count - 7));
0317 
0318     for (int i = 0; i < 5; ++i) {
0319         // we should have got some events
0320         waitForPropertyChange(&rootInfo, supported, NET::Supported);
0321         waitForPropertyChange(&rootInfo, wmCheck, NET::SupportingWMCheck);
0322     }
0323 
0324     // turn something off, just to get another event
0325     rootInfo.setSupported(NET::WM2BlockCompositing, false);
0326     // lets get supported again
0327     supportedCookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), supported, XCB_ATOM_ATOM, 0, 90);
0328     supportedReply.reset(xcb_get_property_reply(connection(), supportedCookie, nullptr));
0329     QVERIFY(supportedReply);
0330     QCOMPARE(supportedReply->format, uint8_t(32));
0331     QCOMPARE(supportedReply->value_len, uint32_t(count - 9));
0332     NETRootInfo clientInfo(connection(), NET::Supported | NET::SupportingWMCheck);
0333     waitForPropertyChange(&clientInfo, supported, NET::Supported);
0334     waitForPropertyChange(&clientInfo, wmCheck, NET::SupportingWMCheck);
0335 }
0336 
0337 void NetRootInfoTestWM::testClientList()
0338 {
0339     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_CLIENT_LIST"));
0340     QVERIFY(connection());
0341     NETRootInfo
0342         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0343     QCOMPARE(rootInfo.clientListCount(), 0);
0344     QVERIFY(!rootInfo.clientList());
0345 
0346     xcb_window_t windows[] = {xcb_generate_id(connection()),
0347                               xcb_generate_id(connection()),
0348                               xcb_generate_id(connection()),
0349                               xcb_generate_id(connection()),
0350                               xcb_generate_id(connection())};
0351     rootInfo.setClientList(windows, 5);
0352     QCOMPARE(rootInfo.clientListCount(), 5);
0353     const xcb_window_t *otherWins = rootInfo.clientList();
0354     for (int i = 0; i < 5; ++i) {
0355         QCOMPARE(otherWins[i], windows[i]);
0356     }
0357 
0358     // compare with the X property
0359     QVERIFY(atom != XCB_ATOM_NONE);
0360     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_WINDOW, 0, 5);
0361     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
0362     QVERIFY(reply);
0363     QCOMPARE(reply->format, uint8_t(32));
0364     QCOMPARE(reply->value_len, uint32_t(5));
0365     const xcb_window_t *propWins = reinterpret_cast<xcb_window_t *>(xcb_get_property_value(reply.get()));
0366     for (int i = 0; i < 5; ++i) {
0367         QCOMPARE(propWins[i], windows[i]);
0368     }
0369 
0370     // wait for our property
0371     NETRootInfo clientInfo(connection(), NET::Supported | NET::SupportingWMCheck | NET::ClientList);
0372     waitForPropertyChange(&clientInfo, atom, NET::ClientList);
0373     QCOMPARE(clientInfo.clientListCount(), 5);
0374     const xcb_window_t *otherWins2 = clientInfo.clientList();
0375     for (int i = 0; i < 5; ++i) {
0376         QCOMPARE(otherWins2[i], windows[i]);
0377     }
0378 }
0379 
0380 void NetRootInfoTestWM::testClientListStacking()
0381 {
0382     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_CLIENT_LIST_STACKING"));
0383     QVERIFY(connection());
0384     NETRootInfo
0385         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0386     QCOMPARE(rootInfo.clientListStackingCount(), 0);
0387     QVERIFY(!rootInfo.clientListStacking());
0388 
0389     xcb_window_t windows[] = {xcb_generate_id(connection()),
0390                               xcb_generate_id(connection()),
0391                               xcb_generate_id(connection()),
0392                               xcb_generate_id(connection()),
0393                               xcb_generate_id(connection())};
0394     rootInfo.setClientListStacking(windows, 5);
0395     QCOMPARE(rootInfo.clientListStackingCount(), 5);
0396     const xcb_window_t *otherWins = rootInfo.clientListStacking();
0397     for (int i = 0; i < 5; ++i) {
0398         QCOMPARE(otherWins[i], windows[i]);
0399     }
0400 
0401     // compare with the X property
0402     QVERIFY(atom != XCB_ATOM_NONE);
0403     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_WINDOW, 0, 5);
0404     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
0405     QVERIFY(reply);
0406     QCOMPARE(reply->format, uint8_t(32));
0407     QCOMPARE(reply->value_len, uint32_t(5));
0408     const xcb_window_t *propWins = reinterpret_cast<xcb_window_t *>(xcb_get_property_value(reply.get()));
0409     for (int i = 0; i < 5; ++i) {
0410         QCOMPARE(propWins[i], windows[i]);
0411     }
0412 
0413     // wait for our property
0414     waitForPropertyChange(&rootInfo, atom, NET::ClientListStacking);
0415     QCOMPARE(rootInfo.clientListStackingCount(), 5);
0416     const xcb_window_t *otherWins2 = rootInfo.clientListStacking();
0417     for (int i = 0; i < 5; ++i) {
0418         QCOMPARE(otherWins2[i], windows[i]);
0419     }
0420 }
0421 
0422 void NetRootInfoTestWM::testVirtualRoots()
0423 {
0424     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_VIRTUAL_ROOTS"));
0425     QVERIFY(connection());
0426     NETRootInfo
0427         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0428     QCOMPARE(rootInfo.virtualRootsCount(), 0);
0429     QVERIFY(!rootInfo.virtualRoots());
0430 
0431     xcb_window_t windows[] = {xcb_generate_id(connection()),
0432                               xcb_generate_id(connection()),
0433                               xcb_generate_id(connection()),
0434                               xcb_generate_id(connection()),
0435                               xcb_generate_id(connection())};
0436     rootInfo.setVirtualRoots(windows, 5);
0437     QCOMPARE(rootInfo.virtualRootsCount(), 5);
0438     const xcb_window_t *otherWins = rootInfo.virtualRoots();
0439     for (int i = 0; i < 5; ++i) {
0440         QCOMPARE(otherWins[i], windows[i]);
0441     }
0442 
0443     // compare with the X property
0444     QVERIFY(atom != XCB_ATOM_NONE);
0445     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_WINDOW, 0, 5);
0446     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
0447     QVERIFY(reply);
0448     QCOMPARE(reply->format, uint8_t(32));
0449     QCOMPARE(reply->value_len, uint32_t(5));
0450     const xcb_window_t *propWins = reinterpret_cast<xcb_window_t *>(xcb_get_property_value(reply.get()));
0451     for (int i = 0; i < 5; ++i) {
0452         QCOMPARE(propWins[i], windows[i]);
0453     }
0454 
0455     // wait for our property - reported to a Client NETRootInfo
0456     NETRootInfo clientInfo(connection(), NET::VirtualRoots);
0457     waitForPropertyChange(&clientInfo, atom, NET::VirtualRoots);
0458     QCOMPARE(rootInfo.virtualRootsCount(), 5);
0459     const xcb_window_t *otherWins2 = rootInfo.virtualRoots();
0460     for (int i = 0; i < 5; ++i) {
0461         QCOMPARE(otherWins2[i], windows[i]);
0462     }
0463 }
0464 
0465 void NetRootInfoTestWM::testNumberOfDesktops()
0466 {
0467     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_NUMBER_OF_DESKTOPS"));
0468     QVERIFY(connection());
0469     NETRootInfo
0470         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0471     QCOMPARE(rootInfo.numberOfDesktops(), 1);
0472     rootInfo.setNumberOfDesktops(4);
0473     QCOMPARE(rootInfo.numberOfDesktops(), 4);
0474 
0475     // compare with the X property
0476     QVERIFY(atom != XCB_ATOM_NONE);
0477     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 1);
0478     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
0479     QVERIFY(reply);
0480     QCOMPARE(reply->format, uint8_t(32));
0481     QCOMPARE(reply->value_len, uint32_t(1));
0482     QCOMPARE(reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()))[0], uint32_t(4));
0483 
0484     // wait for our property
0485     waitForPropertyChange(&rootInfo, atom, NET::NumberOfDesktops);
0486     QCOMPARE(rootInfo.numberOfDesktops(), 4);
0487 }
0488 
0489 void NetRootInfoTestWM::testCurrentDesktop()
0490 {
0491     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_CURRENT_DESKTOP"));
0492     // TODO: verify that current desktop cannot be higher than number of desktops
0493     QVERIFY(connection());
0494     NETRootInfo
0495         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0496     QCOMPARE(rootInfo.currentDesktop(), 1);
0497     rootInfo.setCurrentDesktop(5);
0498     QCOMPARE(rootInfo.currentDesktop(), 5);
0499 
0500     // compare with the X property
0501     QVERIFY(atom != XCB_ATOM_NONE);
0502     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 1);
0503     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
0504     QVERIFY(reply);
0505     QCOMPARE(reply->format, uint8_t(32));
0506     QCOMPARE(reply->value_len, uint32_t(1));
0507     // note: API starts counting at 1, but property starts counting at 5, because of that subtracting one
0508     QCOMPARE(reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()))[0], uint32_t(5 - 1));
0509 
0510     // wait for our property
0511     waitForPropertyChange(&rootInfo, atom, NET::CurrentDesktop);
0512     QCOMPARE(rootInfo.currentDesktop(), 5);
0513 }
0514 
0515 void NetRootInfoTestWM::testDesktopNames()
0516 {
0517     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_DESKTOP_NAMES"));
0518     KXUtils::Atom utf8String(connection(), QByteArrayLiteral("UTF8_STRING"));
0519     QVERIFY(connection());
0520     NETRootInfo
0521         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0522     QVERIFY(!rootInfo.desktopName(0));
0523     QVERIFY(!rootInfo.desktopName(1));
0524     QVERIFY(!rootInfo.desktopName(2));
0525     rootInfo.setDesktopName(1, "foo");
0526     rootInfo.setDesktopName(2, "bar");
0527     rootInfo.setNumberOfDesktops(2);
0528     QCOMPARE(rootInfo.desktopName(1), "foo");
0529     QCOMPARE(rootInfo.desktopName(2), "bar");
0530 
0531     // compare with the X property
0532     QVERIFY(atom != XCB_ATOM_NONE);
0533     QVERIFY(utf8String != XCB_ATOM_NONE);
0534     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, utf8String, 0, 10000);
0535     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
0536     QVERIFY(reply);
0537     QCOMPARE(reply->format, uint8_t(8));
0538     QCOMPARE(reply->value_len, uint32_t(8));
0539     QCOMPARE(reinterpret_cast<const char *>(xcb_get_property_value(reply.get())), "foo\0bar");
0540 
0541     // wait for our property
0542     waitForPropertyChange(&rootInfo, atom, NET::DesktopNames);
0543     QCOMPARE(rootInfo.desktopName(1), "foo");
0544     QCOMPARE(rootInfo.desktopName(2), "bar");
0545     // there should be two events
0546     waitForPropertyChange(&rootInfo, atom, NET::DesktopNames);
0547     QCOMPARE(rootInfo.desktopName(1), "foo");
0548     QCOMPARE(rootInfo.desktopName(2), "bar");
0549 }
0550 
0551 void NetRootInfoTestWM::testActiveWindow()
0552 {
0553     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_ACTIVE_WINDOW"));
0554     QVERIFY(connection());
0555     NETRootInfo
0556         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0557     QVERIFY(rootInfo.activeWindow() == XCB_WINDOW_NONE);
0558     // rootinfo doesn't verify whether our window is a window, so we just generate an ID
0559     xcb_window_t activeWindow = xcb_generate_id(connection());
0560     rootInfo.setActiveWindow(activeWindow);
0561     QCOMPARE(rootInfo.activeWindow(), activeWindow);
0562 
0563     // compare with the X property
0564     QVERIFY(atom != XCB_ATOM_NONE);
0565     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_WINDOW, 0, 1);
0566     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
0567     QVERIFY(reply);
0568     QCOMPARE(reply->format, uint8_t(32));
0569     QCOMPARE(reply->value_len, uint32_t(1));
0570     QCOMPARE(reinterpret_cast<xcb_window_t *>(xcb_get_property_value(reply.get()))[0], activeWindow);
0571 
0572     // wait for our property
0573     waitForPropertyChange(&rootInfo, atom, NET::ActiveWindow);
0574     QCOMPARE(rootInfo.activeWindow(), activeWindow);
0575 }
0576 
0577 void NetRootInfoTestWM::testDesktopGeometry()
0578 {
0579     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_DESKTOP_GEOMETRY"));
0580     QVERIFY(connection());
0581     NETRootInfo
0582         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0583     QCOMPARE(rootInfo.desktopGeometry().width, 0);
0584     QCOMPARE(rootInfo.desktopGeometry().height, 0);
0585 
0586     NETSize size;
0587     size.width = 1000;
0588     size.height = 800;
0589     rootInfo.setDesktopGeometry(size);
0590     QCOMPARE(rootInfo.desktopGeometry().width, size.width);
0591     QCOMPARE(rootInfo.desktopGeometry().height, size.height);
0592 
0593     // compare with the X property
0594     QVERIFY(atom != XCB_ATOM_NONE);
0595     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 2);
0596     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
0597     QVERIFY(reply);
0598     QCOMPARE(reply->format, uint8_t(32));
0599     QCOMPARE(reply->value_len, uint32_t(2));
0600     uint32_t *data = reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()));
0601     QCOMPARE(data[0], uint32_t(size.width));
0602     QCOMPARE(data[1], uint32_t(size.height));
0603 
0604     // wait for our property
0605     waitForPropertyChange(&rootInfo, atom, NET::DesktopGeometry);
0606     QCOMPARE(rootInfo.desktopGeometry().width, size.width);
0607     QCOMPARE(rootInfo.desktopGeometry().height, size.height);
0608 }
0609 
0610 void NetRootInfoTestWM::testDesktopLayout_data()
0611 {
0612     QTest::addColumn<NET::Orientation>("orientation");
0613     QTest::addColumn<QSize>("columnsRows");
0614     QTest::addColumn<NET::DesktopLayoutCorner>("corner");
0615 
0616     QTest::newRow("h/1/1/tl") << NET::OrientationHorizontal << QSize(1, 1) << NET::DesktopLayoutCornerTopLeft;
0617     QTest::newRow("h/1/0/tr") << NET::OrientationHorizontal << QSize(1, 0) << NET::DesktopLayoutCornerTopRight;
0618     QTest::newRow("h/0/1/bl") << NET::OrientationHorizontal << QSize(0, 1) << NET::DesktopLayoutCornerBottomLeft;
0619     QTest::newRow("h/1/2/br") << NET::OrientationHorizontal << QSize(1, 2) << NET::DesktopLayoutCornerBottomRight;
0620     QTest::newRow("v/3/2/tl") << NET::OrientationVertical << QSize(3, 2) << NET::DesktopLayoutCornerTopLeft;
0621     QTest::newRow("v/5/4/tr") << NET::OrientationVertical << QSize(5, 4) << NET::DesktopLayoutCornerTopRight;
0622     QTest::newRow("v/2/1/bl") << NET::OrientationVertical << QSize(2, 1) << NET::DesktopLayoutCornerBottomLeft;
0623     QTest::newRow("v/3/2/br") << NET::OrientationVertical << QSize(3, 2) << NET::DesktopLayoutCornerBottomRight;
0624 }
0625 
0626 void NetRootInfoTestWM::testDesktopLayout()
0627 {
0628     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_DESKTOP_LAYOUT"));
0629     QVERIFY(connection());
0630     NETRootInfo
0631         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0632     QFETCH(NET::Orientation, orientation);
0633     QFETCH(QSize, columnsRows);
0634     QFETCH(NET::DesktopLayoutCorner, corner);
0635 
0636     rootInfo.setDesktopLayout(orientation, columnsRows.width(), columnsRows.height(), corner);
0637     QCOMPARE(rootInfo.desktopLayoutOrientation(), orientation);
0638     QCOMPARE(rootInfo.desktopLayoutColumnsRows(), columnsRows);
0639     QCOMPARE(rootInfo.desktopLayoutCorner(), corner);
0640 
0641     // compare with the X property
0642     QVERIFY(atom != XCB_ATOM_NONE);
0643     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 4);
0644     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
0645     QVERIFY(reply);
0646     QCOMPARE(reply->format, uint8_t(32));
0647     QCOMPARE(reply->value_len, uint32_t(4));
0648     uint32_t *data = reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()));
0649     QCOMPARE(data[0], uint32_t(orientation));
0650     QCOMPARE(data[1], uint32_t(columnsRows.width()));
0651     QCOMPARE(data[2], uint32_t(columnsRows.height()));
0652     QCOMPARE(data[3], uint32_t(corner));
0653 
0654     // wait for our property
0655     waitForPropertyChange(&rootInfo, atom, NET::Property(0), NET::WM2DesktopLayout);
0656     QCOMPARE(rootInfo.desktopLayoutOrientation(), orientation);
0657     QCOMPARE(rootInfo.desktopLayoutColumnsRows(), columnsRows);
0658     QCOMPARE(rootInfo.desktopLayoutCorner(), corner);
0659 
0660     NETRootInfo info2(connection(), NET::WMAllProperties, NET::WM2AllProperties);
0661     QCOMPARE(info2.desktopLayoutColumnsRows(), columnsRows);
0662 }
0663 
0664 void NetRootInfoTestWM::testDesktopViewports()
0665 {
0666     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_DESKTOP_VIEWPORT"));
0667     QVERIFY(connection());
0668     NETRootInfo
0669         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0670     // we need to know the number of desktops, therefore setting it
0671     rootInfo.setNumberOfDesktops(4);
0672     NETPoint desktopOne;
0673     desktopOne.x = 100;
0674     desktopOne.y = 50;
0675     NETPoint desktopTwo;
0676     desktopTwo.x = 200;
0677     desktopTwo.y = 100;
0678     rootInfo.setDesktopViewport(1, desktopOne);
0679     rootInfo.setDesktopViewport(2, desktopTwo);
0680 
0681     const NETPoint compareZero = rootInfo.desktopViewport(0);
0682     QCOMPARE(compareZero.x, 0);
0683     QCOMPARE(compareZero.y, 0);
0684     const NETPoint compareOne = rootInfo.desktopViewport(1);
0685     QCOMPARE(compareOne.x, desktopOne.x);
0686     QCOMPARE(compareOne.y, desktopOne.y);
0687     const NETPoint compareTwo = rootInfo.desktopViewport(2);
0688     QCOMPARE(compareTwo.x, desktopTwo.x);
0689     QCOMPARE(compareTwo.y, desktopTwo.y);
0690     const NETPoint compareThree = rootInfo.desktopViewport(3);
0691     QCOMPARE(compareThree.x, 0);
0692     QCOMPARE(compareThree.y, 0);
0693 
0694     // compare with the X property
0695     QVERIFY(atom != XCB_ATOM_NONE);
0696     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 8);
0697     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
0698     QVERIFY(reply);
0699     QCOMPARE(reply->format, uint8_t(32));
0700     QCOMPARE(reply->value_len, uint32_t(8));
0701     uint32_t *data = reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()));
0702     QCOMPARE(data[0], uint32_t(desktopOne.x));
0703     QCOMPARE(data[1], uint32_t(desktopOne.y));
0704     QCOMPARE(data[2], uint32_t(desktopTwo.x));
0705     QCOMPARE(data[3], uint32_t(desktopTwo.y));
0706     QCOMPARE(data[4], uint32_t(0));
0707     QCOMPARE(data[5], uint32_t(0));
0708     QCOMPARE(data[6], uint32_t(0));
0709     QCOMPARE(data[7], uint32_t(0));
0710 
0711     // wait for our property - two events
0712     waitForPropertyChange(&rootInfo, atom, NET::DesktopViewport);
0713     waitForPropertyChange(&rootInfo, atom, NET::DesktopViewport);
0714     const NETPoint compareOne2 = rootInfo.desktopViewport(1);
0715     QCOMPARE(compareOne2.x, desktopOne.x);
0716     QCOMPARE(compareOne2.y, desktopOne.y);
0717     const NETPoint compareTwo2 = rootInfo.desktopViewport(2);
0718     QCOMPARE(compareTwo2.x, desktopTwo.x);
0719     QCOMPARE(compareTwo2.y, desktopTwo.y);
0720     const NETPoint compareThree2 = rootInfo.desktopViewport(3);
0721     QCOMPARE(compareThree2.x, 0);
0722     QCOMPARE(compareThree2.y, 0);
0723 }
0724 
0725 void NetRootInfoTestWM::testShowingDesktop_data()
0726 {
0727     QTest::addColumn<bool>("set");
0728     QTest::addColumn<uint32_t>("setValue");
0729 
0730     QTest::newRow("true") << true << uint32_t(1);
0731     QTest::newRow("false") << false << uint32_t(0);
0732 }
0733 
0734 void NetRootInfoTestWM::testShowingDesktop()
0735 {
0736     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_SHOWING_DESKTOP"));
0737     QVERIFY(connection());
0738     NETRootInfo
0739         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0740     QFETCH(bool, set);
0741     rootInfo.setShowingDesktop(set);
0742     QCOMPARE(rootInfo.showingDesktop(), set);
0743 
0744     // compare with the X property
0745     QVERIFY(atom != XCB_ATOM_NONE);
0746     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 1);
0747     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
0748     QVERIFY(reply);
0749     QCOMPARE(reply->format, uint8_t(32));
0750     QCOMPARE(reply->value_len, uint32_t(1));
0751     QTEST(reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()))[0], "setValue");
0752 
0753     // wait for our property
0754     waitForPropertyChange(&rootInfo, atom, NET::Property(0), NET::WM2ShowingDesktop);
0755     QCOMPARE(rootInfo.showingDesktop(), set);
0756 }
0757 
0758 void NetRootInfoTestWM::testWorkArea()
0759 {
0760     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_WORKAREA"));
0761     QVERIFY(connection());
0762     NETRootInfo
0763         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
0764     // we need to know the number of desktops, therefore setting it
0765     rootInfo.setNumberOfDesktops(4);
0766     NETRect desktopOne;
0767     desktopOne.pos.x = 10;
0768     desktopOne.pos.y = 5;
0769     desktopOne.size.width = 1000;
0770     desktopOne.size.height = 800;
0771     NETRect desktopTwo;
0772     desktopTwo.pos.x = 20;
0773     desktopTwo.pos.y = 10;
0774     desktopTwo.size.width = 800;
0775     desktopTwo.size.height = 750;
0776     rootInfo.setWorkArea(1, desktopOne);
0777     rootInfo.setWorkArea(2, desktopTwo);
0778 
0779     const NETRect compareZero = rootInfo.workArea(0);
0780     QCOMPARE(compareZero.pos.x, 0);
0781     QCOMPARE(compareZero.pos.y, 0);
0782     QCOMPARE(compareZero.size.width, 0);
0783     QCOMPARE(compareZero.size.height, 0);
0784     const NETRect compareOne = rootInfo.workArea(1);
0785     QCOMPARE(compareOne.pos.x, desktopOne.pos.x);
0786     QCOMPARE(compareOne.pos.y, desktopOne.pos.y);
0787     QCOMPARE(compareOne.size.width, desktopOne.size.width);
0788     QCOMPARE(compareOne.size.height, desktopOne.size.height);
0789     const NETRect compareTwo = rootInfo.workArea(2);
0790     QCOMPARE(compareTwo.pos.x, desktopTwo.pos.x);
0791     QCOMPARE(compareTwo.pos.y, desktopTwo.pos.y);
0792     QCOMPARE(compareTwo.size.width, desktopTwo.size.width);
0793     QCOMPARE(compareTwo.size.height, desktopTwo.size.height);
0794     const NETRect compareThree = rootInfo.workArea(3);
0795     QCOMPARE(compareThree.pos.x, 0);
0796     QCOMPARE(compareThree.pos.y, 0);
0797     QCOMPARE(compareThree.size.width, 0);
0798     QCOMPARE(compareThree.size.height, 0);
0799 
0800     // compare with the X property
0801     QVERIFY(atom != XCB_ATOM_NONE);
0802     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 16);
0803     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
0804     QVERIFY(reply);
0805     QCOMPARE(reply->format, uint8_t(32));
0806     QCOMPARE(reply->value_len, uint32_t(16));
0807     uint32_t *data = reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()));
0808     QCOMPARE(data[0], uint32_t(desktopOne.pos.x));
0809     QCOMPARE(data[1], uint32_t(desktopOne.pos.y));
0810     QCOMPARE(data[2], uint32_t(desktopOne.size.width));
0811     QCOMPARE(data[3], uint32_t(desktopOne.size.height));
0812     QCOMPARE(data[4], uint32_t(desktopTwo.pos.x));
0813     QCOMPARE(data[5], uint32_t(desktopTwo.pos.y));
0814     QCOMPARE(data[6], uint32_t(desktopTwo.size.width));
0815     QCOMPARE(data[7], uint32_t(desktopTwo.size.height));
0816     QCOMPARE(data[8], uint32_t(0));
0817     QCOMPARE(data[9], uint32_t(0));
0818     QCOMPARE(data[10], uint32_t(0));
0819     QCOMPARE(data[11], uint32_t(0));
0820     QCOMPARE(data[12], uint32_t(0));
0821     QCOMPARE(data[13], uint32_t(0));
0822     QCOMPARE(data[14], uint32_t(0));
0823     QCOMPARE(data[15], uint32_t(0));
0824 
0825     // wait for our property - two events
0826     waitForPropertyChange(&rootInfo, atom, NET::WorkArea);
0827     waitForPropertyChange(&rootInfo, atom, NET::WorkArea);
0828     const NETRect compareOne2 = rootInfo.workArea(1);
0829     QCOMPARE(compareOne2.pos.x, desktopOne.pos.x);
0830     QCOMPARE(compareOne2.pos.y, desktopOne.pos.y);
0831     QCOMPARE(compareOne2.size.width, desktopOne.size.width);
0832     QCOMPARE(compareOne2.size.height, desktopOne.size.height);
0833     const NETRect compareTwo2 = rootInfo.workArea(2);
0834     QCOMPARE(compareTwo2.pos.x, desktopTwo.pos.x);
0835     QCOMPARE(compareTwo2.pos.y, desktopTwo.pos.y);
0836     QCOMPARE(compareTwo2.size.width, desktopTwo.size.width);
0837     QCOMPARE(compareTwo2.size.height, desktopTwo.size.height);
0838     const NETRect compareThree2 = rootInfo.workArea(3);
0839     QCOMPARE(compareThree2.pos.x, 0);
0840     QCOMPARE(compareThree2.pos.y, 0);
0841     QCOMPARE(compareThree2.size.width, 0);
0842     QCOMPARE(compareThree2.size.height, 0);
0843 }
0844 
0845 void NetRootInfoTestWM::testDontCrashMapViewports()
0846 {
0847     QProcess p;
0848     const QString processName = QFINDTESTDATA("dontcrashmapviewport");
0849     QVERIFY(!processName.isEmpty());
0850 
0851     p.start(processName, QStringList());
0852     QVERIFY(p.waitForFinished());
0853     QCOMPARE(p.exitStatus(), QProcess::NormalExit);
0854     QCOMPARE(p.exitCode(), 0);
0855 }
0856 
0857 QTEST_GUILESS_MAIN(NetRootInfoTestWM)
0858 
0859 #include "netrootinfotestwm.moc"