diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,7 +30,6 @@ set (xdg_desktop_portal_kde_SRCS ${xdg_desktop_portal_kde_SRCS} screencast.cpp - screencaststream.cpp screencastwidget.cpp screenchooserdialog.cpp remotedesktop.cpp @@ -88,4 +87,4 @@ install(FILES xdg-desktop-portal-kde.notifyrc - DESTINATION ${KNOTIFYRC_INSTALL_DIR}) \ No newline at end of file + DESTINATION ${KNOTIFYRC_INSTALL_DIR}) diff --git a/src/remotedesktop.cpp b/src/remotedesktop.cpp --- a/src/remotedesktop.cpp +++ b/src/remotedesktop.cpp @@ -55,8 +55,8 @@ return 2; } - connect(session, &Session::closed, [this] () { - WaylandIntegration::stopStreaming(); + connect(session, &Session::closed, [] () { + WaylandIntegration::stopAllStreaming(); }); return 0; diff --git a/src/remotedesktopdialog.h b/src/remotedesktopdialog.h --- a/src/remotedesktopdialog.h +++ b/src/remotedesktopdialog.h @@ -22,6 +22,7 @@ #define XDG_DESKTOP_PORTAL_KDE_REMOTEDESKTOP_DIALOG_H #include +#include #include "remotedesktop.h" @@ -38,7 +39,7 @@ bool multiple = false, QDialog *parent = nullptr, Qt::WindowFlags flags = {}); ~RemoteDesktopDialog(); - QList selectedScreens() const; + QVector selectedScreens() const; RemoteDesktopPortal::DeviceTypes deviceTypes() const; private: diff --git a/src/remotedesktopdialog.cpp b/src/remotedesktopdialog.cpp --- a/src/remotedesktopdialog.cpp +++ b/src/remotedesktopdialog.cpp @@ -82,7 +82,7 @@ delete m_dialog; } -QList RemoteDesktopDialog::selectedScreens() const +QVector RemoteDesktopDialog::selectedScreens() const { return m_dialog->screenCastWidget->selectedScreens(); } diff --git a/src/screencast.cpp b/src/screencast.cpp --- a/src/screencast.cpp +++ b/src/screencast.cpp @@ -57,8 +57,8 @@ return 2; } - connect(session, &Session::closed, [this] () { - WaylandIntegration::stopStreaming(); + connect(session, &Session::closed, [] () { + WaylandIntegration::stopAllStreaming(); }); return 0; diff --git a/src/screencaststream.h b/src/screencaststream.h deleted file mode 100644 --- a/src/screencaststream.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright © 2018-2020 Red Hat, Inc - * - * 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 . - * - * Authors: - * Jan Grulich - */ - -#ifndef SCREEN_CAST_STREAM_H -#define SCREEN_CAST_STREAM_H - -#include "waylandintegration.h" - -#include -#include - -#include -#include -#include -#include - -#if PW_CHECK_VERSION(0, 2, 90) -#include -#endif - -#if !PW_CHECK_VERSION(0, 2, 90) -class PwType { -public: - spa_type_media_type media_type; - spa_type_media_subtype media_subtype; - spa_type_format_video format_video; - spa_type_video_format video_format; -}; -#endif - -class ScreenCastStream : public QObject -{ - Q_OBJECT -public: - explicit ScreenCastStream(const QSize &resolution, QObject *parent = nullptr); - ~ScreenCastStream(); - -#if PW_CHECK_VERSION(0, 2, 90) - static void onCoreError(void *data, uint32_t id, int seq, int res, const char *message); - static void onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format); -#else - static void onStateChanged(void *data, pw_remote_state old, pw_remote_state state, const char *error); - static void onStreamFormatChanged(void *data, const struct spa_pod *format); -#endif - static void onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message); - static void onStreamProcess(void *data); - - // Public - void init(); - uint framerate(); - uint nodeId(); - - // Public because we need access from static functions - pw_stream *createStream(); - void removeStream(); - -public Q_SLOTS: - bool recordFrame(gbm_bo *bo, quint32 width, quint32 height, quint32 stride); - -Q_SIGNALS: - void streamReady(uint nodeId); - void startStreaming(); - void stopStreaming(); - -#if !PW_CHECK_VERSION(0, 2, 90) -private: - void initializePwTypes(); -#endif - -public: -#if PW_CHECK_VERSION(0, 2, 90) - struct pw_context *pwContext = nullptr; - struct pw_core *pwCore = nullptr; - struct pw_stream *pwStream = nullptr; - struct pw_thread_loop *pwMainLoop = nullptr; - - spa_hook coreListener; - spa_hook streamListener; - - // event handlers - pw_core_events pwCoreEvents = {}; - pw_stream_events pwStreamEvents = {}; - - uint32_t pwNodeId = 0; -#else - pw_core *pwCore = nullptr; - pw_loop *pwLoop = nullptr; - pw_thread_loop *pwMainLoop = nullptr; - pw_stream *pwStream = nullptr; - pw_remote *pwRemote = nullptr; - pw_type *pwCoreType = nullptr; - PwType *pwType = nullptr; - - spa_hook remoteListener; - spa_hook streamListener; - - // event handlers - pw_remote_events pwRemoteEvents = {}; - pw_stream_events pwStreamEvents = {}; -#endif - - QSize resolution; - - spa_video_info_raw videoFormat; - -}; - -#endif // SCREEN_CAST_STREAM_H diff --git a/src/screencaststream.cpp b/src/screencaststream.cpp deleted file mode 100644 --- a/src/screencaststream.cpp +++ /dev/null @@ -1,611 +0,0 @@ -/* - * Copyright © 2018-2020 Red Hat, Inc - * - * 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 . - * - * Authors: - * Jan Grulich - */ - -#include "screencaststream.h" - -#include -#include -#include -#include - -#include - -#include -#include - -Q_LOGGING_CATEGORY(XdgDesktopPortalKdeScreenCastStream, "xdp-kde-screencast-stream") - -class PwFraction { -public: - int num; - int denom; -}; - -// Stolen from mutter - -#define MAX_TERMS 30 -#define MIN_DIVISOR 1.0e-10 -#define MAX_ERROR 1.0e-20 - -#define PROP_RANGE(min, max) 2, (min), (max) - -#define BITS_PER_PIXEL 4 - -static int greatestCommonDivisor(int a, int b) -{ - while (b != 0) { - int temp = a; - - a = b; - b = temp % b; - } - - return qAbs(a); -} - -static PwFraction pipewireFractionFromDouble(double src) -{ - double V, F; /* double being converted */ - int N, D; /* will contain the result */ - int A; /* current term in continued fraction */ - int64_t N1, D1; /* numerator, denominator of last approx */ - int64_t N2, D2; /* numerator, denominator of previous approx */ - int i; - int gcd; - bool negative = false; - - /* initialize fraction being converted */ - F = src; - if (F < 0.0) { - F = -F; - negative = true; - } - - V = F; - /* initialize fractions with 1/0, 0/1 */ - N1 = 1; - D1 = 0; - N2 = 0; - D2 = 1; - N = 1; - D = 1; - - for (i = 0; i < MAX_TERMS; ++i) { - /* get next term */ - A = (int) F; /* no floor() needed, F is always >= 0 */ - /* get new divisor */ - F = F - A; - - /* calculate new fraction in temp */ - N2 = N1 * A + N2; - D2 = D1 * A + D2; - - /* guard against overflow */ - if (N2 > INT_MAX || D2 > INT_MAX) - break; - - N = N2; - D = D2; - - /* save last two fractions */ - N2 = N1; - D2 = D1; - N1 = N; - D1 = D; - - /* quit if dividing by zero or close enough to target */ - if (F < MIN_DIVISOR || fabs (V - ((double) N) / D) < MAX_ERROR) - break; - - /* Take reciprocal */ - F = 1 / F; - } - - /* fix for overflow */ - if (D == 0) { - N = INT_MAX; - D = 1; - } - - /* fix for negative */ - if (negative) - N = -N; - - /* simplify */ - gcd = greatestCommonDivisor(N, D); - if (gcd) { - N /= gcd; - D /= gcd; - } - - PwFraction fraction; - fraction.num = N; - fraction.denom = D; - - return fraction; -} - -#if PW_CHECK_VERSION(0, 2, 90) -void ScreenCastStream::onCoreError(void *data, uint32_t id, int seq, int res, const char *message) -{ - Q_UNUSED(seq) - ScreenCastStream *pw = static_cast(data); - - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "PipeWire remote error: " << message; - - if (id == PW_ID_CORE) { - if (res == -EPIPE) { - Q_EMIT pw->stopStreaming(); - } - } -} -#else -void ScreenCastStream::onStateChanged(void *data, pw_remote_state old, pw_remote_state state, const char *error) -{ - Q_UNUSED(old); - - ScreenCastStream *pw = static_cast(data); - - switch (state) { - case PW_REMOTE_STATE_ERROR: - // TODO notify error - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Remote error: " << error; - break; - case PW_REMOTE_STATE_CONNECTED: - // TODO notify error - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Remote state: " << pw_remote_state_as_string(state); - pw->pwStream = pw->createStream(); - if (!pw->pwStream) { - Q_EMIT pw->stopStreaming(); - } - break; - default: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Remote state: " << pw_remote_state_as_string(state); - break; - } -} -#endif - -void ScreenCastStream::onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message) -{ - Q_UNUSED(old) - - ScreenCastStream *pw = static_cast(data); - -#if PW_CHECK_VERSION(0, 2, 90) - switch (state) { - case PW_STREAM_STATE_ERROR: - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Stream error: " << error_message; - break; - case PW_STREAM_STATE_PAUSED: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); - if (pw->nodeId() == 0 && pw->pwStream) { - pw->pwNodeId = pw_stream_get_node_id(pw->pwStream); - Q_EMIT pw->streamReady(pw->nodeId()); - } - if (WaylandIntegration::isStreamingEnabled()) { - Q_EMIT pw->stopStreaming(); - } - break; - case PW_STREAM_STATE_STREAMING: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); - Q_EMIT pw->startStreaming(); - break; - case PW_STREAM_STATE_UNCONNECTED: - case PW_STREAM_STATE_CONNECTING: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); - break; - } -#else - switch (state) { - case PW_STREAM_STATE_ERROR: - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Stream error: " << error_message; - break; - case PW_STREAM_STATE_CONFIGURE: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); - Q_EMIT pw->streamReady((uint)pw_stream_get_node_id(pw->pwStream)); - break; - case PW_STREAM_STATE_UNCONNECTED: - case PW_STREAM_STATE_CONNECTING: - case PW_STREAM_STATE_READY: - case PW_STREAM_STATE_PAUSED: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); - Q_EMIT pw->stopStreaming(); - break; - case PW_STREAM_STATE_STREAMING: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); - Q_EMIT pw->startStreaming(); - break; - } -#endif -} - -#if PW_CHECK_VERSION(0, 2, 90) -void ScreenCastStream::onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format) -#else -void ScreenCastStream::onStreamFormatChanged(void *data, const struct spa_pod *format) -#endif -{ - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream format changed"; - - ScreenCastStream *pw = static_cast(data); - - uint8_t paramsBuffer[1024]; - int32_t width, height, stride, size; - struct spa_pod_builder pod_builder; - const struct spa_pod *params[1]; - -#if PW_CHECK_VERSION(0, 2, 90) - if (!format || id != SPA_PARAM_Format) { -#else - if (!format) { - pw_stream_finish_format(pw->pwStream, 0, nullptr, 0); -#endif - return; - } - -#if PW_CHECK_VERSION(0, 2, 90) - spa_format_video_raw_parse (format, &pw->videoFormat); -#else - spa_format_video_raw_parse (format, &pw->videoFormat, &pw->pwType->format_video); -#endif - - width = pw->videoFormat.size.width; - height =pw->videoFormat.size.height; - stride = SPA_ROUND_UP_N (width * BITS_PER_PIXEL, 4); - size = height * stride; - - pod_builder = SPA_POD_BUILDER_INIT (paramsBuffer, sizeof (paramsBuffer)); - -#if PW_CHECK_VERSION(0, 2, 90) - params[0] = (spa_pod*) spa_pod_builder_add_object(&pod_builder, - SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 2, 16), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int (1), - SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), - SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(stride, stride, INT32_MAX), - SPA_PARAM_BUFFERS_align, SPA_POD_Int(16)); - pw_stream_update_params(pw->pwStream, params, 1); -#else - params[0] = (spa_pod*) spa_pod_builder_object(&pod_builder, - pw->pwCoreType->param.idBuffers, pw->pwCoreType->param_buffers.Buffers, - ":", pw->pwCoreType->param_buffers.size, "i", size, - ":", pw->pwCoreType->param_buffers.stride, "i", stride, - ":", pw->pwCoreType->param_buffers.buffers, "iru", 16, PROP_RANGE (2, 16), - ":", pw->pwCoreType->param_buffers.align, "i", 16); - pw_stream_finish_format (pw->pwStream, 0, params, 1); -#endif -} - -ScreenCastStream::ScreenCastStream(const QSize &resolution, QObject *parent) - : QObject(parent) - , resolution(resolution) -{ -#if PW_CHECK_VERSION(0, 2, 90) - pwCoreEvents.version = PW_VERSION_CORE_EVENTS; - pwCoreEvents.error = &onCoreError; - - pwStreamEvents.version = PW_VERSION_STREAM_EVENTS; - pwStreamEvents.state_changed = &onStreamStateChanged; - pwStreamEvents.param_changed = &onStreamParamChanged; -#else - // initialize event handlers, remote end and stream-related - pwRemoteEvents.version = PW_VERSION_REMOTE_EVENTS; - pwRemoteEvents.state_changed = &onStateChanged; - - pwStreamEvents.version = PW_VERSION_STREAM_EVENTS; - pwStreamEvents.state_changed = &onStreamStateChanged; - pwStreamEvents.format_changed = &onStreamFormatChanged; -#endif -} - -ScreenCastStream::~ScreenCastStream() -{ - if (pwMainLoop) { - pw_thread_loop_stop(pwMainLoop); - } - -#if !PW_CHECK_VERSION(0, 2, 90) - if (pwType) { - delete pwType; - } -#endif - - if (pwStream) { - pw_stream_destroy(pwStream); - } - -#if !PW_CHECK_VERSION(0, 2, 90) - if (pwRemote) { - pw_remote_destroy(pwRemote); - } -#endif - -#if PW_CHECK_VERSION(0, 2, 90) - if (pwCore) { - pw_core_disconnect(pwCore); - } - - if (pwContext) { - pw_context_destroy(pwContext); - } -#else - if (pwCore) { - pw_core_destroy(pwCore); - } -#endif - - if (pwMainLoop) { - pw_thread_loop_destroy(pwMainLoop); - } - -#if !PW_CHECK_VERSION(0, 2, 90) - if (pwLoop) { - pw_loop_leave(pwLoop); - pw_loop_destroy(pwLoop); - } -#endif -} - -void ScreenCastStream::init() -{ - pw_init(nullptr, nullptr); - - const auto emitFailureNotification = [](const QString &body) { - KNotification *notification = new KNotification(QStringLiteral("screencastfailure"), KNotification::CloseOnTimeout); - notification->setTitle(i18n("Failed to start screencasting")); - notification->setText(body); - notification->setIconName(QStringLiteral("dialog-error")); - notification->sendEvent(); - }; - -#if PW_CHECK_VERSION(0, 2, 90) - pwMainLoop = pw_thread_loop_new("pipewire-main-loop", nullptr); - pwContext = pw_context_new(pw_thread_loop_get_loop(pwMainLoop), nullptr, 0); - if (!pwContext) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to create PipeWire context"; - emitFailureNotification(i18n("Failed to create PipeWire context")); - return; - } - - pwCore = pw_context_connect(pwContext, nullptr, 0); - if (!pwCore) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to connect PipeWire context"; - emitFailureNotification(i18n("Failed to connect PipeWire context")); - return; - } - - pw_core_add_listener(pwCore, &coreListener, &pwCoreEvents, this); - - pwStream = createStream(); - if (!pwStream) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to create PipeWire stream"; - emitFailureNotification(i18n("Failed to create PipeWire stream")); - return; - } -#else - pwLoop = pw_loop_new(nullptr); - pwMainLoop = pw_thread_loop_new(pwLoop, "pipewire-main-loop"); - pwCore = pw_core_new(pwLoop, nullptr); - pwCoreType = pw_core_get_type(pwCore); - pwRemote = pw_remote_new(pwCore, nullptr, 0); - - initializePwTypes(); - - pw_remote_add_listener(pwRemote, &remoteListener, &pwRemoteEvents, this); - pw_remote_connect(pwRemote); -#endif - - - if (pw_thread_loop_start(pwMainLoop) < 0) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to start main PipeWire loop"; - emitFailureNotification(i18n("Failed to start main PipeWire loop")); - return; - } -} - -uint ScreenCastStream::framerate() -{ - if (pwStream) { - return videoFormat.max_framerate.num / videoFormat.max_framerate.denom; - } - - return 0; -} - -uint ScreenCastStream::nodeId() -{ -#if PW_CHECK_VERSION(0, 2, 90) - return pwNodeId; -#else - if (pwStream) { - return (uint)pw_stream_get_node_id(pwStream); - } - - return 0; -#endif -} - -pw_stream *ScreenCastStream::createStream() -{ -#if !PW_CHECK_VERSION(0, 2, 90) - if (pw_remote_get_state(pwRemote, nullptr) != PW_REMOTE_STATE_CONNECTED) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Cannot create pipewire stream"; - return nullptr; - } -#endif - - uint8_t buffer[1024]; - spa_pod_builder podBuilder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - - const float frameRate = 25; - - spa_fraction maxFramerate; - spa_fraction minFramerate; - const spa_pod *params[1]; - -#if PW_CHECK_VERSION(0, 2, 90) - auto stream = pw_stream_new(pwCore, "kwin-screen-cast", nullptr); -#else - auto stream = pw_stream_new(pwRemote, "kwin-screen-cast", nullptr); -#endif - - PwFraction fraction = pipewireFractionFromDouble(frameRate); - - minFramerate = SPA_FRACTION(1, 1); - maxFramerate = SPA_FRACTION((uint32_t)fraction.num, (uint32_t)fraction.denom); - - spa_rectangle minResolution = SPA_RECTANGLE(1, 1); - spa_rectangle maxResolution = SPA_RECTANGLE((uint32_t)resolution.width(), (uint32_t)resolution.height()); - - spa_fraction paramFraction = SPA_FRACTION(0, 1); - -#if PW_CHECK_VERSION(0, 2, 90) - params[0] = (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(SPA_VIDEO_FORMAT_RGBx), - SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&maxResolution, &minResolution, &maxResolution), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(¶mFraction), - SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&maxFramerate, &minFramerate, &maxFramerate)); -#else - params[0] = (spa_pod*)spa_pod_builder_object(&podBuilder, - pwCoreType->param.idEnumFormat, pwCoreType->spa_format, - "I", pwType->media_type.video, - "I", pwType->media_subtype.raw, - ":", pwType->format_video.format, "I", pwType->video_format.RGBx, - ":", pwType->format_video.size, "Rru", &maxResolution, SPA_POD_PROP_MIN_MAX(&minResolution, &maxResolution), - ":", pwType->format_video.framerate, "F", ¶mFraction, - ":", pwType->format_video.max_framerate, "Fru", &maxFramerate, PROP_RANGE (&minFramerate, &maxFramerate)); -#endif - - pw_stream_add_listener(stream, &streamListener, &pwStreamEvents, this); - - auto flags = static_cast(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_MAP_BUFFERS); - -#if PW_CHECK_VERSION(0, 2, 90) - if (pw_stream_connect(stream, PW_DIRECTION_OUTPUT, SPA_ID_INVALID, flags, params, 1) != 0) { -#else - if (pw_stream_connect(stream, PW_DIRECTION_OUTPUT, nullptr, flags, params, 1) != 0) { -#endif - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Could not connect to stream"; - return nullptr; - } - - return stream; -} - -bool ScreenCastStream::recordFrame(gbm_bo *bo, quint32 width, quint32 height, quint32 stride) -{ - struct pw_buffer *buffer; - struct spa_buffer *spa_buffer; - uint8_t *data = nullptr; - - if (!(buffer = pw_stream_dequeue_buffer(pwStream))) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: couldn't obtain PipeWire buffer"; - return false; - } - - spa_buffer = buffer->buffer; - - if (!(data = (uint8_t *) spa_buffer->datas[0].data)) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: invalid buffer data"; - pw_stream_queue_buffer(pwStream, buffer); - return false; - } - - quint32 streamStride; - const quint32 minStride = SPA_ROUND_UP_N(videoFormat.size.width * BITS_PER_PIXEL, 4); - const quint32 minSrcSize = height * minStride; - - const quint32 srcSize = height * stride; - const quint32 destSize = spa_buffer->datas[0].maxsize; - - // If we can fit source into the pipewire buffer, we can use stride we got from gbm_bo as the client - // should be able to handle it - if (srcSize <= destSize) { - streamStride = stride; - // Fallback to fixed minimum stride, which should be (width * bpp) - } else if (minSrcSize == destSize) { - streamStride = minStride; - } else { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: got buffer with higher stride than we can handle"; - pw_stream_queue_buffer(pwStream, buffer); - return false; - } - - // bind context to render thread - eglMakeCurrent(WaylandIntegration::egl().display, EGL_NO_SURFACE, EGL_NO_SURFACE, WaylandIntegration::egl().context); - - // create EGL image from imported BO - EGLImageKHR image = eglCreateImageKHR(WaylandIntegration::egl().display, nullptr, EGL_NATIVE_PIXMAP_KHR, bo, nullptr); - - if (image == EGL_NO_IMAGE_KHR) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: Error creating EGLImageKHR - " << WaylandIntegration::formatGLError(glGetError()); - pw_stream_queue_buffer(pwStream, buffer); - return false; - } - - // create GL 2D texture for framebuffer - GLuint texture; - glGenTextures(1, &texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindTexture(GL_TEXTURE_2D, texture); - glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); - - glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - - glDeleteTextures(1, &texture); - eglDestroyImageKHR(WaylandIntegration::egl().display, image); - - spa_buffer->datas[0].chunk->offset = 0; - spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize; - spa_buffer->datas[0].chunk->stride = streamStride; - - pw_stream_queue_buffer(pwStream, buffer); - return true; -} - -void ScreenCastStream::removeStream() -{ - // FIXME destroying streams seems to be crashing, Mutter also doesn't remove them, maybe Pipewire does this automatically - // pw_stream_destroy(pwStream); - // pwStream = nullptr; - pw_stream_disconnect(pwStream); -} - -#if !PW_CHECK_VERSION(0, 2, 90) -void ScreenCastStream::initializePwTypes() -{ - // raw C-like ScreenCastStream type map - auto map = pwCoreType->map; - - pwType = new PwType(); - - spa_type_media_type_map(map, &pwType->media_type); - spa_type_media_subtype_map(map, &pwType->media_subtype); - spa_type_format_video_map (map, &pwType->format_video); - spa_type_video_format_map (map, &pwType->video_format); -} -#endif diff --git a/src/screencastwidget.h b/src/screencastwidget.h --- a/src/screencastwidget.h +++ b/src/screencastwidget.h @@ -22,15 +22,16 @@ #define XDG_DESKTOP_PORTAL_KDE_SCREENCAST_WIDGET_H #include +#include class ScreenCastWidget : public QListWidget { Q_OBJECT public: ScreenCastWidget(QWidget *parent = nullptr); ~ScreenCastWidget(); - QList selectedScreens() const; + QVector selectedScreens() const; }; #endif // XDG_DESKTOP_PORTAL_KDE_SCREENCAST_WIDGET_H diff --git a/src/screencastwidget.cpp b/src/screencastwidget.cpp --- a/src/screencastwidget.cpp +++ b/src/screencastwidget.cpp @@ -26,37 +26,28 @@ ScreenCastWidget::ScreenCastWidget(QWidget *parent) : QListWidget(parent) { - QMapIterator it(WaylandIntegration::screens()); + QVectorIterator it(WaylandIntegration::screencastingSources()); while (it.hasNext()) { - it.next(); + const auto current = it.next(); QListWidgetItem *widgetItem = new QListWidgetItem(this); - widgetItem->setData(Qt::UserRole, it.key()); - if (it.value().outputType() == WaylandIntegration::WaylandOutput::Laptop) { - widgetItem->setIcon(QIcon::fromTheme(QStringLiteral("computer-laptop"))); - widgetItem->setText(i18n("Laptop screen\nModel: %1", it.value().model())); - } else if (it.value().outputType() == WaylandIntegration::WaylandOutput::Monitor) { - widgetItem->setIcon(QIcon::fromTheme(QStringLiteral("video-display"))); - widgetItem->setText(i18n("Manufacturer: %1\nModel: %2", it.value().manufacturer(), it.value().model())); - } else { - widgetItem->setIcon(QIcon::fromTheme(QStringLiteral("video-television"))); - widgetItem->setText(i18n("Manufacturer: %1\nModel: %2", it.value().manufacturer(), it.value().model())); - } + widgetItem->setData(Qt::UserRole, QVariant::fromValue(current)); + widgetItem->setIcon(QIcon::fromTheme(current.iconName())); + widgetItem->setText(current.description()); } + Q_ASSERT(count() > 0); itemAt(0, 0)->setSelected(true); } ScreenCastWidget::~ScreenCastWidget() { } -QList ScreenCastWidget::selectedScreens() const +QVector ScreenCastWidget::selectedScreens() const { - QList selectedScreens; - + QVector selectedScreens; for (QListWidgetItem *item : selectedItems()) { - selectedScreens << item->data(Qt::UserRole).toUInt(); + selectedScreens << item->data(Qt::UserRole).value(); } - return selectedScreens; } diff --git a/src/screenchooserdialog.h b/src/screenchooserdialog.h --- a/src/screenchooserdialog.h +++ b/src/screenchooserdialog.h @@ -22,6 +22,7 @@ #define XDG_DESKTOP_PORTAL_KDE_SCREENCHOOSER_DIALOG_H #include +#include namespace Ui { @@ -35,7 +36,7 @@ ScreenChooserDialog(const QString &appName, bool multiple, QDialog *parent = nullptr, Qt::WindowFlags flags = {}); ~ScreenChooserDialog(); - QList selectedScreens() const; + QVector selectedScreens() const; private: Ui::ScreenChooserDialog *m_dialog; diff --git a/src/screenchooserdialog.cpp b/src/screenchooserdialog.cpp --- a/src/screenchooserdialog.cpp +++ b/src/screenchooserdialog.cpp @@ -71,7 +71,7 @@ delete m_dialog; } -QList ScreenChooserDialog::selectedScreens() const +QVector ScreenChooserDialog::selectedScreens() const { return m_dialog->screenView->selectedScreens(); } diff --git a/src/waylandintegration.h b/src/waylandintegration.h --- a/src/waylandintegration.h +++ b/src/waylandintegration.h @@ -35,6 +35,7 @@ namespace KWayland { namespace Client { class PlasmaWindowManagement; + class ScreencastingSource; } } @@ -108,8 +109,8 @@ bool isStreamingEnabled(); void startStreamingInput(); - bool startStreaming(quint32 outputName); - void stopStreaming(); + bool startStreaming(const KWayland::Client::ScreencastingSource &source); + void stopAllStreaming(); void requestPointerButtonPress(quint32 linuxButton); void requestPointerButtonRelease(quint32 linuxButton); @@ -121,6 +122,7 @@ EGLStruct egl(); QMap screens(); + QVector screencastingSources(); QVariant streams(); #endif void init(); diff --git a/src/waylandintegration.cpp b/src/waylandintegration.cpp --- a/src/waylandintegration.cpp +++ b/src/waylandintegration.cpp @@ -21,14 +21,14 @@ #include "waylandintegration.h" #include "waylandintegration_p.h" - #include #include #include #include #include #include +#include #include @@ -45,11 +45,9 @@ #include #if HAVE_PIPEWIRE_SUPPORT -#include "screencaststream.h" - #include #include -#include +#include #endif Q_LOGGING_CATEGORY(XdgDesktopPortalKdeWaylandIntegration, "xdp-kde-wayland-integration") @@ -85,14 +83,14 @@ globalWaylandIntegration->startStreamingInput(); } -bool WaylandIntegration::startStreaming(quint32 outputName) +bool WaylandIntegration::startStreaming(const KWayland::Client::ScreencastingSource &source) { - return globalWaylandIntegration->startStreaming(outputName); + return globalWaylandIntegration->startStreaming(source); } -void WaylandIntegration::stopStreaming() +void WaylandIntegration::stopAllStreaming() { - globalWaylandIntegration->stopStreaming(); + globalWaylandIntegration->stopAllStreaming(); } void WaylandIntegration::requestPointerButtonPress(quint32 linuxButton) @@ -171,7 +169,7 @@ QLatin1String("LCD") }; for (const QLatin1String &pre : embedded) { - if (type.toUpper().startsWith(pre)) { + if (type.startsWith(pre, Qt::CaseInsensitive)) { m_outputType = OutputType::Laptop; return; } @@ -239,7 +237,7 @@ , m_registry(nullptr) #if HAVE_PIPEWIRE_SUPPORT , m_fakeInput(nullptr) - , m_remoteAccessManager(nullptr) + , m_screencasting(nullptr) #endif { #if HAVE_PIPEWIRE_SUPPORT @@ -251,10 +249,6 @@ WaylandIntegration::WaylandIntegrationPrivate::~WaylandIntegrationPrivate() { #if HAVE_PIPEWIRE_SUPPORT - if (m_remoteAccessManager) { - m_remoteAccessManager->destroy(); - } - if (m_gbmDevice) { gbm_device_destroy(m_gbmDevice); } @@ -269,104 +263,86 @@ bool WaylandIntegration::WaylandIntegrationPrivate::isStreamingEnabled() const { - return m_streamingEnabled; -} - -void WaylandIntegration::WaylandIntegrationPrivate::bindOutput(int outputName, int outputVersion) -{ - KWayland::Client::Output *output = new KWayland::Client::Output(this); - output->setup(m_registry->bindOutput(outputName, outputVersion)); - m_bindOutputs << output; + return m_streams.isEmpty(); } void WaylandIntegration::WaylandIntegrationPrivate::startStreamingInput() { m_streamInput = true; } -bool WaylandIntegration::WaylandIntegrationPrivate::startStreaming(quint32 outputName) +QVector WaylandIntegration::screencastingSources() { - WaylandOutput output = m_outputMap.value(outputName); - m_streamedScreenPosition = output.globalPosition(); + return globalWaylandIntegration->videoStreamingSources(); +} - m_stream = new ScreenCastStream(output.resolution()); - m_stream->init(); +bool WaylandIntegration::WaylandIntegrationPrivate::startStreaming(const KWayland::Client::ScreencastingSource &source) +{ + m_streamedScreenPosition = source.geometry().topLeft(); - connect(m_stream, &ScreenCastStream::startStreaming, this, [this, output] { - m_streamingEnabled = true; - startStreamingInput(); - bindOutput(output.waylandOutputName(), output.waylandOutputVersion()); - }); + m_screencasting->create(source); + QEventLoop loop; + bool streamReady = false; + connect(m_screencasting, &KWayland::Client::Screencasting::failed, this, [&] (const KWayland::Client::ScreencastingSource &newSource, const QString &error) { + if (source != newSource) { + return; + } + qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "failed to start streaming" << source.description() << error; - connect(m_stream, &ScreenCastStream::stopStreaming, this, &WaylandIntegrationPrivate::stopStreaming); + KNotification *notification = new KNotification(QStringLiteral("screencastfailure"), KNotification::CloseOnTimeout); + notification->setTitle(i18n("Failed to start screencasting")); + notification->setText(error); + notification->setIconName(QStringLiteral("dialog-error")); + notification->sendEvent(); - bool streamReady = false; - QEventLoop loop; - connect(m_stream, &ScreenCastStream::streamReady, this, [&loop, &streamReady] { + streamReady = false; loop.quit(); - streamReady = true; }); + connect(m_screencasting, &KWayland::Client::Screencasting::created, this, [&] (const KWayland::Client::ScreencastingSource& newSource, uint32_t nodeid) { + if (source != newSource) { + return; + } + m_streams.append({nodeid, {{QLatin1String("size"), source.geometry().size()}}}); + startStreamingInput(); - // HACK wait for stream to be ready - QTimer::singleShot(3000, &loop, &QEventLoop::quit); - loop.exec(); + streamReady = true; + loop.quit(); + }); - disconnect(m_stream, &ScreenCastStream::streamReady, this, nullptr); + connect(m_screencasting, &KWayland::Client::Screencasting::closed, this, &WaylandIntegrationPrivate::stopStreaming); - if (!streamReady) { - if (m_stream) { - delete m_stream; - m_stream = nullptr; - } - return false; - } + disconnect(m_screencasting, &KWayland::Client::Screencasting::created, this, nullptr); - // TODO support multiple outputs + return streamReady; +} - if (m_registry->hasInterface(KWayland::Client::Registry::Interface::RemoteAccessManager)) { - KWayland::Client::Registry::AnnouncedInterface interface = m_registry->interface(KWayland::Client::Registry::Interface::RemoteAccessManager); - if (!interface.name && !interface.version) { - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to start streaming: remote access manager interface is not initialized yet"; - return false; - } - m_remoteAccessManager = m_registry->createRemoteAccessManager(interface.name, interface.version); - connect(m_remoteAccessManager, &KWayland::Client::RemoteAccessManager::bufferReady, this, [this] (const void *output, const KWayland::Client::RemoteBuffer * rbuf) { - Q_UNUSED(output); - connect(rbuf, &KWayland::Client::RemoteBuffer::parametersObtained, this, [this, rbuf] { - processBuffer(rbuf); - }); - }); - m_output = output.waylandOutputName(); - return true; - } +void WaylandIntegration::WaylandIntegrationPrivate::Stream::close() +{ + globalWaylandIntegration->m_screencasting->close(nodeId); +} - if (m_stream) { - delete m_stream; - m_stream = nullptr; +void WaylandIntegration::WaylandIntegrationPrivate::stopAllStreaming() +{ + for (auto & stream : m_streams) { + stream.close(); } + m_streams.clear(); - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to start streaming: no remote access manager interface"; - return false; + m_streamInput = false; + // First unbound outputs and destroy remote access manager so we no longer receive buffers } -void WaylandIntegration::WaylandIntegrationPrivate::stopStreaming() +void WaylandIntegration::WaylandIntegrationPrivate::stopStreaming(uint32_t nodeid) { - m_streamInput = false; - if (m_streamingEnabled) { - m_streamingEnabled = false; - - // First unbound outputs and destroy remote access manager so we no longer receive buffers - if (m_remoteAccessManager) { - m_remoteAccessManager->release(); - m_remoteAccessManager->destroy(); + for(auto it = m_streams.begin(), itEnd = m_streams.end(); it != itEnd; ++it) { + if (it->nodeId == nodeid) { + m_streams.erase(it); + break; } - qDeleteAll(m_bindOutputs); - m_bindOutputs.clear(); + } - if (m_stream) { - delete m_stream; - m_stream = nullptr; - } + if (m_streams.isEmpty()) { + stopAllStreaming(); } } @@ -428,10 +404,12 @@ QVariant WaylandIntegration::WaylandIntegrationPrivate::streams() { - Stream stream; - stream.nodeId = m_stream->nodeId(); - stream.map = QVariantMap({{QLatin1String("size"), m_outputMap.value(m_output).resolution()}}); - return QVariant::fromValue({stream}); + return QVariant::fromValue(m_streams); +} + +QVector WaylandIntegration::WaylandIntegrationPrivate::videoStreamingSources() +{ + return m_screencasting->sources(); } void WaylandIntegration::WaylandIntegrationPrivate::initDrm() @@ -590,52 +568,6 @@ qCDebug(XdgDesktopPortalKdeWaylandIntegration) << " model: " << output.model(); } -void WaylandIntegration::WaylandIntegrationPrivate::processBuffer(const KWayland::Client::RemoteBuffer* rbuf) -{ - QScopedPointer guard(rbuf); - - auto gbmHandle = rbuf->fd(); - auto width = rbuf->width(); - auto height = rbuf->height(); - auto stride = rbuf->stride(); - auto format = rbuf->format(); - - qCDebug(XdgDesktopPortalKdeWaylandIntegration) << QStringLiteral("Incoming GBM fd %1, %2x%3, stride %4, fourcc 0x%5").arg(gbmHandle).arg(width).arg(height).arg(stride).arg(QString::number(format, 16)); - - if (!m_streamingEnabled) { - qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "Streaming is disabled"; - close(gbmHandle); - return; - } - - if (m_lastFrameTime.isValid() && - m_lastFrameTime.msecsTo(QDateTime::currentDateTime()) < (1000 / m_stream->framerate())) { - close(gbmHandle); - return; - } - - if (!gbm_device_is_format_supported(m_gbmDevice, format, GBM_BO_USE_SCANOUT)) { - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: GBM format is not supported by device!"; - close(gbmHandle); - return; - } - - // import GBM buffer that was passed from KWin - gbm_import_fd_data importInfo = {gbmHandle, width, height, stride, format}; - gbm_bo *imported = gbm_bo_import(m_gbmDevice, GBM_BO_IMPORT_FD, &importInfo, GBM_BO_USE_SCANOUT); - if (!imported) { - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: Cannot import passed GBM fd - " << strerror(errno); - close(gbmHandle); - return; - } - - if (m_stream->recordFrame(imported, width, height, stride)) { - m_lastFrameTime = QDateTime::currentDateTime(); - } - - gbm_bo_destroy(imported); - close(gbmHandle); -} #endif void WaylandIntegration::WaylandIntegrationPrivate::setupRegistry() @@ -653,6 +585,10 @@ connect(m_registry, &KWayland::Client::Registry::outputRemoved, this, &WaylandIntegrationPrivate::removeOutput); #endif + connect(m_registry, &KWayland::Client::Registry::screencastingAnnounced, this, [this] (quint32 name, quint32 version) { + qDebug() << "screen!"; + m_screencasting = m_registry->createScreencasting(name, version, this); + }); connect(m_registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, this, [this] (quint32 name, quint32 version) { m_windowManagement = m_registry->createPlasmaWindowManagement(name, version, this); Q_EMIT waylandIntegration()->plasmaWindowManagementInitialized(); diff --git a/src/waylandintegration_p.h b/src/waylandintegration_p.h --- a/src/waylandintegration_p.h +++ b/src/waylandintegration_p.h @@ -25,10 +25,7 @@ #include #include - -#if HAVE_PIPEWIRE_SUPPORT -class ScreenCastStream; -#endif +#include namespace KWayland { namespace Client { @@ -41,7 +38,7 @@ class FakeInput; class RemoteBuffer; class Output; - class RemoteAccessManager; + class Screencasting; #endif } } @@ -74,11 +71,13 @@ #if HAVE_PIPEWIRE_SUPPORT public: - typedef struct { + struct Stream { uint nodeId; QVariantMap map; - } Stream; - typedef QList Streams; + + void close(); + }; + typedef QVector Streams; void authenticate(); @@ -88,10 +87,11 @@ bool isEGLInitialized() const; bool isStreamingEnabled() const; - void bindOutput(int outputName, int outputVersion); void startStreamingInput(); - bool startStreaming(quint32 outputName); - void stopStreaming(); + + bool startStreaming(const KWayland::Client::ScreencastingSource &source); + void stopStreaming(uint32_t nodeid); + void stopAllStreaming(); void requestPointerButtonPress(quint32 linuxButton); void requestPointerButtonRelease(quint32 linuxButton); @@ -101,31 +101,29 @@ void requestKeyboardKeycode(int keycode, bool state); EGLStruct egl(); + QVector videoStreamingSources(); QMap screens(); QVariant streams(); protected Q_SLOTS: void addOutput(quint32 name, quint32 version); void removeOutput(quint32 name); - void processBuffer(const KWayland::Client::RemoteBuffer *rbuf); private: bool m_eglInitialized = false; - bool m_streamingEnabled = false; bool m_streamInput = false; bool m_waylandAuthenticationRequested = false; quint32 m_output; QDateTime m_lastFrameTime; - ScreenCastStream *m_stream = nullptr; + QVector m_streams; QPoint m_streamedScreenPosition; QMap m_outputMap; - QList m_bindOutputs; KWayland::Client::FakeInput *m_fakeInput = nullptr; - KWayland::Client::RemoteAccessManager *m_remoteAccessManager = nullptr; + KWayland::Client::Screencasting *m_screencasting = nullptr; qint32 m_drmFd = 0; // for GBM buffer mmap gbm_device *m_gbmDevice = nullptr; // for passed GBM buffer retrieval