diff --git a/autotests/client/test_wayland_seat.cpp b/autotests/client/test_wayland_seat.cpp --- a/autotests/client/test_wayland_seat.cpp +++ b/autotests/client/test_wayland_seat.cpp @@ -32,14 +32,17 @@ #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" #include "../../src/server/datadevicemanager_interface.h" #include "../../src/server/display.h" #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 @@ void testPointerTransformation(); void testPointerButton_data(); void testPointerButton(); + void testPointerSubSurfaceTree(); void testCursor(); void testCursorDamage(); void testKeyboard(); @@ -76,10 +80,12 @@ 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 @@ , 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 @@ 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 @@ 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 @@ delete m_seatInterface; m_seatInterface = nullptr; + delete m_subCompositorInterface; + m_subCompositorInterface = nullptr; + delete m_display; m_display = nullptr; } @@ -643,6 +668,104 @@ 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 @@ 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 @@ 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/server/pointer_interface.cpp b/src/server/pointer_interface.cpp --- a/src/server/pointer_interface.cpp +++ b/src/server/pointer_interface.cpp @@ -21,6 +21,7 @@ #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 @@ 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::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 @@ } 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,28 +167,28 @@ 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; d->destroyConnection = connect(d->focusedSurface, &QObject::destroyed, this, [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(); }