File indexing completed on 2024-11-10 04:56:05

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2023 Aleix Pol Gonzalez <aleixpol@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "compositor.h"
0010 #include "core/output.h"
0011 #include "generic_scene_opengl_test.h"
0012 #include "opengl/glplatform.h"
0013 #include "pointer_input.h"
0014 #include "scene/workspacescene.h"
0015 #include "wayland_server.h"
0016 #include "window.h"
0017 #include "workspace.h"
0018 
0019 #include <KWayland/Client/output.h>
0020 #include <KWayland/Client/subsurface.h>
0021 #include <KWayland/Client/surface.h>
0022 #include <PipeWireSourceStream>
0023 #include <QPainter>
0024 #include <QScreen>
0025 
0026 #define QCOMPAREIMG(actual, expected, id)                                                        \
0027     {                                                                                            \
0028         if ((actual) != (expected)) {                                                            \
0029             const auto actualFile = QStringLiteral("appium_artifact_actual_%1.png").arg(id);     \
0030             const auto expectedFile = QStringLiteral("appium_artifact_expected_%1.png").arg(id); \
0031             (actual).save(actualFile);                                                           \
0032             (expected).save(expectedFile);                                                       \
0033             qDebug() << "Generated failed file" << actualFile << expectedFile;                   \
0034         }                                                                                        \
0035         QCOMPARE(actual, expected);                                                              \
0036     }
0037 
0038 namespace KWin
0039 {
0040 
0041 static const QString s_socketName = QStringLiteral("wayland_test_buffer_size_change-0");
0042 
0043 class ScreencastingTest : public GenericSceneOpenGLTest
0044 {
0045     Q_OBJECT
0046 public:
0047     ScreencastingTest()
0048         : GenericSceneOpenGLTest(QByteArrayLiteral("O2"))
0049     {
0050         auto wrap = [this](const QString &process, const QStringList &arguments = {}) {
0051             // Make sure PipeWire is running. If it's already running it will just exit
0052             QProcess *p = new QProcess(this);
0053             p->setProcessChannelMode(QProcess::MergedChannels);
0054             p->setArguments(arguments);
0055             connect(this, &QObject::destroyed, p, [p] {
0056                 p->terminate();
0057                 p->waitForFinished();
0058                 p->kill();
0059             });
0060             connect(p, &QProcess::errorOccurred, p, [p](auto status) {
0061                 qDebug() << "error" << status << p->program();
0062             });
0063             connect(p, &QProcess::finished, p, [p](int code, auto status) {
0064                 if (code != 0) {
0065                     qDebug() << p->readAll();
0066                 }
0067                 qDebug() << "finished" << code << status << p->program();
0068             });
0069             p->setProgram(process);
0070             p->start();
0071         };
0072 
0073         // If I run this outside the CI, it breaks the system's pipewire
0074         if (qgetenv("KDECI_BUILD") == "TRUE") {
0075             wrap("pipewire");
0076             wrap("dbus-launch", {"wireplumber"});
0077         }
0078     }
0079 private Q_SLOTS:
0080     void init();
0081     void testWindowCasting();
0082     void testOutputCasting();
0083 
0084 private:
0085     std::optional<QImage> oneFrameAndClose(Test::ScreencastingStreamV1 *stream);
0086 };
0087 
0088 void ScreencastingTest::init()
0089 {
0090     QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::ScreencastingV1));
0091     QVERIFY(KWin::Test::screencasting());
0092     Cursors::self()->hideCursor();
0093 }
0094 
0095 std::optional<QImage> ScreencastingTest::oneFrameAndClose(Test::ScreencastingStreamV1 *stream)
0096 {
0097     Q_ASSERT(stream);
0098     PipeWireSourceStream pwStream;
0099     qDebug() << "start" << stream;
0100     connect(stream, &Test::ScreencastingStreamV1::failed, qGuiApp, [](const QString &error) {
0101         qDebug() << "stream failed with error" << error;
0102         Q_ASSERT(false);
0103     });
0104     connect(stream, &Test::ScreencastingStreamV1::closed, qGuiApp, [&pwStream] {
0105         pwStream.setActive(false);
0106     });
0107     connect(stream, &Test::ScreencastingStreamV1::created, qGuiApp, [&pwStream](quint32 nodeId) {
0108         pwStream.createStream(nodeId, 0);
0109     });
0110 
0111     std::optional<QImage> img;
0112     connect(&pwStream, &PipeWireSourceStream::frameReceived, qGuiApp, [&img](const PipeWireFrame &frame) {
0113         if (frame.dataFrame) {
0114             img = frame.dataFrame->toImage();
0115         }
0116     });
0117 
0118     QSignalSpy spy(&pwStream, &PipeWireSourceStream::frameReceived);
0119     if (!spy.wait()) {
0120         qDebug() << "Did not receive any frames";
0121     }
0122     pwStream.stopStreaming();
0123     return img;
0124 }
0125 
0126 void ScreencastingTest::testWindowCasting()
0127 {
0128     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0129     QVERIFY(surface != nullptr);
0130 
0131     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0132     QVERIFY(shellSurface != nullptr);
0133 
0134     QImage sourceImage(QSize(30, 10), QImage::Format_RGBA8888_Premultiplied);
0135     sourceImage.fill(Qt::red);
0136 
0137     Window *window = Test::renderAndWaitForShown(surface.get(), sourceImage);
0138     QVERIFY(window);
0139 
0140     auto stream = KWin::Test::screencasting()->createWindowStream(window->internalId().toString(), QtWayland::zkde_screencast_unstable_v1::pointer_hidden);
0141 
0142     std::optional<QImage> img = oneFrameAndClose(stream);
0143     QVERIFY(img);
0144     img->convertTo(sourceImage.format());
0145     QCOMPAREIMG(*img, sourceImage, QLatin1String("window_cast"));
0146 }
0147 
0148 void ScreencastingTest::testOutputCasting()
0149 {
0150     auto theOutput = KWin::Test::waylandOutputs().constFirst();
0151 
0152     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0153     QVERIFY(surface != nullptr);
0154 
0155     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0156     QVERIFY(shellSurface != nullptr);
0157 
0158     QImage sourceImage(theOutput->pixelSize(), QImage::Format_RGBA8888_Premultiplied);
0159     sourceImage.fill(Qt::green);
0160     {
0161         QPainter p(&sourceImage);
0162         p.drawRect(100, 100, 100, 100);
0163     }
0164 
0165     Window *window = Test::renderAndWaitForShown(surface.get(), sourceImage);
0166     QVERIFY(window);
0167     QCOMPARE(window->frameGeometry(), window->output()->geometry());
0168 
0169     auto stream = KWin::Test::screencasting()->createOutputStream(theOutput->output(), QtWayland::zkde_screencast_unstable_v1::pointer_hidden);
0170 
0171     std::optional<QImage> img = oneFrameAndClose(stream);
0172     QVERIFY(img);
0173     img->convertTo(sourceImage.format());
0174     QCOMPAREIMG(*img, sourceImage, QLatin1String("output_cast"));
0175 }
0176 
0177 }
0178 
0179 WAYLANDTEST_MAIN(KWin::ScreencastingTest)
0180 #include "screencasting_test.moc"