File indexing completed on 2024-05-12 05:30:31

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 "rules.h"
0015 #include "virtualdesktops.h"
0016 #include "wayland_server.h"
0017 #include "window.h"
0018 #include "workspace.h"
0019 #include "x11window.h"
0020 
0021 #include <KWayland/Client/surface.h>
0022 
0023 #include <QDBusArgument>
0024 #include <QDBusConnection>
0025 #include <QDBusMessage>
0026 #include <QDBusPendingReply>
0027 #include <QUuid>
0028 
0029 #include <netwm.h>
0030 #include <xcb/xcb_icccm.h>
0031 
0032 using namespace KWin;
0033 
0034 static const QString s_socketName = QStringLiteral("wayland_test_kwin_dbus_interface-0");
0035 
0036 const QString s_destination{QStringLiteral("org.kde.KWin")};
0037 const QString s_path{QStringLiteral("/KWin")};
0038 const QString s_interface{QStringLiteral("org.kde.KWin")};
0039 
0040 class TestDbusInterface : public QObject
0041 {
0042     Q_OBJECT
0043 private Q_SLOTS:
0044     void initTestCase();
0045     void init();
0046     void cleanup();
0047 
0048     void testGetWindowInfoInvalidUuid();
0049     void testGetWindowInfoXdgShellClient();
0050     void testGetWindowInfoX11Client();
0051 };
0052 
0053 void TestDbusInterface::initTestCase()
0054 {
0055     qRegisterMetaType<KWin::Window *>();
0056 
0057     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0058     QVERIFY(waylandServer()->init(s_socketName));
0059     Test::setOutputConfig({
0060         QRect(0, 0, 1280, 1024),
0061         QRect(1280, 0, 1280, 1024),
0062     });
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         {QStringLiteral("layer"), NormalLayer},
0144     };
0145 
0146     // let's get the window info
0147     QDBusPendingReply<QVariantMap> reply{getWindowInfo(window->internalId())};
0148     reply.waitForFinished();
0149     QVERIFY(reply.isValid());
0150     QVERIFY(!reply.isError());
0151     auto windowData = reply.value();
0152     windowData.remove(QStringLiteral("uuid"));
0153     QCOMPARE(windowData, expectedData);
0154 
0155     auto verifyProperty = [window](const QString &name) {
0156         QDBusPendingReply<QVariantMap> reply{getWindowInfo(window->internalId())};
0157         reply.waitForFinished();
0158         return reply.value().value(name).toBool();
0159     };
0160 
0161     QVERIFY(!window->isMinimized());
0162     window->setMinimized(true);
0163     QVERIFY(window->isMinimized());
0164     QCOMPARE(verifyProperty(QStringLiteral("minimized")), true);
0165 
0166     QVERIFY(!window->keepAbove());
0167     window->setKeepAbove(true);
0168     QVERIFY(window->keepAbove());
0169     QCOMPARE(verifyProperty(QStringLiteral("keepAbove")), true);
0170 
0171     QVERIFY(!window->keepBelow());
0172     window->setKeepBelow(true);
0173     QVERIFY(window->keepBelow());
0174     QCOMPARE(verifyProperty(QStringLiteral("keepBelow")), true);
0175 
0176     QVERIFY(!window->skipTaskbar());
0177     window->setSkipTaskbar(true);
0178     QVERIFY(window->skipTaskbar());
0179     QCOMPARE(verifyProperty(QStringLiteral("skipTaskbar")), true);
0180 
0181     QVERIFY(!window->skipPager());
0182     window->setSkipPager(true);
0183     QVERIFY(window->skipPager());
0184     QCOMPARE(verifyProperty(QStringLiteral("skipPager")), true);
0185 
0186     QVERIFY(!window->skipSwitcher());
0187     window->setSkipSwitcher(true);
0188     QVERIFY(window->skipSwitcher());
0189     QCOMPARE(verifyProperty(QStringLiteral("skipSwitcher")), true);
0190 
0191     // not testing shaded as that's X11
0192     // not testing fullscreen, maximizeHorizontal, maximizeVertical and noBorder as those require window geometry changes
0193 
0194     const QList<VirtualDesktop *> desktops = VirtualDesktopManager::self()->desktops();
0195     QCOMPARE(window->desktops(), QList<VirtualDesktop *>{desktops[0]});
0196     workspace()->sendWindowToDesktops(window, {desktops[1]}, false);
0197     QCOMPARE(window->desktops(), QList<VirtualDesktop *>{desktops[1]});
0198     reply = getWindowInfo(window->internalId());
0199     reply.waitForFinished();
0200     QCOMPARE(reply.value().value(QStringLiteral("desktops")).toStringList(), window->desktopIds());
0201 
0202     window->move(QPoint(10, 20));
0203     reply = getWindowInfo(window->internalId());
0204     reply.waitForFinished();
0205     QCOMPARE(reply.value().value(QStringLiteral("x")).toInt(), window->x());
0206     QCOMPARE(reply.value().value(QStringLiteral("y")).toInt(), window->y());
0207     // not testing width, height as that would require window geometry change
0208 
0209     // finally close window
0210     const auto id = window->internalId();
0211     QSignalSpy windowClosedSpy(window, &Window::closed);
0212     shellSurface.reset();
0213     surface.reset();
0214     QVERIFY(windowClosedSpy.wait());
0215     QCOMPARE(windowClosedSpy.count(), 1);
0216 
0217     reply = getWindowInfo(id);
0218     reply.waitForFinished();
0219     QVERIFY(reply.value().empty());
0220 }
0221 
0222 void TestDbusInterface::testGetWindowInfoX11Client()
0223 {
0224     Test::XcbConnectionPtr c = Test::createX11Connection();
0225     QVERIFY(!xcb_connection_has_error(c.get()));
0226     const QRect windowGeometry(0, 0, 600, 400);
0227     xcb_window_t windowId = xcb_generate_id(c.get());
0228     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0229                       windowGeometry.x(),
0230                       windowGeometry.y(),
0231                       windowGeometry.width(),
0232                       windowGeometry.height(),
0233                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0234     xcb_size_hints_t hints;
0235     memset(&hints, 0, sizeof(hints));
0236     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0237     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0238     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0239     xcb_icccm_set_wm_class(c.get(), windowId, 7, "foo\0bar");
0240     NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::Properties(), NET::Properties2());
0241     winInfo.setName("Some caption");
0242     winInfo.setDesktopFileName("org.kde.foo");
0243     xcb_map_window(c.get(), windowId);
0244     xcb_flush(c.get());
0245 
0246     // we should get a window for it
0247     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0248     QVERIFY(windowCreatedSpy.wait());
0249     X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
0250     QVERIFY(window);
0251     QCOMPARE(window->window(), windowId);
0252     QCOMPARE(window->clientSize(), windowGeometry.size());
0253 
0254     const QVariantMap expectedData = {
0255         {QStringLiteral("type"), NET::Normal},
0256         {QStringLiteral("x"), window->x()},
0257         {QStringLiteral("y"), window->y()},
0258         {QStringLiteral("width"), window->width()},
0259         {QStringLiteral("height"), window->height()},
0260         {QStringLiteral("desktops"), window->desktopIds()},
0261         {QStringLiteral("minimized"), false},
0262         {QStringLiteral("shaded"), false},
0263         {QStringLiteral("fullscreen"), false},
0264         {QStringLiteral("keepAbove"), false},
0265         {QStringLiteral("keepBelow"), false},
0266         {QStringLiteral("skipTaskbar"), false},
0267         {QStringLiteral("skipPager"), false},
0268         {QStringLiteral("skipSwitcher"), false},
0269         {QStringLiteral("maximizeHorizontal"), false},
0270         {QStringLiteral("maximizeVertical"), false},
0271         {QStringLiteral("noBorder"), false},
0272         {QStringLiteral("role"), QString()},
0273         {QStringLiteral("resourceName"), QStringLiteral("foo")},
0274         {QStringLiteral("resourceClass"), QStringLiteral("bar")},
0275         {QStringLiteral("desktopFile"), QStringLiteral("org.kde.foo")},
0276         {QStringLiteral("caption"), QStringLiteral("Some caption")},
0277 #if KWIN_BUILD_ACTIVITIES
0278         {QStringLiteral("activities"), QStringList()},
0279 #endif
0280         {QStringLiteral("layer"), NormalLayer},
0281     };
0282 
0283     // let's get the window info
0284     QDBusPendingReply<QVariantMap> reply{getWindowInfo(window->internalId())};
0285     reply.waitForFinished();
0286     QVERIFY(reply.isValid());
0287     QVERIFY(!reply.isError());
0288     auto windowData = reply.value();
0289     // not testing clientmachine as that is system dependent due to that also not testing localhost
0290     windowData.remove(QStringLiteral("clientMachine"));
0291     windowData.remove(QStringLiteral("localhost"));
0292     windowData.remove(QStringLiteral("uuid"));
0293     QCOMPARE(windowData, expectedData);
0294 
0295     auto verifyProperty = [window](const QString &name) {
0296         QDBusPendingReply<QVariantMap> reply{getWindowInfo(window->internalId())};
0297         reply.waitForFinished();
0298         return reply.value().value(name).toBool();
0299     };
0300 
0301     QVERIFY(!window->isMinimized());
0302     window->setMinimized(true);
0303     QVERIFY(window->isMinimized());
0304     QCOMPARE(verifyProperty(QStringLiteral("minimized")), true);
0305 
0306     QVERIFY(!window->keepAbove());
0307     window->setKeepAbove(true);
0308     QVERIFY(window->keepAbove());
0309     QCOMPARE(verifyProperty(QStringLiteral("keepAbove")), true);
0310 
0311     QVERIFY(!window->keepBelow());
0312     window->setKeepBelow(true);
0313     QVERIFY(window->keepBelow());
0314     QCOMPARE(verifyProperty(QStringLiteral("keepBelow")), true);
0315 
0316     QVERIFY(!window->skipTaskbar());
0317     window->setSkipTaskbar(true);
0318     QVERIFY(window->skipTaskbar());
0319     QCOMPARE(verifyProperty(QStringLiteral("skipTaskbar")), true);
0320 
0321     QVERIFY(!window->skipPager());
0322     window->setSkipPager(true);
0323     QVERIFY(window->skipPager());
0324     QCOMPARE(verifyProperty(QStringLiteral("skipPager")), true);
0325 
0326     QVERIFY(!window->skipSwitcher());
0327     window->setSkipSwitcher(true);
0328     QVERIFY(window->skipSwitcher());
0329     QCOMPARE(verifyProperty(QStringLiteral("skipSwitcher")), true);
0330 
0331     QVERIFY(!window->isShade());
0332     window->setShade(ShadeNormal);
0333     QVERIFY(window->isShade());
0334     QCOMPARE(verifyProperty(QStringLiteral("shaded")), true);
0335     window->setShade(ShadeNone);
0336     QVERIFY(!window->isShade());
0337 
0338     QVERIFY(!window->noBorder());
0339     window->setNoBorder(true);
0340     QVERIFY(window->noBorder());
0341     QCOMPARE(verifyProperty(QStringLiteral("noBorder")), true);
0342     window->setNoBorder(false);
0343     QVERIFY(!window->noBorder());
0344 
0345     QVERIFY(!window->isFullScreen());
0346     window->setFullScreen(true);
0347     QVERIFY(window->isFullScreen());
0348     QVERIFY(window->clientSize() != windowGeometry.size());
0349     QCOMPARE(verifyProperty(QStringLiteral("fullscreen")), true);
0350     reply = getWindowInfo(window->internalId());
0351     reply.waitForFinished();
0352     QCOMPARE(reply.value().value(QStringLiteral("width")).toInt(), window->width());
0353     QCOMPARE(reply.value().value(QStringLiteral("height")).toInt(), window->height());
0354     window->setFullScreen(false);
0355     QVERIFY(!window->isFullScreen());
0356 
0357     // maximize
0358     window->setMaximize(true, false);
0359     QCOMPARE(verifyProperty(QStringLiteral("maximizeVertical")), true);
0360     QCOMPARE(verifyProperty(QStringLiteral("maximizeHorizontal")), false);
0361     window->setMaximize(false, true);
0362     QCOMPARE(verifyProperty(QStringLiteral("maximizeVertical")), false);
0363     QCOMPARE(verifyProperty(QStringLiteral("maximizeHorizontal")), true);
0364 
0365     const auto id = window->internalId();
0366     // destroy the window
0367     xcb_unmap_window(c.get(), windowId);
0368     xcb_flush(c.get());
0369 
0370     QSignalSpy windowClosedSpy(window, &X11Window::closed);
0371     QVERIFY(windowClosedSpy.wait());
0372     xcb_destroy_window(c.get(), windowId);
0373     c.reset();
0374 
0375     reply = getWindowInfo(id);
0376     reply.waitForFinished();
0377     QVERIFY(reply.value().empty());
0378 }
0379 
0380 WAYLANDTEST_MAIN(TestDbusInterface)
0381 #include "dbus_interface_test.moc"