File indexing completed on 2024-05-26 05:33:24

0001 /*
0002     SPDX-FileCopyrightText: 2018-2020 Red Hat Inc
0003     SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
0004     SPDX-FileContributor: Jan Grulich <jgrulich@redhat.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "screencastmanager.h"
0010 #include "compositor.h"
0011 #include "core/output.h"
0012 #include "core/outputbackend.h"
0013 #include "opengl/gltexture.h"
0014 #include "outputscreencastsource.h"
0015 #include "pipewirecore.h"
0016 #include "regionscreencastsource.h"
0017 #include "scene/workspacescene.h"
0018 #include "screencaststream.h"
0019 #include "wayland/display.h"
0020 #include "wayland/output.h"
0021 #include "wayland_server.h"
0022 #include "window.h"
0023 #include "windowscreencastsource.h"
0024 #include "workspace.h"
0025 
0026 #include <KLocalizedString>
0027 
0028 namespace KWin
0029 {
0030 
0031 ScreencastManager::ScreencastManager()
0032     : m_screencast(new ScreencastV1Interface(waylandServer()->display(), this))
0033     , m_core(new PipeWireCore)
0034 {
0035     m_core->init();
0036     connect(m_screencast, &ScreencastV1Interface::windowScreencastRequested, this, &ScreencastManager::streamWindow);
0037     connect(m_screencast, &ScreencastV1Interface::outputScreencastRequested, this, &ScreencastManager::streamWaylandOutput);
0038     connect(m_screencast, &ScreencastV1Interface::virtualOutputScreencastRequested, this, &ScreencastManager::streamVirtualOutput);
0039     connect(m_screencast, &ScreencastV1Interface::regionScreencastRequested, this, &ScreencastManager::streamRegion);
0040 }
0041 
0042 static QRegion scaleRegion(const QRegion &_region, qreal scale)
0043 {
0044     if (scale == 1.) {
0045         return _region;
0046     }
0047 
0048     QRegion region;
0049     for (auto it = _region.begin(), itEnd = _region.end(); it != itEnd; ++it) {
0050         region += QRect(std::floor(it->x() * scale),
0051                         std::floor(it->y() * scale),
0052                         std::ceil(it->width() * scale),
0053                         std::ceil(it->height() * scale));
0054     }
0055 
0056     return region;
0057 }
0058 
0059 class WindowStream : public ScreenCastStream
0060 {
0061 public:
0062     WindowStream(Window *window, std::shared_ptr<PipeWireCore> pwCore, QObject *parent)
0063         : ScreenCastStream(new WindowScreenCastSource(window), pwCore, parent)
0064         , m_window(window)
0065     {
0066         m_timer.setInterval(0);
0067         m_timer.setSingleShot(true);
0068         setObjectName(window->desktopFileName());
0069         connect(&m_timer, &QTimer::timeout, this, &WindowStream::bufferToStream);
0070         connect(this, &ScreenCastStream::startStreaming, this, &WindowStream::startFeeding);
0071         connect(this, &ScreenCastStream::stopStreaming, this, &WindowStream::stopFeeding);
0072     }
0073 
0074 private:
0075     void startFeeding()
0076     {
0077         connect(m_window, &Window::damaged, this, &WindowStream::markDirty);
0078         markDirty();
0079     }
0080 
0081     void stopFeeding()
0082     {
0083         disconnect(m_window, &Window::damaged, this, &WindowStream::markDirty);
0084         m_timer.stop();
0085     }
0086 
0087     void markDirty()
0088     {
0089         m_timer.start();
0090     }
0091 
0092     void bufferToStream()
0093     {
0094         recordFrame(QRegion(0, 0, m_window->width(), m_window->height()));
0095     }
0096 
0097     Window *m_window;
0098     QTimer m_timer;
0099 };
0100 
0101 void ScreencastManager::streamWindow(ScreencastStreamV1Interface *waylandStream,
0102                                      const QString &winid,
0103                                      ScreencastV1Interface::CursorMode mode)
0104 {
0105     auto window = Workspace::self()->findWindow(QUuid(winid));
0106     if (!window) {
0107         waylandStream->sendFailed(i18n("Could not find window id %1", winid));
0108         return;
0109     }
0110 
0111     auto stream = new WindowStream(window, m_core, this);
0112     stream->setCursorMode(mode, 1, window->clientGeometry());
0113     if (mode != ScreencastV1Interface::CursorMode::Hidden) {
0114         connect(window, &Window::clientGeometryChanged, stream, [window, stream, mode]() {
0115             stream->setCursorMode(mode, 1, window->clientGeometry().toRect());
0116         });
0117     }
0118 
0119     integrateStreams(waylandStream, stream);
0120 }
0121 
0122 void ScreencastManager::streamVirtualOutput(ScreencastStreamV1Interface *stream,
0123                                             const QString &name,
0124                                             const QSize &size,
0125                                             double scale,
0126                                             ScreencastV1Interface::CursorMode mode)
0127 {
0128     auto output = kwinApp()->outputBackend()->createVirtualOutput(name, size, scale);
0129     streamOutput(stream, output, mode);
0130     connect(stream, &ScreencastStreamV1Interface::finished, output, [output] {
0131         kwinApp()->outputBackend()->removeVirtualOutput(output);
0132     });
0133 }
0134 
0135 void ScreencastManager::streamWaylandOutput(ScreencastStreamV1Interface *waylandStream,
0136                                             OutputInterface *output,
0137                                             ScreencastV1Interface::CursorMode mode)
0138 {
0139     streamOutput(waylandStream, output->handle(), mode);
0140 }
0141 
0142 void ScreencastManager::streamOutput(ScreencastStreamV1Interface *waylandStream,
0143                                      Output *streamOutput,
0144                                      ScreencastV1Interface::CursorMode mode)
0145 {
0146     if (!streamOutput) {
0147         waylandStream->sendFailed(i18n("Could not find output"));
0148         return;
0149     }
0150 
0151     auto stream = new ScreenCastStream(new OutputScreenCastSource(streamOutput), m_core, this);
0152     stream->setObjectName(streamOutput->name());
0153     stream->setCursorMode(mode, streamOutput->scale(), streamOutput->geometry());
0154     auto bufferToStream = [stream, streamOutput](const QRegion &damagedRegion) {
0155         if (!damagedRegion.isEmpty()) {
0156             stream->recordFrame(scaleRegion(damagedRegion, streamOutput->scale()));
0157         }
0158     };
0159     connect(stream, &ScreenCastStream::startStreaming, waylandStream, [streamOutput, stream, bufferToStream] {
0160         Compositor::self()->scene()->addRepaint(streamOutput->geometry());
0161         connect(streamOutput, &Output::outputChange, stream, bufferToStream);
0162     });
0163     integrateStreams(waylandStream, stream);
0164 }
0165 
0166 static QString rectToString(const QRect &rect)
0167 {
0168     return QStringLiteral("%1,%2 %3x%4").arg(rect.x()).arg(rect.y()).arg(rect.width()).arg(rect.height());
0169 }
0170 
0171 void ScreencastManager::streamRegion(ScreencastStreamV1Interface *waylandStream, const QRect &geometry, qreal scale, ScreencastV1Interface::CursorMode mode)
0172 {
0173     if (!geometry.isValid()) {
0174         waylandStream->sendFailed(i18n("Invalid region"));
0175         return;
0176     }
0177 
0178     auto source = new RegionScreenCastSource(geometry, scale);
0179     auto stream = new ScreenCastStream(source, m_core, this);
0180     stream->setObjectName(rectToString(geometry));
0181     stream->setCursorMode(mode, scale, geometry);
0182 
0183     connect(stream, &ScreenCastStream::startStreaming, waylandStream, [geometry, stream, source, waylandStream] {
0184         Compositor::self()->scene()->addRepaint(geometry);
0185 
0186         bool found = false;
0187         const auto allOutputs = workspace()->outputs();
0188         for (auto output : allOutputs) {
0189             if (output->geometry().intersects(geometry)) {
0190                 auto bufferToStream = [output, stream, source](const QRegion &damagedRegion) {
0191                     if (damagedRegion.isEmpty()) {
0192                         return;
0193                     }
0194 
0195                     const QRect streamRegion = source->region();
0196                     const QRegion region = output->pixelSize() != output->modeSize() ? output->geometry() : damagedRegion;
0197                     source->updateOutput(output);
0198                     stream->recordFrame(scaleRegion(region.translated(-streamRegion.topLeft()).intersected(streamRegion), source->scale()));
0199                 };
0200                 connect(output, &Output::outputChange, stream, bufferToStream);
0201                 found |= true;
0202             }
0203         }
0204         if (!found) {
0205             waylandStream->sendFailed(i18n("Region outside the workspace"));
0206         }
0207     });
0208     integrateStreams(waylandStream, stream);
0209 }
0210 
0211 void ScreencastManager::integrateStreams(ScreencastStreamV1Interface *waylandStream, ScreenCastStream *stream)
0212 {
0213     connect(waylandStream, &ScreencastStreamV1Interface::finished, stream, &ScreenCastStream::stop);
0214     connect(stream, &ScreenCastStream::stopStreaming, waylandStream, [stream, waylandStream] {
0215         waylandStream->sendClosed();
0216         stream->deleteLater();
0217     });
0218     connect(stream, &ScreenCastStream::streamReady, stream, [waylandStream](uint nodeid) {
0219         waylandStream->sendCreated(nodeid);
0220     });
0221     if (!stream->init()) {
0222         waylandStream->sendFailed(stream->error());
0223         delete stream;
0224     }
0225 }
0226 
0227 } // namespace KWin
0228 
0229 #include "moc_screencastmanager.cpp"