diff --git a/autotests/server/CMakeLists.txt b/autotests/server/CMakeLists.txt --- a/autotests/server/CMakeLists.txt +++ b/autotests/server/CMakeLists.txt @@ -57,3 +57,9 @@ target_link_libraries( testTabletInterface Qt5::Test KF5::WaylandServer KF5::WaylandClient Wayland::Client) add_test(NAME kwayland-testTabletInterface COMMAND testTabletInterface) ecm_mark_as_test(testTabletInterface) + +add_executable(testScreencastingInterface test_screencasting.cpp) +target_link_libraries( testScreencastingInterface Qt5::Test KF5::WaylandServer Wayland::Client KF5::WaylandClient) +# target_include_directories(testScreencastingInterface PUBLIC ${CMAKE_BINARY_DIR}/src/client/ ${CMAKE_BINARY_DIR}/src/client/) +add_test(NAME kwayland-testScreencastingInterface COMMAND testScreencastingInterface) +ecm_mark_as_test(testScreencastingInterface) diff --git a/autotests/server/test_screencasting.cpp b/autotests/server/test_screencasting.cpp new file mode 100644 --- /dev/null +++ b/autotests/server/test_screencasting.cpp @@ -0,0 +1,186 @@ +/******************************************************************** +Copyright 2020 Aleix Pol Gonzalez + +This library 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.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +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 . +*********************************************************************/ +// Qt +#include +#include +#include + +#include + +// WaylandServer +#include "../../src/server/compositor_interface.h" +#include "../../src/server/display.h" +#include "../../src/server/seat_interface.h" +#include "../../src/server/screencasting_interface.h" + +#include "../../src/client/connection_thread.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/registry.h" +#include "../../src/client/seat.h" +#include "../../src/client/screencasting.h" +#include "../../src/client/compositor.h" +#include "../../src/client/screencasting.h" + +class TestScreencastingInterface : public QObject +{ + Q_OBJECT +public: + TestScreencastingInterface() + { + } + + ~TestScreencastingInterface() override; + +private Q_SLOTS: + void initTestCase(); + void testAdd(); + void testCreate(); + +private: + QPointer m_triggered = nullptr; + void sourceTriggered(KWayland::Server::ScreencastingStreamInterface* stream) + { + static int nodeid = 1; + m_triggered = stream; + stream->sendCreated(nodeid++); + } + KWayland::Server::ScreencastingSource createTestSource() { + m_triggered.clear(); + auto f = [this] (KWayland::Server::ScreencastingStreamInterface *r) { sourceTriggered(r); }; + return KWayland::Server::ScreencastingSource("potatoes", "video-display", true, {0,0,200,200}, {400, 400}, f); + } + + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::EventQueue *m_queue = nullptr; + KWayland::Client::Screencasting *m_screencasting = nullptr; + + KWayland::Server::ScreencastingInterface *m_screencastingInterface = nullptr; + + QThread *m_thread; + KWayland::Server::Display *m_display = nullptr; +}; + +static const QString s_socketName = QStringLiteral("kwin-wayland-server-screencasting-test-0"); + +void TestScreencastingInterface::initTestCase() +{ + delete m_display; + m_display = new KWayland::Server::Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + + // setup connection + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected); + QVERIFY(connectedSpy.isValid()); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + m_queue = new KWayland::Client::EventQueue(this); + QVERIFY(!m_queue->isValid()); + m_queue->setup(m_connection); + QVERIFY(m_queue->isValid()); + + KWayland::Client::Registry registry; + + QSignalSpy screencastingSpy(®istry, &KWayland::Client::Registry::screencastingAnnounced); + QVERIFY(screencastingSpy.isValid()); + m_screencastingInterface = m_display->createScreencastingInterface(this); + + connect(®istry, &KWayland::Client::Registry::screencastingAnnounced, this, [this, ®istry] (quint32 name, quint32 version) { + Q_ASSERT(version == 1); + m_screencasting = new KWayland::Client::Screencasting(®istry, name, version, this); + }); + registry.setEventQueue(m_queue); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + wl_display_flush(m_connection->display()); + + QVERIFY(m_screencastingInterface); + QVERIFY(m_screencasting || screencastingSpy.wait()); + QVERIFY(m_screencasting); +} + +TestScreencastingInterface::~TestScreencastingInterface() +{ + delete m_queue; + m_queue = nullptr; + + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + m_connection->deleteLater(); + m_connection = nullptr; + + delete m_display; +} + +void TestScreencastingInterface::testAdd() +{ + QSignalSpy spySourcesChanged(m_screencasting, &KWayland::Client::Screencasting::sourcesChanged); + + QCOMPARE(m_screencasting->sources().count(), 0); + + auto serverSource = createTestSource(); + m_screencastingInterface->addSource(serverSource); + QVERIFY(spySourcesChanged.count() || spySourcesChanged.wait()); + QCOMPARE(m_screencasting->sources().count(), 1); + + m_screencastingInterface->removeSource(serverSource); + QVERIFY(spySourcesChanged.count() == 2 || spySourcesChanged.wait()); + QCOMPARE(m_screencasting->sources().count(), 0); + QVERIFY(!m_triggered); +} + +void TestScreencastingInterface::testCreate() +{ + QSignalSpy spySourcesChanged(m_screencasting, &KWayland::Client::Screencasting::sourcesChanged); + + QCOMPARE(m_screencasting->sources().count(), 0); + m_screencastingInterface->addSource(createTestSource()); + QVERIFY(spySourcesChanged.count() || spySourcesChanged.wait()); + QCOMPARE(m_screencasting->sources().count(), 1); + + QVERIFY(!m_triggered); + KWayland::Client::ScreencastingStream* stream = m_screencasting->create(m_screencasting->sources().constFirst()); + QVERIFY(stream); + + QSignalSpy spyWorking(stream, &KWayland::Client::ScreencastingStream::created); + QVERIFY(spyWorking.count() || spyWorking.wait()); + QVERIFY(m_triggered); + + QSignalSpy spyStop(m_triggered, &KWayland::Server::ScreencastingStreamInterface::stop); + stream->close(); + QVERIFY(spyStop.count() || spyStop.wait()); +} + +QTEST_GUILESS_MAIN(TestScreencastingInterface) +#include "test_screencasting.moc" diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -61,6 +61,7 @@ xdgshell_v6.cpp xdgshell_stable.cpp xdgoutput.cpp + screencasting.cpp ../compat/wayland-xdg-shell-v5-protocol.c ) @@ -202,6 +203,11 @@ BASENAME keystate ) +ecm_add_qtwayland_client_protocol(CLIENT_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/screencast.xml + BASENAME zkde-screencast-unstable-v1 +) + set(CLIENT_GENERATED_FILES ${CMAKE_CURRENT_BINARY_DIR}/wayland-fullscreen-shell-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-management-client-protocol.h @@ -259,6 +265,7 @@ PUBLIC Qt5::Gui PRIVATE Wayland::Client Qt5::Concurrent + Qt5::WaylandClient ) set_target_properties(KF5WaylandClient PROPERTIES VERSION ${KWAYLAND_VERSION_STRING} @@ -289,6 +296,7 @@ keyboard.h keystate.h remote_access.h + screencasting.h outputconfiguration.h outputmanagement.h outputdevice.h diff --git a/src/client/protocols/screencast.xml b/src/client/protocols/screencast.xml new file mode 100644 --- /dev/null +++ b/src/client/protocols/screencast.xml @@ -0,0 +1,68 @@ + + + + + SPDX-License-Identifier: LGPL-2.1-or-later + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Destroy the zkde_screencast_unstable_v1 object. Also disables all zkde_screencast_stream_unstable_v1 issued through this this object. + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/client/registry.h b/src/client/registry.h --- a/src/client/registry.h +++ b/src/client/registry.h @@ -36,6 +36,7 @@ struct org_kde_kwin_blur_manager; struct org_kde_kwin_contrast_manager; struct org_kde_kwin_slide_manager; +struct zkde_screencast_unstable_v1; struct org_kde_plasma_shell; struct org_kde_plasma_virtual_desktop_management; struct org_kde_plasma_window_management; @@ -83,6 +84,7 @@ class BlurManager; class ContrastManager; class SlideManager; +class Screencasting; class Shell; class ShmPool; class ServerSideDecorationManager; @@ -174,6 +176,7 @@ XdgShellStable, ///refers to xdg_wm_base @since 5.48 XdgDecorationUnstableV1, ///refers to zxdg_decoration_manager_v1, @since 5.54 Keystate,/// #include #include +#include /***** * How to add another interface: @@ -372,6 +374,13 @@ &org_kde_kwin_keystate_interface, &Registry::keystateAnnounced, &Registry::keystateRemoved + }}, + {Registry::Interface::Screencasting, { + 1, + QByteArrayLiteral("zkde_screencast_unstable_v1"), + &zkde_screencast_unstable_v1_interface, + &Registry::screencastingAnnounced, + &Registry::screencastingRemoved }} }; @@ -680,6 +689,7 @@ BIND(XdgImporterUnstableV2, zxdg_importer_v2) BIND(IdleInhibitManagerUnstableV1, zwp_idle_inhibit_manager_v1) BIND(Keystate, org_kde_kwin_keystate) +BIND(Screencasting, zkde_screencast_unstable_v1) BIND2(ShadowManager, Shadow, org_kde_kwin_shadow_manager) BIND2(BlurManager, Blur, org_kde_kwin_blur_manager) BIND2(ContrastManager, Contrast, org_kde_kwin_contrast_manager) @@ -742,6 +752,7 @@ CREATE2(ShmPool, Shm) CREATE(AppMenuManager) CREATE(Keystate) +CREATE(Screencasting) CREATE(ServerSideDecorationPaletteManager) #undef CREATE diff --git a/src/client/screencasting.h b/src/client/screencasting.h new file mode 100644 --- /dev/null +++ b/src/client/screencasting.h @@ -0,0 +1,100 @@ +/* + SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include +#include +#include +#include + +#include "registry.h" +#include + +struct zkde_screencast_unstable_v1; + +namespace KWayland +{ +namespace Client +{ +class EventQueue; +class ScreencastingPrivate; +class ScreencastingSourcePrivate; +class ScreencastingStreamPrivate; + +class KWAYLANDCLIENT_EXPORT ScreencastingSource +{ + Q_GADGET +public: + ScreencastingSource(); + ScreencastingSource(const ScreencastingSource& other); + virtual ~ScreencastingSource(); + + quint32 sourceId() const; + QString iconName() const; + QString description() const; + bool isOutput() const; + QRect geometry() const; + QSize size() const; + + bool operator!=(const ScreencastingSource& other) const; + +protected: + ScreencastingSource(quint32 sourceId, const QString &description, const QString &iconName, bool isOutput, const QRect &geometry, const QSize &size); + +private: + friend class ScreencastingPrivate; + QSharedPointer d; +}; + +class KWAYLANDCLIENT_EXPORT ScreencastingStream : public QObject +{ + Q_OBJECT +public: + ScreencastingStream(QObject* parent); + ~ScreencastingStream() override; + + void close(); + +Q_SIGNALS: + void created(quint32 nodeid); + void failed(const QString &error); + void closed(); + +private: + friend class Screencasting; + QScopedPointer d; +}; + +class KWAYLANDCLIENT_EXPORT Screencasting : public QObject +{ + Q_OBJECT +public: + explicit Screencasting(QObject *parent = nullptr); + explicit Screencasting(Registry *registry, int id, int version, QObject *parent = nullptr); + ~Screencasting() override; + + QVector sources() const; + + ScreencastingStream* create(const ScreencastingSource& source); + + void setEventQueue(EventQueue *queue); + void setup(zkde_screencast_unstable_v1* screencasting); + void destroy(); + +Q_SIGNALS: + void initialized(); + void removed(); + void sourcesChanged(); + +private: + QScopedPointer d; +}; + +} +} + +Q_DECLARE_METATYPE(KWayland::Client::ScreencastingSource) diff --git a/src/client/screencasting.cpp b/src/client/screencasting.cpp new file mode 100644 --- /dev/null +++ b/src/client/screencasting.cpp @@ -0,0 +1,211 @@ +/* + SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "screencasting.h" +#include "qwayland-zkde-screencast-unstable-v1.h" +#include +#include +#include + +using namespace KWayland::Client; + +class KWayland::Client::ScreencastingSourcePrivate +{ +public: + ScreencastingSourcePrivate(quint32 sourceId, const QString &description, const QString &iconName, bool isOutput, const QRect &geometry, const QSize &size) + : sourceId(sourceId) + , description(description) + , iconName(iconName) + , isOutput(isOutput) + , geometry(geometry) + , size(size) + {} + + const quint32 sourceId; + const QString description; + const QString iconName; + const bool isOutput; + const QRect geometry; + const QSize size; +}; + +ScreencastingSource::ScreencastingSource() + : d(new ScreencastingSourcePrivate(0, {}, {}, false, {}, {})) +{} + +ScreencastingSource::ScreencastingSource(quint32 sourceId, const QString &description, const QString &iconName, bool isOutput, const QRect &geometry, const QSize &size) + : d(new ScreencastingSourcePrivate(sourceId, description, iconName, isOutput, geometry, size)) +{} + +ScreencastingSource::ScreencastingSource(const ScreencastingSource& other) + : d(other.d) +{} + +ScreencastingSource::~ScreencastingSource() = default; + +quint32 ScreencastingSource::sourceId() const +{ + return d->sourceId; +} + +QString ScreencastingSource::description() const +{ + return d->description; +} + +QString ScreencastingSource::iconName() const +{ + return d->iconName; +} + +bool ScreencastingSource::isOutput() const +{ + return d->isOutput; +} + +QRect ScreencastingSource::geometry() const +{ + return d->geometry; +} + +QSize ScreencastingSource::size() const +{ + return d->size; +} + +bool ScreencastingSource::operator!=(const ScreencastingSource& other) const +{ + return d != other.d; +} + +class KWayland::Client::ScreencastingStreamPrivate : public QtWayland::zkde_screencast_stream_unstable_v1 +{ +public: + ScreencastingStreamPrivate(ScreencastingStream* q) : q(q) {} + + void zkde_screencast_stream_unstable_v1_closed() override { + Q_EMIT q->closed(); + } + + void zkde_screencast_stream_unstable_v1_created(uint32_t node) override { + Q_EMIT q->created(node); + } + void zkde_screencast_stream_unstable_v1_failed(const QString &error) override { + Q_EMIT q->failed(error); + } + +private: + ScreencastingStream* const q; +}; + +ScreencastingStream::ScreencastingStream(QObject* parent) + : QObject(parent) + , d(new ScreencastingStreamPrivate(this)) +{ +} + +ScreencastingStream::~ScreencastingStream() = default; + +void ScreencastingStream::close() +{ + d->close(); +} + +class KWayland::Client::ScreencastingPrivate : public QtWayland::zkde_screencast_unstable_v1 +{ +public: + ScreencastingPrivate(Registry *registry, int id, int version, Screencasting *q) + : QtWayland::zkde_screencast_unstable_v1(*registry, id, version) + , q(q) + { + } + + ScreencastingPrivate(::zkde_screencast_unstable_v1* screencasting, Screencasting *q) + : QtWayland::zkde_screencast_unstable_v1(screencasting) + , q(q) + { + } + + ~ScreencastingPrivate() + { + destroy(); + } + + void zkde_screencast_unstable_v1_add_source(uint32_t sourceId, const QString & description, const QString & iconName, uint32_t type, + int32_t geometryX, int32_t geometryY, int32_t geometryWidth, int32_t geometryHeight, + int32_t width, int32_t height) override + { + m_sources << ScreencastingSource(sourceId, description, iconName, type==source_type_monitor, + {geometryX, geometryY, geometryWidth, geometryHeight}, {width, height}); + if (m_done) + Q_EMIT q->sourcesChanged(); + } + void zkde_screencast_unstable_v1_remove_source(uint32_t sourceId) override { + auto it = std::find_if(m_sources.begin(), m_sources.end(), [sourceId] (const ScreencastingSource &source) { + return source.sourceId() == sourceId; + }); + Q_ASSERT(it != m_sources.end()); + m_sources.erase(it); + Q_EMIT q->sourcesChanged(); + } + + void zkde_screencast_unstable_v1_done() override { + m_done = true; + Q_EMIT q->initialized(); + Q_EMIT q->sourcesChanged(); + } + + ScreencastingSource sourceById(quint32 sourceId) { + auto it = std::find_if(m_sources.begin(), m_sources.end(), [sourceId] (const ScreencastingSource &source) { + return source.sourceId() == sourceId; + }); + if (it == m_sources.constEnd()) + return {}; + return *it; + } + + bool m_done = false; + QVector m_sources; + Screencasting *const q; +}; + +Screencasting::Screencasting(QObject* parent) + : QObject(parent) +{} + +Screencasting::Screencasting(Registry *registry, int id, int version, QObject* parent) + : QObject(parent) + , d(new ScreencastingPrivate(registry, id, version, this)) +{} + +Screencasting::~Screencasting() = default; + +ScreencastingStream* Screencasting::create(const ScreencastingSource& source) +{ + auto stream = new ScreencastingStream(this); + stream->d->init(d->create(source.sourceId())); + return stream; +} + +QVector Screencasting::sources() const +{ + return d->m_sources; +} + +void Screencasting::setEventQueue(EventQueue *queue) +{ + Q_UNUSED(queue); +} + +void Screencasting::setup(::zkde_screencast_unstable_v1* screencasting) +{ + d.reset(new ScreencastingPrivate(screencasting, this)); +} + +void Screencasting::destroy() +{ + d.reset(nullptr); +} diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -64,6 +64,7 @@ xdgshell_v5_interface.cpp xdgshell_v5_interface.cpp xdgshell_v6_interface.cpp + screencasting_interface.cpp ) ecm_qt_declare_logging_category(SERVER_LIB_SRCS @@ -234,6 +235,11 @@ BASENAME tablet-unstable-v2 ) +ecm_add_qtwayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/screencast.xml + BASENAME zkde-screencast-unstable-v1 +) + set(SERVER_GENERATED_SRCS ${CMAKE_CURRENT_BINARY_DIR}/wayland-blur-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-blur-server-protocol.h @@ -293,6 +299,8 @@ ${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-shell-v6-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-tablet-unstable-v2.h ${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-tablet-unstable-v2.cpp + ${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-screecast-unstable-v1.h + ${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-screecast-unstable-v1.cpp ) set_source_files_properties(${SERVER_GENERATED_SRCS} PROPERTIES SKIP_AUTOMOC ON) @@ -369,6 +377,7 @@ relativepointer_interface.h remote_access_interface.h resource.h + screencasting_interface.h seat_interface.h server_decoration_interface.h server_decoration_palette_interface.h diff --git a/src/server/display.h b/src/server/display.h --- a/src/server/display.h +++ b/src/server/display.h @@ -80,6 +80,7 @@ class KeyStateInterface; class LinuxDmabufUnstableV1Interface; class TabletManagerInterface; +class ScreencastingInterface; /** * @brief Class holding the Wayland server display loop. @@ -318,6 +319,13 @@ */ TabletManagerInterface *createTabletManagerInterface(QObject *parent = nullptr); + /** + * Creates an interface to request video feeds of different compositor resources + * + * @since 5.70 + */ + ScreencastingInterface *createScreencastingInterface(QObject *parent = nullptr); + /** * Gets the ClientConnection for the given @p client. * If there is no ClientConnection yet for the given @p client, it will be created. diff --git a/src/server/display.cpp b/src/server/display.cpp --- a/src/server/display.cpp +++ b/src/server/display.cpp @@ -31,6 +31,7 @@ #include "relativepointer_interface_p.h" #include "remote_access_interface.h" #include "seat_interface.h" +#include "screencasting_interface.h" #include "server_decoration_interface.h" #include "server_decoration_palette_interface.h" #include "shadow_interface.h" @@ -378,6 +379,13 @@ return d; } +ScreencastingInterface *Display::createScreencastingInterface(QObject *parent) +{ + auto s = new ScreencastingInterface(this, parent); + connect(this, &Display::aboutToTerminate, s, [s] { delete s; }); + return s; +} + TextInputManagerInterface *Display::createTextInputManager(const TextInputInterfaceVersion &version, QObject *parent) { TextInputManagerInterface *t = nullptr; diff --git a/src/server/screencasting_interface.h b/src/server/screencasting_interface.h new file mode 100644 --- /dev/null +++ b/src/server/screencasting_interface.h @@ -0,0 +1,94 @@ +/* + SPDX-FileCopyrightText: 2016 Oleg Chernovskiy + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +struct wl_resource; + +namespace KWayland +{ +namespace Server +{ + +class Display; +class OutputInterface; +class Screencasting; +class ScreencastingSourcePrivate; +class ScreencastingInterfacePrivate; +class ScreencastingStreamInterfacePrivate; +class ScreencastingStreamInterface; + +class KWAYLANDSERVER_EXPORT ScreencastingSource +{ + Q_GADGET +public: + ScreencastingSource(); + ScreencastingSource(const QString &description, const QString &iconName, bool isOutput, const QRect &geometry, const QSize &size, std::function); + ScreencastingSource(const ScreencastingSource& other); + virtual ~ScreencastingSource(); + + quint32 sourceId() const; + QString iconName() const; + QString description() const; + bool isOutput() const; + QRect geometry() const; + QSize size() const; + void call(ScreencastingStreamInterface *stream); + + bool operator!=(const ScreencastingSource &other) const; + +private: + friend ScreencastingInterfacePrivate; + QSharedPointer d; +}; + +class KWAYLANDSERVER_EXPORT ScreencastingStreamInterface : public QObject +{ + Q_OBJECT +public: + ~ScreencastingStreamInterface() override; + + void sendCreated(quint32 nodeid); + void sendFailed(const QString &error); + void sendClosed(); + +Q_SIGNALS: + void stop(); + +private: + friend class ScreencastingInterfacePrivate; + explicit ScreencastingStreamInterface(QObject *parent = nullptr); + QScopedPointer d; +}; + +class KWAYLANDSERVER_EXPORT ScreencastingInterface : public QObject +{ + Q_OBJECT +public: + virtual ~ScreencastingInterface(); + + void addSource(const ScreencastingSource &source); + void removeSource(const ScreencastingSource &source); + +Q_SIGNALS: + void destroyingClient(void* client); + +private: + explicit ScreencastingInterface(Display *display, QObject *parent = nullptr); + friend class Display; + QScopedPointer d; +}; + +} +} diff --git a/src/server/screencasting_interface.cpp b/src/server/screencasting_interface.cpp new file mode 100644 --- /dev/null +++ b/src/server/screencasting_interface.cpp @@ -0,0 +1,228 @@ +/* + SPDX-FileCopyrightText: 2016 Oleg Chernovskiy + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "screencasting_interface.h" +#include "display.h" +#include + +#include "qwayland-server-zkde-screencast-unstable-v1.h" + +using namespace KWayland::Server; + +static int s_version = 1; + +class KWayland::Server::ScreencastingSourcePrivate +{ +public: + ScreencastingSourcePrivate(const QString &description, const QString &iconName, bool isOutput, const QRect &geometry, const QSize &size, std::function call) + : sourceId(0) + , description(description) + , iconName(iconName) + , isOutput(isOutput) + , geometry(geometry) + , size(size) + , call(call) + {} + + quint32 sourceId; + const QString description; + const QString iconName; + const bool isOutput; + const QRect geometry; + const QSize size; + const std::function call; +}; + +ScreencastingSource::ScreencastingSource() + : d(new ScreencastingSourcePrivate({}, {}, false, {}, {}, [] (ScreencastingStreamInterface *) {})) +{} + +ScreencastingSource::ScreencastingSource(const QString &description, const QString &iconName, bool isOutput, const QRect &geometry, const QSize &size, std::function call) + : d(new ScreencastingSourcePrivate(description, iconName, isOutput, geometry, size, call)) +{} + +ScreencastingSource::ScreencastingSource(const ScreencastingSource& other) + : d(other.d) +{} + +ScreencastingSource::~ScreencastingSource() = default; + +quint32 ScreencastingSource::sourceId() const +{ + return d->sourceId; +} + +QString ScreencastingSource::description() const +{ + return d->description; +} + +QString ScreencastingSource::iconName() const +{ + return d->iconName; +} + +bool ScreencastingSource::isOutput() const +{ + return d->isOutput; +} + +QRect ScreencastingSource::geometry() const +{ + return d->geometry; +} + +QSize ScreencastingSource::size() const +{ + return d->size; +} + +bool ScreencastingSource::operator!=(const ScreencastingSource &other) const +{ + return d != other.d; +} + +void KWayland::Server::ScreencastingSource::call(ScreencastingStreamInterface *stream) +{ + d->call(stream); +} + + +class KWayland::Server::ScreencastingStreamInterfacePrivate : public QtWaylandServer::zkde_screencast_stream_unstable_v1 +{ +public: + ScreencastingStreamInterfacePrivate(ScreencastingStreamInterface *q) : q(q) + {} + + void zkde_screencast_stream_unstable_v1_close(Resource * resource) override { + Q_UNUSED(resource); + Q_EMIT q->stop(); + } + + ScreencastingStreamInterface* const q; +}; + +class KWayland::Server::ScreencastingInterfacePrivate : public QtWaylandServer::zkde_screencast_unstable_v1 +{ +public: + ScreencastingInterfacePrivate(Display *display, ScreencastingInterface* q) + : QtWaylandServer::zkde_screencast_unstable_v1(*display, s_version) + , q(q) + { + } + + void zkde_screencast_unstable_v1_bind_resource(Resource *resource) override { + for (auto source : qAsConst(m_sources)) { + sendSource(resource, source); + } + send_done(resource->handle); + } + + void zkde_screencast_unstable_v1_destroy(Resource *resource) override { + Q_EMIT q->destroyingClient(resource->client()); + } + + void zkde_screencast_unstable_v1_create(Resource *resource, uint32_t sourceId, uint32_t streamid) override { + auto stream = new ScreencastingStreamInterface(q); + stream->d->init(resource->client(), streamid, s_version); + + auto it = std::find_if(m_sources.begin(), m_sources.end(), [sourceId] (const ScreencastingSource &source) { return source.sourceId() == sourceId; }); + if (it == m_sources.end()) { + stream->sendFailed(QObject::tr("Source %1 does not exist").arg(sourceId)); + return; + } + + it->call(stream); + + QObject::connect(q, &ScreencastingInterface::destroyingClient, stream, [stream] (void* client) { + if (stream->d->resource()->client() == client) { + stream->stop(); + } + }); + } + + void sendSource(Resource* resource, const ScreencastingSource &source) { + const QRect geometry = source.geometry(); + const QSize size = source.size(); + send_add_source(resource->handle, source.sourceId(), source.description(), source.iconName(), source.isOutput(), + geometry.x(), geometry.y(), geometry.width(), geometry.height(), + size.width(), size.height()); + } + + void addSource(ScreencastingSource &source) { + static quint32 id = 0; + auto f = [] (const ScreencastingSource &source) { return source.sourceId() == id; }; + while (id == 0 || std::find_if(m_sources.cbegin(), m_sources.cend(), f) != m_sources.cend()) { + ++id; + } + source.d->sourceId = id; + + m_sources << source; + } + + QVector m_sources; + ScreencastingInterface *const q; +}; + +ScreencastingStreamInterface::ScreencastingStreamInterface(QObject* parent) + : QObject(parent) + , d(new ScreencastingStreamInterfacePrivate(this)) +{ +} + +ScreencastingStreamInterface::~ScreencastingStreamInterface() = default; + +void ScreencastingStreamInterface::sendCreated(quint32 nodeid) +{ + d->send_created(nodeid); +} + +void ScreencastingStreamInterface::sendFailed(const QString& error) +{ + d->send_failed(error); + deleteLater(); +} + +void ScreencastingStreamInterface::sendClosed() +{ + d->send_closed(); + deleteLater(); +} + +ScreencastingInterface::ScreencastingInterface(Display *display, QObject *parent) + : QObject(parent) + , d(new ScreencastingInterfacePrivate(display, this)) +{ +} + +ScreencastingInterface::~ScreencastingInterface() = default; + +void ScreencastingInterface::addSource(const ScreencastingSource &source) +{ + Q_ASSERT(source.sourceId() == 0); + + ScreencastingSource ourSource = source; + d->addSource(ourSource); + + for (auto r : d->resourceMap()) { + d->sendSource(r, ourSource); + } +} + +void ScreencastingInterface::removeSource(const ScreencastingSource &sourceToRemove) +{ + auto it = std::find_if(d->m_sources.begin(), d->m_sources.end(), [&sourceToRemove] (const ScreencastingSource &source) { return source.sourceId() == sourceToRemove.sourceId(); }); + if (it == d->m_sources.end()) { + return; + } + + d->m_sources.erase(it); + + for (auto r : d->resourceMap()) { + d->send_remove_source(r->handle, sourceToRemove.sourceId()); + } +} +