diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 9109abcf3a..d47ce6b5b9 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -68,6 +68,7 @@ set(SERVER_LIB_SRCS xdgforeign_v2_interface.cpp xdgoutput_interface.cpp xdgshell_interface.cpp + screencast_interface.cpp ) ecm_qt_declare_logging_category(SERVER_LIB_SRCS @@ -258,6 +259,11 @@ ecm_add_qtwayland_server_protocol(SERVER_LIB_SRCS BASENAME wp-primary-selection-unstable-v1 ) +ecm_add_qtwayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/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 @@ -316,6 +322,8 @@ set(SERVER_GENERATED_SRCS ${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-keyboard-shortcuts-inhibit-unstable-v1.h + ${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-screencast-unstable-v1.h + ${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-screencast-unstable-v1.cpp ) set_source_files_properties(${SERVER_GENERATED_SRCS} PROPERTIES SKIP_AUTOMOC ON) @@ -397,6 +405,7 @@ set(SERVER_LIB_HEADERS relativepointer_interface.h remote_access_interface.h resource.h + screencast_interface.h seat_interface.h server_decoration_interface.h server_decoration_palette_interface.h diff --git a/src/wayland/autotests/server/CMakeLists.txt b/src/wayland/autotests/server/CMakeLists.txt index e5202e7023..151d9666cc 100644 --- a/src/wayland/autotests/server/CMakeLists.txt +++ b/src/wayland/autotests/server/CMakeLists.txt @@ -75,3 +75,16 @@ add_executable(testViewporterInterface test_viewporter_interface.cpp ${VIEWPORTE target_link_libraries(testViewporterInterface Qt5::Test Plasma::KWaylandServer KF5::WaylandClient Wayland::Client) add_test(NAME kwayland-testViewporterInterface COMMAND testViewporterInterface) ecm_mark_as_test(testViewporterInterface) + + +######################################################## +# Test ScreencastInterface +######################################################## +ecm_add_qtwayland_client_protocol(SCREENCAST_SRCS + PROTOCOL PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/screencast.xml + BASENAME zkde-screencast-unstable-v1 +) +add_executable(testScreencastInterface test_screencast.cpp ${SCREENCAST_SRCS}) +target_link_libraries(testScreencastInterface Qt5::Test Plasma::KWaylandServer Wayland::Client KF5::WaylandClient) +add_test(NAME kwayland-testScreencastInterface COMMAND testScreencastInterface) +ecm_mark_as_test(testScreencastInterface) diff --git a/src/wayland/autotests/server/test_screencast.cpp b/src/wayland/autotests/server/test_screencast.cpp new file mode 100644 index 0000000000..fdffa41b35 --- /dev/null +++ b/src/wayland/autotests/server/test_screencast.cpp @@ -0,0 +1,176 @@ +/* + SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +// 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/screencast_interface.h" + +#include +#include +#include +#include +#include + +#include "qwayland-zkde-screencast-unstable-v1.h" + +class ScreencastStream : public QObject, public QtWayland::zkde_screencast_stream_unstable_v1 +{ + Q_OBJECT +public: + ScreencastStream(::zkde_screencast_stream_unstable_v1 *obj, QObject *parent) + : QObject(parent) + , zkde_screencast_stream_unstable_v1(obj) + { + + } + + void zkde_screencast_stream_unstable_v1_created(uint32_t node) override { + Q_EMIT created(node); + } + +Q_SIGNALS: + void created(quint32 node); +}; +class Screencast : public QObject, public QtWayland::zkde_screencast_unstable_v1 +{ + Q_OBJECT +public: + Screencast(QObject* parent) + : QObject(parent) + { + } + + ScreencastStream* createWindowStream(const QString &uuid) { + return new ScreencastStream(stream_window(uuid, 2), this); + } +}; + +class TestScreencastInterface : public QObject +{ + Q_OBJECT +public: + TestScreencastInterface() + { + } + + ~TestScreencastInterface() override; + +private Q_SLOTS: + void initTestCase(); + void testCreate(); + +private: + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::EventQueue *m_queue = nullptr; + Screencast *m_screencast = nullptr; + + KWaylandServer::ScreencastInterface *m_screencastInterface = nullptr; + + QPointer m_triggered = nullptr; + QThread *m_thread; + KWaylandServer::Display *m_display = nullptr; +}; + +static const QString s_socketName = QStringLiteral("kwin-wayland-server-screencast-test-0"); + +void TestScreencastInterface::initTestCase() +{ + delete m_display; + m_display = new KWaylandServer::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 screencastSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced); + QVERIFY(screencastSpy.isValid()); + m_screencastInterface = m_display->createScreencastInterface(this); + connect(m_screencastInterface, &KWaylandServer::ScreencastInterface::windowScreencastRequested, this, [this] (KWaylandServer::ScreencastStreamInterface* stream, const QString &winid) { + Q_UNUSED(winid); + stream->sendCreated(123); + m_triggered = stream; + }); + + connect(®istry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, ®istry] (const QByteArray &interfaceName, quint32 name, quint32 version) { + if (interfaceName != "zkde_screencast_unstable_v1") + return; + Q_ASSERT(version == 1); + m_screencast = new Screencast(this); + m_screencast->init(&*registry, name, version); + }); + registry.setEventQueue(m_queue); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + wl_display_flush(m_connection->display()); + + QVERIFY(m_screencastInterface); + QVERIFY(m_screencast || screencastSpy.wait()); + QVERIFY(m_screencast); +} + +TestScreencastInterface::~TestScreencastInterface() +{ + 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 TestScreencastInterface::testCreate() +{ + auto stream = m_screencast->createWindowStream("3"); + QVERIFY(stream); + + QSignalSpy spyWorking(stream, &ScreencastStream::created); + QVERIFY(spyWorking.count() || spyWorking.wait()); + QVERIFY(m_triggered); + + QSignalSpy spyStop(m_triggered, &KWaylandServer::ScreencastStreamInterface::finished); + stream->close(); + QVERIFY(spyStop.count() || spyStop.wait()); +} + +QTEST_GUILESS_MAIN(TestScreencastInterface) + +#include "test_screencast.moc" diff --git a/src/wayland/display.cpp b/src/wayland/display.cpp index 79d4855882..d6643e8849 100644 --- a/src/wayland/display.cpp +++ b/src/wayland/display.cpp @@ -33,6 +33,7 @@ #include "relativepointer_interface_p.h" #include "remote_access_interface.h" #include "seat_interface.h" +#include "screencast_interface.h" #include "server_decoration_interface.h" #include "server_decoration_palette_interface.h" #include "shadow_interface.h" @@ -341,6 +342,13 @@ ServerSideDecorationManagerInterface *Display::createServerSideDecorationManager return d; } +ScreencastInterface *Display::createScreencastInterface(QObject *parent) +{ + auto s = new ScreencastInterface(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/wayland/display.h b/src/wayland/display.h index 7c639baaed..fe4e1a5324 100644 --- a/src/wayland/display.h +++ b/src/wayland/display.h @@ -81,6 +81,7 @@ class DataControlDeviceManagerV1Interface; class PrimarySelectionDeviceManagerV1Interface; class KeyboardShortcutsInhibitManagerV1Interface; class ViewporterInterface; +class ScreencastInterface; /** * @brief Class holding the Wayland server display loop. @@ -333,6 +334,11 @@ public: */ PrimarySelectionDeviceManagerV1Interface *createPrimarySelectionDeviceManagerV1(QObject *parent = nullptr); + /** + * Creates an interface to request video feeds of different compositor resources + */ + ScreencastInterface *createScreencastInterface(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/wayland/server/screencast_interface.cpp b/src/wayland/server/screencast_interface.cpp new file mode 100644 index 0000000000..0926601560 --- /dev/null +++ b/src/wayland/server/screencast_interface.cpp @@ -0,0 +1,111 @@ +/* + SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "screencast_interface.h" +#include "display.h" +#include + +#include "qwayland-server-zkde-screencast-unstable-v1.h" + +using namespace KWaylandServer; + +static int s_version = 1; + +class KWaylandServer::ScreencastStreamInterfacePrivate : public QtWaylandServer::zkde_screencast_stream_unstable_v1 +{ +public: + ScreencastStreamInterfacePrivate(ScreencastStreamInterface *q) + : q(q) + {} + + void zkde_screencast_stream_unstable_v1_destroy_resource(Resource *resource) override + { + Q_UNUSED(resource); + if (!stopped) { + Q_EMIT q->finished(); + } + + q->deleteLater(); + } + + void zkde_screencast_stream_unstable_v1_close(Resource * resource) override + { + Q_UNUSED(resource); + Q_EMIT q->finished(); + stopped = true; + wl_resource_destroy(resource->handle); + } + + bool stopped = false; + ScreencastStreamInterface *const q; +}; + +ScreencastStreamInterface::ScreencastStreamInterface(QObject* parent) + : QObject(parent) + , d(new ScreencastStreamInterfacePrivate(this)) +{ +} + +ScreencastStreamInterface::~ScreencastStreamInterface() = default; + +void ScreencastStreamInterface::sendCreated(quint32 nodeid) +{ + d->send_created(nodeid); +} + +void ScreencastStreamInterface::sendFailed(const QString& error) +{ + d->send_failed(error); +} + +void ScreencastStreamInterface::sendClosed() +{ + if (!d->stopped) { + d->send_closed(); + } +} + +class KWaylandServer::ScreencastInterfacePrivate : public QtWaylandServer::zkde_screencast_unstable_v1 +{ +public: + ScreencastInterfacePrivate(Display *display, ScreencastInterface* q) + : QtWaylandServer::zkde_screencast_unstable_v1(*display, s_version) + , q(q) + { + } + + ScreencastStreamInterface *createStream(Resource *resource, quint32 streamid) const + { + auto stream = new ScreencastStreamInterface(q); + stream->d->init(resource->client(), streamid, resource->version()); + return stream; + } + + void zkde_screencast_unstable_v1_stream_output(Resource *resource, uint32_t streamid, struct ::wl_resource *output, uint32_t pointer) override + { + Q_EMIT q->outputScreencastRequested(createStream(resource, streamid), output, ScreencastInterface::CursorMode(pointer)); + } + + void zkde_screencast_unstable_v1_stream_window(Resource *resource, uint32_t streamid, const QString &uuid, uint32_t pointer) override + { + Q_EMIT q->windowScreencastRequested(createStream(resource, streamid), uuid, ScreencastInterface::CursorMode(pointer)); + } + + void zkde_screencast_unstable_v1_destroy(Resource * resource) override + { + wl_resource_destroy(resource->handle); + } + + ScreencastInterface *const q; +}; + +ScreencastInterface::ScreencastInterface(Display *display, QObject *parent) + : QObject(parent) + , d(new ScreencastInterfacePrivate(display, this)) +{ +} + +ScreencastInterface::~ScreencastInterface() = default; diff --git a/src/wayland/server/screencast_interface.h b/src/wayland/server/screencast_interface.h new file mode 100644 index 0000000000..79730d16ef --- /dev/null +++ b/src/wayland/server/screencast_interface.h @@ -0,0 +1,73 @@ +/* + 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 +#include +#include + +struct wl_resource; + +namespace KWaylandServer +{ + +class Display; +class OutputInterface; +class Screencast; +class ScreencastSourcePrivate; +class ScreencastInterfacePrivate; +class ScreencastStreamInterfacePrivate; +class ScreencastStreamInterface; + +class KWAYLANDSERVER_EXPORT ScreencastStreamInterface : public QObject +{ + Q_OBJECT +public: + ~ScreencastStreamInterface() override; + + void sendCreated(quint32 nodeid); + void sendFailed(const QString &error); + void sendClosed(); + +Q_SIGNALS: + void finished(); + +private: + friend class ScreencastInterfacePrivate; + explicit ScreencastStreamInterface(QObject *parent); + QScopedPointer d; +}; + +class KWAYLANDSERVER_EXPORT ScreencastInterface : public QObject +{ + Q_OBJECT +public: + virtual ~ScreencastInterface(); + + enum CursorMode { + Hidden = 1, + Embedded = 2, + Metadata = 4, + }; + Q_ENUM(CursorMode); + + +Q_SIGNALS: + void outputScreencastRequested(ScreencastStreamInterface* stream, ::wl_resource *output, CursorMode mode); + void windowScreencastRequested(ScreencastStreamInterface* stream, const QString &winid, CursorMode mode); + +private: + explicit ScreencastInterface(Display *display, QObject *parent = nullptr); + friend class Display; + QScopedPointer d; +}; + +}