diff --git a/autotests/client/CMakeLists.txt b/autotests/client/CMakeLists.txt --- a/autotests/client/CMakeLists.txt +++ b/autotests/client/CMakeLists.txt @@ -1,3 +1,14 @@ +######################################################## +# Test Viewporter +######################################################## +set( testViewporter_SRCS + test_viewporter.cpp + ) +add_executable(testViewporter ${testViewporter_SRCS}) +target_link_libraries( testViewporter Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client Wayland::Server) +add_test(NAME kwayland-testViewporter COMMAND testViewporter) +ecm_mark_as_test(testViewporter) + ######################################################## # Test WaylandConnectionThread ######################################################## diff --git a/autotests/client/test_viewporter.cpp b/autotests/client/test_viewporter.cpp new file mode 100644 --- /dev/null +++ b/autotests/client/test_viewporter.cpp @@ -0,0 +1,558 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#include "../../src/client/compositor.h" +#include "../../src/client/connection_thread.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/surface.h" +#include "../../src/client/region.h" +#include "../../src/client/registry.h" +#include "../../src/client/shm_pool.h" +#include "../../src/client/viewporter.h" +#include "../../src/server/buffer_interface.h" +#include "../../src/server/compositor_interface.h" +#include "../../src/server/display.h" +#include "../../src/server/surface_interface.h" +#include "../../src/server/viewporter_interface.h" + +#include +#include + +#include +#include +#include + +class TestViewporter : public QObject +{ + Q_OBJECT +public: + explicit TestViewporter(QObject *parent = nullptr); +private Q_SLOTS: + void init(); + void cleanup(); + + void testViewportExists(); + void testWithoutBuffer(); + void testDestinationSize(); + void testSourceRectangle(); + void testDestinationSizeAndSourceRectangle(); + void testDataError_data(); + void testDataError(); + void testNoSurface(); + +private: + KWayland::Server::Display *m_display; + KWayland::Server::CompositorInterface *m_compositorInterface; + KWayland::Server::ViewporterInterface *m_viewporterInterface; + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::Compositor *m_compositor; + KWayland::Client::ShmPool *m_shm; + KWayland::Client::EventQueue *m_queue; + KWayland::Client::Viewporter *m_viewporter; + QThread *m_thread; +}; + +static const QString s_socketName = QStringLiteral("kwin-test-viewporter-0"); + +TestViewporter::TestViewporter(QObject *parent) + : QObject(parent) + , m_display(nullptr) + , m_compositorInterface(nullptr) + , m_connection(nullptr) + , m_compositor(nullptr) + , m_thread(nullptr) +{ +} + +void TestViewporter::init() +{ + using namespace KWayland::Server; + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + m_display->createShm(); + + m_compositorInterface = m_display->createCompositor(m_display); + QVERIFY(m_compositorInterface); + m_compositorInterface->create(); + QVERIFY(m_compositorInterface->isValid()); + + m_viewporterInterface = m_display->createViewporterInterface(m_display); + QVERIFY(m_viewporterInterface); + m_viewporterInterface->create(); + QVERIFY(m_viewporterInterface->isValid()); + + // setup connection + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, SIGNAL(connected())); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + m_queue = new KWayland::Client::EventQueue(this); + QVERIFY(!m_queue->isValid()); + m_queue->setup(m_connection); + QVERIFY(m_queue->isValid()); + + KWayland::Client::Registry registry; + registry.setEventQueue(m_queue); + QSignalSpy compositorSpy(®istry, SIGNAL(compositorAnnounced(quint32,quint32))); + QSignalSpy shmSpy(®istry, SIGNAL(shmAnnounced(quint32,quint32))); + QSignalSpy viewporterSpy(®istry, SIGNAL(viewporterAnnounced(quint32,quint32))); + QSignalSpy allAnnounced(®istry, SIGNAL(interfacesAnnounced())); + QVERIFY(allAnnounced.isValid()); + QVERIFY(shmSpy.isValid()); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + QVERIFY(allAnnounced.wait()); + QVERIFY(!compositorSpy.isEmpty()); + QVERIFY(!shmSpy.isEmpty()); + QVERIFY(!viewporterSpy.isEmpty()); + + m_compositor = registry.createCompositor(compositorSpy.first().first().value(), + compositorSpy.first().last().value(), this); + QVERIFY(m_compositor->isValid()); + m_shm = registry.createShmPool(shmSpy.first().first().value(), + shmSpy.first().last().value(), this); + QVERIFY(m_shm->isValid()); + + m_viewporter = registry.createViewporter( + registry.interface(KWayland::Client::Registry::Interface::Viewporter).name, + registry.interface(KWayland::Client::Registry::Interface::Viewporter).version, + this); + QVERIFY(m_viewporter->isValid()); +} + +void TestViewporter::cleanup() +{ + if (m_compositor) { + delete m_compositor; + m_compositor = nullptr; + } + if (m_viewporter) { + delete m_viewporter; + m_viewporter = nullptr; + } + if (m_shm) { + delete m_shm; + m_shm = nullptr; + } + if (m_queue) { + delete m_queue; + m_queue = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + delete m_connection; + m_connection = nullptr; + + delete m_compositorInterface; + m_compositorInterface = nullptr; + + delete m_viewporterInterface; + m_viewporterInterface = nullptr; + + delete m_display; + m_display = nullptr; +} + +void TestViewporter::testViewportExists() +{ + // This test verifies that setting the viewport for a surface twice results in a protocol error. + using namespace KWayland::Client; + using namespace KWayland::Server; + + // Create surface. + QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(serverSurfaceCreated.isValid()); + QScopedPointer s(m_compositor->createSurface()); + + QVERIFY(serverSurfaceCreated.wait()); + SurfaceInterface *serverSurface + = serverSurfaceCreated.first().first().value(); + QVERIFY(serverSurface); + + // Create viewport. + QSignalSpy serverViewportCreated(m_viewporterInterface, &ViewporterInterface::viewportCreated); + QVERIFY(serverViewportCreated.isValid()); + QScopedPointer vp1(m_viewporter->createViewport(s.data(), this)); + + QVERIFY(serverViewportCreated.wait()); + ViewportInterface *serverViewport + = serverViewportCreated.first().first().value(); + QVERIFY(serverViewport); + + // Create second viewport with error. + QSignalSpy errorSpy(m_connection, &ConnectionThread::errorOccurred); + QVERIFY(errorSpy.isValid()); + QScopedPointer vp2(m_viewporter->createViewport(s.data(), this)); + QVERIFY(errorSpy.wait()); +} + +void TestViewporter::testWithoutBuffer() +{ + // This test verifies that setting the viewport while buffer is zero does not change the size. + using namespace KWayland::Client; + using namespace KWayland::Server; + + // Create surface. + QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(serverSurfaceCreated.isValid()); + QScopedPointer s(m_compositor->createSurface()); + + QVERIFY(serverSurfaceCreated.wait()); + SurfaceInterface *serverSurface + = serverSurfaceCreated.first().first().value(); + QVERIFY(serverSurface); + + // Create viewport. + QSignalSpy serverViewportCreated(m_viewporterInterface, &ViewporterInterface::viewportCreated); + QVERIFY(serverViewportCreated.isValid()); + QScopedPointer vp(m_viewporter->createViewport(s.data(), this)); + + QVERIFY(serverViewportCreated.wait()); + ViewportInterface *serverViewport + = serverViewportCreated.first().first().value(); + QVERIFY(serverViewport); + + QVERIFY(!serverSurface->buffer()); + + // Change the destination size. + QSignalSpy sizeChangedSpy(serverSurface, &SurfaceInterface::sizeChanged); + vp->setDestinationSize(QSize(400, 200)); + s->commit(Surface::CommitFlag::None); + + // Even though a new destination size is set if there is no buffer, the surface size has not + // changed. + QVERIFY(!sizeChangedSpy.wait(100)); + QCOMPARE(sizeChangedSpy.count(), 0); + QVERIFY(!serverSurface->size().isValid()); + + // Now change the source rectangle. + vp->setSourceRectangle(QRectF(QPointF(100, 50), QSizeF(400, 200))); + s->commit(Surface::CommitFlag::None); + + // Even though a new source rectangle is set if there is no buffer, the surface size has not + // changed. + QVERIFY(!sizeChangedSpy.wait(100)); + QCOMPARE(sizeChangedSpy.count(), 0); + QVERIFY(!serverSurface->size().isValid()); +} + +void TestViewporter::testDestinationSize() +{ + // This test verifies setting the destination size is received by the server. + using namespace KWayland::Client; + using namespace KWayland::Server; + + // Create surface. + QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(serverSurfaceCreated.isValid()); + QScopedPointer s(m_compositor->createSurface()); + + QVERIFY(serverSurfaceCreated.wait()); + SurfaceInterface *serverSurface + = serverSurfaceCreated.first().first().value(); + QVERIFY(serverSurface); + + // Create viewport. + QSignalSpy serverViewportCreated(m_viewporterInterface, &ViewporterInterface::viewportCreated); + QVERIFY(serverViewportCreated.isValid()); + QScopedPointer vp(m_viewporter->createViewport(s.data(), this)); + + QVERIFY(serverViewportCreated.wait()); + ViewportInterface *serverViewport + = serverViewportCreated.first().first().value(); + QVERIFY(serverViewport); + + // Add a buffer. + QSignalSpy sizeChangedSpy(serverSurface, &SurfaceInterface::sizeChanged); + QVERIFY(sizeChangedSpy.isValid()); + QImage image(QSize(600, 400), QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::red); + s->attachBuffer(m_shm->createBuffer(image)); + s->damage(QRect(0, 0, 600, 400)); + s->commit(Surface::CommitFlag::None); + QVERIFY(sizeChangedSpy.wait()); + + // Change the destination size. + vp->setDestinationSize(QSize(400, 200)); + s->commit(Surface::CommitFlag::None); + + // The size of the surface has changed. + QVERIFY(sizeChangedSpy.wait()); + QCOMPARE(sizeChangedSpy.count(), 2); + QCOMPARE(serverSurface->size(), QSize(400, 200)); +} + +void TestViewporter::testSourceRectangle() +{ + // This test verifies setting the source rectangle is received by the server. + using namespace KWayland::Client; + using namespace KWayland::Server; + + // Create surface. + QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(serverSurfaceCreated.isValid()); + QScopedPointer s(m_compositor->createSurface()); + + QVERIFY(serverSurfaceCreated.wait()); + SurfaceInterface *serverSurface + = serverSurfaceCreated.first().first().value(); + QVERIFY(serverSurface); + + // Create viewport. + QSignalSpy serverViewportCreated(m_viewporterInterface, &ViewporterInterface::viewportCreated); + QVERIFY(serverViewportCreated.isValid()); + QScopedPointer vp(m_viewporter->createViewport(s.data(), this)); + + QVERIFY(serverViewportCreated.wait()); + ViewportInterface *serverViewport + = serverViewportCreated.first().first().value(); + QVERIFY(serverViewport); + + // Add a buffer. + QSignalSpy sizeChangedSpy(serverSurface, &SurfaceInterface::sizeChanged); + QVERIFY(sizeChangedSpy.isValid()); + QImage image(QSize(600, 400), QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::red); + s->attachBuffer(m_shm->createBuffer(image)); + s->damage(QRect(0, 0, 600, 400)); + s->commit(Surface::CommitFlag::None); + QVERIFY(sizeChangedSpy.wait()); + + // Change the source rectangle. + const QRectF rect(QPointF(100, 50), QSizeF(400, 200)); + QSignalSpy sourceRectangleChangedSpy(serverSurface, &SurfaceInterface::sourceRectangleChanged); + vp->setSourceRectangle(rect); + s->commit(Surface::CommitFlag::None); + + // The size of the surface has changed. + QVERIFY(sizeChangedSpy.wait()); + QCOMPARE(sizeChangedSpy.count(), 2); + if (sourceRectangleChangedSpy.count() == 0) { + QVERIFY(sourceRectangleChangedSpy.wait()); + } + QCOMPARE(sourceRectangleChangedSpy.count(), 1); + QCOMPARE(serverSurface->size(), rect.size()); + QCOMPARE(serverSurface->sourceRectangle(), rect); +} + +void TestViewporter::testDestinationSizeAndSourceRectangle() +{ + // This test verifies setting the destination size and source rectangle is received by the + // server. + using namespace KWayland::Client; + using namespace KWayland::Server; + + // Create surface. + QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(serverSurfaceCreated.isValid()); + QScopedPointer s(m_compositor->createSurface()); + + QVERIFY(serverSurfaceCreated.wait()); + SurfaceInterface *serverSurface + = serverSurfaceCreated.first().first().value(); + QVERIFY(serverSurface); + + // Create viewport. + QSignalSpy serverViewportCreated(m_viewporterInterface, &ViewporterInterface::viewportCreated); + QVERIFY(serverViewportCreated.isValid()); + QScopedPointer vp(m_viewporter->createViewport(s.data(), this)); + + QVERIFY(serverViewportCreated.wait()); + ViewportInterface *serverViewport + = serverViewportCreated.first().first().value(); + QVERIFY(serverViewport); + + // Add a buffer. + QSignalSpy sizeChangedSpy(serverSurface, &SurfaceInterface::sizeChanged); + QVERIFY(sizeChangedSpy.isValid()); + QImage image(QSize(600, 400), QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::red); + s->attachBuffer(m_shm->createBuffer(image)); + s->damage(QRect(0, 0, 600, 400)); + s->commit(Surface::CommitFlag::None); + QVERIFY(sizeChangedSpy.wait()); + + // Change the destination size. + const QSize destinationSize = QSize(100, 50); + vp->setDestinationSize(destinationSize); + + // Change the source rectangle. + const QRectF rect(QPointF(100.1, 50.5), QSizeF(400.9, 200.8)); + QSignalSpy sourceRectangleChangedSpy(serverSurface, &SurfaceInterface::sourceRectangleChanged); + vp->setSourceRectangle(rect); + s->commit(Surface::CommitFlag::None); + + // The size of the surface has changed. + QVERIFY(sizeChangedSpy.wait()); + QCOMPARE(sizeChangedSpy.count(), 2); + if (sourceRectangleChangedSpy.count() == 0) { + QVERIFY(sourceRectangleChangedSpy.wait()); + } + QCOMPARE(sourceRectangleChangedSpy.count(), 1); + QCOMPARE(serverSurface->size(), destinationSize); + QVERIFY((serverSurface->sourceRectangle().topLeft() + - rect.topLeft()).manhattanLength() < 0.01); + QVERIFY((serverSurface->sourceRectangle().bottomRight() + - rect.bottomRight()).manhattanLength() < 0.01); +} + +void TestViewporter::testDataError_data() +{ + QTest::addColumn("destinationSize"); + QTest::addColumn("sourceRectangle"); + + QTest::newRow("destination-size-bad-value1") << QSize(-1, 0) << QRectF(-1, -1, -1, -1); + QTest::newRow("destination-size-bad-value2") << QSize(-1, -2) << QRectF(-1, -1, -1, -1); + QTest::newRow("source-rectangle-bad-value1") << QSize(-1, -1) << QRectF(0, 0, 1, -1); + QTest::newRow("source-rectangle-bad-value2") << QSize(-1, -1) << QRectF(0, 0, 1, 1.5); + QTest::newRow("source-rectangle-out-of-buffer") << QSize(-1, -1) << QRectF(0, 0, 601, 400); +} + +void TestViewporter::testDataError() +{ + // This test verifies setting the source rectangle is received by the server. + using namespace KWayland::Client; + using namespace KWayland::Server; + + // Create surface. + QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(serverSurfaceCreated.isValid()); + QScopedPointer s(m_compositor->createSurface()); + + QVERIFY(serverSurfaceCreated.wait()); + SurfaceInterface *serverSurface + = serverSurfaceCreated.first().first().value(); + QVERIFY(serverSurface); + + // Create viewport. + QSignalSpy serverViewportCreated(m_viewporterInterface, &ViewporterInterface::viewportCreated); + QVERIFY(serverViewportCreated.isValid()); + QScopedPointer vp(m_viewporter->createViewport(s.data(), this)); + + QVERIFY(serverViewportCreated.wait()); + ViewportInterface *serverViewport + = serverViewportCreated.first().first().value(); + QVERIFY(serverViewport); + + // Add a buffer. + QSignalSpy sizeChangedSpy(serverSurface, &SurfaceInterface::sizeChanged); + QVERIFY(sizeChangedSpy.isValid()); + QImage image(QSize(600, 400), QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::red); + s->attachBuffer(m_shm->createBuffer(image)); + s->damage(QRect(0, 0, 600, 400)); + s->commit(Surface::CommitFlag::None); + QVERIFY(sizeChangedSpy.wait()); + + QFETCH(QSize, destinationSize); + QFETCH(QRectF, sourceRectangle); + + QSignalSpy errorSpy(m_connection, &ConnectionThread::errorOccurred); + QVERIFY(errorSpy.isValid()); + QVERIFY(!m_connection->hasError()); + + // Set the destination size. + vp->setDestinationSize(destinationSize); + + // Set the source rectangle. + vp->setSourceRectangle(sourceRectangle); + s->commit(); + + // One of these lead to an error. + QVERIFY(errorSpy.wait()); + QVERIFY(m_connection->hasError()); + // TODO: compare protocol error code +} + +void TestViewporter::testNoSurface() +{ + // This test verifies that setting the viewport for a surface twice results in a protocol error. + using namespace KWayland::Client; + using namespace KWayland::Server; + + // Create surface. + QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(serverSurfaceCreated.isValid()); + QScopedPointer surface(m_compositor->createSurface()); + + QVERIFY(serverSurfaceCreated.wait()); + SurfaceInterface *serverSurface + = serverSurfaceCreated.first().first().value(); + QVERIFY(serverSurface); + + // Create viewport. + QSignalSpy serverViewportCreated(m_viewporterInterface, &ViewporterInterface::viewportCreated); + QVERIFY(serverViewportCreated.isValid()); + QScopedPointer vp(m_viewporter->createViewport(surface.data(), this)); + + QVERIFY(serverViewportCreated.wait()); + ViewportInterface *serverViewport + = serverViewportCreated.first().first().value(); + QVERIFY(serverViewport); + + // Add a buffer. + QSignalSpy sizeChangedSpy(serverSurface, &SurfaceInterface::sizeChanged); + QVERIFY(sizeChangedSpy.isValid()); + QImage image(QSize(600, 400), QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::red); + surface->attachBuffer(m_shm->createBuffer(image)); + surface->damage(QRect(0, 0, 600, 400)); + surface->commit(Surface::CommitFlag::None); + QVERIFY(sizeChangedSpy.wait()); + + // Change the destination size. + vp->setDestinationSize(QSize(400, 200)); + surface->commit(Surface::CommitFlag::None); + + // The size of the surface has changed. + QVERIFY(sizeChangedSpy.wait()); + QCOMPARE(sizeChangedSpy.count(), 2); + QCOMPARE(serverSurface->size(), QSize(400, 200)); + + // Now destroy the surface. + surface.reset(); + + // And try to set data with the viewport what leads to a protocol error. + QSignalSpy errorSpy(m_connection, &ConnectionThread::errorOccurred); + QVERIFY(errorSpy.isValid()); + QVERIFY(!m_connection->hasError()); + + vp->setDestinationSize(QSize(500, 300)); + QVERIFY(errorSpy.wait()); + QVERIFY(m_connection->hasError()); + // TODO: compare protocol error code +} + +QTEST_GUILESS_MAIN(TestViewporter) +#include "test_viewporter.moc" diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -53,6 +53,7 @@ textinput.cpp textinput_v0.cpp textinput_v2.cpp + viewporter.cpp xdgdecoration.cpp xdgshell.cpp xdgforeign_v2.cpp @@ -174,7 +175,10 @@ PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/server-decoration-palette.xml BASENAME server-decoration-palette ) - +ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS + PROTOCOL ${WaylandProtocols_DATADIR}/stable/viewporter/viewporter.xml + BASENAME viewporter +) ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS PROTOCOL ${WaylandProtocols_DATADIR}/unstable/xdg-output/xdg-output-unstable-v1.xml BASENAME xdg-output-unstable-v1 @@ -213,6 +217,7 @@ ${CMAKE_CURRENT_BINARY_DIR}/wayland-server-decoration-palette-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-v0-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-v2-client-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-viewporter-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-shell-v6-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-relativepointer-unstable-v1-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-pointer-gestures-unstable-v1-client-protocol.h @@ -308,6 +313,7 @@ surface.h touch.h textinput.h + viewporter.h xdgdecoration.h xdgshell.h xdgforeign.h diff --git a/src/client/registry.h b/src/client/registry.h --- a/src/client/registry.h +++ b/src/client/registry.h @@ -55,6 +55,7 @@ struct org_kde_plasma_window_management; struct org_kde_kwin_server_decoration_manager; struct org_kde_kwin_server_decoration_palette_manager; +struct wp_viewporter; struct xdg_shell; struct zxdg_shell_v6; struct xdg_wm_base; @@ -105,6 +106,7 @@ class TextInputManager; class TextInputManagerUnstableV0; class TextInputManagerUnstableV2; +class Viewporter; class XdgShell; class RelativePointerManager; class XdgExporterUnstableV2; @@ -188,6 +190,7 @@ XdgShellStable, ///refers to xdg_wm_base @since 5.48 XdgDecorationUnstableV1, ///refers to zxdg_decoration_manager_v1, @since 5.54 Keystate,/// #include #include +#include #include #include #include @@ -289,6 +291,13 @@ &Registry::textInputManagerUnstableV2Announced, &Registry::textInputManagerUnstableV2Removed }}, + {Registry::Interface::Viewporter, { + 1, + QByteArrayLiteral("wp_viewporter"), + &wp_viewporter_interface, + &Registry::viewporterAnnounced, + &Registry::viewporterRemoved + }}, {Registry::Interface::XdgShellUnstableV5, { 1, QByteArrayLiteral("xdg_shell"), @@ -684,6 +693,7 @@ BIND(ServerSideDecorationManager, org_kde_kwin_server_decoration_manager) BIND(TextInputManagerUnstableV0, wl_text_input_manager) BIND(TextInputManagerUnstableV2, zwp_text_input_manager_v2) +BIND(Viewporter, wp_viewporter) BIND(XdgShellUnstableV5, xdg_shell) BIND(XdgShellUnstableV6, zxdg_shell_v6) BIND(XdgShellStable, xdg_wm_base) @@ -757,6 +767,7 @@ CREATE(AppMenuManager) CREATE(Keystate) CREATE(ServerSideDecorationPaletteManager) +CREATE(Viewporter) #undef CREATE #undef CREATE2 diff --git a/src/client/viewporter.h b/src/client/viewporter.h new file mode 100644 --- /dev/null +++ b/src/client/viewporter.h @@ -0,0 +1,213 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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 . +*********************************************************************/ +#pragma once + +#include "buffer.h" + +#include +#include +#include + +#include + +struct wl_buffer; +struct wp_viewport; +struct wp_viewporter; + +class QMarginsF; +class QWindow; + +namespace KWayland +{ +namespace Client +{ + +class EventQueue; +class Viewport; +class Surface; + +/** + * @short Wrapper for the wp_viewporter interface. + * + * This class provides a convenient wrapper for the wp_viewporter interface. + * + * To use this class one needs to interact with the Registry. There are two + * possible ways to create the Viewporter interface: + * @code + * Viewporter *vp = registry->createViewporter(name, version); + * @endcode + * + * This creates the Viewporter and sets it up directly. As an alternative this + * can also be done in a more low level way: + * @code + * Viewporter *vp = new Viewporter; + * s->setup(registry->bindViewporter(name, version)); + * @endcode + * + * The Viewporter can be used as a drop-in replacement for any wp_viewporter + * pointer as it provides matching cast operators. + * + * @see Registry + * @since 5.66 + **/ +class KWAYLANDCLIENT_EXPORT Viewporter : public QObject +{ + Q_OBJECT +public: + /** + * Creates a new Viewporter. + * Note: after constructing the Viewporter it is not yet valid and one needs + * to call setup. In order to get a ready to use Viewporter prefer using + * Registry::createViewporter. + **/ + explicit Viewporter(QObject *parent = nullptr); + virtual ~Viewporter(); + + /** + * @returns @c true if managing a wp_viewporter. + **/ + bool isValid() const; + /** + * Setup this Viewporter to manage the @p viewporter. + * When using Registry::createViewporter there is no need to call this + * method. + **/ + void setup(wp_viewporter *viewporter); + /** + * Releases the wp_viewporter interface. + * After the interface has been released the Viewporter instance is no + * longer valid and can be setup with another wp_viewporter interface. + **/ + void release(); + /** + * Destroys the data held by this Viewporter. + * 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 wp_viewporter interface + * once there is a new connection available. + * + * This method is automatically invoked when the Registry which created this + * Viewporter gets destroyed. + * + * @see release + **/ + void destroy(); + + /** + * Sets the @p queue to use for creating a Viewport. + **/ + void setEventQueue(EventQueue *queue); + /** + * @returns The event queue to use for creating a Viewport. + **/ + EventQueue *eventQueue(); + + /** + * Creates and setup a new Viewport with @p parent. + * @param surface The surface to use this Viewport with. + * @param parent The parent to pass to the Viewport. + * @returns The new created Viewport + **/ + Viewport *createViewport(Surface *surface, QObject *parent = nullptr); + + operator wp_viewporter*(); + operator wp_viewporter*() const; + +Q_SIGNALS: + /** + * The corresponding global for this interface on the Registry got removed. + * + * This signal gets only emitted if the Compositor got created by + * Registry::createViewporter + * + * @since 5.66 + **/ + void removed(); + +private: + class Private; + QScopedPointer d; +}; + +/** + * @short Wrapper for the wp_viewport interface. + * + * This class is a convenient wrapper for the wp_viewport interface. + * To create a Viewport call Viewporter::createViewport. + * + **/ +class KWAYLANDCLIENT_EXPORT Viewport : public QObject +{ + Q_OBJECT +public: + ~Viewport() override; + + /** + * Setup this Viewport to manage the @p viewport. + * When using Viewporter::createViewport there is no need to call this + * method. + **/ + void setup(wp_viewport *viewport); + /** + * Releases the wp_viewport interface. + * After the interface has been released the Viewport instance is no + * longer valid and can be setup with another wp_viewport interface. + **/ + void release(); + /** + * Destroys the data held by this Viewport. + * 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 wp_viewport interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, viewport, &Viewport::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + /** + * @returns @c true if managing a wp_viewport. + **/ + bool isValid() const; + + void setSourceRectangle(const QRectF &source); + void setDestinationSize(const QSize &dest); + + operator wp_viewport*(); + operator wp_viewport*() const; + +private: + friend class Viewporter; + explicit Viewport(QObject *parent = nullptr); + class Private; + QScopedPointer d; +}; + +} +} + diff --git a/src/client/viewporter.cpp b/src/client/viewporter.cpp new file mode 100644 --- /dev/null +++ b/src/client/viewporter.cpp @@ -0,0 +1,175 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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 "viewporter.h" + +#include "event_queue.h" +#include "surface.h" +#include "wayland_pointer_p.h" + +#include +#include + +namespace KWayland +{ + +namespace Client +{ + +class Q_DECL_HIDDEN Viewporter::Private +{ +public: + Private() = default; + + WaylandPointer manager; + EventQueue *queue = nullptr; +}; + +Viewporter::Viewporter(QObject *parent) + : QObject(parent) + , d(new Private) +{ +} + +Viewporter::~Viewporter() +{ + release(); +} + +void Viewporter::release() +{ + d->manager.release(); +} + +void Viewporter::destroy() +{ + d->manager.destroy(); +} + +bool Viewporter::isValid() const +{ + return d->manager.isValid(); +} + +void Viewporter::setup(wp_viewporter *manager) +{ + Q_ASSERT(manager); + Q_ASSERT(!d->manager); + d->manager.setup(manager); +} + +void Viewporter::setEventQueue(EventQueue *queue) +{ + d->queue = queue; +} + +EventQueue *Viewporter::eventQueue() +{ + return d->queue; +} + +Viewport *Viewporter::createViewport(Surface *surface, QObject *parent) +{ + Q_ASSERT(isValid()); + Viewport *vp = new Viewport(parent); + auto w = wp_viewporter_get_viewport(d->manager, *surface); + if (d->queue) { + d->queue->addProxy(w); + } + vp->setup(w); + return vp; +} + +Viewporter::operator wp_viewporter*() +{ + return d->manager; +} + +Viewporter::operator wp_viewporter*() const +{ + return d->manager; +} + +class Viewport::Private +{ +public: + WaylandPointer viewport; +}; + +Viewport::Viewport(QObject *parent) + : QObject(parent) + , d(new Private) +{ +} + +Viewport::~Viewport() +{ + release(); +} + +void Viewport::release() +{ + d->viewport.release(); +} + +void Viewport::setup(wp_viewport *viewport) +{ + Q_ASSERT(viewport); + Q_ASSERT(!d->viewport); + d->viewport.setup(viewport); +} + +void Viewport::destroy() +{ + d->viewport.destroy(); +} + +bool Viewport::isValid() const +{ + return d->viewport.isValid(); +} + +void Viewport::setSourceRectangle(const QRectF &source) +{ + Q_ASSERT(isValid()); + wp_viewport_set_source(d->viewport, + wl_fixed_from_double(source.x()), + wl_fixed_from_double(source.y()), + wl_fixed_from_double(source.width()), + wl_fixed_from_double(source.height())); +} + +void Viewport::setDestinationSize(const QSize &dest) +{ + Q_ASSERT(isValid()); + wp_viewport_set_destination(d->viewport, dest.width(), dest.height()); +} + +Viewport::operator wp_viewport*() +{ + return d->viewport; +} + +Viewport::operator wp_viewport*() const +{ + return d->viewport; +} + +} +} diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -54,6 +54,7 @@ textinput_interface_v0.cpp textinput_interface_v2.cpp touch_interface.cpp + viewporter_interface.cpp xdgdecoration_interface.cpp xdgforeign_interface.cpp xdgforeign_v2_interface.cpp @@ -191,6 +192,11 @@ BASENAME remote-access ) +ecm_add_wayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${WaylandProtocols_DATADIR}/stable/viewporter/viewporter.xml + BASENAME viewporter +) + ecm_add_wayland_server_protocol(SERVER_LIB_SRCS PROTOCOL ${WaylandProtocols_DATADIR}/unstable/xdg-output/xdg-output-unstable-v1.xml BASENAME xdg-output @@ -270,6 +276,8 @@ ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-unstable-v2-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-input-unstable-v2-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-text-server-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-viewporter-client-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-viewporter-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-decoration-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-decoration-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-xdg-foreign-unstable-v2-client-protocol.h @@ -364,6 +372,7 @@ surface_interface.h textinput_interface.h touch_interface.h + viewporter_interface.h xdgdecoration_interface.h xdgforeign_interface.h xdgoutput_interface.h diff --git a/src/server/display.h b/src/server/display.h --- a/src/server/display.h +++ b/src/server/display.h @@ -88,6 +88,8 @@ class AppMenuManagerInterface; class ServerSideDecorationPaletteManagerInterface; class PlasmaVirtualDesktopManagementInterface; +enum class ViewporterInterfaceVersion; +class ViewporterInterface; class XdgOutputManagerInterface; class XdgDecorationManagerInterface; class EglStreamControllerInterface; @@ -290,6 +292,14 @@ **/ LinuxDmabufUnstableV1Interface *createLinuxDmabufInterface(QObject *parent = nullptr); + /** + * Creates the ViewporterInterface + * + * @return the created viewporter + * @since 5.66 + */ + ViewporterInterface *createViewporterInterface(QObject *parent = nullptr); + /** * Creates the XdgOutputManagerInterface * diff --git a/src/server/display.cpp b/src/server/display.cpp --- a/src/server/display.cpp +++ b/src/server/display.cpp @@ -46,6 +46,7 @@ #include "shell_interface.h" #include "subcompositor_interface.h" #include "textinput_interface_p.h" +#include "viewporter_interface.h" #include "xdgshell_v5_interface_p.h" #include "xdgforeign_interface.h" #include "xdgshell_v6_interface_p.h" @@ -509,6 +510,13 @@ return b; } +ViewporterInterface *Display::createViewporterInterface(QObject *parent) +{ + auto b = new ViewporterInterface(this, parent); + connect(this, &Display::aboutToTerminate, b, [this, b] { delete b; }); + return b; +} + XdgOutputManagerInterface *Display::createXdgOutputManager(QObject *parent) { auto b = new XdgOutputManagerInterface(this, parent); diff --git a/src/server/surface_interface.h b/src/server/surface_interface.h --- a/src/server/surface_interface.h +++ b/src/server/surface_interface.h @@ -47,6 +47,8 @@ class ShadowInterface; class SlideInterface; class SubSurfaceInterface; +class ViewportInterface; +class ViewporterInterface; /** * @brief Resource representing a wl_surface. @@ -72,6 +74,7 @@ * @see ContrastInterface * @see ShadowInterface * @see SlideInterface + * @see ViewportInterface **/ class KWAYLANDSERVER_EXPORT SurfaceInterface : public Resource { @@ -114,10 +117,17 @@ bool inputIsInfinite() const; qint32 scale() const; OutputInterface::Transform transform() const; +#if KWAYLANDSERVER_ENABLE_DEPRECATED_SINCE(5, 5) /** * @returns the current BufferInterface, might be @c nullptr. + * @deprecated Since 5.66, use constBuffer **/ BufferInterface *buffer(); +#endif + /** + * @returns the current BufferInterface, might be @c nullptr. + **/ + BufferInterface *constBuffer() const; QPoint offset() const; /** * The size of the Surface in global compositor space. @@ -160,6 +170,12 @@ **/ QPointer contrast() const; + /** + * @returns The Viewport for this Surface. + * @since 5.66 + **/ + QPointer viewport() const; + /** * Whether the SurfaceInterface is currently considered to be mapped. * A SurfaceInterface is mapped if it has a non-null BufferInterface attached. @@ -293,6 +309,12 @@ **/ SurfaceInterface* dataProxy() const; + /** + * Returns the source rectangle. If none is set the returned rectangle is not valid. + * @since 5.66 + **/ + QRectF sourceRectangle() const; + Q_SIGNALS: /** * Emitted whenever the SurfaceInterface got damaged. @@ -331,6 +353,10 @@ * @since 5.5 **/ void contrastChanged(); + /** + * @since 5.66 + **/ + void sourceRectangleChanged(); /** * Emitted whenever the tree of sub-surfaces changes in a way which requires a repaint. * @since 5.22 @@ -375,6 +401,8 @@ friend class IdleInhibitManagerUnstableV1Interface; friend class PointerConstraintsUnstableV1Interface; friend class SurfaceRole; + friend class ViewporterInterface; + explicit SurfaceInterface(CompositorInterface *parent, wl_resource *parentResource); class Private; diff --git a/src/server/surface_interface.cpp b/src/server/surface_interface.cpp --- a/src/server/surface_interface.cpp +++ b/src/server/surface_interface.cpp @@ -28,8 +28,12 @@ #include "subcompositor_interface.h" #include "subsurface_interface_p.h" #include "surfacerole_p.h" +#include "viewporter_interface.h" + +#include // Qt #include + // Wayland #include // std @@ -177,6 +181,40 @@ pending.contrastIsSet = true; } +void SurfaceInterface::Private::setSourceRectangle(const QRectF &source) +{ + pending.sourceRectangle = source; + pending.sourceRectangleIsSet = true; +} + +void SurfaceInterface::Private::setDestinationSize(const QSize &dest) +{ + pending.destinationSize = dest; + pending.destinationSizeIsSet = true; +} + +void SurfaceInterface::Private::installViewport(ViewportInterface *vp) +{ + Q_ASSERT(viewport.isNull()); + viewport = QPointer(vp); + connect(viewport, &ViewportInterface::destinationSizeSet, q_func(), + [this](const QSize &size) { + setDestinationSize(size); + } + ); + connect(viewport, &ViewportInterface::sourceRectangleSet, q_func(), + [this](const QRectF &rect) { + setSourceRectangle(rect); + } + ); + connect(viewport, &ViewportInterface::unbound, q_func(), + [this] { + setDestinationSize(QSize()); + setSourceRectangle(QRectF()); + } + ); +} + void SurfaceInterface::Private::installPointerConstraint(LockedPointerInterface *lock) { Q_ASSERT(lockedPointer.isNull()); @@ -341,6 +379,8 @@ const bool blurChanged = source->blurIsSet; const bool contrastChanged = source->contrastIsSet; const bool slideChanged = source->slideIsSet; + const bool sourceRectangleChanged = source->sourceRectangleIsSet; + const bool destinationSizeChanged = source->destinationSizeIsSet; const bool childrenChanged = source->childrenChanged; bool sizeChanged = false; auto buffer = target->buffer; @@ -417,6 +457,45 @@ target->transform = source->transform; target->transformIsSet = true; } + if (destinationSizeChanged) { + target->destinationSize = source->destinationSize; + target->destinationSizeIsSet = true; + + sizeChanged |= (bool)buffer; + } + if (sourceRectangleChanged) { + if (!target->destinationSize.isValid() && source->sourceRectangle.isValid()) { + // Check on size being integer-valued. + const double width = source->sourceRectangle.width(); + const double height = source->sourceRectangle.height(); + if (!qFuzzyCompare(width, (int)width) || !qFuzzyCompare(height, (int)height)) { + wl_resource_post_error(viewport->parentResource(), WP_VIEWPORT_ERROR_BAD_SIZE, + "Source rectangle not integer valued"); + } + if (buffer) { + QSizeF bufferSize = buffer->size() / target->scale; + + if (target->transform == OutputInterface::Transform::Rotated90 || + target->transform == OutputInterface::Transform::Rotated270 || + target->transform == OutputInterface::Transform::Flipped90 || + target->transform == OutputInterface::Transform::Flipped270) { + bufferSize.transpose(); + } + if (!QRectF(QPointF(), bufferSize).contains(source->sourceRectangle)) { + wl_resource_post_error(viewport->parentResource(), + WP_VIEWPORT_ERROR_OUT_OF_BUFFER, + "Source rectangle not contained in buffer"); + } + // TODO: We should make this dependent on the previous size being different. + // But looking at above sizeChanged calculation when setting the buffer + // we need to do fix this there as well (does not look at buffer transform + // and destination size). + sizeChanged = true; + } + } + target->sourceRectangle = source->sourceRectangle; + target->sourceRectangleIsSet = true; + } if (!lockedPointer.isNull()) { lockedPointer->d_func()->commit(); } @@ -505,6 +584,9 @@ if (slideChanged) { emit q->slideOnShowHideChanged(); } + if (sourceRectangleChanged) { + emit q->sourceRectangleChanged(); + } if (childrenChanged) { emit q->subSurfaceTreeChanged(); } @@ -747,12 +829,24 @@ return d->current.buffer; } +BufferInterface *SurfaceInterface::constBuffer() const +{ + Q_D(); + return d->current.buffer; +} + QPoint SurfaceInterface::offset() const { Q_D(); return d->current.offset; } +QRectF SurfaceInterface::sourceRectangle() const +{ + Q_D(); + return d->current.sourceRectangle; +} + SurfaceInterface *SurfaceInterface::get(wl_resource *native) { return Private::get(native); @@ -778,11 +872,17 @@ QSize SurfaceInterface::size() const { Q_D(); - // TODO: apply transform to the buffer size - if (d->current.buffer) { - return d->current.buffer->size() / scale(); + if (!d->current.buffer) { + return QSize(); + } + if (d->current.destinationSize.isValid()) { + return d->current.destinationSize; } - return QSize(); + if (d->current.sourceRectangle.isValid()) { + return d->current.sourceRectangle.size().toSize(); + } + // TODO: apply transform to the buffer size + return d->current.buffer->size() / scale(); } QPointer< ShadowInterface > SurfaceInterface::shadow() const diff --git a/src/server/surface_interface_p.h b/src/server/surface_interface_p.h --- a/src/server/surface_interface_p.h +++ b/src/server/surface_interface_p.h @@ -55,11 +55,15 @@ bool childrenChanged = false; bool scaleIsSet = false; bool transformIsSet = false; + bool sourceRectangleIsSet = false; + bool destinationSizeIsSet = false; qint32 scale = 1; OutputInterface::Transform transform = OutputInterface::Transform::Normal; QList callbacks = QList(); QPoint offset = QPoint(); BufferInterface *buffer = nullptr; + QRectF sourceRectangle = QRectF(); + QSize destinationSize = QSize(); // stacking order: bottom (first) -> top (last) QList> children; QPointer shadow; @@ -80,9 +84,12 @@ void setBlur(const QPointer &blur); void setContrast(const QPointer &contrast); void setSlide(const QPointer &slide); + void setSourceRectangle(const QRectF &source); + void setDestinationSize(const QSize &dest); void installPointerConstraint(LockedPointerInterface *lock); void installPointerConstraint(ConfinedPointerInterface *confinement); void installIdleInhibitor(IdleInhibitorInterface *inhibitor); + void installViewport(ViewportInterface *vp); void commitSubSurface(); void commit(); @@ -105,6 +112,7 @@ QPointer lockedPointer; QPointer confinedPointer; + QPointer viewport; QHash outputDestroyedConnections; QVector idleInhibitors; diff --git a/src/server/viewporter_interface.h b/src/server/viewporter_interface.h new file mode 100644 --- /dev/null +++ b/src/server/viewporter_interface.h @@ -0,0 +1,87 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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 . +*********************************************************************/ +#pragma once + +#include + +#include "global.h" +#include "resource.h" + +namespace KWayland +{ +namespace Server +{ + +class Display; +class SurfaceInterface; +class ViewportInterface; + +/** + * @brief Represents the Global for wp_viewporter interface. + * + * This class creates ViewportInterfaces and attaches them to SurfaceInterfaces. + * + * @see ViewportInterface + * @see SurfaceInterface + * @since 5.66 + **/ +class KWAYLANDSERVER_EXPORT ViewporterInterface : public Global +{ + Q_OBJECT +public: + ~ViewporterInterface() override; + +Q_SIGNALS: + /** + * Emitted whenever this ViewporterInterface created a ViewportInterface. + **/ + void viewportCreated(ViewportInterface*); + +private: + explicit ViewporterInterface(Display *display, QObject *parent = nullptr); + friend class Display; + class Private; +}; + +/** + * @brief Resource for the wp_viewport interface. + * + **/ +class KWAYLANDSERVER_EXPORT ViewportInterface : public Resource +{ + Q_OBJECT +public: + ~ViewportInterface() override; + +Q_SIGNALS: + void destinationSizeSet(const QSize &size); + void sourceRectangleSet(const QRectF &rect); + +private: + explicit ViewportInterface(ViewporterInterface *viewporter, wl_resource *parentResource, + SurfaceInterface *surface); + friend class ViewporterInterface; + + class Private; + Private *d_func() const; +}; + +} +} diff --git a/src/server/viewporter_interface.cpp b/src/server/viewporter_interface.cpp new file mode 100644 --- /dev/null +++ b/src/server/viewporter_interface.cpp @@ -0,0 +1,259 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +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 "viewporter_interface.h" + +#include "display.h" +#include "global_p.h" +#include "resource_p.h" +#include "surface_interface_p.h" + +#include +#include + +namespace KWayland +{ + +namespace Server +{ + +class ViewporterInterface::Private : public Global::Private +{ +public: + Private(ViewporterInterface *q, Display *d); + +private: + void bind(wl_client *client, uint32_t version, uint32_t id) override; + void getViewport(wl_client *client, wl_resource *resource, uint32_t id, + wl_resource *surface); + + static void destroyCallback(wl_client *client, wl_resource *resource); + static void getViewportCallback(wl_client *client, wl_resource *resource, uint32_t id, + wl_resource *surface); + static void unbind(wl_resource *resource); + static Private *cast(wl_resource *r) { + auto viewporter = reinterpret_cast*>( + wl_resource_get_user_data(r))->data(); + if (viewporter) { + return static_cast(viewporter->d.data()); + } + return nullptr; + } + + ViewporterInterface *q; + static const struct wp_viewporter_interface s_interface; + static const quint32 s_version; +}; + +const quint32 ViewporterInterface::Private::s_version = 1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct wp_viewporter_interface ViewporterInterface::Private::s_interface = { + destroyCallback, + getViewportCallback +}; +#endif + +ViewporterInterface::Private::Private(ViewporterInterface *q, Display *d) + : Global::Private(d, &wp_viewporter_interface, s_version) + , q(q) +{ +} + +void ViewporterInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) +{ + auto c = display->getConnection(client); + wl_resource *resource = c->createResource(&wp_viewporter_interface, qMin(version, s_version), + id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + auto ref = new QPointer(q); // Is deleted in unbind. + wl_resource_set_implementation(resource, &s_interface, ref, unbind); +} + +void ViewporterInterface::Private::unbind(wl_resource *resource) +{ + delete reinterpret_cast*>(wl_resource_get_user_data(resource)); +} + +void ViewporterInterface::Private::destroyCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + Q_UNUSED(resource) + unbind(resource); +} + +void ViewporterInterface::Private::getViewportCallback(wl_client *client, wl_resource *resource, + uint32_t id, wl_resource *surface) +{ + auto *privateInstance = cast(resource); + if (!privateInstance) { + // When global is deleted. + return; + } + privateInstance->getViewport(client, resource, id, surface); +} + +void ViewporterInterface::Private::getViewport(wl_client *client, wl_resource *resource, + uint32_t id, wl_resource *surface) +{ + auto *surfaceInterface = SurfaceInterface::get(surface); + if (!surfaceInterface) { + // TODO: send error msg? + return; + } + + if (!surfaceInterface->d_func()->viewport.isNull()) { + // Surface already has a viewport. That's a protocol error. + wl_resource_post_error(resource, WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS, + "Surface already has viewport"); + return; + } + + ViewportInterface *viewport = new ViewportInterface(q, resource, surfaceInterface); + viewport->create(display->getConnection(client), wl_resource_get_version(resource), id); + if (!viewport->resource()) { + wl_resource_post_no_memory(resource); + delete viewport; + return; + } + surfaceInterface->d_func()->installViewport(viewport); + + Q_EMIT q->viewportCreated(viewport); +} + +ViewporterInterface::ViewporterInterface(Display *display, QObject *parent) + : Global(new Private(this, display), parent) +{ +} + +ViewporterInterface::~ViewporterInterface() = default; + +class ViewportInterface::Private : public Resource::Private +{ +public: + Private(ViewportInterface *q, ViewporterInterface *viewporter, wl_resource *parentResource, + SurfaceInterface *_surface); + ~Private() override; + + SurfaceInterface *surface; + +private: + ViewportInterface *q_func() { + return reinterpret_cast(q); + } + + void setSource(double x, double y, double width, double height); + void setDestination(int width, int height); + + static void setSourceCallback(wl_client *client, wl_resource *resource, + wl_fixed_t x, wl_fixed_t y, + wl_fixed_t width, wl_fixed_t height); + static void setDestinationCallback(wl_client *client, wl_resource *resource, + int32_t width, int32_t height); + + static const struct wp_viewport_interface s_interface; +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct wp_viewport_interface ViewportInterface::Private::s_interface = { + resourceDestroyedCallback, + setSourceCallback, + setDestinationCallback +}; +#endif + +void ViewportInterface::Private::setSourceCallback(wl_client *client, wl_resource *resource, + wl_fixed_t x, wl_fixed_t y, + wl_fixed_t width, wl_fixed_t height) +{ + Q_UNUSED(client) + cast(resource)->setSource(wl_fixed_to_double(x), wl_fixed_to_double(y), + wl_fixed_to_double(width), wl_fixed_to_double(height)); +} + +void ViewportInterface::Private::setSource(double x, double y, double width, double height) +{ + if (!surface) { + wl_resource_post_error(parentResource, WP_VIEWPORT_ERROR_NO_SURFACE, + "Viewport without surface"); + return; + } + if (x < 0 || y < 0 || width <= 0 || height <= 0) { + auto cmp = [](double number) { return !qFuzzyCompare(number, -1.); }; + if (cmp(x) || cmp(y) || cmp(width) || cmp(height)) { + wl_resource_post_error(parentResource, WP_VIEWPORT_ERROR_BAD_VALUE, + "Source rectangle not well defined"); + return; + } + } + Q_EMIT q_func()->sourceRectangleSet(QRectF(x, y, width, height)); +} + +void ViewportInterface::Private::setDestinationCallback(wl_client *client, wl_resource *resource, + int32_t width, int32_t height) +{ + Q_UNUSED(client) + cast(resource)->setDestination(width, height); +} + +void ViewportInterface::Private::setDestination(int width, int height) +{ + if (!surface) { + wl_resource_post_error(parentResource, WP_VIEWPORT_ERROR_NO_SURFACE, + "Viewport without surface"); + return; + } + if ((width <= 0 && width != -1) || (height <= 0 && height != -1)) { + wl_resource_post_error(parentResource, WP_VIEWPORT_ERROR_BAD_VALUE, + "Destination size not well defined"); + return; + } + Q_EMIT q_func()->destinationSizeSet(QSize(width, height)); +} + +ViewportInterface::Private::Private(ViewportInterface *q, ViewporterInterface *viewporter, + wl_resource *parentResource, SurfaceInterface *_surface) + : Resource::Private(q, viewporter, parentResource, &wp_viewport_interface, &s_interface) + , surface(_surface) +{ +} + +ViewportInterface::Private::~Private() = default; + +ViewportInterface::ViewportInterface(ViewporterInterface *viewporter, wl_resource *parentResource, + SurfaceInterface *surface) + : Resource(new Private(this, viewporter, parentResource, surface)) +{ + connect(surface, &SurfaceInterface::unbound, this, [this] { + d_func()->surface = nullptr; + }); +} + +ViewportInterface::~ViewportInterface() = default; + +ViewportInterface::Private *ViewportInterface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +} +}