diff --git a/autotests/client/test_wayland_surface.cpp b/autotests/client/test_wayland_surface.cpp --- a/autotests/client/test_wayland_surface.cpp +++ b/autotests/client/test_wayland_surface.cpp @@ -25,6 +25,7 @@ #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 @@ void testSurfaceAt(); void testDestroyAttachedBuffer(); void testDestroyWithPendingCallback(); + void testOutput(); void testDisconnect(); private: @@ -956,5 +958,78 @@ 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/client/output.h b/src/client/output.h --- a/src/client/output.h +++ b/src/client/output.h @@ -242,6 +242,7 @@ } } +Q_DECLARE_METATYPE(KWayland::Client::Output*) Q_DECLARE_METATYPE(KWayland::Client::Output::SubPixel) Q_DECLARE_METATYPE(KWayland::Client::Output::Transform) Q_DECLARE_METATYPE(KWayland::Client::Output::Mode) diff --git a/src/client/surface.h b/src/client/surface.h --- a/src/client/surface.h +++ b/src/client/surface.h @@ -39,6 +39,7 @@ namespace Client { +class Output; class Region; /** @@ -238,6 +239,14 @@ quint32 id() const; /** + * @returns All Outputs the Surface is on, may be none. + * @see outputEntered + * @see outputLeft + * @since 5.27 + **/ + QVector outputs() const; + + /** * All Surfaces which are currently created. * TODO: KF6 return QList instead of const-ref **/ @@ -258,6 +267,28 @@ void frameRendered(); void sizeChanged(const QSize&); + /** + * Emitted whenever a change in the Surface (e.g. creation, movement, resize) results in + * a part of the Surface being within the scanout region of the Output @p o. + * + * @param o The Output the Surface intersects with + * @see outputLeft + * @see outputs + * @since 5.27 + **/ + void outputEntered(KWayland::Client::Output *o); + + /** + * Emitted whenever a change in the Surface (e.g. creation, movement, resize, unmapping) + * results in the Surface no longer being within the scanout region of the Output @p o. + * + * @param o The Output the Surface no longer intersects with + * @see outputEntered + * @see outputs + * @since 5.27 + **/ + void outputLeft(KWayland::Client::Output *o); + private: class Private; QScopedPointer d; diff --git a/src/client/surface.cpp b/src/client/surface.cpp --- a/src/client/surface.cpp +++ b/src/client/surface.cpp @@ -20,6 +20,7 @@ #include "surface.h" #include "buffer.h" #include "region.h" +#include "output.h" #include "wayland_pointer_p.h" #include @@ -46,14 +47,20 @@ QSize size; bool foreign = false; qint32 scale = 1; + QVector outputs; + + void setup(wl_surface *s); static QList s_surfaces; private: void handleFrameCallback(); static void frameCallback(void *data, wl_callback *callback, uint32_t time); + static void enterCallback(void *data, wl_surface *wl_surface, wl_output *output); + static void leaveCallback(void *data, wl_surface *wl_surface, wl_output *output); Surface *q; static const wl_callback_listener s_listener; + static const wl_surface_listener s_surfaceListener; }; QList Surface::Private::s_surfaces = QList(); @@ -130,9 +137,15 @@ void Surface::setup(wl_surface *surface) { - Q_ASSERT(surface); - Q_ASSERT(!d->surface); - d->surface.setup(surface); + d->setup(surface); +} + +void Surface::Private::setup(wl_surface *s) +{ + Q_ASSERT(s); + Q_ASSERT(!surface); + surface.setup(s); + wl_surface_add_listener(s, &s_surfaceListener, this); } void Surface::Private::frameCallback(void *data, wl_callback *callback, uint32_t time) @@ -155,8 +168,37 @@ const struct wl_callback_listener Surface::Private::s_listener = { frameCallback }; + +const struct wl_surface_listener Surface::Private::s_surfaceListener = { + enterCallback, + leaveCallback +}; #endif +void Surface::Private::enterCallback(void *data, wl_surface *surface, wl_output *output) +{ + Q_UNUSED(surface); + auto s = reinterpret_cast(data); + Output *o = Output::get(output); + if (!o) { + return; + } + s->outputs << o; + emit s->q->outputEntered(o); +} + +void Surface::Private::leaveCallback(void *data, wl_surface *surface, wl_output *output) +{ + Q_UNUSED(surface); + auto s = reinterpret_cast(data); + Output *o = Output::get(output); + if (!o) { + return; + } + s->outputs.removeOne(o); + emit s->q->outputLeft(o); +} + void Surface::Private::setupFrameCallback() { Q_ASSERT(!frameCallbackInstalled); @@ -293,5 +335,10 @@ wl_surface_set_buffer_scale(d->surface, scale); } +QVector Surface::outputs() const +{ + return d->outputs; +} + } } diff --git a/src/server/output_interface.h b/src/server/output_interface.h --- a/src/server/output_interface.h +++ b/src/server/output_interface.h @@ -36,6 +36,7 @@ namespace Server { +class ClientConnection; class Display; /** @@ -125,6 +126,12 @@ **/ 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/server/output_interface.cpp b/src/server/output_interface.cpp --- a/src/server/output_interface.cpp +++ b/src/server/output_interface.cpp @@ -506,6 +506,18 @@ 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/server/surface_interface.h b/src/server/surface_interface.h --- a/src/server/surface_interface.h +++ b/src/server/surface_interface.h @@ -206,6 +206,24 @@ 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. **/ static SurfaceInterface *get(wl_resource *native); diff --git a/src/server/surface_interface.cpp b/src/server/surface_interface.cpp --- a/src/server/surface_interface.cpp +++ b/src/server/surface_interface.cpp @@ -688,6 +688,44 @@ 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/server/surface_interface_p.h b/src/server/surface_interface_p.h --- a/src/server/surface_interface_p.h +++ b/src/server/surface_interface_p.h @@ -89,6 +89,8 @@ // waiting on the frame callback of the never visible surface bool subSurfaceIsMapped = true; + QVector outputs; + private: SurfaceInterface *q_func() { return reinterpret_cast(q);