File indexing completed on 2025-03-23 13:47:55
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"