File indexing completed on 2025-03-23 13:48:02
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 #include "kwin_wayland_test.h" 0010 0011 #include "core/output.h" 0012 #include "core/outputbackend.h" 0013 #include "cursor.h" 0014 #include "wayland/seat_interface.h" 0015 #include "wayland_server.h" 0016 #include "workspace.h" 0017 #include "x11window.h" 0018 #include <kwineffects.h> 0019 0020 #include <KWayland/Client/compositor.h> 0021 #include <KWayland/Client/plasmawindowmanagement.h> 0022 #include <KWayland/Client/surface.h> 0023 // screenlocker 0024 #if KWIN_BUILD_SCREENLOCKER 0025 #include <KScreenLocker/KsldApp> 0026 #endif 0027 0028 #include <QPainter> 0029 #include <QRasterWindow> 0030 0031 #include <netwm.h> 0032 #include <xcb/xcb_icccm.h> 0033 0034 namespace KWin 0035 { 0036 0037 static const QString s_socketName = QStringLiteral("wayland_test_kwin_plasma-window-0"); 0038 0039 class PlasmaWindowTest : public QObject 0040 { 0041 Q_OBJECT 0042 private Q_SLOTS: 0043 void initTestCase(); 0044 void init(); 0045 void cleanup(); 0046 void testCreateDestroyX11PlasmaWindow(); 0047 void testInternalWindowNoPlasmaWindow(); 0048 void testPopupWindowNoPlasmaWindow(); 0049 void testLockScreenNoPlasmaWindow(); 0050 void testDestroyedButNotUnmapped(); 0051 0052 private: 0053 KWayland::Client::PlasmaWindowManagement *m_windowManagement = nullptr; 0054 KWayland::Client::Compositor *m_compositor = nullptr; 0055 }; 0056 0057 void PlasmaWindowTest::initTestCase() 0058 { 0059 qRegisterMetaType<KWin::Window *>(); 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 const auto outputs = workspace()->outputs(); 0067 QCOMPARE(outputs.count(), 2); 0068 QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); 0069 QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); 0070 setenv("QT_QPA_PLATFORM", "wayland", true); 0071 setenv("QMLSCENE_DEVICE", "softwarecontext", true); 0072 } 0073 0074 void PlasmaWindowTest::init() 0075 { 0076 QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::WindowManagement)); 0077 m_windowManagement = Test::waylandWindowManagement(); 0078 m_compositor = Test::waylandCompositor(); 0079 0080 workspace()->setActiveOutput(QPoint(640, 512)); 0081 Cursors::self()->mouse()->setPos(QPoint(640, 512)); 0082 } 0083 0084 void PlasmaWindowTest::cleanup() 0085 { 0086 Test::destroyWaylandConnection(); 0087 } 0088 0089 void PlasmaWindowTest::testCreateDestroyX11PlasmaWindow() 0090 { 0091 // this test verifies that a PlasmaWindow gets unmapped on Client side when an X11 window is destroyed 0092 QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated); 0093 0094 // create an xcb window 0095 struct XcbConnectionDeleter 0096 { 0097 void operator()(xcb_connection_t *pointer) 0098 { 0099 xcb_disconnect(pointer); 0100 } 0101 }; 0102 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0103 QVERIFY(!xcb_connection_has_error(c.get())); 0104 const QRect windowGeometry(0, 0, 100, 200); 0105 xcb_window_t windowId = xcb_generate_id(c.get()); 0106 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0107 windowGeometry.x(), 0108 windowGeometry.y(), 0109 windowGeometry.width(), 0110 windowGeometry.height(), 0111 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0112 xcb_size_hints_t hints; 0113 memset(&hints, 0, sizeof(hints)); 0114 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0115 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0116 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0117 xcb_map_window(c.get(), windowId); 0118 xcb_flush(c.get()); 0119 0120 // we should get a window for it 0121 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0122 QVERIFY(windowCreatedSpy.wait()); 0123 X11Window *window = windowCreatedSpy.first().first().value<X11Window *>(); 0124 QVERIFY(window); 0125 QCOMPARE(window->window(), windowId); 0126 QVERIFY(window->isDecorated()); 0127 QVERIFY(window->isActive()); 0128 // verify that it gets the keyboard focus 0129 if (!window->surface()) { 0130 // we don't have a surface yet, so focused keyboard surface if set is not ours 0131 QVERIFY(!waylandServer()->seat()->focusedKeyboardSurface()); 0132 QVERIFY(Test::waitForWaylandSurface(window)); 0133 } 0134 QCOMPARE(waylandServer()->seat()->focusedKeyboardSurface(), window->surface()); 0135 0136 // now that should also give it to us on client side 0137 QVERIFY(plasmaWindowCreatedSpy.wait()); 0138 QCOMPARE(plasmaWindowCreatedSpy.count(), 1); 0139 QCOMPARE(m_windowManagement->windows().count(), 1); 0140 auto pw = m_windowManagement->windows().first(); 0141 QCOMPARE(pw->geometry(), window->frameGeometry()); 0142 QSignalSpy geometryChangedSpy(pw, &KWayland::Client::PlasmaWindow::geometryChanged); 0143 0144 QSignalSpy unmappedSpy(m_windowManagement->windows().first(), &KWayland::Client::PlasmaWindow::unmapped); 0145 QSignalSpy destroyedSpy(m_windowManagement->windows().first(), &QObject::destroyed); 0146 0147 // now shade the window 0148 const QRectF geoBeforeShade = window->frameGeometry(); 0149 QVERIFY(geoBeforeShade.isValid()); 0150 QVERIFY(!geoBeforeShade.isEmpty()); 0151 workspace()->slotWindowShade(); 0152 QVERIFY(window->isShade()); 0153 QVERIFY(window->frameGeometry() != geoBeforeShade); 0154 QVERIFY(geometryChangedSpy.wait()); 0155 QCOMPARE(pw->geometry(), window->frameGeometry()); 0156 // and unshade again 0157 workspace()->slotWindowShade(); 0158 QVERIFY(!window->isShade()); 0159 QCOMPARE(window->frameGeometry(), geoBeforeShade); 0160 QVERIFY(geometryChangedSpy.wait()); 0161 QCOMPARE(pw->geometry(), geoBeforeShade); 0162 0163 // and destroy the window again 0164 xcb_unmap_window(c.get(), windowId); 0165 xcb_flush(c.get()); 0166 0167 QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); 0168 QVERIFY(windowClosedSpy.wait()); 0169 xcb_destroy_window(c.get(), windowId); 0170 c.reset(); 0171 0172 QVERIFY(unmappedSpy.wait()); 0173 QCOMPARE(unmappedSpy.count(), 1); 0174 0175 QVERIFY(destroyedSpy.wait()); 0176 } 0177 0178 class HelperWindow : public QRasterWindow 0179 { 0180 Q_OBJECT 0181 public: 0182 HelperWindow(); 0183 ~HelperWindow() override; 0184 0185 protected: 0186 void paintEvent(QPaintEvent *event) override; 0187 }; 0188 0189 HelperWindow::HelperWindow() 0190 : QRasterWindow(nullptr) 0191 { 0192 } 0193 0194 HelperWindow::~HelperWindow() = default; 0195 0196 void HelperWindow::paintEvent(QPaintEvent *event) 0197 { 0198 QPainter p(this); 0199 p.fillRect(0, 0, width(), height(), Qt::red); 0200 } 0201 0202 void PlasmaWindowTest::testInternalWindowNoPlasmaWindow() 0203 { 0204 // this test verifies that an internal window is not added as a PlasmaWindow 0205 QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated); 0206 HelperWindow win; 0207 win.setGeometry(0, 0, 100, 100); 0208 win.show(); 0209 0210 QVERIFY(!plasmaWindowCreatedSpy.wait(100)); 0211 } 0212 0213 void PlasmaWindowTest::testPopupWindowNoPlasmaWindow() 0214 { 0215 // this test verifies that a popup window is not added as a PlasmaWindow 0216 QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated); 0217 0218 // first create the parent window 0219 std::unique_ptr<KWayland::Client::Surface> parentSurface(Test::createSurface()); 0220 std::unique_ptr<Test::XdgToplevel> parentShellSurface(Test::createXdgToplevelSurface(parentSurface.get())); 0221 Window *parentClient = Test::renderAndWaitForShown(parentSurface.get(), QSize(100, 50), Qt::blue); 0222 QVERIFY(parentClient); 0223 QVERIFY(plasmaWindowCreatedSpy.wait()); 0224 QCOMPARE(plasmaWindowCreatedSpy.count(), 1); 0225 0226 // now let's create a popup window for it 0227 std::unique_ptr<Test::XdgPositioner> positioner(Test::createXdgPositioner()); 0228 positioner->set_size(10, 10); 0229 positioner->set_anchor_rect(0, 0, 10, 10); 0230 positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right); 0231 positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right); 0232 std::unique_ptr<KWayland::Client::Surface> popupSurface(Test::createSurface()); 0233 std::unique_ptr<Test::XdgPopup> popupShellSurface(Test::createXdgPopupSurface(popupSurface.get(), parentShellSurface->xdgSurface(), positioner.get())); 0234 Window *popupWindow = Test::renderAndWaitForShown(popupSurface.get(), QSize(10, 10), Qt::blue); 0235 QVERIFY(popupWindow); 0236 QVERIFY(!plasmaWindowCreatedSpy.wait(100)); 0237 QCOMPARE(plasmaWindowCreatedSpy.count(), 1); 0238 0239 // let's destroy the windows 0240 popupShellSurface.reset(); 0241 QVERIFY(Test::waitForWindowDestroyed(popupWindow)); 0242 parentShellSurface.reset(); 0243 QVERIFY(Test::waitForWindowDestroyed(parentClient)); 0244 } 0245 0246 void PlasmaWindowTest::testLockScreenNoPlasmaWindow() 0247 { 0248 #if KWIN_BUILD_SCREENLOCKER 0249 // this test verifies that lock screen windows are not exposed to PlasmaWindow 0250 QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated); 0251 0252 // this time we use a QSignalSpy on XdgShellClient as it'a a little bit more complex setup 0253 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); 0254 // lock 0255 ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); 0256 QVERIFY(windowAddedSpy.wait()); 0257 QVERIFY(windowAddedSpy.first().first().value<Window *>()->isLockScreen()); 0258 // should not be sent to the window 0259 QVERIFY(plasmaWindowCreatedSpy.isEmpty()); 0260 QVERIFY(!plasmaWindowCreatedSpy.wait(100)); 0261 0262 // fake unlock 0263 QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); 0264 const auto children = ScreenLocker::KSldApp::self()->children(); 0265 for (auto it = children.begin(); it != children.end(); ++it) { 0266 if (qstrcmp((*it)->metaObject()->className(), "LogindIntegration") != 0) { 0267 continue; 0268 } 0269 QMetaObject::invokeMethod(*it, "requestUnlock"); 0270 break; 0271 } 0272 QVERIFY(lockStateChangedSpy.wait()); 0273 QVERIFY(!waylandServer()->isScreenLocked()); 0274 #else 0275 QSKIP("KWin was built without lockscreen support"); 0276 #endif 0277 } 0278 0279 void PlasmaWindowTest::testDestroyedButNotUnmapped() 0280 { 0281 // this test verifies that also when a ShellSurface gets destroyed without a prior unmap 0282 // the PlasmaWindow gets destroyed on Client side 0283 QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated); 0284 0285 // first create the parent window 0286 std::unique_ptr<KWayland::Client::Surface> parentSurface(Test::createSurface()); 0287 std::unique_ptr<Test::XdgToplevel> parentShellSurface(Test::createXdgToplevelSurface(parentSurface.get())); 0288 // map that window 0289 Test::render(parentSurface.get(), QSize(100, 50), Qt::blue); 0290 // this should create a plasma window 0291 QVERIFY(plasmaWindowCreatedSpy.wait()); 0292 QCOMPARE(plasmaWindowCreatedSpy.count(), 1); 0293 auto window = plasmaWindowCreatedSpy.first().first().value<KWayland::Client::PlasmaWindow *>(); 0294 QVERIFY(window); 0295 QSignalSpy destroyedSpy(window, &QObject::destroyed); 0296 0297 // now destroy without an unmap 0298 parentShellSurface.reset(); 0299 parentSurface.reset(); 0300 QVERIFY(destroyedSpy.wait()); 0301 } 0302 0303 } 0304 0305 WAYLANDTEST_MAIN(KWin::PlasmaWindowTest) 0306 #include "plasmawindow_test.moc"