Add support for Surface enter/leave events

Summary:
This change implements support for the wl_surface events enter and
leave. Those events are emitted whenever a surface becomes visible on
an output by e.g. mapping the surface, moving or resizing it. Similar
the leave is sent whenever the surface is no longer on an output.

The server side is not yet fully complete yet. It also needs to emit
when the client binds the output another time and needs to send a
leave before destroying the output.

Reviewers: #plasma_on_wayland

Subscribers: plasma-devel

Tags: #plasma_on_wayland

Differential Revision: https://phabricator.kde.org/D2528
This commit is contained in:
Martin Gräßlin 2016-08-22 14:18:23 +02:00
parent 2cf09cb50d
commit 9f92a05f52
6 changed files with 152 additions and 0 deletions

View file

@ -25,6 +25,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#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<KWayland::Client::Output*>();
QScopedPointer<Surface> 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<SurfaceInterface*>();
QVERIFY(serverSurface);
QCOMPARE(serverSurface->outputs(), QVector<OutputInterface*>());
// create another registry to get notified about added outputs
Registry registry;
registry.setEventQueue(m_queue);
QSignalSpy allAnnounced(&registry, &Registry::interfacesAnnounced);
QVERIFY(allAnnounced.isValid());
registry.create(m_connection);
QVERIFY(registry.isValid());
registry.setup();
QVERIFY(allAnnounced.wait());
QSignalSpy outputAnnouncedSpy(&registry, &Registry::outputAnnounced);
QVERIFY(outputAnnouncedSpy.isValid());
auto serverOutput = m_display->createOutput(m_display);
serverOutput->create();
QVERIFY(outputAnnouncedSpy.wait());
QScopedPointer<Output> clientOutput(registry.createOutput(outputAnnouncedSpy.first().first().value<quint32>(), outputAnnouncedSpy.first().last().value<quint32>()));
QVERIFY(clientOutput->isValid());
m_connection->flush();
m_display->dispatchEvents();
// now enter it
serverSurface->setOutputs(QVector<OutputInterface*>{serverOutput});
QCOMPARE(serverSurface->outputs(), QVector<OutputInterface*>{serverOutput});
QVERIFY(enteredSpy.wait());
QCOMPARE(enteredSpy.count(), 1);
QCOMPARE(enteredSpy.first().first().value<Output*>(), clientOutput.data());
QCOMPARE(s->outputs(), QVector<Output*>{clientOutput.data()});
// adding to same should not trigger
serverSurface->setOutputs(QVector<OutputInterface*>{serverOutput});
// leave again
serverSurface->setOutputs(QVector<OutputInterface*>());
QCOMPARE(serverSurface->outputs(), QVector<OutputInterface*>());
QVERIFY(leftSpy.wait());
QCOMPARE(enteredSpy.count(), 1);
QCOMPARE(leftSpy.count(), 1);
QCOMPARE(leftSpy.first().first().value<Output*>(), clientOutput.data());
QCOMPARE(s->outputs(), QVector<Output*>());
// leave again should not trigger
serverSurface->setOutputs(QVector<OutputInterface*>());
// and enter again, just to verify
serverSurface->setOutputs(QVector<OutputInterface*>{serverOutput});
QCOMPARE(serverSurface->outputs(), QVector<OutputInterface*>{serverOutput});
QVERIFY(enteredSpy.wait());
QCOMPARE(enteredSpy.count(), 2);
QCOMPARE(leftSpy.count(), 1);
}
QTEST_GUILESS_MAIN(TestWaylandSurface)
#include "test_wayland_surface.moc"

View file

@ -506,6 +506,18 @@ bool OutputInterface::isDpmsSupported() const
return d->dpms.supported;
}
QVector<wl_resource *> OutputInterface::clientResources(ClientConnection *client) const
{
Q_D();
QVector<wl_resource *> 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);

View file

@ -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<wl_resource *> clientResources(ClientConnection *client) const;
static OutputInterface *get(wl_resource *native);
Q_SIGNALS:

View file

@ -688,6 +688,44 @@ void SurfaceInterface::resetTrackedDamage()
d->trackedDamage = QRegion();
}
QVector<OutputInterface *> SurfaceInterface::outputs() const
{
Q_D();
return d->outputs;
}
void SurfaceInterface::setOutputs(const QVector<OutputInterface *> &outputs)
{
Q_D();
QVector<OutputInterface *> 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<OutputInterface *> 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()) {

View file

@ -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<OutputInterface *> &outputs);
/**
* @returns All OutputInterfaces the SurfaceInterface is on.
* @see setOutputs
* @since 5.27
**/
QVector<OutputInterface *> outputs() const;
/**
* @returns The SurfaceInterface for the @p native resource.
**/

View file

@ -89,6 +89,8 @@ public:
// waiting on the frame callback of the never visible surface
bool subSurfaceIsMapped = true;
QVector<OutputInterface *> outputs;
private:
SurfaceInterface *q_func() {
return reinterpret_cast<SurfaceInterface *>(q);