diff --git a/src/wayland/autotests/client/test_wayland_surface.cpp b/src/wayland/autotests/client/test_wayland_surface.cpp index 123e660dcc..d13db9a8f5 100644 --- a/src/wayland/autotests/client/test_wayland_surface.cpp +++ b/src/wayland/autotests/client/test_wayland_surface.cpp @@ -25,6 +25,7 @@ License along with this library. If not, see . #include "../../src/client/compositor.h" #include "../../src/client/connection_thread.h" #include "../../src/client/event_queue.h" +#include "../../src/client/output.h" #include "../../src/client/surface.h" #include "../../src/client/region.h" #include "../../src/client/registry.h" @@ -59,6 +60,7 @@ private Q_SLOTS: void testSurfaceAt(); void testDestroyAttachedBuffer(); void testDestroyWithPendingCallback(); + void testOutput(); void testDisconnect(); private: @@ -956,5 +958,78 @@ void TestWaylandSurface::testDisconnect() m_queue->destroy(); } +void TestWaylandSurface::testOutput() +{ + // This test verifies that the enter/leave are sent correctly to the Client + using namespace KWayland::Client; + using namespace KWayland::Server; + qRegisterMetaType(); + QScopedPointer s(m_compositor->createSurface()); + QVERIFY(!s.isNull()); + QVERIFY(s->isValid()); + QVERIFY(s->outputs().isEmpty()); + QSignalSpy enteredSpy(s.data(), &Surface::outputEntered); + QVERIFY(enteredSpy.isValid()); + QSignalSpy leftSpy(s.data(), &Surface::outputLeft); + QVERIFY(leftSpy.isValid()); + // wait for the surface on the Server side + QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(surfaceCreatedSpy.isValid()); + QVERIFY(surfaceCreatedSpy.wait()); + auto serverSurface = surfaceCreatedSpy.first().first().value(); + QVERIFY(serverSurface); + QCOMPARE(serverSurface->outputs(), QVector()); + + // create another registry to get notified about added outputs + Registry registry; + registry.setEventQueue(m_queue); + QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); + QVERIFY(allAnnounced.isValid()); + registry.create(m_connection); + QVERIFY(registry.isValid()); + registry.setup(); + QVERIFY(allAnnounced.wait()); + QSignalSpy outputAnnouncedSpy(®istry, &Registry::outputAnnounced); + QVERIFY(outputAnnouncedSpy.isValid()); + + auto serverOutput = m_display->createOutput(m_display); + serverOutput->create(); + QVERIFY(outputAnnouncedSpy.wait()); + QScopedPointer clientOutput(registry.createOutput(outputAnnouncedSpy.first().first().value(), outputAnnouncedSpy.first().last().value())); + QVERIFY(clientOutput->isValid()); + m_connection->flush(); + m_display->dispatchEvents(); + + // now enter it + serverSurface->setOutputs(QVector{serverOutput}); + QCOMPARE(serverSurface->outputs(), QVector{serverOutput}); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 1); + QCOMPARE(enteredSpy.first().first().value(), clientOutput.data()); + QCOMPARE(s->outputs(), QVector{clientOutput.data()}); + + // adding to same should not trigger + serverSurface->setOutputs(QVector{serverOutput}); + + // leave again + serverSurface->setOutputs(QVector()); + QCOMPARE(serverSurface->outputs(), QVector()); + QVERIFY(leftSpy.wait()); + QCOMPARE(enteredSpy.count(), 1); + QCOMPARE(leftSpy.count(), 1); + QCOMPARE(leftSpy.first().first().value(), clientOutput.data()); + QCOMPARE(s->outputs(), QVector()); + + // leave again should not trigger + serverSurface->setOutputs(QVector()); + + // and enter again, just to verify + serverSurface->setOutputs(QVector{serverOutput}); + QCOMPARE(serverSurface->outputs(), QVector{serverOutput}); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 2); + QCOMPARE(leftSpy.count(), 1); +} + QTEST_GUILESS_MAIN(TestWaylandSurface) #include "test_wayland_surface.moc" diff --git a/src/wayland/output_interface.cpp b/src/wayland/output_interface.cpp index edbe79cb87..75cd1a6f35 100644 --- a/src/wayland/output_interface.cpp +++ b/src/wayland/output_interface.cpp @@ -506,6 +506,18 @@ bool OutputInterface::isDpmsSupported() const return d->dpms.supported; } +QVector OutputInterface::clientResources(ClientConnection *client) const +{ + Q_D(); + QVector ret; + for (auto it = d->resources.constBegin(), end = d->resources.constEnd(); it != end; ++it) { + if (wl_resource_get_client((*it).resource) == client->client()) { + ret << (*it).resource; + } + } + return ret; +} + OutputInterface *OutputInterface::get(wl_resource* native) { return Private::get(native); diff --git a/src/wayland/output_interface.h b/src/wayland/output_interface.h index 17f79a7d3b..0cf9d4f952 100644 --- a/src/wayland/output_interface.h +++ b/src/wayland/output_interface.h @@ -36,6 +36,7 @@ namespace KWayland namespace Server { +class ClientConnection; class Display; /** @@ -125,6 +126,12 @@ public: **/ void setDpmsMode(DpmsMode mode); + /** + * @returns all wl_resources bound for the @p client + * @since 5.27 + **/ + QVector clientResources(ClientConnection *client) const; + static OutputInterface *get(wl_resource *native); Q_SIGNALS: diff --git a/src/wayland/surface_interface.cpp b/src/wayland/surface_interface.cpp index f8fd5bbb36..1579072102 100644 --- a/src/wayland/surface_interface.cpp +++ b/src/wayland/surface_interface.cpp @@ -688,6 +688,44 @@ void SurfaceInterface::resetTrackedDamage() d->trackedDamage = QRegion(); } +QVector SurfaceInterface::outputs() const +{ + Q_D(); + return d->outputs; +} + +void SurfaceInterface::setOutputs(const QVector &outputs) +{ + Q_D(); + QVector removedOutputs = d->outputs; + for (auto it = outputs.constBegin(), end = outputs.constEnd(); it != end; ++it) { + const auto o = *it; + removedOutputs.removeOne(o); + } + for (auto it = removedOutputs.constBegin(), end = removedOutputs.constEnd(); it != end; ++it) { + const auto resources = (*it)->clientResources(client()); + for (wl_resource *r : resources) { + wl_surface_send_leave(d->resource, r); + } + } + // TODO: send leave when OutputInterface gets destroyed + + QVector addedOutputsOutputs = outputs; + for (auto it = d->outputs.constBegin(), end = d->outputs.constEnd(); it != end; ++it) { + const auto o = *it; + addedOutputsOutputs.removeOne(o); + } + for (auto it = addedOutputsOutputs.constBegin(), end = addedOutputsOutputs.constEnd(); it != end; ++it) { + const auto resources = (*it)->clientResources(client()); + for (wl_resource *r : resources) { + wl_surface_send_enter(d->resource, r); + } + } + // TODO: send enter when the client binds the OutputInterface another time + + d->outputs = outputs; +} + SurfaceInterface *SurfaceInterface::surfaceAt(const QPointF &position) { if (!isMapped()) { diff --git a/src/wayland/surface_interface.h b/src/wayland/surface_interface.h index 85b0805d4d..de74ef336a 100644 --- a/src/wayland/surface_interface.h +++ b/src/wayland/surface_interface.h @@ -205,6 +205,24 @@ public: **/ SurfaceInterface *surfaceAt(const QPointF &position); + /** + * Sets the @p outputs this SurfaceInterface overlaps with, may be empty. + * + * The compositor should update whenever the SurfaceInterface becomes visible on + * an OutputInterface by e.g. getting (un)mapped, resized, moved, etc. + * + * @see outputs + * @since 5.27 + **/ + void setOutputs(const QVector &outputs); + + /** + * @returns All OutputInterfaces the SurfaceInterface is on. + * @see setOutputs + * @since 5.27 + **/ + QVector outputs() const; + /** * @returns The SurfaceInterface for the @p native resource. **/ diff --git a/src/wayland/surface_interface_p.h b/src/wayland/surface_interface_p.h index 9890fc6f65..8d7362220e 100644 --- a/src/wayland/surface_interface_p.h +++ b/src/wayland/surface_interface_p.h @@ -89,6 +89,8 @@ public: // waiting on the frame callback of the never visible surface bool subSurfaceIsMapped = true; + QVector outputs; + private: SurfaceInterface *q_func() { return reinterpret_cast(q);