diff --git a/autotests/client/test_remote_access.cpp b/autotests/client/test_remote_access.cpp index 9a17ac8..f97629f 100644 --- a/autotests/client/test_remote_access.cpp +++ b/autotests/client/test_remote_access.cpp @@ -1,369 +1,487 @@ /******************************************************************** Copyright 2016 Oleg Chernovskiy +Copyright 2018 Roman Gilg 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 testSendReleaseCrossScreen(); void testSendClientGone(); void testSendReceiveClientGone(); private: Display *m_display = nullptr; - OutputInterface *m_outputInterface = nullptr; + OutputInterface *m_outputInterface[2] = {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; +}; + +class MockupClient : public QObject +{ + Q_OBJECT +public: + MockupClient(QObject *parent = nullptr); + ~MockupClient(); + + void bindOutput(int index); + + ConnectionThread *connection = nullptr; + QThread *thread = nullptr; + EventQueue *queue = nullptr; + Registry *registry = nullptr; + RemoteAccessManager *remoteAccess = nullptr; + Output *outputs[2] = {nullptr}; }; static const QString s_socketName = QStringLiteral("kwayland-test-remote-access-0"); +MockupClient::MockupClient(QObject *parent) + : QObject(parent) +{ + // setup connection + connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(connection, &ConnectionThread::connected); + QVERIFY(connectedSpy.isValid()); + connection->setSocketName(s_socketName); + + thread = new QThread(this); + connection->moveToThread(thread); + thread->start(); + + connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + queue = new EventQueue(this); + queue->setup(connection); + + registry = new Registry(this); + QSignalSpy interfacesAnnouncedSpy(registry, &Registry::interfacesAnnounced); + QVERIFY(interfacesAnnouncedSpy.isValid()); + registry->setEventQueue(queue); + registry->create(connection); + QVERIFY(registry->isValid()); + registry->setup(); + QVERIFY(interfacesAnnouncedSpy.wait()); + + remoteAccess = registry->createRemoteAccessManager( + registry->interface(Registry::Interface::RemoteAccessManager).name, + registry->interface(Registry::Interface::RemoteAccessManager).version, + this); + QVERIFY(remoteAccess->isValid()); + connection->flush(); +} + +MockupClient::~MockupClient() +{ +#define CLEANUP(variable) \ + if (variable) { \ + delete variable; \ + variable = nullptr; \ + } + CLEANUP(outputs[0]) + CLEANUP(outputs[1]) + CLEANUP(remoteAccess) + CLEANUP(queue) + CLEANUP(registry) + if (thread) { + if (connection) { + connection->flush(); + connection->deleteLater(); + connection = nullptr; + } + + thread->quit(); + thread->wait(); + delete thread; + thread = nullptr; + } +#undef CLEANUP +} + +void MockupClient::bindOutput(int index) +{ + // client-bound output + outputs[index] = registry->createOutput(registry->interfaces(Registry::Interface::Output)[index].name, + registry->interfaces(Registry::Interface::Output)[index].version, + this); + QVERIFY(outputs[index]->isValid()); + connection->flush(); +} + 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(); + + auto initOutputIface = [this](int i) { + m_outputInterface[i] = m_display->createOutput(); + m_outputInterface[i]->create(); + }; + initOutputIface(0); + initOutputIface(1); + 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_outputInterface[0]) + CLEANUP(m_outputInterface[1]) 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(); + auto *client = new MockupClient(this); + client->bindOutput(0); + m_display->dispatchEvents(); QVERIFY(m_remoteAccessInterface->isBound()); // we have one client now - QSignalSpy bufferReadySpy(client, &RemoteAccessManager::bufferReady); + QSignalSpy bufferReadySpy(client->remoteAccess, &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); + m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], 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(); + auto *client1 = new MockupClient(this); + auto *client2 = new MockupClient(this); + client1->bindOutput(0); + client2->bindOutput(0); m_display->dispatchEvents(); - QVERIFY(m_remoteAccessInterface->isBound()); // now we have 2 clients - QSignalSpy bufferReadySpy1(client1, &RemoteAccessManager::bufferReady); + + QSignalSpy bufferReadySpy1(client1->remoteAccess, &RemoteAccessManager::bufferReady); QVERIFY(bufferReadySpy1.isValid()); - QSignalSpy bufferReadySpy2(client2, &RemoteAccessManager::bufferReady); + QSignalSpy bufferReadySpy2(client2->remoteAccess, &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); + m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf); // wait for event loop QVERIFY(bufferReadySpy1.wait()); + if (bufferReadySpy2.size() == 0) { + QVERIFY(bufferReadySpy2.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); + if (paramsObtainedSpy2.size() == 0) { + QVERIFY(paramsObtainedSpy2.wait()); + } 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::testSendReleaseCrossScreen() +{ + // this test verifies that multiple buffers for multiple screens are sent to + // multiple clients and returned back + + // setup + QVERIFY(!m_remoteAccessInterface->isBound()); + auto *client1 = new MockupClient(this); + auto *client2 = new MockupClient(this); + client1->bindOutput(1); + client2->bindOutput(0); + m_display->dispatchEvents(); + QVERIFY(m_remoteAccessInterface->isBound()); // now we have 2 clients + + QSignalSpy bufferReadySpy1(client1->remoteAccess, &RemoteAccessManager::bufferReady); + QVERIFY(bufferReadySpy1.isValid()); + QSignalSpy bufferReadySpy2(client2->remoteAccess, &RemoteAccessManager::bufferReady); + QVERIFY(bufferReadySpy2.isValid()); + + BufferHandle *buf1 = new BufferHandle(); + QTemporaryFile *tmpFile1 = new QTemporaryFile(this); + tmpFile1->open(); + + BufferHandle *buf2 = new BufferHandle(); + QTemporaryFile *tmpFile2 = new QTemporaryFile(this); + tmpFile2->open(); + + buf1->setFd(tmpFile1->handle()); + buf1->setSize(50, 50); + buf1->setFormat(100500); + buf1->setStride(7800); + + buf2->setFd(tmpFile2->handle()); + buf2->setSize(100, 100); + buf2->setFormat(100500); + buf2->setStride(7800); + + m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], buf1); + m_remoteAccessInterface->sendBufferReady(m_outputInterface[1], buf2); + + // wait for event loop + QVERIFY(bufferReadySpy1.wait()); + if (bufferReadySpy2.size() == 0) { + QVERIFY(bufferReadySpy2.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()); + if (paramsObtainedSpy2.size() == 0) { + QVERIFY(paramsObtainedSpy2.wait()); + } + QCOMPARE(paramsObtainedSpy1.size(), 1); + QCOMPARE(paramsObtainedSpy2.size(), 1); + + // release + QSignalSpy bufferReleasedSpy(m_remoteAccessInterface, &RemoteAccessManagerInterface::bufferReleased); + QVERIFY(bufferReleasedSpy.isValid()); + delete rbuf1; + QVERIFY(bufferReleasedSpy.wait()); + + delete rbuf2; + QVERIFY(bufferReleasedSpy.wait()); + + QCOMPARE(bufferReleasedSpy.size(), 2); + + // cleanup + delete buf1; + delete buf2; + delete client1; + delete client2; 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(); + auto *client = new MockupClient(this); + client->bindOutput(0); m_display->dispatchEvents(); QVERIFY(m_remoteAccessInterface->isBound()); // we have one client now - QSignalSpy bufferReadySpy(client, &RemoteAccessManager::bufferReady); + QSignalSpy bufferReadySpy(client->remoteAccess, &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); + m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], 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(); + auto *client = new MockupClient(this); + client->bindOutput(0); m_display->dispatchEvents(); QVERIFY(m_remoteAccessInterface->isBound()); // we have one client now - QSignalSpy bufferReadySpy(client, &RemoteAccessManager::bufferReady); + QSignalSpy bufferReadySpy(client->remoteAccess, &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); + m_remoteAccessInterface->sendBufferReady(m_outputInterface[0], 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/server/remote_access_interface.cpp b/src/server/remote_access_interface.cpp index f8610f6..8b96f38 100644 --- a/src/server/remote_access_interface.cpp +++ b/src/server/remote_access_interface.cpp @@ -1,380 +1,385 @@ /**************************************************************************** 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; + continue; } // 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++; } + if (holder.counter == 0) { + // buffer was not requested by any client + emit q->bufferReleased(buf); + return; + } // 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, &Resource::aboutToBeUnbound, p->q, [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(); } } }