diff --git a/autotests/client/CMakeLists.txt b/autotests/client/CMakeLists.txt --- a/autotests/client/CMakeLists.txt +++ b/autotests/client/CMakeLists.txt @@ -419,3 +419,13 @@ add_test(NAME kwayland-testServerSideDecorationPalette COMMAND testServerSideDecorationPalette) ecm_mark_as_test(testServerSideDecorationPalette) +######################################################## +# Test RemoteAccess +######################################################## +set( testRemoteAccess_SRCS + test_remote_access.cpp + ) +add_executable(testRemoteAccess ${testRemoteAccess_SRCS}) +target_link_libraries( testRemoteAccess Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) +add_test(NAME kwayland-testRemoteAccess COMMAND testRemoteAccess) +ecm_mark_as_test(testRemoteAccess) diff --git a/autotests/client/test_remote_access.cpp b/autotests/client/test_remote_access.cpp new file mode 100644 --- /dev/null +++ b/autotests/client/test_remote_access.cpp @@ -0,0 +1,369 @@ +/******************************************************************** +Copyright 2016 Oleg Chernovskiy + +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 +// client +#include "../../src/client/connection_thread.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/remote_access.h" +#include "../../src/client/registry.h" +#include "../../src/client/output.h" +// server +#include "../../src/server/display.h" +#include "../../src/server/output_interface.h" +#include "../../src/server/remote_access_interface.h" + +#include + +using namespace KWayland::Client; +using namespace KWayland::Server; + +Q_DECLARE_METATYPE(const BufferHandle *) +Q_DECLARE_METATYPE(const RemoteBuffer *) +Q_DECLARE_METATYPE(const void *) + +class RemoteAccessTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + + void testSendReleaseSingle(); + void testSendReleaseMultiple(); + void testSendClientGone(); + void testSendReceiveClientGone(); + +private: + Display *m_display = nullptr; + OutputInterface *m_outputInterface = nullptr; + RemoteAccessManagerInterface *m_remoteAccessInterface = nullptr; + ConnectionThread *m_connection = nullptr; + QThread *m_thread = nullptr; + EventQueue *m_queue = nullptr; + Registry *m_registry = nullptr; + Output *m_output = nullptr; +}; + +static const QString s_socketName = QStringLiteral("kwayland-test-remote-access-0"); + +void RemoteAccessTest::init() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + m_display->createShm(); + m_outputInterface = m_display->createOutput(); + m_outputInterface->create(); + m_remoteAccessInterface = m_display->createRemoteAccessManager(); + m_remoteAccessInterface->create(); + QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased); + QVERIFY(bufferReleasedSpy.isValid()); + + // setup connection + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, &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 EventQueue(this); + m_queue->setup(m_connection); + + m_registry = new Registry(this); + QSignalSpy interfacesAnnouncedSpy(m_registry, &Registry::interfacesAnnounced); + QVERIFY(interfacesAnnouncedSpy.isValid()); + m_registry->setEventQueue(m_queue); + m_registry->create(m_connection); + QVERIFY(m_registry->isValid()); + m_registry->setup(); + QVERIFY(interfacesAnnouncedSpy.wait()); + + // client-bound output + m_output = m_registry->createOutput(m_registry->interface(Registry::Interface::Output).name, + m_registry->interface(Registry::Interface::Output).version, + this); +} + +void RemoteAccessTest::cleanup() +{ +#define CLEANUP(variable) \ + if (variable) { \ + delete variable; \ + variable = nullptr; \ + } + CLEANUP(m_output) + CLEANUP(m_queue) + CLEANUP(m_registry) + if (m_thread) { + if (m_connection) { + m_connection->flush(); + m_connection->deleteLater(); + m_connection = nullptr; + } + + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + + CLEANUP(m_remoteAccessInterface) + CLEANUP(m_display) +#undef CLEANUP +} + +void RemoteAccessTest::testSendReleaseSingle() +{ + // this test verifies that a buffer is sent to client and returned back + + // setup + QVERIFY(!m_remoteAccessInterface->isBound()); + auto client = m_registry->createRemoteAccessManager( + m_registry->interface(Registry::Interface::RemoteAccessManager).name, + m_registry->interface(Registry::Interface::RemoteAccessManager).version, + this); + QVERIFY(client->isValid()); + m_connection->flush(); + m_display->dispatchEvents(); + + QVERIFY(m_remoteAccessInterface->isBound()); // we have one client now + QSignalSpy bufferReadySpy(client, &RemoteAccessManager::bufferReady); + QVERIFY(bufferReadySpy.isValid()); + + BufferHandle *buf = new BufferHandle(); + QTemporaryFile *tmpFile = new QTemporaryFile(this); + tmpFile->open(); + + buf->setFd(tmpFile->handle()); + buf->setSize(50, 50); + buf->setFormat(100500); + buf->setStride(7800); + m_remoteAccessInterface->sendBufferReady(m_outputInterface, buf); + + // receive buffer + QVERIFY(bufferReadySpy.wait()); + auto rbuf = bufferReadySpy.takeFirst()[1].value(); + QSignalSpy paramsObtainedSpy(rbuf, &RemoteBuffer::parametersObtained); + QVERIFY(paramsObtainedSpy.isValid()); + + // wait for params + QVERIFY(paramsObtainedSpy.wait()); + // client fd is different, not subject to check + QCOMPARE(rbuf->width(), 50u); + QCOMPARE(rbuf->height(), 50u); + QCOMPARE(rbuf->format(), 100500u); + QCOMPARE(rbuf->stride(), 7800u); + + // release + QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased); + QVERIFY(bufferReleasedSpy.isValid()); + delete rbuf; + QVERIFY(bufferReleasedSpy.wait()); + + // cleanup + delete buf; + delete client; + m_connection->flush(); + m_display->dispatchEvents(); + QVERIFY(!m_remoteAccessInterface->isBound()); +} + +void RemoteAccessTest::testSendReleaseMultiple() +{ + // this test verifies that a buffer is sent to 2 clients and returned back + + // setup + QVERIFY(!m_remoteAccessInterface->isBound()); + auto client1 = m_registry->createRemoteAccessManager( + m_registry->interface(Registry::Interface::RemoteAccessManager).name, + m_registry->interface(Registry::Interface::RemoteAccessManager).version, + this); + QVERIFY(client1->isValid()); + auto client2 = m_registry->createRemoteAccessManager( + m_registry->interface(Registry::Interface::RemoteAccessManager).name, + m_registry->interface(Registry::Interface::RemoteAccessManager).version, + this); + QVERIFY(client2->isValid()); + m_connection->flush(); + m_display->dispatchEvents(); + + QVERIFY(m_remoteAccessInterface->isBound()); // now we have 2 clients + QSignalSpy bufferReadySpy1(client1, &RemoteAccessManager::bufferReady); + QVERIFY(bufferReadySpy1.isValid()); + QSignalSpy bufferReadySpy2(client2, &RemoteAccessManager::bufferReady); + QVERIFY(bufferReadySpy2.isValid()); + + BufferHandle *buf = new BufferHandle(); + QTemporaryFile *tmpFile = new QTemporaryFile(this); + tmpFile->open(); + + buf->setFd(tmpFile->handle()); + buf->setSize(50, 50); + buf->setFormat(100500); + buf->setStride(7800); + m_remoteAccessInterface->sendBufferReady(m_outputInterface, buf); + + // wait for event loop + QVERIFY(bufferReadySpy1.wait()); + + // receive buffer at client 1 + QCOMPARE(bufferReadySpy1.size(), 1); + auto rbuf1 = bufferReadySpy1.takeFirst()[1].value(); + QSignalSpy paramsObtainedSpy1(rbuf1, &RemoteBuffer::parametersObtained); + QVERIFY(paramsObtainedSpy1.isValid()); + + // receive buffer at client 2 + QCOMPARE(bufferReadySpy2.size(), 1); + auto rbuf2 = bufferReadySpy2.takeFirst()[1].value(); + QSignalSpy paramsObtainedSpy2(rbuf2, &RemoteBuffer::parametersObtained); + QVERIFY(paramsObtainedSpy2.isValid()); + + // wait for event loop + QVERIFY(paramsObtainedSpy1.wait()); + QCOMPARE(paramsObtainedSpy1.size(), 1); + QCOMPARE(paramsObtainedSpy2.size(), 1); + + // release + QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased); + QVERIFY(bufferReleasedSpy.isValid()); + delete rbuf1; + QVERIFY(!bufferReleasedSpy.wait(1000)); // one client released, second still holds buffer! + + delete rbuf2; + QVERIFY(bufferReleasedSpy.wait()); // all clients released, buffer should be freed + + // cleanup + delete buf; + delete client1; + delete client2; + m_connection->flush(); + m_display->dispatchEvents(); + QVERIFY(!m_remoteAccessInterface->isBound()); +} + +void RemoteAccessTest::testSendClientGone() +{ + // this test verifies that when buffer is sent and client is gone, server will release buffer correctly + QVERIFY(!m_remoteAccessInterface->isBound()); + auto client = m_registry->createRemoteAccessManager( + m_registry->interface(Registry::Interface::RemoteAccessManager).name, + m_registry->interface(Registry::Interface::RemoteAccessManager).version, + this); + QVERIFY(client->isValid()); + m_connection->flush(); + m_display->dispatchEvents(); + + QVERIFY(m_remoteAccessInterface->isBound()); // we have one client now + QSignalSpy bufferReadySpy(client, &RemoteAccessManager::bufferReady); + QVERIFY(bufferReadySpy.isValid()); + + BufferHandle *buf = new BufferHandle(); + QTemporaryFile *tmpFile = new QTemporaryFile(this); + tmpFile->open(); + + buf->setFd(tmpFile->handle()); + buf->setSize(50, 50); + buf->setFormat(100500); + buf->setStride(7800); + m_remoteAccessInterface->sendBufferReady(m_outputInterface, buf); + + // release forcefully + QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased); + QVERIFY(bufferReleasedSpy.isValid()); + delete client; + QVERIFY(bufferReleasedSpy.wait()); + + // cleanup + delete buf; + m_connection->flush(); + m_display->dispatchEvents(); + QVERIFY(!m_remoteAccessInterface->isBound()); +} + +void RemoteAccessTest::testSendReceiveClientGone() +{ + // this test verifies that when buffer is sent, received and client is gone, + // both client and server will release buffer correctly + QVERIFY(!m_remoteAccessInterface->isBound()); + auto client = m_registry->createRemoteAccessManager( + m_registry->interface(Registry::Interface::RemoteAccessManager).name, + m_registry->interface(Registry::Interface::RemoteAccessManager).version, + this); + QVERIFY(client->isValid()); + m_connection->flush(); + m_display->dispatchEvents(); + + QVERIFY(m_remoteAccessInterface->isBound()); // we have one client now + QSignalSpy bufferReadySpy(client, &RemoteAccessManager::bufferReady); + QVERIFY(bufferReadySpy.isValid()); + + BufferHandle *buf = new BufferHandle(); + QTemporaryFile *tmpFile = new QTemporaryFile(this); + tmpFile->open(); + + buf->setFd(tmpFile->handle()); + buf->setSize(50, 50); + buf->setFormat(100500); + buf->setStride(7800); + m_remoteAccessInterface->sendBufferReady(m_outputInterface, buf); + + // receive buffer + QVERIFY(bufferReadySpy.wait()); + auto rbuf = bufferReadySpy.takeFirst()[1].value(); + QSignalSpy paramsObtainedSpy(rbuf, &RemoteBuffer::parametersObtained); + QVERIFY(paramsObtainedSpy.isValid()); + + // wait for params + QVERIFY(paramsObtainedSpy.wait()); + // client fd is different, not subject to check + QCOMPARE(rbuf->width(), 50u); + QCOMPARE(rbuf->height(), 50u); + QCOMPARE(rbuf->format(), 100500u); + QCOMPARE(rbuf->stride(), 7800u); + + // release forcefully + QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased); + QVERIFY(bufferReleasedSpy.isValid()); + delete client; + QVERIFY(bufferReleasedSpy.wait()); + + // cleanup + delete buf; + m_connection->flush(); + m_display->dispatchEvents(); + QVERIFY(!m_remoteAccessInterface->isBound()); +} + + +QTEST_GUILESS_MAIN(RemoteAccessTest) +#include "test_remote_access.moc" diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -24,6 +24,7 @@ idle.cpp idleinhibit.cpp keyboard.cpp + remote_access.cpp outputconfiguration.cpp outputmanagement.cpp outputdevice.cpp @@ -193,6 +194,11 @@ set_source_files_properties(${CLIENT_GENERATED_FILES} PROPERTIES SKIP_AUTOMOC ON) +ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/remote-access.xml + BASENAME remote-access +) + add_library(KF5WaylandClient ${CLIENT_LIB_SRCS}) generate_export_header(KF5WaylandClient BASE_NAME @@ -236,6 +242,7 @@ idle.h idleinhibit.h keyboard.h + remote_access.h outputconfiguration.h outputmanagement.h outputdevice.h diff --git a/src/client/output.cpp b/src/client/output.cpp --- a/src/client/output.cpp +++ b/src/client/output.cpp @@ -44,7 +44,7 @@ ~Private(); void setup(wl_output *o); - WaylandPointer output; + WaylandPointer output; EventQueue *queue = nullptr; QSize physicalSize; QPoint globalPosition; diff --git a/src/client/protocols/remote-access.xml b/src/client/protocols/remote-access.xml new file mode 100644 --- /dev/null +++ b/src/client/protocols/remote-access.xml @@ -0,0 +1,49 @@ + + + . + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/client/registry.h b/src/client/registry.h --- a/src/client/registry.h +++ b/src/client/registry.h @@ -42,6 +42,7 @@ struct org_kde_kwin_outputdevice; struct org_kde_kwin_fake_input; struct org_kde_kwin_idle; +struct org_kde_kwin_remote_access_manager; struct org_kde_kwin_dpms_manager; struct org_kde_kwin_shadow_manager; struct org_kde_kwin_blur_manager; @@ -77,6 +78,7 @@ class OutputDevice; class Idle; class IdleInhibitManager; +class RemoteAccessManager; class Output; class PlasmaShell; class PlasmaWindowManagement; @@ -169,7 +171,8 @@ XdgShellUnstableV6, ///< Refers to zxdg_shell_v6 (unstable version 6), @since 5.39 IdleInhibitManagerUnstableV1, ///< Refers to zwp_idle_inhibit_manager_v1 (unstable version 1), @since 5.41 AppMenu, ///Refers to org_kde_kwin_appmenu @since 5.42 - ServerSideDecorationPalette ///Refers to org_kde_kwin_server_decoration_palette_manager @since 5.42 + ServerSideDecorationPalette, ///Refers to org_kde_kwin_server_decoration_palette_manager @since 5.42 + RemoteAccessManager ///< Refers to org_kde_kwin_remote_access_manager interface, @since 5.43 }; explicit Registry(QObject *parent = nullptr); virtual ~Registry(); @@ -404,6 +407,16 @@ * @since 5.4 **/ org_kde_kwin_idle *bindIdle(uint32_t name, uint32_t version) const; + /** + * Binds the org_kde_kwin_remote_access_manager with @p name and @p version. + * If the @p name does not exist or is not for the idle interface, + * @c null will be returned. + * + * Prefer using createRemoteAccessManager instead. + * @see createRemoteAccessManager + * @since 5.23 + **/ + org_kde_kwin_remote_access_manager *bindRemoteAccessManager(uint32_t name, uint32_t version) const; /** * Binds the org_kde_kwin_fake_input with @p name and @p version. * If the @p name does not exist or is not for the fake input interface, @@ -805,6 +818,22 @@ * @since 5.4 **/ Idle *createIdle(quint32 name, quint32 version, QObject *parent = nullptr); + /** + * Creates a RemoteAccessManager and sets it up to manage the interface identified by + * @p name and @p version. + * + * Note: in case @p name is invalid or isn't for the org_kde_kwin_remote_access_manager interface, + * the returned RemoteAccessManager will not be valid. Therefore it's recommended to call + * isValid on the created instance. + * + * @param name The name of the org_kde_kwin_remote_access_manager interface to bind + * @param version The version or the org_kde_kwin_remote_access_manager interface to use + * @param parent The parent for RemoteAccessManager + * + * @returns The created RemoteAccessManager. + * @since 5.23 + **/ + RemoteAccessManager *createRemoteAccessManager(quint32 name, quint32 version, QObject *parent = nullptr); /** * Creates a FakeInput and sets it up to manage the interface identified by * @p name and @p version. @@ -1186,6 +1215,13 @@ * @since 5.4 **/ void idleAnnounced(quint32 name, quint32 version); + /** + * Emitted whenever a org_kde_kwin_remote_access_manager interface gets announced. + * @param name The name for the announced interface + * @param version The maximum supported version of the announced interface + * @since 5.23 + **/ + void remoteAccessManagerAnnounced(quint32 name, quint32 version); /** * Emitted whenever a org_kde_kwin_fake_input interface gets announced. * @param name The name for the announced interface @@ -1402,6 +1438,12 @@ * @since 5.4 **/ void idleRemoved(quint32 name); + /** + * Emitted whenever a org_kde_kwin_remote_access_manager interface gets removed. + * @param name The name for the removed interface + * @since 5.23 + **/ + void remoteAccessManagerRemoved(quint32 name); /** * Emitted whenever a org_kde_kwin_fake_input interface gets removed. * @param name The name for the removed interface diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -27,6 +27,7 @@ #include "fullscreen_shell.h" #include "idle.h" #include "idleinhibit.h" +#include "remote_access.h" #include "logging_p.h" #include "outputconfiguration.h" #include "outputmanagement.h" @@ -62,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -124,7 +126,7 @@ &Registry::dataDeviceManagerRemoved }}, {Registry::Interface::Output, { - 2, + 3, QByteArrayLiteral("wl_output"), &wl_output_interface, &Registry::outputAnnounced, @@ -179,6 +181,13 @@ &Registry::idleAnnounced, &Registry::idleRemoved }}, + {Registry::Interface::RemoteAccessManager, { + 1, + QByteArrayLiteral("org_kde_kwin_remote_access_manager"), + &org_kde_kwin_remote_access_manager_interface, + &Registry::remoteAccessManagerAnnounced, + &Registry::remoteAccessManagerRemoved + }}, {Registry::Interface::FakeInput, { 2, QByteArrayLiteral("org_kde_kwin_fake_input"), @@ -622,6 +631,7 @@ BIND(PlasmaShell, org_kde_plasma_shell) BIND(PlasmaWindowManagement, org_kde_plasma_window_management) BIND(Idle, org_kde_kwin_idle) +BIND(RemoteAccessManager, org_kde_kwin_remote_access_manager) BIND(FakeInput, org_kde_kwin_fake_input) BIND(OutputManagement, org_kde_kwin_outputmanagement) BIND(OutputDevice, org_kde_kwin_outputdevice) @@ -682,6 +692,7 @@ CREATE(PlasmaShell) CREATE(PlasmaWindowManagement) CREATE(Idle) +CREATE(RemoteAccessManager) CREATE(FakeInput) CREATE(OutputManagement) CREATE(OutputDevice) diff --git a/src/client/remote_access.h b/src/client/remote_access.h new file mode 100644 --- /dev/null +++ b/src/client/remote_access.h @@ -0,0 +1,208 @@ +/**************************************************************************** +Copyright 2016 Oleg Chernovskiy + +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 . +****************************************************************************/ +#ifndef KWAYLAND_CLIENT_REMOTE_ACCESS_H +#define KWAYLAND_CLIENT_REMOTE_ACCESS_H + +#include + +#include + +struct org_kde_kwin_remote_access_manager; +struct org_kde_kwin_remote_buffer; +struct wl_output; + +namespace KWayland +{ +namespace Client +{ + +class EventQueue; +class RemoteBuffer; + +/** + * @short Wrapper for the org_kde_kwin_remote_access_manager interface. + * + * This class provides a convenient wrapper for the org_kde_kwin_remote_access_manager interface. + * + * To use this class one needs to interact with the Registry. There are two + * possible ways to create the RemoteAccessManager interface: + * @code + * RemoteAccessManager *c = registry->createRemoteAccessManager(name, version); + * @endcode + * + * This creates the RemoteAccessManager and sets it up directly. As an alternative this + * can also be done in a more low level way: + * @code + * RemoteAccessManager *c = new RemoteAccessManager; + * c->setup(registry->bindRemoteAccessManager(name, version)); + * @endcode + * + * The RemoteAccessManager can be used as a drop-in replacement for any org_kde_kwin_remote_access_manager + * pointer as it provides matching cast operators. + * + * @see Registry + **/ +class KWAYLANDCLIENT_EXPORT RemoteAccessManager : public QObject +{ + Q_OBJECT +public: + /** + * Creates a new RemoteAccessManager. + * Note: after constructing the RemoteAccessManager it is not yet valid and one needs + * to call setup. In order to get a ready to use RemoteAccessManager prefer using + * Registry::createRemoteAccessManager. + **/ + explicit RemoteAccessManager(QObject *parent = nullptr); + virtual ~RemoteAccessManager(); + + /** + * Setup this RemoteAccessManager to manage the @p remoteaccessmanager. + * When using Registry::createRemoteAccessManager there is no need to call this + * method. + **/ + void setup(org_kde_kwin_remote_access_manager *remoteaccessmanager); + /** + * @returns @c true if managing a org_kde_kwin_remote_access_manager. + **/ + bool isValid() const; + /** + * Releases the org_kde_kwin_remote_access_manager interface. + * After the interface has been released the RemoteAccessManager instance is no + * longer valid and can be setup with another org_kde_kwin_remote_access_manager interface. + **/ + void release(); + /** + * Destroys the data held by this RemoteAccessManager. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new org_kde_kwin_remote_access_manager interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, remoteaccessmanager, &RemoteAccessManager::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + /** + * Sets the @p queue to use for creating objects with this RemoteAccessManager. + **/ + void setEventQueue(EventQueue *queue); + /** + * @returns The event queue to use for creating objects with this RemoteAccessManager. + **/ + EventQueue *eventQueue(); + + operator org_kde_kwin_remote_access_manager*(); + operator org_kde_kwin_remote_access_manager*() const; + +Q_SIGNALS: + /** + * The corresponding global for this interface on the Registry got removed. + * + * This signal gets only emitted if the RemoteAccessManager got created by + * Registry::createRemoteAccessManager + **/ + void removed(); + + /** + * Buffer from server is ready to be delivered to this client + * @param buffer_id internal buffer id to be created + **/ + void bufferReady(const void* output, const RemoteBuffer *rbuf); + +private: + class Private; + QScopedPointer d; +}; + +/** + * @short Wrapper for org_kde_kwin_remote_buffer interface. + * The instances of this class are created by parent RemoteAccessManager. + * Deletion (by noLongerNeeded call) is in responsibility of underlying system. + */ +class KWAYLANDCLIENT_EXPORT RemoteBuffer : public QObject +{ + Q_OBJECT +public: + virtual ~RemoteBuffer(); + /** + * Setup this RemoteBuffer to manage the @p remotebuffer. + **/ + void setup(org_kde_kwin_remote_buffer *remotebuffer); + /** + * @returns @c true if managing a org_kde_kwin_remote_buffer. + **/ + bool isValid() const; + /** + * Releases the org_kde_kwin_remote_buffer interface. + * After the interface has been released the RemoteBuffer instance is no + * longer valid and can be setup with another org_kde_kwin_remote_buffer interface. + **/ + void release(); + /** + * Destroys the data held by this RemoteBuffer. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new org_kde_kwin_remote_buffer interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, remotebuffer, &RemoteBuffer::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + operator org_kde_kwin_remote_buffer*(); + operator org_kde_kwin_remote_buffer*() const; + + qint32 fd() const; + quint32 width() const; + quint32 height() const; + quint32 stride() const; + quint32 format() const; + + +Q_SIGNALS: + void parametersObtained(); + +private: + + friend class RemoteAccessManager; + explicit RemoteBuffer(QObject *parent = nullptr); + class Private; + QScopedPointer d; +}; + + +} +} + +#endif diff --git a/src/client/remote_access.cpp b/src/client/remote_access.cpp new file mode 100644 --- /dev/null +++ b/src/client/remote_access.cpp @@ -0,0 +1,246 @@ +/**************************************************************************** +Copyright 2016 Oleg Chernovskiy + +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 . +****************************************************************************/ +#include "remote_access.h" +#include "event_queue.h" +#include "wayland_pointer_p.h" +#include "logging_p.h" +// Wayland +#include + +namespace KWayland +{ +namespace Client +{ + +class RemoteAccessManager::Private +{ +public: + explicit Private(RemoteAccessManager *ram); + void setup(org_kde_kwin_remote_access_manager *k); + + WaylandPointer ram; + EventQueue *queue = nullptr; +private: + static const struct org_kde_kwin_remote_access_manager_listener s_listener; + static void bufferReadyCallback(void *data, org_kde_kwin_remote_access_manager *interface, qint32 buffer_id, wl_output *output); + + RemoteAccessManager *q; +}; + +RemoteAccessManager::Private::Private(RemoteAccessManager *q) + : q(q) +{ +} + +const org_kde_kwin_remote_access_manager_listener RemoteAccessManager::Private::s_listener = { + bufferReadyCallback +}; + +void RemoteAccessManager::Private::bufferReadyCallback(void *data, org_kde_kwin_remote_access_manager *interface, qint32 buffer_id, wl_output *output) +{ + auto ramp = reinterpret_cast(data); + Q_ASSERT(ramp->ram == interface); + + // handle it fully internally, get the buffer immediately + auto requested = org_kde_kwin_remote_access_manager_get_buffer(ramp->ram, buffer_id); + auto rbuf = new RemoteBuffer(ramp->q); + rbuf->setup(requested); + qCDebug(KWAYLAND_CLIENT) << "Got buffer, server fd:" << buffer_id; + + emit ramp->q->bufferReady(output, rbuf); +} + +void RemoteAccessManager::Private::setup(org_kde_kwin_remote_access_manager *k) +{ + Q_ASSERT(k); + Q_ASSERT(!ram); + ram.setup(k); + org_kde_kwin_remote_access_manager_add_listener(k, &s_listener, this); +} + +RemoteAccessManager::RemoteAccessManager(QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ +} + +RemoteAccessManager::~RemoteAccessManager() +{ + release(); +} + +void RemoteAccessManager::setup(org_kde_kwin_remote_access_manager *ram) +{ + d->setup(ram); +} + +void RemoteAccessManager::release() +{ + d->ram.release(); +} + +void RemoteAccessManager::destroy() +{ + d->ram.destroy(); +} + +void RemoteAccessManager::setEventQueue(EventQueue *queue) +{ + d->queue = queue; +} + +EventQueue *RemoteAccessManager::eventQueue() +{ + return d->queue; +} + +RemoteAccessManager::operator org_kde_kwin_remote_access_manager*() { + return d->ram; +} + +RemoteAccessManager::operator org_kde_kwin_remote_access_manager*() const { + return d->ram; +} + +bool RemoteAccessManager::isValid() const +{ + return d->ram.isValid(); +} + +class RemoteBuffer::Private +{ +public: + Private(RemoteBuffer *q); + void setup(org_kde_kwin_remote_buffer *buffer); + + static struct org_kde_kwin_remote_buffer_listener s_listener; + static void paramsCallback(void *data, org_kde_kwin_remote_buffer *rbuf, + qint32 fd, quint32 width, quint32 height, quint32 stride, quint32 format); + + WaylandPointer remotebuffer; + RemoteBuffer *q; + + qint32 fd = 0; + quint32 width = 0; + quint32 height = 0; + quint32 stride = 0; + quint32 format = 0; +}; + +RemoteBuffer::Private::Private(RemoteBuffer *q) + : q(q) +{ +} + +void RemoteBuffer::Private::paramsCallback(void *data, org_kde_kwin_remote_buffer *rbuf, + qint32 fd, quint32 width, quint32 height, quint32 stride, quint32 format) +{ + Q_UNUSED(rbuf) + Private *p = reinterpret_cast(data); + p->fd = fd; + p->width = width; + p->height = height; + p->stride = stride; + p->format = format; + emit p->q->parametersObtained(); +} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +org_kde_kwin_remote_buffer_listener RemoteBuffer::Private::s_listener = { + paramsCallback +}; +#endif + +void RemoteBuffer::Private::setup(org_kde_kwin_remote_buffer *rbuffer) +{ + remotebuffer.setup(rbuffer); + org_kde_kwin_remote_buffer_add_listener(rbuffer, &s_listener, this); +} + +RemoteBuffer::RemoteBuffer(QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ +} + +RemoteBuffer::~RemoteBuffer() +{ + release(); + qCDebug(KWAYLAND_CLIENT) << "Buffer released"; +} + +void RemoteBuffer::setup(org_kde_kwin_remote_buffer *remotebuffer) +{ + Q_ASSERT(remotebuffer); + Q_ASSERT(!d->remotebuffer); + d->setup(remotebuffer); +} + +void RemoteBuffer::release() +{ + d->remotebuffer.release(); +} + +void RemoteBuffer::destroy() +{ + d->remotebuffer.destroy(); +} + +RemoteBuffer::operator org_kde_kwin_remote_buffer*() { + return d->remotebuffer; +} + +RemoteBuffer::operator org_kde_kwin_remote_buffer*() const { + return d->remotebuffer; +} + +bool RemoteBuffer::isValid() const +{ + return d->remotebuffer.isValid(); +} + +qint32 RemoteBuffer::fd() const +{ + return d->fd; +} + +quint32 RemoteBuffer::width() const +{ + return d->width; +} + +quint32 RemoteBuffer::height() const +{ + return d->height; +} + +quint32 RemoteBuffer::stride() const +{ + return d->stride; +} + +quint32 RemoteBuffer::format() const +{ + return d->format; +} + + +} +} diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -16,6 +16,7 @@ idleinhibit_interface_v1.cpp fakeinput_interface.cpp keyboard_interface.cpp + remote_access_interface.cpp outputconfiguration_interface.cpp outputchangeset.cpp outputmanagement_interface.cpp @@ -174,6 +175,11 @@ BASENAME server_decoration_palette ) +ecm_add_wayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/remote-access.xml + BASENAME remote-access +) + set(SERVER_GENERATED_SRCS ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-management-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-management-server-protocol.h @@ -273,6 +279,7 @@ idle_interface.h idleinhibit_interface.h keyboard_interface.h + remote_access_interface.h outputdevice_interface.h outputchangeset.h outputconfiguration_interface.h diff --git a/src/server/display.h b/src/server/display.h --- a/src/server/display.h +++ b/src/server/display.h @@ -54,6 +54,7 @@ class DpmsManagerInterface; class IdleInterface; enum class IdleInhibitManagerInterfaceVersion; +class RemoteAccessManagerInterface; class IdleInhibitManagerInterface; class FakeInputInterface; class OutputInterface; @@ -176,6 +177,7 @@ PlasmaWindowManagementInterface *createPlasmaWindowManagement(QObject *parent = nullptr); QtSurfaceExtensionInterface *createQtSurfaceExtension(QObject *parent = nullptr); IdleInterface *createIdle(QObject *parent = nullptr); + RemoteAccessManagerInterface *createRemoteAccessManager(QObject *parent = nullptr); FakeInputInterface *createFakeInput(QObject *parent = nullptr); ShadowManagerInterface *createShadowManager(QObject *parent = nullptr); BlurManagerInterface *createBlurManager(QObject *parent = nullptr); diff --git a/src/server/display.cpp b/src/server/display.cpp --- a/src/server/display.cpp +++ b/src/server/display.cpp @@ -26,6 +26,7 @@ #include "outputdevice_interface.h" #include "idle_interface.h" #include "idleinhibit_interface_p.h" +#include "remote_access_interface.h" #include "fakeinput_interface.h" #include "logging_p.h" #include "output_interface.h" @@ -292,6 +293,13 @@ return s; } +RemoteAccessManagerInterface *Display::createRemoteAccessManager(QObject *parent) +{ + auto i = new RemoteAccessManagerInterface(this, parent); + connect(this, &Display::aboutToTerminate, i, [this, i] { delete i; }); + return i; +} + IdleInterface *Display::createIdle(QObject *parent) { auto i = new IdleInterface(this, parent); diff --git a/src/server/output_interface.cpp b/src/server/output_interface.cpp --- a/src/server/output_interface.cpp +++ b/src/server/output_interface.cpp @@ -62,6 +62,7 @@ private: static Private *cast(wl_resource *native); + static void releaseCallback(wl_client *client, wl_resource *resource); static void unbind(wl_resource *resource); void bind(wl_client *client, uint32_t version, uint32_t id) override; int32_t toTransform() const; @@ -71,11 +72,12 @@ OutputInterface *q; static QVector s_privates; + static const struct wl_output_interface s_interface; static const quint32 s_version; }; QVector OutputInterface::Private::s_privates; -const quint32 OutputInterface::Private::s_version = 2; +const quint32 OutputInterface::Private::s_version = 3; OutputInterface::Private::Private(OutputInterface *q, Display *d) : Global::Private(d, &wl_output_interface, s_version) @@ -89,6 +91,18 @@ s_privates.removeAll(this); } +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct wl_output_interface OutputInterface::Private::s_interface = { + releaseCallback +}; +#endif + +void OutputInterface::Private::releaseCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client); + unbind(resource); +} + OutputInterface *OutputInterface::Private::get(wl_resource *native) { if (Private *p = cast(native)) { @@ -303,7 +317,7 @@ return; } wl_resource_set_user_data(resource, this); - wl_resource_set_destructor(resource, unbind); + wl_resource_set_implementation(resource, &s_interface, this, unbind); ResourceData r; r.resource = resource; r.version = version; diff --git a/src/server/remote_access_interface.h b/src/server/remote_access_interface.h new file mode 100644 --- /dev/null +++ b/src/server/remote_access_interface.h @@ -0,0 +1,99 @@ +/**************************************************************************** +Copyright 2016 Oleg Chernovskiy + +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 . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_REMOTE_ACCESS_H +#define KWAYLAND_SERVER_REMOTE_ACCESS_H + +#include "global.h" + +namespace KWayland +{ +namespace Server +{ + +class Display; +class OutputInterface; + +/** + * The structure server should fill to use this interface. + * Lifecycle: + * 1. BufferHandle is filled and passed to RemoteAccessManager + * (stored in manager's sent list) + * 2. Clients confirm that they wants this buffer, the RemoteBuffer + * interfaces are then created and wrapped around BufferHandle. + * 3. Once all clients are done with buffer (or disconnected), + * RemoteBuffer notifies manager and release signal is emitted. + * + * It's the responsibility of your process to delete this BufferHandle + * and release its' fd afterwards. + **/ +class KWAYLANDSERVER_EXPORT BufferHandle +{ +public: + explicit BufferHandle(); + virtual ~BufferHandle(); + void setFd(qint32 fd); + void setSize(quint32 width, quint32 height); + void setStride(quint32 stride); + void setFormat(quint32 format); + + qint32 fd() const; + quint32 height() const; + quint32 width() const; + quint32 stride() const; + quint32 format() const; +private: + + friend class RemoteAccessManagerInterface; + friend class RemoteBufferInterface; + class Private; + QScopedPointer d; +}; + +class KWAYLANDSERVER_EXPORT RemoteAccessManagerInterface : public Global +{ + Q_OBJECT +public: + virtual ~RemoteAccessManagerInterface() = default; + + /** + * Store buffer in sent list and notify client that we have a buffer for it + **/ + void sendBufferReady(const OutputInterface *output, const BufferHandle *buf); + /** + * Check whether interface has been bound + **/ + bool isBound() const; + +Q_SIGNALS: + /** + * Previously sent buffer has been released by client + */ + void bufferReleased(const BufferHandle *buf); + +private: + explicit RemoteAccessManagerInterface(Display *display, QObject *parent = nullptr); + friend class Display; + class Private; +}; + +} +} + +#endif diff --git a/src/server/remote_access_interface.cpp b/src/server/remote_access_interface.cpp new file mode 100644 --- /dev/null +++ b/src/server/remote_access_interface.cpp @@ -0,0 +1,381 @@ +/**************************************************************************** +Copyright 2016 Oleg Chernovskiy + +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 . +****************************************************************************/ +#include "output_interface.h" +#include "remote_access_interface.h" +#include "remote_access_interface_p.h" +#include "display.h" +#include "global_p.h" +#include "resource_p.h" +#include "logging_p.h" + +#include + +#include +#include + +#include + +namespace KWayland +{ +namespace Server +{ + +class BufferHandle::Private // @see gbm_import_fd_data +{ +public: + // Note that on client side received fd number will be different + // and meaningful only for client process! + // Thus we can use server-side fd as an implicit unique id + qint32 fd = 0; ///< also internal buffer id for client + quint32 width = 0; + quint32 height = 0; + quint32 stride = 0; + quint32 format = 0; +}; + +BufferHandle::BufferHandle() + : d(new Private) +{ +} + +BufferHandle::~BufferHandle() +{ +} + +void BufferHandle::setFd(qint32 fd) +{ + d->fd = fd; +} + +qint32 BufferHandle::fd() const +{ + return d->fd; +} + +void BufferHandle::setSize(quint32 width, quint32 height) +{ + d->width = width; + d->height = height; +} + +quint32 BufferHandle::width() const +{ + return d->width; +} + +quint32 BufferHandle::height() const +{ + return d->height; +} + +void BufferHandle::setStride(quint32 stride) +{ + d->stride = stride; +} + + +quint32 BufferHandle::stride() const +{ + return d->stride; +} + +void BufferHandle::setFormat(quint32 format) +{ + d->format = format; +} + +quint32 BufferHandle::format() const +{ + return d->format; +} + +/** + * @brief helper struct for manual reference counting. + * automatic counting via QSharedPointer is no-go here as we hold strong reference in sentBuffers. + */ +struct BufferHolder +{ + const BufferHandle *buf; + quint64 counter; +}; + +class RemoteAccessManagerInterface::Private : public Global::Private +{ +public: + Private(RemoteAccessManagerInterface *q, Display *d); + virtual ~Private() override; + + /** + * @brief Send buffer ready notification to all connected clients + * @param output wl_output interface to determine which screen sent this buf + * @param buf buffer containing GBM-related params + */ + void sendBufferReady(const OutputInterface *output, const BufferHandle *buf); + /** + * @brief Release all bound buffers associated with this resource + * @param resource one of bound clients + */ + void release(wl_resource *resource); + + /** + * Clients of this interface. + * This may be screenshot app, video capture app, + * remote control app etc. + */ + QList clientResources; +private: + // methods + static void unbind(wl_resource *resource); + static Private *cast(wl_resource *r) { + return reinterpret_cast(wl_resource_get_user_data(r)); + } + static void getBufferCallback(wl_client *client, wl_resource *resource, uint32_t buffer, int32_t internalBufId); + static void releaseCallback(wl_client *client, wl_resource *resource); + void bind(wl_client *client, uint32_t version, uint32_t id) override; + + /** + * @brief Unreferences counter and frees buffer when it reaches zero + * @param buf holder to decrease reference counter on + * @return true if buffer was released, false otherwise + */ + bool unref(BufferHolder &buf); + + // fields + static const struct org_kde_kwin_remote_access_manager_interface s_interface; + static const quint32 s_version; + + RemoteAccessManagerInterface *q; + + /** + * Buffers that were sent but still not acked by server + * Keys are fd numbers as they are unique + **/ + QHash sentBuffers; +}; + +const quint32 RemoteAccessManagerInterface::Private::s_version = 1; + +RemoteAccessManagerInterface::Private::Private(RemoteAccessManagerInterface *q, Display *d) + : Global::Private(d, &org_kde_kwin_remote_access_manager_interface, s_version) + , q(q) +{ +} + +void RemoteAccessManagerInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) +{ + // create new client resource + auto c = display->getConnection(client); + wl_resource *resource = c->createResource(&org_kde_kwin_remote_access_manager_interface, qMin(version, s_version), id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &s_interface, this, unbind); + + // add newly created client resource to the list + clientResources << resource; +} + +void RemoteAccessManagerInterface::Private::sendBufferReady(const OutputInterface *output, const BufferHandle *buf) +{ + BufferHolder holder {buf, 0}; + // notify clients + qCDebug(KWAYLAND_SERVER) << "Server buffer sent: fd" << buf->fd(); + for (auto res : clientResources) { + auto client = wl_resource_get_client(res); + auto boundScreens = output->clientResources(display->getConnection(client)); + + // clients don't necessarily bind outputs + if (boundScreens.isEmpty()) { + return; + } + + // no reason for client to bind wl_output multiple times, send only to first one + org_kde_kwin_remote_access_manager_send_buffer_ready(res, buf->fd(), boundScreens[0]); + holder.counter++; + } + // store buffer locally, clients will ask it later + sentBuffers[buf->fd()] = holder; +} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct org_kde_kwin_remote_access_manager_interface RemoteAccessManagerInterface::Private::s_interface = { + getBufferCallback, + releaseCallback +}; +#endif + +void RemoteAccessManagerInterface::Private::getBufferCallback(wl_client *client, wl_resource *resource, uint32_t buffer, int32_t internalBufId) +{ + Private *p = cast(resource); + + // client asks for buffer we earlier announced, we must have it + if (Q_UNLIKELY(!p->sentBuffers.contains(internalBufId))) { // no such buffer (?) + wl_resource_post_no_memory(resource); + return; + } + + BufferHolder &bh = p->sentBuffers[internalBufId]; + auto rbuf = new RemoteBufferInterface(p->q, resource, bh.buf); + rbuf->create(p->display->getConnection(client), wl_resource_get_version(resource), buffer); + if (!rbuf->resource()) { + wl_resource_post_no_memory(resource); + delete rbuf; + return; + } + + QObject::connect(rbuf, &QObject::destroyed, [p, rbuf, resource, &bh] { + if (!p->clientResources.contains(resource)) { + // remote buffer destroy confirmed after client is already gone + // all relevant buffers are already unreferenced + return; + } + + qCDebug(KWAYLAND_SERVER) << "Remote buffer returned, client" << wl_resource_get_id(resource) + << ", id" << rbuf->id() + << ", fd" << bh.buf->fd(); + if (p->unref(bh)) { + p->sentBuffers.remove(bh.buf->fd()); + } + }); + + // send buffer params + rbuf->passFd(); +} + +void RemoteAccessManagerInterface::Private::releaseCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client); + unbind(resource); +} + +bool RemoteAccessManagerInterface::Private::unref(BufferHolder &bh) +{ + bh.counter--; + if (!bh.counter) { + // no more clients using this buffer + qCDebug(KWAYLAND_SERVER) << "Buffer released, fd" << bh.buf->fd(); + emit q->bufferReleased(bh.buf); + return true; + } + + return false; +} + +void RemoteAccessManagerInterface::Private::unbind(wl_resource *resource) +{ + // we're unbinding, all sent buffers for this client are now effectively invalid + Private *p = cast(resource); + p->release(resource); +} + +void RemoteAccessManagerInterface::Private::release(wl_resource *resource) +{ + // all holders should decrement their counter as one client is gone + QMutableHashIterator itr(sentBuffers); + while (itr.hasNext()) { + BufferHolder &bh = itr.next().value(); + if (unref(bh)) { + itr.remove(); + } + } + + clientResources.removeAll(resource); +} + +RemoteAccessManagerInterface::Private::~Private() +{ + // server deletes created interfaces, release all held buffers + auto c = clientResources; // shadow copy + for (auto res : c) { + release(res); + } +} + +RemoteAccessManagerInterface::RemoteAccessManagerInterface(Display *display, QObject *parent) + : Global(new Private(this, display), parent) +{ +} + +void RemoteAccessManagerInterface::sendBufferReady(const OutputInterface *output, const BufferHandle *buf) +{ + Private *priv = reinterpret_cast(d.data()); + priv->sendBufferReady(output, buf); +} + +bool RemoteAccessManagerInterface::isBound() const +{ + Private *priv = reinterpret_cast(d.data()); + return !priv->clientResources.isEmpty(); +} + +class RemoteBufferInterface::Private : public Resource::Private +{ +public: + Private(RemoteAccessManagerInterface *ram, RemoteBufferInterface *q, wl_resource *pResource, const BufferHandle *buf); + ~Private(); + + void passFd(); + +private: + static const struct org_kde_kwin_remote_buffer_interface s_interface; + + const BufferHandle *wrapped; +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct org_kde_kwin_remote_buffer_interface RemoteBufferInterface::Private::s_interface = { + resourceDestroyedCallback +}; +#endif + +RemoteBufferInterface::Private::Private(RemoteAccessManagerInterface *ram, RemoteBufferInterface *q, wl_resource *pResource, const BufferHandle *buf) + : Resource::Private(q, ram, pResource, &org_kde_kwin_remote_buffer_interface, &s_interface), wrapped(buf) +{ +} + +RemoteBufferInterface::Private::~Private() +{ +} + +void RemoteBufferInterface::Private::passFd() +{ + org_kde_kwin_remote_buffer_send_gbm_handle(resource, wrapped->fd(), + wrapped->width(), wrapped->height(), wrapped->stride(), wrapped->format()); +} + +RemoteBufferInterface::RemoteBufferInterface(RemoteAccessManagerInterface *ram, wl_resource *pResource, const BufferHandle *buf) + : Resource(new Private(ram, this, pResource, buf), ram) +{ +} + +RemoteBufferInterface::Private *RemoteBufferInterface::d_func() const +{ + return reinterpret_cast(d.data()); +} + + +void RemoteBufferInterface::passFd() +{ + d_func()->passFd(); +} + +} +} diff --git a/src/server/remote_access_interface_p.h b/src/server/remote_access_interface_p.h new file mode 100644 --- /dev/null +++ b/src/server/remote_access_interface_p.h @@ -0,0 +1,58 @@ +/**************************************************************************** +Copyright 2016 Oleg Chernovskiy + +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 . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_REMOTE_ACCESS_P_H +#define KWAYLAND_SERVER_REMOTE_ACCESS_P_H + +#include "resource.h" + +namespace KWayland +{ +namespace Server +{ + +/** + * @class RemoteBufferInterface + * @brief Internally used class. Holds data of passed buffer and client resource. Also controls buffer lifecycle. + * @see RemoteAccessManagerInterface + */ +class RemoteBufferInterface : public Resource +{ + Q_OBJECT +public: + virtual ~RemoteBufferInterface() = default; + + /** + * Sends GBM fd to the client. + * Note that server still has to close mirror fd from its side. + **/ + void passFd(); + +private: + explicit RemoteBufferInterface(RemoteAccessManagerInterface *ram, wl_resource *pResource, const BufferHandle *buf); + friend class RemoteAccessManagerInterface; + + class Private; + Private *d_func() const; +}; + +} +} + +#endif // KWAYLAND_SERVER_REMOTE_ACCESS_P_H diff --git a/src/tools/mapping.txt b/src/tools/mapping.txt --- a/src/tools/mapping.txt +++ b/src/tools/mapping.txt @@ -60,3 +60,5 @@ zwp_confined_pointer_v1;ConfinedPointer zwp_idle_inhibit_manager_v1;IdleInhibitManager zwp_idle_inhibitor_v1;IdleInhibitor +org_kde_kwin_remote_access_manager;RemoteAccessManager +org_kde_kwin_remote_buffer;RemoteBuffer