screencasting: integrate zkde_screencast_unstable_v1
Includes a PipeWire implementation that will send the relevant streams to the processes that need them.
This commit is contained in:
parent
27ea1b9527
commit
38eb72efe3
18 changed files with 1213 additions and 2 deletions
|
@ -391,6 +391,10 @@ add_feature_info("SCHED_RESET_ON_FORK"
|
|||
HAVE_SCHED_RESET_ON_FORK
|
||||
"Required for running kwin_wayland with real-time scheduling")
|
||||
|
||||
|
||||
pkg_check_modules(PipeWire IMPORTED_TARGET libpipewire-0.3)
|
||||
add_feature_info(PipeWire PipeWire_FOUND "Required for Wayland screencasting")
|
||||
|
||||
configure_file(config-kwin.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kwin.h)
|
||||
|
||||
########### global ###############
|
||||
|
@ -449,6 +453,7 @@ set(kwin_SRCS
|
|||
decorations/decorations_logging.cpp
|
||||
decorations/settings.cpp
|
||||
deleted.cpp
|
||||
dmabuftexture.cpp
|
||||
effectloader.cpp
|
||||
effects.cpp
|
||||
egl_context_attribute_builder.cpp
|
||||
|
@ -709,6 +714,7 @@ set_target_properties(kwin PROPERTIES
|
|||
)
|
||||
|
||||
target_link_libraries(kwin ${kwinLibs} kwinglutils ${epoxy_LIBRARY})
|
||||
|
||||
generate_export_header(kwin EXPORT_FILE_NAME kwin_export.h)
|
||||
|
||||
if(CMAKE_SYSTEM MATCHES "FreeBSD")
|
||||
|
@ -749,11 +755,28 @@ ecm_qt_declare_logging_category(kwin_XWAYLAND_SRCS
|
|||
|
||||
set(kwin_WAYLAND_SRCS
|
||||
main_wayland.cpp
|
||||
screencast/screencastmanager.cpp
|
||||
screencast/pipewirecore.cpp
|
||||
screencast/pipewirestream.cpp
|
||||
tabletmodemanager.cpp
|
||||
)
|
||||
ecm_qt_declare_logging_category(kwin_WAYLAND_SRCS
|
||||
HEADER
|
||||
kwinpipewire_logging.h
|
||||
IDENTIFIER
|
||||
KWIN_PIPEWIRE
|
||||
CATEGORY_NAME
|
||||
kwin_pipewire
|
||||
DEFAULT_SEVERITY
|
||||
Warning
|
||||
)
|
||||
|
||||
add_executable(kwin_wayland ${kwin_WAYLAND_SRCS} ${kwin_XWAYLAND_SRCS})
|
||||
target_link_libraries(kwin_wayland kwin KF5::Crash)
|
||||
target_link_libraries(kwin_wayland
|
||||
kwin
|
||||
PkgConfig::PipeWire # required for PipewireStream
|
||||
KF5::Crash
|
||||
)
|
||||
if (HAVE_LIBCAP)
|
||||
target_link_libraries(kwin_wayland ${Libcap_LIBRARIES})
|
||||
endif()
|
||||
|
|
40
dmabuftexture.cpp
Normal file
40
dmabuftexture.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors:
|
||||
* Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*/
|
||||
|
||||
#include "dmabuftexture.h"
|
||||
|
||||
#include "kwineglimagetexture.h"
|
||||
#include "kwinglutils.h"
|
||||
|
||||
using namespace KWin;
|
||||
|
||||
DmaBufTexture::DmaBufTexture(KWin::GLTexture *texture)
|
||||
: m_texture(texture)
|
||||
, m_framebuffer(new KWin::GLRenderTarget(*m_texture))
|
||||
{
|
||||
}
|
||||
|
||||
DmaBufTexture::~DmaBufTexture() = default;
|
||||
|
||||
KWin::GLRenderTarget *DmaBufTexture::framebuffer() const
|
||||
{
|
||||
return m_framebuffer.data();
|
||||
}
|
||||
|
45
dmabuftexture.h
Normal file
45
dmabuftexture.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors:
|
||||
* Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "kwin_export.h"
|
||||
#include <QScopedPointer>
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
class GLRenderTarget;
|
||||
class GLTexture;
|
||||
|
||||
class KWIN_EXPORT DmaBufTexture
|
||||
{
|
||||
public:
|
||||
explicit DmaBufTexture(KWin::GLTexture* texture);
|
||||
virtual ~DmaBufTexture();
|
||||
|
||||
virtual quint32 stride() const = 0;
|
||||
virtual int fd() const = 0;
|
||||
KWin::GLRenderTarget* framebuffer() const;
|
||||
|
||||
protected:
|
||||
QScopedPointer<KWin::GLTexture> m_texture;
|
||||
QScopedPointer<KWin::GLRenderTarget> m_framebuffer;
|
||||
};
|
||||
|
||||
}
|
|
@ -88,6 +88,7 @@ set(kwin_GLUTILSLIB_SRCS
|
|||
kwingltexture.cpp
|
||||
kwinglutils.cpp
|
||||
kwinglutils_funcs.cpp
|
||||
kwineglimagetexture.cpp
|
||||
logging.cpp
|
||||
)
|
||||
|
||||
|
|
44
libkwineffects/kwineglimagetexture.cpp
Normal file
44
libkwineffects/kwineglimagetexture.cpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright (C) 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
|
||||
#include "kwineglimagetexture.h"
|
||||
|
||||
using namespace KWin;
|
||||
|
||||
#include <QDebug>
|
||||
#include <epoxy/egl.h>
|
||||
|
||||
EGLImageTexture::EGLImageTexture(EGLDisplay display, EGLImage image, int internalFormat, const QSize &size)
|
||||
: GLTexture(internalFormat, size, 1, true)
|
||||
, m_image(image)
|
||||
, m_display(display)
|
||||
{
|
||||
if (m_image == EGL_NO_IMAGE_KHR) {
|
||||
return;
|
||||
}
|
||||
|
||||
bind();
|
||||
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image);
|
||||
}
|
||||
|
||||
EGLImageTexture::~EGLImageTexture()
|
||||
{
|
||||
eglDestroyImageKHR(m_display, m_image);
|
||||
}
|
45
libkwineffects/kwineglimagetexture.h
Normal file
45
libkwineffects/kwineglimagetexture.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/********************************************************************
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
Copyright (C) 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kwinglutils_export.h>
|
||||
#include <kwingltexture.h>
|
||||
|
||||
typedef void *EGLImageKHR;
|
||||
typedef void *EGLDisplay;
|
||||
typedef void *EGLClientBuffer;
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
class KWINGLUTILS_EXPORT EGLImageTexture : public GLTexture
|
||||
{
|
||||
public:
|
||||
EGLImageTexture(EGLDisplay display, EGLImageKHR image, int internalFormat, const QSize &size);
|
||||
~EGLImageTexture() override;
|
||||
|
||||
private:
|
||||
EGLImageKHR m_image;
|
||||
EGLDisplay m_display;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -26,12 +26,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include "platform.h"
|
||||
#include "effects.h"
|
||||
#include "tabletmodemanager.h"
|
||||
#include "screencast/screencastmanager.h"
|
||||
#include "wayland_server.h"
|
||||
#include "xwl/xwayland.h"
|
||||
|
||||
// KWayland
|
||||
#include <KWaylandServer/display.h>
|
||||
#include <KWaylandServer/seat_interface.h>
|
||||
|
||||
// KDE
|
||||
#include <KCrash>
|
||||
#include <KLocalizedString>
|
||||
|
@ -163,6 +165,7 @@ void ApplicationWayland::performStartup()
|
|||
VirtualKeyboard::create(this);
|
||||
createBackend();
|
||||
TabletModeManager::create(this);
|
||||
new ScreencastManager(this);
|
||||
}
|
||||
|
||||
void ApplicationWayland::createBackend()
|
||||
|
|
0
pipewirestream.cpp
Normal file
0
pipewirestream.cpp
Normal file
0
pipewirestream.h
Normal file
0
pipewirestream.h
Normal file
|
@ -46,6 +46,7 @@ class Manager;
|
|||
class AbstractOutput;
|
||||
class Edge;
|
||||
class Compositor;
|
||||
class DmaBufTexture;
|
||||
class OverlayWindow;
|
||||
class OpenGLBackend;
|
||||
class Outline;
|
||||
|
@ -83,6 +84,10 @@ public:
|
|||
virtual Screens *createScreens(QObject *parent = nullptr);
|
||||
virtual OpenGLBackend *createOpenGLBackend();
|
||||
virtual QPainterBackend *createQPainterBackend();
|
||||
virtual DmaBufTexture *createDmaBufTexture(const QSize &size) {
|
||||
Q_UNUSED(size);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the Platform that it is about to go down and shall do appropriate cleanup.
|
||||
|
|
115
screencast/pipewirecore.cpp
Normal file
115
screencast/pipewirecore.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright © 2018-2020 Red Hat, Inc
|
||||
* Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors:
|
||||
* Jan Grulich <jgrulich@redhat.com>
|
||||
* Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*/
|
||||
|
||||
#include "pipewirecore.h"
|
||||
#include <QDebug>
|
||||
#include <QSocketNotifier>
|
||||
#include <KLocalizedString>
|
||||
#include "kwinpipewire_logging.h"
|
||||
|
||||
PipeWireCore::PipeWireCore()
|
||||
{
|
||||
pw_init(nullptr, nullptr);
|
||||
pwCoreEvents.version = PW_VERSION_CORE_EVENTS;
|
||||
pwCoreEvents.error = &PipeWireCore::onCoreError;
|
||||
}
|
||||
|
||||
PipeWireCore::~PipeWireCore()
|
||||
{
|
||||
if (pwMainLoop) {
|
||||
pw_loop_leave(pwMainLoop);
|
||||
}
|
||||
|
||||
if (pwCore) {
|
||||
pw_core_disconnect(pwCore);
|
||||
}
|
||||
|
||||
if (pwContext) {
|
||||
pw_context_destroy(pwContext);
|
||||
}
|
||||
|
||||
if (pwMainLoop) {
|
||||
pw_loop_destroy(pwMainLoop);
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireCore::onCoreError(void* data, uint32_t id, int seq, int res, const char* message)
|
||||
{
|
||||
Q_UNUSED(seq)
|
||||
|
||||
qCWarning(KWIN_PIPEWIRE) << "PipeWire remote error: " << message;
|
||||
if (id == PW_ID_CORE && res == -EPIPE) {
|
||||
PipeWireCore *pw = static_cast<PipeWireCore*>(data);
|
||||
Q_EMIT pw->pipewireFailed(QString::fromUtf8(message));
|
||||
}
|
||||
}
|
||||
|
||||
bool PipeWireCore::init()
|
||||
{
|
||||
pwMainLoop = pw_loop_new(nullptr);
|
||||
pw_loop_enter(pwMainLoop);
|
||||
|
||||
QSocketNotifier *notifier = new QSocketNotifier(pw_loop_get_fd(pwMainLoop), QSocketNotifier::Read, this);
|
||||
connect(notifier, &QSocketNotifier::activated, this, [this] {
|
||||
int result = pw_loop_iterate (pwMainLoop, 0);
|
||||
if (result < 0)
|
||||
qCWarning(KWIN_PIPEWIRE) << "pipewire_loop_iterate failed: " << result;
|
||||
}
|
||||
);
|
||||
|
||||
pwContext = pw_context_new(pwMainLoop, nullptr, 0);
|
||||
if (!pwContext) {
|
||||
qCWarning(KWIN_PIPEWIRE) << "Failed to create PipeWire context";
|
||||
m_error = i18n("Failed to create PipeWire context");
|
||||
return false;
|
||||
}
|
||||
|
||||
pwCore = pw_context_connect(pwContext, nullptr, 0);
|
||||
if (!pwCore) {
|
||||
qCWarning(KWIN_PIPEWIRE) << "Failed to connect PipeWire context";
|
||||
m_error = i18n("Failed to connect PipeWire context");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pw_loop_iterate(pwMainLoop, 0) < 0) {
|
||||
qCWarning(KWIN_PIPEWIRE) << "Failed to start main PipeWire loop";
|
||||
m_error = i18n("Failed to start main PipeWire loop");
|
||||
return false;
|
||||
}
|
||||
|
||||
pw_core_add_listener(pwCore, &coreListener, &pwCoreEvents, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
QSharedPointer< PipeWireCore > PipeWireCore::self()
|
||||
{
|
||||
static QWeakPointer<PipeWireCore> global;
|
||||
QSharedPointer<PipeWireCore> ret;
|
||||
if (global) {
|
||||
ret = global.toStrongRef();
|
||||
} else {
|
||||
ret.reset(new PipeWireCore);
|
||||
ret->init();
|
||||
global = ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
54
screencast/pipewirecore.h
Normal file
54
screencast/pipewirecore.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright © 2018-2020 Red Hat, Inc
|
||||
* Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors:
|
||||
* Jan Grulich <jgrulich@redhat.com>
|
||||
* Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <spa/utils/hook.h>
|
||||
#include <pipewire/pipewire.h>
|
||||
|
||||
class PipeWireCore : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PipeWireCore();
|
||||
|
||||
static void onCoreError(void *data, uint32_t id, int seq, int res, const char *message);
|
||||
|
||||
~PipeWireCore();
|
||||
|
||||
bool init();
|
||||
|
||||
static QSharedPointer<PipeWireCore> self();
|
||||
|
||||
struct pw_core *pwCore = nullptr;
|
||||
struct pw_context *pwContext = nullptr;
|
||||
struct pw_loop *pwMainLoop = nullptr;
|
||||
spa_hook coreListener;
|
||||
QString m_error;
|
||||
|
||||
pw_core_events pwCoreEvents = {};
|
||||
|
||||
Q_SIGNALS:
|
||||
void pipewireFailed(const QString &message);
|
||||
};
|
476
screencast/pipewirestream.cpp
Normal file
476
screencast/pipewirestream.cpp
Normal file
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
* Copyright © 2018-2020 Red Hat, Inc
|
||||
* Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors:
|
||||
* Jan Grulich <jgrulich@redhat.com>
|
||||
* Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*/
|
||||
|
||||
#include "pipewirestream.h"
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <gbm.h>
|
||||
#include <spa/buffer/meta.h>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QPainter>
|
||||
#include "utils.h"
|
||||
#include "cursor.h"
|
||||
#include "main.h"
|
||||
#include "platform.h"
|
||||
#include "scenes/opengl/egl_dmabuf.h"
|
||||
#include "platformsupport/scenes/opengl/drm_fourcc.h"
|
||||
#include "kwineglimagetexture.h"
|
||||
#include "dmabuftexture.h"
|
||||
#include "pipewirecore.h"
|
||||
#include "kwinpipewire_logging.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
void PipeWireStream::onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message)
|
||||
{
|
||||
PipeWireStream *pw = static_cast<PipeWireStream*>(data);
|
||||
qCDebug(KWIN_PIPEWIRE) << "state changed"<< pw_stream_state_as_string(old) << " -> " << pw_stream_state_as_string(state) << error_message;
|
||||
|
||||
switch (state) {
|
||||
case PW_STREAM_STATE_ERROR:
|
||||
qCWarning(KWIN_PIPEWIRE) << "Stream error: " << error_message;
|
||||
break;
|
||||
case PW_STREAM_STATE_PAUSED:
|
||||
if (pw->nodeId() == 0 && pw->pwStream) {
|
||||
pw->pwNodeId = pw_stream_get_node_id(pw->pwStream);
|
||||
Q_EMIT pw->streamReady(pw->nodeId());
|
||||
}
|
||||
break;
|
||||
case PW_STREAM_STATE_STREAMING:
|
||||
Q_EMIT pw->startStreaming();
|
||||
break;
|
||||
case PW_STREAM_STATE_CONNECTING:
|
||||
break;
|
||||
case PW_STREAM_STATE_UNCONNECTED:
|
||||
if (!pw->m_stopped) {
|
||||
Q_EMIT pw->stopStreaming();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#define CURSOR_BPP 4
|
||||
#define CURSOR_META_SIZE(w,h) (sizeof(struct spa_meta_cursor) + \
|
||||
sizeof(struct spa_meta_bitmap) + w * h * CURSOR_BPP)
|
||||
|
||||
void PipeWireStream::newStreamParams()
|
||||
{
|
||||
const int bpp = videoFormat.format == SPA_VIDEO_FORMAT_RGB || videoFormat.format == SPA_VIDEO_FORMAT_BGR ? 3 : 4;
|
||||
auto stride = SPA_ROUND_UP_N (m_resolution.width() * bpp, 4);
|
||||
|
||||
uint8_t paramsBuffer[1024];
|
||||
spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT (paramsBuffer, sizeof (paramsBuffer));
|
||||
|
||||
spa_rectangle resolution = SPA_RECTANGLE(uint32_t(m_resolution.width()), uint32_t(m_resolution.height()));
|
||||
const auto cursorSize = KWin::Cursors::self()->currentCursor()->themeSize();
|
||||
const spa_pod *params[] = {
|
||||
(spa_pod*) spa_pod_builder_add_object(&pod_builder,
|
||||
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
|
||||
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&resolution),
|
||||
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 2, 16),
|
||||
SPA_PARAM_BUFFERS_blocks, SPA_POD_Int (1),
|
||||
SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride),
|
||||
SPA_PARAM_BUFFERS_size, SPA_POD_Int(stride * m_resolution.height()),
|
||||
SPA_PARAM_BUFFERS_align, SPA_POD_Int(16)),
|
||||
(spa_pod*) spa_pod_builder_add_object (&pod_builder,
|
||||
SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
|
||||
SPA_PARAM_META_type, SPA_POD_Id (SPA_META_Cursor),
|
||||
SPA_PARAM_META_size, SPA_POD_Int (CURSOR_META_SIZE (cursorSize, cursorSize)))
|
||||
};
|
||||
pw_stream_update_params(pwStream, params, 2);
|
||||
}
|
||||
|
||||
void PipeWireStream::onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format)
|
||||
{
|
||||
if (!format || id != SPA_PARAM_Format) {
|
||||
return;
|
||||
}
|
||||
|
||||
PipeWireStream *pw = static_cast<PipeWireStream *>(data);
|
||||
spa_format_video_raw_parse (format, &pw->videoFormat);
|
||||
qCDebug(KWIN_PIPEWIRE) << "Stream format changed" << pw << pw->videoFormat.format;
|
||||
pw->newStreamParams();
|
||||
}
|
||||
|
||||
void PipeWireStream::onStreamAddBuffer(void *data, pw_buffer *buffer)
|
||||
{
|
||||
PipeWireStream *stream = static_cast<PipeWireStream *>(data);
|
||||
struct spa_data *spa_data = buffer->buffer->datas;
|
||||
|
||||
spa_data->mapoffset = 0;
|
||||
spa_data->flags = SPA_DATA_FLAG_READWRITE;
|
||||
|
||||
QSharedPointer<KWin::DmaBufTexture> dmabuf (KWin::kwinApp()->platform()->createDmaBufTexture(stream->m_resolution));
|
||||
if (dmabuf) {
|
||||
spa_data->type = SPA_DATA_DmaBuf;
|
||||
spa_data->fd = dmabuf->fd();
|
||||
spa_data->data = NULL;
|
||||
spa_data->maxsize = dmabuf->stride() * stream->m_resolution.height();
|
||||
|
||||
stream->m_dmabufDataForPwBuffer.insert(buffer, dmabuf);
|
||||
} else {
|
||||
const int bytesPerPixel = stream->m_hasAlpha ? 4 : 3;
|
||||
const int stride = SPA_ROUND_UP_N (stream->m_resolution.width() * bytesPerPixel, 4);
|
||||
spa_data->maxsize = stride * stream->m_resolution.height();
|
||||
spa_data->type = SPA_DATA_MemFd;
|
||||
spa_data->fd = memfd_create("kwin-screencast-memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
||||
if (spa_data->fd == -1) {
|
||||
qCCritical(KWIN_PIPEWIRE) << "memfd: Can't create memfd";
|
||||
return;
|
||||
}
|
||||
spa_data->mapoffset = 0;
|
||||
|
||||
if (ftruncate (spa_data->fd, spa_data->maxsize) < 0) {
|
||||
qCCritical(KWIN_PIPEWIRE) << "memfd: Can't truncate to" << spa_data->maxsize;
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
|
||||
if (fcntl(spa_data->fd, F_ADD_SEALS, seals) == -1)
|
||||
qCWarning(KWIN_PIPEWIRE) << "memfd: Failed to add seals";
|
||||
|
||||
spa_data->data = mmap(NULL,
|
||||
spa_data->maxsize,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
spa_data->fd,
|
||||
spa_data->mapoffset);
|
||||
if (spa_data->data == MAP_FAILED)
|
||||
qCCritical(KWIN_PIPEWIRE) << "memfd: Failed to mmap memory";
|
||||
else
|
||||
qCDebug(KWIN_PIPEWIRE) << "memfd: created successfully" << spa_data->data << spa_data->maxsize;
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireStream::onStreamRemoveBuffer(void *data, pw_buffer *buffer)
|
||||
{
|
||||
PipeWireStream *stream = static_cast<PipeWireStream *>(data);
|
||||
|
||||
struct spa_buffer *spa_buffer = buffer->buffer;
|
||||
struct spa_data *spa_data = spa_buffer->datas;
|
||||
|
||||
if (spa_data->type == SPA_DATA_DmaBuf) {
|
||||
stream->m_dmabufDataForPwBuffer.remove(buffer);
|
||||
} else if (spa_data->type == SPA_DATA_MemFd) {
|
||||
munmap (spa_data->data, spa_data->maxsize);
|
||||
close (spa_data->fd);
|
||||
}
|
||||
}
|
||||
|
||||
PipeWireStream::PipeWireStream(bool hasAlpha, const QSize &resolution, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_resolution(resolution)
|
||||
, m_hasAlpha(hasAlpha)
|
||||
{
|
||||
pwStreamEvents.version = PW_VERSION_STREAM_EVENTS;
|
||||
pwStreamEvents.add_buffer = &PipeWireStream::onStreamAddBuffer;
|
||||
pwStreamEvents.remove_buffer = &PipeWireStream::onStreamRemoveBuffer;
|
||||
pwStreamEvents.state_changed = &PipeWireStream::onStreamStateChanged;
|
||||
pwStreamEvents.param_changed = &PipeWireStream::onStreamParamChanged;
|
||||
}
|
||||
|
||||
PipeWireStream::~PipeWireStream()
|
||||
{
|
||||
m_stopped = true;
|
||||
if (pwStream) {
|
||||
pw_stream_destroy(pwStream);
|
||||
}
|
||||
}
|
||||
|
||||
bool PipeWireStream::init()
|
||||
{
|
||||
pwCore = PipeWireCore::self();
|
||||
if (!pwCore->m_error.isEmpty()) {
|
||||
m_error = pwCore->m_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
connect(pwCore.data(), &PipeWireCore::pipewireFailed, this, &PipeWireStream::coreFailed);
|
||||
|
||||
if (!createStream()) {
|
||||
qCWarning(KWIN_PIPEWIRE) << "Failed to create PipeWire stream";
|
||||
m_error = i18n("Failed to create PipeWire stream");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint PipeWireStream::framerate()
|
||||
{
|
||||
if (pwStream) {
|
||||
return videoFormat.max_framerate.num / videoFormat.max_framerate.denom;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint PipeWireStream::nodeId()
|
||||
{
|
||||
return pwNodeId;
|
||||
}
|
||||
|
||||
bool PipeWireStream::createStream()
|
||||
{
|
||||
const QByteArray objname = "kwin-screencast-" + objectName().toUtf8();
|
||||
pwStream = pw_stream_new(pwCore->pwCore, objname, nullptr);
|
||||
|
||||
uint8_t buffer[1024];
|
||||
spa_pod_builder podBuilder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
spa_fraction minFramerate = SPA_FRACTION(1, 1);
|
||||
spa_fraction maxFramerate = SPA_FRACTION(25, 1);
|
||||
spa_fraction defaultFramerate = SPA_FRACTION(0, 1);
|
||||
|
||||
spa_rectangle resolution = SPA_RECTANGLE(uint32_t(m_resolution.width()), uint32_t(m_resolution.height()));
|
||||
|
||||
const auto format = m_hasAlpha || m_gbmDevice ? SPA_VIDEO_FORMAT_BGRA : SPA_VIDEO_FORMAT_BGR;
|
||||
|
||||
const spa_pod *param = (spa_pod*)spa_pod_builder_add_object(&podBuilder,
|
||||
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
|
||||
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
|
||||
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
|
||||
SPA_FORMAT_VIDEO_format, SPA_POD_Id(format),
|
||||
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&resolution),
|
||||
SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&defaultFramerate),
|
||||
SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&maxFramerate, &minFramerate, &maxFramerate));
|
||||
|
||||
pw_stream_add_listener(pwStream, &streamListener, &pwStreamEvents, this);
|
||||
auto flags = pw_stream_flags(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS);
|
||||
|
||||
if (pw_stream_connect(pwStream, PW_DIRECTION_OUTPUT, SPA_ID_INVALID, flags, ¶m, 1) != 0) {
|
||||
qCWarning(KWIN_PIPEWIRE) << "Could not connect to stream";
|
||||
pw_stream_destroy(pwStream);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_cursor.mode == KWaylandServer::ScreencastInterface::Embedded) {
|
||||
connect(KWin::Cursors::self(), &KWin::Cursors::positionChanged, this, [this] {
|
||||
if (m_cursor.lastFrameTexture) {
|
||||
m_repainting = true;
|
||||
recordFrame(m_cursor.lastFrameTexture.data(), QRegion{m_cursor.lastRect} | cursorGeometry(KWin::Cursors::self()->currentCursor()));
|
||||
m_repainting = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void PipeWireStream::coreFailed(const QString &errorMessage)
|
||||
{
|
||||
m_error = errorMessage;
|
||||
Q_EMIT stopStreaming();
|
||||
}
|
||||
|
||||
void PipeWireStream::stop()
|
||||
{
|
||||
m_stopped = true;
|
||||
delete this;
|
||||
}
|
||||
|
||||
static KWin::GLTexture *copyTexture(KWin::GLTexture *texture)
|
||||
{
|
||||
KWin::GLTexture *copy = new KWin::GLTexture(texture->internalFormat(), texture->size());
|
||||
copy->setFilter(GL_LINEAR);
|
||||
copy->setWrapMode(GL_CLAMP_TO_EDGE);
|
||||
|
||||
const QRect r({}, texture->size());
|
||||
|
||||
copy->bind();
|
||||
glCopyTextureSubImage2D(copy->texture(), 0, 0, 0, 0, 0, r.width(), r.height());
|
||||
copy->unbind();
|
||||
return copy;
|
||||
}
|
||||
|
||||
void PipeWireStream::recordFrame(KWin::GLTexture* frameTexture, const QRegion &damagedRegion)
|
||||
{
|
||||
Q_ASSERT(!m_stopped);
|
||||
Q_ASSERT(frameTexture);
|
||||
|
||||
if (frameTexture->size() != m_resolution) {
|
||||
m_resolution = frameTexture->size();
|
||||
newStreamParams();
|
||||
return;
|
||||
}
|
||||
|
||||
const char *error = "";
|
||||
auto state = pw_stream_get_state(pwStream, &error);
|
||||
if (state != PW_STREAM_STATE_STREAMING) {
|
||||
if (error) {
|
||||
qCWarning(KWIN_PIPEWIRE) << "Failed to record frame: stream is not active" << error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
struct pw_buffer *buffer = pw_stream_dequeue_buffer(pwStream);
|
||||
|
||||
if (!buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct spa_buffer *spa_buffer = buffer->buffer;
|
||||
struct spa_data *spa_data = spa_buffer->datas;
|
||||
|
||||
uint8_t *data = (uint8_t *) spa_data->data;
|
||||
if (!data && spa_buffer->datas->type != SPA_DATA_DmaBuf) {
|
||||
qCWarning(KWIN_PIPEWIRE) << "Failed to record frame: invalid buffer data";
|
||||
pw_stream_queue_buffer(pwStream, buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto size = frameTexture->size();
|
||||
spa_data->chunk->offset = 0;
|
||||
if (data) {
|
||||
const int bpp = data && !m_hasAlpha ? 3 : 4;
|
||||
const uint stride = SPA_ROUND_UP_N (size.width() * bpp, 4);
|
||||
const uint bufferSize = stride * size.height();
|
||||
|
||||
if (bufferSize > spa_data->maxsize) {
|
||||
qCDebug(KWIN_PIPEWIRE) << "Failed to record frame: frame is too big";
|
||||
pw_stream_queue_buffer(pwStream, buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
spa_data->chunk->size = bufferSize;
|
||||
spa_data->chunk->stride = stride;
|
||||
|
||||
frameTexture->bind();
|
||||
glGetTextureImage(frameTexture->texture(), 0, m_hasAlpha ? GL_BGRA : GL_BGR, GL_UNSIGNED_BYTE, bufferSize, data);
|
||||
auto cursor = KWin::Cursors::self()->currentCursor();
|
||||
if (m_cursor.mode == KWaylandServer::ScreencastInterface::Embedded && m_cursor.viewport.contains(cursor->pos())) {
|
||||
QImage dest(data, size.width(), size.height(), QImage::Format_RGBA8888_Premultiplied);
|
||||
QPainter painter(&dest);
|
||||
const auto position = (cursor->pos() - m_cursor.viewport.topLeft() - cursor->hotspot()) * m_cursor.scale;
|
||||
painter.drawImage(QRect{position, cursor->image().size()}, cursor->image());
|
||||
}
|
||||
} else {
|
||||
using namespace KWin;
|
||||
auto &buf = m_dmabufDataForPwBuffer[buffer];
|
||||
|
||||
spa_data->chunk->stride = buf->stride();
|
||||
spa_data->chunk->size = spa_data->maxsize;
|
||||
|
||||
GLRenderTarget::pushRenderTarget(buf->framebuffer());
|
||||
frameTexture->bind();
|
||||
|
||||
QRect r(QPoint(), size);
|
||||
auto shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture);
|
||||
|
||||
QMatrix4x4 mvp;
|
||||
mvp.ortho(r);
|
||||
shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp);
|
||||
|
||||
QRegion dr = damagedRegion;
|
||||
if (m_cursor.texture) {
|
||||
dr |= m_cursor.lastRect;
|
||||
}
|
||||
|
||||
frameTexture->render(damagedRegion, r, true);
|
||||
|
||||
auto cursor = KWin::Cursors::self()->currentCursor();
|
||||
if (m_cursor.mode == KWaylandServer::ScreencastInterface::Embedded && m_cursor.viewport.contains(cursor->pos())) {
|
||||
if (!m_repainting) //We need to copy the last version of the stream to render the moved cursor on top
|
||||
m_cursor.lastFrameTexture.reset(copyTexture(frameTexture));
|
||||
|
||||
if (!m_cursor.texture || m_cursor.lastKey != cursor->image().cacheKey())
|
||||
m_cursor.texture.reset(new KWin::GLTexture(cursor->image()));
|
||||
|
||||
m_cursor.texture->setYInverted(false);
|
||||
m_cursor.texture->bind();
|
||||
const auto cursorRect = cursorGeometry(cursor);
|
||||
mvp.translate(cursorRect.left(), r.height() - cursorRect.top() - cursor->image().height() * m_cursor.scale);
|
||||
shader->setUniform(GLShader::ModelViewProjectionMatrix, mvp);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
m_cursor.texture->render(cursorRect, cursorRect, true);
|
||||
glDisable(GL_BLEND);
|
||||
m_cursor.texture->unbind();
|
||||
m_cursor.lastRect = cursorRect;
|
||||
}
|
||||
ShaderManager::instance()->popShader();
|
||||
|
||||
GLRenderTarget::popRenderTarget();
|
||||
}
|
||||
frameTexture->unbind();
|
||||
|
||||
if (m_cursor.mode == KWaylandServer::ScreencastInterface::Metadata) {
|
||||
sendCursorData(KWin::Cursors::self()->currentCursor(),
|
||||
(spa_meta_cursor *) spa_buffer_find_meta_data (spa_buffer, SPA_META_Cursor, sizeof (spa_meta_cursor)));
|
||||
}
|
||||
|
||||
pw_stream_queue_buffer(pwStream, buffer);
|
||||
}
|
||||
|
||||
QRect PipeWireStream::cursorGeometry(KWin::Cursor *cursor) const
|
||||
{
|
||||
const auto position = (cursor->pos() - m_cursor.viewport.topLeft() - cursor->hotspot()) * m_cursor.scale;
|
||||
return QRect{position, m_cursor.texture->size()};
|
||||
}
|
||||
|
||||
void PipeWireStream::sendCursorData(KWin::Cursor* cursor, spa_meta_cursor *spa_meta_cursor)
|
||||
{
|
||||
if (!cursor || !spa_meta_cursor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto position = (cursor->pos() - m_cursor.viewport.topLeft()) * m_cursor.scale;
|
||||
|
||||
spa_meta_cursor->id = 1;
|
||||
spa_meta_cursor->position.x = position.x();
|
||||
spa_meta_cursor->position.y = position.y();
|
||||
spa_meta_cursor->hotspot.x = cursor->hotspot().x() * m_cursor.scale;
|
||||
spa_meta_cursor->hotspot.y = cursor->hotspot().y() * m_cursor.scale;
|
||||
spa_meta_cursor->bitmap_offset = 0;
|
||||
|
||||
const QImage image = cursor->image();
|
||||
if (image.cacheKey() == m_cursor.lastKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_cursor.lastKey = image.cacheKey();
|
||||
spa_meta_cursor->bitmap_offset = sizeof (struct spa_meta_cursor);
|
||||
|
||||
struct spa_meta_bitmap *spa_meta_bitmap = SPA_MEMBER (spa_meta_cursor,
|
||||
spa_meta_cursor->bitmap_offset,
|
||||
struct spa_meta_bitmap);
|
||||
spa_meta_bitmap->format = SPA_VIDEO_FORMAT_RGBA;
|
||||
spa_meta_bitmap->offset = sizeof (struct spa_meta_bitmap);
|
||||
|
||||
uint8_t *bitmap_data = SPA_MEMBER (spa_meta_bitmap, spa_meta_bitmap->offset, uint8_t);
|
||||
QImage dest(bitmap_data, image.width(), image.height(), QImage::Format_RGBA8888_Premultiplied);
|
||||
spa_meta_bitmap->size.width = image.width();
|
||||
spa_meta_bitmap->size.height = image.height();
|
||||
spa_meta_bitmap->stride = dest.bytesPerLine();
|
||||
|
||||
QPainter painter(&dest);
|
||||
painter.drawImage(QPoint(), image);
|
||||
}
|
||||
|
||||
void PipeWireStream::setCursorMode(KWaylandServer::ScreencastInterface::CursorMode mode, qreal scale, const QRect &viewport)
|
||||
{
|
||||
m_cursor.mode = mode;
|
||||
m_cursor.scale = scale;
|
||||
m_cursor.viewport = viewport;
|
||||
}
|
114
screencast/pipewirestream.h
Normal file
114
screencast/pipewirestream.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright © 2018-2020 Red Hat, Inc
|
||||
* Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors:
|
||||
* Jan Grulich <jgrulich@redhat.com>
|
||||
* Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSize>
|
||||
#include <QHash>
|
||||
#include <QSharedPointer>
|
||||
#include <KWaylandServer/screencast_interface.h>
|
||||
|
||||
#include "kwinglobals.h"
|
||||
#include "config-kwin.h"
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <spa/param/format-utils.h>
|
||||
#include <spa/param/video/format-utils.h>
|
||||
#include <spa/param/props.h>
|
||||
|
||||
#undef Status
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
class Cursor;
|
||||
class GLTexture;
|
||||
class DmaBufTexture;
|
||||
}
|
||||
class PipeWireCore;
|
||||
|
||||
class KWIN_EXPORT PipeWireStream : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PipeWireStream(bool hasAlpha, const QSize &resolution, QObject *parent);
|
||||
~PipeWireStream();
|
||||
|
||||
bool init();
|
||||
uint framerate();
|
||||
uint nodeId();
|
||||
QString error() const {
|
||||
return m_error;
|
||||
}
|
||||
|
||||
void stop();
|
||||
|
||||
/** Renders @p frame into the current framebuffer into the stream */
|
||||
void recordFrame(KWin::GLTexture *frame, const QRegion &damagedRegion);
|
||||
|
||||
void setCursorMode(KWaylandServer::ScreencastInterface::CursorMode mode, qreal scale, const QRect &viewport);
|
||||
|
||||
Q_SIGNALS:
|
||||
void streamReady(quint32 nodeId);
|
||||
void startStreaming();
|
||||
void stopStreaming();
|
||||
|
||||
private:
|
||||
static void onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format);
|
||||
static void onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message);
|
||||
static void onStreamAddBuffer(void *data, pw_buffer *buffer);
|
||||
static void onStreamRemoveBuffer(void *data, pw_buffer *buffer);
|
||||
|
||||
bool createStream();
|
||||
void updateParams();
|
||||
void coreFailed(const QString &errorMessage);
|
||||
void sendCursorData(KWin::Cursor* cursor, spa_meta_cursor *spa_cursor);
|
||||
void newStreamParams();
|
||||
|
||||
QSharedPointer<PipeWireCore> pwCore;
|
||||
struct pw_stream *pwStream = nullptr;
|
||||
spa_hook streamListener;
|
||||
pw_stream_events pwStreamEvents = {};
|
||||
|
||||
uint32_t pwNodeId = 0;
|
||||
|
||||
QSize m_resolution;
|
||||
bool m_stopped = false;
|
||||
|
||||
spa_video_info_raw videoFormat;
|
||||
QString m_error;
|
||||
const bool m_hasAlpha;
|
||||
struct gbm_device *m_gbmDevice = nullptr;
|
||||
|
||||
struct {
|
||||
KWaylandServer::ScreencastInterface::CursorMode mode = KWaylandServer::ScreencastInterface::Hidden;
|
||||
qreal scale = 1;
|
||||
QRect viewport;
|
||||
qint64 lastKey = 0;
|
||||
QRect lastRect;
|
||||
QScopedPointer<KWin::GLTexture> texture;
|
||||
QScopedPointer<KWin::GLTexture> lastFrameTexture;
|
||||
} m_cursor;
|
||||
bool m_repainting = false;
|
||||
QRect cursorGeometry(KWin::Cursor *cursor) const;
|
||||
|
||||
QHash<struct pw_buffer *, QSharedPointer<KWin::DmaBufTexture>> m_dmabufDataForPwBuffer;
|
||||
};
|
190
screencast/screencastmanager.cpp
Normal file
190
screencast/screencastmanager.cpp
Normal file
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors:
|
||||
* Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*/
|
||||
|
||||
#include "screencastmanager.h"
|
||||
#include "scene.h"
|
||||
#include "workspace.h"
|
||||
#include "composite.h"
|
||||
#include "platform.h"
|
||||
#include "abstract_wayland_output.h"
|
||||
#include "plugins/scenes/opengl/scene_opengl.h"
|
||||
#include "pipewirestream.h"
|
||||
|
||||
#include <KWaylandServer/output_interface.h>
|
||||
#include <KLocalizedString>
|
||||
#include <abstract_client.h>
|
||||
#include <effects.h>
|
||||
#include <deleted.h>
|
||||
|
||||
using namespace KWin;
|
||||
|
||||
ScreencastManager::ScreencastManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
connect(waylandServer()->screencast(), &KWaylandServer::ScreencastInterface::windowScreencastRequested, this, &ScreencastManager::streamWindow);
|
||||
connect(waylandServer()->screencast(), &KWaylandServer::ScreencastInterface::outputScreencastRequested, this, &ScreencastManager::streamOutput);
|
||||
}
|
||||
class EGLFence : public QObject
|
||||
{
|
||||
public:
|
||||
EGLFence(EGLDisplay eglDisplay)
|
||||
: m_eglDisplay(eglDisplay)
|
||||
, m_sync(eglCreateSync(eglDisplay, EGL_SYNC_FENCE_KHR, NULL))
|
||||
{
|
||||
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:
|
||||
WindowStream(KWin::Toplevel *toplevel, QObject *parent)
|
||||
: 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() {
|
||||
auto scene = KWin::Compositor::self()->scene();
|
||||
connect(scene, &Scene::frameRendered, this, &WindowStream::bufferToStream);
|
||||
|
||||
connect(m_toplevel, &Toplevel::damaged, this, &WindowStream::includeDamage);
|
||||
m_toplevel->damaged(m_toplevel, m_toplevel->frameGeometry());
|
||||
}
|
||||
|
||||
void includeDamage(KWin::Toplevel *toplevel, const QRect &damage) {
|
||||
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;
|
||||
KWin::Toplevel *m_toplevel;
|
||||
};
|
||||
|
||||
void ScreencastManager::streamWindow(KWaylandServer::ScreencastStreamInterface *waylandStream, const QString &winid)
|
||||
{
|
||||
auto *toplevel = KWin::Workspace::self()->findToplevel(winid);
|
||||
|
||||
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,
|
||||
::wl_resource *outputResource,
|
||||
KWaylandServer::ScreencastInterface::CursorMode mode)
|
||||
{
|
||||
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) {
|
||||
auto scene = KWin::Compositor::self()->scene();
|
||||
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] {
|
||||
KWin::Compositor::self()->addRepaint(streamOutput->geometry());
|
||||
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;
|
||||
}
|
||||
}
|
49
screencast/screencastmanager.h
Normal file
49
screencast/screencastmanager.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors:
|
||||
* Jan Grulich <jgrulich@redhat.com>
|
||||
* Aleix Pol Gonzalez <aleixpol@kde.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "wayland_server.h"
|
||||
#include <KWaylandServer/screencast_interface.h>
|
||||
|
||||
class PipeWireStream;
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
class AbstractEglBackend;
|
||||
}
|
||||
|
||||
class ScreencastManager
|
||||
: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ScreencastManager(QObject *parent);
|
||||
|
||||
void streamWindow(KWaylandServer::ScreencastStreamInterface *stream, const QString &winid);
|
||||
void streamOutput(KWaylandServer::ScreencastStreamInterface *stream,
|
||||
::wl_resource *outputResource,
|
||||
KWaylandServer::ScreencastInterface::CursorMode mode);
|
||||
|
||||
private:
|
||||
void integrateStreams(KWaylandServer::ScreencastStreamInterface *waylandStream, PipeWireStream *pipewireStream);
|
||||
};
|
||||
|
|
@ -240,7 +240,7 @@ public:
|
|||
return KWin::fetchRequestedInterfaces(client->executablePath());
|
||||
}
|
||||
|
||||
const QSet<QByteArray> interfacesBlackList = {"org_kde_kwin_remote_access_manager", "org_kde_plasma_window_management", "org_kde_kwin_fake_input", "org_kde_kwin_keystate"};
|
||||
const QSet<QByteArray> interfacesBlackList = {"org_kde_kwin_remote_access_manager", "org_kde_plasma_window_management", "org_kde_kwin_fake_input", "org_kde_kwin_keystate", "zkde_screencast_unstable_v1"};
|
||||
QSet<QString> m_reported;
|
||||
|
||||
bool allowInterface(KWaylandServer::ClientConnection *client, const QByteArray &interfaceName) override {
|
||||
|
@ -458,6 +458,8 @@ bool WaylandServer::init(const QByteArray &socketName, InitializationFlags flags
|
|||
m_keyState = m_display->createKeyStateInterface(m_display);
|
||||
m_keyState->create();
|
||||
|
||||
m_screencast = m_display->createScreencastInterface(m_display);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ class LinuxDmabufUnstableV1Buffer;
|
|||
class TabletManagerInterface;
|
||||
class KeyboardShortcutsInhibitManagerV1Interface;
|
||||
class XdgDecorationManagerV1Interface;
|
||||
class ScreencastInterface;
|
||||
}
|
||||
|
||||
|
||||
|
@ -127,6 +128,9 @@ public:
|
|||
{
|
||||
return m_windowManagement;
|
||||
}
|
||||
KWaylandServer::ScreencastInterface *screencast() {
|
||||
return m_screencast;
|
||||
}
|
||||
KWaylandServer::ServerSideDecorationManagerInterface *decorationManager() const {
|
||||
return m_decorationManager;
|
||||
}
|
||||
|
@ -281,6 +285,7 @@ private:
|
|||
KWaylandServer::LinuxDmabufUnstableV1Interface *m_linuxDmabuf = nullptr;
|
||||
KWaylandServer::KeyboardShortcutsInhibitManagerV1Interface *m_keyboardShortcutsInhibitManager = nullptr;
|
||||
QSet<KWaylandServer::LinuxDmabufUnstableV1Buffer*> m_linuxDmabufBuffers;
|
||||
KWaylandServer::ScreencastInterface *m_screencast = nullptr;
|
||||
struct {
|
||||
KWaylandServer::ClientConnection *client = nullptr;
|
||||
QMetaObject::Connection destroyConnection;
|
||||
|
|
Loading…
Reference in a new issue