Commit 38eb72ef authored by Aleix Pol Gonzalez's avatar Aleix Pol Gonzalez 🐧
Browse files

screencasting: integrate zkde_screencast_unstable_v1

Includes a PipeWire implementation that will send the relevant streams
to the processes that need them.
parent 27ea1b95
......@@ -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()
......
/*
* 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();
}
/*
* 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
)
......
/********************************************************************
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);
}
/********************************************************************
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()
......
......@@ -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.
......
/*
* 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;
}
/*
* 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);
};
/*
* 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";