2020-07-22 17:29:23 +00:00
|
|
|
/*
|
2020-08-02 22:22:19 +00:00
|
|
|
SPDX-FileCopyrightText: 2018-2020 Red Hat Inc
|
|
|
|
SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
|
|
|
|
SPDX-FileContributor: Jan Grulich <jgrulich@redhat.com>
|
|
|
|
|
|
|
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
|
|
|
*/
|
2020-07-22 17:29:23 +00:00
|
|
|
|
|
|
|
#include "screencastmanager.h"
|
2020-07-27 11:07:57 +00:00
|
|
|
#include "abstract_client.h"
|
2020-07-22 17:29:23 +00:00
|
|
|
#include "abstract_wayland_output.h"
|
2020-07-27 11:07:57 +00:00
|
|
|
#include "composite.h"
|
|
|
|
#include "deleted.h"
|
|
|
|
#include "effects.h"
|
|
|
|
#include "kwingltexture.h"
|
2020-07-22 17:29:23 +00:00
|
|
|
#include "pipewirestream.h"
|
2020-07-27 11:07:57 +00:00
|
|
|
#include "platform.h"
|
|
|
|
#include "scene.h"
|
|
|
|
#include "wayland_server.h"
|
|
|
|
#include "workspace.h"
|
|
|
|
|
|
|
|
#include <KLocalizedString>
|
2020-07-22 17:29:23 +00:00
|
|
|
|
2020-07-27 11:05:52 +00:00
|
|
|
#include <KWaylandServer/display.h>
|
2020-07-22 17:29:23 +00:00
|
|
|
#include <KWaylandServer/output_interface.h>
|
|
|
|
|
2020-07-28 13:37:04 +00:00
|
|
|
namespace KWin
|
|
|
|
{
|
2020-07-22 17:29:23 +00:00
|
|
|
|
|
|
|
ScreencastManager::ScreencastManager(QObject *parent)
|
|
|
|
: QObject(parent)
|
2020-07-27 11:05:52 +00:00
|
|
|
, m_screencast(waylandServer()->display()->createScreencastInterface(this))
|
2020-07-22 17:29:23 +00:00
|
|
|
{
|
2020-07-27 11:05:52 +00:00
|
|
|
connect(m_screencast, &KWaylandServer::ScreencastInterface::windowScreencastRequested,
|
|
|
|
this, &ScreencastManager::streamWindow);
|
|
|
|
connect(m_screencast, &KWaylandServer::ScreencastInterface::outputScreencastRequested,
|
|
|
|
this, &ScreencastManager::streamOutput);
|
2020-07-22 17:29:23 +00:00
|
|
|
}
|
2020-07-27 11:07:57 +00:00
|
|
|
|
2020-07-22 17:29:23 +00:00
|
|
|
class EGLFence : public QObject
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
EGLFence(EGLDisplay eglDisplay)
|
|
|
|
: m_eglDisplay(eglDisplay)
|
2020-07-28 13:37:04 +00:00
|
|
|
, m_sync(eglCreateSync(eglDisplay, EGL_SYNC_FENCE_KHR, nullptr))
|
2020-07-22 17:29:23 +00:00
|
|
|
{
|
|
|
|
Q_ASSERT(m_sync);
|
|
|
|
glFinish();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool clientWaitSync()
|
|
|
|
{
|
|
|
|
glFenceSync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
|
|
int ret = eglClientWaitSync(m_eglDisplay, m_sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, 0);
|
|
|
|
Q_ASSERT(ret == EGL_CONDITION_SATISFIED_KHR);
|
|
|
|
return ret == EGL_CONDITION_SATISFIED_KHR;
|
|
|
|
}
|
|
|
|
|
|
|
|
~EGLFence() {
|
|
|
|
auto ret = eglDestroySyncKHR(m_eglDisplay, m_sync);
|
|
|
|
Q_ASSERT(ret == EGL_TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
const EGLDisplay m_eglDisplay;
|
|
|
|
const EGLSyncKHR m_sync;
|
|
|
|
};
|
|
|
|
|
|
|
|
class WindowStream : public PipeWireStream
|
|
|
|
{
|
|
|
|
public:
|
2020-07-28 13:37:04 +00:00
|
|
|
WindowStream(Toplevel *toplevel, QObject *parent)
|
2020-07-22 17:29:23 +00:00
|
|
|
: PipeWireStream(toplevel->hasAlpha(), toplevel->clientSize() * toplevel->bufferScale(), parent)
|
|
|
|
, m_toplevel(toplevel)
|
|
|
|
{
|
|
|
|
if (AbstractClient *client = qobject_cast<AbstractClient *>(toplevel)) {
|
|
|
|
setObjectName(client->desktopFileName());
|
|
|
|
}
|
|
|
|
connect(toplevel, &Toplevel::windowClosed, this, &PipeWireStream::stopStreaming);
|
|
|
|
connect(this, &PipeWireStream::startStreaming, this, &WindowStream::startFeeding);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void startFeeding() {
|
2020-07-28 13:37:04 +00:00
|
|
|
auto scene = Compositor::self()->scene();
|
2020-07-22 17:29:23 +00:00
|
|
|
connect(scene, &Scene::frameRendered, this, &WindowStream::bufferToStream);
|
|
|
|
|
|
|
|
connect(m_toplevel, &Toplevel::damaged, this, &WindowStream::includeDamage);
|
|
|
|
m_toplevel->damaged(m_toplevel, m_toplevel->frameGeometry());
|
|
|
|
}
|
|
|
|
|
2020-07-28 13:37:04 +00:00
|
|
|
void includeDamage(Toplevel *toplevel, const QRect &damage) {
|
2020-07-22 17:29:23 +00:00
|
|
|
Q_ASSERT(m_toplevel == toplevel);
|
|
|
|
m_damagedRegion |= damage;
|
|
|
|
}
|
|
|
|
|
|
|
|
void bufferToStream () {
|
|
|
|
if (m_damagedRegion.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
EGLFence fence(kwinApp()->platform()->sceneEglDisplay());
|
|
|
|
QSharedPointer<GLTexture> frameTexture(m_toplevel->effectWindow()->sceneWindow()->windowTexture());
|
|
|
|
const bool wasYInverted = frameTexture->isYInverted();
|
|
|
|
frameTexture->setYInverted(false);
|
|
|
|
|
|
|
|
recordFrame(frameTexture.data(), m_damagedRegion);
|
|
|
|
frameTexture->setYInverted(wasYInverted);
|
|
|
|
m_damagedRegion = {};
|
|
|
|
bool b = fence.clientWaitSync();
|
|
|
|
Q_ASSERT(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
QRegion m_damagedRegion;
|
2020-07-28 13:37:04 +00:00
|
|
|
Toplevel *m_toplevel;
|
2020-07-22 17:29:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
void ScreencastManager::streamWindow(KWaylandServer::ScreencastStreamInterface *waylandStream, const QString &winid)
|
|
|
|
{
|
2020-07-28 13:37:04 +00:00
|
|
|
auto *toplevel = Workspace::self()->findToplevel(winid);
|
2020-07-22 17:29:23 +00:00
|
|
|
|
|
|
|
if (!toplevel) {
|
|
|
|
waylandStream->sendFailed(i18n("Could not find window id %1", winid));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto stream = new WindowStream(toplevel, this);
|
|
|
|
integrateStreams(waylandStream, stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ScreencastManager::streamOutput(KWaylandServer::ScreencastStreamInterface *waylandStream,
|
2020-07-27 11:07:57 +00:00
|
|
|
::wl_resource *outputResource,
|
|
|
|
KWaylandServer::ScreencastInterface::CursorMode mode)
|
2020-07-22 17:29:23 +00:00
|
|
|
{
|
|
|
|
auto outputIface = KWaylandServer::OutputInterface::get(outputResource);
|
|
|
|
if (!outputIface) {
|
|
|
|
waylandStream->sendFailed(i18n("Invalid output"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto outputs = kwinApp()->platform()->enabledOutputs();
|
|
|
|
AbstractWaylandOutput *streamOutput = nullptr;
|
|
|
|
for (auto output : outputs) {
|
|
|
|
if (static_cast<AbstractWaylandOutput *>(output)->waylandOutput() == outputIface) {
|
|
|
|
streamOutput = static_cast<AbstractWaylandOutput *>(output);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!streamOutput) {
|
|
|
|
waylandStream->sendFailed(i18n("Could not find output"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto stream = new PipeWireStream(true, streamOutput->pixelSize(), this);
|
|
|
|
stream->setObjectName(streamOutput->name());
|
|
|
|
stream->setCursorMode(mode, streamOutput->scale(), streamOutput->geometry());
|
|
|
|
connect(streamOutput, &QObject::destroyed, stream, &PipeWireStream::stopStreaming);
|
|
|
|
auto bufferToStream = [streamOutput, stream] (const QRegion &damagedRegion) {
|
2020-07-28 13:37:04 +00:00
|
|
|
auto scene = Compositor::self()->scene();
|
2020-07-22 17:29:23 +00:00
|
|
|
auto texture = scene->textureForOutput(streamOutput);
|
|
|
|
|
|
|
|
const QRect frame({}, streamOutput->modeSize());
|
|
|
|
const QRegion region = damagedRegion.isEmpty() || streamOutput->pixelSize() != streamOutput->modeSize() ? frame : damagedRegion.translated(-streamOutput->geometry().topLeft()).intersected(frame);
|
|
|
|
stream->recordFrame(texture.data(), region);
|
|
|
|
};
|
|
|
|
connect(stream, &PipeWireStream::startStreaming, waylandStream, [streamOutput, stream, bufferToStream] {
|
2020-07-28 13:37:04 +00:00
|
|
|
Compositor::self()->addRepaint(streamOutput->geometry());
|
2020-07-22 17:29:23 +00:00
|
|
|
connect(streamOutput, &AbstractWaylandOutput::outputChange, stream, bufferToStream);
|
|
|
|
});
|
|
|
|
integrateStreams(waylandStream, stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ScreencastManager::integrateStreams(KWaylandServer::ScreencastStreamInterface *waylandStream, PipeWireStream *stream)
|
|
|
|
{
|
|
|
|
connect(waylandStream, &KWaylandServer::ScreencastStreamInterface::finished, stream, &PipeWireStream::stop);
|
|
|
|
connect(stream, &PipeWireStream::stopStreaming, waylandStream, [stream, waylandStream] {
|
|
|
|
waylandStream->sendClosed();
|
|
|
|
delete stream;
|
|
|
|
});
|
|
|
|
connect(stream, &PipeWireStream::streamReady, stream, [waylandStream] (uint nodeid) {
|
|
|
|
waylandStream->sendCreated(nodeid);
|
|
|
|
});
|
|
|
|
if (!stream->init()) {
|
|
|
|
waylandStream->sendFailed(stream->error());
|
|
|
|
delete stream;
|
|
|
|
}
|
|
|
|
}
|
2020-07-28 13:37:04 +00:00
|
|
|
|
|
|
|
} // namespace KWin
|