File indexing completed on 2024-11-10 04:55:57
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"