diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index ab21e99e5b..53b1a91891 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -6,6 +6,7 @@ set(SERVER_LIB_SRCS seat_interface.cpp shell_interface.cpp surface_interface.cpp + subcompositor_interface.cpp ) add_library(KF5WaylandServer ${SERVER_LIB_SRCS}) diff --git a/src/wayland/autotests/client/CMakeLists.txt b/src/wayland/autotests/client/CMakeLists.txt index c7dc316003..b593d17d12 100644 --- a/src/wayland/autotests/client/CMakeLists.txt +++ b/src/wayland/autotests/client/CMakeLists.txt @@ -100,3 +100,26 @@ add_executable(testCompositor ${testCompositor_SRCS}) target_link_libraries( testCompositor Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) add_test(kwayland-testCompositor testCompositor) ecm_mark_as_test(testCompositor) + +######################################################## +# Test SubCompositor +######################################################## +set( testSubCompositor_SRCS + test_wayland_subcompositor.cpp + ) +add_executable(testSubCompositor ${testSubCompositor_SRCS}) +target_link_libraries( testSubCompositor Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) +add_test(kwayland-testSubCompositor testSubCompositor) +ecm_mark_as_test(testSubCompositor) + + +######################################################## +# Test SubSurface +######################################################## +set( testSubSurface_SRCS + test_wayland_subsurface.cpp + ) +add_executable(testSubSurface ${testSubSurface_SRCS}) +target_link_libraries( testSubSurface Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) +add_test(kwayland-testSubSurface testSubSurface) +ecm_mark_as_test(testSubSurface) diff --git a/src/wayland/autotests/client/test_wayland_registry.cpp b/src/wayland/autotests/client/test_wayland_registry.cpp index 77658734b9..1bc07b82a1 100644 --- a/src/wayland/autotests/client/test_wayland_registry.cpp +++ b/src/wayland/autotests/client/test_wayland_registry.cpp @@ -28,6 +28,7 @@ License along with this library. If not, see . #include "../../src/server/output_interface.h" #include "../../src/server/seat_interface.h" #include "../../src/server/shell_interface.h" +#include "../../src/server/subcompositor_interface.h" // Wayland #include @@ -46,6 +47,7 @@ private Q_SLOTS: void testBindOutput(); void testBindShm(); void testBindSeat(); + void testBindSubCompositor(); void testGlobalSync(); void testGlobalSyncThreaded(); void testRemoval(); @@ -57,6 +59,7 @@ private: KWayland::Server::OutputInterface *m_output; KWayland::Server::SeatInterface *m_seat; KWayland::Server::ShellInterface *m_shell; + KWayland::Server::SubCompositorInterface *m_subcompositor; }; static const QString s_socketName = QStringLiteral("kwin-test-wayland-registry-0"); @@ -68,6 +71,7 @@ TestWaylandRegistry::TestWaylandRegistry(QObject *parent) , m_output(nullptr) , m_seat(nullptr) , m_shell(nullptr) + , m_subcompositor(nullptr) { } @@ -85,6 +89,8 @@ void TestWaylandRegistry::init() m_seat->create(); m_shell = m_display->createShell(); m_shell->create(); + m_subcompositor = m_display->createSubCompositor(); + m_subcompositor->create(); } void TestWaylandRegistry::cleanup() @@ -168,6 +174,11 @@ void TestWaylandRegistry::testBindShm() TEST_BIND(KWayland::Client::Registry::Interface::Shm, SIGNAL(shmAnnounced(quint32,quint32)), bindShm, wl_shm_destroy) } +void TestWaylandRegistry::testBindSubCompositor() +{ + TEST_BIND(KWayland::Client::Registry::Interface::SubCompositor, SIGNAL(subCompositorAnnounced(quint32,quint32)), bindSubCompositor, wl_subcompositor_destroy) +} + #undef TEST_BIND void TestWaylandRegistry::testRemoval() @@ -194,6 +205,8 @@ void TestWaylandRegistry::testRemoval() QVERIFY(shellAnnouncedSpy.isValid()); QSignalSpy seatAnnouncedSpy(®istry, SIGNAL(seatAnnounced(quint32,quint32))); QVERIFY(seatAnnouncedSpy.isValid()); + QSignalSpy subCompositorAnnouncedSpy(®istry, SIGNAL(subCompositorAnnounced(quint32,quint32))); + QVERIFY(subCompositorAnnouncedSpy.isValid()); QVERIFY(!registry.isValid()); registry.create(connection.display()); @@ -204,12 +217,14 @@ void TestWaylandRegistry::testRemoval() QVERIFY(!outputAnnouncedSpy.isEmpty()); QVERIFY(!shellAnnouncedSpy.isEmpty()); QVERIFY(!seatAnnouncedSpy.isEmpty()); + QVERIFY(!subCompositorAnnouncedSpy.isEmpty()); QVERIFY(registry.hasInterface(KWayland::Client::Registry::Interface::Compositor)); QVERIFY(registry.hasInterface(KWayland::Client::Registry::Interface::Output)); QVERIFY(registry.hasInterface(KWayland::Client::Registry::Interface::Seat)); QVERIFY(registry.hasInterface(KWayland::Client::Registry::Interface::Shell)); QVERIFY(registry.hasInterface(KWayland::Client::Registry::Interface::Shm)); + QVERIFY(registry.hasInterface(KWayland::Client::Registry::Interface::SubCompositor)); QVERIFY(!registry.hasInterface(KWayland::Client::Registry::Interface::FullscreenShell)); QSignalSpy seatRemovedSpy(®istry, SIGNAL(seatRemoved(quint32))); @@ -244,6 +259,14 @@ void TestWaylandRegistry::testRemoval() QCOMPARE(compositorRemovedSpy.first().first(), compositorAnnouncedSpy.first().first()); QVERIFY(!registry.hasInterface(KWayland::Client::Registry::Interface::Compositor)); + QSignalSpy subCompositorRemovedSpy(®istry, SIGNAL(subCompositorRemoved(quint32))); + QVERIFY(subCompositorRemovedSpy.isValid()); + + delete m_subcompositor; + QVERIFY(subCompositorRemovedSpy.wait()); + QCOMPARE(subCompositorRemovedSpy.first().first(), subCompositorAnnouncedSpy.first().first()); + QVERIFY(!registry.hasInterface(KWayland::Client::Registry::Interface::SubCompositor)); + // cannot test shmRemoved as there is no functionality for it } diff --git a/src/wayland/autotests/client/test_wayland_subcompositor.cpp b/src/wayland/autotests/client/test_wayland_subcompositor.cpp new file mode 100644 index 0000000000..2e848f8fb2 --- /dev/null +++ b/src/wayland/autotests/client/test_wayland_subcompositor.cpp @@ -0,0 +1,173 @@ +/******************************************************************** +Copyright 2014 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +// Qt +#include +// KWin +#include "../../src/client/connection_thread.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/registry.h" +#include "../../src/client/subcompositor.h" +#include "../../src/server/display.h" +#include "../../src/server/subcompositor_interface.h" + +class TestSubCompositor : public QObject +{ + Q_OBJECT +public: + explicit TestSubCompositor(QObject *parent = nullptr); +private Q_SLOTS: + void init(); + void cleanup(); + + void testDestroy(); + void testCast(); + +private: + KWayland::Server::Display *m_display; + KWayland::Server::SubCompositorInterface *m_subcompositorInterface; + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::SubCompositor *m_subCompositor; + KWayland::Client::EventQueue *m_queue; + QThread *m_thread; +}; + +static const QString s_socketName = QStringLiteral("kwayland-test-wayland-subcompositor-0"); + +TestSubCompositor::TestSubCompositor(QObject *parent) + : QObject(parent) + , m_display(nullptr) + , m_subcompositorInterface(nullptr) + , m_connection(nullptr) + , m_subCompositor(nullptr) + , m_queue(nullptr) + , m_thread(nullptr) +{ +} + +void TestSubCompositor::init() +{ + using namespace KWayland::Server; + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + + // setup connection + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, SIGNAL(connected())); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + m_queue = new KWayland::Client::EventQueue(this); + QVERIFY(!m_queue->isValid()); + m_queue->setup(m_connection); + QVERIFY(m_queue->isValid()); + + KWayland::Client::Registry registry; + QSignalSpy subCompositorSpy(®istry, SIGNAL(subCompositorAnnounced(quint32,quint32))); + QVERIFY(subCompositorSpy.isValid()); + QVERIFY(!registry.eventQueue()); + registry.setEventQueue(m_queue); + QCOMPARE(registry.eventQueue(), m_queue); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + + m_subcompositorInterface = m_display->createSubCompositor(m_display); + QVERIFY(m_subcompositorInterface); + m_subcompositorInterface->create(); + QVERIFY(m_subcompositorInterface->isValid()); + + QVERIFY(subCompositorSpy.wait()); + m_subCompositor = registry.createSubCompositor(subCompositorSpy.first().first().value(), subCompositorSpy.first().last().value(), this); +} + +void TestSubCompositor::cleanup() +{ + if (m_subCompositor) { + delete m_subCompositor; + m_subCompositor = nullptr; + } + if (m_queue) { + delete m_queue; + m_queue = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + delete m_connection; + m_connection = nullptr; + + delete m_display; + m_display = nullptr; +} + +void TestSubCompositor::testDestroy() +{ + using namespace KWayland::Client; + connect(m_connection, &ConnectionThread::connectionDied, m_subCompositor, &SubCompositor::destroy); + connect(m_connection, &ConnectionThread::connectionDied, m_queue, &EventQueue::destroy); + QVERIFY(m_subCompositor->isValid()); + + QSignalSpy connectionDiedSpy(m_connection, SIGNAL(connectionDied())); + QVERIFY(connectionDiedSpy.isValid()); + delete m_display; + m_display = nullptr; + QVERIFY(connectionDiedSpy.wait()); + + // now the pool should be destroyed; + QVERIFY(!m_subCompositor->isValid()); + + // calling destroy again should not fail + m_subCompositor->destroy(); +} + +void TestSubCompositor::testCast() +{ + using namespace KWayland::Client; + Registry registry; + QSignalSpy subCompositorSpy(®istry, SIGNAL(subCompositorAnnounced(quint32,quint32))); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + + QVERIFY(subCompositorSpy.wait()); + + SubCompositor c; + auto wlSubComp = registry.bindSubCompositor(subCompositorSpy.first().first().value(), subCompositorSpy.first().last().value()); + c.setup(wlSubComp); + QCOMPARE((wl_subcompositor*)c, wlSubComp); + + const SubCompositor &c2(c); + QCOMPARE((wl_subcompositor*)c2, wlSubComp); +} + +QTEST_MAIN(TestSubCompositor) +#include "test_wayland_subcompositor.moc" diff --git a/src/wayland/autotests/client/test_wayland_subsurface.cpp b/src/wayland/autotests/client/test_wayland_subsurface.cpp new file mode 100644 index 0000000000..c94ebfba6f --- /dev/null +++ b/src/wayland/autotests/client/test_wayland_subsurface.cpp @@ -0,0 +1,560 @@ +/******************************************************************** +Copyright 2014 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +// Qt +#include +// KWin +#include "../../src/client/compositor.h" +#include "../../src/client/connection_thread.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/registry.h" +#include "../../src/client/subcompositor.h" +#include "../../src/client/subsurface.h" +#include "../../src/client/surface.h" +#include "../../src/server/display.h" +#include "../../src/server/compositor_interface.h" +#include "../../src/server/subcompositor_interface.h" +#include "../../src/server/surface_interface.h" +// Wayland +#include + +class TestSubSurface : public QObject +{ + Q_OBJECT +public: + explicit TestSubSurface(QObject *parent = nullptr); +private Q_SLOTS: + void init(); + void cleanup(); + + void testCreate(); + void testMode(); + void testPosition(); + void testPlaceAbove(); + void testPlaceBelow(); + void testDestroy(); + void testCast(); + +private: + KWayland::Server::Display *m_display; + KWayland::Server::CompositorInterface *m_compositorInterface; + KWayland::Server::SubCompositorInterface *m_subcompositorInterface; + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::Compositor *m_compositor; + KWayland::Client::SubCompositor *m_subCompositor; + KWayland::Client::EventQueue *m_queue; + QThread *m_thread; +}; + +static const QString s_socketName = QStringLiteral("kwayland-test-wayland-subsurface-0"); + +TestSubSurface::TestSubSurface(QObject *parent) + : QObject(parent) + , m_display(nullptr) + , m_compositorInterface(nullptr) + , m_subcompositorInterface(nullptr) + , m_connection(nullptr) + , m_compositor(nullptr) + , m_subCompositor(nullptr) + , m_queue(nullptr) + , m_thread(nullptr) +{ +} + +void TestSubSurface::init() +{ + using namespace KWayland::Server; + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + + // setup connection + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, SIGNAL(connected())); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + m_queue = new KWayland::Client::EventQueue(this); + QVERIFY(!m_queue->isValid()); + m_queue->setup(m_connection); + QVERIFY(m_queue->isValid()); + + KWayland::Client::Registry registry; + QSignalSpy compositorSpy(®istry, SIGNAL(compositorAnnounced(quint32,quint32))); + QVERIFY(compositorSpy.isValid()); + QSignalSpy subCompositorSpy(®istry, SIGNAL(subCompositorAnnounced(quint32,quint32))); + QVERIFY(subCompositorSpy.isValid()); + QVERIFY(!registry.eventQueue()); + registry.setEventQueue(m_queue); + QCOMPARE(registry.eventQueue(), m_queue); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + + m_compositorInterface = m_display->createCompositor(m_display); + m_compositorInterface->create(); + QVERIFY(m_compositorInterface->isValid()); + + m_subcompositorInterface = m_display->createSubCompositor(m_display); + QVERIFY(m_subcompositorInterface); + m_subcompositorInterface->create(); + QVERIFY(m_subcompositorInterface->isValid()); + + QVERIFY(subCompositorSpy.wait()); + m_subCompositor = registry.createSubCompositor(subCompositorSpy.first().first().value(), subCompositorSpy.first().last().value(), this); + + if (compositorSpy.isEmpty()) { + QVERIFY(compositorSpy.wait()); + } + m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); +} + +void TestSubSurface::cleanup() +{ + if (m_subCompositor) { + delete m_subCompositor; + m_subCompositor = nullptr; + } + if (m_compositor) { + delete m_compositor; + m_compositor = nullptr; + } + if (m_queue) { + delete m_queue; + m_queue = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + delete m_connection; + m_connection = nullptr; + + delete m_display; + m_display = nullptr; +} + +void TestSubSurface::testCreate() +{ + using namespace KWayland::Client; + using namespace KWayland::Server; + QSignalSpy surfaceCreatedSpy(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); + QVERIFY(surfaceCreatedSpy.isValid()); + + // create two Surfaces + QScopedPointer surface(m_compositor->createSurface()); + QVERIFY(surfaceCreatedSpy.wait()); + SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); + QVERIFY(serverSurface); + + surfaceCreatedSpy.clear(); + QScopedPointer parent(m_compositor->createSurface()); + QVERIFY(surfaceCreatedSpy.wait()); + SurfaceInterface *serverParentSurface = surfaceCreatedSpy.first().first().value(); + QVERIFY(serverParentSurface); + + QSignalSpy subSurfaceCreatedSpy(m_subcompositorInterface, SIGNAL(subSurfaceCreated(KWayland::Server::SubSurfaceInterface*))); + QVERIFY(subSurfaceCreatedSpy.isValid()); + + // create subSurface for surface of parent + QScopedPointer subSurface(m_subCompositor->createSubSurface(QPointer(surface.data()), QPointer(parent.data()))); + + QVERIFY(subSurfaceCreatedSpy.wait()); + SubSurfaceInterface *serverSubSurface = subSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverSubSurface); + QCOMPARE(serverSubSurface->parentSurface().data(), serverParentSurface); + QCOMPARE(serverSubSurface->surface().data(), serverSurface); + QCOMPARE(serverSurface->subSurface().data(), serverSubSurface); + // children are only added after committing the surface + QCOMPARE(serverParentSurface->childSubSurfaces().count(), 0); + // so let's commit the surface, to apply the stacking change + parent->commit(Surface::CommitFlag::None); + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverParentSurface->childSubSurfaces().count(), 1); + QCOMPARE(serverParentSurface->childSubSurfaces().first().data(), serverSubSurface); + + // and let's destroy it again + QSignalSpy destroyedSpy(serverSubSurface, SIGNAL(destroyed(QObject*))); + QVERIFY(destroyedSpy.isValid()); + subSurface.reset(); + QVERIFY(destroyedSpy.wait()); + QCOMPARE(serverSurface->subSurface(), QPointer()); + // only applied after next commit + QCOMPARE(serverParentSurface->childSubSurfaces().count(), 1); + // but the surface should be invalid + QVERIFY(serverParentSurface->childSubSurfaces().first().isNull()); + // committing the state should solve it + parent->commit(Surface::CommitFlag::None); + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverParentSurface->childSubSurfaces().count(), 0); +} + +void TestSubSurface::testMode() +{ + using namespace KWayland::Client; + using namespace KWayland::Server; + // create two Surface + QScopedPointer surface(m_compositor->createSurface()); + QScopedPointer parent(m_compositor->createSurface()); + + QSignalSpy subSurfaceCreatedSpy(m_subcompositorInterface, SIGNAL(subSurfaceCreated(KWayland::Server::SubSurfaceInterface*))); + QVERIFY(subSurfaceCreatedSpy.isValid()); + + // create the SubSurface for surface of parent + QScopedPointer subSurface(m_subCompositor->createSubSurface(QPointer(surface.data()), QPointer(parent.data()))); + QVERIFY(subSurfaceCreatedSpy.wait()); + SubSurfaceInterface *serverSubSurface = subSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverSubSurface); + + // both client and server subsurface should be in synchronized mode + QCOMPARE(subSurface->mode(), SubSurface::Mode::Synchronized); + QCOMPARE(serverSubSurface->mode(), SubSurfaceInterface::Mode::Synchronized); + + // verify that we can change to desynchronized + QSignalSpy modeChangedSpy(serverSubSurface, SIGNAL(modeChanged(KWayland::Server::SubSurfaceInterface::Mode))); + QVERIFY(modeChangedSpy.isValid()); + + subSurface->setMode(SubSurface::Mode::Desynchronized); + QCOMPARE(subSurface->mode(), SubSurface::Mode::Desynchronized); + + QVERIFY(modeChangedSpy.wait()); + QCOMPARE(modeChangedSpy.first().first().value(), SubSurfaceInterface::Mode::Desynchronized); + QCOMPARE(serverSubSurface->mode(), SubSurfaceInterface::Mode::Desynchronized); + + // setting the same again won't change + subSurface->setMode(SubSurface::Mode::Desynchronized); + QCOMPARE(subSurface->mode(), SubSurface::Mode::Desynchronized); + // not testing the signal, we do that after changing to synchronized + + // and change back to synchronized + subSurface->setMode(SubSurface::Mode::Synchronized); + QCOMPARE(subSurface->mode(), SubSurface::Mode::Synchronized); + + QVERIFY(modeChangedSpy.wait()); + QCOMPARE(modeChangedSpy.count(), 2); + QCOMPARE(modeChangedSpy.first().first().value(), SubSurfaceInterface::Mode::Desynchronized); + QCOMPARE(modeChangedSpy.last().first().value(), SubSurfaceInterface::Mode::Synchronized); + QCOMPARE(serverSubSurface->mode(), SubSurfaceInterface::Mode::Synchronized); +} + +void TestSubSurface::testPosition() +{ + using namespace KWayland::Client; + using namespace KWayland::Server; + // create two Surface + QScopedPointer surface(m_compositor->createSurface()); + QScopedPointer parent(m_compositor->createSurface()); + + QSignalSpy subSurfaceCreatedSpy(m_subcompositorInterface, SIGNAL(subSurfaceCreated(KWayland::Server::SubSurfaceInterface*))); + QVERIFY(subSurfaceCreatedSpy.isValid()); + + // create the SubSurface for surface of parent + QScopedPointer subSurface(m_subCompositor->createSubSurface(QPointer(surface.data()), QPointer(parent.data()))); + QVERIFY(subSurfaceCreatedSpy.wait()); + SubSurfaceInterface *serverSubSurface = subSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverSubSurface); + + // both client and server should have a default position + QCOMPARE(subSurface->position(), QPoint()); + QCOMPARE(serverSubSurface->position(), QPoint()); + + QSignalSpy positionChangedSpy(serverSubSurface, SIGNAL(positionChanged(QPoint))); + QVERIFY(positionChangedSpy.isValid()); + + // changing the position should not trigger a direct update on server side + subSurface->setPosition(QPoint(10, 20)); + QCOMPARE(subSurface->position(), QPoint(10, 20)); + // ensure it's processed on server side + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface->position(), QPoint()); + // changing once more + subSurface->setPosition(QPoint(20, 30)); + QCOMPARE(subSurface->position(), QPoint(20, 30)); + // ensure it's processed on server side + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface->position(), QPoint()); + + // committing the parent surface should update the position + parent->commit(Surface::CommitFlag::None); + QVERIFY(positionChangedSpy.wait()); + QCOMPARE(positionChangedSpy.count(), 1); + QCOMPARE(positionChangedSpy.first().first().toPoint(), QPoint(20, 30)); + QCOMPARE(serverSubSurface->position(), QPoint(20, 30)); +} + +void TestSubSurface::testPlaceAbove() +{ + using namespace KWayland::Client; + using namespace KWayland::Server; + // create needed Surfaces (one parent, three client + QScopedPointer surface1(m_compositor->createSurface()); + QScopedPointer surface2(m_compositor->createSurface()); + QScopedPointer surface3(m_compositor->createSurface()); + QScopedPointer parent(m_compositor->createSurface()); + + QSignalSpy subSurfaceCreatedSpy(m_subcompositorInterface, SIGNAL(subSurfaceCreated(KWayland::Server::SubSurfaceInterface*))); + QVERIFY(subSurfaceCreatedSpy.isValid()); + + // create the SubSurfaces for surface of parent + QScopedPointer subSurface1(m_subCompositor->createSubSurface(QPointer(surface1.data()), QPointer(parent.data()))); + QVERIFY(subSurfaceCreatedSpy.wait()); + SubSurfaceInterface *serverSubSurface1 = subSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverSubSurface1); + subSurfaceCreatedSpy.clear(); + QScopedPointer subSurface2(m_subCompositor->createSubSurface(QPointer(surface2.data()), QPointer(parent.data()))); + QVERIFY(subSurfaceCreatedSpy.wait()); + SubSurfaceInterface *serverSubSurface2 = subSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverSubSurface2); + subSurfaceCreatedSpy.clear(); + QScopedPointer subSurface3(m_subCompositor->createSubSurface(QPointer(surface3.data()), QPointer(parent.data()))); + QVERIFY(subSurfaceCreatedSpy.wait()); + SubSurfaceInterface *serverSubSurface3 = subSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverSubSurface3); + subSurfaceCreatedSpy.clear(); + + // so far the stacking order should still be empty + QVERIFY(serverSubSurface1->parentSurface()->childSubSurfaces().isEmpty()); + + // commiting the parent should create the stacking order + parent->commit(Surface::CommitFlag::None); + // ensure it's processed on server side + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().count(), 3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface1); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(1).data(), serverSubSurface2); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(2).data(), serverSubSurface3); + + // raising subsurface1 should place it to top of stack + subSurface1->raise(); + // ensure it's processed on server side + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + // but as long as parent is not committed it shouldn't change on server side + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface1); + // after commit it's changed + parent->commit(Surface::CommitFlag::None); + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().count(), 3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface2); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(1).data(), serverSubSurface3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(2).data(), serverSubSurface1); + + // try placing 3 above 1, should result in 2, 1, 3 + subSurface3->placeAbove(QPointer(subSurface1.data())); + parent->commit(Surface::CommitFlag::None); + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().count(), 3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface2); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(1).data(), serverSubSurface1); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(2).data(), serverSubSurface3); + + // try placing 3 above 2, should result in 2, 3, 1 + subSurface3->placeAbove(QPointer(subSurface2.data())); + parent->commit(Surface::CommitFlag::None); + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().count(), 3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface2); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(1).data(), serverSubSurface3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(2).data(), serverSubSurface1); + + // try placing 1 above 3 - shouldn't change + subSurface1->placeAbove(QPointer(subSurface3.data())); + parent->commit(Surface::CommitFlag::None); + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().count(), 3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface2); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(1).data(), serverSubSurface3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(2).data(), serverSubSurface1); + + // and 2 above 3 - > 3, 2, 1 + subSurface2->placeAbove(QPointer(subSurface3.data())); + parent->commit(Surface::CommitFlag::None); + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().count(), 3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(1).data(), serverSubSurface2); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(2).data(), serverSubSurface1); +} + +void TestSubSurface::testPlaceBelow() +{ + using namespace KWayland::Client; + using namespace KWayland::Server; + // create needed Surfaces (one parent, three client + QScopedPointer surface1(m_compositor->createSurface()); + QScopedPointer surface2(m_compositor->createSurface()); + QScopedPointer surface3(m_compositor->createSurface()); + QScopedPointer parent(m_compositor->createSurface()); + + QSignalSpy subSurfaceCreatedSpy(m_subcompositorInterface, SIGNAL(subSurfaceCreated(KWayland::Server::SubSurfaceInterface*))); + QVERIFY(subSurfaceCreatedSpy.isValid()); + + // create the SubSurfaces for surface of parent + QScopedPointer subSurface1(m_subCompositor->createSubSurface(QPointer(surface1.data()), QPointer(parent.data()))); + QVERIFY(subSurfaceCreatedSpy.wait()); + SubSurfaceInterface *serverSubSurface1 = subSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverSubSurface1); + subSurfaceCreatedSpy.clear(); + QScopedPointer subSurface2(m_subCompositor->createSubSurface(QPointer(surface2.data()), QPointer(parent.data()))); + QVERIFY(subSurfaceCreatedSpy.wait()); + SubSurfaceInterface *serverSubSurface2 = subSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverSubSurface2); + subSurfaceCreatedSpy.clear(); + QScopedPointer subSurface3(m_subCompositor->createSubSurface(QPointer(surface3.data()), QPointer(parent.data()))); + QVERIFY(subSurfaceCreatedSpy.wait()); + SubSurfaceInterface *serverSubSurface3 = subSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverSubSurface3); + subSurfaceCreatedSpy.clear(); + + // so far the stacking order should still be empty + QVERIFY(serverSubSurface1->parentSurface()->childSubSurfaces().isEmpty()); + + // commiting the parent should create the stacking order + parent->commit(Surface::CommitFlag::None); + // ensure it's processed on server side + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().count(), 3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface1); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(1).data(), serverSubSurface2); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(2).data(), serverSubSurface3); + + // lowering subsurface3 should place it to the bottom of stack + subSurface3->lower(); + // ensure it's processed on server side + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + // but as long as parent is not committed it shouldn't change on server side + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface1); + // after commit it's changed + parent->commit(Surface::CommitFlag::None); + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().count(), 3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(1).data(), serverSubSurface1); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(2).data(), serverSubSurface2); + + // place 1 below 3 -> 1, 3, 2 + subSurface1->placeBelow(QPointer(subSurface3.data())); + parent->commit(Surface::CommitFlag::None); + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().count(), 3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface1); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(1).data(), serverSubSurface3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(2).data(), serverSubSurface2); + + // 2 below 3 -> 1, 2, 3 + subSurface2->placeBelow(QPointer(subSurface3.data())); + parent->commit(Surface::CommitFlag::None); + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().count(), 3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface1); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(1).data(), serverSubSurface2); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(2).data(), serverSubSurface3); + + // 1 below 2 -> shouldn't change + subSurface1->placeBelow(QPointer(subSurface2.data())); + parent->commit(Surface::CommitFlag::None); + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().count(), 3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface1); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(1).data(), serverSubSurface2); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(2).data(), serverSubSurface3); + + // and 3 below 1 -> 3, 1, 2 + subSurface3->placeBelow(QPointer(subSurface1.data())); + parent->commit(Surface::CommitFlag::None); + wl_display_flush(m_connection->display()); + QCoreApplication::processEvents(); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().count(), 3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(0).data(), serverSubSurface3); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(1).data(), serverSubSurface1); + QCOMPARE(serverSubSurface1->parentSurface()->childSubSurfaces().at(2).data(), serverSubSurface2); +} + +void TestSubSurface::testDestroy() +{ + using namespace KWayland::Client; + + // create two Surfaces + QScopedPointer surface(m_compositor->createSurface()); + QScopedPointer parent(m_compositor->createSurface()); + // create subSurface for surface of parent + QScopedPointer subSurface(m_subCompositor->createSubSurface(QPointer(surface.data()), QPointer(parent.data()))); + + connect(m_connection, &ConnectionThread::connectionDied, m_compositor, &Compositor::destroy); + connect(m_connection, &ConnectionThread::connectionDied, m_subCompositor, &SubCompositor::destroy); + connect(m_connection, &ConnectionThread::connectionDied, m_queue, &EventQueue::destroy); + connect(m_connection, &ConnectionThread::connectionDied, surface.data(), &Surface::destroy); + connect(m_connection, &ConnectionThread::connectionDied, parent.data(), &Surface::destroy); + connect(m_connection, &ConnectionThread::connectionDied, subSurface.data(), &SubSurface::destroy); + QVERIFY(subSurface->isValid()); + + QSignalSpy connectionDiedSpy(m_connection, SIGNAL(connectionDied())); + QVERIFY(connectionDiedSpy.isValid()); + delete m_display; + m_display = nullptr; + QVERIFY(connectionDiedSpy.wait()); + + // now the pool should be destroyed; + QVERIFY(!subSurface->isValid()); + + // calling destroy again should not fail + subSurface->destroy(); +} + +void TestSubSurface::testCast() +{ + using namespace KWayland::Client; + + // create two Surfaces + QScopedPointer surface(m_compositor->createSurface()); + QScopedPointer parent(m_compositor->createSurface()); + // create subSurface for surface of parent + QScopedPointer subSurface(m_subCompositor->createSubSurface(QPointer(surface.data()), QPointer(parent.data()))); + + QCOMPARE(SubSurface::get(*(subSurface.data())), QPointer(subSurface.data())); +} + +QTEST_MAIN(TestSubSurface) +#include "test_wayland_subsurface.moc" diff --git a/src/wayland/display.cpp b/src/wayland/display.cpp index 07a08b68da..fe71df50b9 100644 --- a/src/wayland/display.cpp +++ b/src/wayland/display.cpp @@ -22,6 +22,7 @@ License along with this library. If not, see . #include "output_interface.h" #include "seat_interface.h" #include "shell_interface.h" +#include "subcompositor_interface.h" #include #include @@ -164,6 +165,13 @@ SeatInterface *Display::createSeat(QObject *parent) return seat; } +SubCompositorInterface *Display::createSubCompositor(QObject *parent) +{ + auto c = new SubCompositorInterface(this, parent); + connect(this, &Display::aboutToTerminate, c, [this,c] { delete c; }); + return c; +} + void Display::createShm() { Q_ASSERT(d->running); diff --git a/src/wayland/display.h b/src/wayland/display.h index a47ece87eb..553ed14c22 100644 --- a/src/wayland/display.h +++ b/src/wayland/display.h @@ -37,6 +37,7 @@ class CompositorInterface; class OutputInterface; class SeatInterface; class ShellInterface; +class SubCompositorInterface; class KWAYLANDSERVER_EXPORT Display : public QObject { @@ -68,6 +69,7 @@ public: void createShm(); ShellInterface *createShell(QObject *parent = nullptr); SeatInterface *createSeat(QObject *parent = nullptr); + SubCompositorInterface *createSubCompositor(QObject *parent = nullptr); Q_SIGNALS: void socketNameChanged(const QString&); diff --git a/src/wayland/subcompositor_interface.cpp b/src/wayland/subcompositor_interface.cpp new file mode 100644 index 0000000000..a7bcacb258 --- /dev/null +++ b/src/wayland/subcompositor_interface.cpp @@ -0,0 +1,337 @@ +/******************************************************************** +Copyright 2014 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#include "subcompositor_interface.h" +#include "subsurface_interface_p.h" +#include "display.h" +#include "surface_interface_p.h" +// Wayland +#include + +namespace KWayland +{ +namespace Server +{ + +static const quint32 s_version = 1; + +class SubCompositorInterface::Private +{ +public: + Private(SubCompositorInterface *q, Display *d); + void create(); + + Display *display; + wl_global *compositor; + +private: + void bind(wl_client *client, uint32_t version, uint32_t id); + void subsurface(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface, wl_resource *parent); + + static void bind(wl_client *client, void *data, uint32_t version, uint32_t id); + static void unbind(wl_resource *resource); + static void destroyCallback(wl_client *client, wl_resource *resource); + static void subsurfaceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface, wl_resource *parent); + + static Private *cast(wl_resource *r) { + return reinterpret_cast(wl_resource_get_user_data(r)); + } + + SubCompositorInterface *q; + static const struct wl_subcompositor_interface s_interface; +}; + +const struct wl_subcompositor_interface SubCompositorInterface::Private::s_interface = { + destroyCallback, + subsurfaceCallback +}; + +SubCompositorInterface::Private::Private(SubCompositorInterface *q, Display *d) + : display(d) + , compositor(nullptr) + , q(q) +{ +} + +void SubCompositorInterface::Private::create() +{ + Q_ASSERT(!compositor); + compositor = wl_global_create(*display, &wl_subcompositor_interface, s_version, this, bind); +} + +void SubCompositorInterface::Private::bind(wl_client *client, void *data, uint32_t version, uint32_t id) +{ + reinterpret_cast(data)->bind(client, version, id); +} + +void SubCompositorInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) +{ + wl_resource *resource = wl_resource_create(client, &wl_subcompositor_interface, qMin(version, s_version), id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &s_interface, this, unbind); +} + +void SubCompositorInterface::Private::unbind(wl_resource *resource) +{ + Q_UNUSED(resource) +} + +void SubCompositorInterface::Private::destroyCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + Q_UNUSED(resource) +} + +void SubCompositorInterface::Private::subsurfaceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface, wl_resource *sparent) +{ + cast(resource)->subsurface(client, resource, id, surface, sparent); +} + +void SubCompositorInterface::Private::subsurface(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *nativeSurface, wl_resource *nativeParentSurface) +{ + Q_UNUSED(client) + SurfaceInterface *surface = SurfaceInterface::get(nativeSurface); + SurfaceInterface *parentSurface = SurfaceInterface::get(nativeParentSurface); + if (!surface || !parentSurface) { + wl_resource_post_error(resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, "Surface or parent surface not found"); + return; + } + if (surface == parentSurface) { + wl_resource_post_error(resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, "Cannot become sub composite to same surface"); + return; + } + // TODO: add check that surface is not already used in an interface (e.g. Shell) + // TODO: add check that parentSurface is not a child of surface + SubSurfaceInterface *s = new SubSurfaceInterface(q); + s->d->create(client, wl_resource_get_version(resource), id, surface, parentSurface); + if (!s->subSurface()) { + wl_resource_post_no_memory(resource); + delete s; + return; + } + emit q->subSurfaceCreated(s); +} + +SubCompositorInterface::SubCompositorInterface(Display *display, QObject *parent) + : QObject(parent) + , d(new Private(this, display)) +{ +} + +SubCompositorInterface::~SubCompositorInterface() +{ + destroy(); +} + +void SubCompositorInterface::destroy() +{ + if (!d->compositor) { + return; + } + wl_global_destroy(d->compositor); + d->compositor = nullptr; +} + +void SubCompositorInterface::create() +{ + d->create(); +} + +bool SubCompositorInterface::isValid() const +{ + return d->compositor != nullptr; +} + +const struct wl_subsurface_interface SubSurfaceInterface::Private::s_interface = { + destroyCallback, + setPositionCallback, + placeAboveCallback, + placeBelowCallback, + setSyncCallback, + setDeSyncCallback +}; + +SubSurfaceInterface::Private *SubSurfaceInterface::Private::cast(wl_resource *r) +{ + return reinterpret_cast(wl_resource_get_user_data(r)); +} + +SubSurfaceInterface::Private::Private(SubSurfaceInterface *q) + : q(q) +{ +} + +SubSurfaceInterface::Private::~Private() +{ + // no need to notify the surface as it's tracking a QPointer which will be reset automatically + if (parent) { + parent->d->removeChild(QPointer(q)); + } + if (subSurface) { + wl_resource_destroy(subSurface); + } +} + +void SubSurfaceInterface::Private::create(wl_client *client, quint32 version, quint32 id, SurfaceInterface *s, SurfaceInterface *p) +{ + Q_ASSERT(!subSurface); + subSurface = wl_resource_create(client, &wl_subsurface_interface, version, id); + if (!subSurface) { + return; + } + surface = s; + parent = p; + surface->d->subSurface = QPointer(q); + parent->d->addChild(QPointer(q)); + wl_resource_set_implementation(subSurface, &s_interface, this, unbind); +} + +void SubSurfaceInterface::Private::commit() +{ + if (scheduledPosChange) { + scheduledPosChange = false; + pos = scheduledPos; + scheduledPos = QPoint(); + emit q->positionChanged(pos); + } +} + +void SubSurfaceInterface::Private::unbind(wl_resource *r) +{ + auto s = cast(r); + s->subSurface = nullptr; + s->q->deleteLater(); +} + +void SubSurfaceInterface::Private::destroyCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + cast(resource)->q->deleteLater(); +} + +void SubSurfaceInterface::Private::setPositionCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y) +{ + Q_UNUSED(client) + // TODO: is this a fixed position? + cast(resource)->setPosition(QPoint(x, y)); +} + +void SubSurfaceInterface::Private::setPosition(const QPoint &p) +{ + if (scheduledPos == p) { + return; + } + scheduledPos = p; + scheduledPosChange = true; +} + +void SubSurfaceInterface::Private::placeAboveCallback(wl_client *client, wl_resource *resource, wl_resource *sibling) +{ + Q_UNUSED(client) + cast(resource)->placeAbove(SurfaceInterface::get(sibling)); +} + +void SubSurfaceInterface::Private::placeAbove(SurfaceInterface *sibling) +{ + if (parent.isNull()) { + // TODO: raise error + return; + } + if (!parent->d->raiseChild(QPointer(q), sibling)) { + wl_resource_post_error(subSurface, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, "Incorrect sibling"); + } +} + +void SubSurfaceInterface::Private::placeBelowCallback(wl_client *client, wl_resource *resource, wl_resource *sibling) +{ + Q_UNUSED(client) + cast(resource)->placeBelow(SurfaceInterface::get(sibling)); +} + +void SubSurfaceInterface::Private::placeBelow(SurfaceInterface *sibling) +{ + if (parent.isNull()) { + // TODO: raise error + return; + } + if (!parent->d->lowerChild(QPointer(q), sibling)) { + wl_resource_post_error(subSurface, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, "Incorrect sibling"); + } +} + +void SubSurfaceInterface::Private::setSyncCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + cast(resource)->setMode(Mode::Synchronized); +} + +void SubSurfaceInterface::Private::setDeSyncCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + cast(resource)->setMode(Mode::Desynchronized); +} + +void SubSurfaceInterface::Private::setMode(Mode m) +{ + if (mode == m) { + return; + } + mode = m; + emit q->modeChanged(m); +} + +SubSurfaceInterface::SubSurfaceInterface(SubCompositorInterface *parent) + : QObject(/*parent*/) + , d(new Private(this)) +{ + Q_UNUSED(parent) +} + +SubSurfaceInterface::~SubSurfaceInterface() = default; + +QPoint SubSurfaceInterface::position() const +{ + return d->pos; +} + +wl_resource *SubSurfaceInterface::subSurface() +{ + return d->subSurface; +} + +QPointer SubSurfaceInterface::surface() +{ + return d->surface; +} + +QPointer SubSurfaceInterface::parentSurface() +{ + return d->parent; +} + +SubSurfaceInterface::Mode SubSurfaceInterface::mode() const +{ + return d->mode; +} + +} +} diff --git a/src/wayland/subcompositor_interface.h b/src/wayland/subcompositor_interface.h new file mode 100644 index 0000000000..7ff142cf9c --- /dev/null +++ b/src/wayland/subcompositor_interface.h @@ -0,0 +1,97 @@ +/******************************************************************** +Copyright 2014 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#ifndef WAYLAND_SERVER_SUBCOMPOSITOR_INTERFACE_H +#define WAYLAND_SERVER_SUBCOMPOSITOR_INTERFACE_H + +#include +#include + +#include + +struct wl_resource; + +namespace KWayland +{ +namespace Server +{ + +class Display; +class SurfaceInterface; +class SubSurfaceInterface; + +class KWAYLANDSERVER_EXPORT SubCompositorInterface : public QObject +{ + Q_OBJECT +public: + virtual ~SubCompositorInterface(); + + void create(); + void destroy(); + bool isValid() const; + +Q_SIGNALS: + void subSurfaceCreated(KWayland::Server::SubSurfaceInterface*); + +private: + explicit SubCompositorInterface(Display *display, QObject *parent = nullptr); + friend class Display; + class Private; + QScopedPointer d; +}; + +class KWAYLANDSERVER_EXPORT SubSurfaceInterface : public QObject +{ + Q_OBJECT + Q_PROPERTY(QPoint position READ position NOTIFY positionChanged) + Q_PROPERTY(KWayland::Server::SubSurfaceInterface::Mode mode READ mode NOTIFY modeChanged) +public: + virtual ~SubSurfaceInterface(); + + QPoint position() const; + + enum class Mode { + Synchronized, + Desynchronized + }; + Mode mode() const; + + wl_resource *subSurface(); + QPointer surface(); + QPointer parentSurface(); + +Q_SIGNALS: + void positionChanged(const QPoint&); + void modeChanged(KWayland::Server::SubSurfaceInterface::Mode); + +private: + friend class SubCompositorInterface; + friend class SurfaceInterface; + explicit SubSurfaceInterface(SubCompositorInterface *parent); + + class Private; + QScopedPointer d; +}; + +} +} + +Q_DECLARE_METATYPE(KWayland::Server::SubSurfaceInterface::Mode) + +#endif diff --git a/src/wayland/subsurface_interface_p.h b/src/wayland/subsurface_interface_p.h new file mode 100644 index 0000000000..82bf57337f --- /dev/null +++ b/src/wayland/subsurface_interface_p.h @@ -0,0 +1,75 @@ +/******************************************************************** +Copyright 2014 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#ifndef WAYLAND_SERVER_SUBSURFACE_INTERFACE_P_H +#define WAYLAND_SERVER_SUBSURFACE_INTERFACE_P_H + +#include "subcompositor_interface.h" +// Qt +#include +// Wayland +#include + +namespace KWayland +{ +namespace Server +{ + +class SubSurfaceInterface::Private +{ +public: + Private(SubSurfaceInterface *q); + ~Private(); + + void create(wl_client *client, quint32 version, quint32 id, SurfaceInterface *surface, SurfaceInterface *parent); + void commit(); + + QPoint pos = QPoint(0, 0); + QPoint scheduledPos = QPoint(); + bool scheduledPosChange = false; + Mode mode = Mode::Synchronized; + wl_resource *subSurface = nullptr; + QPointer surface; + QPointer parent; + +private: + void setMode(Mode mode); + void setPosition(const QPoint &pos); + void placeAbove(SurfaceInterface *sibling); + void placeBelow(SurfaceInterface *sibling); + + static void unbind(wl_resource *r); + static void destroyCallback(wl_client *client, wl_resource *resource); + static void setPositionCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y); + static void placeAboveCallback(wl_client *client, wl_resource *resource, wl_resource *sibling); + static void placeBelowCallback(wl_client *client, wl_resource *resource, wl_resource *sibling); + static void setSyncCallback(wl_client *client, wl_resource *resource); + static void setDeSyncCallback(wl_client *client, wl_resource *resource); + + static Private *cast(wl_resource *r); + + SubSurfaceInterface *q; + + static const struct wl_subsurface_interface s_interface; +}; + +} +} + +#endif diff --git a/src/wayland/surface_interface.cpp b/src/wayland/surface_interface.cpp index a983f4d620..79a74e1073 100644 --- a/src/wayland/surface_interface.cpp +++ b/src/wayland/surface_interface.cpp @@ -18,75 +18,21 @@ You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "surface_interface.h" +#include "surface_interface_p.h" #include "buffer_interface.h" #include "compositor_interface.h" +#include "subcompositor_interface.h" +#include "subsurface_interface_p.h" // Wayland #include +// std +#include namespace KWayland { namespace Server { -class SurfaceInterface::Private -{ -public: - struct State { - QRegion damage = QRegion(); - QRegion opaque = QRegion(); - QRegion input = QRegion(); - qint32 scale = 1; - OutputInterface::Transform transform = OutputInterface::Transform::Normal; - QList callbacks = QList(); - QPoint offset = QPoint(); - BufferInterface *buffer = nullptr; - }; - Private(SurfaceInterface *q, CompositorInterface *c); - ~Private(); - - void create(wl_client *client, quint32 version, quint32 id); - void destroy(); - - static SurfaceInterface *get(wl_resource *native); - - CompositorInterface *compositor; - wl_resource *surface = nullptr; - wl_client *client = nullptr; - State current; - State pending; - -private: - void commit(); - void damage(const QRect &rect); - void setScale(qint32 scale); - void setTransform(OutputInterface::Transform transform); - void addFrameCallback(uint32_t callback); - void attachBuffer(wl_resource *buffer, const QPoint &offset); - - static Private *cast(wl_resource *r) { - return reinterpret_cast(wl_resource_get_user_data(r)); - } - - static void unbind(wl_resource *r); - static void destroyFrameCallback(wl_resource *r); - - static void destroyCallback(wl_client *client, wl_resource *resource); - static void attachCallback(wl_client *client, wl_resource *resource, wl_resource *buffer, int32_t sx, int32_t sy); - static void damageCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height); - static void frameCallaback(wl_client *client, wl_resource *resource, uint32_t callback); - static void opaqueRegionCallback(wl_client *client, wl_resource *resource, wl_resource *region); - static void inputRegionCallback(wl_client *client, wl_resource *resource, wl_resource *region); - static void commitCallback(wl_client *client, wl_resource *resource); - // since version 2 - static void bufferTransformCallback(wl_client *client, wl_resource *resource, int32_t transform); - // since version 3 - static void bufferScaleCallback(wl_client *client, wl_resource *resource, int32_t scale); - - SurfaceInterface *q; - static const struct wl_surface_interface s_interface; - static QList s_allSurfaces; -}; - QList SurfaceInterface::Private::s_allSurfaces; SurfaceInterface::Private::Private(SurfaceInterface *q, CompositorInterface *c) @@ -102,6 +48,88 @@ SurfaceInterface::Private::~Private() s_allSurfaces.removeAll(this); } +SurfaceInterface::Private *SurfaceInterface::Private::cast(wl_resource *r) +{ + return reinterpret_cast(wl_resource_get_user_data(r)); +} + +void SurfaceInterface::Private::addChild(QPointer< SubSurfaceInterface > subSurface) +{ + pending.children.append(subSurface); +} + +void SurfaceInterface::Private::removeChild(QPointer< SubSurfaceInterface > subSurface) +{ + pending.children.removeAll(subSurface); +} + +bool SurfaceInterface::Private::raiseChild(QPointer subsurface, SurfaceInterface *sibling) +{ + auto it = std::find(pending.children.begin(), pending.children.end(), subsurface); + if (it == pending.children.end()) { + return false; + } + if (pending.children.count() == 1) { + // nothing to do + return true; + } + if (sibling == q) { + // it's to the parent, so needs to become last item + pending.children.append(*it); + pending.children.erase(it); + return true; + } + if (!sibling->subSurface()) { + // not a sub surface + return false; + } + auto siblingIt = std::find(pending.children.begin(), pending.children.end(), sibling->subSurface()); + if (siblingIt == pending.children.end() || siblingIt == it) { + // not a sibling + return false; + } + auto value = (*it); + pending.children.erase(it); + // find the iterator again + siblingIt = std::find(pending.children.begin(), pending.children.end(), sibling->subSurface()); + pending.children.insert(++siblingIt, value); + return true; +} + +bool SurfaceInterface::Private::lowerChild(QPointer subsurface, SurfaceInterface *sibling) +{ + auto it = std::find(pending.children.begin(), pending.children.end(), subsurface); + if (it == pending.children.end()) { + return false; + } + if (pending.children.count() == 1) { + // nothing to do + return true; + } + if (sibling == q) { + // it's to the parent, so needs to become first item + auto value = *it; + pending.children.erase(it); + pending.children.prepend(value); + return true; + } + if (!sibling->subSurface()) { + // not a sub surface + return false; + } + auto siblingIt = std::find(pending.children.begin(), pending.children.end(), sibling->subSurface()); + if (siblingIt == pending.children.end() || siblingIt == it) { + // not a sibling + return false; + } + auto value = (*it); + pending.children.erase(it); + // find the iterator again + siblingIt = std::find(pending.children.begin(), pending.children.end(), sibling->subSurface()); + pending.children.insert(siblingIt, value); + return true; +} + SurfaceInterface *SurfaceInterface::Private::get(wl_resource *native) { auto it = std::find_if(s_allSurfaces.constBegin(), s_allSurfaces.constEnd(), [native](Private *s) { @@ -202,6 +230,14 @@ void SurfaceInterface::Private::commit() // copy values current = pending; pending = State{}; + pending.children = current.children; + // commit all subSurfaces to apply position changes + for (auto it = current.children.constBegin(); it != current.children.constEnd(); ++it) { + if (!(*it)) { + continue; + } + (*it)->d->commit(); + } if (opaqueRegionChanged) { emit q->opaqueChanged(current.opaque); } @@ -371,5 +407,15 @@ SurfaceInterface *SurfaceInterface::get(wl_resource *native) return Private::get(native); } +QList< QPointer< SubSurfaceInterface > > SurfaceInterface::childSubSurfaces() const +{ + return d->current.children; +} + +QPointer< SubSurfaceInterface > SurfaceInterface::subSurface() const +{ + return d->subSurface; +} + } } diff --git a/src/wayland/surface_interface.h b/src/wayland/surface_interface.h index c98cc93f8f..92f0629615 100644 --- a/src/wayland/surface_interface.h +++ b/src/wayland/surface_interface.h @@ -23,6 +23,7 @@ License along with this library. If not, see . #include "output_interface.h" #include +#include #include #include @@ -33,6 +34,7 @@ namespace Server { class BufferInterface; class CompositorInterface; +class SubSurfaceInterface; class KWAYLANDSERVER_EXPORT SurfaceInterface : public QObject { @@ -60,6 +62,15 @@ public: BufferInterface *buffer(); QPoint offset() const; + /** + * @returns The SubSurface for this Surface in case there is one. + **/ + QPointer subSurface() const; + /** + * @returns Children in stacking order from bottom (first) to top (last). + **/ + QList> childSubSurfaces() const; + static SurfaceInterface *get(wl_resource *native); Q_SIGNALS: @@ -71,6 +82,7 @@ Q_SIGNALS: private: friend class CompositorInterface; + friend class SubSurfaceInterface; explicit SurfaceInterface(CompositorInterface *parent); class Private; diff --git a/src/wayland/surface_interface_p.h b/src/wayland/surface_interface_p.h new file mode 100644 index 0000000000..530cd17aba --- /dev/null +++ b/src/wayland/surface_interface_p.h @@ -0,0 +1,100 @@ +/******************************************************************** +Copyright 2014 Martin Gräßlin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#ifndef WAYLAND_SERVER_SURFACE_INTERFACE_P_H +#define WAYLAND_SERVER_SURFACE_INTERFACE_P_H + +#include "surface_interface.h" +// Wayland +#include + +namespace KWayland +{ +namespace Server +{ + +class SurfaceInterface::Private +{ +public: + struct State { + QRegion damage = QRegion(); + QRegion opaque = QRegion(); + QRegion input = QRegion(); + qint32 scale = 1; + OutputInterface::Transform transform = OutputInterface::Transform::Normal; + QList callbacks = QList(); + QPoint offset = QPoint(); + BufferInterface *buffer = nullptr; + // stacking order: bottom (first) -> top (last) + QList> children; + }; + Private(SurfaceInterface *q, CompositorInterface *c); + ~Private(); + + void create(wl_client *client, quint32 version, quint32 id); + void destroy(); + + void addChild(QPointer subsurface); + void removeChild(QPointer subsurface); + bool raiseChild(QPointer subsurface, SurfaceInterface *sibling); + bool lowerChild(QPointer subsurface, SurfaceInterface *sibling); + + static SurfaceInterface *get(wl_resource *native); + + CompositorInterface *compositor; + wl_resource *surface = nullptr; + wl_client *client = nullptr; + State current; + State pending; + QPointer subSurface; + +private: + void commit(); + void damage(const QRect &rect); + void setScale(qint32 scale); + void setTransform(OutputInterface::Transform transform); + void addFrameCallback(uint32_t callback); + void attachBuffer(wl_resource *buffer, const QPoint &offset); + + static Private *cast(wl_resource *r); + + static void unbind(wl_resource *r); + static void destroyFrameCallback(wl_resource *r); + + static void destroyCallback(wl_client *client, wl_resource *resource); + static void attachCallback(wl_client *client, wl_resource *resource, wl_resource *buffer, int32_t sx, int32_t sy); + static void damageCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height); + static void frameCallaback(wl_client *client, wl_resource *resource, uint32_t callback); + static void opaqueRegionCallback(wl_client *client, wl_resource *resource, wl_resource *region); + static void inputRegionCallback(wl_client *client, wl_resource *resource, wl_resource *region); + static void commitCallback(wl_client *client, wl_resource *resource); + // since version 2 + static void bufferTransformCallback(wl_client *client, wl_resource *resource, int32_t transform); + // since version 3 + static void bufferScaleCallback(wl_client *client, wl_resource *resource, int32_t scale); + + SurfaceInterface *q; + static const struct wl_surface_interface s_interface; + static QList s_allSurfaces; +}; + +} +} + +#endif