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