diff --git a/cmake/modules/FindPipeWire.cmake b/cmake/modules/FindPipeWire.cmake index 4a85b0a..2775aa9 100644 --- a/cmake/modules/FindPipeWire.cmake +++ b/cmake/modules/FindPipeWire.cmake @@ -1,109 +1,109 @@ #.rst: # FindPipeWire # ------- # # Try to find PipeWire on a Unix system. # # This will define the following variables: # # ``PipeWire_FOUND`` # True if (the requested version of) PipeWire is available # ``PipeWire_VERSION`` # The version of PipeWire # ``PipeWire_LIBRARIES`` # This can be passed to target_link_libraries() instead of the ``PipeWire::PipeWire`` # target # ``PipeWire_INCLUDE_DIRS`` # This should be passed to target_include_directories() if the target is not # used for linking # ``PipeWire_DEFINITIONS`` # This should be passed to target_compile_options() if the target is not # used for linking # # If ``PipeWire_FOUND`` is TRUE, it will also define the following imported target: # # ``PipeWire::PipeWire`` # The PipeWire library # # In general we recommend using the imported target, as it is easier to use. # Bear in mind, however, that if the target is in the link interface of an # exported library, it must be made available by the package config file. #============================================================================= # Copyright 2014 Alex Merry # Copyright 2014 Martin Gräßlin # Copyright 2018 Jan Grulich # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #============================================================================= # Use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls find_package(PkgConfig QUIET) -pkg_check_modules(PKG_PipeWire QUIET libpipewire-0.1 libpipewire-0.2) +pkg_check_modules(PKG_PipeWire QUIET libpipewire-0.2 libpipewire-0.3) set(PipeWire_DEFINITIONS "${PKG_PipeWire_CFLAGS_OTHER}") set(PipeWire_VERSION "${PKG_PipeWire_VERSION}") find_path(PipeWire_INCLUDE_DIRS NAMES pipewire/pipewire.h HINTS ${PKG_PipeWire_INCLUDE_DIRS} ) find_library(PipeWire_LIBRARIES NAMES - pipewire-0.1 pipewire-0.2 + pipewire-0.2 pipewire-0.3 HINTS ${PKG_PipeWire_LIBRARIES_DIRS} ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(PipeWire FOUND_VAR PipeWire_FOUND REQUIRED_VARS PipeWire_LIBRARIES PipeWire_INCLUDE_DIRS VERSION_VAR PipeWire_VERSION ) if(PipeWire_FOUND AND NOT TARGET PipeWire::PipeWire) add_library(PipeWire::PipeWire UNKNOWN IMPORTED) set_target_properties(PipeWire::PipeWire PROPERTIES IMPORTED_LOCATION "${PipeWire_LIBRARIES}" INTERFACE_COMPILE_OPTIONS "${PipeWire_DEFINITIONS}" INTERFACE_INCLUDE_DIRECTORIES "${PipeWire_INCLUDE_DIRS}" ) endif() mark_as_advanced(PipeWire_LIBRARIES PipeWire_INCLUDE_DIRS) include(FeatureSummary) set_package_properties(PipeWire PROPERTIES URL "http://www.pipewire.org" DESCRIPTION "PipeWire - multimedia processing" ) diff --git a/framebuffers/pipewire/pw_framebuffer.cpp b/framebuffers/pipewire/pw_framebuffer.cpp index f3e133a..72c5610 100644 --- a/framebuffers/pipewire/pw_framebuffer.cpp +++ b/framebuffers/pipewire/pw_framebuffer.cpp @@ -1,753 +1,783 @@ /* This file is part of the KDE project Copyright (C) 2018 Oleg Chernovskiy + Copyright (C) 2018 Jan Grulich 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 3 of the License, or (at your option) any later version. */ // system #include #include // Qt #include #include #include #include #include #include #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) #include #endif // pipewire -#include -#include -#include +#include + +#if !PW_CHECK_VERSION(0, 2, 9) +#include #include +#include +#include +#endif +#include #include + +#include +#include +#include +#include + #include #include "pw_framebuffer.h" #include "xdp_dbus_screencast_interface.h" #include "xdp_dbus_remotedesktop_interface.h" -#ifdef __has_include - #if __has_include() - #include - #else - #define PW_API_PRE_0_2_0 - #endif // __has_include() -#else - #define PW_API_PRE_0_2_0 -#endif // __has_include - static const uint MIN_SUPPORTED_XDP_KDE_SC_VERSION = 1; Q_DECLARE_METATYPE(PWFrameBuffer::Stream); Q_DECLARE_METATYPE(PWFrameBuffer::Streams); const QDBusArgument &operator >> (const QDBusArgument &arg, PWFrameBuffer::Stream &stream) { arg.beginStructure(); arg >> stream.nodeId; arg.beginMap(); while (!arg.atEnd()) { QString key; QVariant map; arg.beginMapEntry(); arg >> key >> map; arg.endMapEntry(); stream.map.insert(key, map); } arg.endMap(); arg.endStructure(); return arg; } +#if !PW_CHECK_VERSION(0, 2, 9) /** * @brief The PwType class - helper class to contain pointers to raw C pipewire media mappings */ 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 /** * @brief The PWFrameBuffer::Private class - private counterpart of PWFramebuffer class. This is the entity where * whole logic resides, for more info search for "d-pointer pattern" information. */ class PWFrameBuffer::Private { public: Private(PWFrameBuffer *q); ~Private(); private: friend class PWFrameBuffer; static void onStateChanged(void *data, pw_remote_state old, pw_remote_state state, const char *error); static void onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message); -#if defined(PW_API_PRE_0_2_0) - static void onStreamFormatChanged(void *data, struct spa_pod *format); - static void onNewBuffer(void *data, uint32_t id); -#else static void onStreamFormatChanged(void *data, const struct spa_pod *format); static void onStreamProcess(void *data); -#endif // defined(PW_API_PRE_0_2_0) void initDbus(); void initPw(); +#if !PW_CHECK_VERSION(0, 2, 9) void initializePwTypes(); +#endif // dbus handling void handleSessionCreated(quint32 &code, QVariantMap &results); void handleDevicesSelected(quint32 &code, QVariantMap &results); void handleSourcesSelected(quint32 &code, QVariantMap &results); void handleRemoteDesktopStarted(quint32 &code, QVariantMap &results); // pw handling - void processPwEvents(); void createReceivingStream(); -#if defined(PW_API_PRE_0_2_0) - void handleFrame(spa_buffer *spaBuffer); -#else void handleFrame(pw_buffer *pwBuffer); -#endif // defined(PW_API_PRE_0_2_0) + // link to public interface PWFrameBuffer *q; // pipewire stuff +#if PW_CHECK_VERSION(0, 2, 9) + struct pw_core *pwCore = nullptr; + struct pw_loop *pwLoop = nullptr; + struct pw_stream *pwStream = nullptr; + struct pw_remote *pwRemote = nullptr; + struct pw_thread_loop *pwMainLoop = nullptr; +#else pw_core *pwCore = nullptr; - pw_type *pwCoreType = nullptr; - pw_remote *pwRemote = nullptr; - pw_stream *pwStream = nullptr; pw_loop *pwLoop = nullptr; - QScopedPointer pwType; + pw_stream *pwStream = nullptr; + pw_remote *pwRemote = nullptr; + pw_thread_loop *pwMainLoop = nullptr; + pw_type *pwCoreType = nullptr; + PwType *pwType = nullptr; +#endif + + uint pwStreamNodeId = 0; // event handlers pw_remote_events pwRemoteEvents = {}; pw_stream_events pwStreamEvents = {}; // wayland-like listeners // ...of events that happen in pipewire server spa_hook remoteListener = {}; // ...of events that happen with the stream we consume spa_hook streamListener = {}; // negotiated video format - QScopedPointer videoFormat; - - // listens on pipewire socket - QScopedPointer socketNotifier; + spa_video_info_raw *videoFormat = nullptr; // requests a session from XDG Desktop Portal // auto-generated and compiled from xdp_dbus_interface.xml file QScopedPointer dbusXdpScreenCastService; QScopedPointer dbusXdpRemoteDesktopService; // XDP screencast session handle QDBusObjectPath sessionPath; // Pipewire file descriptor QDBusUnixFileDescriptor pipewireFd; // screen geometry holder struct { quint32 width; quint32 height; } screenGeometry; - // real image with allocated memory which poses as a destination when we get a buffer from pipewire - // and as source when we pass the frame back to protocol - QImage fbImage; - // sanity indicator bool isValid = true; }; PWFrameBuffer::Private::Private(PWFrameBuffer *q) : q(q) { // 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; -#if defined(PW_API_PRE_0_2_0) - pwStreamEvents.new_buffer = &onNewBuffer; -#else pwStreamEvents.process = &onStreamProcess; -#endif // defined(PW_API_PRE_0_2_0) } /** * @brief PWFrameBuffer::Private::initDbus - initialize D-Bus connectivity with XDG Desktop Portal. * Based on XDG_CURRENT_DESKTOP environment variable it will give us implementation that we need, * in case of KDE it is xdg-desktop-portal-kde binary. */ void PWFrameBuffer::Private::initDbus() { qInfo() << "Initializing D-Bus connectivity with XDG Desktop Portal"; dbusXdpScreenCastService.reset(new OrgFreedesktopPortalScreenCastInterface(QLatin1String("org.freedesktop.portal.Desktop"), QLatin1String("/org/freedesktop/portal/desktop"), QDBusConnection::sessionBus())); dbusXdpRemoteDesktopService.reset(new OrgFreedesktopPortalRemoteDesktopInterface(QLatin1String("org.freedesktop.portal.Desktop"), QLatin1String("/org/freedesktop/portal/desktop"), QDBusConnection::sessionBus())); auto version = dbusXdpScreenCastService->version(); if (version < MIN_SUPPORTED_XDP_KDE_SC_VERSION) { qWarning() << "Unsupported XDG Portal screencast interface version:" << version; isValid = false; return; } // create session auto sessionParameters = QVariantMap { #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) { QLatin1String("session_handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) }, { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } #else { QLatin1String("session_handle_token"), QStringLiteral("krfb%1").arg(qrand()) }, { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(qrand()) } #endif }; auto sessionReply = dbusXdpRemoteDesktopService->CreateSession(sessionParameters); sessionReply.waitForFinished(); if (!sessionReply.isValid()) { qWarning("Couldn't initialize XDP-KDE screencast session"); isValid = false; return; } qInfo() << "DBus session created: " << sessionReply.value().path(); QDBusConnection::sessionBus().connect(QString(), sessionReply.value().path(), QLatin1String("org.freedesktop.portal.Request"), QLatin1String("Response"), this->q, SLOT(handleXdpSessionCreated(uint, QVariantMap))); } void PWFrameBuffer::handleXdpSessionCreated(quint32 code, QVariantMap results) { d->handleSessionCreated(code, results); } /** * @brief PWFrameBuffer::Private::handleSessionCreated - handle creation of ScreenCast session. * XDG Portal answers with session path if it was able to successfully create the screencast. * * @param code return code for dbus call. Zero is success, non-zero means error * @param results map with results of call. */ void PWFrameBuffer::Private::handleSessionCreated(quint32 &code, QVariantMap &results) { if (code != 0) { qWarning() << "Failed to create session: " << code; isValid = false; return; } sessionPath = QDBusObjectPath(results.value(QLatin1String("session_handle")).toString()); // select sources for the session auto selectionOptions = QVariantMap { { QLatin1String("types"), 7 }, // request all (KeyBoard, Pointer, TouchScreen) #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } #else { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(qrand()) } #endif }; auto selectorReply = dbusXdpRemoteDesktopService->SelectDevices(sessionPath, selectionOptions); selectorReply.waitForFinished(); if (!selectorReply.isValid()) { qWarning() << "Couldn't select devices for the remote-desktop session"; isValid = false; return; } QDBusConnection::sessionBus().connect(QString(), selectorReply.value().path(), QLatin1String("org.freedesktop.portal.Request"), QLatin1String("Response"), this->q, SLOT(handleXdpDevicesSelected(uint, QVariantMap))); } void PWFrameBuffer::handleXdpDevicesSelected(quint32 code, QVariantMap results) { d->handleDevicesSelected(code, results); } /** * @brief PWFrameBuffer::Private::handleDevicesCreated - handle selection of devices we want to use for remote desktop * * @param code return code for dbus call. Zero is success, non-zero means error * @param results map with results of call. */ void PWFrameBuffer::Private::handleDevicesSelected(quint32 &code, QVariantMap &results) { if (code != 0) { qWarning() << "Failed to select devices: " << code; isValid = false; return; } // select sources for the session auto selectionOptions = QVariantMap { { QLatin1String("types"), 1 }, // only MONITOR is supported { QLatin1String("multiple"), false }, #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } #else { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(qrand()) } #endif }; auto selectorReply = dbusXdpScreenCastService->SelectSources(sessionPath, selectionOptions); selectorReply.waitForFinished(); if (!selectorReply.isValid()) { qWarning() << "Couldn't select sources for the screen-casting session"; isValid = false; return; } QDBusConnection::sessionBus().connect(QString(), selectorReply.value().path(), QLatin1String("org.freedesktop.portal.Request"), QLatin1String("Response"), this->q, SLOT(handleXdpSourcesSelected(uint, QVariantMap))); } void PWFrameBuffer::handleXdpSourcesSelected(quint32 code, QVariantMap results) { d->handleSourcesSelected(code, results); } /** * @brief PWFrameBuffer::Private::handleSourcesSelected - handle Screencast sources selection. * XDG Portal shows a dialog at this point which allows you to select monitor from the list. * This function is called after you make a selection. * * @param code return code for dbus call. Zero is success, non-zero means error * @param results map with results of call. */ void PWFrameBuffer::Private::handleSourcesSelected(quint32 &code, QVariantMap &) { if (code != 0) { qWarning() << "Failed to select sources: " << code; isValid = false; return; } // start session auto startParameters = QVariantMap { #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } #else { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(qrand()) } #endif }; auto startReply = dbusXdpRemoteDesktopService->Start(sessionPath, QString(), startParameters); startReply.waitForFinished(); QDBusConnection::sessionBus().connect(QString(), startReply.value().path(), QLatin1String("org.freedesktop.portal.Request"), QLatin1String("Response"), this->q, SLOT(handleXdpRemoteDesktopStarted(uint, QVariantMap))); } void PWFrameBuffer::handleXdpRemoteDesktopStarted(quint32 code, QVariantMap results) { d->handleRemoteDesktopStarted(code, results); } /** * @brief PWFrameBuffer::Private::handleScreencastStarted - handle Screencast start. * At this point there shall be ready pipewire stream to consume. * * @param code return code for dbus call. Zero is success, non-zero means error * @param results map with results of call. */ void PWFrameBuffer::Private::handleRemoteDesktopStarted(quint32 &code, QVariantMap &results) { if (code != 0) { qWarning() << "Failed to start screencast: " << code; isValid = false; return; } // there should be only one stream Streams streams = qdbus_cast(results.value(QLatin1String("streams"))); if (streams.isEmpty()) { // maybe we should check deeper with qdbus_cast but this suffices for now qWarning() << "Failed to get screencast streams"; isValid = false; return; } auto streamReply = dbusXdpScreenCastService->OpenPipeWireRemote(sessionPath, QVariantMap()); streamReply.waitForFinished(); if (!streamReply.isValid()) { qWarning() << "Couldn't open pipewire remote for the screen-casting session"; isValid = false; return; } pipewireFd = streamReply.value(); if (!pipewireFd.isValid()) { qWarning() << "Couldn't get pipewire connection file descriptor"; isValid = false; return; } QSize streamResolution = qdbus_cast(streams.first().map.value(QLatin1String("size"))); screenGeometry.width = streamResolution.width(); screenGeometry.height = streamResolution.height(); - fbImage = QImage(screenGeometry.width, screenGeometry.height, QImage::Format_ARGB32); + + pwStreamNodeId = streams.first().nodeId; + + // Reallocate our buffer with actual needed size + q->fb = static_cast(malloc(screenGeometry.width * screenGeometry.height * 4)); + + if (!q->fb) { + qWarning() << "Failed to allocate buffer"; + return; + } + + Q_EMIT q->frameBufferChanged(); initPw(); } /** * @brief PWFrameBuffer::Private::initPw - initialize Pipewire socket connectivity. * pipewireFd should be pointing to existing file descriptor that was passed by D-Bus at this point. */ void PWFrameBuffer::Private::initPw() { qInfo() << "Initializing Pipewire connectivity"; // init pipewire (required) pw_init(nullptr, nullptr); // args are not used anyways // initialize our source pwLoop = pw_loop_new(nullptr); - socketNotifier.reset(new QSocketNotifier(pw_loop_get_fd(pwLoop), QSocketNotifier::Read)); - QObject::connect(socketNotifier.data(), &QSocketNotifier::activated, this->q, &PWFrameBuffer::processPwEvents); + pwMainLoop = pw_thread_loop_new(pwLoop, "pipewire-main-loop"); // create PipeWire core object (required) pwCore = pw_core_new(pwLoop, nullptr); +#if !PW_CHECK_VERSION(0, 2, 9) pwCoreType = pw_core_get_type(pwCore); - // pw_remote should be initialized before type maps or connection error will happen - pwRemote = pw_remote_new(pwCore, nullptr, 0); - // init type maps initializePwTypes(); +#endif + + // pw_remote should be initialized before type maps or connection error will happen + pwRemote = pw_remote_new(pwCore, nullptr, 0); // init PipeWire remote, add listener to handle events pw_remote_add_listener(pwRemote, &remoteListener, &pwRemoteEvents, this); pw_remote_connect_fd(pwRemote, pipewireFd.fileDescriptor()); + + if (pw_thread_loop_start(pwMainLoop) < 0) { + qWarning() << "Failed to start main PipeWire loop"; + isValid = false; + } } +#if !PW_CHECK_VERSION(0, 2, 9) /** * @brief PWFrameBuffer::Private::initializePwTypes - helper method to initialize and map all needed * Pipewire types from core to type structure. */ void PWFrameBuffer::Private::initializePwTypes() { // raw C-like PipeWire type map - auto map = pwCoreType->map; + spa_type_map *map = pwCoreType->map; + pwType = new PwType(); - pwType.reset(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 /** * @brief PWFrameBuffer::Private::onStateChanged - global state tracking for pipewire connection * @param data pointer that you have set in pw_remote_add_listener call's last argument * @param state new state that connection has changed to * @param error optional error message, is set to non-null if state is error */ void PWFrameBuffer::Private::onStateChanged(void *data, pw_remote_state /*old*/, pw_remote_state state, const char *error) { qInfo() << "remote state: " << pw_remote_state_as_string(state); PWFrameBuffer::Private *d = static_cast(data); switch (state) { case PW_REMOTE_STATE_ERROR: qWarning() << "remote error: " << error; break; case PW_REMOTE_STATE_CONNECTED: d->createReceivingStream(); break; default: qInfo() << "remote state: " << pw_remote_state_as_string(state); break; } } /** * @brief PWFrameBuffer::Private::onStreamStateChanged - called whenever stream state changes on pipewire server * @param data pointer that you have set in pw_stream_add_listener call's last argument * @param state new state that stream has changed to * @param error_message optional error message, is set to non-null if state is error */ void PWFrameBuffer::Private::onStreamStateChanged(void *data, pw_stream_state /*old*/, pw_stream_state state, const char *error_message) { qInfo() << "Stream state changed: " << pw_stream_state_as_string(state); auto *d = static_cast(data); switch (state) { case PW_STREAM_STATE_ERROR: qWarning() << "pipewire stream error: " << error_message; break; case PW_STREAM_STATE_CONFIGURE: pw_stream_set_active(d->pwStream, true); break; default: break; } } /** * @brief PWFrameBuffer::Private::onStreamFormatChanged - being executed after stream is set to active * and after setup has been requested to connect to it. The actual video format is being negotiated here. * @param data pointer that you have set in pw_stream_add_listener call's last argument * @param format format that's being proposed */ #if defined(PW_API_PRE_0_2_0) void PWFrameBuffer::Private::onStreamFormatChanged(void *data, struct spa_pod *format) #else void PWFrameBuffer::Private::onStreamFormatChanged(void *data, const struct spa_pod *format) #endif // defined(PW_API_PRE_0_2_0) { qInfo() << "Stream format changed"; auto *d = static_cast(data); const int bpp = 4; if (!format) { pw_stream_finish_format(d->pwStream, 0, nullptr, 0); return; } - d->videoFormat.reset(new spa_video_info_raw); - spa_format_video_raw_parse(format, d->videoFormat.data(), &d->pwType->format_video); - + d->videoFormat = new spa_video_info_raw(); +#if PW_CHECK_VERSION(0, 2, 9) + spa_format_video_raw_parse(format, d->videoFormat); +#else + spa_format_video_raw_parse(format, d->videoFormat, &d->pwType->format_video); +#endif auto width = d->videoFormat->size.width; auto height = d->videoFormat->size.height; auto stride = SPA_ROUND_UP_N(width * bpp, 4); auto size = height * stride; uint8_t buffer[1024]; - auto builder = spa_pod_builder {buffer, sizeof(buffer)}; + auto builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); // setup buffers and meta header for new format -#if defined(PW_API_PRE_0_2_0) - struct spa_pod *params[2]; -#else const struct spa_pod *params[2]; -#endif // defined(PW_API_PRE_0_2_0) +#if PW_CHECK_VERSION(0, 2, 9) + params[0] = reinterpret_cast(spa_pod_builder_add_object(&builder, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + ":", SPA_PARAM_BUFFERS_size, "i", size, + ":", SPA_PARAM_BUFFERS_stride, "i", stride, + ":", SPA_PARAM_BUFFERS_buffers, "?ri", SPA_CHOICE_RANGE(8, 1, 32), + ":", SPA_PARAM_BUFFERS_align, "i", 16)); + params[1] = reinterpret_cast(spa_pod_builder_add_object(&builder, + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, + ":", SPA_PARAM_META_type, "I", SPA_META_Header, + ":", SPA_PARAM_META_size, "i", sizeof(struct spa_meta_header))); +#else params[0] = reinterpret_cast(spa_pod_builder_object(&builder, - d->pwCoreType->param.idBuffers, d->pwCoreType->param_buffers.Buffers, - ":", d->pwCoreType->param_buffers.size, "i", size, - ":", d->pwCoreType->param_buffers.stride, "i", stride, - ":", d->pwCoreType->param_buffers.buffers, "iru", 8, SPA_POD_PROP_MIN_MAX(1, 32), - ":", d->pwCoreType->param_buffers.align, "i", 16)); + d->pwCoreType->param.idBuffers, d->pwCoreType->param_buffers.Buffers, + ":", d->pwCoreType->param_buffers.size, "i", size, + ":", d->pwCoreType->param_buffers.stride, "i", stride, + ":", d->pwCoreType->param_buffers.buffers, "iru", 8, SPA_POD_PROP_MIN_MAX(1, 32), + ":", d->pwCoreType->param_buffers.align, "i", 16)); params[1] = reinterpret_cast(spa_pod_builder_object(&builder, - d->pwCoreType->param.idMeta, d->pwCoreType->param_meta.Meta, - ":", d->pwCoreType->param_meta.type, "I", d->pwCoreType->meta.Header, - ":", d->pwCoreType->param_meta.size, "i", sizeof(struct spa_meta_header))); + d->pwCoreType->param.idMeta, d->pwCoreType->param_meta.Meta, + ":", d->pwCoreType->param_meta.type, "I", d->pwCoreType->meta.Header, + ":", d->pwCoreType->param_meta.size, "i", sizeof(struct spa_meta_header))); +#endif pw_stream_finish_format(d->pwStream, 0, params, 2); } /** * @brief PWFrameBuffer::Private::onNewBuffer - called when new buffer is available in pipewire stream * @param data pointer that you have set in pw_stream_add_listener call's last argument * @param id */ -#if defined(PW_API_PRE_0_2_0) -void PWFrameBuffer::Private::onNewBuffer(void *data, uint32_t id) -#else void PWFrameBuffer::Private::onStreamProcess(void *data) -#endif // defined(PW_API_PRE_0_2_0) { - qDebug() << "New buffer received"; + qWarning() << "OnStreamProcess()"; auto *d = static_cast(data); -#if defined(PW_API_PRE_0_2_0) - auto buf = pw_stream_peek_buffer(d->pwStream, id); -#else - auto buf = pw_stream_dequeue_buffer(d->pwStream); -#endif // defined(PW_API_PRE_0_2_0) + pw_buffer *buf; + if (!(buf = pw_stream_dequeue_buffer(d->pwStream))) { + return; + } d->handleFrame(buf); -#if defined(PW_API_PRE_0_2_0) - pw_stream_recycle_buffer(d->pwStream, id); -#else pw_stream_queue_buffer(d->pwStream, buf); -#endif // defined(PW_API_PRE_0_2_0) } -#if defined(PW_API_PRE_0_2_0) -void PWFrameBuffer::Private::handleFrame(spa_buffer *spaBuffer) -{ -#else void PWFrameBuffer::Private::handleFrame(pw_buffer *pwBuffer) { auto *spaBuffer = pwBuffer->buffer; -#endif // defined(PW_API_PRE_0_2_0) - auto mapLength = spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset; - void *src = nullptr; src = spaBuffer->datas[0].data; if (!src) { return; } + quint32 maxSize = spaBuffer->datas[0].maxsize; qint32 srcStride = spaBuffer->datas[0].chunk->stride; if (srcStride != q->paddedWidth()) { qWarning() << "Got buffer with stride different from screen stride" << srcStride << "!=" << q->paddedWidth(); return; } - fbImage.bits(); - q->tiles.append(fbImage.rect()); - std::memcpy(fbImage.bits(), src, spaBuffer->datas[0].maxsize); -} - -/** - * @brief PWFrameBuffer::Private::processPwEvents - called when Pipewire socket notifies there's - * data to process by remote interface. - */ -void PWFrameBuffer::Private::processPwEvents() { - qDebug() << "Iterating over pipewire loop..."; - - int result = pw_loop_iterate(pwLoop, 0); - if (result < 0) { - qWarning() << "Failed to iterate over pipewire loop: " << spa_strerror(result); - } + q->tiles.append(QRect(0, 0, q->width(), q->height())); + std::memcpy(q->fb, src, maxSize); } /** * @brief PWFrameBuffer::Private::createReceivingStream - create a stream that will consume Pipewire buffers * and copy the framebuffer to the existing image that we track. The state of the stream and configuration * are later handled by the corresponding listener. */ void PWFrameBuffer::Private::createReceivingStream() { - auto pwMinScreenBounds = spa_rectangle {1, 1}; - auto pwScreenBounds = spa_rectangle {screenGeometry.width, screenGeometry.height}; + spa_rectangle pwMinScreenBounds = SPA_RECTANGLE(1, 1); + spa_rectangle pwMaxScreenBounds = SPA_RECTANGLE(screenGeometry.width, screenGeometry.height); - auto pwFramerate = spa_fraction {25, 1}; - auto pwFramerateMin = spa_fraction {0, 1}; - auto pwFramerateMax = spa_fraction {60, 1}; + spa_fraction pwFramerateMin = SPA_FRACTION(0, 1); + spa_fraction pwFramerateMax = SPA_FRACTION(60, 1); auto reuseProps = pw_properties_new("pipewire.client.reuse", "1", nullptr); // null marks end of varargs pwStream = pw_stream_new(pwRemote, "krfb-fb-consume-stream", reuseProps); uint8_t buffer[1024] = {}; const spa_pod *params[1]; - auto builder = spa_pod_builder{buffer, sizeof(buffer)}; + auto builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + +#if PW_CHECK_VERSION(0, 2, 9) + params[0] = reinterpret_cast(spa_pod_builder_add_object(&builder, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + ":", SPA_FORMAT_mediaType, "I", SPA_MEDIA_TYPE_video, + ":", SPA_FORMAT_mediaSubtype, "I", SPA_MEDIA_SUBTYPE_raw, + ":", SPA_FORMAT_VIDEO_format, "I", SPA_VIDEO_FORMAT_RGBx, + ":", SPA_FORMAT_VIDEO_size, "?rR", SPA_CHOICE_RANGE(&pwMaxScreenBounds, &pwMinScreenBounds, &pwMaxScreenBounds), + ":", SPA_FORMAT_VIDEO_framerate, "F", &pwFramerateMin, + ":", SPA_FORMAT_VIDEO_maxFramerate, "?rF", SPA_CHOICE_RANGE(&pwFramerateMax, &pwFramerateMin, &pwFramerateMax))); +#else params[0] = reinterpret_cast(spa_pod_builder_object(&builder, - pwCoreType->param.idEnumFormat, pwCoreType->spa_format, - "I", pwType->media_type.video, - "I", pwType->media_subtype.raw, - ":", pwType->format_video.format, "I", pwType->video_format.xRGB, - ":", pwType->format_video.size, "Rru", &pwScreenBounds, 2, &pwMinScreenBounds, &pwScreenBounds, - ":", pwType->format_video.framerate, "F", &pwFramerateMin, - ":", pwType->format_video.max_framerate, "Fru", &pwFramerate, 2, &pwFramerateMin, &pwFramerateMax)); + 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", &pwMaxScreenBounds, SPA_POD_PROP_MIN_MAX(&pwMinScreenBounds, &pwMaxScreenBounds), + ":", pwType->format_video.framerate, "F", &pwFramerateMin, + ":", pwType->format_video.max_framerate, "Fru", &pwFramerateMax, 2, &pwFramerateMin, &pwFramerateMax)); +#endif pw_stream_add_listener(pwStream, &streamListener, &pwStreamEvents, this); auto flags = static_cast(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS); +#if PW_CHECK_VERSION(0, 2, 9) + if (pw_stream_connect(pwStream, PW_DIRECTION_INPUT, pwStreamNodeId, flags, params, 1) != 0) { +#else if (pw_stream_connect(pwStream, PW_DIRECTION_INPUT, nullptr, flags, params, 1) != 0) { +#endif qWarning() << "Could not connect receiving stream"; isValid = false; } } PWFrameBuffer::Private::~Private() { + if (pwMainLoop) { + pw_thread_loop_stop(pwMainLoop); + } + +#if !PW_CHECK_VERSION(0, 2, 9) + if (pwType) { + delete pwType; + } +#endif + if (pwStream) { - pw_stream_disconnect(pwStream); pw_stream_destroy(pwStream); } + if (pwRemote) { - pw_remote_disconnect(pwRemote); + pw_remote_destroy(pwRemote); } if (pwCore) pw_core_destroy(pwCore); + if (pwMainLoop) { + pw_thread_loop_destroy(pwMainLoop); + } + if (pwLoop) { - pw_loop_leave(pwLoop); pw_loop_destroy(pwLoop); } } PWFrameBuffer::PWFrameBuffer(WId winid, QObject *parent) : FrameBuffer (winid, parent), d(new Private(this)) { // D-Bus is most important in init chain, no toys for us if something is wrong with XDP // PipeWire connectivity is initialized after D-Bus session is started d->initDbus(); - // framebuffer from public interface will point directly to image data - fb = reinterpret_cast(d->fbImage.bits()); + // FIXME: for now use some initial size, later on we will reallocate this with the actual size we get from portal + d->screenGeometry.width = 800; + d->screenGeometry.height = 600; + fb = nullptr; } PWFrameBuffer::~PWFrameBuffer() { + free(fb); fb = nullptr; } -void PWFrameBuffer::processPwEvents() -{ - d->processPwEvents(); -} - int PWFrameBuffer::depth() { return 32; } int PWFrameBuffer::height() { return static_cast(d->screenGeometry.height); } int PWFrameBuffer::width() { return static_cast(d->screenGeometry.width); } +int PWFrameBuffer::paddedWidth() +{ + return width() * 4; +} + void PWFrameBuffer::getServerFormat(rfbPixelFormat &format) { format.bitsPerPixel = 32; format.depth = 32; format.trueColour = true; format.bigEndian = false; } void PWFrameBuffer::startMonitor() { } void PWFrameBuffer::stopMonitor() { } bool PWFrameBuffer::isValid() const { return d->isValid; } diff --git a/framebuffers/pipewire/pw_framebuffer.h b/framebuffers/pipewire/pw_framebuffer.h index 0fa9606..58c0466 100644 --- a/framebuffers/pipewire/pw_framebuffer.h +++ b/framebuffers/pipewire/pw_framebuffer.h @@ -1,57 +1,57 @@ /* This file is part of the KDE project Copyright (C) 2018 Oleg Chernovskiy + Copyright (C) 2018 Jan Grulich 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 3 of the License, or (at your option) any later version. */ #ifndef KRFB_FRAMEBUFFER_XCB_XCB_FRAMEBUFFER_H #define KRFB_FRAMEBUFFER_XCB_XCB_FRAMEBUFFER_H #include "framebuffer.h" #include #include /** * @brief The PWFrameBuffer class - framebuffer implementation based on XDG Desktop Portal ScreenCast interface. * The design relies heavily on a presence of XDG D-Bus service and PipeWire daemon. * * @author Oleg Chernovskiy */ class PWFrameBuffer: public FrameBuffer { Q_OBJECT public: typedef struct { uint nodeId; QVariantMap map; } Stream; typedef QList Streams; PWFrameBuffer(WId winid, QObject *parent = nullptr); virtual ~PWFrameBuffer() override; int depth() override; int height() override; int width() override; + int paddedWidth() override; void getServerFormat(rfbPixelFormat &format) override; void startMonitor() override; void stopMonitor() override; bool isValid() const; private slots: void handleXdpSessionCreated(quint32 code, QVariantMap results); void handleXdpDevicesSelected(quint32 code, QVariantMap results); void handleXdpSourcesSelected(quint32 code, QVariantMap results); void handleXdpRemoteDesktopStarted(quint32 code, QVariantMap results); private: - void processPwEvents(); - class Private; const QScopedPointer d; }; #endif diff --git a/krfb/framebuffer.h b/krfb/framebuffer.h index f6430a3..9bec404 100644 --- a/krfb/framebuffer.h +++ b/krfb/framebuffer.h @@ -1,57 +1,60 @@ /* This file is part of the KDE project Copyright (C) 2007 Alessandro Praduroux 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. */ #ifndef FRAMEBUFFER_H #define FRAMEBUFFER_H #include "rfb.h" #include "krfbprivate_export.h" #include #include #include #include class FrameBuffer; /** @author Alessandro Praduroux */ class KRFBPRIVATE_EXPORT FrameBuffer : public QObject { Q_OBJECT public: explicit FrameBuffer(WId id, QObject *parent = 0); virtual ~FrameBuffer(); char *data(); virtual QList modifiedTiles(); virtual int paddedWidth(); virtual int width(); virtual int height(); virtual int depth(); virtual void startMonitor(); virtual void stopMonitor(); virtual void getServerFormat(rfbPixelFormat &format); +Q_SIGNALS: + void frameBufferChanged(); + protected: WId win; char *fb; QList tiles; private: Q_DISABLE_COPY(FrameBuffer) }; #endif diff --git a/krfb/main.cpp b/krfb/main.cpp index f1f8e02..5606cb2 100644 --- a/krfb/main.cpp +++ b/krfb/main.cpp @@ -1,152 +1,151 @@ /*************************************************************************** main.cpp ------------------- begin : Sat Dec 8 03:23:02 CET 2001 copyright : (C) 2001-2003 by Tim Jansen email : tim@tjansen.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "mainwindow.h" #include "trayicon.h" #include "invitationsrfbserver.h" #include "krfbconfig.h" #include "krfb_version.h" #include #include #include #include #include #include #include #include #include #include #include #include static const char description[] = I18N_NOOP("VNC-compatible server to share " "desktops"); - static bool checkX11Capabilities() { int bp1, bp2, majorv, minorv; Bool r = XTestQueryExtension(QX11Info::display(), &bp1, &bp2, &majorv, &minorv); if ((!r) || (((majorv * 1000) + minorv) < 2002)) { KMessageBox::error(0, i18n("Your X11 Server does not support the required XTest extension " "version 2.2. Sharing your desktop is not possible."), i18n("Desktop Sharing Error")); return false; } return true; } - static void checkOldX11PluginConfig() { if (KrfbConfig::preferredFrameBufferPlugin() == QStringLiteral("x11")) { qDebug() << "Detected deprecated configuration: preferredFrameBufferPlugin = x11"; KConfigSkeletonItem *config_item = KrfbConfig::self()->findItem( QStringLiteral("preferredFrameBufferPlugin")); if (config_item) { config_item->setProperty(QStringLiteral("xcb")); KrfbConfig::self()->save(); qDebug() << " Fixed preferredFrameBufferPlugin from x11 to xcb."; } } } - int main(int argc, char *argv[]) { QApplication app(argc, argv); KLocalizedString::setApplicationDomain("krfb"); KAboutData aboutData("krfb", i18n("Desktop Sharing"), QStringLiteral(KRFB_VERSION_STRING), i18n(description), KAboutLicense::GPL, i18n("(c) 2009-2010, Collabora Ltd.\n" "(c) 2007, Alessandro Praduroux\n" "(c) 2001-2003, Tim Jansen\n" "(c) 2001, Johannes E. Schindelin\n" "(c) 2000-2001, Const Kaplinsky\n" "(c) 2000, Tridia Corporation\n" "(c) 1999, AT&T Laboratories Boston\n")); aboutData.addAuthor(i18n("George Goldberg"), i18n("Telepathy tubes support"), "george.goldberg@collabora.co.uk"); aboutData.addAuthor(i18n("George Kiagiadakis"), QString(), "george.kiagiadakis@collabora.co.uk"); aboutData.addAuthor(i18n("Alessandro Praduroux"), i18n("KDE4 porting"), "pradu@pradu.it"); aboutData.addAuthor(i18n("Tim Jansen"), i18n("Original author"), "tim@tjansen.de"); aboutData.addCredit(i18n("Johannes E. Schindelin"), i18n("libvncserver")); aboutData.addCredit(i18n("Const Kaplinsky"), i18n("TightVNC encoder")); aboutData.addCredit(i18n("Tridia Corporation"), i18n("ZLib encoder")); aboutData.addCredit(i18n("AT&T Laboratories Boston"), i18n("original VNC encoders and " "protocol design")); QCommandLineParser parser; KAboutData::setApplicationData(aboutData); parser.addVersionOption(); parser.addHelpOption(); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); KDBusService service(KDBusService::Unique, &app); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("nodialog"), i18n("Do not show the invitations management dialog at startup"))); app.setQuitOnLastWindowClosed(false); - if (!checkX11Capabilities()) { - return 1; - } + if (QX11Info::isPlatformX11()) { + if (!checkX11Capabilities()) { + return 1; + } - // upgrade the configuration - checkOldX11PluginConfig(); + // upgrade the configuration + checkOldX11PluginConfig(); + } //init the core InvitationsRfbServer::init(); //init the GUI MainWindow mainWindow; TrayIcon trayicon(&mainWindow); if (KrfbConfig::startMinimized()) { mainWindow.hide(); } else if (app.isSessionRestored() && KMainWindow::canBeRestored(1)) { mainWindow.restore(1, false); } else if (!parser.isSet("nodialog")) { mainWindow.show(); } sigset_t sigs; sigemptyset(&sigs); sigaddset(&sigs, SIGPIPE); sigprocmask(SIG_BLOCK, &sigs, 0); return app.exec(); } diff --git a/krfb/rfbserver.cpp b/krfb/rfbserver.cpp index d04bf1c..c39239f 100644 --- a/krfb/rfbserver.cpp +++ b/krfb/rfbserver.cpp @@ -1,282 +1,293 @@ /* Copyright (C) 2009-2010 Collabora Ltd @author George Goldberg @author George Kiagiadakis Copyright (C) 2007 Alessandro Praduroux 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 Lesser General Public License along with this program. If not, see . */ #include "rfbserver.h" #include "rfbservermanager.h" #include #include #include #include struct RfbServer::Private { QByteArray listeningAddress; int listeningPort; bool passwordRequired; rfbScreenInfoPtr screen; QSocketNotifier *notifier; }; RfbServer::RfbServer(QObject *parent) : QObject(parent), d(new Private) { d->listeningAddress = "0.0.0.0"; d->listeningPort = 0; d->passwordRequired = true; d->screen = NULL; d->notifier = NULL; RfbServerManager::instance()->registerServer(this); } RfbServer::~RfbServer() { if (d->screen) { rfbScreenCleanup(d->screen); } delete d; RfbServerManager::instance()->unregisterServer(this); } QByteArray RfbServer::listeningAddress() const { return d->listeningAddress; } int RfbServer::listeningPort() const { return d->listeningPort; } bool RfbServer::passwordRequired() const { return d->passwordRequired; } void RfbServer::setListeningAddress(const QByteArray& address) { d->listeningAddress = address; } void RfbServer::setListeningPort(int port) { d->listeningPort = port; } void RfbServer::setPasswordRequired(bool passwordRequired) { d->passwordRequired = passwordRequired; } bool RfbServer::start() { if (!d->screen) { d->screen = RfbServerManager::instance()->newScreen(); if (!d->screen) { qDebug() << "Unable to get rbfserver screen"; return false; } // server hooks d->screen->screenData = this; d->screen->newClientHook = newClientHook; d->screen->kbdAddEvent = keyboardHook; d->screen->ptrAddEvent = pointerHook; d->screen->passwordCheck = passwordCheck; d->screen->setXCutText = clipboardHook; } else { //if we already have a screen, stop listening first rfbShutdownServer(d->screen, false); } if (listeningAddress() != "0.0.0.0") { strncpy(d->screen->thisHost, listeningAddress().data(), 254); } if (listeningPort() == 0) { d->screen->autoPort = 1; } d->screen->port = listeningPort(); // Disable/Enable password checking if (passwordRequired()) { d->screen->authPasswdData = (void *)1; } else { d->screen->authPasswdData = (void *)0; } qDebug() << "Starting server. Listen port:" << listeningPort() << "Listen Address:" << listeningAddress() << "Password enabled:" << passwordRequired(); rfbInitServer(d->screen); if (!rfbIsActive(d->screen)) { qDebug() << "Failed to start server"; rfbShutdownServer(d->screen, false); return false; }; d->notifier = new QSocketNotifier(d->screen->listenSock, QSocketNotifier::Read, this); d->notifier->setEnabled(true); connect(d->notifier, &QSocketNotifier::activated, this, &RfbServer::onListenSocketActivated); connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &RfbServer::krfbSendServerCutText); return true; } void RfbServer::stop() { if (d->screen) { rfbShutdownServer(d->screen, true); if (d->notifier) { d->notifier->setEnabled(false); d->notifier->deleteLater(); d->notifier = NULL; } } } +void RfbServer::updateFrameBuffer(char *fb, int width, int height, int depth) +{ + int bpp = depth >> 3; + + if (bpp != 1 && bpp != 2 && bpp != 4) { + bpp = 4; + } + + rfbNewFramebuffer(d->screen, fb, width, height, 8, 3, bpp); +} + void RfbServer::updateScreen(const QList & modifiedTiles) { if (d->screen) { QList::const_iterator it = modifiedTiles.constBegin(); for(; it != modifiedTiles.constEnd(); ++it) { rfbMarkRectAsModified(d->screen, it->x(), it->y(), it->right(), it->bottom()); } } } /* * Code copied from vino's bundled libvncserver: * Copyright (C) 2000, 2001 Const Kaplinsky. All Rights Reserved. * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. * License: GPL v2 or later */ void krfb_rfbSetCursorPosition(rfbScreenInfoPtr screen, rfbClientPtr client, int x, int y) { rfbClientIteratorPtr iterator; rfbClientPtr cl; if (x == screen->cursorX || y == screen->cursorY) return; LOCK(screen->cursorMutex); screen->cursorX = x; screen->cursorY = y; UNLOCK(screen->cursorMutex); /* Inform all clients about this cursor movement. */ iterator = rfbGetClientIterator(screen); while ((cl = rfbClientIteratorNext(iterator)) != NULL) { cl->cursorWasMoved = true; } rfbReleaseClientIterator(iterator); /* The cursor was moved by this client, so don't send CursorPos. */ if (client) { client->cursorWasMoved = false; } } void RfbServer::updateCursorPosition(const QPoint & position) { if (d->screen) { krfb_rfbSetCursorPosition(d->screen, NULL, position.x(), position.y()); } } void RfbServer::krfbSendServerCutText() { if(d->screen) { QString text = QApplication::clipboard()->text(); rfbSendServerCutText(d->screen, text.toLocal8Bit().data(),text.length()); } } void RfbServer::onListenSocketActivated() { rfbProcessNewConnection(d->screen); } void RfbServer::pendingClientFinished(RfbClient *client) { //qDebug(); if (client) { RfbServerManager::instance()->addClient(client); client->getRfbClientPtr()->clientGoneHook = clientGoneHook; } } //static rfbNewClientAction RfbServer::newClientHook(rfbClientPtr cl) { //qDebug() << "New client"; RfbServer *server = static_cast(cl->screen->screenData); PendingRfbClient *pendingClient = server->newClient(cl); connect(pendingClient, &PendingRfbClient::finished, server, &RfbServer::pendingClientFinished); return RFB_CLIENT_ON_HOLD; } //static void RfbServer::clientGoneHook(rfbClientPtr cl) { //qDebug() << "client gone"; RfbClient *client = static_cast(cl->clientData); RfbServerManager::instance()->removeClient(client); client->deleteLater(); } //static rfbBool RfbServer::passwordCheck(rfbClientPtr cl, const char *encryptedPassword, int len) { PendingRfbClient *client = static_cast(cl->clientData); Q_ASSERT(client); return client->checkPassword(QByteArray::fromRawData(encryptedPassword, len)); } //static void RfbServer::keyboardHook(rfbBool down, rfbKeySym keySym, rfbClientPtr cl) { RfbClient *client = static_cast(cl->clientData); client->handleKeyboardEvent(down ? true : false, keySym); } //static void RfbServer::pointerHook(int bm, int x, int y, rfbClientPtr cl) { RfbClient *client = static_cast(cl->clientData); client->handleMouseEvent(bm, x, y); } //static void RfbServer::clipboardHook(char *str, int len, rfbClientPtr /*cl*/) { QApplication::clipboard()->setText(QString::fromLocal8Bit(str,len)); } #include "rfbserver.moc" diff --git a/krfb/rfbserver.h b/krfb/rfbserver.h index d084815..731acc3 100644 --- a/krfb/rfbserver.h +++ b/krfb/rfbserver.h @@ -1,72 +1,73 @@ /* Copyright (C) 2009-2010 Collabora Ltd @author George Goldberg @author George Kiagiadakis Copyright (C) 2007 Alessandro Praduroux 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 Lesser General Public License along with this program. If not, see . */ #ifndef RFBSERVER_H #define RFBSERVER_H #include "rfb.h" #include "rfbclient.h" #include class RfbServer : public QObject { Q_OBJECT public: RfbServer(QObject *parent = 0); virtual ~RfbServer(); QByteArray listeningAddress() const; int listeningPort() const; bool passwordRequired() const; void setListeningAddress(const QByteArray & address); void setListeningPort(int port); void setPasswordRequired(bool passwordRequired); public Q_SLOTS: virtual bool start(); virtual void stop(); + void updateFrameBuffer(char *fb, int width, int height, int depth); void updateScreen(const QList & modifiedTiles); void updateCursorPosition(const QPoint & position); private Q_SLOTS: void krfbSendServerCutText(); void onListenSocketActivated(); void pendingClientFinished(RfbClient *client); protected: virtual PendingRfbClient *newClient(rfbClientPtr client) = 0; private: static rfbNewClientAction newClientHook(rfbClientPtr cl); static void clientGoneHook(rfbClientPtr cl); static rfbBool passwordCheck(rfbClientPtr cl, const char *encryptedPassword, int len); static void keyboardHook(rfbBool down, rfbKeySym keySym, rfbClientPtr cl); static void pointerHook(int bm, int x, int y, rfbClientPtr cl); static void clipboardHook(char *str, int len, rfbClientPtr cl); Q_DISABLE_COPY(RfbServer) struct Private; Private *const d; }; #endif // RFBSERVER_H diff --git a/krfb/rfbservermanager.cpp b/krfb/rfbservermanager.cpp index 17c5eb3..b90a5ef 100644 --- a/krfb/rfbservermanager.cpp +++ b/krfb/rfbservermanager.cpp @@ -1,236 +1,243 @@ /* Copyright (C) 2009-2010 Collabora Ltd @author George Goldberg @author George Kiagiadakis Copyright (C) 2007 Alessandro Praduroux Copyright (C) 2001-2003 by Tim Jansen 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 Lesser General Public License along with this program. If not, see . */ #include "rfbservermanager.h" #include "rfbserver.h" #include "framebuffer.h" #include "framebuffermanager.h" #include "sockethelpers.h" #include "krfbconfig.h" #include #include #include #include #include #include #include #include #include static const char *cur = " " " x " " xx " " xxx " " xxxx " " xxxxx " " xxxxxx " " xxxxxxx " " xxxxxxxx " " xxxxxxxxx " " xxxxxxxxxx " " xxxxx " " xx xxx " " x xxx " " xxx " " xxx " " xxx " " xxx " " "; static const char *mask = "xx " "xxx " "xxxx " "xxxxx " "xxxxxx " "xxxxxxx " "xxxxxxxx " "xxxxxxxxx " "xxxxxxxxxx " "xxxxxxxxxxx " "xxxxxxxxxxxx " "xxxxxxxxxx " "xxxxxxxx " "xxxxxxxx " "xx xxxxx " " xxxxx " " xxxxx " " xxxxx " " xxx "; struct RfbServerManagerStatic { RfbServerManager server; }; Q_GLOBAL_STATIC(RfbServerManagerStatic, s_instance) RfbServerManager* RfbServerManager::instance() { return &s_instance->server; } struct RfbServerManager::Private { QSharedPointer fb; rfbCursorPtr myCursor; QByteArray desktopName; QTimer rfbUpdateTimer; QSet servers; QSet clients; }; RfbServerManager::RfbServerManager() : QObject(), d(new Private) { init(); } RfbServerManager::~RfbServerManager() { delete d; } void RfbServerManager::init() { //qDebug(); d->fb = FrameBufferManager::instance()->frameBuffer(QApplication::desktop()->winId()); d->myCursor = rfbMakeXCursor(19, 19, (char *) cur, (char *) mask); d->myCursor->cleanup = false; d->desktopName = QString("%1@%2 (shared desktop)") //FIXME check if we can use utf8 .arg(KUser().loginName(),QHostInfo::localHostName()).toLatin1(); + connect(d->fb.data(), &FrameBuffer::frameBufferChanged, this, &RfbServerManager::updateFrameBuffer); connect(&d->rfbUpdateTimer, &QTimer::timeout, this, &RfbServerManager::updateScreens); connect(qApp, &QApplication::aboutToQuit, this, &RfbServerManager::cleanup); } +void RfbServerManager::updateFrameBuffer() +{ + Q_FOREACH(RfbServer *server, d->servers) { + server->updateFrameBuffer(d->fb->data(), d->fb->width(), d->fb->height(), d->fb->depth()); + } +} + void RfbServerManager::updateScreens() { QList rects = d->fb->modifiedTiles(); QPoint currentCursorPos = QCursor::pos(); Q_FOREACH(RfbServer *server, d->servers) { server->updateScreen(rects); server->updateCursorPosition(currentCursorPos); } //update() might disconnect some of the clients, which will synchronously //call the removeClient() method and will change d->clients, so we need //to copy the set here to avoid problems. QSet clients = d->clients; Q_FOREACH(RfbClient *client, clients) { client->update(); } } void RfbServerManager::cleanup() { //qDebug(); //copy because d->servers is going to be modified while we delete the servers QSet servers = d->servers; Q_FOREACH(RfbServer *server, servers) { delete server; } Q_ASSERT(d->servers.isEmpty()); Q_ASSERT(d->clients.isEmpty()); d->myCursor->cleanup = true; rfbFreeCursor(d->myCursor); d->fb.clear(); } void RfbServerManager::registerServer(RfbServer* server) { d->servers.insert(server); } void RfbServerManager::unregisterServer(RfbServer* server) { d->servers.remove(server); } rfbScreenInfoPtr RfbServerManager::newScreen() { rfbScreenInfoPtr screen = NULL; if (!d->fb.isNull()) { int w = d->fb->width(); int h = d->fb->height(); int depth = d->fb->depth(); int bpp = depth >> 3; if (bpp != 1 && bpp != 2 && bpp != 4) { bpp = 4; } //qDebug() << "bpp: " << bpp; rfbLogEnable(0); screen = rfbGetScreen(0, 0, w, h, 8, 3, bpp); screen->paddedWidthInBytes = d->fb->paddedWidth(); d->fb->getServerFormat(screen->serverFormat); screen->frameBuffer = d->fb->data(); - screen->desktopName = d->desktopName.constData(); screen->cursor = d->myCursor; } return screen; } void RfbServerManager::addClient(RfbClient* cc) { if (d->clients.size() == 0) { //qDebug() << "Starting framebuffer monitor"; d->fb->startMonitor(); d->rfbUpdateTimer.start(50); } d->clients.insert(cc); KNotification::event("UserAcceptsConnection", i18n("The remote user %1 is now connected.", cc->name())); Q_EMIT clientConnected(cc); } void RfbServerManager::removeClient(RfbClient* cc) { d->clients.remove(cc); if (d->clients.size() == 0) { //qDebug() << "Stopping framebuffer monitor"; d->fb->stopMonitor(); d->rfbUpdateTimer.stop(); } KNotification::event("ConnectionClosed", i18n("The remote user %1 disconnected.", cc->name())); Q_EMIT clientDisconnected(cc); } #include "rfbservermanager.moc" diff --git a/krfb/rfbservermanager.h b/krfb/rfbservermanager.h index e3a735f..56849e3 100644 --- a/krfb/rfbservermanager.h +++ b/krfb/rfbservermanager.h @@ -1,65 +1,66 @@ /* Copyright (C) 2009-2010 Collabora Ltd @author George Goldberg @author George Kiagiadakis Copyright (C) 2007 Alessandro Praduroux 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 Lesser General Public License along with this program. If not, see . */ #ifndef RFBSERVERMANAGER_H #define RFBSERVERMANAGER_H #include "rfb.h" #include class RfbClient; struct RfbServerManagerStatic; class RfbServer; class RfbServerManager : public QObject { Q_OBJECT public: static RfbServerManager *instance(); Q_SIGNALS: void clientConnected(RfbClient *cc); void clientDisconnected(RfbClient *cc); private Q_SLOTS: void init(); + void updateFrameBuffer(); void updateScreens(); void cleanup(); private: void registerServer(RfbServer *server); void unregisterServer(RfbServer *server); rfbScreenInfoPtr newScreen(); void addClient(RfbClient *cc); void removeClient(RfbClient *cc); RfbServerManager(); virtual ~RfbServerManager(); Q_DISABLE_COPY(RfbServerManager) friend class RfbServer; friend struct RfbServerManagerStatic; struct Private; Private *const d; }; #endif // RFBSERVERMANAGER_H