Files
RedBear-OS/local/recipes/kde/kf6-kwindowsystem/source/autotests/netrootinfotestwm.cpp
T
2026-04-14 10:51:06 +01:00

860 lines
36 KiB
C++

/*
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "nettesthelper.h"
#include <netwm.h>
#include <QProcess>
#include <QStandardPaths>
#include <qtest_widgets.h>
// system
#include <unistd.h>
using Property = UniqueCPointer<xcb_get_property_reply_t>;
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<xcb_connection_t *> m_connections;
std::unique_ptr<QProcess> 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<xcb_generic_error_t> 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<xcb_generic_event_t> 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<xcb_property_notify_event_t *>(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_window_t *>(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_window_t *>(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<const char *>(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_window_t *>(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_window_t *>(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_window_t *>(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<uint32_t *>(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<uint32_t *>(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<const char *>(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_window_t *>(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<uint32_t *>(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<NET::Orientation>("orientation");
QTest::addColumn<QSize>("columnsRows");
QTest::addColumn<NET::DesktopLayoutCorner>("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<uint32_t *>(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<uint32_t *>(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<bool>("set");
QTest::addColumn<uint32_t>("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<uint32_t *>(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<uint32_t *>(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"