From f0ce2a0f533df5e332d3afa4b28c924715418864 Mon Sep 17 00:00:00 2001 From: Aleix Pol Date: Fri, 4 Mar 2022 04:22:20 +0100 Subject: [PATCH] screencasting: Support regions from the v3 of the protocol --- src/plugins/screencast/CMakeLists.txt | 1 + .../screencast/regionscreencastsource.cpp | 115 ++++++++++++++++++ .../screencast/regionscreencastsource.h | 46 +++++++ src/plugins/screencast/screencastmanager.cpp | 43 +++++++ src/plugins/screencast/screencastmanager.h | 4 + 5 files changed, 209 insertions(+) create mode 100644 src/plugins/screencast/regionscreencastsource.cpp create mode 100644 src/plugins/screencast/regionscreencastsource.h diff --git a/src/plugins/screencast/CMakeLists.txt b/src/plugins/screencast/CMakeLists.txt index 4a8369312b..03b65cd463 100644 --- a/src/plugins/screencast/CMakeLists.txt +++ b/src/plugins/screencast/CMakeLists.txt @@ -4,6 +4,7 @@ target_sources(KWinScreencastPlugin PRIVATE main.cpp outputscreencastsource.cpp pipewirecore.cpp + regionscreencastsource.cpp screencastmanager.cpp screencastsource.cpp screencaststream.cpp diff --git a/src/plugins/screencast/regionscreencastsource.cpp b/src/plugins/screencast/regionscreencastsource.cpp new file mode 100644 index 0000000000..f7a9417e1e --- /dev/null +++ b/src/plugins/screencast/regionscreencastsource.cpp @@ -0,0 +1,115 @@ +/* + SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "regionscreencastsource.h" +#include "screencastutils.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace KWin +{ + +RegionScreenCastSource::RegionScreenCastSource(const QRect ®ion, qreal scale, QObject *parent) + : ScreenCastSource(parent) + , m_region(region) + , m_scale(scale) +{ + Q_ASSERT(m_region.isValid()); + Q_ASSERT(m_scale > 0); +} + +QSize RegionScreenCastSource::textureSize() const +{ + return m_region.size() * m_scale; +} + +bool RegionScreenCastSource::hasAlphaChannel() const +{ + return true; +} + +void RegionScreenCastSource::updateOutput(AbstractWaylandOutput *output) +{ + m_last = output->renderLoop()->lastPresentationTimestamp(); + + if (!m_renderedTexture.isNull()) { + const QSharedPointer outputTexture = Compositor::self()->scene()->textureForOutput(output); + const auto outputGeometry = output->geometry(); + if (!outputTexture || !m_region.intersects(output->geometry())) { + return; + } + + GLRenderTarget::pushRenderTarget(m_target.data()); + const QRect geometry({0, 0}, m_target->size()); + + ShaderBinder shaderBinder(ShaderTrait::MapTexture); + QMatrix4x4 projectionMatrix; + projectionMatrix.ortho(m_region); + + const QPoint pos = outputGeometry.topLeft(); + projectionMatrix.translate(pos.x(), pos.y()); + + shaderBinder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, projectionMatrix); + + outputTexture->bind(); + outputTexture->render(output->geometry()); + outputTexture->unbind(); + GLRenderTarget::popRenderTarget(); + } +} + +std::chrono::nanoseconds RegionScreenCastSource::clock() const +{ + return m_last; +} + +void RegionScreenCastSource::render(GLRenderTarget *target) +{ + if (!m_renderedTexture) { + m_renderedTexture.reset(new GLTexture(hasAlphaChannel() ? GL_RGBA8 : GL_RGB8, textureSize())); + m_target.reset(new GLRenderTarget(m_renderedTexture.data())); + const auto allOutputs = kwinApp()->platform()->enabledOutputs(); + for (auto output : allOutputs) { + AbstractWaylandOutput *streamOutput = qobject_cast(output); + if (streamOutput->geometry().intersects(m_region)) { + updateOutput(streamOutput); + } + } + } + + GLRenderTarget::pushRenderTarget(target); + QRect r(QPoint(), target->size()); + auto shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); + + QMatrix4x4 projectionMatrix; + projectionMatrix.ortho(r); + shader->setUniform(GLShader::ModelViewProjectionMatrix, projectionMatrix); + + m_renderedTexture->bind(); + m_renderedTexture->render(r); + m_renderedTexture->unbind(); + + ShaderManager::instance()->popShader(); + GLRenderTarget::popRenderTarget(); +} + +void RegionScreenCastSource::render(QImage *image) +{ + GLTexture offscreenTexture(hasAlphaChannel() ? GL_RGBA8 : GL_RGB8, textureSize()); + GLRenderTarget offscreenTarget(&offscreenTexture); + + render(&offscreenTarget); + grabTexture(&offscreenTexture, image); +} + +} diff --git a/src/plugins/screencast/regionscreencastsource.h b/src/plugins/screencast/regionscreencastsource.h new file mode 100644 index 0000000000..5b0bfe5424 --- /dev/null +++ b/src/plugins/screencast/regionscreencastsource.h @@ -0,0 +1,46 @@ +/* + SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include "screencastsource.h" + +#include +#include +#include + +namespace KWin +{ +class AbstractWaylandOutput; + +class RegionScreenCastSource : public ScreenCastSource +{ + Q_OBJECT + +public: + explicit RegionScreenCastSource(const QRect ®ion, qreal scale, QObject *parent = nullptr); + + bool hasAlphaChannel() const override; + QSize textureSize() const override; + + void render(GLRenderTarget *target) override; + void render(QImage *image) override; + std::chrono::nanoseconds clock() const override; + + QRect region() const { + return m_region; + } + void updateOutput(AbstractWaylandOutput *output); + +private: + const QRect m_region; + const qreal m_scale; + QScopedPointer m_target; + QScopedPointer m_renderedTexture; + std::chrono::nanoseconds m_last; +}; + +} // namespace KWin diff --git a/src/plugins/screencast/screencastmanager.cpp b/src/plugins/screencast/screencastmanager.cpp index bfcfac5a75..44a4f08eae 100644 --- a/src/plugins/screencast/screencastmanager.cpp +++ b/src/plugins/screencast/screencastmanager.cpp @@ -14,6 +14,7 @@ #include "effects.h" #include "kwingltexture.h" #include "outputscreencastsource.h" +#include "regionscreencastsource.h" #include "screencaststream.h" #include "platform.h" #include "scene.h" @@ -37,6 +38,7 @@ ScreencastManager::ScreencastManager(QObject *parent) this, &ScreencastManager::streamWindow); connect(m_screencast, &KWaylandServer::ScreencastV1Interface::outputScreencastRequested, this, &ScreencastManager::streamWaylandOutput); connect(m_screencast, &KWaylandServer::ScreencastV1Interface::virtualOutputScreencastRequested, this, &ScreencastManager::streamVirtualOutput); + connect(m_screencast, &KWaylandServer::ScreencastV1Interface::regionScreencastRequested, this, &ScreencastManager::streamRegion); } class WindowStream : public ScreenCastStream @@ -143,6 +145,47 @@ void ScreencastManager::streamOutput(KWaylandServer::ScreencastStreamV1Interface integrateStreams(waylandStream, stream); } +static QString rectToString(const QRect &rect) +{ + return QStringLiteral("%1,%2 %3x%4").arg(rect.x()).arg(rect.y()).arg(rect.width()).arg(rect.height()); +} + +void ScreencastManager::streamRegion(KWaylandServer::ScreencastStreamV1Interface *waylandStream, const QRect &geometry, qreal scale, KWaylandServer::ScreencastV1Interface::CursorMode mode) +{ + if (!geometry.isValid()) { + waylandStream->sendFailed(i18n("Invalid region")); + return; + } + + auto source = new RegionScreenCastSource(geometry, scale); + auto stream = new ScreenCastStream(source, this); + stream->setObjectName(rectToString(geometry)); + stream->setCursorMode(mode, scale, geometry); + + connect(stream, &ScreenCastStream::startStreaming, waylandStream, [geometry, stream, source] { + Compositor::self()->scene()->addRepaint(geometry); + + const auto allOutputs = kwinApp()->platform()->enabledOutputs(); + for (auto output : allOutputs) { + AbstractWaylandOutput *streamOutput = qobject_cast(output); + if (streamOutput->geometry().intersects(geometry)) { + auto bufferToStream = [streamOutput, stream, source] (const QRegion &damagedRegion) { + if (damagedRegion.isEmpty()) { + return; + } + + const QRect streamRegion = source->region(); + const QRegion region = streamOutput->pixelSize() != streamOutput->modeSize() ? streamOutput->geometry() : damagedRegion; + source->updateOutput(streamOutput); + stream->recordFrame(region.translated(-streamRegion.topLeft()).intersected(streamRegion)); + }; + connect(streamOutput, &AbstractWaylandOutput::outputChange, stream, bufferToStream); + } + } + }); + integrateStreams(waylandStream, stream); +} + void ScreencastManager::integrateStreams(KWaylandServer::ScreencastStreamV1Interface *waylandStream, ScreenCastStream *stream) { connect(waylandStream, &KWaylandServer::ScreencastStreamV1Interface::finished, stream, &ScreenCastStream::stop); diff --git a/src/plugins/screencast/screencastmanager.h b/src/plugins/screencast/screencastmanager.h index 3836c2828b..cab329eebd 100644 --- a/src/plugins/screencast/screencastmanager.h +++ b/src/plugins/screencast/screencastmanager.h @@ -36,6 +36,10 @@ private: const QSize &size, double scale, KWaylandServer::ScreencastV1Interface::CursorMode mode); + void streamRegion(KWaylandServer::ScreencastStreamV1Interface *stream, + const QRect &geometry, + qreal scale, + KWaylandServer::ScreencastV1Interface::CursorMode mode); void integrateStreams(KWaylandServer::ScreencastStreamV1Interface *waylandStream, ScreenCastStream *stream);