diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 092f725aaa..bfb268b484 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -37,6 +37,8 @@ set(SERVER_LIB_SRCS 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 @@ ecm_add_wayland_server_protocol(SERVER_LIB_SRCS 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 @@ install(FILES 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/wayland/autotests/client/CMakeLists.txt b/src/wayland/autotests/client/CMakeLists.txt index cb2d29d827..b04a6087e8 100644 --- a/src/wayland/autotests/client/CMakeLists.txt +++ b/src/wayland/autotests/client/CMakeLists.txt @@ -330,3 +330,15 @@ add_executable(testSelection ${testSelection_SRCS}) 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/src/wayland/autotests/client/test_wayland_registry.cpp b/src/wayland/autotests/client/test_wayland_registry.cpp index 13c335041a..32acff300e 100644 --- a/src/wayland/autotests/client/test_wayland_registry.cpp +++ b/src/wayland/autotests/client/test_wayland_registry.cpp @@ -30,6 +30,7 @@ License along with this library. If not, see . #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 @@ License along with this library. If not, see . #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 @@ private Q_SLOTS: void testBindServerSideDecorationManager(); void testBindTextInputManagerUnstableV0(); void testBindTextInputManagerUnstableV2(); + void testBindXdgShellUnstableV5(); void testGlobalSync(); void testGlobalSyncThreaded(); void testRemoval(); @@ -96,6 +100,7 @@ private: 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 @@ TestWaylandRegistry::TestWaylandRegistry(QObject *parent) , m_serverSideDecorationManager(nullptr) , m_textInputManagerV0(nullptr) , m_textInputManagerV2(nullptr) + , m_xdgShellUnstableV5(nullptr) { } @@ -152,6 +158,9 @@ void TestWaylandRegistry::init() 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 @@ void TestWaylandRegistry::testBindTextInputManagerUnstableV2() 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/src/wayland/autotests/client/test_xdg_shell.cpp b/src/wayland/autotests/client/test_xdg_shell.cpp new file mode 100644 index 0000000000..c62630f906 --- /dev/null +++ b/src/wayland/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/wayland/display.cpp b/src/wayland/display.cpp index 2c77456546..3a6dc17e7f 100644 --- a/src/wayland/display.cpp +++ b/src/wayland/display.cpp @@ -40,6 +40,7 @@ License along with this library. If not, see . #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 @@ TextInputManagerInterface *Display::createTextInputManager(const TextInputInterf 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/wayland/display.h b/src/wayland/display.h index c4b6741638..e56aa19794 100644 --- a/src/wayland/display.h +++ b/src/wayland/display.h @@ -71,6 +71,9 @@ class ShellInterface; class SubCompositorInterface; enum class TextInputInterfaceVersion; class TextInputManagerInterface; +class XdgShellV5Interface; +enum class XdgShellInterfaceVersion; +class XdgShellInterface; /** * @brief Class holding the Wayland server display loop. @@ -179,6 +182,13 @@ public: **/ 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. diff --git a/src/wayland/server/generic_shell_surface_p.h b/src/wayland/server/generic_shell_surface_p.h new file mode 100644 index 0000000000..3c39d325c2 --- /dev/null +++ b/src/wayland/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/wayland/server/shell_interface.cpp b/src/wayland/server/shell_interface.cpp index 9a7226cca4..1fa414a6b4 100644 --- a/src/wayland/server/shell_interface.cpp +++ b/src/wayland/server/shell_interface.cpp @@ -18,6 +18,7 @@ You should have received a copy of the GNU Lesser General Public 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 @@ const struct wl_shell_interface ShellInterface::Private::s_interface = { #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,12 +85,13 @@ public: 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); @@ -101,16 +100,9 @@ private: 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 @@ void ShellInterface::Private::createSurface(wl_client *client, uint32_t version, *********************************/ 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 @@ ShellSurfaceInterface::Private::Private(ShellSurfaceInterface *q, ShellInterface 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 @@ void ShellSurfaceInterface::requestSize(const QSize &size) 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 @@ void ShellSurfaceInterface::Private::resizeCallback(wl_client *client, wl_resour 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 @@ void ShellSurfaceInterface::Private::setMaximizedCallback(wl_client *client, wl_ 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/wayland/server/shell_interface.h b/src/wayland/server/shell_interface.h index d31e27fabf..95a0aca093 100644 --- a/src/wayland/server/shell_interface.h +++ b/src/wayland/server/shell_interface.h @@ -40,6 +40,8 @@ class Display; class SeatInterface; class SurfaceInterface; class ShellSurfaceInterface; +template +class GenericShellSurface; /** * @brief Global for the wl_shell interface. @@ -293,6 +295,7 @@ Q_SIGNALS: 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/wayland/server/xdgshell_v5_interface.cpp b/src/wayland/server/xdgshell_v5_interface.cpp new file mode 100644 index 0000000000..27dd59184d --- /dev/null +++ b/src/wayland/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/wayland/server/xdgshell_v5_interface_p.h b/src/wayland/server/xdgshell_v5_interface_p.h new file mode 100644 index 0000000000..a7fd9da871 --- /dev/null +++ b/src/wayland/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/wayland/tools/mapping.txt b/src/wayland/tools/mapping.txt index bd2630bc5b..3706952bff 100644 --- a/src/wayland/tools/mapping.txt +++ b/src/wayland/tools/mapping.txt @@ -44,3 +44,6 @@ wl_text_input;TextInputUnstableV0 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 diff --git a/src/wayland/xdgshell_interface.cpp b/src/wayland/xdgshell_interface.cpp new file mode 100644 index 0000000000..00976780ae --- /dev/null +++ b/src/wayland/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/wayland/xdgshell_interface.h b/src/wayland/xdgshell_interface.h new file mode 100644 index 0000000000..90518db67f --- /dev/null +++ b/src/wayland/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/wayland/xdgshell_interface_p.h b/src/wayland/xdgshell_interface_p.h new file mode 100644 index 0000000000..a69134b9c6 --- /dev/null +++ b/src/wayland/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