diff --git a/src/wayland/autotests/client/test_wayland_blur.cpp b/src/wayland/autotests/client/test_wayland_blur.cpp index f6cd202ade..b2010a17fc 100644 --- a/src/wayland/autotests/client/test_wayland_blur.cpp +++ b/src/wayland/autotests/client/test_wayland_blur.cpp @@ -31,6 +31,7 @@ private Q_SLOTS: void testCreate(); void testSurfaceDestroy(); + void testGlobalDestroy(); private: KWayland::Server::Display *m_display; @@ -197,5 +198,41 @@ void TestBlur::testSurfaceDestroy() QVERIFY(blurDestroyedSpy.wait()); } + +void TestBlur::testGlobalDestroy() +{ + Registry registry; + QSignalSpy blurAnnouncedSpy(®istry, &Registry::blurAnnounced); + QSignalSpy blurRemovedSpy(®istry, &Registry::blurRemoved); + + registry.setEventQueue(m_queue); + + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + + QVERIFY(blurAnnouncedSpy.wait()); + + QSignalSpy serverSurfaceCreated(m_compositorInterface, &KWayland::Server::CompositorInterface::surfaceCreated); + QScopedPointer surface(m_compositor->createSurface()); + QVERIFY(serverSurfaceCreated.wait()); + + auto serverSurface = serverSurfaceCreated.first().first().value(); + QSignalSpy blurChanged(serverSurface, &KWayland::Server::SurfaceInterface::blurChanged); + + m_blurManagerInterface->remove(); + + // we've deleted our global, but the clients have some operations in flight as they haven't processed this yet + // when we process them, we should not throw an error and kill the client + + QScopedPointer blur(m_blurManager->createBlur(surface.data())); + blur->setRegion(m_compositor->createRegion(QRegion(0, 0, 10, 20), nullptr)); + blur->commit(); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // client finally sees the blur is gone + QVERIFY(blurRemovedSpy.wait()); +} + QTEST_GUILESS_MAIN(TestBlur) #include "test_wayland_blur.moc" diff --git a/src/wayland/server/global.cpp b/src/wayland/server/global.cpp index 2bbf79abc3..38da712d82 100644 --- a/src/wayland/server/global.cpp +++ b/src/wayland/server/global.cpp @@ -26,12 +26,14 @@ Global::Private::~Private() = default; void Global::Private::bind(wl_client *client, void *data, uint32_t version, uint32_t id) { auto d = reinterpret_cast(data); + if (!d) { + return; + } d->bind(client, version, id); } void Global::Private::create() { - Q_ASSERT(!global); global = wl_global_create(*display, m_interface, m_version, this, bind); } @@ -51,6 +53,14 @@ void Global::create() d->create(); } +void Global::remove() +{ + if (!d->global) { + return; + } + wl_global_remove(d->global); +} + void Global::destroy() { if (!d->global) { diff --git a/src/wayland/server/global.h b/src/wayland/server/global.h index e156dedf53..4a7b039218 100644 --- a/src/wayland/server/global.h +++ b/src/wayland/server/global.h @@ -53,6 +53,24 @@ public: * to the clients. **/ void create(); + + /** + * Removes the Global from the registry, but does not delete the underlying wl_global + * + * Removal of a global is racey. A client could be trying to bind at that moment. + * Typically globals are static for the lifespan of the compositor, however there are exceptions + * + * For those cases this call will remove the global from the registry, but keep the wl_global instance alive + * and handling bind requests. + * + * The compositor can then remove the Global wrapper (this object) deleting the wl_global after an arbitrary delay or + * keep it around for re-use for the duration of the compositor. + * + * See https://gitlab.freedesktop.org/wayland/wayland/issues/10 + * + * @since 5.70 + */ + void remove(); /** * Destroys the low level wl_global. Afterwards the Global is no longer shown to clients. **/