diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 831aef3146..3a9625108e 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -58,6 +58,7 @@ set(SERVER_LIB_SRCS textinput_interface_v0.cpp textinput_interface_v2.cpp touch_interface.cpp + viewporter_interface.cpp xdgdecoration_v1_interface.cpp xdgforeign_interface.cpp xdgforeign_v2_interface.cpp @@ -238,6 +239,11 @@ ecm_add_qtwayland_server_protocol(SERVER_LIB_SRCS BASENAME keyboard-shortcuts-inhibit-unstable-v1 ) +ecm_add_qtwayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${WaylandProtocols_DATADIR}/stable/viewporter/viewporter.xml + BASENAME viewporter +) + set(SERVER_GENERATED_SRCS ${CMAKE_CURRENT_BINARY_DIR}/wayland-blur-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-blur-server-protocol.h @@ -386,6 +392,7 @@ set(SERVER_LIB_HEADERS tablet_interface.h textinput_interface.h touch_interface.h + viewporter_interface.h xdgdecoration_v1_interface.h xdgforeign_interface.h xdgoutput_interface.h diff --git a/src/wayland/autotests/server/CMakeLists.txt b/src/wayland/autotests/server/CMakeLists.txt index 58205d266e..e5202e7023 100644 --- a/src/wayland/autotests/server/CMakeLists.txt +++ b/src/wayland/autotests/server/CMakeLists.txt @@ -64,3 +64,14 @@ target_link_libraries(testKeyboardShortcutsInhibitorInterface Qt5::Test Plasma:: add_test(NAME kwayland-testKeyboardShortcutsInhibitorInterface COMMAND testKeyboardShortcutsInhibitorInterface) ecm_mark_as_test(testKeyboardShortcutsInhibitorInterface) +######################################################## +# Test Viewporter Interface +######################################################## +ecm_add_qtwayland_client_protocol(VIEWPORTER_SRCS + PROTOCOL ${WaylandProtocols_DATADIR}/stable/viewporter/viewporter.xml + BASENAME viewporter + ) +add_executable(testViewporterInterface test_viewporter_interface.cpp ${VIEWPORTER_SRCS}) +target_link_libraries(testViewporterInterface Qt5::Test Plasma::KWaylandServer KF5::WaylandClient Wayland::Client) +add_test(NAME kwayland-testViewporterInterface COMMAND testViewporterInterface) +ecm_mark_as_test(testViewporterInterface) diff --git a/src/wayland/autotests/server/test_viewporter_interface.cpp b/src/wayland/autotests/server/test_viewporter_interface.cpp new file mode 100644 index 0000000000..b36bd65564 --- /dev/null +++ b/src/wayland/autotests/server/test_viewporter_interface.cpp @@ -0,0 +1,193 @@ +/* + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include +#include + +#include "../../src/server/compositor_interface.h" +#include "../../src/server/display.h" +#include "../../src/server/surface_interface.h" +#include "../../src/server/viewporter_interface.h" + +#include "KWayland/Client/compositor.h" +#include "KWayland/Client/connection_thread.h" +#include "KWayland/Client/event_queue.h" +#include "KWayland/Client/registry.h" +#include "KWayland/Client/shm_pool.h" +#include "KWayland/Client/surface.h" + +#include "qwayland-viewporter.h" + +using namespace KWaylandServer; + +class Viewporter : public QtWayland::wp_viewporter +{ +}; + +class Viewport : public QtWayland::wp_viewport +{ +}; + +class TestViewporterInterface : public QObject +{ + Q_OBJECT + +public: + ~TestViewporterInterface() override; + +private Q_SLOTS: + void initTestCase(); + void testCropScale(); + +private: + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::EventQueue *m_queue; + KWayland::Client::Compositor *m_clientCompositor; + KWayland::Client::ShmPool *m_shm; + + QThread *m_thread; + Display m_display; + CompositorInterface *m_serverCompositor; + Viewporter *m_viewporter; +}; + +static const QString s_socketName = QStringLiteral("kwin-wayland-server-viewporter-test-0"); + +void TestViewporterInterface::initTestCase() +{ + m_display.setSocketName(s_socketName); + m_display.start(); + QVERIFY(m_display.isRunning()); + + m_display.createShm(); + m_display.createViewporter(); + + m_serverCompositor = m_display.createCompositor(this); + m_serverCompositor->create(); + QVERIFY(m_serverCompositor->isValid()); + + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::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()); + QVERIFY(!m_connection->connections().isEmpty()); + + m_queue = new KWayland::Client::EventQueue(this); + QVERIFY(!m_queue->isValid()); + m_queue->setup(m_connection); + QVERIFY(m_queue->isValid()); + + auto registry = new KWayland::Client::Registry(this); + connect(registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, registry](const QByteArray &interface, quint32 id, quint32 version) { + if (interface == QByteArrayLiteral("wp_viewporter")) { + m_viewporter = new Viewporter(); + m_viewporter->init(*registry, id, version); + } + }); + QSignalSpy allAnnouncedSpy(registry, &KWayland::Client::Registry::interfaceAnnounced); + QSignalSpy compositorSpy(registry, &KWayland::Client::Registry::compositorAnnounced); + QSignalSpy shmSpy(registry, &KWayland::Client::Registry::shmAnnounced); + registry->setEventQueue(m_queue); + registry->create(m_connection->display()); + QVERIFY(registry->isValid()); + registry->setup(); + QVERIFY(allAnnouncedSpy.wait()); + + m_clientCompositor = registry->createCompositor(compositorSpy.first().first().value(), + compositorSpy.first().last().value(), this); + QVERIFY(m_clientCompositor->isValid()); + + m_shm = registry->createShmPool(shmSpy.first().first().value(), + shmSpy.first().last().value(), this); + QVERIFY(m_shm->isValid()); +} + +TestViewporterInterface::~TestViewporterInterface() +{ + 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; + } + m_connection->deleteLater(); + m_connection = nullptr; +} + +void TestViewporterInterface::testCropScale() +{ + // Create a test surface. + QSignalSpy serverSurfaceCreatedSpy(m_serverCompositor, &CompositorInterface::surfaceCreated); + QVERIFY(serverSurfaceCreatedSpy.isValid()); + QScopedPointer clientSurface(m_clientCompositor->createSurface(this)); + QVERIFY(serverSurfaceCreatedSpy.wait()); + SurfaceInterface *serverSurface = serverSurfaceCreatedSpy.first().first().value(); + QVERIFY(serverSurface); + + QSignalSpy serverSurfaceMappedSpy(serverSurface, &SurfaceInterface::mapped); + QVERIFY(serverSurfaceMappedSpy.isValid()); + QSignalSpy serverSurfaceSizeChangedSpy(serverSurface, &SurfaceInterface::sizeChanged); + QVERIFY(serverSurfaceSizeChangedSpy.isValid()); + + // Map the surface. + QImage image(QSize(100, 50), QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::black); + KWayland::Client::Buffer::Ptr buffer = m_shm->createBuffer(image); + clientSurface->attachBuffer(buffer); + clientSurface->damage(image.rect()); + clientSurface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(serverSurfaceMappedSpy.wait()); + QCOMPARE(serverSurface->size(), QSize(100, 50)); + QCOMPARE(serverSurface->viewport(), QRectF(0, 0, 100, 50)); + + // Create a viewport for the surface. + QScopedPointer clientViewport(new Viewport); + clientViewport->init(m_viewporter->get_viewport(*clientSurface)); + + // Crop the surface. + clientViewport->set_source(wl_fixed_from_double(10), wl_fixed_from_double(10), + wl_fixed_from_double(30), wl_fixed_from_double(20)); + clientSurface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(serverSurfaceSizeChangedSpy.wait()); + QCOMPARE(serverSurface->size(), QSize(30, 20)); + QCOMPARE(serverSurface->viewport(), QRectF(10, 10, 30, 20)); + + // Scale the surface. + clientViewport->set_destination(500, 250); + clientSurface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(serverSurfaceSizeChangedSpy.wait()); + QCOMPARE(serverSurface->size(), QSize(500, 250)); + QCOMPARE(serverSurface->viewport(), QRectF(10, 10, 30, 20)); + + // If the viewport is destroyed, the crop and scale state will be unset on a next commit. + clientViewport->destroy(); + clientSurface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(serverSurfaceSizeChangedSpy.wait()); + QCOMPARE(serverSurface->size(), QSize(100, 50)); + QCOMPARE(serverSurface->viewport(), QRectF(0, 0, 100, 50)); +} + +QTEST_GUILESS_MAIN(TestViewporterInterface) + +#include "test_viewporter_interface.moc" diff --git a/src/wayland/display.cpp b/src/wayland/display.cpp index 51a214fcde..23edc0a608 100644 --- a/src/wayland/display.cpp +++ b/src/wayland/display.cpp @@ -39,6 +39,7 @@ #include "subcompositor_interface.h" #include "tablet_interface.h" #include "textinput_interface_p.h" +#include "viewporter_interface.h" #include "xdgdecoration_v1_interface.h" #include "xdgforeign_interface.h" #include "xdgoutput_interface.h" @@ -516,6 +517,13 @@ KeyboardShortcutsInhibitManagerV1Interface *Display::createKeyboardShortcutsInhi return d; } +ViewporterInterface *Display::createViewporter(QObject *parent) +{ + auto viewporter = new ViewporterInterface(this, parent); + connect(this, &Display::aboutToTerminate, viewporter, [viewporter] { delete viewporter; }); + return viewporter; +} + void Display::createShm() { Q_ASSERT(d->display); diff --git a/src/wayland/display.h b/src/wayland/display.h index f0c7de27bd..8849f7bf17 100644 --- a/src/wayland/display.h +++ b/src/wayland/display.h @@ -79,6 +79,7 @@ class LinuxDmabufUnstableV1Interface; class TabletManagerInterface; class DataControlDeviceManagerV1Interface; class KeyboardShortcutsInhibitManagerV1Interface; +class ViewporterInterface; /** * @brief Class holding the Wayland server display loop. @@ -327,6 +328,11 @@ public: */ KeyboardShortcutsInhibitManagerV1Interface *createKeyboardShortcutsInhibitManagerV1(QObject *parent = nullptr); + /** + * Creates the viewporter compositor extension. + */ + ViewporterInterface *createViewporter(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/surface_interface.cpp b/src/wayland/surface_interface.cpp index 40ea3dad62..e0aeed7967 100644 --- a/src/wayland/surface_interface.cpp +++ b/src/wayland/surface_interface.cpp @@ -329,16 +329,13 @@ void SurfaceInterface::Private::swapStates(State *source, State *target, bool em const bool slideChanged = source->slideIsSet; const bool childrenChanged = source->childrenChanged; const bool visibilityChanged = bufferChanged && (bool(source->buffer) != bool(target->buffer)); - bool sizeChanged = false; - auto buffer = target->buffer; + const QRectF oldViewport = target->viewport; + const QSize oldSize = target->size; if (bufferChanged) { // TODO: is the reffing correct for subsurfaces? - QSize oldSize; if (target->buffer) { - oldSize = target->buffer->size(); if (emitChanged) { target->buffer->unref(); - QObject::disconnect(target->buffer, &BufferInterface::sizeChanged, q, &SurfaceInterface::sizeChanged); } else { delete target->buffer; target->buffer = nullptr; @@ -347,21 +344,22 @@ void SurfaceInterface::Private::swapStates(State *source, State *target, bool em if (source->buffer) { if (emitChanged) { source->buffer->ref(); - QObject::connect(source->buffer, &BufferInterface::sizeChanged, q, &SurfaceInterface::sizeChanged); } - const QSize newSize = source->buffer->size(); - sizeChanged = newSize.isValid() && newSize != oldSize; } - buffer = source->buffer; - } - // copy values - if (bufferChanged) { - target->buffer = buffer; + target->buffer = source->buffer; target->offset = source->offset; target->damage = source->damage; target->bufferDamage = source->bufferDamage; target->bufferIsSet = source->bufferIsSet; } + if (source->sourceGeometryIsSet) { + target->sourceGeometry = source->sourceGeometry; + target->sourceGeometryIsSet = true; + } + if (source->destinationSizeIsSet) { + target->destinationSize = source->destinationSize; + target->destinationSizeIsSet = true; + } if (childrenChanged) { target->childrenChanged = source->childrenChanged; target->children = source->children; @@ -414,6 +412,21 @@ void SurfaceInterface::Private::swapStates(State *source, State *target, bool em if (!emitChanged) { return; } + if (target->buffer) { + if (target->sourceGeometry.isValid()) { + target->viewport = target->sourceGeometry; + } else { + target->viewport = QRectF(QPointF(0, 0), invertBufferTransform(target, target->buffer->size())); + } + if (target->destinationSize.isValid()) { + target->size = target->destinationSize; + } else { + target->size = target->viewport.size().toSize(); + } + } else { + target->viewport = QRectF(); + target->size = QSize(); + } if (opaqueRegionChanged) { emit q->opaqueChanged(target->opaque); } @@ -422,9 +435,6 @@ void SurfaceInterface::Private::swapStates(State *source, State *target, bool em } if (scaleFactorChanged) { emit q->scaleChanged(target->scale); - if (buffer && !sizeChanged) { - emit q->sizeChanged(); - } } if (transformChanged) { emit q->transformChanged(target->transform); @@ -457,9 +467,12 @@ void SurfaceInterface::Private::swapStates(State *source, State *target, bool em } } } - if (sizeChanged) { + if (target->size != oldSize) { emit q->sizeChanged(); } + if (target->viewport != oldViewport) { + emit q->viewportChanged(); + } if (shadowChanged) { emit q->shadowChanged(); } @@ -742,11 +755,13 @@ QPointer< SubSurfaceInterface > SurfaceInterface::subSurface() const QSize SurfaceInterface::size() const { Q_D(); - // TODO: apply transform to the buffer size - if (d->current.buffer) { - return d->current.buffer->size() / scale(); - } - return QSize(); + return d->current.size; +} + +QRectF SurfaceInterface::viewport() const +{ + Q_D(); + return d->current.viewport; } QRect SurfaceInterface::boundingRect() const @@ -1084,10 +1099,55 @@ QSizeF SurfaceInterface::Private::invertBufferTransform(const State *state, cons return transformed; } +QPointF SurfaceInterface::Private::viewportTransform(const State *state, const QPointF &point) const +{ + if (!viewportExtension) + return point; + + qreal x = state->size.width() * (point.x() - state->viewport.x()) / state->viewport.width(); + qreal y = state->size.height() * (point.y() - state->viewport.y()) / state->viewport.height(); + + return QPointF(x, y); +} + +QPointF SurfaceInterface::Private::invertViewportTransform(const State *state, const QPointF &point) const +{ + if (!viewportExtension) + return point; + + qreal x = point.x() * state->viewport.width() / state->size.width() + state->viewport.x(); + qreal y = point.y() * state->viewport.height() / state->size.height() + state->viewport.y(); + + return QPointF(x, y); +} + +QSizeF SurfaceInterface::Private::viewportTransform(const State *state, const QSizeF &size) const +{ + if (!viewportExtension) + return size; + + qreal width = size.width() * state->size.width() / state->viewport.width(); + qreal height = size.height() * state->size.height() / state->viewport.height(); + + return QSizeF(width, height); +} + +QSizeF SurfaceInterface::Private::invertViewportTransform(const State *state, const QSizeF &size) const +{ + if (!viewportExtension) + return size; + + qreal width = size.width() * state->viewport.width() / state->size.width(); + qreal height = size.height() * state->viewport.height() / state->size.height(); + + return QSize(width, height); +} + QPointF SurfaceInterface::Private::mapToBuffer(const State *state, const QPointF &point) const { QPointF transformed = point; + transformed = invertViewportTransform(state, transformed); transformed = bufferTransform(state, transformed); return transformed; @@ -1098,6 +1158,7 @@ QPointF SurfaceInterface::Private::mapFromBuffer(const State *state, const QPoin QPointF transformed = point; transformed = invertBufferTransform(state, transformed); + transformed = viewportTransform(state, transformed); return transformed; } @@ -1106,6 +1167,7 @@ QSizeF SurfaceInterface::Private::mapToBuffer(const State *state, const QSizeF & { QSizeF transformed = size; + transformed = invertViewportTransform(state, transformed); transformed = bufferTransform(state, transformed); return transformed; @@ -1116,6 +1178,7 @@ QSizeF SurfaceInterface::Private::mapFromBuffer(const State *state, const QSizeF QSizeF transformed = size; transformed = invertBufferTransform(state, transformed); + transformed = viewportTransform(state, transformed); return transformed; } diff --git a/src/wayland/surface_interface.h b/src/wayland/surface_interface.h index 10f359f161..c69cb414d2 100644 --- a/src/wayland/surface_interface.h +++ b/src/wayland/surface_interface.h @@ -185,11 +185,17 @@ public: BufferInterface *buffer(); QPoint offset() const; /** - * The size of the Surface in global compositor space. - * @see For buffer size use BufferInterface::size - * from SurfaceInterface::buffer - * @since 5.3 - **/ + * Returns the rectangle that indicates what area of the attached buffer is displayed. + * + * The size of the viewport rectangle doesn't correspond to the size of the surface. + */ + QRectF viewport() const; + /** + * Returns the current size of the surface, in surface coordinates. + * + * Note that there is no direct relationship between the surface size and the buffer size. + * In order to determine the size of the currently attached buffer, use buffer()->size(). + */ QSize size() const; /** * Returns the rectangle that bounds this surface and all of its sub-surfaces. @@ -378,7 +384,19 @@ Q_SIGNALS: void damaged(const QRegion&); void opaqueChanged(const QRegion&); void inputChanged(const QRegion&); + /** + * This signal is emitted when the scale of the attached buffer has changed. + * + * Note that the compositor has to re-compute the texture coordinates after the scale + * of the buffer has been changed. + */ void scaleChanged(qint32); + /** + * This signal is emitted when the buffer transform has changed. + * + * Note that the compositor has to re-compute the texture coordinates after the buffer + * transform has been changed. + */ void transformChanged(KWaylandServer::OutputInterface::Transform); /** * Emitted when the Surface becomes visible, i.e. a non-null buffer has been attached. @@ -389,9 +407,21 @@ Q_SIGNALS: **/ void unmapped(); /** + * This signal is emitted when the surface size has changed. + * + * Note that the compositor has to re-compute the texture coordinates after the surface + * size has been changed. + * * @since 5.3 - **/ + */ void sizeChanged(); + /** + * This signal is emitted when the viewport rectangle has changed. + * + * Note that the compositor has to re-compute the texture coordinates after the viewport + * rectangle has been changed. + */ + void viewportChanged(); /** * @since 5.4 **/ @@ -462,6 +492,7 @@ private: friend class IdleInhibitManagerUnstableV1Interface; friend class PointerConstraintsUnstableV1Interface; friend class SurfaceRole; + friend class ViewportInterface; explicit SurfaceInterface(CompositorInterface *parent, wl_resource *parentResource); class Private; diff --git a/src/wayland/surface_interface_p.h b/src/wayland/surface_interface_p.h index e15e6a1a0e..0603887663 100644 --- a/src/wayland/surface_interface_p.h +++ b/src/wayland/surface_interface_p.h @@ -19,6 +19,7 @@ namespace KWaylandServer class IdleInhibitorInterface; class SurfaceRole; +class ViewportInterface; class SurfaceInterface::Private : public Resource::Private { @@ -28,6 +29,12 @@ public: QRegion bufferDamage = QRegion(); QRegion opaque = QRegion(); QRegion input = QRegion(); + QRectF sourceGeometry = QRectF(); + QSize destinationSize = QSize(); + QRectF viewport = QRectF(); + QSize size = QSize(); + bool sourceGeometryIsSet = false; + bool destinationSizeIsSet = false; bool inputIsSet = false; bool opaqueIsSet = false; bool bufferIsSet = false; @@ -84,6 +91,10 @@ public: QPointF invertBufferTransform(const State *state, const QPointF &point) const; QSizeF bufferTransform(const State *state, const QSizeF &size) const; QSizeF invertBufferTransform(const State *state, const QSizeF &size) const; + QPointF viewportTransform(const State *state, const QPointF &point) const; + QPointF invertViewportTransform(const State *state, const QPointF &point) const; + QSizeF viewportTransform(const State *state, const QSizeF &size) const; + QSizeF invertViewportTransform(const State *state, const QSizeF &size) const; SurfaceRole *role = nullptr; @@ -105,7 +116,7 @@ public: QPointer confinedPointer; QHash outputDestroyedConnections; QVector idleInhibitors; - + ViewportInterface *viewportExtension = nullptr; SurfaceInterface *dataProxy = nullptr; private: diff --git a/src/wayland/viewporter_interface.cpp b/src/wayland/viewporter_interface.cpp new file mode 100644 index 0000000000..7833cb99bc --- /dev/null +++ b/src/wayland/viewporter_interface.cpp @@ -0,0 +1,152 @@ +/* + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "viewporter_interface.h" +#include "display.h" +#include "surface_interface_p.h" +#include "viewporter_interface_p.h" + +static const int s_version = 1; + +namespace KWaylandServer +{ + +class ViewporterInterfacePrivate : public QtWaylandServer::wp_viewporter +{ +protected: + void wp_viewporter_destroy(Resource *resource) override; + void wp_viewporter_get_viewport(Resource *resource, uint32_t id, struct ::wl_resource *surface) override; +}; + +void ViewporterInterfacePrivate::wp_viewporter_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void ViewporterInterfacePrivate::wp_viewporter_get_viewport(Resource *resource, uint32_t id, struct ::wl_resource *surface_resource) +{ + SurfaceInterface *surface = SurfaceInterface::get(surface_resource); + ViewportInterface *viewport = ViewportInterface::get(surface); + + if (viewport) { + wl_resource_post_error(resource->handle, error_viewport_exists, + "the specified surface already has a viewport"); + return; + } + + wl_resource *viewportResource = wl_resource_create(resource->client(), &wp_viewport_interface, + resource->version(), id); + + new ViewportInterface(surface, viewportResource); +} + +ViewportInterface::ViewportInterface(SurfaceInterface *surface, wl_resource *resource) + : QtWaylandServer::wp_viewport(resource) + , surface(surface) +{ + SurfaceInterface::Private *surfacePrivate = surface->d_func(); + surfacePrivate->viewportExtension = this; +} + +ViewportInterface::~ViewportInterface() +{ + if (surface) { + SurfaceInterface::Private *surfacePrivate = surface->d_func(); + surfacePrivate->viewportExtension = nullptr; + } +} + +ViewportInterface *ViewportInterface::get(SurfaceInterface *surface) +{ + return surface->d_func()->viewportExtension; +} + +void ViewportInterface::wp_viewport_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + delete this; +} + +void ViewportInterface::wp_viewport_destroy(Resource *resource) +{ + if (surface) { + SurfaceInterface::Private *surfacePrivate = surface->d_func(); + surfacePrivate->pending.sourceGeometry = QRectF(); + surfacePrivate->pending.sourceGeometryIsSet = true; + surfacePrivate->pending.destinationSize = QSize(); + surfacePrivate->pending.destinationSizeIsSet = true; + } + + wl_resource_destroy(resource->handle); +} + +void ViewportInterface::wp_viewport_set_source(Resource *resource, wl_fixed_t x_fixed, wl_fixed_t y_fixed, wl_fixed_t width_fixed, wl_fixed_t height_fixed) +{ + if (!surface) { + wl_resource_post_error(resource->handle, error_no_surface, + "the wl_surface for this viewport no longer exists"); + return; + } + + const qreal x = wl_fixed_to_double(x_fixed); + const qreal y = wl_fixed_to_double(y_fixed); + const qreal width = wl_fixed_to_double(width_fixed); + const qreal height = wl_fixed_to_double(height_fixed); + + if (x == -1 && y == -1 && width == -1 && height == -1) { + SurfaceInterface::Private *surfacePrivate = surface->d_func(); + surfacePrivate->pending.sourceGeometry = QRectF(); + surfacePrivate->pending.sourceGeometryIsSet = true; + return; + } + + if (x < 0 || y < 0 || width <= 0 || height <= 0) { + wl_resource_post_error(resource->handle, error_bad_value, "invalid source geometry"); + return; + } + + SurfaceInterface::Private *surfacePrivate = surface->d_func(); + surfacePrivate->pending.sourceGeometry = QRectF(x, y, width, height); + surfacePrivate->pending.sourceGeometryIsSet = true; +} + +void ViewportInterface::wp_viewport_set_destination(Resource *resource, int32_t width, int32_t height) +{ + if (!surface) { + wl_resource_post_error(resource->handle, error_no_surface, + "the wl_surface for this viewport no longer exists"); + return; + } + + if (width == -1 && height == -1) { + SurfaceInterface::Private *surfacePrivate = surface->d_func(); + surfacePrivate->pending.destinationSize = QSize(); + surfacePrivate->pending.destinationSizeIsSet = true; + return; + } + + if (width <= 0 || height <= 0) { + wl_resource_post_error(resource->handle, error_bad_value, "invalid destination size"); + return; + } + + SurfaceInterface::Private *surfacePrivate = surface->d_func(); + surfacePrivate->pending.destinationSize = QSize(width, height); + surfacePrivate->pending.destinationSizeIsSet = true; +} + +ViewporterInterface::ViewporterInterface(Display *display, QObject *parent) + : QObject(parent) + , d(new ViewporterInterfacePrivate) +{ + d->init(*display, s_version); +} + +ViewporterInterface::~ViewporterInterface() +{ +} + +} // namespace KWaylandServer diff --git a/src/wayland/viewporter_interface.h b/src/wayland/viewporter_interface.h new file mode 100644 index 0000000000..9ff44646ca --- /dev/null +++ b/src/wayland/viewporter_interface.h @@ -0,0 +1,41 @@ +/* + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include + +#include + +namespace KWaylandServer +{ + +class Display; +class ViewporterInterfacePrivate; + +/** + * The ViewporterInterface is an extension that allows clients to crop and scale surfaces. + * + * The ViewporterInterface extensions provides a way for Wayland clients to crop and scale their + * surfaces. This effectively breaks the direct connection between the buffer and the surface size. + * + * ViewporterInterface corresponds to the Wayland interface @c wp_viewporter. + * + * @since 5.20 + */ +class KWAYLANDSERVER_EXPORT ViewporterInterface : public QObject +{ + Q_OBJECT + +public: + explicit ViewporterInterface(Display *display, QObject *parent = nullptr); + ~ViewporterInterface() override; + +private: + QScopedPointer d; +}; + +} // namespace KWaylandServer diff --git a/src/wayland/viewporter_interface_p.h b/src/wayland/viewporter_interface_p.h new file mode 100644 index 0000000000..b9d23bb60a --- /dev/null +++ b/src/wayland/viewporter_interface_p.h @@ -0,0 +1,35 @@ +/* + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include "qwayland-server-viewporter.h" + +#include + +namespace KWaylandServer +{ + +class SurfaceInterface; + +class ViewportInterface : public QtWaylandServer::wp_viewport +{ +public: + ViewportInterface(SurfaceInterface *surface, wl_resource *resource); + ~ViewportInterface() override; + + static ViewportInterface *get(SurfaceInterface *surface); + + QPointer surface; + +protected: + void wp_viewport_destroy_resource(Resource *resource) override; + void wp_viewport_destroy(Resource *resource) override; + void wp_viewport_set_source(Resource *resource, wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) override; + void wp_viewport_set_destination(Resource *resource, int32_t width, int32_t height) override; +}; + +} // namespace KWaylandServer