diff --git a/autotests/client/CMakeLists.txt b/autotests/client/CMakeLists.txt --- a/autotests/client/CMakeLists.txt +++ b/autotests/client/CMakeLists.txt @@ -330,3 +330,15 @@ target_link_libraries( testSelection Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client) add_test(kwayland-testSelection testSelection) ecm_mark_as_test(testSelection) + +######################################################## +# Test XdgShellV5 +######################################################## +set( testXdgShellV5_SRCS + test_xdg_shell.cpp + ) +add_executable(testXdgShellV5 ${testXdgShellV5_SRCS}) +target_link_libraries( testXdgShellV5 Qt5::Test Qt5::Gui KF5::WaylandServer KF5::WaylandClient Wayland::Client) +add_test(kwayland-testXdgShellV5 testXdgShellV5) +ecm_mark_as_test(testXdgShellV5) + diff --git a/autotests/client/test_wayland_registry.cpp b/autotests/client/test_wayland_registry.cpp --- a/autotests/client/test_wayland_registry.cpp +++ b/autotests/client/test_wayland_registry.cpp @@ -30,6 +30,7 @@ #include "../../src/client/server_decoration.h" #include "../../src/client/shell.h" #include "../../src/client/subcompositor.h" +#include "../../src/client/xdgshell.h" #include "../../src/server/compositor_interface.h" #include "../../src/server/datadevicemanager_interface.h" #include "../../src/server/display.h" @@ -45,12 +46,14 @@ #include "../../src/server/outputmanagement_interface.h" #include "../../src/server/outputdevice_interface.h" #include "../../src/server/textinput_interface.h" +#include "../../src/server/xdgshell_interface.h" // Wayland #include #include #include #include #include +#include class TestWaylandRegistry : public QObject { @@ -76,6 +79,7 @@ void testBindServerSideDecorationManager(); void testBindTextInputManagerUnstableV0(); void testBindTextInputManagerUnstableV2(); + void testBindXdgShellUnstableV5(); void testGlobalSync(); void testGlobalSyncThreaded(); void testRemoval(); @@ -96,6 +100,7 @@ KWayland::Server::ServerSideDecorationManagerInterface *m_serverSideDecorationManager; KWayland::Server::TextInputManagerInterface *m_textInputManagerV0; KWayland::Server::TextInputManagerInterface *m_textInputManagerV2; + KWayland::Server::XdgShellInterface *m_xdgShellUnstableV5; }; static const QString s_socketName = QStringLiteral("kwin-test-wayland-registry-0"); @@ -114,6 +119,7 @@ , m_serverSideDecorationManager(nullptr) , m_textInputManagerV0(nullptr) , m_textInputManagerV2(nullptr) + , m_xdgShellUnstableV5(nullptr) { } @@ -152,6 +158,9 @@ m_textInputManagerV2 = m_display->createTextInputManager(KWayland::Server::TextInputInterfaceVersion::UnstableV2); QCOMPARE(m_textInputManagerV2->interfaceVersion(), KWayland::Server::TextInputInterfaceVersion::UnstableV2); m_textInputManagerV2->create(); + m_xdgShellUnstableV5 = m_display->createXdgShell(KWayland::Server::XdgShellInterfaceVersion::UnstableV5); + m_xdgShellUnstableV5->create(); + QCOMPARE(m_xdgShellUnstableV5->interfaceVersion(), KWayland::Server::XdgShellInterfaceVersion::UnstableV5); } void TestWaylandRegistry::cleanup() @@ -289,6 +298,11 @@ TEST_BIND(KWayland::Client::Registry::Interface::TextInputManagerUnstableV2, SIGNAL(textInputManagerUnstableV2Announced(quint32,quint32)), bindTextInputManagerUnstableV2, zwp_text_input_manager_v2_destroy) } +void TestWaylandRegistry::testBindXdgShellUnstableV5() +{ + TEST_BIND(KWayland::Client::Registry::Interface::XdgShellUnstableV5, SIGNAL(xdgShellUnstableV5Announced(quint32,quint32)), bindXdgShellUnstableV5, xdg_shell_destroy) +} + #undef TEST_BIND void TestWaylandRegistry::testRemoval() diff --git a/autotests/client/test_xdg_shell.cpp b/autotests/client/test_xdg_shell.cpp new file mode 100644 --- /dev/null +++ b/autotests/client/test_xdg_shell.cpp @@ -0,0 +1,649 @@ +/******************************************************************** +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 +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; +}; + +static const QString s_socketName = QStringLiteral("kwayland-test-xdg_shell-0"); + +void XdgShellTest::init() +{ + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + m_display->createShm(); + m_o1Interface = m_display->createOutput(m_display); + m_o1Interface->addMode(QSize(1024, 768)); + m_o1Interface->create(); + m_o2Interface = m_display->createOutput(m_display); + m_o2Interface->addMode(QSize(1024, 768)); + m_o2Interface->create(); + m_seatInterface = m_display->createSeat(m_display); + m_seatInterface->setHasKeyboard(true); + m_seatInterface->setHasPointer(true); + m_seatInterface->setHasTouch(true); + 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->create(); + + // setup connection + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); + QVERIFY(connectedSpy.isValid()); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + m_queue = new EventQueue(this); + m_queue->setup(m_connection); + + Registry registry; + QSignalSpy interfacesAnnouncedSpy(®istry, &Registry::interfacesAnnounced); + QVERIFY(interfacesAnnouncedSpy.isValid()); + QSignalSpy interfaceAnnouncedSpy(®istry, &Registry::interfaceAnnounced); + QVERIFY(interfaceAnnouncedSpy.isValid()); + QSignalSpy outputAnnouncedSpy(®istry, &Registry::outputAnnounced); + QVERIFY(outputAnnouncedSpy.isValid()); + QSignalSpy xdgShellAnnouncedSpy(®istry, &Registry::xdgShellUnstableV5Announced); + QVERIFY(xdgShellAnnouncedSpy.isValid()); + registry.setEventQueue(m_queue); + registry.create(m_connection); + QVERIFY(registry.isValid()); + registry.setup(); + QVERIFY(interfacesAnnouncedSpy.wait()); + + QCOMPARE(outputAnnouncedSpy.count(), 2); + m_output1 = registry.createOutput(outputAnnouncedSpy.first().at(0).value(), outputAnnouncedSpy.first().at(1).value(), this); + m_output2 = registry.createOutput(outputAnnouncedSpy.last().at(0).value(), outputAnnouncedSpy.last().at(1).value(), this); + + m_shmPool = registry.createShmPool(registry.interface(Registry::Interface::Shm).name, registry.interface(Registry::Interface::Shm).version, this); + QVERIFY(m_shmPool); + QVERIFY(m_shmPool->isValid()); + + m_compositor = registry.createCompositor(registry.interface(Registry::Interface::Compositor).name, registry.interface(Registry::Interface::Compositor).version, this); + QVERIFY(m_compositor); + QVERIFY(m_compositor->isValid()); + + m_seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name, registry.interface(Registry::Interface::Seat).version, this); + QVERIFY(m_seat); + QVERIFY(m_seat->isValid()); + + QCOMPARE(xdgShellAnnouncedSpy.count(), 1); + + m_xdgShell = registry.createXdgShell(registry.interface(Registry::Interface::XdgShellUnstableV5).name, + registry.interface(Registry::Interface::XdgShellUnstableV5).version, + this); + QVERIFY(m_xdgShell); + QVERIFY(m_xdgShell->isValid()); +} + +void XdgShellTest::cleanup() +{ +#define CLEANUP(variable) \ + if (variable) { \ + delete variable; \ + variable = nullptr; \ + } + CLEANUP(m_xdgShell) + CLEANUP(m_compositor) + CLEANUP(m_shmPool) + CLEANUP(m_output1) + CLEANUP(m_output2) + CLEANUP(m_seat) + CLEANUP(m_queue) + if (m_connection) { + m_connection->deleteLater(); + m_connection = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + + CLEANUP(m_compositorInterface) + CLEANUP(m_xdgShellInterface) + CLEANUP(m_o1Interface); + CLEANUP(m_o2Interface); + CLEANUP(m_seatInterface); + CLEANUP(m_display) +#undef CLEANUP +} + +void XdgShellTest::testCreateSurface() +{ + // this test verifies that we can create a surface + // first created the signal spies for the server + QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(surfaceCreatedSpy.isValid()); + QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated); + QVERIFY(xdgSurfaceCreatedSpy.isValid()); + + // create surface + QScopedPointer surface(m_compositor->createSurface()); + QVERIFY(!surface.isNull()); + QVERIFY(surfaceCreatedSpy.wait()); + auto serverSurface = surfaceCreatedSpy.first().first().value(); + QVERIFY(serverSurface); + + // create shell surface + QScopedPointer xdgSurface(m_xdgShell->createSurface(surface.data())); + QVERIFY(!xdgSurface.isNull()); + QVERIFY(xdgSurfaceCreatedSpy.wait()); + // verify base things + auto serverXdgSurface = xdgSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverXdgSurface); + QCOMPARE(serverXdgSurface->isConfigurePending(), false); + QCOMPARE(serverXdgSurface->title(), QString()); + QCOMPARE(serverXdgSurface->windowClass(), QByteArray()); + QCOMPARE(serverXdgSurface->isTransient(), false); + QCOMPARE(serverXdgSurface->transientFor(), QPointer()); + QCOMPARE(serverXdgSurface->surface(), serverSurface); + + // now let's destroy it + QSignalSpy destroyedSpy(serverXdgSurface, &QObject::destroyed); + QVERIFY(destroyedSpy.isValid()); + xdgSurface.reset(); + 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 + // first create surface + SURFACE + + // should not have a title yet + QCOMPARE(serverXdgSurface->title(), QString()); + + // lets' change the title + QSignalSpy titleChangedSpy(serverXdgSurface, &XdgShellSurfaceInterface::titleChanged); + QVERIFY(titleChangedSpy.isValid()); + xdgSurface->setTitle(QStringLiteral("foo")); + QVERIFY(titleChangedSpy.wait()); + QCOMPARE(titleChangedSpy.count(), 1); + QCOMPARE(titleChangedSpy.first().first().toString(), QStringLiteral("foo")); + QCOMPARE(serverXdgSurface->title(), QStringLiteral("foo")); +} + +void XdgShellTest::testWindowClass() +{ + // this test verifies that we can change the window class/app id of a shell surface + // first create surface + SURFACE + + // should not have a window class yet + QCOMPARE(serverXdgSurface->windowClass(), QByteArray()); + + // let's change the window class + QSignalSpy windowClassChanged(serverXdgSurface, &XdgShellSurfaceInterface::windowClassChanged); + QVERIFY(windowClassChanged.isValid()); + xdgSurface->setAppId(QByteArrayLiteral("org.kde.xdgsurfacetest")); + QVERIFY(windowClassChanged.wait()); + QCOMPARE(windowClassChanged.count(), 1); + QCOMPARE(windowClassChanged.first().first().toByteArray(), QByteArrayLiteral("org.kde.xdgsurfacetest")); + QCOMPARE(serverXdgSurface->windowClass(), QByteArrayLiteral("org.kde.xdgsurfacetest")); +} + +void XdgShellTest::testMaximize() +{ + // this test verifies that the maximize/unmaximize calls work + SURFACE + + QSignalSpy maximizeRequestedSpy(serverXdgSurface, &XdgShellSurfaceInterface::maximizedChanged); + QVERIFY(maximizeRequestedSpy.isValid()); + + xdgSurface->setMaximized(true); + QVERIFY(maximizeRequestedSpy.wait()); + QCOMPARE(maximizeRequestedSpy.count(), 1); + QCOMPARE(maximizeRequestedSpy.last().first().toBool(), true); + + xdgSurface->setMaximized(false); + QVERIFY(maximizeRequestedSpy.wait()); + QCOMPARE(maximizeRequestedSpy.count(), 2); + QCOMPARE(maximizeRequestedSpy.last().first().toBool(), false); +} + +void XdgShellTest::testMinimize() +{ + // this test verifies that the minimize request is delivered + SURFACE + + QSignalSpy minimizeRequestedSpy(serverXdgSurface, &XdgShellSurfaceInterface::minimizeRequested); + QVERIFY(minimizeRequestedSpy.isValid()); + + xdgSurface->requestMinimize(); + QVERIFY(minimizeRequestedSpy.wait()); + QCOMPARE(minimizeRequestedSpy.count(), 1); +} + +void XdgShellTest::testFullscreen() +{ + qRegisterMetaType(); + // this test verifies going to/from fullscreen + 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 fullscreenSpy(serverXdgSurface, &XdgShellSurfaceInterface::fullscreenChanged); + QVERIFY(fullscreenSpy.isValid()); + + // without an output + xdgSurface->setFullscreen(true, nullptr); + QVERIFY(fullscreenSpy.wait()); + QCOMPARE(fullscreenSpy.count(), 1); + QCOMPARE(fullscreenSpy.last().at(0).toBool(), true); + QVERIFY(!fullscreenSpy.last().at(1).value()); + + // unset + xdgSurface->setFullscreen(false); + QVERIFY(fullscreenSpy.wait()); + QCOMPARE(fullscreenSpy.count(), 2); + QCOMPARE(fullscreenSpy.last().at(0).toBool(), false); + QVERIFY(!fullscreenSpy.last().at(1).value()); + + // with outputs + xdgSurface->setFullscreen(true, m_output1); + QVERIFY(fullscreenSpy.wait()); + QCOMPARE(fullscreenSpy.count(), 3); + QCOMPARE(fullscreenSpy.last().at(0).toBool(), true); + QCOMPARE(fullscreenSpy.last().at(1).value(), m_o1Interface); + + // now other output + xdgSurface->setFullscreen(true, m_output2); + QVERIFY(fullscreenSpy.wait()); + QCOMPARE(fullscreenSpy.count(), 4); + QCOMPARE(fullscreenSpy.last().at(0).toBool(), true); + QCOMPARE(fullscreenSpy.last().at(1).value(), m_o2Interface); +} + +void XdgShellTest::testShowWindowMenu() +{ + qRegisterMetaType(); + // this test verifies that the show window menu request works + SURFACE + + QSignalSpy windowMenuSpy(serverXdgSurface, &XdgShellSurfaceInterface::windowMenuRequested); + QVERIFY(windowMenuSpy.isValid()); + + // TODO: the serial needs to be a proper one + xdgSurface->requestShowWindowMenu(m_seat, 20, QPoint(30, 40)); + QVERIFY(windowMenuSpy.wait()); + QCOMPARE(windowMenuSpy.count(), 1); + QCOMPARE(windowMenuSpy.first().at(0).value(), m_seatInterface); + QCOMPARE(windowMenuSpy.first().at(1).value(), 20u); + QCOMPARE(windowMenuSpy.first().at(2).toPoint(), QPoint(30, 40)); +} + +void XdgShellTest::testMove() +{ + qRegisterMetaType(); + // this test verifies that the move request works + SURFACE + + QSignalSpy moveSpy(serverXdgSurface, &XdgShellSurfaceInterface::moveRequested); + QVERIFY(moveSpy.isValid()); + + // TODO: the serial needs to be a proper one + xdgSurface->requestMove(m_seat, 50); + QVERIFY(moveSpy.wait()); + QCOMPARE(moveSpy.count(), 1); + QCOMPARE(moveSpy.first().at(0).value(), m_seatInterface); + QCOMPARE(moveSpy.first().at(1).value(), 50u); +} + +void XdgShellTest::testResize_data() +{ + QTest::addColumn("edges"); + + QTest::newRow("none") << Qt::Edges(); + QTest::newRow("top") << Qt::Edges(Qt::TopEdge); + QTest::newRow("bottom") << Qt::Edges(Qt::BottomEdge); + QTest::newRow("left") << Qt::Edges(Qt::LeftEdge); + QTest::newRow("top left") << Qt::Edges(Qt::TopEdge | Qt::LeftEdge); + QTest::newRow("bottom left") << Qt::Edges(Qt::BottomEdge | Qt::LeftEdge); + QTest::newRow("right") << Qt::Edges(Qt::RightEdge); + QTest::newRow("top right") << Qt::Edges(Qt::TopEdge | Qt::RightEdge); + QTest::newRow("bottom right") << Qt::Edges(Qt::BottomEdge | Qt::RightEdge); +} + +void XdgShellTest::testResize() +{ + qRegisterMetaType(); + // this test verifies that the resize request works + SURFACE + + QSignalSpy resizeSpy(serverXdgSurface, &XdgShellSurfaceInterface::resizeRequested); + QVERIFY(resizeSpy.isValid()); + + // TODO: the serial needs to be a proper one + QFETCH(Qt::Edges, edges); + xdgSurface->requestResize(m_seat, 60, edges); + QVERIFY(resizeSpy.wait()); + QCOMPARE(resizeSpy.count(), 1); + QCOMPARE(resizeSpy.first().at(0).value(), m_seatInterface); + QCOMPARE(resizeSpy.first().at(1).value(), 60u); + QCOMPARE(resizeSpy.first().at(2).value(), edges); +} + +void XdgShellTest::testTransient() +{ + // this test verifies that setting the transient for works + SURFACE + QScopedPointer surface2(m_compositor->createSurface()); + QScopedPointer xdgSurface2(m_xdgShell->createSurface(surface2.data())); + QVERIFY(xdgSurfaceCreatedSpy.wait()); + auto serverXdgSurface2 = xdgSurfaceCreatedSpy.last().first().value(); + QVERIFY(serverXdgSurface2); + + QVERIFY(!serverXdgSurface->isTransient()); + QVERIFY(!serverXdgSurface2->isTransient()); + + // now make xdsgSurface2 a transient for xdgSurface + QSignalSpy transientForSpy(serverXdgSurface2, &XdgShellSurfaceInterface::transientForChanged); + QVERIFY(transientForSpy.isValid()); + xdgSurface2->setTransientFor(xdgSurface.data()); + + QVERIFY(transientForSpy.wait()); + QCOMPARE(transientForSpy.count(), 1); + QVERIFY(serverXdgSurface2->isTransient()); + QCOMPARE(serverXdgSurface2->transientFor().data(), serverXdgSurface); + QVERIFY(!serverXdgSurface->isTransient()); + + // unset the transient for + xdgSurface2->setTransientFor(nullptr); + QVERIFY(transientForSpy.wait()); + QCOMPARE(transientForSpy.count(), 2); + QVERIFY(!serverXdgSurface2->isTransient()); + QVERIFY(serverXdgSurface2->transientFor().isNull()); + QVERIFY(!serverXdgSurface->isTransient()); +} + +void XdgShellTest::testClose() +{ + // this test verifies that a close request is sent to the client + SURFACE + + QSignalSpy closeSpy(xdgSurface.data(), &XdgShellSurface::closeRequested); + QVERIFY(closeSpy.isValid()); + + serverXdgSurface->close(); + QVERIFY(closeSpy.wait()); + QCOMPARE(closeSpy.count(), 1); + + QSignalSpy destroyedSpy(serverXdgSurface, &XdgShellSurfaceInterface::destroyed); + QVERIFY(destroyedSpy.isValid()); + xdgSurface.reset(); + QVERIFY(destroyedSpy.wait()); +} + +void XdgShellTest::testConfigureStates_data() +{ + QTest::addColumn("serverStates"); + QTest::addColumn("clientStates"); + + const auto sa = XdgShellSurfaceInterface::States(XdgShellSurfaceInterface::State::Activated); + const auto sm = XdgShellSurfaceInterface::States(XdgShellSurfaceInterface::State::Maximized); + const auto sf = XdgShellSurfaceInterface::States(XdgShellSurfaceInterface::State::Fullscreen); + const auto sr = XdgShellSurfaceInterface::States(XdgShellSurfaceInterface::State::Resizing); + + const auto ca = XdgShellSurface::States(XdgShellSurface::State::Activated); + const auto cm = XdgShellSurface::States(XdgShellSurface::State::Maximized); + const auto cf = XdgShellSurface::States(XdgShellSurface::State::Fullscreen); + const auto cr = XdgShellSurface::States(XdgShellSurface::State::Resizing); + + QTest::newRow("none") << XdgShellSurfaceInterface::States() << XdgShellSurface::States(); + QTest::newRow("Active") << sa << ca; + QTest::newRow("Maximize") << sm << cm; + QTest::newRow("Fullscreen") << sf << cf; + QTest::newRow("Resizing") << sr << cr; + + QTest::newRow("Active/Maximize") << (sa | sm) << (ca | cm); + QTest::newRow("Active/Fullscreen") << (sa | sf) << (ca | cf); + QTest::newRow("Active/Resizing") << (sa | sr) << (ca | cr); + QTest::newRow("Maximize/Fullscreen") << (sm | sf) << (cm | cf); + QTest::newRow("Maximize/Resizing") << (sm | sr) << (cm | cr); + QTest::newRow("Fullscreen/Resizing") << (sf | sr) << (cf | cr); + + QTest::newRow("Active/Maximize/Fullscreen") << (sa | sm | sf) << (ca | cm | cf); + QTest::newRow("Active/Maximize/Resizing") << (sa | sm | sr) << (ca | cm | cr); + QTest::newRow("Maximize/Fullscreen|Resizing") << (sm | sf | sr) << (cm | cf | cr); + + QTest::newRow("Active/Maximize/Fullscreen/Resizing") << (sa | sm | sf | sr) << (ca | cm | cf | cr); +} + +void XdgShellTest::testConfigureStates() +{ + qRegisterMetaType(); + // this test verifies that configure states works + SURFACE + + QSignalSpy configureSpy(xdgSurface.data(), &XdgShellSurface::configureRequested); + QVERIFY(configureSpy.isValid()); + + QFETCH(XdgShellSurfaceInterface::States, serverStates); + serverXdgSurface->configure(serverStates); + QVERIFY(configureSpy.wait()); + QCOMPARE(configureSpy.count(), 1); + QCOMPARE(configureSpy.first().at(0).toSize(), QSize(0, 0)); + QTEST(configureSpy.first().at(1).value(), "clientStates"); + QCOMPARE(configureSpy.first().at(2).value(), m_display->serial()); + + QSignalSpy ackSpy(serverXdgSurface, &XdgShellSurfaceInterface::configureAcknowledged); + QVERIFY(ackSpy.isValid()); + + xdgSurface->ackConfigure(configureSpy.first().at(2).value()); + QVERIFY(ackSpy.wait()); + QCOMPARE(ackSpy.count(), 1); + QCOMPARE(ackSpy.first().first().value(), configureSpy.first().at(2).value()); +} + +void XdgShellTest::testConfigureMultipleAcks() +{ + qRegisterMetaType(); + // this test verifies that with multiple configure requests the last acknowledged one acknowledges all + SURFACE + + QSignalSpy configureSpy(xdgSurface.data(), &XdgShellSurface::configureRequested); + QVERIFY(configureSpy.isValid()); + QSignalSpy sizeChangedSpy(xdgSurface.data(), &XdgShellSurface::sizeChanged); + QVERIFY(sizeChangedSpy.isValid()); + QSignalSpy ackSpy(serverXdgSurface, &XdgShellSurfaceInterface::configureAcknowledged); + QVERIFY(ackSpy.isValid()); + + serverXdgSurface->configure(XdgShellSurfaceInterface::States(), QSize(10, 20)); + const quint32 serial1 = m_display->serial(); + serverXdgSurface->configure(XdgShellSurfaceInterface::States(), QSize(20, 30)); + const quint32 serial2 = m_display->serial(); + QVERIFY(serial1 != serial2); + serverXdgSurface->configure(XdgShellSurfaceInterface::States(), QSize(30, 40)); + const quint32 serial3 = m_display->serial(); + QVERIFY(serial1 != serial3); + QVERIFY(serial2 != serial3); + + QVERIFY(configureSpy.wait()); + QCOMPARE(configureSpy.count(), 3); + QCOMPARE(configureSpy.at(0).at(0).toSize(), QSize(10, 20)); + QCOMPARE(configureSpy.at(0).at(1).value(), XdgShellSurface::States()); + QCOMPARE(configureSpy.at(0).at(2).value(), serial1); + QCOMPARE(configureSpy.at(1).at(0).toSize(), QSize(20, 30)); + QCOMPARE(configureSpy.at(1).at(1).value(), XdgShellSurface::States()); + QCOMPARE(configureSpy.at(1).at(2).value(), serial2); + QCOMPARE(configureSpy.at(2).at(0).toSize(), QSize(30, 40)); + QCOMPARE(configureSpy.at(2).at(1).value(), XdgShellSurface::States()); + QCOMPARE(configureSpy.at(2).at(2).value(), serial3); + QCOMPARE(sizeChangedSpy.count(), 3); + QCOMPARE(sizeChangedSpy.at(0).at(0).toSize(), QSize(10, 20)); + QCOMPARE(sizeChangedSpy.at(1).at(0).toSize(), QSize(20, 30)); + QCOMPARE(sizeChangedSpy.at(2).at(0).toSize(), QSize(30, 40)); + QCOMPARE(xdgSurface->size(), QSize(30, 40)); + + xdgSurface->ackConfigure(serial3); + QVERIFY(ackSpy.wait()); + QCOMPARE(ackSpy.count(), 3); + QCOMPARE(ackSpy.at(0).first().value(), serial1); + QCOMPARE(ackSpy.at(1).first().value(), serial2); + QCOMPARE(ackSpy.at(2).first().value(), serial3); + + // configure once more with a null size + serverXdgSurface->configure(XdgShellSurfaceInterface::States()); + // should not change size + QVERIFY(configureSpy.wait()); + QCOMPARE(configureSpy.count(), 4); + QCOMPARE(sizeChangedSpy.count(), 3); + 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/src/client/CMakeLists.txt b/src/client/CMakeLists.txt --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -45,6 +45,8 @@ textinput.cpp textinput_v0.cpp textinput_v2.cpp + xdgshell.cpp + xdgshell_v5.cpp ) ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS @@ -117,6 +119,10 @@ PROTOCOL ${KWAYLAND_SOURCE_DIR}/src/client/protocols/text-input-unstable-v2.xml BASENAME text-input-v2 ) +ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS + PROTOCOL ${KWAYLAND_SOURCE_DIR}/src/client/protocols/xdg-shell-unstable-v5.xml + BASENAME xdg-shell-v5 +) add_library(KF5WaylandClient ${CLIENT_LIB_SRCS}) generate_export_header(KF5WaylandClient @@ -185,6 +191,7 @@ surface.h touch.h textinput.h + xdgshell.h DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KWayland/Client COMPONENT Devel ) diff --git a/src/client/protocols/xdg-shell-unstable-v5.xml b/src/client/protocols/xdg-shell-unstable-v5.xml new file mode 100644 --- /dev/null +++ b/src/client/protocols/xdg-shell-unstable-v5.xml @@ -0,0 +1,625 @@ + + + + + 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. + + + + + The 'current' member of this enum gives the version of the + protocol. Implementations can compare this to the version + they implement using static_assert to ensure the protocol and + implementation versions match. + + + + + + + + + + + + + + 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. + + + + + + Negotiate the unstable version of the interface. This + mechanism is in place to ensure client and server agree on the + unstable versions of the protocol that they speak or exit + cleanly if they don't agree. This request will go away once + the xdg-shell protocol is stable. + + + + + + + This creates an xdg_surface for the given surface and gives it the + xdg_surface role. A wl_surface can only be given an xdg_surface role + once. If get_xdg_surface is called with a wl_surface that already has + an active xdg_surface associated with it, or if it had any other role, + an error is raised. + + See the documentation of xdg_surface for more details about what an + xdg_surface is and how it is used. + + + + + + + + This creates an xdg_popup for the given surface and gives it the + xdg_popup role. A wl_surface can only be given an xdg_popup role + once. If get_xdg_popup is called with a wl_surface that already has + an active xdg_popup associated with it, or if it had any other role, + an error is raised. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + + + + + + + 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. + + 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. + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides requests to treat surfaces like windows, allowing to set + properties like maximized, fullscreen, minimized, and to move and resize + them, and associate metadata like title and app id. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. Prior to committing the new + state, it can set up initial configuration, such as maximizing or setting + a window geometry. + + Even without attaching a buffer the compositor must respond to initial + committed configuration, for instance sending a configure event with + expected window geometry if the client maximized its surface during + initialization. + + For a surface to be mapped by the compositor the client must have + committed both an xdg_surface state and a buffer. + + + + + 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. + + Desktop environments may extend this enum by taking up a range of + values and documenting the range they chose in this description. + They are not required to document the values for the range that they + chose. Ideally, any good extensions from a desktop environment should + make its way into standardization into this enum. + + The current reserved ranges are: + + 0x0000 - 0x0FFF: xdg-shell core values, documented below. + 0x1000 - 0x1FFF: GNOME + 0x2000 - 0x2FFF: EFL + + + + 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. + + + + + + + The configure event asks the client to resize its surface or to + change its state. + + 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 need 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 should arrange their surface for the new size and + states, and then send a 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. + + + + + + + + + + + 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, 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. + + The compositor expects that the most recently received + ack_configure request at the time of a commit indicates which + configure event the client is responding to. + + + + + + + The window geometry of a window 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 once, 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 mode is meant for extremely simple clients. + + If responding to a configure event, the window geometry in here + must respect the sizing negotiations specified by the states in + the configure event. + + 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. + + + + + + + + + + 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. + + + + + + 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 your + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data... + + + + + + + A popup surface is a short-lived, temporary surface that can be + used to implement menus. It takes an explicit grab on the surface + that will be dismissed when the user dismisses the 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. + + When the popup is dismissed, a popup_done event will be sent out, + and at the same time the surface will be unmapped. The xdg_popup + object is now inert and cannot be reactivated, so clients should + destroy it. Explicitly destroying the xdg_popup object will also + dismiss the popup and unmap the surface. + + Clients will receive events for all their surfaces during this + grab (which is an "owner-events" grab in X11 parlance). This is + done so that users can navigate through submenus and other + "nested" popup windows without having to dismiss the topmost + popup. + + 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 an xdg_surface or xdg_popup + role. + + Specifying an xdg_popup for the parent 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. + + If there is an existing popup when creating a new popup, the + parent must be the current topmost popup. + + A parent surface must be mapped before the new popup is mapped. + + When compositors choose to dismiss a popup, they will likely + dismiss every nested popup as well. When a compositor dismisses + popups, it will follow the same dismissing order as required + from the client. + + 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_shell.get_xdg_popup. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_popup state to take effect. + + For a surface to be mapped by the compositor the client must have + committed both the xdg_popup state and a buffer. + + + + + 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. + + + + + + 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 @@ -49,6 +49,7 @@ struct org_kde_plasma_shell; struct org_kde_plasma_window_management; struct org_kde_kwin_server_decoration_manager; +struct xdg_shell; namespace KWayland { @@ -80,6 +81,7 @@ class TextInputManager; class TextInputManagerUnstableV0; class TextInputManagerUnstableV2; +class XdgShell; /** * @short Wrapper for the wl_registry interface. @@ -138,7 +140,8 @@ OutputDevice, ///< Refers to the org_kde_kwin_outputdevice interface ServerSideDecorationManager, ///< Refers to org_kde_kwin_server_decoration_manager TextInputManagerUnstableV0, ///< Refers to wl_text_input_manager, @since 5.23 - TextInputManagerUnstableV2 ///< Refers to zwp_text_input_manager_v2, @since 5.23 + TextInputManagerUnstableV2, ///< Refers to zwp_text_input_manager_v2, @since 5.23 + XdgShellUnstableV5 ///< Refers to xdg_shell (unstable version 5), @since 5.25 }; explicit Registry(QObject *parent = nullptr); virtual ~Registry(); @@ -463,6 +466,16 @@ * @since 5.23 **/ zwp_text_input_manager_v2 *bindTextInputManagerUnstableV2(uint32_t name, uint32_t version) const; + /** + * Binds the xdg_shell (unstable version 5) 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.25 + **/ + xdg_shell *bindXdgShellUnstableV5(uint32_t name, uint32_t version) const; ///@} /** @@ -801,6 +814,24 @@ * @since 5.23 **/ TextInputManager *createTextInputManager(quint32 name, quint32 version, QObject *parent = nullptr); + /** + * Creates an XdgShell and sets it up to manage the interface identified by + * @p name and @p version. + * + * This factory method supports the following interfaces: + * @li xdg_shell (Unstable version 5) + * + * If @p name is for one of the supported interfaces the corresponding shell will be created, + * otherwise @c null will be returned. + * + * @param name The name of the interface to bind + * @param version The version of the interface to use + * @param parent The parent for the XdgShell + * + * @returns The created XdgShell + * @since 5.25 + **/ + XdgShell *createXdgShell(quint32 name, quint32 version, QObject *parent = nullptr); ///@} /** @@ -962,6 +993,13 @@ * @since 5.23 **/ void textInputManagerUnstableV2Announced(quint32 name, quint32 version); + /** + * Emitted whenever a xdg_shell (unstable version 5) 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 xdgShellUnstableV5Announced(quint32 name, quint32 version); ///@} /** * @name Interface removed signals. @@ -1091,6 +1129,12 @@ * @since 5.23 **/ void textInputManagerUnstableV2Removed(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 xdgShellUnstableV5Removed(quint32 name); ///@} /** * Generic announced signal which gets emitted whenever an interface gets diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -43,6 +43,8 @@ #include "shm_pool.h" #include "subcompositor.h" #include "textinput_p.h" +#include "xdgshell.h" +#include "xdgshell_p.h" #include "wayland_pointer_p.h" // Qt #include @@ -63,6 +65,7 @@ #include #include #include +#include /***** * How to add another interface: @@ -244,6 +247,13 @@ &zwp_text_input_manager_v2_interface, &Registry::textInputManagerUnstableV2Announced, &Registry::textInputManagerUnstableV2Removed + }}, + {Registry::Interface::XdgShellUnstableV5, { + 1, + QByteArrayLiteral("xdg_shell"), + &xdg_shell_interface, + &Registry::xdgShellUnstableV5Announced, + &Registry::xdgShellUnstableV5Removed }} }; @@ -538,6 +548,7 @@ BIND(ServerSideDecorationManager, org_kde_kwin_server_decoration_manager) BIND(TextInputManagerUnstableV0, wl_text_input_manager) BIND(TextInputManagerUnstableV2, zwp_text_input_manager_v2) +BIND(XdgShellUnstableV5, xdg_shell) BIND2(ShadowManager, Shadow, org_kde_kwin_shadow_manager) BIND2(BlurManager, Blur, org_kde_kwin_blur_manager) BIND2(ContrastManager, Contrast, org_kde_kwin_contrast_manager) @@ -607,6 +618,16 @@ } } +XdgShell *Registry::createXdgShell(quint32 name, quint32 version, QObject *parent) +{ + switch (d->interfaceForName(name)) { + case Interface::XdgShellUnstableV5: + return d->create(name, version, parent, &Registry::bindXdgShellUnstableV5); + default: + return nullptr; + } +} + namespace { static const wl_interface *wlInterface(Registry::Interface interface) { diff --git a/src/client/xdgshell.h b/src/client/xdgshell.h new file mode 100644 --- /dev/null +++ b/src/client/xdgshell.h @@ -0,0 +1,424 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#ifndef KWAYLAND_CLIENT_XDG_SHELL_V5_H +#define KWAYLAND_CLIENT_XDG_SHELL_V5_H + +#include + +#include + +struct xdg_shell; +struct xdg_surface; +struct xdg_popup; + +namespace KWayland +{ +namespace Client +{ + +class EventQueue; +class Output; +class Surface; +class Seat; +class XdgShellPopup; +class XdgShellSurface; + +/** + * @short Wrapper for the xdg_shell interface. + * + * This class provides a convenient wrapper for the xdg_shell interface. + * + * To use this class one needs to interact with the Registry. There are two + * possible ways to create the XdgShell interface: + * @code + * XdgShell *c = registry->createXdgShell(name, version); + * @endcode + * + * This creates the XdgShell and sets it up directly. As an alternative this + * can also be done in a more low level way: + * @code + * XdgShell *c = new XdgShell; + * c->setup(registry->bindXdgShell(name, version)); + * @endcode + * + * The XdgShell can be used as a drop-in replacement for any xdg_shell + * pointer as it provides matching cast operators. + * + * @see Registry + * @since 5.25 + **/ +class KWAYLANDCLIENT_EXPORT XdgShell : public QObject +{ + Q_OBJECT +public: + virtual ~XdgShell(); + + /** + * Setup this XdgShell to manage the @p xdgshellv5. + * When using Registry::createXdgShell there is no need to call this + * method. + **/ + void setup(xdg_shell *xdgshellv5); + /** + * @returns @c true if managing a xdg_shell. + **/ + bool isValid() const; + /** + * Releases the xdg_shell interface. + * After the interface has been released the XdgShell instance is no + * longer valid and can be setup with another xdg_shell interface. + **/ + void release(); + /** + * Destroys the data held by this XdgShell. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new xdg_shell interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, xdgshellv5, &XdgShell::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + /** + * Sets the @p queue to use for creating objects with this XdgShell. + **/ + void setEventQueue(EventQueue *queue); + /** + * @returns The event queue to use for creating objects with this XdgShell. + **/ + EventQueue *eventQueue(); + + /** + * Creates a new XdgShellSurface for the given @p surface. + **/ + XdgShellSurface *createSurface(Surface *surface, QObject *parent = nullptr); + + /** + * Creates a new XdgShellPopup for the given @p surface on top of @p parentSurface. + **/ + XdgShellPopup *createPopup(Surface *surface, Surface *parentSurface, Seat *seat, quint32 serial, const QPoint &parentPos, QObject *parent = nullptr); + + operator xdg_shell*(); + operator xdg_shell*() const; + +Q_SIGNALS: + /** + * The corresponding global for this interface on the Registry got removed. + * + * This signal gets only emitted if the XdgShell got created by + * Registry::createXdgShell + **/ + void removed(); + +protected: + /** + * Creates a new XdgShell. + * Note: after constructing the XdgShell it is not yet valid and one needs + * to call setup. In order to get a ready to use XdgShell prefer using + * Registry::createXdgShell. + **/ + class Private; + explicit XdgShell(Private *p, QObject *parent = nullptr); + +private: + QScopedPointer d; +}; + +/** + * + * @since 5.25 + **/ +class KWAYLANDCLIENT_EXPORT XdgShellSurface : public QObject +{ + Q_OBJECT +public: + virtual ~XdgShellSurface(); + /** + * States the Surface can be in + **/ + enum class State { + /** + * The Surface is maximized. + **/ + Maximized = 1 << 0, + /** + * The Surface is fullscreen. + **/ + Fullscreen = 1 << 1, + /** + * The Surface is currently being resized by the Compositor. + **/ + Resizing = 1 << 2, + /** + * The Surface is considered active. Does not imply keyboard focus. + **/ + Activated = 1 << 3 + }; + Q_DECLARE_FLAGS(States, State) + + /** + * Setup this XdgShellSurface to manage the @p xdgsurfacev5. + * When using XdgShell::createXdgShellSurface there is no need to call this + * method. + **/ + void setup(xdg_surface *xdgsurfacev5); + /** + * @returns @c true if managing a xdg_surface. + **/ + bool isValid() const; + /** + * Releases the xdg_surface interface. + * After the interface has been released the XdgShellSurface instance is no + * longer valid and can be setup with another xdg_surface interface. + **/ + void release(); + /** + * Destroys the data held by this XdgShellSurface. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new xdg_surface interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, xdgsurfacev5, &XdgShellSurface::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + /** + * Sets the @p queue to use for bound proxies. + **/ + void setEventQueue(EventQueue *queue); + /** + * @returns The event queue to use for bound proxies. + **/ + EventQueue *eventQueue(); + + /** + * The currently configured size. + * @see sizeChanged + * @see setSize + **/ + QSize size() const; + + /** + * Sets the size for the XdgShellSurface to @p size. + * This is mostly an internal information. The actual size of the XdgShellSurface is + * determined by the size of the Buffer attached to the XdgShellSurface's Surface. + * + * @param size The new size to be used for the XdgShellSurface + * @see size + * @see sizeChanged + **/ + void setSize(const QSize &size); + + /** + * Set this XdgShellSurface as transient for @p parent. + **/ + void setTransientFor(XdgShellSurface *parent); + + /** + * Sets the window title of this XdgShellSurface to @p title. + **/ + void setTitle(const QString &title); + + /** + * Set an application identifier for the surface. + **/ + void setAppId(const QByteArray &appId); + + /** + * Requests to show the window menu at @p pos in surface coordinates. + **/ + void requestShowWindowMenu(Seat *seat, quint32 serial, const QPoint &pos); + + /** + * Requests a move on the given @p seat after the pointer button press with the given @p serial. + * + * @param seat The seat on which to move the window + * @param serial The serial of the pointer button press which should trigger the move + **/ + void requestMove(Seat *seat, quint32 serial); + + /** + * Requests a resize on the given @p seat after the pointer button press with the given @p serial. + * + * @param seat The seat on which to resize the window + * @param serial The serial of the pointer button press which should trigger the resize + * @param edges A hint for the compositor to set e.g. an appropriate cursor image + **/ + void requestResize(Seat *seat, quint32 serial, Qt::Edges edges); + + /** + * When a configure event is received, if a client commits the + * Surface in response to the configure event, then the client + * must make an ackConfigure request sometime before the commit + * request, passing along the @p serial of the configure event. + * @see configureRequested + **/ + void ackConfigure(quint32 serial); + + /** + * Request to set this XdgShellSurface to be maximized if @p set is @c true. + * If @p set is @c false it requests to unset the maximized state - if set. + * + * @param set Whether the XdgShellSurface should be maximized + **/ + void setMaximized(bool set); + + /** + * Request to set this XdgShellSurface as fullscreen on @p output. + * If @p set is @c true the Surface should be set to fullscreen, otherwise restore + * from fullscreen state. + * + * @param set Whether the Surface should be fullscreen or not + * @param output Optional output as hint to the compositor where the Surface should be put + **/ + void setFullscreen(bool set, Output *output = nullptr); + + /** + * Request to the compositor to minimize this XdgShellSurface. + **/ + void requestMinimize(); + + operator xdg_surface*(); + operator xdg_surface*() const; + +Q_SIGNALS: + /** + * The compositor requested to close this window. + **/ + void closeRequested(); + /** + * The compositor sent a configure with the new @p size and the @p states. + * Before the next commit of the surface the @p serial needs to be passed to ackConfigure. + **/ + void configureRequested(const QSize &size, KWayland::Client::XdgShellSurface::States states, quint32 serial); + + /** + * Emitted whenever the size of the XdgShellSurface changes by e.g. receiving a configure request. + * + * @see configureRequested + * @see size + * @see setSize + **/ + void sizeChanged(const QSize &); + +protected: + class Private; + explicit XdgShellSurface(Private *p, QObject *parent = nullptr); + +private: + QScopedPointer d; +}; + +/** + * A XdgShellPopup is a short-lived, temporary surface that can be + * used to implement menus. It takes an explicit grab on the surface + * that will be dismissed when the user dismisses the 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. + * @since 5.25 + **/ +class KWAYLANDCLIENT_EXPORT XdgShellPopup : public QObject +{ + Q_OBJECT +public: + virtual ~XdgShellPopup(); + + /** + * Setup this XdgShellPopup to manage the @p xdgpopupv5. + * When using XdgShell::createXdgShellPopup there is no need to call this + * method. + **/ + void setup(xdg_popup *xdgpopupv5); + /** + * @returns @c true if managing an xdg_popup. + **/ + bool isValid() const; + /** + * Releases the xdg_popup interface. + * After the interface has been released the XdgShellPopup instance is no + * longer valid and can be setup with another xdg_popup interface. + **/ + void release(); + /** + * Destroys the data held by this XdgShellPopup. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new xdg_popup interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, xdgpopupv5, &XdgShellPopup::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + /** + * Sets the @p queue to use for bound proxies. + **/ + void setEventQueue(EventQueue *queue); + /** + * @returns The event queue to use for bound proxies. + **/ + EventQueue *eventQueue(); + + operator xdg_popup*(); + operator xdg_popup*() 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(); + +protected: + class Private; + explicit XdgShellPopup(Private *p, QObject *parent = nullptr); + +private: + QScopedPointer d; +}; + +} +} + +Q_DECLARE_METATYPE(KWayland::Client::XdgShellSurface::State) +Q_DECLARE_METATYPE(KWayland::Client::XdgShellSurface::States) + +#endif diff --git a/src/client/xdgshell.cpp b/src/client/xdgshell.cpp new file mode 100644 --- /dev/null +++ b/src/client/xdgshell.cpp @@ -0,0 +1,281 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#include "xdgshell_p.h" +#include "event_queue.h" +#include "wayland_pointer_p.h" +#include "seat.h" +#include "surface.h" +#include "output.h" +#include + +namespace KWayland +{ +namespace Client +{ + +XdgShell::Private::~Private() = default; + +XdgShell::XdgShell(Private *p, QObject *parent) + : QObject(parent) + , d(p) +{ +} + +XdgShell::~XdgShell() +{ + release(); +} + +void XdgShell::setup(xdg_shell *xdgshellv5) +{ + d->setupV5(xdgshellv5); +} + +void XdgShell::release() +{ + d->release(); +} + +void XdgShell::destroy() +{ + d->destroy(); +} + +void XdgShell::setEventQueue(EventQueue *queue) +{ + d->queue = queue; +} + +EventQueue *XdgShell::eventQueue() +{ + return d->queue; +} + +XdgShell::operator xdg_shell*() { + return *(d.data()); +} + +XdgShell::operator xdg_shell*() const { + return *(d.data()); +} + +bool XdgShell::isValid() const +{ + return d->isValid(); +} + +XdgShellSurface *XdgShell::createSurface(Surface *surface, QObject *parent) +{ + return d->getXdgSurface(surface, parent); +} + +XdgShellPopup *XdgShell::createPopup(Surface *surface, Surface *parentSurface, Seat *seat, quint32 serial, const QPoint &parentPos, QObject *parent) +{ + return d->getXdgPopup(surface, parentSurface, seat, serial, parentPos, parent); +} + +XdgShellSurface::Private::Private(XdgShellSurface *q) + : q(q) +{ +} + +XdgShellSurface::Private::~Private() = default; + +XdgShellSurface::XdgShellSurface(Private *p, QObject *parent) + : QObject(parent) + , d(p) +{ +} + +XdgShellSurface::~XdgShellSurface() +{ + release(); +} + +void XdgShellSurface::setup(xdg_surface *xdgsurfacev5) +{ + d->setupV5(xdgsurfacev5); +} + +void XdgShellSurface::release() +{ + d->release(); +} + +void XdgShellSurface::destroy() +{ + d->destroy(); +} + +void XdgShellSurface::setEventQueue(EventQueue *queue) +{ + d->queue = queue; +} + +EventQueue *XdgShellSurface::eventQueue() +{ + return d->queue; +} + +XdgShellSurface::operator xdg_surface*() { + return *(d.data()); +} + +XdgShellSurface::operator xdg_surface*() const { + return *(d.data()); +} + +bool XdgShellSurface::isValid() const +{ + return d->isValid(); +} + +void XdgShellSurface::setTransientFor(XdgShellSurface *parent) +{ + d->setTransientFor(parent); +} + +void XdgShellSurface::setTitle(const QString &title) +{ + d->setTitle(title); +} + +void XdgShellSurface::setAppId(const QByteArray &appId) +{ + d->setAppId(appId); +} + +void XdgShellSurface::requestShowWindowMenu(Seat *seat, quint32 serial, const QPoint &pos) +{ + d->showWindowMenu(seat, serial, pos.x(), pos.y()); +} + +void XdgShellSurface::requestMove(Seat *seat, quint32 serial) +{ + d->move(seat, serial); +} + +void XdgShellSurface::requestResize(Seat *seat, quint32 serial, Qt::Edges edges) +{ + d->resize(seat, serial, edges); +} + +void XdgShellSurface::ackConfigure(quint32 serial) +{ + d->ackConfigure(serial); +} + +void XdgShellSurface::setMaximized(bool set) +{ + if (set) { + d->setMaximized(); + } else { + d->unsetMaximized(); + } +} + +void XdgShellSurface::setFullscreen(bool set, Output *output) +{ + if (set) { + d->setFullscreen(output); + } else { + d->unsetFullscreen(); + } +} + +void XdgShellSurface::requestMinimize() +{ + d->setMinimized(); +} + +void XdgShellSurface::setSize(const QSize &size) +{ + if (d->size == size) { + return; + } + d->size = size; + emit sizeChanged(size); +} + +QSize XdgShellSurface::size() const +{ + return d->size; +} + +XdgShellPopup::Private::~Private() = default; + + +XdgShellPopup::Private::Private(XdgShellPopup *q) + : q(q) +{ +} + +XdgShellPopup::XdgShellPopup(Private *p, QObject *parent) + : QObject(parent) + , d(p) +{ +} + +XdgShellPopup::~XdgShellPopup() +{ + release(); +} + +void XdgShellPopup::setup(xdg_popup *xdgpopupv5) +{ + d->setupV5(xdgpopupv5); +} + +void XdgShellPopup::release() +{ + d->release(); +} + +void XdgShellPopup::destroy() +{ + d->destroy(); +} + +void XdgShellPopup::setEventQueue(EventQueue *queue) +{ + d->queue = queue; +} + +EventQueue *XdgShellPopup::eventQueue() +{ + return d->queue; +} + +XdgShellPopup::operator xdg_popup*() { + return *(d.data()); +} + +XdgShellPopup::operator xdg_popup*() const { + return *(d.data()); +} + +bool XdgShellPopup::isValid() const +{ + return d->isValid(); +} + +} +} + diff --git a/src/client/xdgshell_p.h b/src/client/xdgshell_p.h new file mode 100644 --- /dev/null +++ b/src/client/xdgshell_p.h @@ -0,0 +1,159 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#ifndef KWAYLAND_CLIENT_XDGSHELL_P_H +#define KWAYLAND_CLIENT_XDGSHELL_P_H +#include "xdgshell.h" + +#include + +namespace KWayland +{ +namespace Client +{ + +class XdgShell::Private +{ +public: + virtual ~Private(); + virtual void setupV5(xdg_shell *xdgshellv5) { + Q_UNUSED(xdgshellv5) + } + virtual void release() = 0; + virtual void destroy() = 0; + virtual bool isValid() const = 0; + virtual operator xdg_shell*() { + return nullptr; + } + virtual operator xdg_shell*() 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; + + EventQueue *queue = nullptr; + +protected: + Private() = default; +}; + +class XdgShellUnstableV5 : public XdgShell +{ + Q_OBJECT +public: + explicit XdgShellUnstableV5(QObject *parent = nullptr); + virtual ~XdgShellUnstableV5(); + +private: + class Private; +}; + +class XdgShellSurface::Private +{ +public: + virtual ~Private(); + EventQueue *queue = nullptr; + QSize size; + + virtual void setupV5(xdg_surface *surface) { + Q_UNUSED(surface) + } + virtual void release() = 0; + virtual void destroy() = 0; + virtual bool isValid() const = 0; + virtual operator xdg_surface*() { + return nullptr; + } + virtual operator xdg_surface*() const { + return nullptr; + } + + virtual void setTransientFor(XdgShellSurface *parent) = 0; + virtual void setTitle(const QString &title) = 0; + virtual void setAppId(const QByteArray &appId) = 0; + virtual void showWindowMenu(Seat *seat, quint32 serial, qint32 x, qint32 y) = 0; + virtual void move(Seat *seat, quint32 serial) = 0; + virtual void resize(Seat *seat, quint32 serial, Qt::Edges edges) = 0; + virtual void ackConfigure(quint32 serial) = 0; + virtual void setMaximized() = 0; + virtual void unsetMaximized() = 0; + virtual void setFullscreen(Output *output) = 0; + virtual void unsetFullscreen() = 0; + virtual void setMinimized() = 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: + Private(XdgShellPopup *q); + virtual ~Private(); + + EventQueue *queue = nullptr; + + virtual void setupV5(xdg_popup *p) { + Q_UNUSED(p) + } + virtual void release() = 0; + virtual void destroy() = 0; + virtual bool isValid() const = 0; + virtual operator xdg_popup*() { + return nullptr; + } + virtual operator xdg_popup*() const { + return nullptr; + } + +protected: + XdgShellPopup *q; + +private: +}; + +class XdgShellPopupUnstableV5 : public XdgShellPopup +{ +public: + virtual ~XdgShellPopupUnstableV5(); + +private: + explicit XdgShellPopupUnstableV5(QObject *parent = nullptr); + friend class XdgShellUnstableV5; + class Private; +}; + +} +} + +#endif diff --git a/src/client/xdgshell_v5.cpp b/src/client/xdgshell_v5.cpp new file mode 100644 --- /dev/null +++ b/src/client/xdgshell_v5.cpp @@ -0,0 +1,380 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#include "xdgshell_p.h" +#include "event_queue.h" +#include "output.h" +#include "seat.h" +#include "surface.h" +#include "wayland_pointer_p.h" +#include + +namespace KWayland +{ +namespace Client +{ + +class XdgShellUnstableV5::Private : public XdgShell::Private +{ +public: + void setupV5(xdg_shell *shell) override; + void release() override; + void destroy() override; + bool isValid() const override; + XdgShellSurface *getXdgSurface(Surface *surface, QObject *parent) override; + XdgShellPopup *getXdgPopup(Surface *surface, Surface *parentSurface, Seat *seat, quint32 serial, const QPoint &parentPos, QObject *parent) override; + operator xdg_shell*() override { + return xdgshellv5; + } + operator xdg_shell*() const override { + return xdgshellv5; + } + + WaylandPointer xdgshellv5; +}; + +void XdgShellUnstableV5::Private::setupV5(xdg_shell *shell) +{ + Q_ASSERT(shell); + Q_ASSERT(!xdgshellv5); + xdgshellv5.setup(shell); + xdg_shell_use_unstable_version(xdgshellv5, 5); +} + +void XdgShellUnstableV5::Private::release() +{ + xdgshellv5.release(); +} + +void XdgShellUnstableV5::Private::destroy() +{ + xdgshellv5.destroy(); +} + +bool XdgShellUnstableV5::Private::isValid() const +{ + return xdgshellv5.isValid(); +} + +XdgShellSurface *XdgShellUnstableV5::Private::getXdgSurface(Surface *surface, QObject *parent) +{ + Q_ASSERT(isValid()); + XdgShellSurface *s = new XdgShellSurfaceUnstableV5(parent); + auto w = xdg_shell_get_xdg_surface(xdgshellv5, *surface); + if (queue) { + queue->addProxy(w); + } + s->setup(w); + return s; +} + +XdgShellPopup *XdgShellUnstableV5::Private::getXdgPopup(Surface *surface, Surface *parentSurface, Seat *seat, quint32 serial, const QPoint &parentPos, QObject *parent) +{ + Q_ASSERT(isValid()); + XdgShellPopup *s = new XdgShellPopupUnstableV5(parent); + auto w = xdg_shell_get_xdg_popup(xdgshellv5, *surface, *parentSurface, *seat, serial, parentPos.x(), parentPos.y()); + if (queue) { + queue->addProxy(w); + } + s->setup(w); + return s; +} + +XdgShellUnstableV5::XdgShellUnstableV5(QObject *parent) + : XdgShell(new Private, parent) +{ +} + +XdgShellUnstableV5::~XdgShellUnstableV5() = default; + +class XdgShellSurfaceUnstableV5::Private : public XdgShellSurface::Private +{ +public: + Private(XdgShellSurface *q); + WaylandPointer xdgsurfacev5; + + void setupV5(xdg_surface *surface) override; + void release() override; + void destroy() override; + bool isValid() const override; + operator xdg_surface*() override { + return xdgsurfacev5; + } + operator xdg_surface*() const override { + return xdgsurfacev5; + } + + 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; + +private: + static void configureCallback(void *data, xdg_surface *xdg_surface, int32_t width, int32_t height, wl_array *states, uint32_t serial); + static void closeCallback(void *data, xdg_surface *xdg_surface); + + static const struct xdg_surface_listener s_listener; +}; + +const struct xdg_surface_listener XdgShellSurfaceUnstableV5::Private::s_listener = { + configureCallback, + closeCallback +}; + +void XdgShellSurfaceUnstableV5::Private::configureCallback(void *data, xdg_surface *xdg_surface, int32_t width, int32_t height, wl_array *wlStates, uint32_t serial) +{ + auto s = reinterpret_cast(data); + Q_ASSERT(s->xdgsurfacev5 == xdg_surface); + uint32_t *state = reinterpret_cast(wlStates->data); + size_t numStates = wlStates->size / sizeof(uint32_t); + States states; + for (size_t i = 0; i < numStates; i++) { + switch (state[i]) { + case XDG_SURFACE_STATE_MAXIMIZED: + states = states | XdgShellSurface::State::Maximized; + break; + case XDG_SURFACE_STATE_FULLSCREEN: + states = states | XdgShellSurface::State::Fullscreen; + break; + case XDG_SURFACE_STATE_RESIZING: + states = states | XdgShellSurface::State::Resizing; + break; + case XDG_SURFACE_STATE_ACTIVATED: + states = states | XdgShellSurface::State::Activated; + break; + } + } + const QSize size = QSize(width, height); + emit s->q->configureRequested(size, states, serial); + if (!size.isNull()) { + s->q->setSize(size); + } +} + +void XdgShellSurfaceUnstableV5::Private::closeCallback(void *data, xdg_surface *xdg_surface) +{ + auto s = reinterpret_cast(data); + Q_ASSERT(s->xdgsurfacev5 == xdg_surface); + emit s->q->closeRequested(); +} + +XdgShellSurfaceUnstableV5::Private::Private(XdgShellSurface *q) + : XdgShellSurface::Private(q) +{ +} + +void XdgShellSurfaceUnstableV5::Private::setupV5(xdg_surface *surface) +{ + Q_ASSERT(surface); + Q_ASSERT(!xdgsurfacev5); + xdgsurfacev5.setup(surface); + xdg_surface_add_listener(xdgsurfacev5, &s_listener, this); +} + +void XdgShellSurfaceUnstableV5::Private::release() +{ + xdgsurfacev5.release(); +} + +void XdgShellSurfaceUnstableV5::Private::destroy() +{ + xdgsurfacev5.destroy(); +} + +bool XdgShellSurfaceUnstableV5::Private::isValid() const +{ + return xdgsurfacev5.isValid(); +} + + +void XdgShellSurfaceUnstableV5::Private::setTransientFor(XdgShellSurface *parent) +{ + xdg_surface *parentSurface = nullptr; + if (parent) { + parentSurface = *parent; + } + xdg_surface_set_parent(xdgsurfacev5, parentSurface); +} + +void XdgShellSurfaceUnstableV5::Private::setTitle(const QString & title) +{ + xdg_surface_set_title(xdgsurfacev5, title.toUtf8().constData()); +} + +void XdgShellSurfaceUnstableV5::Private::setAppId(const QByteArray & appId) +{ + xdg_surface_set_app_id(xdgsurfacev5, appId.constData()); +} + +void XdgShellSurfaceUnstableV5::Private::showWindowMenu(Seat *seat, quint32 serial, qint32 x, qint32 y) +{ + xdg_surface_show_window_menu(xdgsurfacev5, *seat, serial, x, y); +} + +void XdgShellSurfaceUnstableV5::Private::move(Seat *seat, quint32 serial) +{ + xdg_surface_move(xdgsurfacev5, *seat, serial); +} + +void XdgShellSurfaceUnstableV5::Private::resize(Seat *seat, quint32 serial, Qt::Edges edges) +{ + uint wlEdge = XDG_SURFACE_RESIZE_EDGE_NONE; + if (edges.testFlag(Qt::TopEdge)) { + if (edges.testFlag(Qt::LeftEdge) && ((edges & ~Qt::LeftEdge) == Qt::TopEdge)) { + wlEdge = XDG_SURFACE_RESIZE_EDGE_TOP_LEFT; + } else if (edges.testFlag(Qt::RightEdge) && ((edges & ~Qt::RightEdge) == Qt::TopEdge)) { + wlEdge = XDG_SURFACE_RESIZE_EDGE_TOP_RIGHT; + } else if ((edges & ~Qt::TopEdge) == Qt::Edges()) { + wlEdge = XDG_SURFACE_RESIZE_EDGE_TOP; + } + } else if (edges.testFlag(Qt::BottomEdge)) { + if (edges.testFlag(Qt::LeftEdge) && ((edges & ~Qt::LeftEdge) == Qt::BottomEdge)) { + wlEdge = XDG_SURFACE_RESIZE_EDGE_BOTTOM_LEFT; + } else if (edges.testFlag(Qt::RightEdge) && ((edges & ~Qt::RightEdge) == Qt::BottomEdge)) { + wlEdge = XDG_SURFACE_RESIZE_EDGE_BOTTOM_RIGHT; + } else if ((edges & ~Qt::BottomEdge) == Qt::Edges()) { + wlEdge = XDG_SURFACE_RESIZE_EDGE_BOTTOM; + } + } else if (edges.testFlag(Qt::RightEdge) && ((edges & ~Qt::RightEdge) == Qt::Edges())) { + wlEdge = XDG_SURFACE_RESIZE_EDGE_RIGHT; + } else if (edges.testFlag(Qt::LeftEdge) && ((edges & ~Qt::LeftEdge) == Qt::Edges())) { + wlEdge = XDG_SURFACE_RESIZE_EDGE_LEFT; + } + xdg_surface_resize(xdgsurfacev5, *seat, serial, wlEdge); +} + +void XdgShellSurfaceUnstableV5::Private::ackConfigure(quint32 serial) +{ + xdg_surface_ack_configure(xdgsurfacev5, serial); +} + +void XdgShellSurfaceUnstableV5::Private::setMaximized() +{ + xdg_surface_set_maximized(xdgsurfacev5); +} + +void XdgShellSurfaceUnstableV5::Private::unsetMaximized() +{ + xdg_surface_unset_maximized(xdgsurfacev5); +} + +void XdgShellSurfaceUnstableV5::Private::setFullscreen(Output *output) +{ + wl_output *o = nullptr; + if (output) { + o = *output; + } + xdg_surface_set_fullscreen(xdgsurfacev5, o); +} + +void XdgShellSurfaceUnstableV5::Private::unsetFullscreen() +{ + xdg_surface_unset_fullscreen(xdgsurfacev5); +} + +void XdgShellSurfaceUnstableV5::Private::setMinimized() +{ + xdg_surface_set_minimized(xdgsurfacev5); +} + +XdgShellSurfaceUnstableV5::XdgShellSurfaceUnstableV5(QObject *parent) + : XdgShellSurface(new Private(this), parent) +{ +} + +XdgShellSurfaceUnstableV5::~XdgShellSurfaceUnstableV5() = default; + +class XdgShellPopupUnstableV5::Private : public XdgShellPopup::Private +{ +public: + Private(XdgShellPopup *q); + + void setupV5(xdg_popup *p) override; + void release() override; + void destroy() override; + bool isValid() const override; + operator xdg_popup*() override { + return xdgpopupv5; + } + operator xdg_popup*() const override { + return xdgpopupv5; + } + WaylandPointer xdgpopupv5; + +private: + static void popupDoneCallback(void *data, xdg_popup *xdg_popup); + static const struct xdg_popup_listener s_listener; +}; + +const struct xdg_popup_listener XdgShellPopupUnstableV5::Private::s_listener = { + popupDoneCallback +}; + +void XdgShellPopupUnstableV5::Private::popupDoneCallback(void *data, xdg_popup *xdg_popup) +{ + auto s = reinterpret_cast(data); + Q_ASSERT(s->xdgpopupv5 == xdg_popup); + emit s->q->popupDone(); +} + +XdgShellPopupUnstableV5::Private::Private(XdgShellPopup *q) + : XdgShellPopup::Private(q) +{ +} + +void XdgShellPopupUnstableV5::Private::setupV5(xdg_popup *p) +{ + Q_ASSERT(p); + Q_ASSERT(!xdgpopupv5); + xdgpopupv5.setup(p); + xdg_popup_add_listener(xdgpopupv5, &s_listener, this); +} + +void XdgShellPopupUnstableV5::Private::release() +{ + xdgpopupv5.release(); +} + +void XdgShellPopupUnstableV5::Private::destroy() +{ + xdgpopupv5.destroy(); +} + +bool XdgShellPopupUnstableV5::Private::isValid() const +{ + return xdgpopupv5.isValid(); +} + +XdgShellPopupUnstableV5::XdgShellPopupUnstableV5(QObject *parent) + : XdgShellPopup(new Private(this), parent) +{ +} + +XdgShellPopupUnstableV5::~XdgShellPopupUnstableV5() = default; + +} +} diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -37,6 +37,8 @@ textinput_interface.cpp textinput_interface_v0.cpp textinput_interface_v2.cpp + xdgshell_interface.cpp + xdgshell_v5_interface.cpp ) ecm_add_wayland_server_protocol(SERVER_LIB_SRCS @@ -113,6 +115,11 @@ BASENAME text-input-unstable-v2 ) +ecm_add_wayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${KWAYLAND_SOURCE_DIR}/src/client/protocols/xdg-shell-unstable-v5.xml + BASENAME xdg-shell-v5 +) + add_library(KF5WaylandServer ${SERVER_LIB_SRCS}) generate_export_header(KF5WaylandServer BASE_NAME @@ -183,6 +190,7 @@ surface_interface.h textinput_interface.h touch_interface.h + xdgshell_interface.h DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KWayland/Server COMPONENT Devel ) diff --git a/src/server/display.h b/src/server/display.h --- a/src/server/display.h +++ b/src/server/display.h @@ -71,6 +71,9 @@ class SubCompositorInterface; enum class TextInputInterfaceVersion; class TextInputManagerInterface; +class XdgShellV5Interface; +enum class XdgShellInterfaceVersion; +class XdgShellInterface; /** * @brief Class holding the Wayland server display loop. @@ -180,6 +183,13 @@ TextInputManagerInterface *createTextInputManager(const TextInputInterfaceVersion &version, QObject *parent = nullptr); /** + * Creates the XdgShell in interface @p version. + * + * @since 5.25 + **/ + XdgShellInterface *createXdgShell(const XdgShellInterfaceVersion &version, QObject *parent = nullptr); + + /** * Gets the ClientConnection for the given @p client. * If there is no ClientConnection yet for the given @p client, it will be created. * @param client The native client for which the ClientConnection is retrieved diff --git a/src/server/display.cpp b/src/server/display.cpp --- a/src/server/display.cpp +++ b/src/server/display.cpp @@ -40,6 +40,7 @@ #include "shell_interface.h" #include "subcompositor_interface.h" #include "textinput_interface_p.h" +#include "xdgshell_v5_interface_p.h" #include #include @@ -356,6 +357,18 @@ return t; } +XdgShellInterface *Display::createXdgShell(const XdgShellInterfaceVersion &version, QObject *parent) +{ + XdgShellInterface *x = nullptr; + switch (version) { + case XdgShellInterfaceVersion::UnstableV5: + x = new XdgShellV5Interface(this, parent); + break; + } + connect(this, &Display::aboutToTerminate, x, [x] { delete x; }); + return x; +} + void Display::createShm() { Q_ASSERT(d->display); diff --git a/src/server/generic_shell_surface_p.h b/src/server/generic_shell_surface_p.h new file mode 100644 --- /dev/null +++ b/src/server/generic_shell_surface_p.h @@ -0,0 +1,129 @@ +/******************************************************************** +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 . +*********************************************************************/ +#ifndef KWAYLAND_SERVER_GENERIC_SHELL_SURFACE_P_H +#define KWAYLAND_SERVER_GENERIC_SHELL_SURFACE_P_H + +#include "seat_interface.h" +#include "surface_interface.h" +#include + +namespace KWayland +{ + +namespace Server +{ + +template +class GenericShellSurface +{ +public: + GenericShellSurface(T *shellSurface, SurfaceInterface *surface) + : surface(surface) + , shellSurface(shellSurface) + {} + + SurfaceInterface *surface; + QString title; + QByteArray windowClass; + +protected: + void setTitle(const QString &title); + void setWindowClass(const QByteArray &wc); + + static void moveCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial); + template + static void resizeCallback(wl_client *client, wl_resource *resource, wl_resource * seat, uint32_t serial, uint32_t edges); + static void setTitleCallback(wl_client *client, wl_resource *resource, const char *title); + static void setAppIdCallback(wl_client *client, wl_resource *resource, const char *app_id); + +private: + T *q_func() { + return shellSurface; + } + static typename T::Private *userData(wl_resource *resource) { + return reinterpret_cast(wl_resource_get_user_data(resource)); + } + T *shellSurface; +}; + +template +void GenericShellSurface::setTitleCallback(wl_client *client, wl_resource *resource, const char *title) +{ + auto s = userData(resource); + Q_ASSERT(client == *s->client); + s->setTitle(QString::fromUtf8(title)); +} + +template +void GenericShellSurface::setAppIdCallback(wl_client *client, wl_resource *resource, const char *app_id) +{ + auto s = userData(resource); + Q_ASSERT(client == *s->client); + s->setWindowClass(QByteArray(app_id)); +} + +template +void GenericShellSurface::setTitle(const QString &t) +{ + if (title == t) { + return; + } + title = t; + Q_Q(T); + emit q->titleChanged(title); +} + +template +void GenericShellSurface::setWindowClass(const QByteArray &wc) +{ + if (windowClass == wc) { + return; + } + windowClass = wc; + Q_Q(T); + emit q->windowClassChanged(windowClass); +} + +template +void GenericShellSurface::moveCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial) +{ + auto s = userData(resource); + Q_ASSERT(client == *s->client); + emit s->q_func()->moveRequested(SeatInterface::get(seat), serial); +} + +namespace { +template +Qt::Edges edgesToQtEdges(T edges); +} + +template +template +void GenericShellSurface::resizeCallback(wl_client *client, wl_resource *resource, wl_resource * seat, uint32_t serial, uint32_t edges) +{ + auto s = userData(resource); + Q_ASSERT(client == *s->client); + emit s->q_func()->resizeRequested(SeatInterface::get(seat), serial, edgesToQtEdges(U(edges))); +} + +} +} + +#endif diff --git a/src/server/shell_interface.h b/src/server/shell_interface.h --- a/src/server/shell_interface.h +++ b/src/server/shell_interface.h @@ -40,6 +40,8 @@ class SeatInterface; class SurfaceInterface; class ShellSurfaceInterface; +template +class GenericShellSurface; /** * @brief Global for the wl_shell interface. @@ -293,6 +295,7 @@ private: friend class ShellInterface; explicit ShellSurfaceInterface(ShellInterface *shell, SurfaceInterface *parent, wl_resource *parentResource); + friend class GenericShellSurface; class Private; Private *d_func() const; }; diff --git a/src/server/shell_interface.cpp b/src/server/shell_interface.cpp --- a/src/server/shell_interface.cpp +++ b/src/server/shell_interface.cpp @@ -18,6 +18,7 @@ License along with this library. If not, see . *********************************************************************/ #include "shell_interface.h" +#include "generic_shell_surface_p.h" #include "global_p.h" #include "resource_p.h" #include "display.h" @@ -64,15 +65,12 @@ #endif -class ShellSurfaceInterface::Private : public Resource::Private +class ShellSurfaceInterface::Private : public Resource::Private, public GenericShellSurface { public: Private(ShellSurfaceInterface *q, ShellInterface *shell, SurfaceInterface *surface, wl_resource *parentResource); void ping(); - SurfaceInterface *surface; - QString title; - QByteArray windowClass; QScopedPointer pingTimer; quint32 pingSerial = 0; enum class WindowMode { @@ -87,30 +85,24 @@ bool acceptsKeyboardFocus = true; void setWindowMode(WindowMode newWindowMode); + ShellSurfaceInterface *q_func() { + return reinterpret_cast(q); + } + private: // interface callbacks static void pongCallback(wl_client *client, wl_resource *resource, uint32_t serial); - static void moveCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial); - static void resizeCallback(wl_client *client, wl_resource *resource, wl_resource *seat, - uint32_t serial, uint32_t edges); static void setToplevelCallback(wl_client *client, wl_resource *resource); static void setTransientCallback(wl_client *client, wl_resource *resource, wl_resource *parent, int32_t x, int32_t y, uint32_t flags); static void setFullscreenCallback(wl_client *client, wl_resource *resource, uint32_t method, uint32_t framerate, wl_resource *output); static void setPopupCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial, wl_resource *parent, int32_t x, int32_t y, uint32_t flags); static void setMaximizedCallback(wl_client *client, wl_resource *resource, wl_resource *output); - static void setTitleCallback(wl_client *client, wl_resource *resource, const char *title); - static void setClassCallback(wl_client *client, wl_resource *resource, const char *class_); - void setTitle(const QString &title); - void setWindowClass(const QByteArray &windowClass); void pong(quint32 serial); void setAcceptsFocus(quint32 flags); - ShellSurfaceInterface *q_func() { - return reinterpret_cast(q); - } static const struct wl_shell_surface_interface s_interface; }; @@ -166,7 +158,7 @@ *********************************/ ShellSurfaceInterface::Private::Private(ShellSurfaceInterface *q, ShellInterface *shell, SurfaceInterface *surface, wl_resource *parentResource) : Resource::Private(q, shell, parentResource, &wl_shell_surface_interface, &s_interface) - , surface(surface) + , GenericShellSurface(q, surface) , pingTimer(new QTimer) { pingTimer->setSingleShot(true); @@ -177,14 +169,14 @@ const struct wl_shell_surface_interface ShellSurfaceInterface::Private::s_interface = { pongCallback, moveCallback, - resizeCallback, + resizeCallback, setToplevelCallback, setTransientCallback, setFullscreenCallback, setPopupCallback, setMaximizedCallback, setTitleCallback, - setClassCallback + setAppIdCallback }; #endif @@ -262,17 +254,10 @@ d->client->flush(); } -void ShellSurfaceInterface::Private::moveCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial) +namespace { +template <> +Qt::Edges edgesToQtEdges(wl_shell_surface_resize edges) { - auto s = cast(resource); - Q_ASSERT(client == *s->client); - emit s->q_func()->moveRequested(SeatInterface::get(seat), serial); -} - -void ShellSurfaceInterface::Private::resizeCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial, uint32_t edges) -{ - auto s = cast(resource); - Q_ASSERT(client == *s->client); Qt::Edges qtEdges; switch (edges) { case WL_SHELL_SURFACE_RESIZE_TOP: @@ -299,10 +284,14 @@ case WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT: qtEdges = Qt::BottomEdge | Qt::RightEdge; break; + case WL_SHELL_SURFACE_RESIZE_NONE: + break; default: + Q_UNREACHABLE(); break; } - emit s->q_func()->resizeRequested(SeatInterface::get(seat), serial, qtEdges); + return qtEdges; +} } void ShellSurfaceInterface::Private::setToplevelCallback(wl_client *client, wl_resource *resource) @@ -401,40 +390,6 @@ s->setWindowMode(WindowMode::Maximized); } -void ShellSurfaceInterface::Private::setTitleCallback(wl_client *client, wl_resource *resource, const char *title) -{ - auto s = cast(resource); - Q_ASSERT(client == *s->client); - s->setTitle(QString::fromUtf8(title)); -} - -void ShellSurfaceInterface::Private::setTitle(const QString &t) -{ - if (title == t) { - return; - } - title = t; - Q_Q(ShellSurfaceInterface); - emit q->titleChanged(title); -} - -void ShellSurfaceInterface::Private::setClassCallback(wl_client *client, wl_resource *resource, const char *class_) -{ - auto s = cast(resource); - Q_ASSERT(client == *s->client); - s->setWindowClass(QByteArray(class_)); -} - -void ShellSurfaceInterface::Private::setWindowClass(const QByteArray &wc) -{ - if (windowClass == wc) { - return; - } - windowClass = wc; - Q_Q(ShellSurfaceInterface); - emit q->windowClassChanged(windowClass); -} - SurfaceInterface *ShellSurfaceInterface::surface() const { Q_D(); return d->surface; diff --git a/src/server/xdgshell_interface.h b/src/server/xdgshell_interface.h new file mode 100644 --- /dev/null +++ b/src/server/xdgshell_interface.h @@ -0,0 +1,304 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_XDGSHELL_INTERFACE_H +#define KWAYLAND_SERVER_XDGSHELL_INTERFACE_H + +#include "global.h" +#include "resource.h" + +#include + +#include + +namespace KWayland +{ +namespace Server +{ + +class OutputInterface; +class SeatInterface; +class SurfaceInterface; +class XdgShellPopupInterface; +class XdgShellSurfaceInterface; +template +class GenericShellSurface; + +/** + * Enum describing the different InterfaceVersion encapsulated in this implementation. + * + * @since 5.25 + **/ +enum class XdgShellInterfaceVersion +{ + /** + * xdg_shell (unstable v5) + **/ + UnstableV5 +}; + +/** + * + * @since 5.25 + **/ +class KWAYLANDSERVER_EXPORT XdgShellInterface : public Global +{ + Q_OBJECT +public: + virtual ~XdgShellInterface(); + + /** + * @returns The interface version used by this XdgShellInterface + **/ + XdgShellInterfaceVersion interfaceVersion() const; + + /** + * @returns The XdgShellSurfaceInterface for the @p native resource. + **/ + XdgShellSurfaceInterface *getSurface(wl_resource *native); + +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 + **/ + void popupCreated(KWayland::Server::XdgShellPopupInterface *surface, KWayland::Server::SeatInterface *seat, quint32 serial); + +protected: + class Private; + explicit XdgShellInterface(Private *d, QObject *parent = nullptr); + +private: + Private *d_func() const; +}; + + +/** + * + * @since 5.25 + **/ +class KWAYLANDSERVER_EXPORT XdgShellSurfaceInterface : public Resource +{ + Q_OBJECT +public: + virtual ~XdgShellSurfaceInterface(); + + /** + * @returns The interface version used by this XdgShellSurfaceInterface + **/ + XdgShellInterfaceVersion interfaceVersion() const; + + /** + * States the Surface can be in + **/ + enum class State { + /** + * The Surface is maximized. + **/ + Maximized = 1 << 0, + /** + * The Surface is fullscreen. + **/ + Fullscreen = 1 << 1, + /** + * The Surface is currently being resized by the Compositor. + **/ + Resizing = 1 << 2, + /** + * The Surface is considered active. Does not imply keyboard focus. + **/ + Activated = 1 << 3 + }; + Q_DECLARE_FLAGS(States, State) + + /** + * Sends a configure event to the Surface. + * This tells the Surface the current @p states it is in and the @p size it should have. + * If @p size has width and height at @c 0, the Surface can choose the size. + * + * The Surface acknowledges the configure event with @link{configureAcknowledged}. + * + * @param states The states the surface is in + * @param size The requested size + * @returns The serial of the configure event + * @see configureAcknowledged + * @see isConfigurePending + **/ + quint32 configure(States states, const QSize &size = QSize(0, 0)); + + /** + * @returns @c true if there is a not yet acknowledged configure event. + * @see configure + * @see configureAcknowledged + **/ + bool isConfigurePending() const; + + /** + * @return The SurfaceInterface this XdgSurfaceV5Interface got created for. + **/ + SurfaceInterface *surface() const; + + /** + * @returns The title of this surface. + * @see titleChanged + **/ + QString title() const; + QByteArray windowClass() const; + + /** + * @returns Whether this Surface is a transient for another Surface, that is it has a parent. + * @see transientFor + **/ + bool isTransient() const; + /** + * @returns the parent surface if the surface is a transient for another surface + * @see isTransient + **/ + QPointer transientFor() const; + + /** + * Request the client to close the window. + **/ + void close(); + +Q_SIGNALS: + /** + * Emitted whenever the title changes. + * + * @see title + **/ + void titleChanged(const QString&); + /** + * Emitted whenever the window class changes. + * + * @see windowClass + **/ + void windowClassChanged(const QByteArray&); + /** + * The surface requested a window move. + * + * @param seat The SeatInterface on which the surface requested the move + * @param serial The serial of the implicit mouse grab which triggered the move + **/ + void moveRequested(KWayland::Server::SeatInterface *seat, quint32 serial); + /** + * The surface requested a window resize. + * + * @param seat The SeatInterface on which the surface requested the resize + * @param serial The serial of the implicit mouse grab which triggered the resize + * @param edges A hint which edges are involved in the resize + **/ + void resizeRequested(KWayland::Server::SeatInterface *seat, quint32 serial, Qt::Edges edges); + void windowMenuRequested(KWayland::Server::SeatInterface *seat, quint32 serial, const QPoint &surfacePos); + /** + * The surface requested a change of maximized state. + * @param maximized Whether the window wants to be maximized + **/ + void maximizedChanged(bool maximized); + /** + * The surface requested a change of fullscreen state + * @param fullscreen Whether the window wants to be fullscreen + * @param output An optional output hint on which the window wants to be fullscreen + **/ + void fullscreenChanged(bool fullscreen, KWayland::Server::OutputInterface *output); + /** + * The surface requested to be minimized. + **/ + void minimizeRequested(); + /** + * A configure event with @p serial got acknowledged. + * @see configure + **/ + void configureAcknowledged(quint32 serial); + /** + * Emitted whenever the parent surface changes. + * @see isTransient + * @see transientFor + **/ + void transientForChanged(); + +protected: + class Private; + explicit XdgShellSurfaceInterface(Private *p); + +private: + Private *d_func() const; + friend class GenericShellSurface; +}; + +/** + * + * @since 5.25 + **/ +class KWAYLANDSERVER_EXPORT XdgShellPopupInterface : public Resource +{ + Q_OBJECT +public: + virtual ~XdgShellPopupInterface(); + + /** + * @return The SurfaceInterface this XdgShellPopupInterface got created for. + **/ + SurfaceInterface *surface() const; + + /** + * @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. + * + * @returns offset in parent coordinate system. + * @see transientFor + **/ + QPoint transientOffset() 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(); + +protected: + class Private; + explicit XdgShellPopupInterface(Private *p); + +private: + friend class GenericShellSurface; + + Private *d_func() const; +}; + +} +} + +Q_DECLARE_METATYPE(KWayland::Server::XdgShellSurfaceInterface *) +Q_DECLARE_METATYPE(KWayland::Server::XdgShellPopupInterface *) +Q_DECLARE_METATYPE(KWayland::Server::XdgShellSurfaceInterface::State) +Q_DECLARE_METATYPE(KWayland::Server::XdgShellSurfaceInterface::States) + +#endif diff --git a/src/server/xdgshell_interface.cpp b/src/server/xdgshell_interface.cpp new file mode 100644 --- /dev/null +++ b/src/server/xdgshell_interface.cpp @@ -0,0 +1,179 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#include "xdgshell_interface_p.h" + +namespace KWayland +{ +namespace Server +{ + +XdgShellInterface::Private::Private(XdgShellInterfaceVersion interfaceVersion, XdgShellInterface *q, Display *d, const wl_interface *interface, quint32 version) + : Global::Private(d, interface, version) + , interfaceVersion(interfaceVersion) + , q(q) +{ +} + +XdgShellInterface::XdgShellInterface(Private *d, QObject *parent) + : Global(d, parent) +{ +} + +XdgShellInterface::~XdgShellInterface() = default; + +XdgShellSurfaceInterface *XdgShellInterface::getSurface(wl_resource *native) +{ + Q_UNUSED(native) + return nullptr; +} + +XdgShellInterfaceVersion XdgShellInterface::interfaceVersion() const +{ + Q_D(); + return d->interfaceVersion; +} + +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) + : Resource::Private(q, c, parentResource, interface, implementation) + , GenericShellSurface(q, surface) + , interfaceVersion(interfaceVersion) +{ +} + +XdgShellSurfaceInterface::Private::~Private() = default; + +XdgShellSurfaceInterface::XdgShellSurfaceInterface(Private *p) + : Resource(p) +{ +} + +XdgShellSurfaceInterface::~XdgShellSurfaceInterface() = default; + +XdgShellInterfaceVersion XdgShellSurfaceInterface::interfaceVersion() const +{ + Q_D(); + return d->interfaceVersion; +} + +quint32 XdgShellSurfaceInterface::configure(States states, const QSize &size) +{ + Q_D(); + return d->configure(states, size); +} + +bool XdgShellSurfaceInterface::isConfigurePending() const +{ + Q_D(); + return !d->configureSerials.isEmpty(); +} + +SurfaceInterface *XdgShellSurfaceInterface::surface() const +{ + Q_D(); + return d->surface; +} + +QString XdgShellSurfaceInterface::title() const +{ + Q_D(); + return d->title; +} + +QByteArray XdgShellSurfaceInterface::windowClass() const +{ + Q_D(); + return d->windowClass; +} + +bool XdgShellSurfaceInterface::isTransient() const +{ + Q_D(); + return !d->parent.isNull(); +} + +QPointer XdgShellSurfaceInterface::transientFor() const +{ + Q_D(); + return d->parent; +} + +void XdgShellSurfaceInterface::close() +{ + Q_D(); + d->close(); +} + +XdgShellSurfaceInterface::Private *XdgShellSurfaceInterface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +XdgShellPopupInterface::Private::Private(XdgShellInterfaceVersion interfaceVersion, XdgShellPopupInterface *q, XdgShellInterface *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) +{ +} + +XdgShellPopupInterface::Private::~Private() = default; + +XdgShellPopupInterface::XdgShellPopupInterface(Private *p) + : Resource(p) +{ +} + +XdgShellPopupInterface::~XdgShellPopupInterface() = default; + +SurfaceInterface *XdgShellPopupInterface::surface() const +{ + Q_D(); + return d->surface; +} + +QPointer XdgShellPopupInterface::transientFor() const +{ + Q_D(); + return d->parent; +} + +QPoint XdgShellPopupInterface::transientOffset() const +{ + Q_D(); + return d->transientOffset; +} + +void XdgShellPopupInterface::popupDone() +{ + Q_D(); + return d->popupDone(); +} + +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 new file mode 100644 --- /dev/null +++ b/src/server/xdgshell_interface_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_XDGSHELL_INTERFACE_P_H +#define KWAYLAND_SERVER_XDGSHELL_INTERFACE_P_H +#include "xdgshell_interface.h" +#include "global_p.h" +#include "generic_shell_surface_p.h" +#include "resource_p.h" + +namespace KWayland +{ +namespace Server +{ + +class XdgShellInterface::Private : public Global::Private +{ +public: + XdgShellInterfaceVersion interfaceVersion; + +protected: + Private(XdgShellInterfaceVersion interfaceVersion, XdgShellInterface *q, Display *d, const wl_interface *interface, quint32 version); + XdgShellInterface *q; +}; + +class XdgShellSurfaceInterface::Private : public Resource::Private, public GenericShellSurface +{ +public: + virtual ~Private(); + + virtual void close() = 0; + virtual quint32 configure(States states, const QSize &size) = 0; + + XdgShellSurfaceInterface *q_func() { + return reinterpret_cast(q); + } + + QVector configureSerials; + QPointer parent; + XdgShellInterfaceVersion interfaceVersion; + +protected: + Private(XdgShellInterfaceVersion interfaceVersion, XdgShellSurfaceInterface *q, XdgShellInterface *c, SurfaceInterface *surface, wl_resource *parentResource, const wl_interface *interface, const void *implementation); + +}; + +class XdgShellPopupInterface::Private : public Resource::Private, public GenericShellSurface +{ +public: + virtual ~Private(); + virtual void popupDone() = 0; + + XdgShellPopupInterface *q_func() { + return reinterpret_cast(q); + } + + QPointer parent; + QPoint transientOffset; + XdgShellInterfaceVersion interfaceVersion; + +protected: + Private(XdgShellInterfaceVersion interfaceVersion, XdgShellPopupInterface *q, XdgShellInterface *c, SurfaceInterface *surface, wl_resource *parentResource, const wl_interface *interface, const void *implementation); + +}; + +} +} + +#endif diff --git a/src/server/xdgshell_v5_interface.cpp b/src/server/xdgshell_v5_interface.cpp new file mode 100644 --- /dev/null +++ b/src/server/xdgshell_v5_interface.cpp @@ -0,0 +1,475 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#include "xdgshell_v5_interface_p.h" +#include "xdgshell_interface_p.h" +#include "generic_shell_surface_p.h" +#include "display.h" +#include "global_p.h" +#include "resource_p.h" +#include "output_interface.h" +#include "seat_interface.h" +#include "surface_interface.h" + +#include + +namespace KWayland +{ +namespace Server +{ + +class XdgShellV5Interface::Private : public XdgShellInterface::Private +{ +public: + Private(XdgShellV5Interface *q, Display *d); + + QVector surfaces; + +private: + 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; + + static void unbind(wl_resource *resource); + static Private *cast(wl_resource *r) { + return reinterpret_cast(wl_resource_get_user_data(r)); + } + + 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); + static void getXdgPopupCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface, wl_resource * parent, wl_resource * seat, uint32_t serial, int32_t x, int32_t y); + static void pongCallback(wl_client *client, wl_resource *resource, uint32_t serial); + + XdgShellV5Interface *q; + static const struct xdg_shell_interface s_interface; + static const quint32 s_version; +}; + +class XdgPopupV5Interface::Private : public XdgShellPopupInterface::Private +{ +public: + Private(XdgPopupV5Interface *q, XdgShellV5Interface *c, SurfaceInterface *surface, wl_resource *parentResource); + ~Private(); + + void popupDone() override; + + XdgPopupV5Interface *q_func() { + return reinterpret_cast(q); + } + +private: + + static const struct xdg_popup_interface s_interface; +}; + +const quint32 XdgShellV5Interface::Private::s_version = 1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct xdg_shell_interface XdgShellV5Interface::Private::s_interface = { + destroyCallback, + useUnstableVersionCallback, + getXdgSurfaceCallback, + getXdgPopupCallback, + pongCallback +}; +#endif + +void XdgShellV5Interface::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 XdgShellV5Interface::Private::useUnstableVersionCallback(wl_client *client, wl_resource *resource, int32_t version) +{ + Q_UNUSED(client) + Q_UNUSED(resource) + Q_UNUSED(version) + // TODO: implement +} + +void XdgShellV5Interface::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 XdgShellV5Interface::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](XdgSurfaceV5Interface *s) { + return surface == s->surface(); + } + ); + if (it != surfaces.constEnd()) { + wl_resource_post_error(surface->resource(), XDG_SHELL_ERROR_ROLE, "ShellSurface already created"); + return; + } + XdgSurfaceV5Interface *shellSurface = new XdgSurfaceV5Interface(q, surface, parentResource); + surfaces << shellSurface; + QObject::connect(shellSurface, &XdgSurfaceV5Interface::destroyed, q, + [this, shellSurface] { + surfaces.removeAll(shellSurface); + } + ); + shellSurface->d->create(display->getConnection(client), version, id); + emit q->surfaceCreated(shellSurface); +} + +void XdgShellV5Interface::Private::getXdgPopupCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface, wl_resource * parent, wl_resource * seat, uint32_t serial, int32_t x, int32_t y) +{ + auto s = cast(resource); + s->createPopup(client, wl_resource_get_version(resource), id, SurfaceInterface::get(surface), SurfaceInterface::get(parent), SeatInterface::get(seat), serial, QPoint(x, y), resource); +} + +void XdgShellV5Interface::Private::createPopup(wl_client *client, uint32_t version, uint32_t id, SurfaceInterface *surface, SurfaceInterface *parent, SeatInterface *seat, quint32 serial, const QPoint &pos, wl_resource *parentResource) +{ + XdgPopupV5Interface *popupSurface = new XdgPopupV5Interface(q, surface, parentResource); + auto d = popupSurface->d_func(); + d->parent = QPointer(parent); + d->transientOffset = pos; + d->create(display->getConnection(client), version, id); + emit q->popupCreated(popupSurface, 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 +} + +XdgShellV5Interface::Private::Private(XdgShellV5Interface *q, Display *d) + : XdgShellInterface::Private(XdgShellInterfaceVersion::UnstableV5, q, d, &xdg_shell_interface, s_version) + , q(q) +{ +} + +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); + 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 XdgShellV5Interface::Private::unbind(wl_resource *resource) +{ + Q_UNUSED(resource) + // TODO: implement? +} + +XdgSurfaceV5Interface *XdgShellV5Interface::getSurface(wl_resource *resource) +{ + if (!resource) { + return nullptr; + } + Q_D(); + auto it = std::find_if(d->surfaces.constBegin(), d->surfaces.constEnd(), + [resource] (XdgSurfaceV5Interface *surface) { + return surface->resource() == resource; + } + ); + if (it != d->surfaces.constEnd()) { + return *it; + } + return nullptr; +} + +XdgShellV5Interface::Private *XdgShellV5Interface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +class XdgSurfaceV5Interface::Private : public XdgShellSurfaceInterface::Private +{ +public: + Private(XdgSurfaceV5Interface *q, XdgShellV5Interface *c, SurfaceInterface *surface, wl_resource *parentResource); + ~Private(); + + void close() override; + quint32 configure(States states, const QSize &size) override; + + XdgSurfaceV5Interface *q_func() { + return reinterpret_cast(q); + } + +private: + static void setParentCallback(wl_client *client, 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 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 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 xdg_surface_interface s_interface; +}; + +namespace { +template <> +Qt::Edges edgesToQtEdges(xdg_surface_resize_edge edges) +{ + Qt::Edges qtEdges; + switch (edges) { + case XDG_SURFACE_RESIZE_EDGE_TOP: + qtEdges = Qt::TopEdge; + break; + case XDG_SURFACE_RESIZE_EDGE_BOTTOM: + qtEdges = Qt::BottomEdge; + break; + case XDG_SURFACE_RESIZE_EDGE_LEFT: + qtEdges = Qt::LeftEdge; + break; + case XDG_SURFACE_RESIZE_EDGE_TOP_LEFT: + qtEdges = Qt::TopEdge | Qt::LeftEdge; + break; + case XDG_SURFACE_RESIZE_EDGE_BOTTOM_LEFT: + qtEdges = Qt::BottomEdge | Qt::LeftEdge; + break; + case XDG_SURFACE_RESIZE_EDGE_RIGHT: + qtEdges = Qt::RightEdge; + break; + case XDG_SURFACE_RESIZE_EDGE_TOP_RIGHT: + qtEdges = Qt::TopEdge | Qt::RightEdge; + break; + case XDG_SURFACE_RESIZE_EDGE_BOTTOM_RIGHT: + qtEdges = Qt::BottomEdge | Qt::RightEdge; + break; + case XDG_SURFACE_RESIZE_EDGE_NONE: + break; + default: + Q_UNREACHABLE(); + break; + } + return qtEdges; +} +} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct xdg_surface_interface XdgSurfaceV5Interface::Private::s_interface = { + resourceDestroyedCallback, + setParentCallback, + setTitleCallback, + setAppIdCallback, + showWindowMenuCallback, + moveCallback, + resizeCallback, + ackConfigureCallback, + setWindowGeometryCallback, + setMaximizedCallback, + unsetMaximizedCallback, + setFullscreenCallback, + unsetFullscreenCallback, + setMinimizedCallback +}; +#endif + +void XdgSurfaceV5Interface::Private::setParentCallback(wl_client *client, wl_resource *resource, wl_resource *parent) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + auto parentSurface = static_cast(s->q->global())->getSurface(parent); + if (s->parent.data() != parentSurface) { + s->parent = QPointer(parentSurface); + emit s->q_func()->transientForChanged(); + } +} + +void XdgSurfaceV5Interface::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)); +} + +void XdgSurfaceV5Interface::Private::ackConfigureCallback(wl_client *client, wl_resource *resource, uint32_t serial) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + if (!s->configureSerials.contains(serial)) { + // TODO: send error? + return; + } + while (!s->configureSerials.isEmpty()) { + quint32 i = s->configureSerials.takeFirst(); + emit s->q_func()->configureAcknowledged(i); + if (i == serial) { + break; + } + } +} + +void XdgSurfaceV5Interface::Private::setWindowGeometryCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) +{ + // TODO: implement + Q_UNUSED(client) + Q_UNUSED(resource) + Q_UNUSED(x) + Q_UNUSED(y) + Q_UNUSED(width) + Q_UNUSED(height) +} + +void XdgSurfaceV5Interface::Private::setMaximizedCallback(wl_client *client, wl_resource *resource) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + s->q_func()->maximizedChanged(true); +} + +void XdgSurfaceV5Interface::Private::unsetMaximizedCallback(wl_client *client, wl_resource *resource) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + s->q_func()->maximizedChanged(false); +} + +void XdgSurfaceV5Interface::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 XdgSurfaceV5Interface::Private::unsetFullscreenCallback(wl_client *client, wl_resource *resource) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + s->q_func()->fullscreenChanged(false, nullptr); +} + +void XdgSurfaceV5Interface::Private::setMinimizedCallback(wl_client *client, wl_resource *resource) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + s->q_func()->minimizeRequested(); +} + +XdgSurfaceV5Interface::Private::Private(XdgSurfaceV5Interface *q, XdgShellV5Interface *c, SurfaceInterface *surface, wl_resource *parentResource) + : XdgShellSurfaceInterface::Private(XdgShellInterfaceVersion::UnstableV5, q, c, surface, parentResource, &xdg_surface_interface, &s_interface) +{ +} + +XdgSurfaceV5Interface::Private::~Private() = default; + +void XdgSurfaceV5Interface::Private::close() +{ + xdg_surface_send_close(resource); + client->flush(); +} + +quint32 XdgSurfaceV5Interface::Private::configure(States states, const QSize &size) +{ + 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 = XDG_SURFACE_STATE_MAXIMIZED; + } + if (states.testFlag(State::Fullscreen)) { + uint32_t *s = reinterpret_cast(wl_array_add(&state, sizeof(uint32_t))); + *s = XDG_SURFACE_STATE_FULLSCREEN; + } + if (states.testFlag(State::Resizing)) { + uint32_t *s = reinterpret_cast(wl_array_add(&state, sizeof(uint32_t))); + *s = XDG_SURFACE_STATE_RESIZING; + } + if (states.testFlag(State::Activated)) { + uint32_t *s = reinterpret_cast(wl_array_add(&state, sizeof(uint32_t))); + *s = XDG_SURFACE_STATE_ACTIVATED; + } + configureSerials << serial; + xdg_surface_send_configure(resource, size.width(), size.height(), &state, serial); + client->flush(); + wl_array_release(&state); + + return serial; +} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct xdg_popup_interface XdgPopupV5Interface::Private::s_interface = { + resourceDestroyedCallback +}; +#endif + +XdgPopupV5Interface::Private::Private(XdgPopupV5Interface *q, XdgShellV5Interface *c, SurfaceInterface *surface, wl_resource *parentResource) + : XdgShellPopupInterface::Private(XdgShellInterfaceVersion::UnstableV5, q, c, surface, parentResource, &xdg_popup_interface, &s_interface) +{ +} + +XdgPopupV5Interface::Private::~Private() = default; + + +void XdgPopupV5Interface::Private::popupDone() +{ + if (!resource) { + return; + } + // TODO: dismiss all child popups + xdg_popup_send_popup_done(resource); + client->flush(); +} + +XdgShellV5Interface::XdgShellV5Interface(Display *display, QObject *parent) + : XdgShellInterface(new Private(this, display), parent) +{ +} + +XdgShellV5Interface::~XdgShellV5Interface() = default; + +XdgSurfaceV5Interface::XdgSurfaceV5Interface(XdgShellV5Interface *parent, SurfaceInterface *surface, wl_resource *parentResource) + : KWayland::Server::XdgShellSurfaceInterface(new Private(this, parent, surface, parentResource)) +{ +} + +XdgSurfaceV5Interface::~XdgSurfaceV5Interface() = default; + +XdgPopupV5Interface::XdgPopupV5Interface(XdgShellV5Interface *parent, SurfaceInterface *surface, wl_resource *parentResource) + : XdgShellPopupInterface(new Private(this, parent, surface, parentResource)) +{ +} + +XdgPopupV5Interface::~XdgPopupV5Interface() = default; + +XdgPopupV5Interface::Private *XdgPopupV5Interface::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 new file mode 100644 --- /dev/null +++ b/src/server/xdgshell_v5_interface_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_XDGSHELL_V5_INTERFACE_P_H +#define KWAYLAND_SERVER_XDGSHELL_V5_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 XdgPopupV5Interface; +class XdgSurfaceV5Interface; +template +class GenericShellSurface; + +class XdgShellV5Interface : public XdgShellInterface +{ + Q_OBJECT +public: + virtual ~XdgShellV5Interface(); + + /** + * @returns The XdgSurfaceV5Interface for the @p native resource. + **/ + XdgSurfaceV5Interface *getSurface(wl_resource *native); + +private: + explicit XdgShellV5Interface(Display *display, QObject *parent = nullptr); + friend class Display; + class Private; + Private *d_func() const; +}; + +class XdgSurfaceV5Interface : public XdgShellSurfaceInterface +{ + Q_OBJECT +public: + virtual ~XdgSurfaceV5Interface(); + +private: + explicit XdgSurfaceV5Interface(XdgShellV5Interface *parent, SurfaceInterface *surface, wl_resource *parentResource); + friend class XdgShellV5Interface; + + class Private; +}; + +class XdgPopupV5Interface : public XdgShellPopupInterface +{ + Q_OBJECT +public: + virtual ~XdgPopupV5Interface(); + +private: + explicit XdgPopupV5Interface(XdgShellV5Interface *parent, SurfaceInterface *surface, wl_resource *parentResource); + friend class XdgShellV5Interface; + friend class GenericShellSurface; + + 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 @@ -44,3 +44,6 @@ wl_text_input_manager;TextInputManagerUnstableV0 zwp_text_input_v2;TextInputUnstableV2 zwp_text_input_manager_v2;TextInputManagerUnstableV2 +xdg_shell;XdgShellV5 +xdg_surface;XdgSurfaceV5 +xdg_popup;XdgPopupV5