File indexing completed on 2024-05-05 17:35:43

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2018 Martin Flöser <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0008 */
0009 #include "config-kwin.h"
0010 
0011 #include "kwin_wayland_test.h"
0012 
0013 #include "atoms.h"
0014 #include "core/outputbackend.h"
0015 #include "deleted.h"
0016 #include "rules.h"
0017 #include "virtualdesktops.h"
0018 #include "wayland_server.h"
0019 #include "window.h"
0020 #include "workspace.h"
0021 #include "x11window.h"
0022 
0023 #include <KWayland/Client/surface.h>
0024 
0025 #include <QDBusArgument>
0026 #include <QDBusConnection>
0027 #include <QDBusMessage>
0028 #include <QDBusPendingReply>
0029 #include <QUuid>
0030 
0031 #include <netwm.h>
0032 #include <xcb/xcb_icccm.h>
0033 
0034 using namespace KWin;
0035 
0036 static const QString s_socketName = QStringLiteral("wayland_test_kwin_dbus_interface-0");
0037 
0038 const QString s_destination{QStringLiteral("org.kde.KWin")};
0039 const QString s_path{QStringLiteral("/KWin")};
0040 const QString s_interface{QStringLiteral("org.kde.KWin")};
0041 
0042 class TestDbusInterface : public QObject
0043 {
0044     Q_OBJECT
0045 private Q_SLOTS:
0046     void initTestCase();
0047     void init();
0048     void cleanup();
0049 
0050     void testGetWindowInfoInvalidUuid();
0051     void testGetWindowInfoXdgShellClient();
0052     void testGetWindowInfoX11Client();
0053 };
0054 
0055 void TestDbusInterface::initTestCase()
0056 {
0057     qRegisterMetaType<KWin::Deleted *>();
0058     qRegisterMetaType<KWin::Window *>();
0059 
0060     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0061     QVERIFY(waylandServer()->init(s_socketName));
0062     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024)));
0063 
0064     kwinApp()->start();
0065     QVERIFY(applicationStartedSpy.wait());
0066     VirtualDesktopManager::self()->setCount(4);
0067 }
0068 
0069 void TestDbusInterface::init()
0070 {
0071     QVERIFY(Test::setupWaylandConnection());
0072 }
0073 
0074 void TestDbusInterface::cleanup()
0075 {
0076     Test::destroyWaylandConnection();
0077 }
0078 
0079 namespace
0080 {
0081 QDBusPendingCall getWindowInfo(const QUuid &uuid)
0082 {
0083     auto msg = QDBusMessage::createMethodCall(s_destination, s_path, s_interface, QStringLiteral("getWindowInfo"));
0084     msg.setArguments({uuid.toString()});
0085     return QDBusConnection::sessionBus().asyncCall(msg);
0086 }
0087 }
0088 
0089 void TestDbusInterface::testGetWindowInfoInvalidUuid()
0090 {
0091     QDBusPendingReply<QVariantMap> reply{getWindowInfo(QUuid::createUuid())};
0092     reply.waitForFinished();
0093     QVERIFY(reply.isValid());
0094     QVERIFY(!reply.isError());
0095     const auto windowData = reply.value();
0096     QVERIFY(windowData.empty());
0097 }
0098 
0099 void TestDbusInterface::testGetWindowInfoXdgShellClient()
0100 {
0101     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0102 
0103     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0104     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0105     shellSurface->set_app_id(QStringLiteral("org.kde.foo"));
0106     shellSurface->set_title(QStringLiteral("Test window"));
0107 
0108     // now let's render
0109     Test::render(surface.get(), QSize(100, 50), Qt::blue);
0110     QVERIFY(windowAddedSpy.isEmpty());
0111     QVERIFY(windowAddedSpy.wait());
0112     auto window = windowAddedSpy.first().first().value<Window *>();
0113     QVERIFY(window);
0114 
0115     const QVariantMap expectedData = {
0116         {QStringLiteral("type"), int(NET::Normal)},
0117         {QStringLiteral("x"), window->x()},
0118         {QStringLiteral("y"), window->y()},
0119         {QStringLiteral("width"), window->width()},
0120         {QStringLiteral("height"), window->height()},
0121         {QStringLiteral("desktops"), window->desktopIds()},
0122         {QStringLiteral("minimized"), false},
0123         {QStringLiteral("shaded"), false},
0124         {QStringLiteral("fullscreen"), false},
0125         {QStringLiteral("keepAbove"), false},
0126         {QStringLiteral("keepBelow"), false},
0127         {QStringLiteral("skipTaskbar"), false},
0128         {QStringLiteral("skipPager"), false},
0129         {QStringLiteral("skipSwitcher"), false},
0130         {QStringLiteral("maximizeHorizontal"), false},
0131         {QStringLiteral("maximizeVertical"), false},
0132         {QStringLiteral("noBorder"), false},
0133         {QStringLiteral("clientMachine"), QString()},
0134         {QStringLiteral("localhost"), true},
0135         {QStringLiteral("role"), QString()},
0136         {QStringLiteral("resourceName"), QStringLiteral("testDbusInterface")},
0137         {QStringLiteral("resourceClass"), QStringLiteral("org.kde.foo")},
0138         {QStringLiteral("desktopFile"), QStringLiteral("org.kde.foo")},
0139         {QStringLiteral("caption"), QStringLiteral("Test window")},
0140 #if KWIN_BUILD_ACTIVITIES
0141         {QStringLiteral("activities"), QStringList()},
0142 #endif
0143     };
0144 
0145     // let's get the window info
0146     QDBusPendingReply<QVariantMap> reply{getWindowInfo(window->internalId())};
0147     reply.waitForFinished();
0148     QVERIFY(reply.isValid());
0149     QVERIFY(!reply.isError());
0150     auto windowData = reply.value();
0151     windowData.remove(QStringLiteral("uuid"));
0152     QCOMPARE(windowData, expectedData);
0153 
0154     auto verifyProperty = [window](const QString &name) {
0155         QDBusPendingReply<QVariantMap> reply{getWindowInfo(window->internalId())};
0156         reply.waitForFinished();
0157         return reply.value().value(name).toBool();
0158     };
0159 
0160     QVERIFY(!window->isMinimized());
0161     window->setMinimized(true);
0162     QVERIFY(window->isMinimized());
0163     QCOMPARE(verifyProperty(QStringLiteral("minimized")), true);
0164 
0165     QVERIFY(!window->keepAbove());
0166     window->setKeepAbove(true);
0167     QVERIFY(window->keepAbove());
0168     QCOMPARE(verifyProperty(QStringLiteral("keepAbove")), true);
0169 
0170     QVERIFY(!window->keepBelow());
0171     window->setKeepBelow(true);
0172     QVERIFY(window->keepBelow());
0173     QCOMPARE(verifyProperty(QStringLiteral("keepBelow")), true);
0174 
0175     QVERIFY(!window->skipTaskbar());
0176     window->setSkipTaskbar(true);
0177     QVERIFY(window->skipTaskbar());
0178     QCOMPARE(verifyProperty(QStringLiteral("skipTaskbar")), true);
0179 
0180     QVERIFY(!window->skipPager());
0181     window->setSkipPager(true);
0182     QVERIFY(window->skipPager());
0183     QCOMPARE(verifyProperty(QStringLiteral("skipPager")), true);
0184 
0185     QVERIFY(!window->skipSwitcher());
0186     window->setSkipSwitcher(true);
0187     QVERIFY(window->skipSwitcher());
0188     QCOMPARE(verifyProperty(QStringLiteral("skipSwitcher")), true);
0189 
0190     // not testing shaded as that's X11
0191     // not testing fullscreen, maximizeHorizontal, maximizeVertical and noBorder as those require window geometry changes
0192 
0193     QCOMPARE(window->desktop(), 1);
0194     workspace()->sendWindowToDesktop(window, 2, false);
0195     QCOMPARE(window->desktop(), 2);
0196     reply = getWindowInfo(window->internalId());
0197     reply.waitForFinished();
0198     QCOMPARE(reply.value().value(QStringLiteral("desktops")).toStringList(), window->desktopIds());
0199 
0200     window->move(QPoint(10, 20));
0201     reply = getWindowInfo(window->internalId());
0202     reply.waitForFinished();
0203     QCOMPARE(reply.value().value(QStringLiteral("x")).toInt(), window->x());
0204     QCOMPARE(reply.value().value(QStringLiteral("y")).toInt(), window->y());
0205     // not testing width, height as that would require window geometry change
0206 
0207     // finally close window
0208     const auto id = window->internalId();
0209     QSignalSpy windowClosedSpy(window, &Window::windowClosed);
0210     shellSurface.reset();
0211     surface.reset();
0212     QVERIFY(windowClosedSpy.wait());
0213     QCOMPARE(windowClosedSpy.count(), 1);
0214 
0215     reply = getWindowInfo(id);
0216     reply.waitForFinished();
0217     QVERIFY(reply.value().empty());
0218 }
0219 
0220 struct XcbConnectionDeleter
0221 {
0222     void operator()(xcb_connection_t *pointer)
0223     {
0224         xcb_disconnect(pointer);
0225     }
0226 };
0227 
0228 void TestDbusInterface::testGetWindowInfoX11Client()
0229 {
0230     std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
0231     QVERIFY(!xcb_connection_has_error(c.get()));
0232     const QRect windowGeometry(0, 0, 600, 400);
0233     xcb_window_t windowId = xcb_generate_id(c.get());
0234     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0235                       windowGeometry.x(),
0236                       windowGeometry.y(),
0237                       windowGeometry.width(),
0238                       windowGeometry.height(),
0239                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0240     xcb_size_hints_t hints;
0241     memset(&hints, 0, sizeof(hints));
0242     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0243     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0244     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0245     xcb_icccm_set_wm_class(c.get(), windowId, 7, "foo\0bar");
0246     NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::Properties(), NET::Properties2());
0247     winInfo.setName("Some caption");
0248     winInfo.setDesktopFileName("org.kde.foo");
0249     xcb_map_window(c.get(), windowId);
0250     xcb_flush(c.get());
0251 
0252     // we should get a window for it
0253     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0254     QVERIFY(windowCreatedSpy.wait());
0255     X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
0256     QVERIFY(window);
0257     QCOMPARE(window->window(), windowId);
0258     QCOMPARE(window->clientSize(), windowGeometry.size());
0259 
0260     const QVariantMap expectedData = {
0261         {QStringLiteral("type"), NET::Normal},
0262         {QStringLiteral("x"), window->x()},
0263         {QStringLiteral("y"), window->y()},
0264         {QStringLiteral("width"), window->width()},
0265         {QStringLiteral("height"), window->height()},
0266         {QStringLiteral("desktops"), window->desktopIds()},
0267         {QStringLiteral("minimized"), false},
0268         {QStringLiteral("shaded"), false},
0269         {QStringLiteral("fullscreen"), false},
0270         {QStringLiteral("keepAbove"), false},
0271         {QStringLiteral("keepBelow"), false},
0272         {QStringLiteral("skipTaskbar"), false},
0273         {QStringLiteral("skipPager"), false},
0274         {QStringLiteral("skipSwitcher"), false},
0275         {QStringLiteral("maximizeHorizontal"), false},
0276         {QStringLiteral("maximizeVertical"), false},
0277         {QStringLiteral("noBorder"), false},
0278         {QStringLiteral("role"), QString()},
0279         {QStringLiteral("resourceName"), QStringLiteral("foo")},
0280         {QStringLiteral("resourceClass"), QStringLiteral("bar")},
0281         {QStringLiteral("desktopFile"), QStringLiteral("org.kde.foo")},
0282         {QStringLiteral("caption"), QStringLiteral("Some caption")},
0283 #if KWIN_BUILD_ACTIVITIES
0284         {QStringLiteral("activities"), QStringList()},
0285 #endif
0286     };
0287 
0288     // let's get the window info
0289     QDBusPendingReply<QVariantMap> reply{getWindowInfo(window->internalId())};
0290     reply.waitForFinished();
0291     QVERIFY(reply.isValid());
0292     QVERIFY(!reply.isError());
0293     auto windowData = reply.value();
0294     // not testing clientmachine as that is system dependent due to that also not testing localhost
0295     windowData.remove(QStringLiteral("clientMachine"));
0296     windowData.remove(QStringLiteral("localhost"));
0297     windowData.remove(QStringLiteral("uuid"));
0298     QCOMPARE(windowData, expectedData);
0299 
0300     auto verifyProperty = [window](const QString &name) {
0301         QDBusPendingReply<QVariantMap> reply{getWindowInfo(window->internalId())};
0302         reply.waitForFinished();
0303         return reply.value().value(name).toBool();
0304     };
0305 
0306     QVERIFY(!window->isMinimized());
0307     window->setMinimized(true);
0308     QVERIFY(window->isMinimized());
0309     QCOMPARE(verifyProperty(QStringLiteral("minimized")), true);
0310 
0311     QVERIFY(!window->keepAbove());
0312     window->setKeepAbove(true);
0313     QVERIFY(window->keepAbove());
0314     QCOMPARE(verifyProperty(QStringLiteral("keepAbove")), true);
0315 
0316     QVERIFY(!window->keepBelow());
0317     window->setKeepBelow(true);
0318     QVERIFY(window->keepBelow());
0319     QCOMPARE(verifyProperty(QStringLiteral("keepBelow")), true);
0320 
0321     QVERIFY(!window->skipTaskbar());
0322     window->setSkipTaskbar(true);
0323     QVERIFY(window->skipTaskbar());
0324     QCOMPARE(verifyProperty(QStringLiteral("skipTaskbar")), true);
0325 
0326     QVERIFY(!window->skipPager());
0327     window->setSkipPager(true);
0328     QVERIFY(window->skipPager());
0329     QCOMPARE(verifyProperty(QStringLiteral("skipPager")), true);
0330 
0331     QVERIFY(!window->skipSwitcher());
0332     window->setSkipSwitcher(true);
0333     QVERIFY(window->skipSwitcher());
0334     QCOMPARE(verifyProperty(QStringLiteral("skipSwitcher")), true);
0335 
0336     QVERIFY(!window->isShade());
0337     window->setShade(ShadeNormal);
0338     QVERIFY(window->isShade());
0339     QCOMPARE(verifyProperty(QStringLiteral("shaded")), true);
0340     window->setShade(ShadeNone);
0341     QVERIFY(!window->isShade());
0342 
0343     QVERIFY(!window->noBorder());
0344     window->setNoBorder(true);
0345     QVERIFY(window->noBorder());
0346     QCOMPARE(verifyProperty(QStringLiteral("noBorder")), true);
0347     window->setNoBorder(false);
0348     QVERIFY(!window->noBorder());
0349 
0350     QVERIFY(!window->isFullScreen());
0351     window->setFullScreen(true);
0352     QVERIFY(window->isFullScreen());
0353     QVERIFY(window->clientSize() != windowGeometry.size());
0354     QCOMPARE(verifyProperty(QStringLiteral("fullscreen")), true);
0355     reply = getWindowInfo(window->internalId());
0356     reply.waitForFinished();
0357     QCOMPARE(reply.value().value(QStringLiteral("width")).toInt(), window->width());
0358     QCOMPARE(reply.value().value(QStringLiteral("height")).toInt(), window->height());
0359     window->setFullScreen(false);
0360     QVERIFY(!window->isFullScreen());
0361 
0362     // maximize
0363     window->setMaximize(true, false);
0364     QCOMPARE(verifyProperty(QStringLiteral("maximizeVertical")), true);
0365     QCOMPARE(verifyProperty(QStringLiteral("maximizeHorizontal")), false);
0366     window->setMaximize(false, true);
0367     QCOMPARE(verifyProperty(QStringLiteral("maximizeVertical")), false);
0368     QCOMPARE(verifyProperty(QStringLiteral("maximizeHorizontal")), true);
0369 
0370     const auto id = window->internalId();
0371     // destroy the window
0372     xcb_unmap_window(c.get(), windowId);
0373     xcb_flush(c.get());
0374 
0375     QSignalSpy windowClosedSpy(window, &X11Window::windowClosed);
0376     QVERIFY(windowClosedSpy.wait());
0377     xcb_destroy_window(c.get(), windowId);
0378     c.reset();
0379 
0380     reply = getWindowInfo(id);
0381     reply.waitForFinished();
0382     QVERIFY(reply.value().empty());
0383 }
0384 
0385 WAYLANDTEST_MAIN(TestDbusInterface)
0386 #include "dbus_interface_test.moc"