diff --git a/autotests/client/CMakeLists.txt b/autotests/client/CMakeLists.txt --- a/autotests/client/CMakeLists.txt +++ b/autotests/client/CMakeLists.txt @@ -355,3 +355,14 @@ target_link_libraries( testPointerConstraints Qt5::Test Qt5::Gui KF5::WaylandServer KF5::WaylandClient Wayland::Client) add_test(kwayland-testPointerConstraints testPointerConstraints) ecm_mark_as_test(testPointerConstraints) + +######################################################## +# 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(kwayland-testRemoteAccess 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,353 @@ +/******************************************************************** +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" +// server +#include "../../src/server/display.h" +#include "../../src/server/remote_access_interface.h" + +#include + +using namespace KWayland::Client; +using namespace KWayland::Server; + +Q_DECLARE_METATYPE(const BufferHandle *) + +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; + RemoteAccessManagerInterface *m_remoteAccessInterface = nullptr; + ConnectionThread *m_connection = nullptr; + QThread *m_thread = nullptr; + EventQueue *m_queue = nullptr; + Registry *m_registry = nullptr; +}; + +static const QString s_socketName = QStringLiteral("kwayland-test-remote-access-0"); + +void RemoteAccessTest::init() +{ + qRegisterMetaType("BufferHandle"); + + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + m_display->createShm(); + 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()); +} + +void RemoteAccessTest::cleanup() +{ +#define CLEANUP(variable) \ + if (variable) { \ + delete variable; \ + variable = nullptr; \ + } + 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(buf); + + // receive buffer + QVERIFY(bufferReadySpy.wait()); + auto rbuf = bufferReadySpy.takeFirst().first().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()); + rbuf->release(); + 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(buf); + + // wait for event loop + QVERIFY(bufferReadySpy1.wait()); + + // receive buffer at client 1 + QCOMPARE(bufferReadySpy1.size(), 1); + auto rbuf1 = bufferReadySpy1.takeFirst().first().value(); + QSignalSpy paramsObtainedSpy1(rbuf1, &RemoteBuffer::parametersObtained); + QVERIFY(paramsObtainedSpy1.isValid()); + + // receive buffer at client 2 + QCOMPARE(bufferReadySpy2.size(), 1); + auto rbuf2 = bufferReadySpy2.takeFirst().first().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()); + rbuf1->release(); + QVERIFY(!bufferReleasedSpy.wait(1000)); // one client released, second still holds buffer! + + rbuf2->release(); + 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(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(buf); + + // receive buffer + QVERIFY(bufferReadySpy.wait()); + auto rbuf = bufferReadySpy.takeFirst().first().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 @@ -22,6 +22,7 @@ fullscreen_shell.cpp idle.cpp keyboard.cpp + remote_access.cpp outputconfiguration.cpp outputmanagement.cpp outputdevice.cpp @@ -139,6 +140,11 @@ BASENAME pointer-constraints-unstable-v1 ) +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 @@ -180,6 +186,7 @@ fullscreen_shell.h idle.h keyboard.h + remote_access.h outputconfiguration.h outputmanagement.h outputdevice.h diff --git a/src/client/fakeinput.h b/src/client/fakeinput.h --- a/src/client/fakeinput.h +++ b/src/client/fakeinput.h @@ -136,6 +136,10 @@ **/ void requestPointerMove(const QSizeF &delta); /** + * Request an absolute pointer motion to @p position. + **/ + void requestPointerMoveAbsolute(const QPointF &pos); + /** * Convenience overload. * @see requestPointerButtonPress(quint32) **/ @@ -206,9 +210,10 @@ **/ void requestTouchFrame(); + void requestKeyboardEvent(const char *type, quint32 key, quint32 state); + operator org_kde_kwin_fake_input*(); operator org_kde_kwin_fake_input*() const; - Q_SIGNALS: /** * The corresponding global for this interface on the Registry got removed. diff --git a/src/client/fakeinput.cpp b/src/client/fakeinput.cpp --- a/src/client/fakeinput.cpp +++ b/src/client/fakeinput.cpp @@ -100,6 +100,19 @@ org_kde_kwin_fake_input_pointer_motion(d->manager, wl_fixed_from_double(delta.width()), wl_fixed_from_double(delta.height())); } +void FakeInput::requestPointerMoveAbsolute(const QPointF &pos) +{ + Q_ASSERT(d->manager.isValid()); + org_kde_kwin_fake_input_pointer_motion_abs(d->manager, wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); +} + +void FakeInput::requestKeyboardEvent(const char *type, quint32 key, quint32 state) +{ + Q_ASSERT(d->manager.isValid()); + org_kde_kwin_fake_input_keyboard_button(d->manager, type, key, state); +} + + void FakeInput::Private::sendPointerButtonState(Qt::MouseButton button, quint32 state) { #if HAVE_LINUX_INPUT_H diff --git a/src/client/protocols/fake-input.xml b/src/client/protocols/fake-input.xml --- a/src/client/protocols/fake-input.xml +++ b/src/client/protocols/fake-input.xml @@ -16,7 +16,7 @@ You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . ]]> - + This interface allows other processes to provide fake input events. Purpose is on the one hand side to provide testing facilities like XTest on X11. @@ -83,5 +83,16 @@ A client should use this request to send touch frame event. + + + + + + + + + + + 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,48 @@ + + + . + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/client/registry.h b/src/client/registry.h --- a/src/client/registry.h +++ b/src/client/registry.h @@ -41,6 +41,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; @@ -69,6 +70,7 @@ class OutputManagement; class OutputDevice; class Idle; +class RemoteAccessManager; class Output; class PlasmaShell; class PlasmaWindowManagement; @@ -150,7 +152,8 @@ XdgShellUnstableV5, ///< Refers to xdg_shell (unstable version 5), @since 5.25 RelativePointerManagerUnstableV1, ///< Refers to zwp_relative_pointer_manager_v1, @since 5.28 PointerGesturesUnstableV1, ///< Refers to zwp_pointer_gestures_v1, @since 5.29 - PointerConstraintsUnstableV1 ///< Refers to zwp_pointer_constraints_v1, @since 5.29 + PointerConstraintsUnstableV1, ///< Refers to zwp_pointer_constraints_v1, @since 5.29 + RemoteAccessManager ///< Refers to org_kde_kwin_remote_access_manager interface, @since 5.29 }; explicit Registry(QObject *parent = nullptr); virtual ~Registry(); @@ -386,6 +389,16 @@ **/ 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, * @c null will be returned. @@ -723,6 +736,22 @@ **/ 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. * @@ -1024,6 +1053,13 @@ **/ 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 * @param version The maximum supported version of the announced interface @@ -1190,6 +1226,12 @@ **/ 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 * @since 5.4 diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -26,6 +26,7 @@ #include "fakeinput.h" #include "fullscreen_shell.h" #include "idle.h" +#include "remote_access.h" #include "logging_p.h" #include "outputconfiguration.h" #include "outputmanagement.h" @@ -57,6 +58,7 @@ #include #include #include +#include #include #include #include @@ -170,8 +172,15 @@ &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, + 3, QByteArrayLiteral("org_kde_kwin_fake_input"), &org_kde_kwin_fake_input_interface, &Registry::fakeInputAnnounced, @@ -569,6 +578,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) @@ -622,6 +632,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,207 @@ +/**************************************************************************** +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; + +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(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(); + quint32 width(); + quint32 height(); + quint32 stride(); + quint32 format(); + + +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 *org_kde_kwin_remoteaccess, quint32 buffer_id); + + 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, quint32 buffer_id) +{ + 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(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() +{ + return d->fd; +} + +quint32 RemoteBuffer::width() +{ + return d->width; +} + +quint32 RemoteBuffer::height() +{ + return d->height; +} + +quint32 RemoteBuffer::stride() +{ + return d->stride; +} + +quint32 RemoteBuffer::format() +{ + 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 @@ -12,6 +12,7 @@ idle_interface.cpp fakeinput_interface.cpp keyboard_interface.cpp + remote_access_interface.cpp outputconfiguration_interface.cpp outputchangeset.cpp outputmanagement_interface.cpp @@ -141,6 +142,11 @@ BASENAME pointer-constraints-unstable-v1 ) +ecm_add_wayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${KWAYLAND_SOURCE_DIR}/src/client/protocols/remote-access.xml + BASENAME remote-access +) + add_library(KF5WaylandServer ${SERVER_LIB_SRCS}) generate_export_header(KF5WaylandServer BASE_NAME @@ -186,6 +192,7 @@ global.h idle_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 @@ -53,6 +53,7 @@ class DataDeviceManagerInterface; class DpmsManagerInterface; class IdleInterface; +class RemoteAccessManagerInterface; class FakeInputInterface; class OutputInterface; class OutputDeviceInterface; @@ -171,6 +172,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 @@ -25,6 +25,7 @@ #include "outputmanagement_interface.h" #include "outputdevice_interface.h" #include "idle_interface.h" +#include "remote_access_interface.h" #include "fakeinput_interface.h" #include "logging_p.h" #include "output_interface.h" @@ -287,6 +288,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/fakeinput_interface.h b/src/server/fakeinput_interface.h --- a/src/server/fakeinput_interface.h +++ b/src/server/fakeinput_interface.h @@ -114,6 +114,10 @@ **/ void pointerMotionRequested(const QSizeF &delta); /** + * Request a pointer motion to @p absolute location. + **/ + void pointerMotionAbsoluteRequested(const QPointF &pos); + /** * Requests a pointer button pressed for @p button. **/ void pointerButtonPressRequested(quint32 button); @@ -156,6 +160,8 @@ **/ void touchFrameRequested(); + void keyboardEvent(quint32 keySym, bool state); + private: friend class FakeInputInterface; FakeInputDevice(wl_resource *resource, FakeInputInterface *parent); diff --git a/src/server/fakeinput_interface.cpp b/src/server/fakeinput_interface.cpp --- a/src/server/fakeinput_interface.cpp +++ b/src/server/fakeinput_interface.cpp @@ -42,6 +42,7 @@ void bind(wl_client *client, uint32_t version, uint32_t id) override; static void authenticateCallback(wl_client *client, wl_resource *resource, const char *application, const char *reason); static void pointerMotionCallback(wl_client *client, wl_resource *resource, wl_fixed_t delta_x, wl_fixed_t delta_y); + static void pointerMotionAbsoluteCallback(wl_client *client, wl_resource *resource, wl_fixed_t x, wl_fixed_t y); static void buttonCallback(wl_client *client, wl_resource *resource, uint32_t button, uint32_t state); static void axisCallback(wl_client *client, wl_resource *resource, uint32_t axis, wl_fixed_t value); static void touchDownCallback(wl_client *client, wl_resource *resource, quint32 id, wl_fixed_t x, wl_fixed_t y); @@ -49,6 +50,7 @@ static void touchUpCallback(wl_client *client, wl_resource *resource, quint32 id); static void touchCancelCallback(wl_client *client, wl_resource *resource); static void touchFrameCallback(wl_client *client, wl_resource *resource); + static void keyboardButtonCallback(wl_client *client, wl_resource *resource, const char *type, uint32_t key, uint32_t state); static void unbind(wl_resource *resource); static Private *cast(wl_resource *r) { @@ -74,7 +76,7 @@ FakeInputDevice *q; }; -const quint32 FakeInputInterface::Private::s_version = 2; +const quint32 FakeInputInterface::Private::s_version = 3; QList FakeInputInterface::Private::touchIds = QList(); #ifndef DOXYGEN_SHOULD_SKIP_THIS @@ -87,7 +89,9 @@ touchMotionCallback, touchUpCallback, touchCancelCallback, - touchFrameCallback + touchFrameCallback, + pointerMotionAbsoluteCallback, + keyboardButtonCallback }; #endif @@ -149,6 +153,17 @@ emit d->pointerMotionRequested(QSizeF(wl_fixed_to_double(delta_x), wl_fixed_to_double(delta_y))); } +void FakeInputInterface::Private::pointerMotionAbsoluteCallback(wl_client *client, wl_resource *resource, wl_fixed_t x, wl_fixed_t y) +{ + Q_UNUSED(client) + FakeInputDevice *d = device(resource); + if (!d || !d->isAuthenticated()) { + return; + } + emit d->pointerMotionAbsoluteRequested(QPointF(wl_fixed_to_double(x), wl_fixed_to_double(y))); +} + + void FakeInputInterface::Private::axisCallback(wl_client *client, wl_resource *resource, uint32_t axis, wl_fixed_t value) { Q_UNUSED(client) @@ -253,6 +268,22 @@ emit d->touchFrameRequested(); } +void FakeInputInterface::Private::keyboardButtonCallback(wl_client *client, wl_resource *resource, const char *type, uint32_t key, uint32_t state) +{ + Q_UNUSED(client) + FakeInputDevice *d = device(resource); + if (!d || !d->isAuthenticated()) { + return; + } + + if (QByteArray(type) != QByteArrayLiteral("xkb")) { + return; + } + + emit d->keyboardEvent(key, state == 0 ? true : false); + +} + FakeInputInterface::FakeInputInterface(Display *display, QObject *parent) : Global(new Private(this, display), parent) { 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,98 @@ +/**************************************************************************** +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; + +/** + * 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(qint32 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 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,370 @@ +/**************************************************************************** +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_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(qint32 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; + quint32 counter; +}; + +class RemoteAccessManagerInterface::Private : public Global::Private +{ +public: + Private(RemoteAccessManagerInterface *q, Display *d); + virtual ~Private(); + + /** + * @brief Send buffer ready notification to all connected clients + * @param buf buffer containing GBM-related params + */ + void sendBufferReady(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 clients; +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, uint32_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 + clients << resource; +} + +void RemoteAccessManagerInterface::Private::sendBufferReady(const BufferHandle *buf) +{ + BufferHolder holder {buf, 0}; + // notify clients + qCDebug(KWAYLAND_SERVER) << "Server buffer sent: fd" << buf->fd(); + for (auto res : clients) { + org_kde_kwin_remote_access_manager_send_buffer_ready(res, buf->fd()); + 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, uint32_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->clients.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(); + } + } + + clients.removeAll(resource); +} + +RemoteAccessManagerInterface::Private::~Private() +{ + // server deletes created interfaces, release all held buffers + auto c = clients; // shadow copy + for (auto res : c) { + release(res); + } +} + +RemoteAccessManagerInterface::RemoteAccessManagerInterface(Display *display, QObject *parent) + : Global(new Private(this, display), parent) +{ +} + +void RemoteAccessManagerInterface::sendBufferReady(const BufferHandle *buf) +{ + Private *priv = reinterpret_cast(d.data()); + priv->sendBufferReady(buf); +} + +bool RemoteAccessManagerInterface::isBound() const +{ + Private *priv = reinterpret_cast(d.data()); + return !priv->clients.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 @@ -55,3 +55,5 @@ zwp_pointer_constraints_v1;PointerConstraints zwp_locked_pointer_v1;LockedPointer zwp_confined_pointer_v1;ConfinedPointer +org_kde_kwin_remote_access_manager;RemoteAccessManager +org_kde_kwin_remote_buffer;RemoteBuffer diff --git a/src/tools/testserver/testserver.cpp b/src/tools/testserver/testserver.cpp --- a/src/tools/testserver/testserver.cpp +++ b/src/tools/testserver/testserver.cpp @@ -98,6 +98,13 @@ m_seat->setPointerPos(m_cursorPos); } ); + connect(device, &FakeInputDevice::pointerMotionAbsoluteRequested, this, + [this] (const QPointF &pos) { + m_seat->setTimestamp(m_timeSinceStart->elapsed()); + m_cursorPos = pos; + m_seat->setPointerPos(m_cursorPos); + } + ); connect(device, &FakeInputDevice::pointerButtonPressRequested, this, [this] (quint32 button) { m_seat->setTimestamp(m_timeSinceStart->elapsed());