/* SPDX-FileCopyrightText: 2013 Martin Gräßlin SPDX-License-Identifier: LGPL-2.1-or-later */ #include "nettesthelper.h" #include #include #include #include // system #include using Property = UniqueCPointer; Q_DECLARE_METATYPE(NET::Orientation) Q_DECLARE_METATYPE(NET::DesktopLayoutCorner) static const char *s_wmName = "netrootinfotest"; class NetRootInfoTestWM : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void testCtor(); void testSupported(); void testClientList(); void testClientListStacking(); void testNumberOfDesktops(); void testCurrentDesktop(); void testDesktopNames(); void testDesktopLayout_data(); void testDesktopLayout(); void testDesktopGeometry(); void testDesktopViewports(); void testShowingDesktop_data(); void testShowingDesktop(); void testWorkArea(); void testActiveWindow(); void testVirtualRoots(); void testDontCrashMapViewports(); private: void waitForPropertyChange(NETRootInfo *info, xcb_atom_t atom, NET::Property prop, NET::Property2 prop2 = NET::Property2(0)); xcb_connection_t *connection() { return m_connection; } xcb_connection_t *m_connection; QList m_connections; std::unique_ptr m_xvfb; xcb_window_t m_supportWindow; xcb_window_t m_rootWindow; }; void NetRootInfoTestWM::cleanupTestCase() { while (!m_connections.isEmpty()) { xcb_disconnect(m_connections.takeFirst()); } } void NetRootInfoTestWM::initTestCase() { } void NetRootInfoTestWM::init() { // first reset just to be sure m_connection = nullptr; m_supportWindow = XCB_WINDOW_NONE; // start Xvfb const QString xfvbExec = QStandardPaths::findExecutable(QStringLiteral("Xvfb")); QVERIFY(!xfvbExec.isEmpty()); m_xvfb.reset(new QProcess); // use pipe to pass fd to Xvfb to get back the display id int pipeFds[2]; QVERIFY(pipe(pipeFds) == 0); m_xvfb->start(xfvbExec, QStringList{QStringLiteral("-displayfd"), QString::number(pipeFds[1])}); QVERIFY(m_xvfb->waitForStarted()); QCOMPARE(m_xvfb->state(), QProcess::Running); // reads from pipe, closes write side close(pipeFds[1]); QFile readPipe; QVERIFY(readPipe.open(pipeFds[0], QIODevice::ReadOnly, QFileDevice::AutoCloseHandle)); QByteArray displayNumber = readPipe.readLine(); readPipe.close(); displayNumber.prepend(QByteArray(":")); displayNumber.remove(displayNumber.size() - 1, 1); // create X connection int screen = 0; m_connection = xcb_connect(displayNumber.constData(), &screen); QVERIFY(m_connection); QVERIFY(!xcb_connection_has_error(m_connection)); m_rootWindow = KXUtils::rootWindow(m_connection, screen); uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE}; xcb_change_window_attributes(m_connection, m_rootWindow, XCB_CW_EVENT_MASK, values); // create support window values[0] = true; m_supportWindow = xcb_generate_id(m_connection); xcb_create_window(m_connection, XCB_COPY_FROM_PARENT, m_supportWindow, m_rootWindow, 0, 0, 1, 1, 0, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, XCB_CW_OVERRIDE_REDIRECT, values); const uint32_t lowerValues[] = {XCB_STACK_MODE_BELOW}; // we need to do the lower window with a roundtrip, otherwise NETRootInfo is not functioning UniqueCPointer error( xcb_request_check(m_connection, xcb_configure_window_checked(m_connection, m_supportWindow, XCB_CONFIG_WINDOW_STACK_MODE, lowerValues))); QVERIFY(!error); } void NetRootInfoTestWM::cleanup() { // destroy support window xcb_destroy_window(connection(), m_supportWindow); m_supportWindow = XCB_WINDOW_NONE; // close connection // delay till clenupTestCase as otherwise xcb reuses the same memory address m_connections << connection(); // kill Xvfb m_xvfb->terminate(); m_xvfb->waitForFinished(); m_xvfb.reset(); } void NetRootInfoTestWM::waitForPropertyChange(NETRootInfo *info, xcb_atom_t atom, NET::Property prop, NET::Property2 prop2) { while (true) { UniqueCPointer event(xcb_wait_for_event(connection())); if (!event) { break; } if ((event->response_type & ~0x80) != XCB_PROPERTY_NOTIFY) { continue; } xcb_property_notify_event_t *pe = reinterpret_cast(event.get()); if (pe->window != m_rootWindow) { continue; } if (pe->atom != atom) { continue; } NET::Properties dirty; NET::Properties2 dirty2; info->event(event.get(), &dirty, &dirty2); if (prop != 0) { QVERIFY(dirty & prop); } if (prop2 != 0) { QVERIFY(dirty2 & prop2); } if (!prop) { QCOMPARE(dirty, NET::Properties()); } if (!prop2) { QCOMPARE(dirty2, NET::Properties2()); } break; } } void NetRootInfoTestWM::testCtor() { QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); QCOMPARE(rootInfo.xcbConnection(), connection()); QCOMPARE(rootInfo.rootWindow(), m_rootWindow); QCOMPARE(rootInfo.supportWindow(), m_supportWindow); QCOMPARE(rootInfo.wmName(), s_wmName); QCOMPARE(rootInfo.supportedProperties(), NET::WMAllProperties); QCOMPARE(rootInfo.supportedProperties2(), NET::WM2AllProperties); QCOMPARE(rootInfo.supportedActions(), NET::Actions(~0u)); QCOMPARE(rootInfo.supportedStates(), NET::States(~0u)); QCOMPARE(rootInfo.supportedWindowTypes(), NET::AllTypesMask); QCOMPARE(rootInfo.passedProperties(), NET::WMAllProperties); QCOMPARE(rootInfo.passedProperties2(), NET::WM2AllProperties); QCOMPARE(rootInfo.passedActions(), NET::Actions(~0u)); QCOMPARE(rootInfo.passedStates(), NET::States(~0u)); QCOMPARE(rootInfo.passedWindowTypes(), NET::AllTypesMask); } void NetRootInfoTestWM::testSupported() { KXUtils::Atom supported(connection(), QByteArrayLiteral("_NET_SUPPORTED")); KXUtils::Atom wmCheck(connection(), QByteArrayLiteral("_NET_SUPPORTING_WM_CHECK")); KXUtils::Atom wmName(connection(), QByteArrayLiteral("_NET_WM_NAME")); KXUtils::Atom utf8String(connection(), QByteArrayLiteral("UTF8_STRING")); QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); int count = 0; for (int i = 0; i < 34; ++i) { if (i == 12) { continue; } QVERIFY(rootInfo.isSupported(NET::Property(1 << i))); count++; } for (int i = 0; i < 22; ++i) { QVERIFY(rootInfo.isSupported(NET::Property2(1 << i))); count++; } QVERIFY(rootInfo.isSupported(NET::WM2GTKShowWindowMenu)); count++; for (int i = 0; i < 17; ++i) { QVERIFY(rootInfo.isSupported(NET::WindowTypeMask(1 << i))); count++; } for (int i = 0; i < 13; ++i) { QVERIFY(rootInfo.isSupported(NET::State(1 << i))); count++; } for (int i = 0; i < 10; ++i) { QVERIFY(rootInfo.isSupported(NET::Action(1 << i))); count++; } // NET::WMFrameExtents has two properties count += 1; // XAWState, WMGeometry, WM2TransientFor, WM2GroupLeader, WM2WindowClass, WM2WindowRole, WM2ClientMachine count -= 7; // WM2BlockCompositing has 3 properties count += 2; // Add _GTK_FRAME_EXTENTS ++count; QVERIFY(supported != XCB_ATOM_NONE); QVERIFY(utf8String != XCB_ATOM_NONE); QVERIFY(wmCheck != XCB_ATOM_NONE); QVERIFY(wmName != XCB_ATOM_NONE); // we should have got some events waitForPropertyChange(&rootInfo, supported, NET::Supported); waitForPropertyChange(&rootInfo, wmCheck, NET::SupportingWMCheck); // get the cookies of the things to check xcb_get_property_cookie_t supportedCookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), supported, XCB_ATOM_ATOM, 0, 101); xcb_get_property_cookie_t wmCheckRootCookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), wmCheck, XCB_ATOM_WINDOW, 0, 1); xcb_get_property_cookie_t wmCheckSupportWinCookie = xcb_get_property_unchecked(connection(), false, m_supportWindow, wmCheck, XCB_ATOM_WINDOW, 0, 1); xcb_get_property_cookie_t wmNameCookie = xcb_get_property_unchecked(connection(), false, m_supportWindow, wmName, utf8String, 0, 16); Property supportedReply(xcb_get_property_reply(connection(), supportedCookie, nullptr)); QVERIFY(supportedReply); QCOMPARE(supportedReply->format, uint8_t(32)); QCOMPARE(supportedReply->value_len, uint32_t(count)); // TODO: check that the correct atoms are set? Property wmCheckRootReply(xcb_get_property_reply(connection(), wmCheckRootCookie, nullptr)); QVERIFY(wmCheckRootReply); QCOMPARE(wmCheckRootReply->format, uint8_t(32)); QCOMPARE(wmCheckRootReply->value_len, uint32_t(1)); QCOMPARE(reinterpret_cast(xcb_get_property_value(wmCheckRootReply.get()))[0], m_supportWindow); Property wmCheckSupportReply(xcb_get_property_reply(connection(), wmCheckSupportWinCookie, nullptr)); QVERIFY(wmCheckSupportReply); QCOMPARE(wmCheckSupportReply->format, uint8_t(32)); QCOMPARE(wmCheckSupportReply->value_len, uint32_t(1)); QCOMPARE(reinterpret_cast(xcb_get_property_value(wmCheckSupportReply.get()))[0], m_supportWindow); Property wmNameReply(xcb_get_property_reply(connection(), wmNameCookie, nullptr)); QVERIFY(wmNameReply); QCOMPARE(wmNameReply->format, uint8_t(8)); QCOMPARE(wmNameReply->value_len, uint32_t(15)); QCOMPARE(reinterpret_cast(xcb_get_property_value(wmNameReply.get())), s_wmName); // disable some supported rootInfo.setSupported(NET::WMFrameExtents, false); rootInfo.setSupported(NET::WM2KDETemporaryRules, false); rootInfo.setSupported(NET::ActionChangeDesktop, false); rootInfo.setSupported(NET::FullScreen, false); QVERIFY(rootInfo.isSupported(NET::ToolbarMask)); QVERIFY(rootInfo.isSupported(NET::OnScreenDisplayMask)); QVERIFY(rootInfo.isSupported(NET::DockMask)); rootInfo.setSupported(NET::ToolbarMask, false); rootInfo.setSupported(NET::OnScreenDisplayMask, false); QVERIFY(!rootInfo.isSupported(NET::WMFrameExtents)); QVERIFY(!rootInfo.isSupported(NET::WM2KDETemporaryRules)); QVERIFY(!rootInfo.isSupported(NET::ActionChangeDesktop)); QVERIFY(!rootInfo.isSupported(NET::FullScreen)); QVERIFY(!rootInfo.isSupported(NET::ToolbarMask)); QVERIFY(!rootInfo.isSupported(NET::OnScreenDisplayMask)); QVERIFY(rootInfo.isSupported(NET::DockMask)); // lets get supported again supportedCookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), supported, XCB_ATOM_ATOM, 0, 90); supportedReply.reset(xcb_get_property_reply(connection(), supportedCookie, nullptr)); QVERIFY(supportedReply); QCOMPARE(supportedReply->format, uint8_t(32)); QCOMPARE(supportedReply->value_len, uint32_t(count - 7)); for (int i = 0; i < 5; ++i) { // we should have got some events waitForPropertyChange(&rootInfo, supported, NET::Supported); waitForPropertyChange(&rootInfo, wmCheck, NET::SupportingWMCheck); } // turn something off, just to get another event rootInfo.setSupported(NET::WM2BlockCompositing, false); // lets get supported again supportedCookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), supported, XCB_ATOM_ATOM, 0, 90); supportedReply.reset(xcb_get_property_reply(connection(), supportedCookie, nullptr)); QVERIFY(supportedReply); QCOMPARE(supportedReply->format, uint8_t(32)); QCOMPARE(supportedReply->value_len, uint32_t(count - 9)); NETRootInfo clientInfo(connection(), NET::Supported | NET::SupportingWMCheck); waitForPropertyChange(&clientInfo, supported, NET::Supported); waitForPropertyChange(&clientInfo, wmCheck, NET::SupportingWMCheck); } void NetRootInfoTestWM::testClientList() { KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_CLIENT_LIST")); QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); QCOMPARE(rootInfo.clientListCount(), 0); QVERIFY(!rootInfo.clientList()); xcb_window_t windows[] = {xcb_generate_id(connection()), xcb_generate_id(connection()), xcb_generate_id(connection()), xcb_generate_id(connection()), xcb_generate_id(connection())}; rootInfo.setClientList(windows, 5); QCOMPARE(rootInfo.clientListCount(), 5); const xcb_window_t *otherWins = rootInfo.clientList(); for (int i = 0; i < 5; ++i) { QCOMPARE(otherWins[i], windows[i]); } // compare with the X property QVERIFY(atom != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_WINDOW, 0, 5); Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); QVERIFY(reply); QCOMPARE(reply->format, uint8_t(32)); QCOMPARE(reply->value_len, uint32_t(5)); const xcb_window_t *propWins = reinterpret_cast(xcb_get_property_value(reply.get())); for (int i = 0; i < 5; ++i) { QCOMPARE(propWins[i], windows[i]); } // wait for our property NETRootInfo clientInfo(connection(), NET::Supported | NET::SupportingWMCheck | NET::ClientList); waitForPropertyChange(&clientInfo, atom, NET::ClientList); QCOMPARE(clientInfo.clientListCount(), 5); const xcb_window_t *otherWins2 = clientInfo.clientList(); for (int i = 0; i < 5; ++i) { QCOMPARE(otherWins2[i], windows[i]); } } void NetRootInfoTestWM::testClientListStacking() { KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_CLIENT_LIST_STACKING")); QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); QCOMPARE(rootInfo.clientListStackingCount(), 0); QVERIFY(!rootInfo.clientListStacking()); xcb_window_t windows[] = {xcb_generate_id(connection()), xcb_generate_id(connection()), xcb_generate_id(connection()), xcb_generate_id(connection()), xcb_generate_id(connection())}; rootInfo.setClientListStacking(windows, 5); QCOMPARE(rootInfo.clientListStackingCount(), 5); const xcb_window_t *otherWins = rootInfo.clientListStacking(); for (int i = 0; i < 5; ++i) { QCOMPARE(otherWins[i], windows[i]); } // compare with the X property QVERIFY(atom != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_WINDOW, 0, 5); Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); QVERIFY(reply); QCOMPARE(reply->format, uint8_t(32)); QCOMPARE(reply->value_len, uint32_t(5)); const xcb_window_t *propWins = reinterpret_cast(xcb_get_property_value(reply.get())); for (int i = 0; i < 5; ++i) { QCOMPARE(propWins[i], windows[i]); } // wait for our property waitForPropertyChange(&rootInfo, atom, NET::ClientListStacking); QCOMPARE(rootInfo.clientListStackingCount(), 5); const xcb_window_t *otherWins2 = rootInfo.clientListStacking(); for (int i = 0; i < 5; ++i) { QCOMPARE(otherWins2[i], windows[i]); } } void NetRootInfoTestWM::testVirtualRoots() { KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_VIRTUAL_ROOTS")); QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); QCOMPARE(rootInfo.virtualRootsCount(), 0); QVERIFY(!rootInfo.virtualRoots()); xcb_window_t windows[] = {xcb_generate_id(connection()), xcb_generate_id(connection()), xcb_generate_id(connection()), xcb_generate_id(connection()), xcb_generate_id(connection())}; rootInfo.setVirtualRoots(windows, 5); QCOMPARE(rootInfo.virtualRootsCount(), 5); const xcb_window_t *otherWins = rootInfo.virtualRoots(); for (int i = 0; i < 5; ++i) { QCOMPARE(otherWins[i], windows[i]); } // compare with the X property QVERIFY(atom != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_WINDOW, 0, 5); Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); QVERIFY(reply); QCOMPARE(reply->format, uint8_t(32)); QCOMPARE(reply->value_len, uint32_t(5)); const xcb_window_t *propWins = reinterpret_cast(xcb_get_property_value(reply.get())); for (int i = 0; i < 5; ++i) { QCOMPARE(propWins[i], windows[i]); } // wait for our property - reported to a Client NETRootInfo NETRootInfo clientInfo(connection(), NET::VirtualRoots); waitForPropertyChange(&clientInfo, atom, NET::VirtualRoots); QCOMPARE(rootInfo.virtualRootsCount(), 5); const xcb_window_t *otherWins2 = rootInfo.virtualRoots(); for (int i = 0; i < 5; ++i) { QCOMPARE(otherWins2[i], windows[i]); } } void NetRootInfoTestWM::testNumberOfDesktops() { KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_NUMBER_OF_DESKTOPS")); QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); QCOMPARE(rootInfo.numberOfDesktops(), 1); rootInfo.setNumberOfDesktops(4); QCOMPARE(rootInfo.numberOfDesktops(), 4); // compare with the X property QVERIFY(atom != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 1); Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); QVERIFY(reply); QCOMPARE(reply->format, uint8_t(32)); QCOMPARE(reply->value_len, uint32_t(1)); QCOMPARE(reinterpret_cast(xcb_get_property_value(reply.get()))[0], uint32_t(4)); // wait for our property waitForPropertyChange(&rootInfo, atom, NET::NumberOfDesktops); QCOMPARE(rootInfo.numberOfDesktops(), 4); } void NetRootInfoTestWM::testCurrentDesktop() { KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_CURRENT_DESKTOP")); // TODO: verify that current desktop cannot be higher than number of desktops QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); QCOMPARE(rootInfo.currentDesktop(), 1); rootInfo.setCurrentDesktop(5); QCOMPARE(rootInfo.currentDesktop(), 5); // compare with the X property QVERIFY(atom != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 1); Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); QVERIFY(reply); QCOMPARE(reply->format, uint8_t(32)); QCOMPARE(reply->value_len, uint32_t(1)); // note: API starts counting at 1, but property starts counting at 5, because of that subtracting one QCOMPARE(reinterpret_cast(xcb_get_property_value(reply.get()))[0], uint32_t(5 - 1)); // wait for our property waitForPropertyChange(&rootInfo, atom, NET::CurrentDesktop); QCOMPARE(rootInfo.currentDesktop(), 5); } void NetRootInfoTestWM::testDesktopNames() { KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_DESKTOP_NAMES")); KXUtils::Atom utf8String(connection(), QByteArrayLiteral("UTF8_STRING")); QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); QVERIFY(!rootInfo.desktopName(0)); QVERIFY(!rootInfo.desktopName(1)); QVERIFY(!rootInfo.desktopName(2)); rootInfo.setDesktopName(1, "foo"); rootInfo.setDesktopName(2, "bar"); rootInfo.setNumberOfDesktops(2); QCOMPARE(rootInfo.desktopName(1), "foo"); QCOMPARE(rootInfo.desktopName(2), "bar"); // compare with the X property QVERIFY(atom != XCB_ATOM_NONE); QVERIFY(utf8String != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, utf8String, 0, 10000); Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); QVERIFY(reply); QCOMPARE(reply->format, uint8_t(8)); QCOMPARE(reply->value_len, uint32_t(8)); QCOMPARE(reinterpret_cast(xcb_get_property_value(reply.get())), "foo\0bar"); // wait for our property waitForPropertyChange(&rootInfo, atom, NET::DesktopNames); QCOMPARE(rootInfo.desktopName(1), "foo"); QCOMPARE(rootInfo.desktopName(2), "bar"); // there should be two events waitForPropertyChange(&rootInfo, atom, NET::DesktopNames); QCOMPARE(rootInfo.desktopName(1), "foo"); QCOMPARE(rootInfo.desktopName(2), "bar"); } void NetRootInfoTestWM::testActiveWindow() { KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_ACTIVE_WINDOW")); QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); QVERIFY(rootInfo.activeWindow() == XCB_WINDOW_NONE); // rootinfo doesn't verify whether our window is a window, so we just generate an ID xcb_window_t activeWindow = xcb_generate_id(connection()); rootInfo.setActiveWindow(activeWindow); QCOMPARE(rootInfo.activeWindow(), activeWindow); // compare with the X property QVERIFY(atom != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_WINDOW, 0, 1); Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); QVERIFY(reply); QCOMPARE(reply->format, uint8_t(32)); QCOMPARE(reply->value_len, uint32_t(1)); QCOMPARE(reinterpret_cast(xcb_get_property_value(reply.get()))[0], activeWindow); // wait for our property waitForPropertyChange(&rootInfo, atom, NET::ActiveWindow); QCOMPARE(rootInfo.activeWindow(), activeWindow); } void NetRootInfoTestWM::testDesktopGeometry() { KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_DESKTOP_GEOMETRY")); QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); QCOMPARE(rootInfo.desktopGeometry().width, 0); QCOMPARE(rootInfo.desktopGeometry().height, 0); NETSize size; size.width = 1000; size.height = 800; rootInfo.setDesktopGeometry(size); QCOMPARE(rootInfo.desktopGeometry().width, size.width); QCOMPARE(rootInfo.desktopGeometry().height, size.height); // compare with the X property QVERIFY(atom != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 2); Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); QVERIFY(reply); QCOMPARE(reply->format, uint8_t(32)); QCOMPARE(reply->value_len, uint32_t(2)); uint32_t *data = reinterpret_cast(xcb_get_property_value(reply.get())); QCOMPARE(data[0], uint32_t(size.width)); QCOMPARE(data[1], uint32_t(size.height)); // wait for our property waitForPropertyChange(&rootInfo, atom, NET::DesktopGeometry); QCOMPARE(rootInfo.desktopGeometry().width, size.width); QCOMPARE(rootInfo.desktopGeometry().height, size.height); } void NetRootInfoTestWM::testDesktopLayout_data() { QTest::addColumn("orientation"); QTest::addColumn("columnsRows"); QTest::addColumn("corner"); QTest::newRow("h/1/1/tl") << NET::OrientationHorizontal << QSize(1, 1) << NET::DesktopLayoutCornerTopLeft; QTest::newRow("h/1/0/tr") << NET::OrientationHorizontal << QSize(1, 0) << NET::DesktopLayoutCornerTopRight; QTest::newRow("h/0/1/bl") << NET::OrientationHorizontal << QSize(0, 1) << NET::DesktopLayoutCornerBottomLeft; QTest::newRow("h/1/2/br") << NET::OrientationHorizontal << QSize(1, 2) << NET::DesktopLayoutCornerBottomRight; QTest::newRow("v/3/2/tl") << NET::OrientationVertical << QSize(3, 2) << NET::DesktopLayoutCornerTopLeft; QTest::newRow("v/5/4/tr") << NET::OrientationVertical << QSize(5, 4) << NET::DesktopLayoutCornerTopRight; QTest::newRow("v/2/1/bl") << NET::OrientationVertical << QSize(2, 1) << NET::DesktopLayoutCornerBottomLeft; QTest::newRow("v/3/2/br") << NET::OrientationVertical << QSize(3, 2) << NET::DesktopLayoutCornerBottomRight; } void NetRootInfoTestWM::testDesktopLayout() { KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_DESKTOP_LAYOUT")); QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); QFETCH(NET::Orientation, orientation); QFETCH(QSize, columnsRows); QFETCH(NET::DesktopLayoutCorner, corner); rootInfo.setDesktopLayout(orientation, columnsRows.width(), columnsRows.height(), corner); QCOMPARE(rootInfo.desktopLayoutOrientation(), orientation); QCOMPARE(rootInfo.desktopLayoutColumnsRows(), columnsRows); QCOMPARE(rootInfo.desktopLayoutCorner(), corner); // compare with the X property QVERIFY(atom != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 4); Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); QVERIFY(reply); QCOMPARE(reply->format, uint8_t(32)); QCOMPARE(reply->value_len, uint32_t(4)); uint32_t *data = reinterpret_cast(xcb_get_property_value(reply.get())); QCOMPARE(data[0], uint32_t(orientation)); QCOMPARE(data[1], uint32_t(columnsRows.width())); QCOMPARE(data[2], uint32_t(columnsRows.height())); QCOMPARE(data[3], uint32_t(corner)); // wait for our property waitForPropertyChange(&rootInfo, atom, NET::Property(0), NET::WM2DesktopLayout); QCOMPARE(rootInfo.desktopLayoutOrientation(), orientation); QCOMPARE(rootInfo.desktopLayoutColumnsRows(), columnsRows); QCOMPARE(rootInfo.desktopLayoutCorner(), corner); NETRootInfo info2(connection(), NET::WMAllProperties, NET::WM2AllProperties); QCOMPARE(info2.desktopLayoutColumnsRows(), columnsRows); } void NetRootInfoTestWM::testDesktopViewports() { KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_DESKTOP_VIEWPORT")); QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); // we need to know the number of desktops, therefore setting it rootInfo.setNumberOfDesktops(4); NETPoint desktopOne; desktopOne.x = 100; desktopOne.y = 50; NETPoint desktopTwo; desktopTwo.x = 200; desktopTwo.y = 100; rootInfo.setDesktopViewport(1, desktopOne); rootInfo.setDesktopViewport(2, desktopTwo); const NETPoint compareZero = rootInfo.desktopViewport(0); QCOMPARE(compareZero.x, 0); QCOMPARE(compareZero.y, 0); const NETPoint compareOne = rootInfo.desktopViewport(1); QCOMPARE(compareOne.x, desktopOne.x); QCOMPARE(compareOne.y, desktopOne.y); const NETPoint compareTwo = rootInfo.desktopViewport(2); QCOMPARE(compareTwo.x, desktopTwo.x); QCOMPARE(compareTwo.y, desktopTwo.y); const NETPoint compareThree = rootInfo.desktopViewport(3); QCOMPARE(compareThree.x, 0); QCOMPARE(compareThree.y, 0); // compare with the X property QVERIFY(atom != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 8); Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); QVERIFY(reply); QCOMPARE(reply->format, uint8_t(32)); QCOMPARE(reply->value_len, uint32_t(8)); uint32_t *data = reinterpret_cast(xcb_get_property_value(reply.get())); QCOMPARE(data[0], uint32_t(desktopOne.x)); QCOMPARE(data[1], uint32_t(desktopOne.y)); QCOMPARE(data[2], uint32_t(desktopTwo.x)); QCOMPARE(data[3], uint32_t(desktopTwo.y)); QCOMPARE(data[4], uint32_t(0)); QCOMPARE(data[5], uint32_t(0)); QCOMPARE(data[6], uint32_t(0)); QCOMPARE(data[7], uint32_t(0)); // wait for our property - two events waitForPropertyChange(&rootInfo, atom, NET::DesktopViewport); waitForPropertyChange(&rootInfo, atom, NET::DesktopViewport); const NETPoint compareOne2 = rootInfo.desktopViewport(1); QCOMPARE(compareOne2.x, desktopOne.x); QCOMPARE(compareOne2.y, desktopOne.y); const NETPoint compareTwo2 = rootInfo.desktopViewport(2); QCOMPARE(compareTwo2.x, desktopTwo.x); QCOMPARE(compareTwo2.y, desktopTwo.y); const NETPoint compareThree2 = rootInfo.desktopViewport(3); QCOMPARE(compareThree2.x, 0); QCOMPARE(compareThree2.y, 0); } void NetRootInfoTestWM::testShowingDesktop_data() { QTest::addColumn("set"); QTest::addColumn("setValue"); QTest::newRow("true") << true << uint32_t(1); QTest::newRow("false") << false << uint32_t(0); } void NetRootInfoTestWM::testShowingDesktop() { KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_SHOWING_DESKTOP")); QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); QFETCH(bool, set); rootInfo.setShowingDesktop(set); QCOMPARE(rootInfo.showingDesktop(), set); // compare with the X property QVERIFY(atom != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 1); Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); QVERIFY(reply); QCOMPARE(reply->format, uint8_t(32)); QCOMPARE(reply->value_len, uint32_t(1)); QTEST(reinterpret_cast(xcb_get_property_value(reply.get()))[0], "setValue"); // wait for our property waitForPropertyChange(&rootInfo, atom, NET::Property(0), NET::WM2ShowingDesktop); QCOMPARE(rootInfo.showingDesktop(), set); } void NetRootInfoTestWM::testWorkArea() { KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_WORKAREA")); QVERIFY(connection()); NETRootInfo rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u)); // we need to know the number of desktops, therefore setting it rootInfo.setNumberOfDesktops(4); NETRect desktopOne; desktopOne.pos.x = 10; desktopOne.pos.y = 5; desktopOne.size.width = 1000; desktopOne.size.height = 800; NETRect desktopTwo; desktopTwo.pos.x = 20; desktopTwo.pos.y = 10; desktopTwo.size.width = 800; desktopTwo.size.height = 750; rootInfo.setWorkArea(1, desktopOne); rootInfo.setWorkArea(2, desktopTwo); const NETRect compareZero = rootInfo.workArea(0); QCOMPARE(compareZero.pos.x, 0); QCOMPARE(compareZero.pos.y, 0); QCOMPARE(compareZero.size.width, 0); QCOMPARE(compareZero.size.height, 0); const NETRect compareOne = rootInfo.workArea(1); QCOMPARE(compareOne.pos.x, desktopOne.pos.x); QCOMPARE(compareOne.pos.y, desktopOne.pos.y); QCOMPARE(compareOne.size.width, desktopOne.size.width); QCOMPARE(compareOne.size.height, desktopOne.size.height); const NETRect compareTwo = rootInfo.workArea(2); QCOMPARE(compareTwo.pos.x, desktopTwo.pos.x); QCOMPARE(compareTwo.pos.y, desktopTwo.pos.y); QCOMPARE(compareTwo.size.width, desktopTwo.size.width); QCOMPARE(compareTwo.size.height, desktopTwo.size.height); const NETRect compareThree = rootInfo.workArea(3); QCOMPARE(compareThree.pos.x, 0); QCOMPARE(compareThree.pos.y, 0); QCOMPARE(compareThree.size.width, 0); QCOMPARE(compareThree.size.height, 0); // compare with the X property QVERIFY(atom != XCB_ATOM_NONE); xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 16); Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); QVERIFY(reply); QCOMPARE(reply->format, uint8_t(32)); QCOMPARE(reply->value_len, uint32_t(16)); uint32_t *data = reinterpret_cast(xcb_get_property_value(reply.get())); QCOMPARE(data[0], uint32_t(desktopOne.pos.x)); QCOMPARE(data[1], uint32_t(desktopOne.pos.y)); QCOMPARE(data[2], uint32_t(desktopOne.size.width)); QCOMPARE(data[3], uint32_t(desktopOne.size.height)); QCOMPARE(data[4], uint32_t(desktopTwo.pos.x)); QCOMPARE(data[5], uint32_t(desktopTwo.pos.y)); QCOMPARE(data[6], uint32_t(desktopTwo.size.width)); QCOMPARE(data[7], uint32_t(desktopTwo.size.height)); QCOMPARE(data[8], uint32_t(0)); QCOMPARE(data[9], uint32_t(0)); QCOMPARE(data[10], uint32_t(0)); QCOMPARE(data[11], uint32_t(0)); QCOMPARE(data[12], uint32_t(0)); QCOMPARE(data[13], uint32_t(0)); QCOMPARE(data[14], uint32_t(0)); QCOMPARE(data[15], uint32_t(0)); // wait for our property - two events waitForPropertyChange(&rootInfo, atom, NET::WorkArea); waitForPropertyChange(&rootInfo, atom, NET::WorkArea); const NETRect compareOne2 = rootInfo.workArea(1); QCOMPARE(compareOne2.pos.x, desktopOne.pos.x); QCOMPARE(compareOne2.pos.y, desktopOne.pos.y); QCOMPARE(compareOne2.size.width, desktopOne.size.width); QCOMPARE(compareOne2.size.height, desktopOne.size.height); const NETRect compareTwo2 = rootInfo.workArea(2); QCOMPARE(compareTwo2.pos.x, desktopTwo.pos.x); QCOMPARE(compareTwo2.pos.y, desktopTwo.pos.y); QCOMPARE(compareTwo2.size.width, desktopTwo.size.width); QCOMPARE(compareTwo2.size.height, desktopTwo.size.height); const NETRect compareThree2 = rootInfo.workArea(3); QCOMPARE(compareThree2.pos.x, 0); QCOMPARE(compareThree2.pos.y, 0); QCOMPARE(compareThree2.size.width, 0); QCOMPARE(compareThree2.size.height, 0); } void NetRootInfoTestWM::testDontCrashMapViewports() { QProcess p; const QString processName = QFINDTESTDATA("dontcrashmapviewport"); QVERIFY(!processName.isEmpty()); p.start(processName, QStringList()); QVERIFY(p.waitForFinished()); QCOMPARE(p.exitStatus(), QProcess::NormalExit); QCOMPARE(p.exitCode(), 0); } QTEST_GUILESS_MAIN(NetRootInfoTestWM) #include "netrootinfotestwm.moc"