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

1107 lines
40 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>;
// clang-format off
#define INFO NETWinInfo info(m_connection, m_testWindow, m_rootWindow, NET::WMAllProperties, NET::WM2AllProperties, NET::Client);
#define ATOM(name) \
KXUtils::Atom atom(connection(), QByteArrayLiteral(#name));
#define UTF8 KXUtils::Atom utf8String(connection(), QByteArrayLiteral("UTF8_STRING"));
#define GETPROP(type, length, formatSize) \
xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, m_testWindow, \
atom, type, 0, length); \
Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); \
QVERIFY(reply); \
QCOMPARE(reply->format, uint8_t(formatSize)); \
QCOMPARE(reply->value_len, uint32_t(length));
#define VERIFYDELETED(t) \
xcb_get_property_cookie_t cookieDeleted = xcb_get_property_unchecked(connection(), false, m_testWindow, \
atom, t, 0, 1); \
Property replyDeleted(xcb_get_property_reply(connection(), cookieDeleted, nullptr)); \
QVERIFY(replyDeleted); \
QVERIFY(replyDeleted->type == XCB_ATOM_NONE);
class NetWinInfoTestClient : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void init();
void cleanup();
void testBlockCompositing();
void testUserTime();
void testStartupId();
void testDesktopFileName();
void testAppMenuObjectPath();
void testAppMenuServiceName();
void testHandledIcons_data();
void testHandledIcons();
void testPid();
void testName();
void testIconName();
void testExtendedStrut();
void testIconGeometry();
void testWindowType_data();
void testWindowType();
void testActivities_data();
void testActivities();
void testWindowRole();
void testWindowClass();
void testClientMachine();
void testGroupLeader();
void testUrgency_data();
void testUrgency();
void testInput_data();
void testInput();
void testInitialMappingState_data();
void testInitialMappingState();
void testIconPixmap_data();
void testIconPixmap();
void testTransientFor();
void testProtocols_data();
void testProtocols();
void testOpaqueRegion_data();
void testOpaqueRegion();
private:
void performNameTest(xcb_atom_t atom, const char *(NETWinInfo:: *getter)(void)const, void (NETWinInfo:: *setter)(const char *), NET::Property property);
void waitForPropertyChange(NETWinInfo *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_rootWindow;
xcb_window_t m_testWindow;
};
void NetWinInfoTestClient::initTestCase()
{
}
void NetWinInfoTestClient::cleanupTestCase()
{
// close connection
while (!m_connections.isEmpty()) {
xcb_disconnect(m_connections.takeFirst());
}
}
void NetWinInfoTestClient::init()
{
// first reset just to be sure
m_connection = nullptr;
m_rootWindow = XCB_WINDOW_NONE;
m_testWindow = XCB_WINDOW_NONE;
const QString xfvbExec = QStandardPaths::findExecutable(QStringLiteral("Xvfb"));
QVERIFY(!xfvbExec.isEmpty());
// start Xvfb
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(QStringLiteral("Xvfb"), 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);
// create test window
m_testWindow = xcb_generate_id(m_connection);
uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE};
xcb_create_window(m_connection, XCB_COPY_FROM_PARENT, m_testWindow,
m_rootWindow,
0, 0, 100, 100, 0, XCB_COPY_FROM_PARENT,
XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values);
// and map it
xcb_map_window(m_connection, m_testWindow);
}
void NetWinInfoTestClient::cleanup()
{
// destroy test window
xcb_unmap_window(m_connection, m_testWindow);
xcb_destroy_window(m_connection, m_testWindow);
m_testWindow = XCB_WINDOW_NONE;
// delay till clenupTestCase as otherwise xcb reuses the same memory address
m_connections << connection();
// kill Xvfb
m_xvfb->terminate();
m_xvfb->waitForFinished();
}
void NetWinInfoTestClient::waitForPropertyChange(NETWinInfo *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_testWindow) {
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 NetWinInfoTestClient::testBlockCompositing()
{
QVERIFY(connection());
ATOM(_KDE_NET_WM_BLOCK_COMPOSITING)
INFO
QVERIFY(!info.isBlockingCompositing());
info.setBlockingCompositing(true);
QVERIFY(info.isBlockingCompositing());
// compare with the X property
QVERIFY(atom != XCB_ATOM_NONE);
GETPROP(XCB_ATOM_CARDINAL, 1, 32)
QCOMPARE(reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()))[0], uint32_t(1));
// and wait for our event
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2BlockCompositing);
QVERIFY(info.isBlockingCompositing());
// setting false should delete the property again
info.setBlockingCompositing(false);
QVERIFY(!info.isBlockingCompositing());
VERIFYDELETED(XCB_ATOM_CARDINAL)
// and wait for our event
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2BlockCompositing);
QVERIFY(!info.isBlockingCompositing());
}
void NetWinInfoTestClient::testUserTime()
{
QVERIFY(connection());
ATOM(_NET_WM_USER_TIME)
INFO
QCOMPARE(info.userTime(), uint32_t(-1));
info.setUserTime(500);
QCOMPARE(info.userTime(), uint32_t(500));
// compare with the X property
QVERIFY(atom != XCB_ATOM_NONE);
GETPROP(XCB_ATOM_CARDINAL, 1, 32)
QCOMPARE(reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()))[0], uint32_t(500));
// and wait for our event
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2UserTime);
QCOMPARE(info.userTime(), uint32_t(500));
}
void NetWinInfoTestClient::testStartupId()
{
QVERIFY(connection());
ATOM(_NET_STARTUP_ID)
UTF8
INFO
QVERIFY(!info.startupId());
info.setStartupId("foo");
QCOMPARE(info.startupId(), "foo");
// compare with the X property
QVERIFY(atom != XCB_ATOM_NONE);
QVERIFY(utf8String != XCB_ATOM_NONE);
GETPROP(utf8String, 3, 8)
QCOMPARE(reinterpret_cast<const char *>(xcb_get_property_value(reply.get())), "foo");
// and wait for our event
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2StartupId);
QCOMPARE(info.startupId(), "foo");
}
void NetWinInfoTestClient::testAppMenuObjectPath()
{
ATOM(_KDE_NET_WM_APPMENU_OBJECT_PATH)
INFO
QVERIFY(!info.appMenuObjectPath());
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow,
atom, XCB_ATOM_STRING, 8, 3, "foo");
xcb_flush(connection());
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2AppMenuObjectPath);
QCOMPARE(info.appMenuObjectPath(), "foo");
}
void NetWinInfoTestClient::testAppMenuServiceName()
{
ATOM(_KDE_NET_WM_APPMENU_SERVICE_NAME)
INFO
QVERIFY(!info.appMenuServiceName());
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow,
atom, XCB_ATOM_STRING, 8, 3, "foo");
xcb_flush(connection());
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2AppMenuServiceName);
QCOMPARE(info.appMenuServiceName(), "foo");
}
void NetWinInfoTestClient::testDesktopFileName()
{
QVERIFY(connection());
ATOM(_KDE_NET_WM_DESKTOP_FILE)
UTF8
INFO
QVERIFY(!info.desktopFileName());
info.setDesktopFileName("foo");
QCOMPARE(info.desktopFileName(), "foo");
// compare with the X property
QVERIFY(atom != XCB_ATOM_NONE);
QVERIFY(utf8String != XCB_ATOM_NONE);
GETPROP(utf8String, 3, 8)
QCOMPARE(reinterpret_cast<const char *>(xcb_get_property_value(reply.get())), "foo");
// and wait for our event
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2DesktopFileName);
QCOMPARE(info.desktopFileName(), "foo");
}
void NetWinInfoTestClient::testHandledIcons_data()
{
QTest::addColumn<bool>("handled");
QTest::addColumn<uint32_t>("value");
QTest::newRow("enabled") << true << uint32_t(1);
QTest::newRow("disabled") << false << uint32_t(0);
}
void NetWinInfoTestClient::testHandledIcons()
{
QVERIFY(connection());
ATOM(_NET_WM_HANDLED_ICONS)
INFO
QVERIFY(!info.handledIcons());
QFETCH(bool, handled);
info.setHandledIcons(handled);
QCOMPARE(info.handledIcons(), handled);
// compare with the X property
QVERIFY(atom != XCB_ATOM_NONE);
GETPROP(XCB_ATOM_CARDINAL, 1, 32)
QTEST(reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()))[0], "value");
// and wait for our event
waitForPropertyChange(&info, atom, NET::WMHandledIcons);
QCOMPARE(info.handledIcons(), handled);
}
void NetWinInfoTestClient::testPid()
{
QVERIFY(connection());
ATOM(_NET_WM_PID)
INFO
QCOMPARE(info.pid(), 0);
info.setPid(m_xvfb->processId());
QCOMPARE(info.pid(), m_xvfb->processId());
// compare with the X property
QVERIFY(atom != XCB_ATOM_NONE);
GETPROP(XCB_ATOM_CARDINAL, 1, 32)
QCOMPARE(reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()))[0], uint32_t(m_xvfb->processId()));
// and wait for our event
waitForPropertyChange(&info, atom, NET::WMPid);
QCOMPARE(info.pid(), m_xvfb->processId());
}
void NetWinInfoTestClient::performNameTest(xcb_atom_t atom, const char *(NETWinInfo:: *getter)(void)const, void (NETWinInfo:: *setter)(const char *), NET::Property property)
{
UTF8
INFO
QVERIFY(!(info.*getter)());
(info.*setter)("foo");
QCOMPARE((info.*getter)(), "foo");
// compare with the X property
QVERIFY(atom != XCB_ATOM_NONE);
QVERIFY(utf8String != XCB_ATOM_NONE);
GETPROP(utf8String, 3, 8)
QCOMPARE(reinterpret_cast<const char *>(xcb_get_property_value(reply.get())), "foo");
// and wait for our event
waitForPropertyChange(&info, atom, property);
QCOMPARE((info.*getter)(), "foo");
// delete the string
(info.*setter)("");
QCOMPARE((info.*getter)(), "");
VERIFYDELETED(utf8String)
// and wait for our event
waitForPropertyChange(&info, atom, property);
QVERIFY(!(info.*getter)());
// set it again, to ensure that we don't leak on tear-down
(info.*setter)("bar");
QCOMPARE((info.*getter)(), "bar");
xcb_flush(connection());
waitForPropertyChange(&info, atom, property);
QCOMPARE((info.*getter)(), "bar");
}
void NetWinInfoTestClient::testIconName()
{
QVERIFY(connection());
ATOM(_NET_WM_ICON_NAME)
performNameTest(atom, &NETWinInfo::iconName, &NETWinInfo::setIconName, NET::WMIconName);
}
void NetWinInfoTestClient::testName()
{
QVERIFY(connection());
ATOM(_NET_WM_NAME)
performNameTest(atom, &NETWinInfo::name, &NETWinInfo::setName, NET::WMName);
}
void NetWinInfoTestClient::testExtendedStrut()
{
QVERIFY(connection());
ATOM(_NET_WM_STRUT_PARTIAL)
INFO
NETExtendedStrut extents = info.extendedStrut();
QCOMPARE(extents.left_width, 0);
QCOMPARE(extents.right_width, 0);
QCOMPARE(extents.top_width, 0);
QCOMPARE(extents.bottom_width, 0);
QCOMPARE(extents.left_start, 0);
QCOMPARE(extents.left_end, 0);
QCOMPARE(extents.right_start, 0);
QCOMPARE(extents.right_end, 0);
QCOMPARE(extents.top_start, 0);
QCOMPARE(extents.top_end, 0);
QCOMPARE(extents.bottom_start, 0);
QCOMPARE(extents.bottom_end, 0);
NETExtendedStrut newExtents;
newExtents.left_width = 10;
newExtents.right_width = 20;
newExtents.top_width = 30;
newExtents.bottom_width = 40;
newExtents.left_start = 50;
newExtents.left_end = 60;
newExtents.right_start = 70;
newExtents.right_end = 80;
newExtents.top_start = 90;
newExtents.top_end = 91;
newExtents.bottom_start = 92;
newExtents.bottom_end = 93;
info.setExtendedStrut(newExtents);
extents = info.extendedStrut();
QCOMPARE(extents.left_width, newExtents.left_width);
QCOMPARE(extents.right_width, newExtents.right_width);
QCOMPARE(extents.top_width, newExtents.top_width);
QCOMPARE(extents.bottom_width, newExtents.bottom_width);
QCOMPARE(extents.left_start, newExtents.left_start);
QCOMPARE(extents.left_end, newExtents.left_end);
QCOMPARE(extents.right_start, newExtents.right_start);
QCOMPARE(extents.right_end, newExtents.right_end);
QCOMPARE(extents.top_start, newExtents.top_start);
QCOMPARE(extents.top_end, newExtents.top_end);
QCOMPARE(extents.bottom_start, newExtents.bottom_start);
QCOMPARE(extents.bottom_end, newExtents.bottom_end);
// compare with the X property
QVERIFY(atom != XCB_ATOM_NONE);
GETPROP(XCB_ATOM_CARDINAL, 12, 32)
uint32_t *data = reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()));
QCOMPARE(data[ 0], uint32_t(newExtents.left_width));
QCOMPARE(data[ 1], uint32_t(newExtents.right_width));
QCOMPARE(data[ 2], uint32_t(newExtents.top_width));
QCOMPARE(data[ 3], uint32_t(newExtents.bottom_width));
QCOMPARE(data[ 4], uint32_t(newExtents.left_start));
QCOMPARE(data[ 5], uint32_t(newExtents.left_end));
QCOMPARE(data[ 6], uint32_t(newExtents.right_start));
QCOMPARE(data[ 7], uint32_t(newExtents.right_end));
QCOMPARE(data[ 8], uint32_t(newExtents.top_start));
QCOMPARE(data[ 9], uint32_t(newExtents.top_end));
QCOMPARE(data[10], uint32_t(newExtents.bottom_start));
QCOMPARE(data[11], uint32_t(newExtents.bottom_end));
// and wait for our event
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2ExtendedStrut);
extents = info.extendedStrut();
QCOMPARE(extents.left_width, newExtents.left_width);
QCOMPARE(extents.right_width, newExtents.right_width);
QCOMPARE(extents.top_width, newExtents.top_width);
QCOMPARE(extents.bottom_width, newExtents.bottom_width);
QCOMPARE(extents.left_start, newExtents.left_start);
QCOMPARE(extents.left_end, newExtents.left_end);
QCOMPARE(extents.right_start, newExtents.right_start);
QCOMPARE(extents.right_end, newExtents.right_end);
QCOMPARE(extents.top_start, newExtents.top_start);
QCOMPARE(extents.top_end, newExtents.top_end);
QCOMPARE(extents.bottom_start, newExtents.bottom_start);
QCOMPARE(extents.bottom_end, newExtents.bottom_end);
}
void NetWinInfoTestClient::testIconGeometry()
{
QVERIFY(connection());
ATOM(_NET_WM_ICON_GEOMETRY)
INFO
NETRect geo = info.iconGeometry();
QCOMPARE(geo.pos.x, 0);
QCOMPARE(geo.pos.y, 0);
QCOMPARE(geo.size.width, 0);
QCOMPARE(geo.size.height, 0);
NETRect newGeo;
newGeo.pos.x = 10;
newGeo.pos.y = 20;
newGeo.size.width = 30;
newGeo.size.height = 40;
info.setIconGeometry(newGeo);
geo = info.iconGeometry();
QCOMPARE(geo.pos.x, newGeo.pos.x);
QCOMPARE(geo.pos.y, newGeo.pos.y);
QCOMPARE(geo.size.width, newGeo.size.width);
QCOMPARE(geo.size.height, newGeo.size.height);
// compare with the X property
QVERIFY(atom != XCB_ATOM_NONE);
GETPROP(XCB_ATOM_CARDINAL, 4, 32)
uint32_t *data = reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.get()));
QCOMPARE(data[0], uint32_t(newGeo.pos.x));
QCOMPARE(data[1], uint32_t(newGeo.pos.y));
QCOMPARE(data[2], uint32_t(newGeo.size.width));
QCOMPARE(data[3], uint32_t(newGeo.size.height));
// and wait for our event
waitForPropertyChange(&info, atom, NET::WMIconGeometry);
geo = info.iconGeometry();
QCOMPARE(geo.pos.x, newGeo.pos.x);
QCOMPARE(geo.pos.y, newGeo.pos.y);
QCOMPARE(geo.size.width, newGeo.size.width);
QCOMPARE(geo.size.height, newGeo.size.height);
}
Q_DECLARE_METATYPE(NET::WindowType)
void NetWinInfoTestClient::testWindowType_data()
{
QTest::addColumn<NET::WindowType>("type");
QTest::addColumn<int>("length");
QTest::addColumn<QByteArray>("typeAtom");
QTest::addColumn<QByteArray>("secondaryTypeAtom");
QTest::newRow("override") << NET::Override << 2 << QByteArrayLiteral("_KDE_NET_WM_WINDOW_TYPE_OVERRIDE") << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NORMAL");
QTest::newRow("TopMenu") << NET::TopMenu << 2 << QByteArrayLiteral("_KDE_NET_WM_WINDOW_TYPE_TOPMENU") << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_DOCK");
QTest::newRow("Utility") << NET::Utility << 2 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_UTILITY") << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_DIALOG");
QTest::newRow("Splash") << NET::Splash << 2 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_SPLASH") << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_DOCK");
// TODO: this should be 2
QTest::newRow("DropdownMenu") << NET::DropdownMenu << 1 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU") << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_MENU");
// TODO: this should be 2
QTest::newRow("PopupMenu") << NET::PopupMenu << 1 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_POPUP_MENU") << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_MENU");
// TODO: this should be 2
QTest::newRow("Notification") << NET::Notification << 1 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NOTIFICATION") << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_UTILITY");
QTest::newRow("Dialog") << NET::Dialog << 1 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_DIALOG") << QByteArray();
QTest::newRow("Menu") << NET::Menu << 1 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_MENU") << QByteArray();
QTest::newRow("Toolbar") << NET::Toolbar << 1 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_TOOLBAR") << QByteArray();
QTest::newRow("Dock") << NET::Dock << 1 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_DOCK") << QByteArray();
QTest::newRow("Desktop") << NET::Desktop << 1 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_DESKTOP") << QByteArray();
QTest::newRow("Tooltip") << NET::Tooltip << 1 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_TOOLTIP") << QByteArray();
QTest::newRow("ComboBox") << NET::ComboBox << 1 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_COMBO") << QByteArray();
QTest::newRow("DNDIcon") << NET::DNDIcon << 1 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_DND") << QByteArray();
QTest::newRow("Normal") << NET::Normal << 1 << QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NORMAL") << QByteArray();
QTest::newRow("OnScreenDisplay") << NET::OnScreenDisplay << 1 << QByteArrayLiteral("_KDE_NET_WM_WINDOW_TYPE_ON_SCREEN_DISPLAY") << QByteArray();
QTest::newRow("CriticalNotification") << NET::CriticalNotification << 1 << QByteArrayLiteral("_KDE_NET_WM_WINDOW_TYPE_CRITICAL_NOTIFICATION") << QByteArray();
QTest::newRow("AppletPopup") << NET::AppletPopup << 1 << QByteArrayLiteral("_KDE_NET_WM_WINDOW_TYPE_APPLET_POPUP") << QByteArray();
}
void NetWinInfoTestClient::testWindowType()
{
QVERIFY(connection());
ATOM(_NET_WM_WINDOW_TYPE)
INFO
QVERIFY(info.hasWindowType());
QVERIFY(!info.hasNETSupport());
QCOMPARE(info.windowType(NET::AllTypesMask), NET::Unknown);
QFETCH(NET::WindowType, type);
info.setWindowType(type);
// it does not update the internal type!
QCOMPARE(info.windowType(NET::AllTypesMask), NET::Unknown);
QFETCH(int, length);
QFETCH(QByteArray, typeAtom);
// compare the X property
KXUtils::Atom type1(connection(), typeAtom);
QVERIFY(atom != XCB_ATOM_NONE);
GETPROP(XCB_ATOM_ATOM, length, 32)
xcb_atom_t *atoms = reinterpret_cast<xcb_atom_t *>(xcb_get_property_value(reply.get()));
QCOMPARE(atoms[0], xcb_atom_t(type1));
if (reply->value_len > 1) {
QFETCH(QByteArray, secondaryTypeAtom);
KXUtils::Atom type2(connection(), secondaryTypeAtom);
QVERIFY(type2 != XCB_ATOM_NONE);
QCOMPARE(atoms[1], xcb_atom_t(type2));
}
waitForPropertyChange(&info, atom, NET::WMWindowType);
QCOMPARE(info.windowType(NET::AllTypesMask), type);
QVERIFY(info.hasNETSupport());
}
void NetWinInfoTestClient::testClientMachine()
{
QVERIFY(connection());
INFO
QVERIFY(!info.clientMachine());
// client machine needs to be changed using xcb
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow,
XCB_ATOM_WM_CLIENT_MACHINE, XCB_ATOM_STRING, 8, 9, "localhost");
xcb_flush(connection());
// only updated after event
waitForPropertyChange(&info, XCB_ATOM_WM_CLIENT_MACHINE, NET::Property(0), NET::WM2ClientMachine);
QCOMPARE(info.clientMachine(), "localhost");
}
void NetWinInfoTestClient::testGroupLeader()
{
QVERIFY(connection());
INFO
QVERIFY(info.groupLeader() == XCB_WINDOW_NONE);
// group leader needs to be changed through wm hints
uint32_t values[] = {
1 << 6, /* WindowGroupHint*/
1, /* Input */
1, /* Normal State */
XCB_NONE, /* icon pixmap */
XCB_NONE, /* icon window */
XCB_NONE, /* icon x */
XCB_NONE, /* icon y */
XCB_NONE, /* icon mask */
m_rootWindow /* group leader */
};
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow,
XCB_ATOM_WM_HINTS, XCB_ATOM_WM_HINTS, 32, 9, values);
xcb_flush(connection());
// only updated after event
waitForPropertyChange(&info, XCB_ATOM_WM_HINTS, NET::Property(0), NET::WM2GroupLeader);
QCOMPARE(info.groupLeader(), m_rootWindow);
}
void NetWinInfoTestClient::testUrgency_data()
{
QTest::addColumn<quint32>("flags");
QTest::addColumn<bool>("expected");
QTest::newRow("urgency") << quint32(1 << 8) << true;
QTest::newRow("none") << quint32(0) << false;
QTest::newRow("group_urgency") << quint32((1 << 6) | (1 << 8)) << true;
QTest::newRow("input") << quint32(1) << false;
}
void NetWinInfoTestClient::testUrgency()
{
QVERIFY(connection());
INFO
QVERIFY(!info.urgency());
QFETCH(quint32, flags);
// group leader needs to be changed through wm hints
uint32_t values[] = {
flags,
1, /* Input */
1, /* Normal State */
XCB_NONE, /* icon pixmap */
XCB_NONE, /* icon window */
XCB_NONE, /* icon x */
XCB_NONE, /* icon y */
XCB_NONE, /* icon mask */
XCB_NONE /* group leader */
};
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow,
XCB_ATOM_WM_HINTS, XCB_ATOM_WM_HINTS, 32, 9, values);
xcb_flush(connection());
// only updated after event
waitForPropertyChange(&info, XCB_ATOM_WM_HINTS, NET::Property(0), NET::WM2Urgency);
QTEST(info.urgency(), "expected");
}
void NetWinInfoTestClient::testInput_data()
{
QTest::addColumn<quint32>("flags");
QTest::addColumn<quint32>("input");
QTest::addColumn<bool>("expected");
QTest::newRow("flag_input") << quint32(1) << quint32(1) << true;
QTest::newRow("flag_noinput") << quint32(1) << quint32(0) << false;
QTest::newRow("noflag_input") << quint32(0) << quint32(1) << true;
QTest::newRow("noflag_noinput") << quint32(0) << quint32(0) << true;
QTest::newRow("flag_with_other_input") << quint32(1 | 1 << 8) << quint32(1) << true;
QTest::newRow("flag_with_other_noinput") << quint32(1 | 1 << 8) << quint32(0) << false;
}
void NetWinInfoTestClient::testInput()
{
QVERIFY(connection());
INFO
QVERIFY(info.input());
QFETCH(quint32, flags);
QFETCH(quint32, input);
// group leader needs to be changed through wm hints
uint32_t values[] = {
flags,
input, /* Input */
1, /* Normal State */
XCB_NONE, /* icon pixmap */
XCB_NONE, /* icon window */
XCB_NONE, /* icon x */
XCB_NONE, /* icon y */
XCB_NONE, /* icon mask */
XCB_NONE /* group leader */
};
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow,
XCB_ATOM_WM_HINTS, XCB_ATOM_WM_HINTS, 32, 9, values);
xcb_flush(connection());
// only updated after event
waitForPropertyChange(&info, XCB_ATOM_WM_HINTS, NET::Property(0), NET::WM2Urgency);
QTEST(info.input(), "expected");
}
Q_DECLARE_METATYPE(NET::MappingState)
void NetWinInfoTestClient::testInitialMappingState_data()
{
QTest::addColumn<quint32>("flags");
QTest::addColumn<quint32>("state");
QTest::addColumn<NET::MappingState>("expected");
QTest::newRow("flag-iconic") << quint32(2) << quint32(3) << NET::Iconic;
QTest::newRow("flag-normal") << quint32(2) << quint32(1) << NET::Visible;
QTest::newRow("flag-invalid") << quint32(2) << quint32(8) << NET::Withdrawn;
QTest::newRow("noflag-iconic") << quint32(256) << quint32(3) << NET::Withdrawn;
QTest::newRow("noflag-normal") << quint32(256) << quint32(1) << NET::Withdrawn;
}
void NetWinInfoTestClient::testInitialMappingState()
{
QVERIFY(connection());
INFO
QCOMPARE(info.initialMappingState(), NET::Withdrawn);
QFETCH(quint32, flags);
QFETCH(quint32, state);
// group leader needs to be changed through wm hints
uint32_t values[] = {
flags,
1, /* Input */
state, /* Normal State */
XCB_NONE, /* icon pixmap */
XCB_NONE, /* icon window */
XCB_NONE, /* icon x */
XCB_NONE, /* icon y */
XCB_NONE, /* icon mask */
XCB_NONE /* group leader */
};
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow,
XCB_ATOM_WM_HINTS, XCB_ATOM_WM_HINTS, 32, 9, values);
xcb_flush(connection());
// only updated after event
waitForPropertyChange(&info, XCB_ATOM_WM_HINTS, NET::Property(0), NET::WM2InitialMappingState);
QTEST(info.initialMappingState(), "expected");
}
void NetWinInfoTestClient::testIconPixmap_data()
{
QTest::addColumn<quint32>("flags");
QTest::addColumn<quint32>("icon");
QTest::addColumn<quint32>("mask");
QTest::addColumn<quint32>("expectedPixmap");
QTest::addColumn<quint32>("expectedMask");
QTest::newRow("invalid-flags") << 1u << 2u << 3u << 0u << 0u;
QTest::newRow("pixmap-flags") << 4u << 2u << 3u << 2u << 0u;
QTest::newRow("mask-flags") << 32u << 2u << 3u << 0u << 3u;
QTest::newRow("pixmap-mask-flags") << 36u << 2u << 3u << 2u << 3u;
}
void NetWinInfoTestClient::testIconPixmap()
{
QVERIFY(connection());
INFO
QCOMPARE(info.icccmIconPixmap(), 0u);
QCOMPARE(info.icccmIconPixmapMask(), 0u);
QFETCH(quint32, flags);
QFETCH(quint32, icon);
QFETCH(quint32, mask);
// icon pixmap needs to be changed through wm hints
uint32_t values[] = {
flags,
1, /* Input */
XCB_NONE, /* Normal State */
icon, /* icon pixmap */
XCB_NONE, /* icon window */
XCB_NONE, /* icon x */
XCB_NONE, /* icon y */
mask, /* icon mask */
XCB_NONE /* group leader */
};
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow,
XCB_ATOM_WM_HINTS, XCB_ATOM_WM_HINTS, 32, 9, values);
xcb_flush(connection());
// only updated after event
waitForPropertyChange(&info, XCB_ATOM_WM_HINTS, NET::Property(0), NET::WM2IconPixmap);
QTEST(info.icccmIconPixmap(), "expectedPixmap");
QTEST(info.icccmIconPixmapMask(), "expectedMask");
}
void NetWinInfoTestClient::testTransientFor()
{
QVERIFY(connection());
INFO
QVERIFY(info.transientFor() == XCB_WINDOW_NONE);
// transient for needs to be changed using xcb
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow,
XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32, 1, &m_rootWindow);
xcb_flush(connection());
// only updated after event
waitForPropertyChange(&info, XCB_ATOM_WM_TRANSIENT_FOR, NET::Property(0), NET::WM2TransientFor);
QCOMPARE(info.transientFor(), m_rootWindow);
}
void NetWinInfoTestClient::testWindowClass()
{
QVERIFY(connection());
INFO
QVERIFY(!info.windowClassClass());
QVERIFY(!info.windowClassName());
// window class needs to be changed using xcb
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow,
XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, 7, "foo\0bar");
xcb_flush(connection());
// only updated after event
waitForPropertyChange(&info, XCB_ATOM_WM_CLASS, NET::Property(0), NET::WM2WindowClass);
QCOMPARE(info.windowClassName(), "foo");
QCOMPARE(info.windowClassClass(), "bar");
}
void NetWinInfoTestClient::testWindowRole()
{
QVERIFY(connection());
ATOM(WM_WINDOW_ROLE)
INFO
QVERIFY(!info.windowRole());
// window role needs to be changed using xcb
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow,
atom, XCB_ATOM_STRING, 8, 3, "bar");
xcb_flush(connection());
// only updated after event
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2WindowRole);
QCOMPARE(info.windowRole(), "bar");
}
void NetWinInfoTestClient::testActivities_data()
{
QTest::addColumn<QByteArray>("activities");
QTest::addColumn<QByteArray>("expectedActivities");
const QByteArray testActivities = QByteArrayLiteral("foo,bar");
const QByteArray allActivities = QByteArrayLiteral(KDE_ALL_ACTIVITIES_UUID);
QTest::newRow("activities") << testActivities << testActivities;
QTest::newRow("empty") << QByteArray() << allActivities;
QTest::newRow("\\0") << QByteArrayLiteral("\0") << allActivities;
}
void NetWinInfoTestClient::testActivities()
{
QVERIFY(connection());
ATOM(_KDE_NET_WM_ACTIVITIES)
INFO
QVERIFY(!info.activities());
QFETCH(QByteArray, activities);
// activities needs to be changed using xcb
info.setActivities(activities.isNull() ? nullptr : activities.constData());
xcb_flush(connection());
// only updated after event
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2Activities);
QTEST(QByteArray(info.activities()), "expectedActivities");
}
Q_DECLARE_METATYPE(NET::Protocols)
void NetWinInfoTestClient::testProtocols_data()
{
QTest::addColumn<NET::Protocols>("protocols");
QTest::addColumn<bool>("takeFocus");
QTest::addColumn<bool>("deleteWindow");
QTest::addColumn<bool>("ping");
QTest::addColumn<bool>("sync");
QTest::addColumn<bool>("context");
const NET::Protocol t = NET::TakeFocusProtocol;
const NET::Protocol d = NET::DeleteWindowProtocol;
const NET::Protocol p = NET::PingProtocol;
const NET::Protocol s = NET::SyncRequestProtocol;
const NET::Protocol c = NET::ContextHelpProtocol;
QTest::newRow("none") << NET::Protocols(NET::NoProtocol) << false << false << false << false << false;
QTest::newRow("t") << NET::Protocols(t) << true << false << false << false << false;
QTest::newRow("d") << NET::Protocols(d) << false << true << false << false << false;
QTest::newRow("p") << NET::Protocols(p) << false << false << true << false << false;
QTest::newRow("s") << NET::Protocols(s) << false << false << false << true << false;
QTest::newRow("c") << NET::Protocols(c) << false << false << false << false << true;
// all two combinations with t
QTest::newRow("t/d") << NET::Protocols(t | d) << true << true << false << false << false;
QTest::newRow("t/p") << NET::Protocols(t | p) << true << false << true << false << false;
QTest::newRow("t/s") << NET::Protocols(t | s) << true << false << false << true << false;
QTest::newRow("t/c") << NET::Protocols(t | c) << true << false << false << false << true;
// all two combinations with d
QTest::newRow("d/p") << NET::Protocols(d | p) << false << true << true << false << false;
QTest::newRow("d/s") << NET::Protocols(d | s) << false << true << false << true << false;
QTest::newRow("d/c") << NET::Protocols(d | c) << false << true << false << false << true;
// all two combinations with p
QTest::newRow("p/s") << NET::Protocols(p | s) << false << false << true << true << false;
QTest::newRow("p/c") << NET::Protocols(p | c) << false << false << true << false << true;
// and remaining two combination
QTest::newRow("s/c") << NET::Protocols(s | c) << false << false << false << true << true;
// all three combinations with t
QTest::newRow("t/d/p") << NET::Protocols(t | d | p) << true << true << true << false << false;
QTest::newRow("t/d/s") << NET::Protocols(t | d | s) << true << true << false << true << false;
QTest::newRow("t/d/c") << NET::Protocols(t | d | c) << true << true << false << false << true;
QTest::newRow("t/p/s") << NET::Protocols(t | p | s) << true << false << true << true << false;
QTest::newRow("t/p/c") << NET::Protocols(t | p | c) << true << false << true << false << true;
QTest::newRow("t/s/c") << NET::Protocols(t | s | c) << true << false << false << true << true;
// all three combinations with d
QTest::newRow("d/p/s") << NET::Protocols(d | p | s) << false << true << true << true << false;
QTest::newRow("d/p/c") << NET::Protocols(d | p | c) << false << true << true << false << true;
QTest::newRow("d/s/c") << NET::Protocols(d | s | c) << false << true << false << true << true;
// and remaining
QTest::newRow("p/s/c") << NET::Protocols(p | s | c) << false << false << true << true << true;
QTest::newRow("t/d/p/s") << NET::Protocols(t | d | p | s) << true << true << true << true << false;
QTest::newRow("t/d/p/c") << NET::Protocols(t | d | p | c) << true << true << true << false << true;
QTest::newRow("t/d/s/c") << NET::Protocols(t | d | s | c) << true << true << false << true << true;
QTest::newRow("t/p/s/c") << NET::Protocols(t | p | s | c) << true << false << true << true << true;
QTest::newRow("d/p/s/c") << NET::Protocols(d | p | s | c) << false << true << true << true << true;
QTest::newRow("all") << NET::Protocols(t | d | p | s | c) << true << true << true << true << true;
}
void NetWinInfoTestClient::testProtocols()
{
QVERIFY(connection());
ATOM(WM_PROTOCOLS)
KXUtils::Atom takeFocus(connection(), QByteArrayLiteral("WM_TAKE_FOCUS"));
KXUtils::Atom deleteWindow(connection(), QByteArrayLiteral("WM_DELETE_WINDOW"));
KXUtils::Atom ping(connection(), QByteArrayLiteral("_NET_WM_PING"));
KXUtils::Atom syncRequest(connection(), QByteArrayLiteral("_NET_WM_SYNC_REQUEST"));
KXUtils::Atom contextHelp(connection(), QByteArrayLiteral("_NET_WM_CONTEXT_HELP"));
INFO
QVERIFY(!info.supportsProtocol(NET::TakeFocusProtocol));
QVERIFY(!info.supportsProtocol(NET::DeleteWindowProtocol));
QVERIFY(!info.supportsProtocol(NET::PingProtocol));
QVERIFY(!info.supportsProtocol(NET::SyncRequestProtocol));
QVERIFY(!info.supportsProtocol(NET::ContextHelpProtocol));
QCOMPARE(info.protocols(), NET::Protocols(NET::NoProtocol));
QList<xcb_atom_t> props;
QFETCH(NET::Protocols, protocols);
if (protocols.testFlag(NET::TakeFocusProtocol)) {
props << takeFocus;
}
if (protocols.testFlag(NET::DeleteWindowProtocol)) {
props << deleteWindow;
}
if (protocols.testFlag(NET::PingProtocol)) {
props << ping;
}
if (protocols.testFlag(NET::SyncRequestProtocol)) {
props << syncRequest;
}
if (protocols.testFlag(NET::ContextHelpProtocol)) {
props << contextHelp;
}
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow, atom, XCB_ATOM_ATOM, 32, props.size(), props.constData());
xcb_flush(connection());
// only updated after event
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2Protocols);
QCOMPARE(info.protocols(), protocols);
QTEST(info.supportsProtocol(NET::TakeFocusProtocol), "takeFocus");
QTEST(info.supportsProtocol(NET::DeleteWindowProtocol), "deleteWindow");
QTEST(info.supportsProtocol(NET::PingProtocol), "ping");
QTEST(info.supportsProtocol(NET::SyncRequestProtocol), "sync");
QTEST(info.supportsProtocol(NET::ContextHelpProtocol), "context");
xcb_delete_property(connection(), m_testWindow, atom);
xcb_flush(connection());
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2Protocols);
QVERIFY(!info.supportsProtocol(NET::TakeFocusProtocol));
QVERIFY(!info.supportsProtocol(NET::DeleteWindowProtocol));
QVERIFY(!info.supportsProtocol(NET::PingProtocol));
QVERIFY(!info.supportsProtocol(NET::SyncRequestProtocol));
QVERIFY(!info.supportsProtocol(NET::ContextHelpProtocol));
QCOMPARE(info.protocols(), NET::Protocols(NET::NoProtocol));
}
void NetWinInfoTestClient::testOpaqueRegion_data()
{
QTest::addColumn<QList<QRect> >("geometries");
QTest::newRow("none") << QList<QRect>();
QTest::newRow("empty") << QList<QRect>({QRect(0, 0, 0, 0)});
QTest::newRow("one rect") << QList<QRect>({QRect(10, 20, 30, 40)});
QTest::newRow("two rect") << QList<QRect>({QRect(10, 20, 30, 40), QRect(1, 2, 4, 5)});
QTest::newRow("multiple") << QList<QRect>({QRect(10, 20, 30, 40),
QRect(1, 2, 4, 5),
QRect(100, 0, 200, 400),
QRect(1, 2, 4, 5)});
}
void NetWinInfoTestClient::testOpaqueRegion()
{
QVERIFY(connection());
ATOM(_NET_WM_OPAQUE_REGION)
INFO
QCOMPARE(info.opaqueRegion().size(), std::size_t(0));
QFETCH(QList<QRect>, geometries);
QList<qint32> data;
for (auto it = geometries.constBegin(); it != geometries.constEnd(); ++it) {
const QRect &r = *it;
data << r.x();
data << r.y();
data << r.width();
data << r.height();
}
xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow, atom, XCB_ATOM_CARDINAL, 32, data.size(), data.constData());
xcb_flush(connection());
// only updated after event
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2OpaqueRegion);
const auto opaqueRegion = info.opaqueRegion();
QCOMPARE(opaqueRegion.size(), std::size_t(geometries.size()));
for (std::size_t i = 0; i < opaqueRegion.size(); ++i) {
auto r1 = opaqueRegion.at(i);
auto r2 = geometries.at(i);
QCOMPARE(r1.pos.x, r2.x());
QCOMPARE(r1.pos.y, r2.y());
QCOMPARE(r1.size.width, r2.width());
QCOMPARE(r1.size.height, r2.height());
}
xcb_delete_property(connection(), m_testWindow, atom);
xcb_flush(connection());
waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2OpaqueRegion);
QCOMPARE(info.opaqueRegion().size(), std::size_t(0));
}
QTEST_GUILESS_MAIN(NetWinInfoTestClient)
#include "netwininfotestclient.moc"