diff --git a/autotests/client/CMakeLists.txt b/autotests/client/CMakeLists.txt --- a/autotests/client/CMakeLists.txt +++ b/autotests/client/CMakeLists.txt @@ -342,13 +342,26 @@ ######################################################## set( testXdgShellV5_SRCS test_xdg_shell.cpp + test_xdg_shell_v5.cpp ) add_executable(testXdgShellV5 ${testXdgShellV5_SRCS}) target_link_libraries( testXdgShellV5 Qt5::Test Qt5::Gui KF5::WaylandServer KF5::WaylandClient Wayland::Client) add_test(NAME kwayland-testXdgShellV5 COMMAND testXdgShellV5) ecm_mark_as_test(testXdgShellV5) ######################################################## +# Test XdgShellV6 +######################################################## +set( testXdgShellV6_SRCS + test_xdg_shell.cpp + test_xdg_shell_v6.cpp + ) +add_executable(testXdgShellV6 ${testXdgShellV6_SRCS}) +target_link_libraries( testXdgShellV6 Qt5::Test Qt5::Gui KF5::WaylandServer KF5::WaylandClient Wayland::Client) +add_test(kwayland-testXdgShellV6 testXdgShellV6) +ecm_mark_as_test(testXdgShellV6) + +######################################################## # Test Pointer Constraints ######################################################## add_executable(testPointerConstraints test_pointer_constraints.cpp) diff --git a/autotests/client/test_xdg_shell.h b/autotests/client/test_xdg_shell.h new file mode 100644 --- /dev/null +++ b/autotests/client/test_xdg_shell.h @@ -0,0 +1,101 @@ +/******************************************************************** +Copyright 2016 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 +// client +#include "../../src/client/xdgshell.h" +#include "../../src/client/connection_thread.h" +#include "../../src/client/compositor.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/registry.h" +#include "../../src/client/output.h" +#include "../../src/client/seat.h" +#include "../../src/client/shm_pool.h" +#include "../../src/client/surface.h" +// server +#include "../../src/server/display.h" +#include "../../src/server/compositor_interface.h" +#include "../../src/server/output_interface.h" +#include "../../src/server/seat_interface.h" +#include "../../src/server/surface_interface.h" +#include "../../src/server/xdgshell_interface.h" + +using namespace KWayland::Client; +using namespace KWayland::Server; + +Q_DECLARE_METATYPE(Qt::MouseButton) + +class XdgShellTest : public QObject +{ + Q_OBJECT + +protected: + XdgShellTest(XdgShellInterfaceVersion version); +private Q_SLOTS: + void init(); + void cleanup(); + + void testCreateSurface(); + void testTitle(); + void testWindowClass(); + void testMaximize(); + void testMinimize(); + void testFullscreen(); + void testShowWindowMenu(); + void testMove(); + void testResize_data(); + void testResize(); + void testTransient(); + void testPing(); + void testClose(); + void testConfigureStates_data(); + void testConfigureStates(); + void testConfigureMultipleAcks(); + +protected: + XdgShellInterface *m_xdgShellInterface = nullptr; + Compositor *m_compositor = nullptr; + XdgShell *m_xdgShell = nullptr; + Display *m_display = nullptr; + CompositorInterface *m_compositorInterface = nullptr; + OutputInterface *m_o1Interface = nullptr; + OutputInterface *m_o2Interface = nullptr; + SeatInterface *m_seatInterface = nullptr; + ConnectionThread *m_connection = nullptr; + QThread *m_thread = nullptr; + EventQueue *m_queue = nullptr; + ShmPool *m_shmPool = nullptr; + Output *m_output1 = nullptr; + Output *m_output2 = nullptr; + Seat *m_seat = nullptr; + +private: + XdgShellInterfaceVersion m_version; +}; + +#define SURFACE \ + QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated); \ + QVERIFY(xdgSurfaceCreatedSpy.isValid()); \ + QScopedPointer surface(m_compositor->createSurface()); \ + QScopedPointer xdgSurface(m_xdgShell->createSurface(surface.data())); \ + QCOMPARE(xdgSurface->size(), QSize()); \ + QVERIFY(xdgSurfaceCreatedSpy.wait()); \ + auto serverXdgSurface = xdgSurfaceCreatedSpy.first().first().value(); \ + QVERIFY(serverXdgSurface); diff --git a/autotests/client/test_xdg_shell.cpp b/autotests/client/test_xdg_shell.cpp --- a/autotests/client/test_xdg_shell.cpp +++ b/autotests/client/test_xdg_shell.cpp @@ -17,72 +17,12 @@ You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ -// Qt -#include -// client -#include "../../src/client/xdgshell.h" -#include "../../src/client/connection_thread.h" -#include "../../src/client/compositor.h" -#include "../../src/client/event_queue.h" -#include "../../src/client/registry.h" -#include "../../src/client/output.h" -#include "../../src/client/seat.h" -#include "../../src/client/shm_pool.h" -#include "../../src/client/surface.h" -// server -#include "../../src/server/display.h" -#include "../../src/server/compositor_interface.h" -#include "../../src/server/output_interface.h" -#include "../../src/server/seat_interface.h" -#include "../../src/server/surface_interface.h" -#include "../../src/server/xdgshell_interface.h" - -using namespace KWayland::Client; -using namespace KWayland::Server; - -Q_DECLARE_METATYPE(Qt::MouseButton) - -class XdgShellTest : public QObject -{ - Q_OBJECT -private Q_SLOTS: - void init(); - void cleanup(); - - void testCreateSurface(); - void testTitle(); - void testWindowClass(); - void testMaximize(); - void testMinimize(); - void testFullscreen(); - void testShowWindowMenu(); - void testMove(); - void testResize_data(); - void testResize(); - void testTransient(); - void testClose(); - void testConfigureStates_data(); - void testConfigureStates(); - void testConfigureMultipleAcks(); - void testPopup(); - -private: - Display *m_display = nullptr; - CompositorInterface *m_compositorInterface = nullptr; - OutputInterface *m_o1Interface = nullptr; - OutputInterface *m_o2Interface = nullptr; - SeatInterface *m_seatInterface = nullptr; - XdgShellInterface *m_xdgShellInterface = nullptr; - ConnectionThread *m_connection = nullptr; - QThread *m_thread = nullptr; - EventQueue *m_queue = nullptr; - Compositor *m_compositor = nullptr; - ShmPool *m_shmPool = nullptr; - XdgShell *m_xdgShell = nullptr; - Output *m_output1 = nullptr; - Output *m_output2 = nullptr; - Seat *m_seat = nullptr; -}; + +#include "test_xdg_shell.h" + +XdgShellTest::XdgShellTest(XdgShellInterfaceVersion version): + m_version(version) +{} static const QString s_socketName = QStringLiteral("kwayland-test-xdg_shell-0"); @@ -107,8 +47,8 @@ m_seatInterface->create(); m_compositorInterface = m_display->createCompositor(m_display); m_compositorInterface->create(); - m_xdgShellInterface = m_display->createXdgShell(XdgShellInterfaceVersion::UnstableV5, m_display); - QCOMPARE(m_xdgShellInterface->interfaceVersion(), XdgShellInterfaceVersion::UnstableV5); + m_xdgShellInterface = m_display->createXdgShell(m_version, m_display); + QCOMPARE(m_xdgShellInterface->interfaceVersion(), m_version); m_xdgShellInterface->create(); // setup connection @@ -134,7 +74,11 @@ QVERIFY(interfaceAnnouncedSpy.isValid()); QSignalSpy outputAnnouncedSpy(®istry, &Registry::outputAnnounced); QVERIFY(outputAnnouncedSpy.isValid()); - QSignalSpy xdgShellAnnouncedSpy(®istry, &Registry::xdgShellUnstableV5Announced); + + auto shellAnnouncedSignal = m_version == XdgShellInterfaceVersion::UnstableV5 ? + &Registry::xdgShellUnstableV5Announced : &Registry::xdgShellUnstableV6Announced; + + QSignalSpy xdgShellAnnouncedSpy(®istry, shellAnnouncedSignal); QVERIFY(xdgShellAnnouncedSpy.isValid()); registry.setEventQueue(m_queue); registry.create(m_connection); @@ -160,8 +104,10 @@ QCOMPARE(xdgShellAnnouncedSpy.count(), 1); - m_xdgShell = registry.createXdgShell(registry.interface(Registry::Interface::XdgShellUnstableV5).name, - registry.interface(Registry::Interface::XdgShellUnstableV5).version, + Registry::Interface iface = m_version == XdgShellInterfaceVersion::UnstableV5 ? Registry::Interface::XdgShellUnstableV5 : Registry::Interface::XdgShellUnstableV6; + + m_xdgShell = registry.createXdgShell(registry.interface(iface).name, + registry.interface(iface).version, this); QVERIFY(m_xdgShell); QVERIFY(m_xdgShell->isValid()); @@ -238,16 +184,6 @@ QVERIFY(destroyedSpy.wait()); } -#define SURFACE \ - QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated); \ - QVERIFY(xdgSurfaceCreatedSpy.isValid()); \ - QScopedPointer surface(m_compositor->createSurface()); \ - QScopedPointer xdgSurface(m_xdgShell->createSurface(surface.data())); \ - QCOMPARE(xdgSurface->size(), QSize()); \ - QVERIFY(xdgSurfaceCreatedSpy.wait()); \ - auto serverXdgSurface = xdgSurfaceCreatedSpy.first().first().value(); \ - QVERIFY(serverXdgSurface); - void XdgShellTest::testTitle() { // this test verifies that we can change the title of a shell surface @@ -464,6 +400,30 @@ QVERIFY(!serverXdgSurface->isTransient()); } +void XdgShellTest::testPing() +{ + // this test verifies that a ping request is sent to the client + SURFACE + + QSignalSpy pingSpy(m_xdgShellInterface, &XdgShellInterface::pongReceived); + QVERIFY(pingSpy.isValid()); + + quint32 serial = m_xdgShellInterface->ping(); + QVERIFY(pingSpy.wait()); + QCOMPARE(pingSpy.count(), 1); + QCOMPARE(pingSpy.takeFirst().at(0).value(), serial); + + // test of a ping failure + // disconnecting the connection thread to the queue will break the connection and pings will do a timeout + disconnect(m_connection, &ConnectionThread::eventsRead, m_queue, &EventQueue::dispatch); + m_xdgShellInterface->ping(); + QSignalSpy pingDelayedSpy(m_xdgShellInterface, &XdgShellInterface::pingDelayed); + QVERIFY(pingDelayedSpy.wait()); + + QSignalSpy pingTimeoutSpy(m_xdgShellInterface, &XdgShellInterface::pingTimeout); + QVERIFY(pingTimeoutSpy.wait()); +} + void XdgShellTest::testClose() { // this test verifies that a close request is sent to the client @@ -599,51 +559,4 @@ QCOMPARE(xdgSurface->size(), QSize(30, 40)); } -void XdgShellTest::testPopup() -{ - // this test verifies that the creation of popups works correctly - SURFACE - QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); - QVERIFY(surfaceCreatedSpy.isValid()); - QSignalSpy xdgPopupSpy(m_xdgShellInterface, &XdgShellInterface::popupCreated); - QVERIFY(xdgPopupSpy.isValid()); - - QScopedPointer popupSurface(m_compositor->createSurface()); - QVERIFY(surfaceCreatedSpy.wait()); - - // TODO: proper serial - QScopedPointer xdgPopup(m_xdgShell->createPopup(popupSurface.data(), surface.data(), m_seat, 120, QPoint(10, 20))); - QVERIFY(xdgPopupSpy.wait()); - QCOMPARE(xdgPopupSpy.count(), 1); - QCOMPARE(xdgPopupSpy.first().at(1).value(), m_seatInterface); - QCOMPARE(xdgPopupSpy.first().at(2).value(), 120u); - auto serverXdgPopup = xdgPopupSpy.first().first().value(); - QVERIFY(serverXdgPopup); - - QCOMPARE(serverXdgPopup->surface(), surfaceCreatedSpy.first().first().value()); - QCOMPARE(serverXdgPopup->transientFor().data(), serverXdgSurface->surface()); - QCOMPARE(serverXdgPopup->transientOffset(), QPoint(10, 20)); - - // now also a popup for the popup - QScopedPointer popup2Surface(m_compositor->createSurface()); - QScopedPointer xdgPopup2(m_xdgShell->createPopup(popup2Surface.data(), popupSurface.data(), m_seat, 121, QPoint(5, 7))); - QVERIFY(xdgPopupSpy.wait()); - QCOMPARE(xdgPopupSpy.count(), 2); - QCOMPARE(xdgPopupSpy.last().at(1).value(), m_seatInterface); - QCOMPARE(xdgPopupSpy.last().at(2).value(), 121u); - auto serverXdgPopup2 = xdgPopupSpy.last().first().value(); - QVERIFY(serverXdgPopup2); - - QCOMPARE(serverXdgPopup2->surface(), surfaceCreatedSpy.last().first().value()); - QCOMPARE(serverXdgPopup2->transientFor().data(), serverXdgPopup->surface()); - QCOMPARE(serverXdgPopup2->transientOffset(), QPoint(5, 7)); - - QSignalSpy popup2DoneSpy(xdgPopup2.data(), &XdgShellPopup::popupDone); - QVERIFY(popup2DoneSpy.isValid()); - serverXdgPopup2->popupDone(); - QVERIFY(popup2DoneSpy.wait()); - // TODO: test that this sends also the done to all parents -} - -QTEST_GUILESS_MAIN(XdgShellTest) #include "test_xdg_shell.moc" diff --git a/autotests/client/test_xdg_shell_v5.cpp b/autotests/client/test_xdg_shell_v5.cpp new file mode 100644 --- /dev/null +++ b/autotests/client/test_xdg_shell_v5.cpp @@ -0,0 +1,72 @@ +#include "test_xdg_shell.h" + +class XdgShellTestV5 : public XdgShellTest { + Q_OBJECT +public: + XdgShellTestV5() : + XdgShellTest(KWayland::Server::XdgShellInterfaceVersion::UnstableV5) {} +private Q_SLOTS: + void testPopup(); +}; + +void XdgShellTestV5::testPopup() +{ + // this test verifies that the creation of popups works correctly + SURFACE + QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(surfaceCreatedSpy.isValid()); + QSignalSpy xdgPopupSpy(m_xdgShellInterface, &XdgShellInterface::popupCreated); + + //check as well as the compat signal, the new signal is also fired + QSignalSpy xdgPopupSpyNew(m_xdgShellInterface, &XdgShellInterface::xdgPopupCreated); + + + QVERIFY(xdgPopupSpy.isValid()); + + QScopedPointer popupSurface(m_compositor->createSurface()); + QVERIFY(surfaceCreatedSpy.wait()); + + // TODO: proper serial + QScopedPointer xdgPopup(m_xdgShell->createPopup(popupSurface.data(), surface.data(), m_seat, 120, QPoint(10, 20))); + QVERIFY(xdgPopupSpy.wait()); + QCOMPARE(xdgPopupSpy.count(), 1); + QCOMPARE(xdgPopupSpyNew.count(), 1); + + + QCOMPARE(xdgPopupSpy.first().at(1).value(), m_seatInterface); + QCOMPARE(xdgPopupSpy.first().at(2).value(), 120u); + auto serverXdgPopup = xdgPopupSpy.first().first().value(); + QVERIFY(serverXdgPopup); + + QCOMPARE(serverXdgPopup->surface(), surfaceCreatedSpy.first().first().value()); + QCOMPARE(serverXdgPopup->transientFor().data(), serverXdgSurface->surface()); + QCOMPARE(serverXdgPopup->transientOffset(), QPoint(10, 20)); + + // now also a popup for the popup + QScopedPointer popup2Surface(m_compositor->createSurface()); + QScopedPointer xdgPopup2(m_xdgShell->createPopup(popup2Surface.data(), popupSurface.data(), m_seat, 121, QPoint(5, 7))); + QVERIFY(xdgPopupSpy.wait()); + QCOMPARE(xdgPopupSpy.count(), 2); + QCOMPARE(xdgPopupSpy.last().at(1).value(), m_seatInterface); + QCOMPARE(xdgPopupSpy.last().at(2).value(), 121u); + auto serverXdgPopup2 = xdgPopupSpy.last().first().value(); + QVERIFY(serverXdgPopup2); + + QCOMPARE(serverXdgPopup2->surface(), surfaceCreatedSpy.last().first().value()); + QCOMPARE(serverXdgPopup2->transientFor().data(), serverXdgPopup->surface()); + QCOMPARE(serverXdgPopup2->transientOffset(), QPoint(5, 7)); + + QSignalSpy popup2DoneSpy(xdgPopup2.data(), &XdgShellPopup::popupDone); + QVERIFY(popup2DoneSpy.isValid()); + serverXdgPopup2->popupDone(); + QVERIFY(popup2DoneSpy.wait()); + // TODO: test that this sends also the done to all parents +} + + + + +QTEST_GUILESS_MAIN(XdgShellTestV5) + +#include "test_xdg_shell_v5.moc" + diff --git a/autotests/client/test_xdg_shell_v6.cpp b/autotests/client/test_xdg_shell_v6.cpp new file mode 100644 --- /dev/null +++ b/autotests/client/test_xdg_shell_v6.cpp @@ -0,0 +1,201 @@ +#include "test_xdg_shell.h" +#include + +class XdgShellTestV6 : public XdgShellTest { + Q_OBJECT +public: + XdgShellTestV6() : + XdgShellTest(KWayland::Server::XdgShellInterfaceVersion::UnstableV6) {} + +private Q_SLOTS: + void testMaxSize(); + void testMinSize(); + + void testPopup_data(); + void testPopup(); + + void testMultipleRoles1(); + void testMultipleRoles2(); +}; + +void XdgShellTestV6::testMaxSize() +{ + qRegisterMetaType(); + // this test verifies changing the window maxSize + QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated); + QVERIFY(xdgSurfaceCreatedSpy.isValid()); + QScopedPointer surface(m_compositor->createSurface()); + QScopedPointer xdgSurface(m_xdgShell->createSurface(surface.data())); + QVERIFY(xdgSurfaceCreatedSpy.wait()); + auto serverXdgSurface = xdgSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverXdgSurface); + + QSignalSpy maxSizeSpy(serverXdgSurface, &XdgShellSurfaceInterface::maxSizeChanged); + QVERIFY(maxSizeSpy.isValid()); + + xdgSurface->setMaxSize(QSize(100, 100)); + QVERIFY(maxSizeSpy.wait()); + QCOMPARE(maxSizeSpy.count(), 1); + QCOMPARE(maxSizeSpy.last().at(0).value(), QSize(100,100)); + + xdgSurface->setMaxSize(QSize(200, 200)); + QVERIFY(maxSizeSpy.wait()); + QCOMPARE(maxSizeSpy.count(), 2); + QCOMPARE(maxSizeSpy.last().at(0).value(), QSize(200,200)); +} + + +void XdgShellTestV6::testPopup_data() +{ + QTest::addColumn("positioners"); + XdgPositioner positioner(QSize(10,10), QRect(100,100,50,50)); + QTest::newRow("default") << positioner; + + XdgPositioner positioner2(QSize(20,20), QRect(101,102,51,52)); + QTest::newRow("sizeAndAnchorRect") << positioner2; + + positioner.setAnchorEdge(Qt::TopEdge | Qt::RightEdge); + QTest::newRow("anchorEdge") << positioner; + + positioner.setGravity(Qt::BottomEdge); + QTest::newRow("gravity") << positioner; + + positioner.setGravity(Qt::TopEdge | Qt::RightEdge); + QTest::newRow("gravity2") << positioner; + + positioner.setConstraints(XdgPositioner::Constraint::SlideX | XdgPositioner::Constraint::FlipY); + QTest::newRow("constraints") << positioner; + + positioner.setConstraints(XdgPositioner::Constraint::SlideX | XdgPositioner::Constraint::SlideY | XdgPositioner::Constraint::FlipX | XdgPositioner::Constraint::FlipY | XdgPositioner::Constraint::ResizeX | XdgPositioner::Constraint::ResizeY); + QTest::newRow("constraints2") << positioner; + + positioner.setAnchorOffset(QPoint(4,5)); + QTest::newRow("offset") << positioner; +} + +void XdgShellTestV6::testPopup() +{ + QSignalSpy xdgTopLevelCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated); + QSignalSpy xdgPopupCreatedSpy(m_xdgShellInterface, &XdgShellInterface::xdgPopupCreated); + + QScopedPointer parentSurface(m_compositor->createSurface()); + QScopedPointer xdgParentSurface(m_xdgShell->createSurface(parentSurface.data())); + + QVERIFY(xdgTopLevelCreatedSpy.wait()); + auto serverXdgTopLevel = xdgTopLevelCreatedSpy.first().first().value(); + + XdgPositioner positioner(QSize(10,10), QRect(100,100,50,50)); + + QScopedPointer surface(m_compositor->createSurface()); + QScopedPointer xdgSurface(m_xdgShell->createPopup(surface.data(), xdgParentSurface.data(), positioner)); + QVERIFY(xdgPopupCreatedSpy.wait()); + auto serverXdgPopup = xdgPopupCreatedSpy.first().first().value(); + QVERIFY(serverXdgPopup); + + QCOMPARE(serverXdgPopup->initialSize(), positioner.initialSize()); + QCOMPARE(serverXdgPopup->anchorRect(), positioner.anchorRect()); + QCOMPARE(serverXdgPopup->anchorEdge(), positioner.anchorEdge()); + QCOMPARE(serverXdgPopup->gravity(), positioner.gravity()); + QCOMPARE(serverXdgPopup->anchorOffset(), positioner.anchorOffset()); + //we have different enums for client server, but they share the same values + QCOMPARE((int)serverXdgPopup->constraintAdjustments(), (int)positioner.constraints()); + QCOMPARE(serverXdgPopup->transientFor().data(), serverXdgTopLevel->surface()); +} + +void XdgShellTestV6::testMinSize() +{ + qRegisterMetaType(); + // this test verifies changing the window minSize + QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated); + QVERIFY(xdgSurfaceCreatedSpy.isValid()); + QScopedPointer surface(m_compositor->createSurface()); + QScopedPointer xdgSurface(m_xdgShell->createSurface(surface.data())); + QVERIFY(xdgSurfaceCreatedSpy.wait()); + auto serverXdgSurface = xdgSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverXdgSurface); + + QSignalSpy minSizeSpy(serverXdgSurface, &XdgShellSurfaceInterface::minSizeChanged); + QVERIFY(minSizeSpy.isValid()); + + xdgSurface->setMinSize(QSize(200, 200)); + QVERIFY(minSizeSpy.wait()); + QCOMPARE(minSizeSpy.count(), 1); + QCOMPARE(minSizeSpy.last().at(0).value(), QSize(200,200)); + + xdgSurface->setMinSize(QSize(100, 100)); + QVERIFY(minSizeSpy.wait()); + QCOMPARE(minSizeSpy.count(), 2); + QCOMPARE(minSizeSpy.last().at(0).value(), QSize(100,100)); +} + +//top level then toplevel +void XdgShellTestV6::testMultipleRoles1() +{ + //setting multiple roles on an xdg surface should fail + QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated); + QSignalSpy xdgPopupCreatedSpy(m_xdgShellInterface, &XdgShellInterface::xdgPopupCreated); + + QVERIFY(xdgSurfaceCreatedSpy.isValid()); + QVERIFY(xdgPopupCreatedSpy.isValid()); + + QScopedPointer surface(m_compositor->createSurface()); + //This is testing we work when a client does something stupid + //we can't use KWayland API here because by design that stops you from doing anything stupid + auto xdgSurface = zxdg_shell_v6_get_xdg_surface(*m_xdgShell, *surface.data()); + + //create a top level + auto xdgTopLevel1 = zxdg_surface_v6_get_toplevel(xdgSurface); + QVERIFY(xdgSurfaceCreatedSpy.wait()); + + //now try to create another top level for the same xdg surface. It should fail + auto xdgTopLevel2 = zxdg_surface_v6_get_toplevel(xdgSurface); + QVERIFY(!xdgSurfaceCreatedSpy.wait(10)); + + zxdg_toplevel_v6_destroy(xdgTopLevel1); + zxdg_toplevel_v6_destroy(xdgTopLevel2); + zxdg_surface_v6_destroy(xdgSurface); +} + +//toplevel then popup +void XdgShellTestV6::testMultipleRoles2() +{ + QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated); + QSignalSpy xdgPopupCreatedSpy(m_xdgShellInterface, &XdgShellInterface::xdgPopupCreated); + + QVERIFY(xdgSurfaceCreatedSpy.isValid()); + QVERIFY(xdgPopupCreatedSpy.isValid()); + + QScopedPointer surface(m_compositor->createSurface()); + QScopedPointer parentSurface(m_compositor->createSurface()); + + auto parentXdgSurface = zxdg_shell_v6_get_xdg_surface(*m_xdgShell, *parentSurface.data()); + auto xdgTopLevelParent = zxdg_surface_v6_get_toplevel(parentXdgSurface); + QVERIFY(xdgSurfaceCreatedSpy.wait()); + + + auto xdgSurface = zxdg_shell_v6_get_xdg_surface(*m_xdgShell, *surface.data()); + //create a top level + auto xdgTopLevel1 = zxdg_surface_v6_get_toplevel(xdgSurface); + QVERIFY(xdgSurfaceCreatedSpy.wait()); + + //now try to create a popup on the same xdg surface. It should fail + + auto positioner = zxdg_shell_v6_create_positioner(*m_xdgShell); + zxdg_positioner_v6_set_anchor_rect(positioner,10, 10, 10, 10); + zxdg_positioner_v6_set_size(positioner,10, 100); + + auto xdgPopup2 = zxdg_surface_v6_get_popup(xdgSurface, parentXdgSurface, positioner); + QVERIFY(!xdgPopupCreatedSpy.wait(10)); + + zxdg_positioner_v6_destroy(positioner); + zxdg_toplevel_v6_destroy(xdgTopLevel1); + zxdg_toplevel_v6_destroy(xdgTopLevelParent); + zxdg_popup_v6_destroy(xdgPopup2); + zxdg_surface_v6_destroy(xdgSurface); +} + + +QTEST_GUILESS_MAIN(XdgShellTestV6) + +#include "test_xdg_shell_v6.moc" + diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -50,6 +50,7 @@ textinput_v2.cpp xdgshell.cpp xdgshell_v5.cpp + xdgshell_v6.cpp ) ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS @@ -127,6 +128,10 @@ BASENAME xdg-shell-v5 ) ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/xdg-shell-unstable-v6.xml + BASENAME xdg-shell-v6 +) +ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/relative-pointer-unstable-v1.xml BASENAME relativepointer-unstable-v1 ) diff --git a/src/client/protocols/xdg-shell-unstable-v6.xml b/src/client/protocols/xdg-shell-unstable-v6.xml new file mode 100644 --- /dev/null +++ b/src/client/protocols/xdg-shell-unstable-v6.xml @@ -0,0 +1,1044 @@ + + + + + Copyright © 2008-2013 Kristian Høgsberg + Copyright © 2013 Rafael Antognolli + Copyright © 2013 Jasper St. Pierre + Copyright © 2010-2013 Intel Corporation + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + xdg_shell allows clients to turn a wl_surface into a "real window" + which can be dragged, resized, stacked, and moved around by the + user. Everything about this interface is suited towards traditional + desktop environments. + + + + + + + + + + + + + + Destroy this xdg_shell object. + + Destroying a bound xdg_shell object while there are surfaces + still alive created by this xdg_shell object instance is illegal + and will result in a protocol error. + + + + + + Create a positioner object. A positioner object is used to position + surfaces relative to some parent surface. See the interface description + and xdg_surface.get_popup for details. + + + + + + + This creates an xdg_surface for the given surface. While xdg_surface + itself is not a role, the corresponding surface may only be assigned + a role extending xdg_surface, such as xdg_toplevel or xdg_popup. + + This creates an xdg_surface for the given surface. An xdg_surface is + used as basis to define a role to a given surface, such as xdg_toplevel + or xdg_popup. It also manages functionality shared between xdg_surface + based surface roles. + + See the documentation of xdg_surface for more details about what an + xdg_surface is and how it is used. + + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_shell.ping. + + + + + + + The ping event asks the client if it's still alive. Pass the + serial specified in the event back to the compositor by sending + a "pong" request back with the specified serial. See xdg_shell.ping. + + Compositors can use this to determine if the client is still + alive. It's unspecified what will happen if the client doesn't + respond to the ping request, or in what timeframe. Clients should + try to respond in a reasonable amount of time. + + A compositor is free to ping in any way it wants, but a client must + always respond to any xdg_shell object it created. + + + + + + + + The xdg_positioner provides a collection of rules for the placement of a + child surface relative to a parent surface. Rules can be defined to ensure + the child surface remains within the visible area's borders, and to + specify how the child surface changes its position, such as sliding along + an axis, or flipping around a rectangle. These positioner-created rules are + constrained by the requirement that a child surface must intersect with or + be at least partially adjacent to its parent surface. + + See the various requests for details about possible rules. + + At the time of the request, the compositor makes a copy of the rules + specified by the xdg_positioner. Thus, after the request is complete the + xdg_positioner object can be destroyed or reused; further changes to the + object will have no effect on previous usages. + + For an xdg_positioner object to be considered complete, it must have a + non-zero size set by set_size, and a non-zero anchor rectangle set by + set_anchor_rect. Passing an incomplete xdg_positioner object when + positioning a surface raises an error. + + + + + + + + + Notify the compositor that the xdg_positioner will no longer be used. + + + + + + Set the size of the surface that is to be positioned with the positioner + object. The size is in surface-local coordinates and corresponds to the + window geometry. See xdg_surface.set_window_geometry. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + Specify the anchor rectangle within the parent surface that the child + surface will be placed relative to. The rectangle is relative to the + window geometry as defined by xdg_surface.set_window_geometry of the + parent surface. The rectangle must be at least 1x1 large. + + When the xdg_positioner object is used to position a child surface, the + anchor rectangle may not extend outside the window geometry of the + positioned child's parent surface. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + + + + + + + + + + + Defines a set of edges for the anchor rectangle. These are used to + derive an anchor point that the child surface will be positioned + relative to. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left position of the rectangle); otherwise, the derived + anchor point will be centered on the specified edge, or in the center of + the anchor rectangle if no edge is specified. + + If two parallel anchor edges are specified (e.g. 'left' and 'right'), + the invalid_input error is raised. + + + + + + + + + + + + + + + Defines in what direction a surface should be positioned, relative to + the anchor point of the parent surface. If two orthogonal gravities are + specified (e.g. 'bottom' and 'right'), then the child surface will be + placed in the specified direction; otherwise, the child surface will be + centered over the anchor point on any axis that had no gravity + specified. + + If two parallel gravities are specified (e.g. 'left' and 'right'), the + invalid_input error is raised. + + + + + + + The constraint adjustment value define ways the compositor will adjust + the position of the surface, if the unadjusted position would result + in the surface being partly constrained. + + Whether a surface is considered 'constrained' is left to the compositor + to determine. For example, the surface may be partly outside the + compositor's defined 'work area', thus necessitating the child surface's + position be adjusted until it is entirely inside the work area. + + The adjustments can be combined, according to a defined precedence: 1) + Flip, 2) Slide, 3) Resize. + + + + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of a monitor. + + + + + Slide the surface along the x axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the x axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + x axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Slide the surface along the y axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the y axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + y axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Invert the anchor and gravity on the x axis if the surface is + constrained on the x axis. For example, if the left edge of the + surface is constrained, the gravity is 'left' and the anchor is + 'left', change the gravity to 'right' and the anchor to 'right'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_x adjustment will be the one before the + adjustment. + + + + + Invert the anchor and gravity on the y axis if the surface is + constrained on the y axis. For example, if the bottom edge of the + surface is constrained, the gravity is 'bottom' and the anchor is + 'bottom', change the gravity to 'top' and the anchor to 'top'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_y adjustment will be the one before the + adjustment. + + + + + Resize the surface horizontally so that it is completely + unconstrained. + + + + + Resize the surface vertically so that it is completely unconstrained. + + + + + + + Specify how the window should be positioned if the originally intended + position caused the surface to be constrained, meaning at least + partially outside positioning boundaries set by the compositor. The + adjustment is set by constructing a bitmask describing the adjustment to + be made when the surface is constrained on that axis. + + If no bit for one axis is set, the compositor will assume that the child + surface should not change its position on that axis when constrained. + + If more than one bit for one axis is set, the order of how adjustments + are applied is specified in the corresponding adjustment descriptions. + + The default adjustment is none. + + + + + + + Specify the surface position offset relative to the position of the + anchor on the anchor rectangle and the anchor on the surface. For + example if the anchor of the anchor rectangle is at (x, y), the surface + has the gravity bottom|right, and the offset is (ox, oy), the calculated + surface position will be (x + ox, y + oy). The offset position of the + surface is the one used for constraint testing. See + set_constraint_adjustment. + + An example use case is placing a popup menu on top of a user interface + element, while aligning the user interface element of the parent surface + with some user interface element placed somewhere in the popup surface. + + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides a base set of functionality required to construct user + interface elements requiring management by the compositor, such as + toplevel windows, menus, etc. The types of functionality are split into + xdg_surface roles. + + Creating an xdg_surface does not set the role for a wl_surface. In order + to map an xdg_surface, the client must create a role-specific object + using, e.g., get_toplevel, get_popup. The wl_surface for any given + xdg_surface can have at most one role, and may not be assigned any role + not based on xdg_surface. + + A role must be assigned before any other requests are made to the + xdg_surface object. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. + + Creating an xdg_surface from a wl_surface which has a buffer attached or + committed is a client error, and any attempts by a client to attach or + manipulate a buffer prior to the first xdg_surface.configure call must + also be treated as errors. + + For a surface to be mapped by the compositor, the following conditions + must be met: (1) the client has assigned a xdg_surface based role to the + surface, (2) the client has set and committed the xdg_surface state and + the role dependent state to the surface and (3) the client has committed a + buffer to the surface. + + + + + + + + + + + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed. + + + + + + This creates an xdg_toplevel object for the given xdg_surface and gives + the associated wl_surface the xdg_toplevel role. + + See the documentation of xdg_toplevel for more details about what an + xdg_toplevel is and how it is used. + + + + + + + This creates an xdg_popup object for the given xdg_surface and gives the + associated wl_surface the xdg_popup role. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + + + The window geometry of a surface is its "visible bounds" from the + user's perspective. Client-side decorations often have invisible + portions like drop-shadows which should be ignored for the + purposes of aligning, placing and constraining windows. + + The window geometry is double buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Once the window geometry of the surface is set, it is not possible to + unset it, and it will remain the same until set_window_geometry is + called again, even if a new subsurface or buffer is attached. + + If never set, the value is the full bounds of the surface, + including any subsurfaces. This updates dynamically on every + commit. This unset is meant for extremely simple clients. + + The arguments are given in the surface-local coordinate space of + the wl_surface associated with this xdg_surface. + + The width and height must be greater than zero. Setting an invalid size + will raise an error. When applied, the effective window geometry will be + the set window geometry clamped to the bounding rectangle of the + combined geometry of the surface of the xdg_surface and the associated + subsurfaces. + + + + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + For instance, for toplevel surfaces the compositor might use this + information to move a surface to the top left only when the client has + drawn itself for the maximized or fullscreen state. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + The configure event marks the end of a configure sequence. A configure + sequence is a set of one or more events configuring the state of the + xdg_surface, including the final xdg_surface.configure event. + + Where applicable, xdg_surface surface roles will during a configure + sequence extend this event as a latched state sent as events before the + xdg_surface.configure event. Such events should be considered to make up + a set of atomically applied configuration states, where the + xdg_surface.configure commits the accumulated state. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + If the client receives multiple configure events before it can respond + to one, it is free to discard all but the last event it received. + + + + + + + + This interface defines an xdg_surface role which allows a surface to, + among other things, set window-like properties such as maximize, + fullscreen, and minimize, set application-specific metadata like title and + id, and well as trigger user interactive operations such as interactive + resize and move. + + + + + Unmap and destroy the window. The window will be effectively + hidden from the user's point of view, and all state like + maximization, fullscreen, and so on, will be lost. + + + + + + Set the "parent" of this surface. This window should be stacked + above a parent. The parent surface must be mapped as long as this + surface is mapped. + + Parent windows should be set on dialogs, toolboxes, or other + "auxiliary" surfaces, so that the parent is raised when the dialog + is raised. + + + + + + + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + + + + + + + Set an application identifier for the surface. + + The app ID identifies the general class of applications to which + the surface belongs. The compositor can use this to group multiple + surfaces together, or to determine how to launch a new application. + + For D-Bus activatable applications, the app ID is used as the D-Bus + service name. + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + + See the desktop-entry specification [0] for more details on + application identifiers and how they relate to well-known D-Bus + names and .desktop files. + + [0] http://standards.freedesktop.org/desktop-entry-spec/ + + + + + + + Clients implementing client-side decorations might want to show + a context menu when right-clicking on the decorations, giving the + user a menu that they can use to maximize or minimize the window. + + This request asks the compositor to pop up such a window menu at + the given position, relative to the local surface coordinates of + the parent surface. There are no guarantees as to what menu items + the window menu contains. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + + + + + + + + + + Start an interactive, user-driven move of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive move (touch, + pointer, etc). + + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized), or if the passed serial + is no longer valid. + + If triggered, the surface will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the move. It is up to the + compositor to visually indicate that the move is taking place, such as + updating a pointer cursor, during the move. There is no guarantee + that the device focus will return when the move is completed. + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + + + + + + + + + + + + + + + Start a user-driven, interactive resize of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive resize (touch, + pointer, etc). + + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + If triggered, the client will receive configure events with the + "resize" state enum value and the expected sizes. See the "resize" + enum value for more details about what is required. The client + must also acknowledge configure events using "ack_configure". After + the resize is completed, the client will receive another "configure" + event without the resize state. + + If triggered, the surface also will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the resize. It is up to the + compositor to visually indicate that the resize is taking place, + such as updating a pointer cursor, during the resize. There is no + guarantee that the device focus will return when the resize is + completed. + + The edges parameter specifies how the surface should be resized, + and is one of the values of the resize_edge enum. The compositor + may use this information to update the surface position for + example when dragging the top left corner. The compositor may also + use this information to adapt its behavior, e.g. choose an + appropriate cursor image. + + + + + + + + + The different state values used on the surface. This is designed for + state values like maximized, fullscreen. It is paired with the + configure event to ensure that both the client and the compositor + setting the state can be synchronized. + + States set in this way are double-buffered. They will get applied on + the next commit. + + + + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client. + + + + + The surface is fullscreen. The window geometry specified in the configure + event must be obeyed by the client. + + + + + The surface is being resized. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. + Clients that have aspect ratio or cell sizing configuration can use + a smaller size, however. + + + + + Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. + + + + + + + Set a maximum size for the window. + + The client can specify a maximum size so that the compositor does + not try to configure the window beyond this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the maximum + size. The compositor may decide to ignore the values set by the + client and request a larger size. + + If never set, or a value of zero in the request, means that the + client has no expected maximum size in the given dimension. + As a result, a client wishing to reset the maximum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a maximum size to be smaller than the minimum size of + a surface is illegal and will result in a protocol error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + protocol error. + + + + + + + + Set a minimum size for the window. + + The client can specify a minimum size so that the compositor does + not try to configure the window below this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the minimum + size. The compositor may decide to ignore the values set by the + client and request a smaller size. + + If never set, or a value of zero in the request, means that the + client has no expected minimum size in the given dimension. + As a result, a client wishing to reset the minimum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a minimum size to be larger than the maximum size of + a surface is illegal and will result in a protocol error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + protocol error. + + + + + + + + Maximize the surface. + + After requesting that the surface should be maximized, the compositor + will respond by emitting a configure event with the "maximized" state + and the required window geometry. The client should then update its + content, drawing it in a maximized state, i.e. without shadow or other + decoration outside of the window geometry. The client must also + acknowledge the configure when committing the new content (see + ack_configure). + + It is up to the compositor to decide how and where to maximize the + surface, for example which output and what region of the screen should + be used. + + If the surface was already maximized, the compositor will still emit + a configure event with the "maximized" state. + + + + + + Unmaximize the surface. + + After requesting that the surface should be unmaximized, the compositor + will respond by emitting a configure event without the "maximized" + state. If available, the compositor will include the window geometry + dimensions the window had prior to being maximized in the configure + request. The client must then update its content, drawing it in a + regular state, i.e. potentially with shadow, etc. The client must also + acknowledge the configure when committing the new content (see + ack_configure). + + It is up to the compositor to position the surface after it was + unmaximized; usually the position the surface had before maximizing, if + applicable. + + If the surface was already not maximized, the compositor will still + emit a configure event without the "maximized" state. + + + + + + Make the surface fullscreen. + + You can specify an output that you would prefer to be fullscreen. + If this value is NULL, it's up to the compositor to choose which + display will be used to map this surface. + + If the surface doesn't cover the whole output, the compositor will + position the surface in the center of the output and compensate with + black borders filling the rest of the output. + + + + + + + + Request that the compositor minimize your surface. There is no + way to know if the surface is currently minimized, nor is there + any way to unset minimization on this surface. + + If you are looking to throttle redrawing when minimized, please + instead use the wl_surface.frame event for this, as this will + also work with live previews on windows in Alt-Tab, Expose or + similar compositor features. + + + + + + This configure event asks the client to resize its toplevel surface or + to change its state. The configured state should not be applied + immediately. See xdg_surface.configure for details. + + The width and height arguments specify a hint to the window + about how its surface should be resized in window geometry + coordinates. See set_window_geometry. + + If the width or height arguments are zero, it means the client + should decide its own window dimension. This may happen when the + compositor needs to configure the state of the surface but doesn't + have any information about any previous or expected dimension. + + The states listed in the event specify how the width/height + arguments should be interpreted, and possibly how it should be + drawn. + + Clients must send an ack_configure in response to this event. See + xdg_surface.configure and xdg_surface.ack_configure for details. + + + + + + + + + The close event is sent by the compositor when the user + wants the surface to be closed. This should be equivalent to + the user clicking the close button in client-side decorations, + if your application has any. + + This is only a request that the user intends to close the + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data, etc. + + + + + + + A popup surface is a short-lived, temporary surface. It can be used to + implement for example menus, popovers, tooltips and other similar user + interface concepts. + + A popup can be made to take an explicit grab. See xdg_popup.grab for + details. + + When the popup is dismissed, a popup_done event will be sent out, and at + the same time the surface will be unmapped. See the xdg_popup.popup_done + event for details. + + Explicitly destroying the xdg_popup object will also dismiss the popup and + unmap the surface. Clients that want to dismiss the popup when another + surface of their own is clicked should dismiss the popup using the destroy + request. + + The parent surface must have either the xdg_toplevel or xdg_popup surface + role. + + A newly created xdg_popup will be stacked on top of all previously created + xdg_popup surfaces associated with the same xdg_toplevel. + + The parent of an xdg_popup must be mapped (see the xdg_surface + description) before the xdg_popup itself. + + The x and y arguments passed when creating the popup object specify + where the top left of the popup should be placed, relative to the + local surface coordinates of the parent surface. See + xdg_surface.get_popup. An xdg_popup must intersect with or be at least + partially adjacent to its parent surface. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_popup state to take effect. + + + + + + + + + This destroys the popup. Explicitly destroying the xdg_popup + object will also dismiss the popup, and unmap the surface. + + If this xdg_popup is not the "topmost" popup, a protocol error + will be sent. + + + + + + This request makes the created popup take an explicit grab. An explicit + grab will be dismissed when the user dismisses the popup, or when the + client destroys the xdg_popup. This can be done by the user clicking + outside the surface, using the keyboard, or even locking the screen + through closing the lid or a timeout. + + If the compositor denies the grab, the popup will be immediately + dismissed. + + This request must be used in response to some sort of user action like a + button press, key press, or touch down event. The serial number of the + event should be passed as 'serial'. + + The parent of a grabbing popup must either be an xdg_toplevel surface or + another xdg_popup with an explicit grab. If the parent is another + xdg_popup it means that the popups are nested, with this popup now being + the topmost popup. + + Nested popups must be destroyed in the reverse order they were created + in, e.g. the only popup you are allowed to destroy at all times is the + topmost one. + + When compositors choose to dismiss a popup, they may dismiss every + nested grabbing popup as well. When a compositor dismisses popups, it + will follow the same dismissing order as required from the client. + + The parent of a grabbing popup must either be another xdg_popup with an + active explicit grab, or an xdg_popup or xdg_toplevel, if there are no + explicit grabs already taken. + + If the topmost grabbing popup is destroyed, the grab will be returned to + the parent of the popup, if that parent previously had an explicit grab. + + If the parent is a grabbing popup which has already been dismissed, this + popup will be immediately dismissed. If the parent is a popup that did + not take an explicit grab, an error will be raised. + + During a popup grab, the client owning the grab will receive pointer + and touch events for all their surfaces as normal (similar to an + "owner-events" grab in X11 parlance), while the top most grabbing popup + will always have keyboard focus. + + + + + + + + This event asks the popup surface to configure itself given the + configuration. The configured state should not be applied immediately. + See xdg_surface.configure for details. + + The x and y arguments represent the position the popup was placed at + given the xdg_positioner rule, relative to the upper left corner of the + window geometry of the parent surface. + + + + + + + + + + The popup_done event is sent out when a popup is dismissed by the + compositor. The client should destroy the xdg_popup object at this + point. + + + + + diff --git a/src/client/registry.h b/src/client/registry.h --- a/src/client/registry.h +++ b/src/client/registry.h @@ -50,6 +50,7 @@ struct org_kde_plasma_window_management; struct org_kde_kwin_server_decoration_manager; struct xdg_shell; +struct zxdg_shell_v6; struct zwp_relative_pointer_manager_v1; struct zwp_pointer_gestures_v1; struct zwp_pointer_constraints_v1; @@ -150,7 +151,8 @@ XdgShellUnstableV5, ///< Refers to xdg_shell (unstable version 5), @since 5.25 RelativePointerManagerUnstableV1, ///< Refers to zwp_relative_pointer_manager_v1, @since 5.28 PointerGesturesUnstableV1, ///< Refers to zwp_pointer_gestures_v1, @since 5.29 - PointerConstraintsUnstableV1 ///< Refers to zwp_pointer_constraints_v1, @since 5.29 + PointerConstraintsUnstableV1, ///< Refers to zwp_pointer_constraints_v1, @since 5.29 + XdgShellUnstableV6 ///< Refers to zxdg_shell_v6 (unstable version 6), @since 5.XX }; explicit Registry(QObject *parent = nullptr); virtual ~Registry(); @@ -486,6 +488,16 @@ **/ xdg_shell *bindXdgShellUnstableV5(uint32_t name, uint32_t version) const; /** + * Binds the zxdg_shell_v6 (unstable version 6) with @p name and @p version. + * If the @p name does not exist or is not for the xdg shell interface in unstable version 5, + * @c null will be returned. + * + * Prefer using createXdgShell instead. + * @see createXdgShell + * @since 5.FIXME + **/ + zxdg_shell_v6 *bindXdgShellUnstableV6(uint32_t name, uint32_t version) const; + /** * Binds the zwp_relative_pointer_manager_v1 with @p name and @p version. * If the @p name does not exist or is not for the relative pointer interface in unstable version 1, * @c null will be returned. @@ -1094,6 +1106,14 @@ **/ void xdgShellUnstableV5Announced(quint32 name, quint32 version); /** + * Emitted whenever a zxdg_shell_v6 (unstable version 6) interface gets announced. + * @param name The name for the announced interface + * @param version The maximum supported version of the announced interface + * @since 5.25 + **/ + void xdgShellUnstableV6Announced(quint32 name, quint32 version); + + /** * Emitted whenever a zwp_relative_pointer_manager_v1 interface gets announced. * @param name The name for the announced interface * @param version The maximum supported version of the announced interface @@ -1250,6 +1270,12 @@ **/ void xdgShellUnstableV5Removed(quint32 name); /** + * Emitted whenever an xdg_shell (unstable version 5) interface gets removed. + * @param name The name for the removed interface + * @since 5.25 + **/ + void xdgShellUnstableV6Removed(quint32 name); + /** * Emitted whenever a zwp_relative_pointer_manager_v1 interface gets removed. * @param name The name for the removed interface * @since 5.28 diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -69,6 +69,7 @@ #include #include #include +#include #include #include #include @@ -281,6 +282,13 @@ &zwp_pointer_constraints_v1_interface, &Registry::pointerConstraintsUnstableV1Announced, &Registry::pointerConstraintsUnstableV1Removed + }}, + {Registry::Interface::XdgShellUnstableV6, { + 1, + QByteArrayLiteral("zxdg_shell_v6"), + &zxdg_shell_v6_interface, + &Registry::xdgShellUnstableV6Announced, + &Registry::xdgShellUnstableV6Removed }} }; @@ -578,6 +586,7 @@ BIND(TextInputManagerUnstableV0, wl_text_input_manager) BIND(TextInputManagerUnstableV2, zwp_text_input_manager_v2) BIND(XdgShellUnstableV5, xdg_shell) +BIND(XdgShellUnstableV6, zxdg_shell_v6) BIND(RelativePointerManagerUnstableV1, zwp_relative_pointer_manager_v1) BIND(PointerGesturesUnstableV1, zwp_pointer_gestures_v1) BIND(PointerConstraintsUnstableV1, zwp_pointer_constraints_v1) @@ -656,6 +665,8 @@ switch (d->interfaceForName(name)) { case Interface::XdgShellUnstableV5: return d->create(name, version, parent, &Registry::bindXdgShellUnstableV5); + case Interface::XdgShellUnstableV6: + return d->create(name, version, parent, &Registry::bindXdgShellUnstableV6); default: return nullptr; } diff --git a/src/client/xdgshell.h b/src/client/xdgshell.h --- a/src/client/xdgshell.h +++ b/src/client/xdgshell.h @@ -21,13 +21,20 @@ #define KWAYLAND_CLIENT_XDG_SHELL_V5_H #include - +#include +#include #include struct xdg_shell; struct xdg_surface; struct xdg_popup; +struct zxdg_shell_v6; +struct zxdg_toplevel_v6; +struct zxdg_surface_v6; +struct zxdg_popup_v6; +struct zxdg_position_v6; + namespace KWayland { namespace Client @@ -40,6 +47,95 @@ class XdgShellPopup; class XdgShellSurface; +/* + * Builder class describing how a popup should be positioned + * when created + * + * @since 5.XDGMERGE_VERSION + */ +class KWAYLANDCLIENT_EXPORT XdgPositioner +{ +public: + /* + * Flags describing how a popup should be reposition if constrained + */ + enum class Constraint { + /* + * Slide the popup on the X axis until there is room + */ + SlideX = 1 << 0, + /* + * Slide the popup on the Y axis until there is room + */ + SlideY = 1 << 1, + /* + * Invert the anchor and gravity on the X axis + */ + FlipX = 1 << 2, + /* + * Invert the anchor and gravity on the Y axis + */ + FlipY = 1 << 3, + /* + * Resize the popup in the X axis + */ + ResizeX = 1 << 4, + /* + * Resize the popup in the Y axis + */ + ResizeY = 1 << 5 + }; + + Q_DECLARE_FLAGS(Constraints, Constraint) + + XdgPositioner(const QSize &initialSize = QSize(), const QRect &anchor = QRect()); + XdgPositioner(const XdgPositioner &other); + ~XdgPositioner(); + + /* + * Which edge of the anchor should the popup be positioned around + */ + Qt::Edges anchorEdge() const; + void setAnchorEdge(Qt::Edges edge); + + /* + * Specifies in what direction the popup should be positioned around the anchor + * i.e if the gravity is "bottom", then then the top of top of the poup will be at the anchor edge + * if the gravity is top, then the bottom of the popup will be at the anchor edge + * + */ + Qt::Edges gravity() const; + void setGravity(Qt::Edges edge); + + /* + * The area this popup should be positioned around + */ + QRect anchorRect() const; + void setAnchorRect(const QRect &anchor); + + /* + * The size of the surface that is to be positioned. + */ + QSize initialSize() const; + void setInitialSize(const QSize &size); + + /* + * Specifies how the compositor should position the popup if it does not fit in the requested position + */ + Constraints constraints() const; + void setConstraints(Constraints constaints); + + /* + * An additional offset that should be applied from the anchor. + */ + QPoint anchorOffset() const; + void setAnchorOffset(const QPoint &offset); + +private: + class Private; + QScopedPointer d; +}; + /** * @short Wrapper for the xdg_shell interface. * @@ -76,6 +172,9 @@ * method. **/ void setup(xdg_shell *xdgshellv5); + + void setup(zxdg_shell_v6 *xdgshellv6); + /** * @returns @c true if managing a xdg_shell. **/ @@ -118,11 +217,25 @@ /** * Creates a new XdgShellPopup for the given @p surface on top of @p parentSurface. + * @deprecated This method is only valid for v5 **/ XdgShellPopup *createPopup(Surface *surface, Surface *parentSurface, Seat *seat, quint32 serial, const QPoint &parentPos, QObject *parent = nullptr); + /** + * Creates a new XdgShellPopup for the given @p surface on top of @p parentSurface with the given @p positioner. + **/ + XdgShellPopup *createPopup(Surface *surface, XdgShellSurface *parentSurface, const XdgPositioner &positioner, QObject *parent = nullptr); + + /** + * Creates a new XdgShellPopup for the given @p surface on top of @p parentSurface with the given @p positioner. + **/ + XdgShellPopup *createPopup(Surface *surface, XdgShellPopup *parentSurface, const XdgPositioner &positioner, QObject *parent = nullptr); + operator xdg_shell*(); operator xdg_shell*() const; + operator zxdg_shell_v6*(); + operator zxdg_shell_v6*() const; + Q_SIGNALS: /** @@ -185,6 +298,9 @@ * method. **/ void setup(xdg_surface *xdgsurfacev5); + + void setup(zxdg_surface_v6 *xdgsurfacev6, zxdg_toplevel_v6 *toplevel); + /** * @returns @c true if managing a xdg_surface. **/ @@ -308,9 +424,26 @@ **/ void requestMinimize(); + /* + * Set this surface to have a given maximum size + * @since 5.XDGMERGE_VERSION + */ + void setMaxSize(const QSize &size); + + /* + * Set this surface to have a given minimum size + * @since 5.XDGMERGE_VERSION + */ + void setMinSize(const QSize &size); + operator xdg_surface*(); operator xdg_surface*() const; + operator zxdg_surface_v6*(); + operator zxdg_surface_v6*() const; + operator zxdg_toplevel_v6*(); + operator zxdg_toplevel_v6*() const; + Q_SIGNALS: /** * The compositor requested to close this window. @@ -359,6 +492,14 @@ * method. **/ void setup(xdg_popup *xdgpopupv5); + + /** + * Setup this XdgShellPopup to manage the @p xdgpopupv6 + * When using XdgShell::createXdgShellPopup there is no need to call this + * method. + **/ + void setup(zxdg_surface_v6 *xdgsurfacev6, zxdg_popup_v6 *xdgpopup6); + /** * @returns @c true if managing an xdg_popup. **/ @@ -395,16 +536,34 @@ **/ EventQueue *eventQueue(); + /** + * Requests a grab on this popup + * @since 5.XDGMERGE_VERSION + */ + void requestGrab(Seat *seat, quint32 serial); + + operator xdg_popup*(); operator xdg_popup*() const; + operator zxdg_surface_v6*(); + operator zxdg_surface_v6*() const; + operator zxdg_popup_v6*(); + operator zxdg_popup_v6*() const; + Q_SIGNALS: /** * This signal is emitted when a XdgShellPopup is dismissed by the * compositor. The user should delete this instance at this point. **/ void popupDone(); + /** + * + **/ + void configureRequested(const QRect &relativePosition, quint32 serial); + + protected: class Private; explicit XdgShellPopup(Private *p, QObject *parent = nullptr); @@ -416,7 +575,14 @@ } } +Q_DECLARE_OPERATORS_FOR_FLAGS(KWayland::Client::XdgShellSurface::States) +Q_DECLARE_OPERATORS_FOR_FLAGS(KWayland::Client::XdgPositioner::Constraints) + +Q_DECLARE_METATYPE(KWayland::Client::XdgPositioner) Q_DECLARE_METATYPE(KWayland::Client::XdgShellSurface::State) Q_DECLARE_METATYPE(KWayland::Client::XdgShellSurface::States) +Q_DECLARE_METATYPE(KWayland::Client::XdgPositioner::Constraint) +Q_DECLARE_METATYPE(KWayland::Client::XdgPositioner::Constraints) + #endif diff --git a/src/client/xdgshell.cpp b/src/client/xdgshell.cpp --- a/src/client/xdgshell.cpp +++ b/src/client/xdgshell.cpp @@ -48,6 +48,12 @@ d->setupV5(xdgshellv5); } +void XdgShell::setup(zxdg_shell_v6 *xdgshellv6) +{ + d->setupV6(xdgshellv6); +} + + void XdgShell::release() { d->release(); @@ -76,6 +82,14 @@ return *(d.data()); } +XdgShell::operator zxdg_shell_v6*() { + return *(d.data()); +} + +XdgShell::operator zxdg_shell_v6*() const { + return *(d.data()); +} + bool XdgShell::isValid() const { return d->isValid(); @@ -91,6 +105,16 @@ return d->getXdgPopup(surface, parentSurface, seat, serial, parentPos, parent); } +XdgShellPopup *XdgShell::createPopup(Surface *surface, XdgShellSurface *parentSurface, const XdgPositioner &positioner, QObject *parent) +{ + return d->getXdgPopup(surface, parentSurface, positioner, parent); +} + +XdgShellPopup *XdgShell::createPopup(Surface *surface, XdgShellPopup *parentSurface, const XdgPositioner &positioner, QObject *parent) +{ + return d->getXdgPopup(surface, parentSurface, positioner, parent); +} + XdgShellSurface::Private::Private(XdgShellSurface *q) : q(q) { @@ -114,6 +138,11 @@ d->setupV5(xdgsurfacev5); } +void XdgShellSurface::setup(zxdg_surface_v6 *xdgsurfacev6, zxdg_toplevel_v6 *xdgtoplevelv6) +{ + d->setupV6(xdgsurfacev6, xdgtoplevelv6); +} + void XdgShellSurface::release() { d->release(); @@ -142,6 +171,22 @@ return *(d.data()); } +XdgShellSurface::operator zxdg_surface_v6*() { + return *(d.data()); +} + +XdgShellSurface::operator zxdg_surface_v6*() const { + return *(d.data()); +} + +XdgShellSurface::operator zxdg_toplevel_v6*() { + return *(d.data()); +} + +XdgShellSurface::operator zxdg_toplevel_v6*() const { + return *(d.data()); +} + bool XdgShellSurface::isValid() const { return d->isValid(); @@ -200,6 +245,16 @@ } } +void XdgShellSurface::setMaxSize(const QSize &size) +{ + d->setMaxSize(size); +} + +void XdgShellSurface::setMinSize(const QSize &size) +{ + d->setMinSize(size); +} + void XdgShellSurface::requestMinimize() { d->setMinimized(); @@ -243,6 +298,11 @@ d->setupV5(xdgpopupv5); } +void XdgShellPopup::setup(zxdg_surface_v6 *xdgsurfacev6, zxdg_popup_v6 *xdgpopupv6) +{ + d->setupV6(xdgsurfacev6, xdgpopupv6); +} + void XdgShellPopup::release() { d->release(); @@ -263,19 +323,119 @@ return d->queue; } +void XdgShellPopup::requestGrab(KWayland::Client::Seat* seat, quint32 serial) +{ + d->requestGrab(seat, serial); +} + XdgShellPopup::operator xdg_popup*() { return *(d.data()); } XdgShellPopup::operator xdg_popup*() const { return *(d.data()); } +XdgShellPopup::operator zxdg_surface_v6*() { + return *(d.data()); +} + +XdgShellPopup::operator zxdg_surface_v6*() const { + return *(d.data()); +} + +XdgShellPopup::operator zxdg_popup_v6*() { + return *(d.data()); +} + +XdgShellPopup::operator zxdg_popup_v6*() const { + return *(d.data()); +} + bool XdgShellPopup::isValid() const { return d->isValid(); } +XdgPositioner::XdgPositioner(const QSize& initialSize, const QRect& anchor) +:d (new Private) +{ + d->initialSize = initialSize; + d->anchorRect = anchor; +} + + +XdgPositioner::XdgPositioner(const XdgPositioner &other) +:d (new Private) +{ + *d = *other.d; +} + +XdgPositioner::~XdgPositioner() +{ +} + +void XdgPositioner::setInitialSize(const QSize& size) +{ + d->initialSize = size; +} + +QSize XdgPositioner::initialSize() const +{ + return d->initialSize; +} + +void XdgPositioner::setAnchorRect(const QRect& anchor) +{ + d->anchorRect = anchor; +} + +QRect XdgPositioner::anchorRect() const +{ + return d->anchorRect; +} + +void XdgPositioner::setAnchorEdge(Qt::Edges edge) +{ + d->anchorEdge = edge; +} + +Qt::Edges XdgPositioner::anchorEdge() const +{ + return d->anchorEdge; +} + +void XdgPositioner::setAnchorOffset(const QPoint& offset) +{ + d->anchorOffset = offset; +} + +QPoint XdgPositioner::anchorOffset() const +{ + return d->anchorOffset; +} + +void XdgPositioner::setGravity(Qt::Edges edge) +{ + d->gravity = edge; +} + +Qt::Edges XdgPositioner::gravity() const +{ + return d->gravity; +} + +void XdgPositioner::setConstraints(Constraints constraints) +{ + d->constraints = constraints; +} + +XdgPositioner::Constraints XdgPositioner::constraints() const +{ + return d->constraints; +} + + } } diff --git a/src/client/xdgshell_p.h b/src/client/xdgshell_p.h --- a/src/client/xdgshell_p.h +++ b/src/client/xdgshell_p.h @@ -22,6 +22,8 @@ #include "xdgshell.h" #include +#include +#include namespace KWayland { @@ -35,6 +37,9 @@ virtual void setupV5(xdg_shell *xdgshellv5) { Q_UNUSED(xdgshellv5) } + virtual void setupV6(zxdg_shell_v6 *xdgshellv6) { + Q_UNUSED(xdgshellv6) + } virtual void release() = 0; virtual void destroy() = 0; virtual bool isValid() const = 0; @@ -44,8 +49,43 @@ virtual operator xdg_shell*() const { return nullptr; } + virtual operator zxdg_shell_v6*() { + return nullptr; + } + virtual operator zxdg_shell_v6*() const { + return nullptr; + } virtual XdgShellSurface *getXdgSurface(Surface *surface, QObject *parent) = 0; - virtual XdgShellPopup *getXdgPopup(Surface *surface, Surface *parentSurface, Seat *seat, quint32 serial, const QPoint &parentPos, QObject *parent) = 0; + + virtual XdgShellPopup *getXdgPopup(Surface *surface, Surface *parentSurface, Seat *seat, quint32 serial, const QPoint &parentPos, QObject *parent) { + Q_UNUSED(surface) + Q_UNUSED(parentSurface) + Q_UNUSED(seat) + Q_UNUSED(serial) + Q_UNUSED(parentPos) + Q_UNUSED(parent) + qWarning() << __func__ << " is not supported in this version"; + return nullptr; + }; + + virtual XdgShellPopup *getXdgPopup(Surface *surface, XdgShellSurface *parentSurface, const XdgPositioner &positioner, QObject *parent) { + Q_UNUSED(surface) + Q_UNUSED(parentSurface) + Q_UNUSED(positioner) + Q_UNUSED(parent) + + return nullptr; + } + + virtual XdgShellPopup *getXdgPopup(Surface *surface, XdgShellPopup *parentSurface, const XdgPositioner &positioner, QObject *parent) { + + Q_UNUSED(surface) + Q_UNUSED(parentSurface) + Q_UNUSED(positioner) + Q_UNUSED(parent) + + return nullptr; + } EventQueue *queue = nullptr; @@ -64,6 +104,41 @@ class Private; }; +class XdgShellUnstableV6 : public XdgShell +{ + Q_OBJECT +public: + explicit XdgShellUnstableV6(QObject *parent = nullptr); + virtual ~XdgShellUnstableV6(); + +private: + class Private; +}; + +class XdgShellSurfaceUnstableV5 : public XdgShellSurface +{ + Q_OBJECT +public: + virtual ~XdgShellSurfaceUnstableV5(); + +private: + explicit XdgShellSurfaceUnstableV5(QObject *parent = nullptr); + friend class XdgShellUnstableV5; + class Private; +}; + +class XdgTopLevelUnstableV6 : public XdgShellSurface +{ + Q_OBJECT +public: + virtual ~XdgTopLevelUnstableV6(); + +private: + explicit XdgTopLevelUnstableV6(QObject *parent = nullptr); + friend class XdgShellUnstableV6; + class Private; +}; + class XdgShellSurface::Private { public: @@ -74,6 +149,11 @@ virtual void setupV5(xdg_surface *surface) { Q_UNUSED(surface) } + virtual void setupV6(zxdg_surface_v6 *surface, zxdg_toplevel_v6 *toplevel) + { + Q_UNUSED(toplevel) + Q_UNUSED(surface) + } virtual void release() = 0; virtual void destroy() = 0; virtual bool isValid() const = 0; @@ -83,6 +163,18 @@ virtual operator xdg_surface*() const { return nullptr; } + virtual operator zxdg_surface_v6*() { + return nullptr; + } + virtual operator zxdg_surface_v6*() const { + return nullptr; + } + virtual operator zxdg_toplevel_v6*() { + return nullptr; + } + virtual operator zxdg_toplevel_v6*() const { + return nullptr; + } virtual void setTransientFor(XdgShellSurface *parent) = 0; virtual void setTitle(const QString &title) = 0; @@ -96,25 +188,15 @@ virtual void setFullscreen(Output *output) = 0; virtual void unsetFullscreen() = 0; virtual void setMinimized() = 0; + virtual void setMaxSize(const QSize &size) = 0; + virtual void setMinSize(const QSize &size) = 0; protected: Private(XdgShellSurface *q); XdgShellSurface *q; }; -class XdgShellSurfaceUnstableV5 : public XdgShellSurface -{ - Q_OBJECT -public: - virtual ~XdgShellSurfaceUnstableV5(); - -private: - explicit XdgShellSurfaceUnstableV5(QObject *parent = nullptr); - friend class XdgShellUnstableV5; - class Private; -}; - class XdgShellPopup::Private { public: @@ -126,22 +208,54 @@ virtual void setupV5(xdg_popup *p) { Q_UNUSED(p) } + virtual void setupV6(zxdg_surface_v6 *s, zxdg_popup_v6 *p) { + Q_UNUSED(s) + Q_UNUSED(p) + } virtual void release() = 0; virtual void destroy() = 0; virtual bool isValid() const = 0; + virtual void requestGrab(Seat *seat, quint32 serial) { + Q_UNUSED(seat); + Q_UNUSED(serial); + }; virtual operator xdg_popup*() { return nullptr; } virtual operator xdg_popup*() const { return nullptr; } + virtual operator zxdg_surface_v6*() { + return nullptr; + } + virtual operator zxdg_surface_v6*() const { + return nullptr; + } + virtual operator zxdg_popup_v6*() { + return nullptr; + } + virtual operator zxdg_popup_v6*() const { + return nullptr; + } protected: XdgShellPopup *q; private: }; +class XdgPositioner::Private +{ +public: + QSize initialSize; + QRect anchorRect; + Qt::Edges gravity; + Qt::Edges anchorEdge; + XdgPositioner::Constraints constraints; + QPoint anchorOffset; +}; + + class XdgShellPopupUnstableV5 : public XdgShellPopup { public: @@ -153,6 +267,19 @@ class Private; }; +class XdgShellPopupUnstableV6 : public XdgShellPopup +{ +public: + virtual ~XdgShellPopupUnstableV6(); + +private: + explicit XdgShellPopupUnstableV6(QObject *parent = nullptr); + friend class XdgShellUnstableV6; + class Private; +}; + + + } } diff --git a/src/client/xdgshell_v5.cpp b/src/client/xdgshell_v5.cpp --- a/src/client/xdgshell_v5.cpp +++ b/src/client/xdgshell_v5.cpp @@ -46,15 +46,29 @@ return xdgshellv5; } + static void pingCallback(void *data, struct xdg_shell *shell, uint32_t serial); + WaylandPointer xdgshellv5; + static const struct xdg_shell_listener s_shellListener; +}; + +const struct xdg_shell_listener XdgShellUnstableV5::Private::s_shellListener = { + pingCallback, }; +void XdgShellUnstableV5::Private::pingCallback(void *data, struct xdg_shell *shell, uint32_t serial) +{ + Q_UNUSED(data); + xdg_shell_pong(shell, serial); +} + void XdgShellUnstableV5::Private::setupV5(xdg_shell *shell) { Q_ASSERT(shell); Q_ASSERT(!xdgshellv5); xdgshellv5.setup(shell); xdg_shell_use_unstable_version(xdgshellv5, 5); + xdg_shell_add_listener(shell, &s_shellListener, this); } void XdgShellUnstableV5::Private::release() @@ -132,6 +146,8 @@ void setFullscreen(Output *output) override; void unsetFullscreen() override; void setMinimized() override; + void setMaxSize(const QSize &size) override; + void setMinSize(const QSize &size) override; private: static void configureCallback(void *data, xdg_surface *xdg_surface, int32_t width, int32_t height, wl_array *states, uint32_t serial); @@ -301,6 +317,18 @@ xdg_surface_set_minimized(xdgsurfacev5); } +void XdgShellSurfaceUnstableV5::Private::setMaxSize(const QSize &size) +{ + Q_UNUSED(size) + //TODO: notify an error? +} + +void XdgShellSurfaceUnstableV5::Private::setMinSize(const QSize &size) +{ + Q_UNUSED(size) + //TODO: notify an error? +} + XdgShellSurfaceUnstableV5::XdgShellSurfaceUnstableV5(QObject *parent) : XdgShellSurface(new Private(this), parent) { diff --git a/src/client/xdgshell_v6.cpp b/src/client/xdgshell_v6.cpp new file mode 100644 --- /dev/null +++ b/src/client/xdgshell_v6.cpp @@ -0,0 +1,585 @@ +/**************************************************************************** +Copyright 2017 David Edmundson + +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 "xdgshell_p.h" +#include "event_queue.h" +#include "output.h" +#include "seat.h" +#include "surface.h" +#include "wayland_pointer_p.h" +#include + +#include + +namespace KWayland +{ +namespace Client +{ + +class XdgShellUnstableV6::Private : public XdgShell::Private +{ +public: + void setupV6(zxdg_shell_v6 *shell) override; + void release() override; + void destroy() override; + bool isValid() const override; + XdgShellSurface *getXdgSurface(Surface *surface, QObject *parent) override; + + XdgShellPopup *getXdgPopup(Surface *surface, XdgShellSurface *parentSurface, const XdgPositioner &positioner, QObject *parent) override; + XdgShellPopup *getXdgPopup(Surface *surface, XdgShellPopup *parentSurface, const XdgPositioner &positioner, QObject *parent) override; + + operator zxdg_shell_v6*() override { + return xdgshellv6; + } + operator zxdg_shell_v6*() const override { + return xdgshellv6; + } + +private: + XdgShellPopup *internalGetXdgPopup(Surface *surface, zxdg_surface_v6 *parentSurface, const XdgPositioner &positioner, QObject *parent); + static void pingCallback(void *data, struct zxdg_shell_v6 *shell, uint32_t serial); + + WaylandPointer xdgshellv6; + static const struct zxdg_shell_v6_listener s_shellListener; +}; + +const struct zxdg_shell_v6_listener XdgShellUnstableV6::Private::s_shellListener = { + pingCallback, +}; + +void XdgShellUnstableV6::Private::pingCallback(void *data, struct zxdg_shell_v6 *shell, uint32_t serial) +{ + Q_UNUSED(data) + zxdg_shell_v6_pong(shell, serial); +} + +void XdgShellUnstableV6::Private::setupV6(zxdg_shell_v6 *shell) +{ + Q_ASSERT(shell); + Q_ASSERT(!xdgshellv6); + xdgshellv6.setup(shell); + zxdg_shell_v6_add_listener(shell, &s_shellListener, this); +} + +void XdgShellUnstableV6::Private::release() +{ + xdgshellv6.release(); +} + +void XdgShellUnstableV6::Private::destroy() +{ + xdgshellv6.destroy(); +} + +bool XdgShellUnstableV6::Private::isValid() const +{ + return xdgshellv6.isValid(); +} + +XdgShellSurface *XdgShellUnstableV6::Private::getXdgSurface(Surface *surface, QObject *parent) +{ + Q_ASSERT(isValid()); + auto ss = zxdg_shell_v6_get_xdg_surface(xdgshellv6, *surface); + + if (!ss) { + return nullptr; + } + + auto s = new XdgTopLevelUnstableV6(parent); + auto toplevel = zxdg_surface_v6_get_toplevel(ss); + if (queue) { + queue->addProxy(ss); + queue->addProxy(toplevel); + } + s->setup(ss, toplevel); + return s; +} + +XdgShellPopup *XdgShellUnstableV6::Private::getXdgPopup(Surface *surface, XdgShellSurface *parentSurface, const XdgPositioner &positioner, QObject *parent) +{ + return internalGetXdgPopup(surface, *parentSurface, positioner, parent); +} + +XdgShellPopup *XdgShellUnstableV6::Private::getXdgPopup(Surface *surface, XdgShellPopup *parentSurface, const XdgPositioner &positioner, QObject *parent) +{ + return internalGetXdgPopup(surface, *parentSurface, positioner, parent); +} + +XdgShellPopup *XdgShellUnstableV6::Private::internalGetXdgPopup(Surface *surface, zxdg_surface_v6 *parentSurface, const XdgPositioner &positioner, QObject *parent) +{ + Q_ASSERT(isValid()); + auto ss = zxdg_shell_v6_get_xdg_surface(xdgshellv6, *surface); + if (!ss) { + return nullptr; + } + + auto p = zxdg_shell_v6_create_positioner(xdgshellv6); + + auto anchorRect = positioner.anchorRect(); + zxdg_positioner_v6_set_anchor_rect(p, anchorRect.x(), anchorRect.y(), anchorRect.width(), anchorRect.height()); + + QSize initialSize = positioner.initialSize(); + zxdg_positioner_v6_set_size(p, initialSize.width(), initialSize.height()); + + QPoint anchorOffset = positioner.anchorOffset(); + if (!anchorOffset.isNull()) { + zxdg_positioner_v6_set_offset(p, anchorOffset.x(), anchorOffset.y()); + } + + uint32_t anchor = 0; + if (positioner.anchorEdge().testFlag(Qt::LeftEdge)) { + anchor |= ZXDG_POSITIONER_V6_ANCHOR_LEFT; + } + if (positioner.anchorEdge().testFlag(Qt::TopEdge)) { + anchor |= ZXDG_POSITIONER_V6_ANCHOR_TOP; + } + if (positioner.anchorEdge().testFlag(Qt::RightEdge)) { + anchor |= ZXDG_POSITIONER_V6_ANCHOR_RIGHT; + } + if (positioner.anchorEdge().testFlag(Qt::BottomEdge)) { + anchor |= ZXDG_POSITIONER_V6_ANCHOR_BOTTOM; + } + if (anchor != 0) { + zxdg_positioner_v6_set_anchor(p, anchor); + } + + uint32_t gravity = 0; + if (positioner.anchorEdge().testFlag(Qt::LeftEdge)) { + gravity |= ZXDG_POSITIONER_V6_GRAVITY_LEFT; + } + if (positioner.anchorEdge().testFlag(Qt::TopEdge)) { + gravity |= ZXDG_POSITIONER_V6_GRAVITY_TOP; + } + if (positioner.anchorEdge().testFlag(Qt::RightEdge)) { + gravity |= ZXDG_POSITIONER_V6_GRAVITY_RIGHT; + } + if (positioner.anchorEdge().testFlag(Qt::BottomEdge)) { + gravity |= ZXDG_POSITIONER_V6_GRAVITY_BOTTOM; + } + zxdg_positioner_v6_set_anchor(p, anchor); + if (gravity != 0) { + zxdg_positioner_v6_set_gravity(p, gravity); + } + + uint32_t constraint = 0; + if (positioner.constraints().testFlag(XdgPositioner::Constraint::SlideX)) { + constraint |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X; + } + if (positioner.constraints().testFlag(XdgPositioner::Constraint::SlideY)) { + constraint |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y; + } + if (positioner.constraints().testFlag(XdgPositioner::Constraint::FlipX)) { + constraint |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X; + } + if (positioner.constraints().testFlag(XdgPositioner::Constraint::FlipY)) { + constraint |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y; + } + if (positioner.constraints().testFlag(XdgPositioner::Constraint::ResizeX)) { + constraint |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y; + } + if (positioner.constraints().testFlag(XdgPositioner::Constraint::ResizeY)) { + constraint |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y; + } + if (constraint != 0) { + zxdg_positioner_v6_set_constraint_adjustment(p, constraint); + } + + XdgShellPopup *s = new XdgShellPopupUnstableV6(parent); + auto popup = zxdg_surface_v6_get_popup(ss, parentSurface, p); + if (queue) { + //deliberately not adding the positioner because the positioner has no events sent to it + queue->addProxy(ss); + queue->addProxy(popup); + } + s->setup(ss, popup); + + zxdg_positioner_v6_destroy(p); + + return s; +} + +XdgShellUnstableV6::XdgShellUnstableV6(QObject *parent) + : XdgShell(new Private, parent) +{ +} + +XdgShellUnstableV6::~XdgShellUnstableV6() = default; + + +//A top level wraps both xdg_surface_v6 and xdg_top_level into the public API XdgShelllSurface +class XdgTopLevelUnstableV6::Private : public XdgShellSurface::Private +{ +public: + Private(XdgShellSurface *q); + WaylandPointer xdgtoplevelv6; + WaylandPointer xdgsurfacev6; + + void setupV6(zxdg_surface_v6 *surface, zxdg_toplevel_v6 *toplevel) override; + void release() override; + void destroy() override; + bool isValid() const override; + + operator zxdg_surface_v6*() override { + return xdgsurfacev6; + } + operator zxdg_surface_v6*() const override { + return xdgsurfacev6; + } + operator zxdg_toplevel_v6*() override { + return xdgtoplevelv6; + } + operator zxdg_toplevel_v6*() const override { + return xdgtoplevelv6; + } + + void setTransientFor(XdgShellSurface *parent) override; + void setTitle(const QString &title) override; + void setAppId(const QByteArray &appId) override; + void showWindowMenu(Seat *seat, quint32 serial, qint32 x, qint32 y) override; + void move(Seat *seat, quint32 serial) override; + void resize(Seat *seat, quint32 serial, Qt::Edges edges) override; + void ackConfigure(quint32 serial) override; + void setMaximized() override; + void unsetMaximized() override; + void setFullscreen(Output *output) override; + void unsetFullscreen() override; + void setMinimized() override; + void setMaxSize(const QSize &size) override; + void setMinSize(const QSize &size) override; + +private: + QSize pendingSize; + States pendingState; + + static void configureCallback(void *data, struct zxdg_toplevel_v6 *xdg_toplevel, int32_t width, int32_t height, struct wl_array *state); + static void closeCallback(void *data, zxdg_toplevel_v6 *xdg_toplevel); + static void surfaceConfigureCallback(void *data, zxdg_surface_v6 *xdg_surface, uint32_t serial); + + static const struct zxdg_toplevel_v6_listener s_toplevelListener; + static const struct zxdg_surface_v6_listener s_surfaceListener; +}; + +const struct zxdg_toplevel_v6_listener XdgTopLevelUnstableV6::Private::s_toplevelListener = { + configureCallback, + closeCallback +}; + +const struct zxdg_surface_v6_listener XdgTopLevelUnstableV6::Private::s_surfaceListener = { + surfaceConfigureCallback +}; + +void XdgTopLevelUnstableV6::Private::surfaceConfigureCallback(void *data, struct zxdg_surface_v6 *surface, uint32_t serial) +{ + Q_UNUSED(surface) + auto s = reinterpret_cast(data); + s->q->configureRequested(s->pendingSize, s->pendingState, serial); + if (!s->pendingSize.isNull()) { + s->q->setSize(s->pendingSize); + s->pendingSize = QSize(); + } + s->pendingState = 0; +} + +void XdgTopLevelUnstableV6::Private::configureCallback(void *data, struct zxdg_toplevel_v6 *xdg_toplevel, int32_t width, int32_t height, struct wl_array *state) +{ + Q_UNUSED(xdg_toplevel) + auto s = reinterpret_cast(data); + States states; + + uint32_t *statePtr = reinterpret_cast(state->data); + for (size_t i = 0; i < state->size / sizeof(uint32_t); i++) { + switch (statePtr[i]) { + case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED: + states = states | XdgShellSurface::State::Maximized; + break; + case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN: + states = states | XdgShellSurface::State::Fullscreen; + break; + case ZXDG_TOPLEVEL_V6_STATE_RESIZING: + states = states | XdgShellSurface::State::Resizing; + break; + case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED: + states = states | XdgShellSurface::State::Activated; + break; + } + } + s->pendingSize = QSize(width, height); + s->pendingState = states; +} + +void XdgTopLevelUnstableV6::Private::closeCallback(void *data, zxdg_toplevel_v6 *xdg_toplevel) +{ + auto s = reinterpret_cast(data); + Q_ASSERT(s->xdgtoplevelv6 == xdg_toplevel); + emit s->q->closeRequested(); +} + +XdgTopLevelUnstableV6::Private::Private(XdgShellSurface *q) + : XdgShellSurface::Private(q) +{ +} + +void XdgTopLevelUnstableV6::Private::setupV6(zxdg_surface_v6 *surface, zxdg_toplevel_v6 *topLevel) +{ + Q_ASSERT(surface); + Q_ASSERT(!xdgtoplevelv6); + xdgsurfacev6.setup(surface); + xdgtoplevelv6.setup(topLevel); + zxdg_surface_v6_add_listener(xdgsurfacev6, &s_surfaceListener, this); + zxdg_toplevel_v6_add_listener(xdgtoplevelv6, &s_toplevelListener, this); +} + +void XdgTopLevelUnstableV6::Private::release() +{ + xdgtoplevelv6.release(); + xdgsurfacev6.release(); +} + +void XdgTopLevelUnstableV6::Private::destroy() +{ + xdgtoplevelv6.destroy(); + xdgsurfacev6.destroy(); +} + +bool XdgTopLevelUnstableV6::Private::isValid() const +{ + return xdgtoplevelv6.isValid() && xdgsurfacev6.isValid(); +} + +void XdgTopLevelUnstableV6::Private::setTransientFor(XdgShellSurface *parent) +{ + zxdg_toplevel_v6 *parentSurface = nullptr; + if (parent) { + parentSurface = *parent; + } + zxdg_toplevel_v6_set_parent(xdgtoplevelv6, parentSurface); +} + +void XdgTopLevelUnstableV6::Private::setTitle(const QString & title) +{ + zxdg_toplevel_v6_set_title(xdgtoplevelv6, title.toUtf8().constData()); +} + +void XdgTopLevelUnstableV6::Private::setAppId(const QByteArray & appId) +{ + zxdg_toplevel_v6_set_app_id(xdgtoplevelv6, appId.constData()); +} + +void XdgTopLevelUnstableV6::Private::showWindowMenu(Seat *seat, quint32 serial, qint32 x, qint32 y) +{ + zxdg_toplevel_v6_show_window_menu(xdgtoplevelv6, *seat, serial, x, y); +} + +void XdgTopLevelUnstableV6::Private::move(Seat *seat, quint32 serial) +{ + zxdg_toplevel_v6_move(xdgtoplevelv6, *seat, serial); +} + +void XdgTopLevelUnstableV6::Private::resize(Seat *seat, quint32 serial, Qt::Edges edges) +{ + uint wlEdge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_NONE; + if (edges.testFlag(Qt::TopEdge)) { + if (edges.testFlag(Qt::LeftEdge) && ((edges & ~Qt::LeftEdge) == Qt::TopEdge)) { + wlEdge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT; + } else if (edges.testFlag(Qt::RightEdge) && ((edges & ~Qt::RightEdge) == Qt::TopEdge)) { + wlEdge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_RIGHT; + } else if ((edges & ~Qt::TopEdge) == Qt::Edges()) { + wlEdge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP; + } + } else if (edges.testFlag(Qt::BottomEdge)) { + if (edges.testFlag(Qt::LeftEdge) && ((edges & ~Qt::LeftEdge) == Qt::BottomEdge)) { + wlEdge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_LEFT; + } else if (edges.testFlag(Qt::RightEdge) && ((edges & ~Qt::RightEdge) == Qt::BottomEdge)) { + wlEdge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_RIGHT; + } else if ((edges & ~Qt::BottomEdge) == Qt::Edges()) { + wlEdge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM; + } + } else if (edges.testFlag(Qt::RightEdge) && ((edges & ~Qt::RightEdge) == Qt::Edges())) { + wlEdge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_RIGHT; + } else if (edges.testFlag(Qt::LeftEdge) && ((edges & ~Qt::LeftEdge) == Qt::Edges())) { + wlEdge = ZXDG_TOPLEVEL_V6_RESIZE_EDGE_LEFT; + } + zxdg_toplevel_v6_resize(xdgtoplevelv6, *seat, serial, wlEdge); +} + +void XdgTopLevelUnstableV6::Private::ackConfigure(quint32 serial) +{ + zxdg_surface_v6_ack_configure(xdgsurfacev6, serial); +} + +void XdgTopLevelUnstableV6::Private::setMaximized() +{ + zxdg_toplevel_v6_set_maximized(xdgtoplevelv6); +} + +void XdgTopLevelUnstableV6::Private::unsetMaximized() +{ + zxdg_toplevel_v6_unset_maximized(xdgtoplevelv6); +} + +void XdgTopLevelUnstableV6::Private::setFullscreen(Output *output) +{ + wl_output *o = nullptr; + if (output) { + o = *output; + } + zxdg_toplevel_v6_set_fullscreen(xdgtoplevelv6, o); +} + +void XdgTopLevelUnstableV6::Private::unsetFullscreen() +{ + zxdg_toplevel_v6_unset_fullscreen(xdgtoplevelv6); +} + +void XdgTopLevelUnstableV6::Private::setMinimized() +{ + zxdg_toplevel_v6_set_minimized(xdgtoplevelv6); +} + +void XdgTopLevelUnstableV6::Private::setMaxSize(const QSize &size) +{ + zxdg_toplevel_v6_set_max_size(xdgtoplevelv6, size.width(), size.height()); +} + +void XdgTopLevelUnstableV6::Private::setMinSize(const QSize &size) +{ + zxdg_toplevel_v6_set_min_size(xdgtoplevelv6, size.width(), size.height()); +} + +XdgTopLevelUnstableV6::XdgTopLevelUnstableV6(QObject *parent) + : XdgShellSurface(new Private(this), parent) +{ +} + +XdgTopLevelUnstableV6::~XdgTopLevelUnstableV6() = default; + +class XdgShellPopupUnstableV6::Private : public XdgShellPopup::Private +{ +public: + Private(XdgShellPopup *q); + + void setupV6(zxdg_surface_v6 *s, zxdg_popup_v6 *p) override; + void release() override; + void destroy() override; + bool isValid() const override; + void requestGrab(Seat *seat, quint32 serial) override; + operator zxdg_surface_v6*() override { + return xdgsurfacev6; + } + operator zxdg_surface_v6*() const override { + return xdgsurfacev6; + } + operator zxdg_popup_v6*() override { + return xdgpopupv6; + } + operator zxdg_popup_v6*() const override { + return xdgpopupv6; + } + WaylandPointer xdgsurfacev6; + WaylandPointer xdgpopupv6; + + QRect pendingRect; + +private: + static void configureCallback(void *data, zxdg_popup_v6 *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height); + static void popupDoneCallback(void *data, zxdg_popup_v6 *xdg_popup); + static void surfaceConfigureCallback(void *data, zxdg_surface_v6 *xdg_surface, uint32_t serial); + + static const struct zxdg_popup_v6_listener s_popupListener; + static const struct zxdg_surface_v6_listener s_surfaceListener; +}; + +const struct zxdg_popup_v6_listener XdgShellPopupUnstableV6::Private::s_popupListener = { + configureCallback, + popupDoneCallback +}; + +const struct zxdg_surface_v6_listener XdgShellPopupUnstableV6::Private::s_surfaceListener = { + surfaceConfigureCallback, +}; + +void XdgShellPopupUnstableV6::Private::configureCallback(void *data, zxdg_popup_v6 *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height) +{ + Q_UNUSED(xdg_popup); + auto s = reinterpret_cast(data); + s->pendingRect = QRect(x, y, width, height); +} + +void XdgShellPopupUnstableV6::Private::surfaceConfigureCallback(void *data, struct zxdg_surface_v6 *surface, uint32_t serial) +{ + Q_UNUSED(surface); + auto s = reinterpret_cast(data); + s->q->configureRequested(s->pendingRect, serial); + s->pendingRect = QRect(); +} + +void XdgShellPopupUnstableV6::Private::popupDoneCallback(void *data, zxdg_popup_v6 *xdg_popup) +{ + auto s = reinterpret_cast(data); + Q_ASSERT(s->xdgpopupv6 == xdg_popup); + emit s->q->popupDone(); +} + +XdgShellPopupUnstableV6::Private::Private(XdgShellPopup *q) + : XdgShellPopup::Private(q) +{ +} + +void XdgShellPopupUnstableV6::Private::setupV6(zxdg_surface_v6 *s, zxdg_popup_v6 *p) +{ + Q_ASSERT(p); + Q_ASSERT(!xdgsurfacev6); + Q_ASSERT(!xdgpopupv6); + + xdgsurfacev6.setup(s); + xdgpopupv6.setup(p); + zxdg_surface_v6_add_listener(xdgsurfacev6, &s_surfaceListener, this); + zxdg_popup_v6_add_listener(xdgpopupv6, &s_popupListener, this); +} + +void XdgShellPopupUnstableV6::Private::release() +{ + xdgpopupv6.release(); +} + +void XdgShellPopupUnstableV6::Private::destroy() +{ + xdgpopupv6.destroy(); +} + +bool XdgShellPopupUnstableV6::Private::isValid() const +{ + return xdgpopupv6.isValid(); +} + +void XdgShellPopupUnstableV6::Private::requestGrab(Seat *seat, quint32 serial) +{ + zxdg_popup_v6_grab(xdgpopupv6, *seat, serial); +} + + +XdgShellPopupUnstableV6::XdgShellPopupUnstableV6(QObject *parent) + : XdgShellPopup(new Private(this), parent) +{ +} + +XdgShellPopupUnstableV6::~XdgShellPopupUnstableV6() = default; + +} +} diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -45,6 +45,7 @@ textinput_interface_v2.cpp xdgshell_interface.cpp xdgshell_v5_interface.cpp + xdgshell_v6_interface.cpp ) ecm_add_wayland_server_protocol(SERVER_LIB_SRCS @@ -132,6 +133,11 @@ ) ecm_add_wayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/xdg-shell-unstable-v6.xml + BASENAME xdg-shell-v6 +) + +ecm_add_wayland_server_protocol(SERVER_LIB_SRCS PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/pointer-gestures-unstable-v1.xml BASENAME pointer-gestures-unstable-v1 ) diff --git a/src/server/display.cpp b/src/server/display.cpp --- a/src/server/display.cpp +++ b/src/server/display.cpp @@ -44,6 +44,7 @@ #include "subcompositor_interface.h" #include "textinput_interface_p.h" #include "xdgshell_v5_interface_p.h" +#include "xdgshell_v6_interface_p.h" #include #include @@ -367,6 +368,9 @@ case XdgShellInterfaceVersion::UnstableV5: x = new XdgShellV5Interface(this, parent); break; + case XdgShellInterfaceVersion::UnstableV6: + x = new XdgShellV6Interface(this, parent); + break; } connect(this, &Display::aboutToTerminate, x, [x] { delete x; }); return x; diff --git a/src/server/xdgshell_interface.h b/src/server/xdgshell_interface.h --- a/src/server/xdgshell_interface.h +++ b/src/server/xdgshell_interface.h @@ -50,9 +50,47 @@ /** * xdg_shell (unstable v5) **/ - UnstableV5 + UnstableV5, + /** + * zxdg_shell_v6 (unstable v6) + * @since 5.XDGMERGE_VERSION + **/ + UnstableV6 +}; + +/* + * Flags describing how a popup should be reposition if constrained + * @since 5.XDGMERGE_VERSION + */ +enum class PositionerConstraint { + /* + * Slide the popup on the X axis until there is room + */ + SlideX = 1 << 0, + /* + * Slide the popup on the Y axis until there is room + */ + SlideY = 1 << 1, + /* + * Invert the anchor and gravity on the X axis + */ + FlipX = 1 << 2, + /* + * Invert the anchor and gravity on the Y axis + */ + FlipY = 1 << 3, + /* + * Resize the popup in the X axis + */ + ResizeX = 1 << 4, + /* + * Resize the popup in the Y axis + */ + ResizeY = 1 << 5 }; +Q_DECLARE_FLAGS(PositionerConstraints, PositionerConstraint) + /** * * @since 5.25 @@ -73,19 +111,70 @@ **/ XdgShellSurfaceInterface *getSurface(wl_resource *native); + /** + * Confirm the client is still alive and responding + * + * Will result in pong being emitted + * + * @returns unique identifier for this request + * @since XDGMERGE_VERSION + */ + quint32 ping(); + Q_SIGNALS: void surfaceCreated(KWayland::Server::XdgShellSurfaceInterface *surface); + /** * Emitted whenever a new popup got created. * * A popup only gets created in response to an action on the @p seat. * + * * @param surface The popup xdg shell surface which got created * @param seat The seat on which an action triggered the popup * @param serial The serial of the action on the seat + * + * @deprecated use both xdgPopupCreated + * and XdgShellPopupInterface::grabbed **/ + void popupCreated(KWayland::Server::XdgShellPopupInterface *surface, KWayland::Server::SeatInterface *seat, quint32 serial); + /* + * Emitted whenever a new popup gets created. + * + * @param surface The popup xdg shell surface which got created + * @since XDGMERGE_VERSION + */ + void xdgPopupCreated(KWayland::Server::XdgShellPopupInterface *surface); + + /* + * Emitted in response to a ping request + * + * @param serial unique identifier for the request + * @since XDGMERGE_VERSION + */ + void pongReceived(quint32 serial); + + /* + * Emitted when the application takes more than expected + * to answer to a ping, this will always be emitted before + * eventuallt pingTimeout gets emitted + * + * @param serial unique identifier for the request + * @since XDGMERGE_VERSION + */ + void pingDelayed(quint32 serial); + + /* + * Emitted when the application doesn't answer to a ping + * and the serve gave up on it + * + * @param serial unique identifier for the request + * @since XDGMERGE_VERSION + */ + void pingTimeout(quint32 serial); + protected: class Private; explicit XdgShellInterface(Private *d, QObject *parent = nullptr); @@ -239,6 +328,18 @@ **/ void transientForChanged(); + /** + * Emitted whenever the maximun size hint changes + * @since 5.XDGMERGE_VERSION + */ + void maxSizeChanged(const QSize &size); + + /** + * Emitted whenever the minimum size hint changes + * @since 5.XDGMERGE_VERSION + */ + void minSizeChanged(const QSize &size); + protected: class Private; explicit XdgShellSurfaceInterface(Private *p); @@ -263,26 +364,97 @@ **/ SurfaceInterface *surface() const; + /* + * Ask the popup surface to configure itself for the given configuration. + * + * @arg rect. The position of the surface relative to the transient parent + * @since 5.XDGMERGE_VERSION + */ + quint32 configure(const QRect &rect); + /** * @returns the parent surface. * @see transientOffset **/ QPointer transientFor() const; + /** * The offset of the Surface in the coordinate system of the SurfaceInterface this surface is a transient for. * + * For XDG V6 this returns the point on the anchorRect defined by the anchor edge. + * * @returns offset in parent coordinate system. * @see transientFor **/ QPoint transientOffset() const; + /* + * The size of the surface that is to be positioned. + * + * @since 5.XDGMERGE_VERSION + */ + QSize initialSize() const; + + /* + * The area this popup should be positioned around + * @since 5.XDGMERGE_VERSION + */ + QRect anchorRect() const; + + /* + * Which edge of the anchor should the popup be positioned around + * @since 5.XDGMERGE_VERSION + */ + Qt::Edges anchorEdge() const; + + /* + * An additional offset that should be applied to the popup from the anchor rect + * + * @since 5.XDGMERGE_VERSION + */ + QPoint anchorOffset() const; + + /* + * Specifies in what direction the popup should be positioned around the anchor + * i.e if the gravity is "bottom", then then the top of top of the poup will be at the anchor edge + * if the gravity is top, then the bottom of the popup will be at the anchor edge + * + * @since 5.XDGMERGE_VERSION + */ + + //DAVE left + right is illegal, so this is possible a useless return value? Maybe an enum with 9 entries left, topleft, top, .. + Qt::Edges gravity() const; + + /* + * Specifies how the compositor should position the popup if it does not fit in the requested position + * @since 5.XDGMERGE_VERSION + */ + PositionerConstraints constraintAdjustments() const; + /** * Dismiss this popup. This indicates to the client that it should destroy this popup. * The Compositor can invoke this method when e.g. the user clicked outside the popup * to dismiss it. **/ void popupDone(); +Q_SIGNALS: + /** + * A configure event with @p serial got acknowledged. + * @see configure + * @since 5.XDGMERGE_VERSION + **/ + void configureAcknowledged(quint32 serial); + + /* + * The client requested that this popup gets a grab event + * + * @param seat The seat on which an action triggered the popup + * @param serial The serial of the action on the seat + * @since 5.XDGMERGE_VERSION + */ + void grabbed(KWayland::Server::SeatInterface *seat, quint32 serial); + protected: class Private; explicit XdgShellPopupInterface(Private *p); diff --git a/src/server/xdgshell_interface.cpp b/src/server/xdgshell_interface.cpp --- a/src/server/xdgshell_interface.cpp +++ b/src/server/xdgshell_interface.cpp @@ -31,6 +31,30 @@ { } +void XdgShellInterface::Private::setupTimer(quint32 serial) +{ + QTimer *pingTimer = new QTimer(); + pingTimer->setSingleShot(false); + pingTimer->setInterval(1000); + int attempt = 0; + connect(pingTimer, &QTimer::timeout, q, [this, serial, attempt]() mutable { + ++attempt; + if (attempt == 1) { + emit q->pingDelayed(serial); + } else { + emit q->pingTimeout(serial); + auto timerIt = pingTimers.find(serial); + if (timerIt != pingTimers.end()) { + delete timerIt.value(); + pingTimers.erase(timerIt); + } + } + }); + + pingTimers.insert(serial, pingTimer); + pingTimer->start(); +} + XdgShellInterface::XdgShellInterface(Private *d, QObject *parent) : Global(d, parent) { @@ -50,12 +74,17 @@ return d->interfaceVersion; } +quint32 XdgShellInterface::ping() +{ + return d_func()->ping(); +} + XdgShellInterface::Private *XdgShellInterface::d_func() const { return reinterpret_cast(d.data()); } -XdgShellSurfaceInterface::Private::Private(XdgShellInterfaceVersion interfaceVersion, XdgShellSurfaceInterface *q, XdgShellInterface *c, SurfaceInterface *surface, wl_resource *parentResource, const wl_interface *interface, const void *implementation) +XdgShellSurfaceInterface::Private::Private(XdgShellInterfaceVersion interfaceVersion, XdgShellSurfaceInterface *q, Global *c, SurfaceInterface *surface, wl_resource *parentResource, const wl_interface *interface, const void *implementation) : Resource::Private(q, c, parentResource, interface, implementation) , GenericShellSurface(q, surface) , interfaceVersion(interfaceVersion) @@ -158,18 +187,83 @@ return d->parent; } +QSize XdgShellPopupInterface::initialSize() const +{ + Q_D(); + return d->initialSize; +} + QPoint XdgShellPopupInterface::transientOffset() const { + QRect rect = anchorRect(); + const QPoint center = rect.isEmpty() ? rect.topLeft() : rect.center(); + rect = rect.adjusted(0,0,1,1); //compensate for the stupid QRect::right +1 fiasco + + switch(anchorEdge()) { + case Qt::TopEdge | Qt::LeftEdge: + return rect.topLeft(); + case Qt::TopEdge: + return QPoint(center.x(), rect.y()); + case Qt::TopEdge | Qt::RightEdge: + return rect.topRight(); + case Qt::RightEdge: + return QPoint(rect.right(), center.y()); + case Qt::BottomEdge | Qt::RightEdge: + return rect.bottomRight(); + case Qt::BottomEdge: + return QPoint(center.x(), rect.bottom()); + case Qt::BottomEdge | Qt::LeftEdge: + return rect.bottomLeft(); + case Qt::LeftEdge: + return QPoint(rect.left(), center.y()); + default: + return center; + } + Q_UNREACHABLE(); +} + +QRect XdgShellPopupInterface::anchorRect() const +{ + Q_D(); + return d->anchorRect; +} + +Qt::Edges XdgShellPopupInterface::anchorEdge() const +{ Q_D(); - return d->transientOffset; + return d->anchorEdge; +} + +Qt::Edges XdgShellPopupInterface::gravity() const +{ + Q_D(); + return d->gravity; +} + +QPoint XdgShellPopupInterface::anchorOffset() const +{ + Q_D(); + return d->anchorOffset; +} + +PositionerConstraints XdgShellPopupInterface::constraintAdjustments() const +{ + Q_D(); + return d->constraintAdjustments; } void XdgShellPopupInterface::popupDone() { Q_D(); return d->popupDone(); } +quint32 XdgShellPopupInterface::configure(const QRect &rect) +{ + Q_D(); + return d->configure(rect); +} + XdgShellPopupInterface::Private *XdgShellPopupInterface::d_func() const { return reinterpret_cast(d.data()); diff --git a/src/server/xdgshell_interface_p.h b/src/server/xdgshell_interface_p.h --- a/src/server/xdgshell_interface_p.h +++ b/src/server/xdgshell_interface_p.h @@ -24,6 +24,8 @@ #include "generic_shell_surface_p.h" #include "resource_p.h" +#include + namespace KWayland { namespace Server @@ -34,6 +36,11 @@ public: XdgShellInterfaceVersion interfaceVersion; + virtual quint32 ping() = 0; + void setupTimer(quint32 serial); + //pingserial/timer correspondence + QHash pingTimers; + protected: Private(XdgShellInterfaceVersion interfaceVersion, XdgShellInterface *q, Display *d, const wl_interface *interface, quint32 version); XdgShellInterface *q; @@ -56,8 +63,7 @@ XdgShellInterfaceVersion interfaceVersion; protected: - Private(XdgShellInterfaceVersion interfaceVersion, XdgShellSurfaceInterface *q, XdgShellInterface *c, SurfaceInterface *surface, wl_resource *parentResource, const wl_interface *interface, const void *implementation); - + Private(XdgShellInterfaceVersion interfaceVersion, XdgShellSurfaceInterface *q, Global *c, SurfaceInterface *surface, wl_resource *parentResource, const wl_interface *interface, const void *implementation); }; class XdgShellPopupInterface::Private : public Resource::Private, public GenericShellSurface @@ -70,8 +76,24 @@ return reinterpret_cast(q); } + virtual quint32 configure(const QRect &rect) { + Q_UNUSED(rect) + return 0; + }; + + QVector configureSerials; QPointer parent; - QPoint transientOffset; + QSize initialSize; + + /* + * + */ + QRect anchorRect; + Qt::Edges anchorEdge; + Qt::Edges gravity; + PositionerConstraints constraintAdjustments; + QPoint anchorOffset; + XdgShellInterfaceVersion interfaceVersion; protected: diff --git a/src/server/xdgshell_v5_interface.cpp b/src/server/xdgshell_v5_interface.cpp --- a/src/server/xdgshell_v5_interface.cpp +++ b/src/server/xdgshell_v5_interface.cpp @@ -45,12 +45,15 @@ void createSurface(wl_client *client, uint32_t version, uint32_t id, SurfaceInterface *surface, wl_resource *parentResource); void createPopup(wl_client *client, uint32_t version, uint32_t id, SurfaceInterface *surface, SurfaceInterface *parent, SeatInterface *seat, quint32 serial, const QPoint &pos, wl_resource *parentResource); void bind(wl_client *client, uint32_t version, uint32_t id) override; + quint32 ping() override; static void unbind(wl_resource *resource); static Private *cast(wl_resource *r) { return reinterpret_cast(wl_resource_get_user_data(r)); } + wl_resource *resource = nullptr; + static void destroyCallback(wl_client *client, wl_resource *resource); static void useUnstableVersionCallback(wl_client *client, wl_resource *resource, int32_t version); static void getXdgSurfaceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface); @@ -145,17 +148,30 @@ XdgPopupV5Interface *popupSurface = new XdgPopupV5Interface(q, surface, parentResource); auto d = popupSurface->d_func(); d->parent = QPointer(parent); - d->transientOffset = pos; + d->anchorRect = QRect(pos, QSize(0,0)); + //default open like a normal popup + d->anchorEdge = Qt::BottomEdge; + d->gravity = Qt::TopEdge; d->create(display->getConnection(client), version, id); + + //compat emit q->popupCreated(popupSurface, seat, serial); + + //new system + emit q->xdgPopupCreated(popupSurface); + emit popupSurface->grabbed(seat, serial); } void XdgShellV5Interface::Private::pongCallback(wl_client *client, wl_resource *resource, uint32_t serial) { Q_UNUSED(client) - Q_UNUSED(resource) - Q_UNUSED(serial) - // TODO: implement + auto s = cast(resource); + auto timerIt = s->pingTimers.find(serial); + if (timerIt != s->pingTimers.end() && timerIt.value()->isActive()) { + delete timerIt.value(); + s->pingTimers.erase(timerIt); + emit s->q->pongReceived(serial); + } } XdgShellV5Interface::Private::Private(XdgShellV5Interface *q, Display *d) @@ -167,7 +183,7 @@ void XdgShellV5Interface::Private::bind(wl_client *client, uint32_t version, uint32_t id) { auto c = display->getConnection(client); - wl_resource *resource = c->createResource(&xdg_shell_interface, qMin(version, s_version), id); + resource = c->createResource(&xdg_shell_interface, qMin(version, s_version), id); if (!resource) { wl_client_post_no_memory(client); return; @@ -199,6 +215,18 @@ return nullptr; } +quint32 XdgShellV5Interface::Private::ping() +{ + if (!resource) { + return -1; + } + const quint32 pingSerial = display->nextSerial(); + xdg_shell_send_ping(resource, pingSerial); + + setupTimer(pingSerial); + return pingSerial; +} + XdgShellV5Interface::Private *XdgShellV5Interface::d_func() const { return reinterpret_cast(d.data()); diff --git a/src/server/xdgshell_v5_interface_p.h b/src/server/xdgshell_v5_interface_p.h --- a/src/server/xdgshell_v5_interface_p.h +++ b/src/server/xdgshell_v5_interface_p.h @@ -53,6 +53,8 @@ **/ XdgSurfaceV5Interface *getSurface(wl_resource *native); + void ping(); + private: explicit XdgShellV5Interface(Display *display, QObject *parent = nullptr); friend class Display; diff --git a/src/server/xdgshell_v6_interface.cpp b/src/server/xdgshell_v6_interface.cpp new file mode 100644 --- /dev/null +++ b/src/server/xdgshell_v6_interface.cpp @@ -0,0 +1,962 @@ +/**************************************************************************** +Copyright 2017 David Edmundson + +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 "xdgshell_v6_interface_p.h" +#include "xdgshell_interface_p.h" +#include "generic_shell_surface_p.h" +#include "display.h" +#include "global_p.h" +#include "global.h" +#include "resource_p.h" +#include "output_interface.h" +#include "seat_interface.h" +#include "surface_interface.h" + +#include + +namespace KWayland +{ +namespace Server +{ + +class XdgShellV6Interface::Private : public XdgShellInterface::Private +{ +public: + Private(XdgShellV6Interface *q, Display *d); + + QVector surfaces; + QVector positioners; + +private: + + void createSurface(wl_client *client, uint32_t version, uint32_t id, SurfaceInterface *surface, wl_resource *parentResource); + void createPositioner(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource); + + void bind(wl_client *client, uint32_t version, uint32_t id) override; + + quint32 ping() override; + + static void unbind(wl_resource *resource); + static Private *cast(wl_resource *r) { + return reinterpret_cast(wl_resource_get_user_data(r)); + } + + wl_resource *resource = nullptr; + + static void destroyCallback(wl_client *client, wl_resource *resource); + static void createPositionerCallback(wl_client *client, wl_resource *resource, uint32_t id); + static void getXdgSurfaceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface); + static void pongCallback(wl_client *client, wl_resource *resource, uint32_t serial); + + XdgShellV6Interface *q; + static const struct zxdg_shell_v6_interface s_interface; + static const quint32 s_version; +}; + +class XdgPopupV6Interface::Private : public XdgShellPopupInterface::Private +{ +public: + Private(XdgPopupV6Interface *q, XdgShellV6Interface *c, SurfaceInterface *surface, wl_resource *parentResource); + ~Private(); + + void ackConfigure(quint32 serial) { + if (!configureSerials.contains(serial)) { + // TODO: send error? + return; + } + while (!configureSerials.isEmpty()) { + quint32 i = configureSerials.takeFirst(); + emit q_func()->configureAcknowledged(i); + if (i == serial) { + break; + } + } + } + + void popupDone() override; + quint32 configure(const QRect &rect) override; + + XdgPopupV6Interface *q_func() { + return reinterpret_cast(q); + } +private: + static void grabCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial); + + static const struct zxdg_popup_v6_interface s_interface; +}; + +class XdgSurfaceV6Interface::Private : public KWayland::Server::Resource::Private +{ +public: + Private(XdgSurfaceV6Interface* q, XdgShellV6Interface* c, SurfaceInterface* surface, wl_resource* parentResource); + + ~Private(); + + wl_resource *parentResource; + + XdgSurfaceV6Interface *q_func() { + return reinterpret_cast(q); + } + + void createTopLevel(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource); + void createPopup(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource, wl_resource *parentWindow, wl_resource *positioner); + XdgShellV6Interface *m_shell; + SurfaceInterface *m_surface; + + //effectively a union, only one of these should be populated. + //a surface cannot have two roles + QPointer m_topLevel; + QPointer m_popup; + +private: + static void destroyCallback(wl_client *client, wl_resource *resource); + static void getTopLevelCallback(wl_client *client, wl_resource *resource, uint32_t id); + static void getPopupCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *parent, wl_resource *positioner); + static void ackConfigureCallback(wl_client *client, wl_resource *resource, uint32_t serial); + static void setWindowGeometryCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height); + static const struct zxdg_surface_v6_interface s_interface; +}; + +class XdgTopLevelV6Interface::Private : public XdgShellSurfaceInterface::Private +{ +public: + Private(XdgTopLevelV6Interface* q, XdgShellV6Interface* c, SurfaceInterface* surface, wl_resource* parentResource); + ~Private(); + + void close() override; + + void ackConfigure(quint32 serial) { + if (!configureSerials.contains(serial)) { + // TODO: send error? + return; + } + while (!configureSerials.isEmpty()) { + quint32 i = configureSerials.takeFirst(); + emit q_func()->configureAcknowledged(i); + if (i == serial) { + break; + } + } + } + + quint32 configure(States states, const QSize &size) override { + if (!resource) { + return 0; + } + const quint32 serial = global->display()->nextSerial(); + wl_array state; + wl_array_init(&state); + if (states.testFlag(State::Maximized)) { + uint32_t *s = reinterpret_cast(wl_array_add(&state, sizeof(uint32_t))); + *s = ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED; + } + if (states.testFlag(State::Fullscreen)) { + uint32_t *s = reinterpret_cast(wl_array_add(&state, sizeof(uint32_t))); + *s = ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN; + } + if (states.testFlag(State::Resizing)) { + uint32_t *s = reinterpret_cast(wl_array_add(&state, sizeof(uint32_t))); + *s = ZXDG_TOPLEVEL_V6_STATE_RESIZING; + } + if (states.testFlag(State::Activated)) { + uint32_t *s = reinterpret_cast(wl_array_add(&state, sizeof(uint32_t))); + *s = ZXDG_TOPLEVEL_V6_STATE_ACTIVATED; + } + configureSerials << serial; + zxdg_toplevel_v6_send_configure(resource, size.width(), size.height(), &state); + + zxdg_surface_v6_send_configure(parentResource, serial); + + client->flush(); + wl_array_release(&state); + return serial; + }; + + XdgTopLevelV6Interface *q_func() { + return reinterpret_cast(q); + } + +private: + static void destroyCallback(wl_client *client, wl_resource *resource); + static void setParentCallback(struct wl_client *client, struct wl_resource *resource, wl_resource *parent); + static void showWindowMenuCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial, int32_t x, int32_t y); + static void setMaxSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height); + static void setMinSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height); + static void setMaximizedCallback(wl_client *client, wl_resource *resource); + static void unsetMaximizedCallback(wl_client *client, wl_resource *resource); + static void setFullscreenCallback(wl_client *client, wl_resource *resource, wl_resource *output); + static void unsetFullscreenCallback(wl_client *client, wl_resource *resource); + static void setMinimizedCallback(wl_client *client, wl_resource *resource); + + static const struct zxdg_toplevel_v6_interface s_interface; +}; + + +const quint32 XdgShellV6Interface::Private::s_version = 1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zxdg_shell_v6_interface XdgShellV6Interface::Private::s_interface = { + destroyCallback, + createPositionerCallback, + getXdgSurfaceCallback, + pongCallback +}; +#endif + +void XdgShellV6Interface::Private::destroyCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + // TODO: send protocol error if there are still surfaces mapped + wl_resource_destroy(resource); +} + +void XdgShellV6Interface::Private::createPositionerCallback(wl_client *client, wl_resource *resource, uint32_t id) +{ + auto s = cast(resource); + s->createPositioner(client, wl_resource_get_version(resource), id, resource); +} + +void XdgShellV6Interface::Private::getXdgSurfaceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface) +{ + auto s = cast(resource); + s->createSurface(client, wl_resource_get_version(resource), id, SurfaceInterface::get(surface), resource); +} + +void XdgShellV6Interface::Private::createSurface(wl_client *client, uint32_t version, uint32_t id, SurfaceInterface *surface, wl_resource *parentResource) +{ + auto it = std::find_if(surfaces.constBegin(), surfaces.constEnd(), + [surface](XdgSurfaceV6Interface *s) { + return false; + return surface == s->surface(); + } + ); + if (it != surfaces.constEnd()) { + wl_resource_post_error(surface->resource(), ZXDG_SHELL_V6_ERROR_ROLE, "ShellSurface already created"); + return; + } + XdgSurfaceV6Interface *shellSurface = new XdgSurfaceV6Interface(q, surface, parentResource); + surfaces << shellSurface; + QObject::connect(shellSurface, &XdgSurfaceV6Interface::destroyed, q, + [this, shellSurface] { + surfaces.removeAll(shellSurface); + } + ); + + shellSurface->d->create(display->getConnection(client), version, id); +} + +void XdgShellV6Interface::Private::createPositioner(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource) +{ + Q_UNUSED(client) + + XdgPositionerV6Interface *positioner = new XdgPositionerV6Interface(q, parentResource); + positioners << positioner; + QObject::connect(positioner, &Resource::destroyed, q, + [this, positioner] { + positioners.removeAll(positioner); + } + ); + positioner->d->create(display->getConnection(client), version, id); +} + +void XdgShellV6Interface::Private::pongCallback(wl_client *client, wl_resource *resource, uint32_t serial) +{ + Q_UNUSED(client) + auto s = cast(resource); + auto timerIt = s->pingTimers.find(serial); + if (timerIt != s->pingTimers.end() && timerIt.value()->isActive()) { + delete timerIt.value(); + s->pingTimers.erase(timerIt); + emit s->q->pongReceived(serial); + } +} + +XdgShellV6Interface::Private::Private(XdgShellV6Interface *q, Display *d) + : XdgShellInterface::Private(XdgShellInterfaceVersion::UnstableV6, q, d, &zxdg_shell_v6_interface, 1) + , q(q) +{ +} + +void XdgShellV6Interface::Private::bind(wl_client *client, uint32_t version, uint32_t id) +{ + auto c = display->getConnection(client); + resource = c->createResource(&zxdg_shell_v6_interface, qMin(version, s_version), id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &s_interface, this, unbind); + // TODO: should we track, yes we need to track to be able to ping! +} + +void XdgShellV6Interface::Private::unbind(wl_resource *resource) +{ + auto p = cast(resource); + p->resource = nullptr; +} + +XdgTopLevelV6Interface *XdgShellV6Interface::getSurface(wl_resource *resource) +{ + if (!resource) { + return nullptr; + } + Q_D(); + + for (auto it = d->surfaces.constBegin(); it != d->surfaces.constEnd() ; it++) { + auto topLevel = (*it)->topLevel(); + if (topLevel && topLevel->resource() == resource) { + return topLevel; + } + } + return nullptr; +} + +XdgSurfaceV6Interface *XdgShellV6Interface::realGetSurface(wl_resource *resource) +{ + if (!resource) { + return nullptr; + } + Q_D(); + + for (auto it = d->surfaces.constBegin(); it != d->surfaces.constEnd() ; it++) { + if ((*it)->resource() == resource) { + return (*it); + } + } + return nullptr; +} + +XdgPositionerV6Interface *XdgShellV6Interface::getPositioner(wl_resource *resource) +{ + if (!resource) { + return nullptr; + } + Q_D(); + for (auto it = d->positioners.constBegin(); it != d->positioners.constEnd() ; it++) { + if ((*it)->resource() == resource) { + return *it; + } + } + return nullptr; +} + +quint32 XdgShellV6Interface::Private::ping() +{ + if (!resource) { + return -1; + } + const quint32 pingSerial = display->nextSerial(); + zxdg_shell_v6_send_ping(resource, pingSerial); + + setupTimer(pingSerial); + return pingSerial; +} + +XdgShellV6Interface::Private *XdgShellV6Interface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +namespace { +template <> +Qt::Edges edgesToQtEdges(zxdg_toplevel_v6_resize_edge edges) +{ + Qt::Edges qtEdges; + switch (edges) { + case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP: + qtEdges = Qt::TopEdge; + break; + case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM: + qtEdges = Qt::BottomEdge; + break; + case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_LEFT: + qtEdges = Qt::LeftEdge; + break; + case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT: + qtEdges = Qt::TopEdge | Qt::LeftEdge; + break; + case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_LEFT: + qtEdges = Qt::BottomEdge | Qt::LeftEdge; + break; + case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_RIGHT: + qtEdges = Qt::RightEdge; + break; + case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_RIGHT: + qtEdges = Qt::TopEdge | Qt::RightEdge; + break; + case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_RIGHT: + qtEdges = Qt::BottomEdge | Qt::RightEdge; + break; + case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_NONE: + break; + default: + Q_UNREACHABLE(); + break; + } + return qtEdges; +} +} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zxdg_surface_v6_interface XdgSurfaceV6Interface::Private::s_interface = { + destroyCallback, + getTopLevelCallback, + getPopupCallback, + setWindowGeometryCallback, + ackConfigureCallback +}; +#endif + +void XdgSurfaceV6Interface::Private::destroyCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + //FIXME check if we have attached toplevels first and throw an error + wl_resource_destroy(resource); +} + +void XdgSurfaceV6Interface::Private::getTopLevelCallback(wl_client *client, wl_resource *resource, uint32_t id) +{ + auto s = cast(resource); + s->createTopLevel(client, wl_resource_get_version(resource), id, resource); +} + +void XdgSurfaceV6Interface::Private::createTopLevel(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource) +{ + if (m_topLevel) { + wl_resource_post_error(parentResource, ZXDG_SHELL_V6_ERROR_ROLE, "Toplevel already created on this surface"); + return; + } + if (m_popup) { + wl_resource_post_error(parentResource, ZXDG_SHELL_V6_ERROR_ROLE, "Popup already created on this surface"); + return; + } + m_topLevel = new XdgTopLevelV6Interface (m_shell, m_surface, parentResource); + m_topLevel->d->create(m_shell->display()->getConnection(client), version, id); + + emit m_shell->surfaceCreated(m_topLevel); +} + + +void XdgSurfaceV6Interface::Private::getPopupCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *parent, wl_resource *positioner) +{ + auto s = cast(resource); + s->createPopup(client, wl_resource_get_version(resource), id, resource, parent, positioner); +} + +void XdgSurfaceV6Interface::Private::createPopup(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource, wl_resource *parentSurface, wl_resource *positioner) +{ + + if (m_topLevel) { + wl_resource_post_error(parentResource, ZXDG_SHELL_V6_ERROR_ROLE, "Toplevel already created on this surface"); + return; + } + if (m_popup) { + wl_resource_post_error(parentResource, ZXDG_SHELL_V6_ERROR_ROLE, "Popup already created on this surface"); + return; + } + + auto xdgPositioner = m_shell->getPositioner(positioner); + if (!xdgPositioner) { + wl_resource_post_error(parentResource, ZXDG_SHELL_V6_ERROR_INVALID_POSITIONER, "Invalid positioner"); + return; + } + m_popup = new XdgPopupV6Interface(m_shell, m_surface, parentResource); + auto pd = m_popup->d_func(); + + pd->create(m_shell->display()->getConnection(client), version, id); + + auto parentXdgSurface = m_shell->realGetSurface(parentSurface); + if (parentXdgSurface) { + pd->parent = parentXdgSurface->surface(); + } else { + wl_resource_post_error(parentResource, ZXDG_SHELL_V6_ERROR_INVALID_POPUP_PARENT, "Invalid popup parent"); + return; + } + + pd->initialSize = xdgPositioner->initialSize(); + pd->anchorRect = xdgPositioner->anchorRect(); + pd->anchorEdge = xdgPositioner->anchorEdge(); + pd->gravity = xdgPositioner->gravity(); + pd->constraintAdjustments = xdgPositioner->constraintAdjustments(); + pd->anchorOffset = xdgPositioner->anchorOffset(); + + emit m_shell->xdgPopupCreated(m_popup.data()); +} + + +void XdgSurfaceV6Interface::Private::ackConfigureCallback(wl_client *client, wl_resource *resource, uint32_t serial) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + + if (s->m_topLevel) { + s->m_topLevel->d_func()->ackConfigure(serial); + } else if (s->m_popup) { + s->m_popup->d_func()->ackConfigure(serial); + } +} + +void XdgSurfaceV6Interface::Private::setWindowGeometryCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) +{ + // TODO: implement - not done for v5 either + Q_UNUSED(client) + Q_UNUSED(resource) + Q_UNUSED(x) + Q_UNUSED(y) + Q_UNUSED(width) + Q_UNUSED(height) +} + +XdgSurfaceV6Interface::Private::Private(XdgSurfaceV6Interface *q, XdgShellV6Interface *c, SurfaceInterface *surface, wl_resource *parentResource) + : KWayland::Server::Resource::Private(q, c, parentResource, &zxdg_surface_v6_interface, &s_interface), + m_shell(c), + m_surface(surface) +{ +} + +XdgSurfaceV6Interface::Private::~Private() = default; + + +class XdgPositionerV6Interface::Private : public KWayland::Server::Resource::Private +{ +public: + Private(XdgPositionerV6Interface *q, XdgShellV6Interface *c, wl_resource* parentResource); + + QSize initialSize; + QRect anchorRect; + Qt::Edges anchorEdge; + Qt::Edges gravity; + PositionerConstraints constraintAdjustments; + QPoint anchorOffset; + +private: + static void setSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height); + static void setAnchorRectCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height); + static void setAnchorCallback(wl_client *client, wl_resource *resource, uint32_t anchor); + static void setGravityCallback(wl_client *client, wl_resource *resource, uint32_t gravity); + static void setConstraintAdjustmentCallback(wl_client *client, wl_resource *resource, uint32_t constraint_adjustment); + static void setOffsetCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y); + + static const struct zxdg_positioner_v6_interface s_interface; +}; + +XdgPositionerV6Interface::Private::Private(XdgPositionerV6Interface *q, XdgShellV6Interface *c, wl_resource *parentResource) + : KWayland::Server::Resource::Private(q, c, parentResource, &zxdg_positioner_v6_interface, &s_interface) +{ +} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zxdg_positioner_v6_interface XdgPositionerV6Interface::Private::s_interface = { + resourceDestroyedCallback, + setSizeCallback, + setAnchorRectCallback, + setAnchorCallback, + setGravityCallback, + setConstraintAdjustmentCallback, + setOffsetCallback +}; +#endif + + +void XdgPositionerV6Interface::Private::setSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height) { + Q_UNUSED(client) + auto s = cast(resource); + s->initialSize = QSize(width, height); +} + +void XdgPositionerV6Interface::Private::setAnchorRectCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) +{ + Q_UNUSED(client) + auto s = cast(resource); + s->anchorRect = QRect(x, y, width, height); +} + +void XdgPositionerV6Interface::Private::setAnchorCallback(wl_client *client, wl_resource *resource, uint32_t anchor) { + Q_UNUSED(client) + + auto s = cast(resource); + //Note - see David E's email to wayland-devel about this being bad API + if ((anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) && + (anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT)) { + wl_resource_post_error(resource, ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, "Invalid arguments"); + return; + } + if ((anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP) && + (anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM)) { + wl_resource_post_error(resource, ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, "Invalid arguments"); + return; + } + + Qt::Edges edges; + if (anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) { + edges |= Qt::LeftEdge; + } + if (anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP) { + edges |= Qt::TopEdge; + } + if (anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT) { + edges |= Qt::RightEdge; + } + if (anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) { + edges |= Qt::BottomEdge; + } + + s->anchorEdge = edges; +} + +void XdgPositionerV6Interface::Private::setGravityCallback(wl_client *client, wl_resource *resource, uint32_t gravity) { + Q_UNUSED(client) + auto s = cast(resource); + if ((gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) && + (gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT)) { + wl_resource_post_error(resource, ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, "Invalid arguments"); + return; + } + if ((gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) && + (gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM)) { + wl_resource_post_error(resource, ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, "Invalid arguments"); + return; + } + + Qt::Edges edges; + if (gravity & ZXDG_POSITIONER_V6_ANCHOR_LEFT) { + edges |= Qt::LeftEdge; + } + if (gravity & ZXDG_POSITIONER_V6_ANCHOR_TOP) { + edges |= Qt::TopEdge; + } + if (gravity & ZXDG_POSITIONER_V6_ANCHOR_RIGHT) { + edges |= Qt::RightEdge; + } + if (gravity & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) { + edges |= Qt::BottomEdge; + } + + s->gravity = edges; +} + +void XdgPositionerV6Interface::Private::setConstraintAdjustmentCallback(wl_client *client, wl_resource *resource, uint32_t constraint_adjustment) { + Q_UNUSED(client) + auto s = cast(resource); + PositionerConstraints constraints; + if (constraint_adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X) { + constraints |= PositionerConstraint::SlideX; + } + if (constraint_adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y) { + constraints |= PositionerConstraint::SlideY; + } + if (constraint_adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X) { + constraints |= PositionerConstraint::FlipX; + } + if (constraint_adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y) { + constraints |= PositionerConstraint::FlipY; + } + if (constraint_adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_X) { + constraints |= PositionerConstraint::ResizeX; + } + if (constraint_adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y) { + constraints |= PositionerConstraint::ResizeY; + } + s->constraintAdjustments = constraints; +} + +void XdgPositionerV6Interface::Private::setOffsetCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y) +{ + Q_UNUSED(client) + auto s = cast(resource); + s->anchorOffset = QPoint(x,y); +} + +void XdgTopLevelV6Interface::Private::close() +{ + zxdg_toplevel_v6_send_close(resource); + client->flush(); +} + +void XdgTopLevelV6Interface::Private::setMaxSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + s->q_func()->maxSizeChanged(QSize(width, height)); +} + +void XdgTopLevelV6Interface::Private::setMinSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + s->q_func()->minSizeChanged(QSize(width, height)); +} + +const struct zxdg_toplevel_v6_interface XdgTopLevelV6Interface::Private::s_interface = { + destroyCallback, + setParentCallback, + setTitleCallback, + setAppIdCallback, + showWindowMenuCallback, + moveCallback, + resizeCallback, + setMaxSizeCallback, + setMinSizeCallback, + setMaximizedCallback, + unsetMaximizedCallback, + setFullscreenCallback, + unsetFullscreenCallback, + setMinimizedCallback +}; + +void XdgTopLevelV6Interface::Private::destroyCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + wl_resource_destroy(resource); +} + +void XdgTopLevelV6Interface::Private::setParentCallback(wl_client *client, wl_resource *resource, wl_resource *parent) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + if (!parent) { + //setting null is valid API. Clear + s->parent = nullptr; + emit s->q_func()->transientForChanged(); + } else { + auto parentSurface = static_cast(s->q->global())->getSurface(parent); + if (s->parent.data() != parentSurface) { + s->parent = QPointer(parentSurface); + emit s->q_func()->transientForChanged(); + } + } +} + +void XdgTopLevelV6Interface::Private::showWindowMenuCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial, int32_t x, int32_t y) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + emit s->q_func()->windowMenuRequested(SeatInterface::get(seat), serial, QPoint(x, y)); +} + +XdgTopLevelV6Interface::Private::Private(XdgTopLevelV6Interface *q, XdgShellV6Interface *c, SurfaceInterface *surface, wl_resource *parentResource) + : XdgShellSurfaceInterface::Private(XdgShellInterfaceVersion::UnstableV6, q, c, surface, parentResource, &zxdg_toplevel_v6_interface, &s_interface) +{ +} + +void XdgTopLevelV6Interface::Private::setMaximizedCallback(wl_client *client, wl_resource *resource) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + s->q_func()->maximizedChanged(true); +} + +void XdgTopLevelV6Interface::Private::unsetMaximizedCallback(wl_client *client, wl_resource *resource) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + s->q_func()->maximizedChanged(false); +} + +void XdgTopLevelV6Interface::Private::setFullscreenCallback(wl_client *client, wl_resource *resource, wl_resource *output) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + OutputInterface *o = nullptr; + if (output) { + o = OutputInterface::get(output); + } + s->q_func()->fullscreenChanged(true, o); +} + +void XdgTopLevelV6Interface::Private::unsetFullscreenCallback(wl_client *client, wl_resource *resource) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + s->q_func()->fullscreenChanged(false, nullptr); +} + +void XdgTopLevelV6Interface::Private::setMinimizedCallback(wl_client *client, wl_resource *resource) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + s->q_func()->minimizeRequested(); +} + +XdgTopLevelV6Interface::Private::~Private() = default; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zxdg_popup_v6_interface XdgPopupV6Interface::Private::s_interface = { + resourceDestroyedCallback, + grabCallback +}; +#endif + +XdgPopupV6Interface::Private::Private(XdgPopupV6Interface *q, XdgShellV6Interface *c, SurfaceInterface *surface, wl_resource *parentResource) + : XdgShellPopupInterface::Private(XdgShellInterfaceVersion::UnstableV6, q, c, surface, parentResource, &zxdg_popup_v6_interface, &s_interface) +{ +} + +void XdgPopupV6Interface::Private::grabCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial) +{ + Q_UNUSED(client) + auto s = cast(resource); + auto seatInterface = SeatInterface::get(seat); + s->q_func()->grabbed(seatInterface, serial); +} + +XdgPopupV6Interface::Private::~Private() = default; + +quint32 XdgPopupV6Interface::Private::configure(const QRect &rect) +{ + if (!resource) { + return 0; + } + const quint32 serial = global->display()->nextSerial(); + configureSerials << serial; + zxdg_popup_v6_send_configure(resource, rect.x(), rect.y(), rect.width(), rect.height()); + zxdg_surface_v6_send_configure(parentResource, serial); + client->flush(); + + return serial; +} + +void XdgPopupV6Interface::Private::popupDone() +{ + if (!resource) { + return; + } + // TODO: dismiss all child popups + zxdg_popup_v6_send_popup_done(resource); + client->flush(); +} + +XdgShellV6Interface::XdgShellV6Interface(Display *display, QObject *parent) + : XdgShellInterface(new Private(this, display), parent) +{ +} + +Display* XdgShellV6Interface::display() const +{ + return d->display; +} + +XdgShellV6Interface::~XdgShellV6Interface() = default; + +XdgSurfaceV6Interface::XdgSurfaceV6Interface(XdgShellV6Interface *parent, SurfaceInterface *surface, wl_resource *parentResource) + : KWayland::Server::Resource(new Private(this, parent, surface, parentResource)) +{ +} + +XdgSurfaceV6Interface::~XdgSurfaceV6Interface() = default; + +SurfaceInterface* XdgSurfaceV6Interface::surface() const +{ + Q_D(); + return d->m_surface; +} + +XdgPositionerV6Interface::XdgPositionerV6Interface(XdgShellV6Interface *parent, wl_resource *parentResource) + : KWayland::Server::Resource(new Private(this, parent, parentResource)) +{ +} + +QSize XdgPositionerV6Interface::initialSize() const +{ + Q_D(); + return d->initialSize; +} + +QRect XdgPositionerV6Interface::anchorRect() const +{ + Q_D(); + return d->anchorRect; +} + +Qt::Edges XdgPositionerV6Interface::anchorEdge() const +{ + Q_D(); + return d->anchorEdge; +} + +Qt::Edges XdgPositionerV6Interface::gravity() const +{ + Q_D(); + return d->gravity; +} + +PositionerConstraints XdgPositionerV6Interface::constraintAdjustments() const +{ + Q_D(); + return d->constraintAdjustments; +} + +QPoint XdgPositionerV6Interface::anchorOffset() const +{ + Q_D(); + return d->anchorOffset; +} + + +XdgPositionerV6Interface::Private *XdgPositionerV6Interface::d_func() const +{ + return reinterpret_cast(d.data()); +} + + +XdgTopLevelV6Interface* XdgSurfaceV6Interface::topLevel() const +{ + Q_D(); + return d->m_topLevel.data(); +} + +XdgPopupV6Interface* XdgSurfaceV6Interface::popup() const +{ + Q_D(); + return d->m_popup.data(); +} + +XdgSurfaceV6Interface::Private *XdgSurfaceV6Interface::d_func() const +{ + return reinterpret_cast(d.data()); +} + + +XdgTopLevelV6Interface::XdgTopLevelV6Interface(XdgShellV6Interface *parent, SurfaceInterface *surface, wl_resource *parentResource) + : KWayland::Server::XdgShellSurfaceInterface(new Private(this, parent, surface, parentResource)) +{ +} + +XdgTopLevelV6Interface::~XdgTopLevelV6Interface() = default; + +XdgTopLevelV6Interface::Private *XdgTopLevelV6Interface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +XdgPopupV6Interface::XdgPopupV6Interface(XdgShellV6Interface *parent, SurfaceInterface *surface, wl_resource *parentResource) + : XdgShellPopupInterface(new Private(this, parent, surface, parentResource)) +{ +} + +XdgPopupV6Interface::~XdgPopupV6Interface() = default; + +XdgPopupV6Interface::Private *XdgPopupV6Interface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +} +} + diff --git a/src/server/xdgshell_v6_interface_p.h b/src/server/xdgshell_v6_interface_p.h new file mode 100644 --- /dev/null +++ b/src/server/xdgshell_v6_interface_p.h @@ -0,0 +1,148 @@ +/**************************************************************************** +Copyright 2017 David Edmundson + +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 KWAYLAND_SERVER_XDGSHELL_V6_INTERFACE_P_H +#define KWAYLAND_SERVER_XDGSHELL_V6_INTERFACE_P_H + +#include "global.h" +#include "resource.h" +#include "xdgshell_interface.h" + +#include + +#include + +namespace KWayland +{ +namespace Server +{ + +class Display; +class OutputInterface; +class SeatInterface; +class SurfaceInterface; +class XdgTopLevelV6Interface; +class XdgPopupV6Interface; +class XdgPositionerV6Interface; +class XdgSurfaceV6Interface; +template +class GenericShellSurface; + +class XdgShellV6Interface : public XdgShellInterface +{ + Q_OBJECT +public: + virtual ~XdgShellV6Interface(); + + /** + * @returns The XdgTopLevelV6Interface for the @p native resource. + **/ + XdgTopLevelV6Interface *getSurface(wl_resource *native); + //DAVE we want to rename this, as it's bloody confusing. But XdgShellInterface::getSurface exists and expects that + //also use a less terrible argument name than native. It's obvious it's native from the type + + XdgPositionerV6Interface *getPositioner(wl_resource *native); + + XdgSurfaceV6Interface *realGetSurface(wl_resource *native); + + Display *display() const; + + void ping(); + +private: + explicit XdgShellV6Interface(Display *display, QObject *parent = nullptr); + friend class Display; + class Private; + Private *d_func() const; +}; + +class XdgSurfaceV6Interface : public KWayland::Server::Resource +{ + Q_OBJECT +public: + virtual ~XdgSurfaceV6Interface(); + SurfaceInterface* surface() const; + XdgTopLevelV6Interface* topLevel() const; + XdgPopupV6Interface *popup() const; + +private: + explicit XdgSurfaceV6Interface(XdgShellV6Interface *parent, SurfaceInterface *surface, wl_resource *parentResource); + friend class XdgShellV6Interface; + + class Private; + Private *d_func() const; +}; + +class XdgTopLevelV6Interface : public +XdgShellSurfaceInterface +{ + Q_OBJECT +public: + virtual ~XdgTopLevelV6Interface(); + +private: + explicit XdgTopLevelV6Interface(XdgShellV6Interface *parent, SurfaceInterface *surface, wl_resource *parentResource); + friend class XdgShellV6Interface; + friend class XdgSurfaceV6Interface; + + class Private; + Private *d_func() const; +}; + +class XdgPopupV6Interface : public XdgShellPopupInterface +{ + Q_OBJECT +public: + virtual ~XdgPopupV6Interface(); + +private: + explicit XdgPopupV6Interface(XdgShellV6Interface *parent, SurfaceInterface *surface, wl_resource *parentResource); + friend class XdgShellV6Interface; + friend class XdgSurfaceV6Interface; + friend class GenericShellSurface; + + class Private; + Private *d_func() const; +}; + +/* + * This is a private internal class that keeps track of sent data + * At the time of PopupCreation these values are copied to the popup + */ +class XdgPositionerV6Interface: public KWayland::Server::Resource +{ +public: + QSize initialSize() const; + QRect anchorRect() const; + Qt::Edges anchorEdge() const; + Qt::Edges gravity() const; + PositionerConstraints constraintAdjustments() const; + QPoint anchorOffset() const; +private: + explicit XdgPositionerV6Interface(XdgShellV6Interface *parent, wl_resource *parentResource); + friend class XdgShellV6Interface; + + class Private; + Private *d_func() const; +}; + +} +} + +#endif diff --git a/src/tools/mapping.txt b/src/tools/mapping.txt --- a/src/tools/mapping.txt +++ b/src/tools/mapping.txt @@ -47,6 +47,9 @@ xdg_shell;XdgShellV5 xdg_surface;XdgSurfaceV5 xdg_popup;XdgPopupV5 +zxdg_shell_v6;XdgShellV6 +zxdg_surface_v6;XdgSurfaceV6 +zxdg_popup_v6;XdgPopupV6 zwp_relative_pointer_manager_v1;RelativePointerManagerUnstableV1 zwp_relative_pointer_v1;RelativePointerUnstableV1 zwp_pointer_gestures_v1;PointerGesturesUnstableV1 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -59,3 +59,8 @@ add_executable(plasmasurface-test plasmasurfacetest.cpp) target_link_libraries(plasmasurface-test Qt5::Gui KF5::WaylandClient) ecm_mark_as_test(plasmasurface-test) + + +add_executable(xdg-test xdgtest.cpp) +target_link_libraries(xdg-test Qt5::Gui KF5::WaylandClient) +ecm_mark_as_test(xdg-test) diff --git a/tests/xdgtest.cpp b/tests/xdgtest.cpp new file mode 100644 --- /dev/null +++ b/tests/xdgtest.cpp @@ -0,0 +1,176 @@ +/******************************************************************** +Copyright 2015 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 "../src/client/compositor.h" +#include "../src/client/connection_thread.h" +#include "../src/client/event_queue.h" +#include "../src/client/registry.h" +#include "../src/client/shadow.h" +#include "../src/client/shell.h" +#include "../src/client/shm_pool.h" +#include "../src/client/surface.h" +#include "../src/client/xdgshell.h" +// Qt +#include +#include +#include + +using namespace KWayland::Client; + +class XdgTest : public QObject +{ + Q_OBJECT +public: + explicit XdgTest(QObject *parent = nullptr); + virtual ~XdgTest(); + + void init(); + +private: + void setupRegistry(Registry *registry); + void render(); + void renderPopup(); + QThread *m_connectionThread; + ConnectionThread *m_connectionThreadObject; + EventQueue *m_eventQueue = nullptr; + Compositor *m_compositor = nullptr; + ShmPool *m_shm = nullptr; + Surface *m_surface = nullptr; + XdgShell *m_xdgShell = nullptr; + XdgShellSurface *m_xdgShellSurface = nullptr; + Surface *m_popupSurface = nullptr; + XdgShellPopup *m_xdgShellPopup = nullptr; +}; + +XdgTest::XdgTest(QObject *parent) + : QObject(parent) + , m_connectionThread(new QThread(this)) + , m_connectionThreadObject(new ConnectionThread()) +{ +} + +XdgTest::~XdgTest() +{ + m_connectionThread->quit(); + m_connectionThread->wait(); + m_connectionThreadObject->deleteLater(); +} + +void XdgTest::init() +{ + connect(m_connectionThreadObject, &ConnectionThread::connected, this, + [this] { + m_eventQueue = new EventQueue(this); + m_eventQueue->setup(m_connectionThreadObject); + + Registry *registry = new Registry(this); + setupRegistry(registry); + }, + Qt::QueuedConnection + ); + m_connectionThreadObject->moveToThread(m_connectionThread); + m_connectionThread->start(); + + m_connectionThreadObject->initConnection(); +} + +void XdgTest::setupRegistry(Registry *registry) +{ + connect(registry, &Registry::compositorAnnounced, this, + [this, registry](quint32 name, quint32 version) { + m_compositor = registry->createCompositor(name, version, this); + } + ); + connect(registry, &Registry::shmAnnounced, this, + [this, registry](quint32 name, quint32 version) { + m_shm = registry->createShmPool(name, version, this); + } + ); + connect(registry, &Registry::xdgShellUnstableV6Announced, this, + [this, registry](quint32 name, quint32 version) { + m_xdgShell = registry->createXdgShell(name, version, this); + m_xdgShell->setEventQueue(m_eventQueue); + } + ); + connect(registry, &Registry::interfacesAnnounced, this, + [this] { + Q_ASSERT(m_compositor); + Q_ASSERT(m_xdgShell); + Q_ASSERT(m_shm); + m_surface = m_compositor->createSurface(this); + Q_ASSERT(m_surface); + m_xdgShellSurface = m_xdgShell->createSurface(m_surface, this); + Q_ASSERT(m_xdgShellSurface); + connect(m_xdgShellSurface, &XdgShellSurface::sizeChanged, this, &XdgTest::render); + render(); + + //create popup + m_popupSurface = m_compositor->createSurface(this); + + XdgPositioner positioner(QSize(50,50), QRect(100,100, 20, 20)); + m_xdgShellPopup = m_xdgShell->createPopup(m_popupSurface, m_xdgShellSurface, positioner, this); + renderPopup(); + } + ); + registry->setEventQueue(m_eventQueue); + registry->create(m_connectionThreadObject); + registry->setup(); +} + + +void XdgTest::render() +{ + const QSize &size = m_xdgShellSurface->size().isValid() ? m_xdgShellSurface->size() : QSize(300, 200); + auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef(); + buffer->setUsed(true); + QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied); + image.fill(QColor(255, 255, 255, 128)); + + m_surface->attachBuffer(*buffer); + m_surface->damage(QRect(QPoint(0, 0), size)); + m_surface->commit(Surface::CommitFlag::None); + buffer->setUsed(false); +} + +void XdgTest::renderPopup() +{ + QSize size(200,200); + auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef(); + buffer->setUsed(true); + QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied); + image.fill(QColor(255, 0, 0, 255)); + + m_popupSurface->attachBuffer(*buffer); + m_popupSurface->damage(QRect(QPoint(0, 0), size)); + m_popupSurface->commit(Surface::CommitFlag::None); + buffer->setUsed(false); +} + + + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + XdgTest client; + client.init(); + + return app.exec(); +} + +#include "xdgtest.moc"