From 84a1d5eadce1fa4d48b971dcee2673acae1802e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Wed, 6 Apr 2016 09:52:13 +0200 Subject: [PATCH] [server] Support sub-surfaces from PointerInterface Summary: The idea behind this change is to make the existance of sub-surfaces an implementation detail for pointer events. The user of the library does not need to care about on which sub-surface the pointer is on. It only needs to care about the main surface and passes the focus to the main surface. Internally the PointerInterface takes care of sending the enter to the sub-surface at the current pointer position. Also whenever the pointer position changes, the PointerInterface evaluates whether it triggered a change for the focused sub-surface and sends enter/leave events accordingly. If the focused sub-surface does not change, it sends motion events as normally, but of course under consideration of the sub-surface position. Overall this means that from pointer usage perspective a user of the library doesn't need to care about the fact that there are sub-surfaces at all. The library does the correct thing for it. Reviewers: #plasma Subscribers: plasma-devel Projects: #plasma Differential Revision: https://phabricator.kde.org/D1329 --- .../autotests/client/test_wayland_seat.cpp | 125 ++++++++++++++++++ src/wayland/pointer_interface.cpp | 70 ++++++++-- 2 files changed, 184 insertions(+), 11 deletions(-) diff --git a/src/wayland/autotests/client/test_wayland_seat.cpp b/src/wayland/autotests/client/test_wayland_seat.cpp index 4067c73164..17756926fb 100644 --- a/src/wayland/autotests/client/test_wayland_seat.cpp +++ b/src/wayland/autotests/client/test_wayland_seat.cpp @@ -32,6 +32,8 @@ License along with this library. If not, see . #include "../../src/client/registry.h" #include "../../src/client/seat.h" #include "../../src/client/shm_pool.h" +#include "../../src/client/subcompositor.h" +#include "../../src/client/subsurface.h" #include "../../src/client/touch.h" #include "../../src/server/buffer_interface.h" #include "../../src/server/compositor_interface.h" @@ -40,6 +42,7 @@ License along with this library. If not, see . #include "../../src/server/keyboard_interface.h" #include "../../src/server/pointer_interface.h" #include "../../src/server/seat_interface.h" +#include "../../src/server/subcompositor_interface.h" #include "../../src/server/surface_interface.h" // Wayland #include @@ -63,6 +66,7 @@ private Q_SLOTS: void testPointerTransformation(); void testPointerButton_data(); void testPointerButton(); + void testPointerSubSurfaceTree(); void testCursor(); void testCursorDamage(); void testKeyboard(); @@ -76,10 +80,12 @@ private: KWayland::Server::Display *m_display; KWayland::Server::CompositorInterface *m_compositorInterface; KWayland::Server::SeatInterface *m_seatInterface; + KWayland::Server::SubCompositorInterface *m_subCompositorInterface; KWayland::Client::ConnectionThread *m_connection; KWayland::Client::Compositor *m_compositor; KWayland::Client::Seat *m_seat; KWayland::Client::ShmPool *m_shm; + KWayland::Client::SubCompositor * m_subCompositor; KWayland::Client::EventQueue *m_queue; QThread *m_thread; }; @@ -91,10 +97,12 @@ TestWaylandSeat::TestWaylandSeat(QObject *parent) , m_display(nullptr) , m_compositorInterface(nullptr) , m_seatInterface(nullptr) + , m_subCompositorInterface(nullptr) , m_connection(nullptr) , m_compositor(nullptr) , m_seat(nullptr) , m_shm(nullptr) + , m_subCompositor(nullptr) , m_queue(nullptr) , m_thread(nullptr) { @@ -115,6 +123,11 @@ void TestWaylandSeat::init() m_compositorInterface->create(); QVERIFY(m_compositorInterface->isValid()); + m_subCompositorInterface = m_display->createSubCompositor(m_display); + QVERIFY(m_subCompositorInterface); + m_subCompositorInterface->create(); + QVERIFY(m_subCompositorInterface->isValid()); + // setup connection m_connection = new KWayland::Client::ConnectionThread; QSignalSpy connectedSpy(m_connection, SIGNAL(connected())); @@ -158,10 +171,19 @@ void TestWaylandSeat::init() m_shm = new KWayland::Client::ShmPool(this); m_shm->setup(registry.bindShm(shmSpy.first().first().value(), shmSpy.first().last().value())); QVERIFY(m_shm->isValid()); + + m_subCompositor = registry.createSubCompositor(registry.interface(KWayland::Client::Registry::Interface::SubCompositor).name, + registry.interface(KWayland::Client::Registry::Interface::SubCompositor).version, + this); + QVERIFY(m_subCompositor->isValid()); } void TestWaylandSeat::cleanup() { + if (m_subCompositor) { + delete m_subCompositor; + m_subCompositor = nullptr; + } if (m_shm) { delete m_shm; m_shm = nullptr; @@ -195,6 +217,9 @@ void TestWaylandSeat::cleanup() delete m_seatInterface; m_seatInterface = nullptr; + delete m_subCompositorInterface; + m_subCompositorInterface = nullptr; + delete m_display; m_display = nullptr; } @@ -643,6 +668,104 @@ void TestWaylandSeat::testPointerButton() QCOMPARE(buttonChangedSpy.last().at(3).value(), Pointer::ButtonState::Released); } +void TestWaylandSeat::testPointerSubSurfaceTree() +{ + // this test verifies that pointer motion on a surface with sub-surfaces sends motion enter/leave to the sub-surface + using namespace KWayland::Client; + using namespace KWayland::Server; + + // first create the pointer + QSignalSpy hasPointerChangedSpy(m_seat, &Seat::hasPointerChanged); + QVERIFY(hasPointerChangedSpy.isValid()); + m_seatInterface->setHasPointer(true); + QVERIFY(hasPointerChangedSpy.wait()); + QScopedPointer pointer(m_seat->createPointer()); + + // create a sub surface tree + // parent surface (100, 100) with one sub surface taking the half of it's size (50, 100) + // which has two further children (50, 50) which are overlapping + QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(surfaceCreatedSpy.isValid()); + QScopedPointer parentSurface(m_compositor->createSurface()); + QScopedPointer childSurface(m_compositor->createSurface()); + QScopedPointer grandChild1Surface(m_compositor->createSurface()); + QScopedPointer grandChild2Surface(m_compositor->createSurface()); + QScopedPointer childSubSurface(m_subCompositor->createSubSurface(childSurface.data(), parentSurface.data())); + QScopedPointer grandChild1SubSurface(m_subCompositor->createSubSurface(grandChild1Surface.data(), childSurface.data())); + QScopedPointer grandChild2SubSurface(m_subCompositor->createSubSurface(grandChild2Surface.data(), childSurface.data())); + grandChild2SubSurface->setPosition(QPoint(0, 25)); + + // let's map the surfaces + auto render = [this] (Surface *s, const QSize &size) { + QImage image(size, QImage::Format_ARGB32); + image.fill(Qt::black); + s->attachBuffer(m_shm->createBuffer(image)); + s->damage(QRect(QPoint(0, 0), size)); + s->commit(Surface::CommitFlag::None); + }; + render(grandChild2Surface.data(), QSize(50, 50)); + render(grandChild1Surface.data(), QSize(50, 50)); + render(childSurface.data(), QSize(50, 100)); + render(parentSurface.data(), QSize(100, 100)); + + QVERIFY(surfaceCreatedSpy.wait()); + auto serverSurface = surfaceCreatedSpy.first().first().value(); + QVERIFY(serverSurface->isMapped()); + + // send in pointer events + QSignalSpy enteredSpy(pointer.data(), &Pointer::entered); + QVERIFY(enteredSpy.isValid()); + QSignalSpy leftSpy(pointer.data(), &Pointer::left); + QVERIFY(leftSpy.isValid()); + QSignalSpy motionSpy(pointer.data(), &Pointer::motion); + QVERIFY(motionSpy.isValid()); + // first to the grandChild2 in the overlapped area + quint32 timestamp = 1; + m_seatInterface->setTimestamp(timestamp++); + m_seatInterface->setPointerPos(QPointF(25, 50)); + m_seatInterface->setFocusedPointerSurface(serverSurface); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 1); + QCOMPARE(leftSpy.count(), 0); + QCOMPARE(motionSpy.count(), 0); + QCOMPARE(enteredSpy.last().last().toPointF(), QPointF(25, 25)); + QCOMPARE(pointer->enteredSurface(), grandChild2Surface.data()); + // a motion on grandchild2 + m_seatInterface->setTimestamp(timestamp++); + m_seatInterface->setPointerPos(QPointF(25, 60)); + QVERIFY(motionSpy.wait()); + QCOMPARE(enteredSpy.count(), 1); + QCOMPARE(leftSpy.count(), 0); + QCOMPARE(motionSpy.count(), 1); + QCOMPARE(motionSpy.last().first().toPointF(), QPointF(25, 35)); + // motion which changes to childSurface + m_seatInterface->setTimestamp(timestamp++); + m_seatInterface->setPointerPos(QPointF(25, 80)); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 2); + QCOMPARE(leftSpy.count(), 1); + QCOMPARE(motionSpy.count(), 1); + QCOMPARE(enteredSpy.last().last().toPointF(), QPointF(25, 80)); + QCOMPARE(pointer->enteredSurface(), childSurface.data()); + // a leave for the whole surface + m_seatInterface->setTimestamp(timestamp++); + m_seatInterface->setFocusedPointerSurface(nullptr); + QVERIFY(leftSpy.wait()); + QCOMPARE(enteredSpy.count(), 2); + QCOMPARE(leftSpy.count(), 2); + QCOMPARE(motionSpy.count(), 1); + // a new enter on the main surface + m_seatInterface->setTimestamp(timestamp++); + m_seatInterface->setPointerPos(QPointF(75, 50)); + m_seatInterface->setFocusedPointerSurface(serverSurface); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 3); + QCOMPARE(leftSpy.count(), 2); + QCOMPARE(motionSpy.count(), 1); + QCOMPARE(enteredSpy.last().last().toPointF(), QPointF(75, 50)); + QCOMPARE(pointer->enteredSurface(), parentSurface.data()); +} + void TestWaylandSeat::testCursor() { using namespace KWayland::Client; @@ -1019,6 +1142,7 @@ void TestWaylandSeat::testDestroy() m_compositor = nullptr; connect(m_connection, &ConnectionThread::connectionDied, m_seat, &Seat::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_shm, &ShmPool::destroy); + connect(m_connection, &ConnectionThread::connectionDied, m_subCompositor, &SubCompositor::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_queue, &EventQueue::destroy); QVERIFY(m_seat->isValid()); @@ -1028,6 +1152,7 @@ void TestWaylandSeat::testDestroy() m_display = nullptr; m_compositorInterface = nullptr; m_seatInterface = nullptr; + m_subCompositorInterface = nullptr; QVERIFY(connectionDiedSpy.wait()); // now the seat should be destroyed; diff --git a/src/wayland/pointer_interface.cpp b/src/wayland/pointer_interface.cpp index 0c8486b990..05d7e37d5d 100644 --- a/src/wayland/pointer_interface.cpp +++ b/src/wayland/pointer_interface.cpp @@ -21,6 +21,7 @@ License along with this library. If not, see . #include "resource_p.h" #include "seat_interface.h" #include "display.h" +#include "subcompositor_interface.h" #include "surface_interface.h" // Wayland #include @@ -38,9 +39,13 @@ public: SeatInterface *seat; SurfaceInterface *focusedSurface = nullptr; + QPointer focusedChildSurface; QMetaObject::Connection destroyConnection; Cursor *cursor = nullptr; + void sendLeave(SurfaceInterface *surface, quint32 serial); + void sendEnter(SurfaceInterface *surface, const QPointF &parentSurfacePosition, quint32 serial); + private: PointerInterface *q_func() { return reinterpret_cast(q); @@ -89,6 +94,36 @@ void PointerInterface::Private::setCursor(quint32 serial, SurfaceInterface *surf } } +void PointerInterface::Private::sendLeave(SurfaceInterface *surface, quint32 serial) +{ + if (!surface) { + return; + } + if (resource && surface->resource()) { + wl_pointer_send_leave(resource, serial, surface->resource()); + } +} + +namespace { +static QPointF surfacePosition(SurfaceInterface *surface) { + if (surface && surface->subSurface()) { + return surface->subSurface()->position() + surfacePosition(surface->subSurface()->parentSurface().data()); + } + return QPointF(); +} +} + +void PointerInterface::Private::sendEnter(SurfaceInterface *surface, const QPointF &parentSurfacePosition, quint32 serial) +{ + if (!surface) { + return; + } + const QPointF adjustedPos = parentSurfacePosition - surfacePosition(surface); + wl_pointer_send_enter(resource, serial, + surface->resource(), + wl_fixed_from_double(adjustedPos.x()), wl_fixed_from_double(adjustedPos.y())); +} + #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_pointer_interface PointerInterface::Private::s_interface = { setCursorCallback, @@ -108,8 +143,21 @@ PointerInterface::PointerInterface(SeatInterface *parent, wl_resource *parentRes } if (d->focusedSurface && d->resource) { const QPointF pos = d->seat->focusedPointerSurfaceTransformation().map(d->seat->pointerPos()); - wl_pointer_send_motion(d->resource, d->seat->timestamp(), - wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); + auto targetSurface = d->focusedSurface->surfaceAt(pos); + if (!targetSurface) { + targetSurface = d->focusedSurface; + } + if (targetSurface != d->focusedChildSurface.data()) { + const quint32 serial = d->seat->display()->nextSerial(); + d->sendLeave(d->focusedChildSurface.data(), serial); + d->focusedChildSurface = QPointer(targetSurface); + d->sendEnter(targetSurface, pos, serial); + d->client->flush(); + } else { + const QPointF adjustedPos = pos - surfacePosition(d->focusedChildSurface); + wl_pointer_send_motion(d->resource, d->seat->timestamp(), + wl_fixed_from_double(adjustedPos.x()), wl_fixed_from_double(adjustedPos.y())); + } } }); } @@ -119,14 +167,11 @@ PointerInterface::~PointerInterface() = default; void PointerInterface::setFocusedSurface(SurfaceInterface *surface, quint32 serial) { Q_D(); - if (d->focusedSurface) { - if (d->resource && d->focusedSurface->resource()) { - wl_pointer_send_leave(d->resource, serial, d->focusedSurface->resource()); - } - disconnect(d->destroyConnection); - } + d->sendLeave(d->focusedChildSurface.data(), serial); + disconnect(d->destroyConnection); if (!surface) { d->focusedSurface = nullptr; + d->focusedChildSurface.clear(); return; } d->focusedSurface = surface; @@ -134,13 +179,16 @@ void PointerInterface::setFocusedSurface(SurfaceInterface *surface, quint32 seri [this] { Q_D(); d->focusedSurface = nullptr; + d->focusedChildSurface.clear(); } ); const QPointF pos = d->seat->focusedPointerSurfaceTransformation().map(d->seat->pointerPos()); - wl_pointer_send_enter(d->resource, serial, - d->focusedSurface->resource(), - wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); + d->focusedChildSurface = QPointer(d->focusedSurface->surfaceAt(pos)); + if (!d->focusedChildSurface) { + d->focusedChildSurface = QPointer(d->focusedSurface); + } + d->sendEnter(d->focusedChildSurface.data(), pos, serial); d->client->flush(); }