File indexing completed on 2024-05-19 09:23:11

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"