From 23d3fc712cdf7fc0a4ff837082de9ba773e8605c Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Wed, 23 Mar 2022 15:35:50 +0000 Subject: Implement fractional_scale_v1 and wp_viewport This allows compositors to hint a non-integer scale to use on a window which we can hook to Qt's existing fractional scaling support. The viewport is used to communicate the relationship between buffer size and logical size to the compositor. It is a non-integer alternative to wl_buffer_scale Change-Id: I1a850f1bcd40e8d04e241e18a538b11f18bc671c Reviewed-by: Qt CI Bot Reviewed-by: David Edmundson --- src/3rdparty/protocol/fractional-scale-v1.xml | 102 +++++++++++++++++++ src/3rdparty/protocol/qt_attribution.json | 17 ++++ src/client/CMakeLists.txt | 4 + src/client/qwaylanddisplay.cpp | 17 ++++ src/client/qwaylanddisplay_p.h | 8 ++ src/client/qwaylandfractionalscale.cpp | 36 +++++++ src/client/qwaylandfractionalscale_p.h | 50 +++++++++ src/client/qwaylandviewport.cpp | 35 +++++++ src/client/qwaylandviewport_p.h | 42 ++++++++ src/client/qwaylandwindow.cpp | 61 +++++++++-- src/client/qwaylandwindow_p.h | 7 +- sync.profile | 4 + tests/auto/client/CMakeLists.txt | 2 + tests/auto/client/scaling/CMakeLists.txt | 10 ++ tests/auto/client/scaling/tst_scaling.cpp | 135 +++++++++++++++++++++++++ tests/auto/client/shared/CMakeLists.txt | 6 ++ tests/auto/client/shared/fractionalscalev1.cpp | 40 ++++++++ tests/auto/client/shared/fractionalscalev1.h | 39 +++++++ tests/auto/client/shared/mockcompositor.cpp | 3 + tests/auto/client/shared/mockcompositor.h | 4 + tests/auto/client/shared/viewport.cpp | 58 +++++++++++ tests/auto/client/shared/viewport.h | 50 +++++++++ 22 files changed, 723 insertions(+), 7 deletions(-) create mode 100644 src/3rdparty/protocol/fractional-scale-v1.xml create mode 100644 src/client/qwaylandfractionalscale.cpp create mode 100644 src/client/qwaylandfractionalscale_p.h create mode 100644 src/client/qwaylandviewport.cpp create mode 100644 src/client/qwaylandviewport_p.h create mode 100644 tests/auto/client/scaling/CMakeLists.txt create mode 100644 tests/auto/client/scaling/tst_scaling.cpp create mode 100644 tests/auto/client/shared/fractionalscalev1.cpp create mode 100644 tests/auto/client/shared/fractionalscalev1.h create mode 100644 tests/auto/client/shared/viewport.cpp create mode 100644 tests/auto/client/shared/viewport.h diff --git a/src/3rdparty/protocol/fractional-scale-v1.xml b/src/3rdparty/protocol/fractional-scale-v1.xml new file mode 100644 index 00000000..350bfc01 --- /dev/null +++ b/src/3rdparty/protocol/fractional-scale-v1.xml @@ -0,0 +1,102 @@ + + + + Copyright © 2022 Kenny Levinsen + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows a compositor to suggest for surfaces to render at + fractional scales. + + A client can submit scaled content by utilizing wp_viewport. This is done by + creating a wp_viewport object for the surface and setting the destination + rectangle to the surface size before the scale factor is applied. + + The buffer size is calculated by multiplying the surface size by the + intended scale. + + The wl_surface buffer scale should remain set to 1. + + If a surface has a surface-local size of 100 px by 50 px and wishes to + submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should + be used and the wp_viewport destination rectangle should be 100 px by 50 px. + + For toplevel surfaces, the size is rounded halfway away from zero. The + rounding algorithm for subsurface position and size is not defined. + + + + + A global interface for requesting surfaces to use fractional scales. + + + + + Informs the server that the client will not be using this protocol + object anymore. This does not affect any other objects, + wp_fractional_scale_v1 objects included. + + + + + + + + + + Create an add-on object for the the wl_surface to let the compositor + request fractional scales. If the given wl_surface already has a + wp_fractional_scale_v1 object associated, the fractional_scale_exists + protocol error is raised. + + + + + + + + + An additional interface to a wl_surface object which allows the compositor + to inform the client of the preferred scale. + + + + + Destroy the fractional scale object. When this object is destroyed, + preferred_scale events will no longer be sent. + + + + + + Notification of a new preferred scale for this surface that the + compositor suggests that the client should use. + + The sent scale is the numerator of a fraction with a denominator of 120. + + + + + diff --git a/src/3rdparty/protocol/qt_attribution.json b/src/3rdparty/protocol/qt_attribution.json index 7b81d551..e30cb1b6 100644 --- a/src/3rdparty/protocol/qt_attribution.json +++ b/src/3rdparty/protocol/qt_attribution.json @@ -318,5 +318,22 @@ "License": "MIT License", "LicenseFile": "MIT_LICENSE.txt", "Copyright": "Copyright © 2015-2016 Red Hat Inc." + }, + + { + "Id": "fractional-scale-v1", + "Name": "Wayland Fractional Scale Protocol", + "QDocModule": "qtwaylandcompositor", + "QtUsage": "Used in the Qt Wayland platform plugin", + "Files": "fractional-scale-v1.xml", + + "Description": "Send a preferred scale to different clients", + "Homepage": "https://wayland.freedesktop.org", + "Version": "1", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/raw/1.31/unstable/fractional-scale/fractional-scale-v1.xml", + "LicenseId": "MIT", + "License": "MIT License", + "LicenseFile": "MIT_LICENSE.txt", + "Copyright": "Copyright © 2022 Kenny Levinsen" } ] diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 820c9ead..47312010 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -32,6 +32,7 @@ qt_internal_add_module(WaylandClient qwaylanddecorationplugin.cpp qwaylanddecorationplugin_p.h qwaylanddisplay.cpp qwaylanddisplay_p.h qwaylandextendedsurface.cpp qwaylandextendedsurface_p.h + qwaylandfractionalscale.cpp qwaylandfractionalscale_p.h qwaylandinputcontext.cpp qwaylandinputcontext_p.h qwaylandtextinputv1.cpp qwaylandtextinputv1_p.h qwaylandtextinputv2.cpp qwaylandtextinputv2_p.h @@ -50,6 +51,7 @@ qt_internal_add_module(WaylandClient qwaylandsubsurface.cpp qwaylandsubsurface_p.h qwaylandsurface.cpp qwaylandsurface_p.h qwaylandtouch.cpp qwaylandtouch_p.h + qwaylandviewport.cpp qwaylandviewport_p.h qwaylandwindow.cpp qwaylandwindow_p.h qwaylandwindowmanagerintegration.cpp qwaylandwindowmanagerintegration_p.h shellintegration/qwaylandclientshellapi_p.h @@ -88,6 +90,8 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wayland.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wp-primary-selection-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-output-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/fractional-scale-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/viewporter.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-key-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-text-input-method-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-windowmanager.xml diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index 18eb71c3..52ea6492 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -50,6 +50,8 @@ #include #include #include +#include +#include #include @@ -294,6 +296,17 @@ struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion) return mSubCompositor->get_subsurface(window->wlSurface(), parent->wlSurface()); } +::wp_viewport *QWaylandDisplay::createViewport(QWaylandWindow *window) +{ + if (!mViewporter) { + qCWarning(lcQpaWayland) << "Can't create wp_viewport, not supported by the compositor."; + return nullptr; + } + + Q_ASSERT(window->wlSurface()); + return mViewporter->get_viewport(window->wlSurface()); +} + QWaylandShellIntegration *QWaylandDisplay::shellIntegration() const { return mWaylandIntegration->shellIntegration(); @@ -597,6 +610,10 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin mXdgOutputManager.reset(new QWaylandXdgOutputManagerV1(this, id, version)); for (auto *screen : std::as_const(mWaitingScreens)) screen->initXdgOutput(xdgOutputManager()); + } else if (interface == QLatin1String(QtWayland::wp_fractional_scale_manager_v1::interface()->name)) { + mFractionalScaleManager.reset(new QtWayland::wp_fractional_scale_manager_v1(registry, id, 1)); + } else if (interface == QLatin1String("wp_viewporter")) { + mViewporter.reset(new QtWayland::wp_viewporter(registry, id, qMin(1u, version))); } mGlobals.append(RegistryGlobal(id, interface, version, registry)); diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h index 473016f1..640f33b8 100644 --- a/src/client/qwaylanddisplay_p.h +++ b/src/client/qwaylanddisplay_p.h @@ -36,6 +36,7 @@ #endif struct wl_cursor_image; +struct wp_viewport; QT_BEGIN_NAMESPACE @@ -50,6 +51,8 @@ namespace QtWayland { class zwp_text_input_manager_v2; class zwp_text_input_manager_v4; class qt_text_input_method_manager_v1; + class wp_viewporter; + class wp_fractional_scale_manager_v1; } namespace QtWaylandClient { @@ -110,6 +113,7 @@ public: struct wl_surface *createSurface(void *handle); struct ::wl_region *createRegion(const QRegion &qregion); struct ::wl_subsurface *createSubSurface(QWaylandWindow *window, QWaylandWindow *parent); + struct ::wp_viewport *createViewport(QWaylandWindow *window); QWaylandShellIntegration *shellIntegration() const; QWaylandClientBufferIntegration *clientBufferIntegration() const; @@ -146,6 +150,8 @@ public: QtWayland::zwp_text_input_manager_v4 *textInputManagerv4() const { return mTextInputManagerv4.data(); } QWaylandHardwareIntegration *hardwareIntegration() const { return mHardwareIntegration.data(); } QWaylandXdgOutputManagerV1 *xdgOutputManager() const { return mXdgOutputManager.data(); } + QtWayland::wp_fractional_scale_manager_v1 *fractionalScaleManager() const { return mFractionalScaleManager.data(); } + QtWayland::wp_viewporter *viewporter() const { return mViewporter.data(); } struct RegistryGlobal { uint32_t id; @@ -265,6 +271,8 @@ private: QScopedPointer mTextInputManagerv4; QScopedPointer mHardwareIntegration; QScopedPointer mXdgOutputManager; + QScopedPointer mViewporter; + QScopedPointer mFractionalScaleManager; int mFd = -1; int mWritableNotificationFd = -1; QList mGlobals; diff --git a/src/client/qwaylandfractionalscale.cpp b/src/client/qwaylandfractionalscale.cpp new file mode 100644 index 00000000..324a0b72 --- /dev/null +++ b/src/client/qwaylandfractionalscale.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandfractionalscale_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandFractionalScale::QWaylandFractionalScale(struct ::wp_fractional_scale_v1 *object) + : QtWayland::wp_fractional_scale_v1(object) +{} + + +QWaylandFractionalScale::~QWaylandFractionalScale() +{ + destroy(); +} + +qreal QWaylandFractionalScale::preferredScale() const +{ + return mPreferredScale; +} + +void QWaylandFractionalScale::wp_fractional_scale_v1_preferred_scale(uint scale) +{ + qreal preferredScale = scale / 120.0; // hardcoded denominator determined in the spec + if (preferredScale != mPreferredScale) { + mPreferredScale = preferredScale; + Q_EMIT preferredScaleChanged(); + } +} + +} + +QT_END_NAMESPACE diff --git a/src/client/qwaylandfractionalscale_p.h b/src/client/qwaylandfractionalscale_p.h new file mode 100644 index 00000000..0483eb33 --- /dev/null +++ b/src/client/qwaylandfractionalscale_p.h @@ -0,0 +1,50 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDFRACTIONALSCALE_P_H +#define QWAYLANDFRACTIONALSCALE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandFractionalScale : public QObject, public QtWayland::wp_fractional_scale_v1 +{ + Q_OBJECT +public: + explicit QWaylandFractionalScale(struct ::wp_fractional_scale_v1 *object); + ~QWaylandFractionalScale(); + + qreal preferredScale() const; + +Q_SIGNALS: + void preferredScaleChanged(); + +protected: + void wp_fractional_scale_v1_preferred_scale(uint scale) override; + +private: + qreal mPreferredScale = 1.0; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/client/qwaylandviewport.cpp b/src/client/qwaylandviewport.cpp new file mode 100644 index 00000000..3252718c --- /dev/null +++ b/src/client/qwaylandviewport.cpp @@ -0,0 +1,35 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwaylandviewport_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +QWaylandViewport::QWaylandViewport(::wp_viewport *viewport) + : QtWayland::wp_viewport(viewport) +{ +} + +QWaylandViewport::~QWaylandViewport() +{ + destroy(); +} + +void QWaylandViewport::setSource(const QRectF &source) +{ + set_source(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 QWaylandViewport::setDestination(const QSize &destination) +{ + set_destination(destination.width(), destination.height()); +} + +} + +QT_END_NAMESPACE diff --git a/src/client/qwaylandviewport_p.h b/src/client/qwaylandviewport_p.h new file mode 100644 index 00000000..e1dfeb3a --- /dev/null +++ b/src/client/qwaylandviewport_p.h @@ -0,0 +1,42 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QWAYLANDVIEWPORT_P_H +#define QWAYLANDVIEWPORT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + +class QWaylandViewport : public QtWayland::wp_viewport +{ +public: + explicit QWaylandViewport(::wp_viewport *viewport); + ~QWaylandViewport() override; + + void setSource(const QRectF &source); + void setDestination(const QSize &destination); + +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDVIEWPORT_P_H diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index f4d49c84..56b9af28 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -7,6 +7,7 @@ #include "qwaylanddisplay_p.h" #include "qwaylandsurface_p.h" #include "qwaylandinputdevice_p.h" +#include "qwaylandfractionalscale_p.h" #include "qwaylandscreen_p.h" #include "qwaylandshellsurface_p.h" #include "qwaylandsubsurface_p.h" @@ -16,6 +17,7 @@ #include "qwaylanddecorationfactory_p.h" #include "qwaylandshmbackingstore_p.h" #include "qwaylandshellintegration_p.h" +#include "qwaylandviewport_p.h" #include #include @@ -29,6 +31,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE namespace QtWaylandClient { @@ -91,6 +95,26 @@ void QWaylandWindow::initWindow() initializeWlSurface(); } + if (mDisplay->fractionalScaleManager() && qApp->highDpiScaleFactorRoundingPolicy() == Qt::HighDpiScaleFactorRoundingPolicy::PassThrough) { + mFractionalScale.reset(new QWaylandFractionalScale(mDisplay->fractionalScaleManager()->get_fractional_scale(mSurface->object()))); + + mScale = mFractionalScale->preferredScale(); + connect(mFractionalScale.data(), &QWaylandFractionalScale::preferredScaleChanged, this, [this]() { + if (mScale == mFractionalScale->preferredScale()) { + return; + } + mScale = mFractionalScale->preferredScale(); + ensureSize(); + if (mViewport) + updateViewport(); + if (isExposed()) { + // redraw at the new DPR + window()->requestUpdate(); + sendExposeEvent(QRect(QPoint(), geometry().size())); + } + }); + } + if (shouldCreateSubSurface()) { Q_ASSERT(!mSubSurfaceWindow); @@ -146,11 +170,17 @@ void QWaylandWindow::initWindow() } } + if (display()->viewporter() && !window()->flags().testFlag(Qt::BypassWindowManagerHint)) { + mViewport.reset(new QWaylandViewport(display()->createViewport(this))); + } + // Enable high-dpi rendering. Scale() returns the screen scale factor and will // typically be integer 1 (normal-dpi) or 2 (high-dpi). Call set_buffer_scale() // to inform the compositor that high-resolution buffers will be provided. - if (mSurface->version() >= 3) - mSurface->set_buffer_scale(mScale); + if (mViewport) + updateViewport(); + else if (mSurface->version() >= 3) + mSurface->set_buffer_scale(std::ceil(scale())); setWindowFlags(window()->flags()); QRect geometry = windowGeometry(); @@ -220,6 +250,8 @@ void QWaylandWindow::reset() mShellSurface = nullptr; delete mSubSurfaceWindow; mSubSurfaceWindow = nullptr; + mViewport.reset(); + mFractionalScale.reset(); if (mSurface) { emit wlSurfaceDestroyed(); @@ -322,6 +354,8 @@ void QWaylandWindow::setGeometry_helper(const QRect &rect) QPlatformWindow::setGeometry(QRect(rect.x(), rect.y(), qBound(minimum.width(), rect.width(), maximum.width()), qBound(minimum.height(), rect.height(), maximum.height()))); + if (mViewport) + updateViewport(); if (mSubSurfaceWindow) { QMargins m = static_cast(QPlatformWindow::parent())->clientSideMargins(); @@ -370,6 +404,12 @@ void QWaylandWindow::setGeometry(const QRect &r) setOpaqueArea(QRect(QPoint(0, 0), rect.size())); } +void QWaylandWindow::updateViewport() +{ + if (!surfaceSize().isEmpty()) + mViewport->setDestination(surfaceSize()); +} + void QWaylandWindow::setGeometryFromApplyConfigure(const QPoint &globalPosition, const QSize &sizeWithMargins) { QMargins margins = clientSideMargins(); @@ -1242,6 +1282,7 @@ void QWaylandWindow::handleScreensChanged() return; QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); + mLastReportedScreen = newScreen; if (fixedToplevelPositions && !QPlatformWindow::parent() && window()->type() != Qt::Popup && window()->type() != Qt::ToolTip @@ -1251,11 +1292,19 @@ void QWaylandWindow::handleScreensChanged() setGeometry(geometry); } - int scale = newScreen->isPlaceholder() ? 1 : static_cast(newScreen)->scale(); + if (mFractionalScale) + return; + + int scale = mLastReportedScreen->isPlaceholder() ? 1 : static_cast(mLastReportedScreen)->scale(); + if (scale != mScale) { mScale = scale; - if (mSurface && mSurface->version() >= 3) - mSurface->set_buffer_scale(mScale); + if (mSurface) { + if (mViewport) + updateViewport(); + else if (mSurface->version() >= 3) + mSurface->set_buffer_scale(std::ceil(mScale)); + } ensureSize(); } } @@ -1558,4 +1607,4 @@ void QWaylandWindow::closeChildPopups() { QT_END_NAMESPACE -#include "moc_qwaylandwindow_p.cpp" +#include "qwaylandwindow.moc" diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index 0f8c5515..6531606a 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -53,6 +53,8 @@ class QWaylandPointerEvent; class QWaylandPointerGestureSwipeEvent; class QWaylandPointerGesturePinchEvent; class QWaylandSurface; +class QWaylandFractionalScale; +class QWaylandViewport; class Q_WAYLANDCLIENT_EXPORT QWaylandWindow : public QObject, public QPlatformWindow { @@ -233,6 +235,8 @@ protected: // mSurface can be written by the main thread. Other threads should claim a read lock for access mutable QReadWriteLock mSurfaceLock; QScopedPointer mSurface; + QScopedPointer mFractionalScale; + QScopedPointer mViewport; QWaylandShellSurface *mShellSurface = nullptr; QWaylandSubSurface *mSubSurfaceWindow = nullptr; @@ -284,7 +288,7 @@ protected: bool mSentInitialResize = false; QPoint mOffset; - int mScale = 1; + qreal mScale = 1; QPlatformScreen *mLastReportedScreen = nullptr; QIcon mWindowIcon; @@ -314,6 +318,7 @@ private: QPlatformScreen *calculateScreenFromSurfaceEvents() const; void setOpaqueArea(const QRegion &opaqueArea); bool isOpaque() const; + void updateViewport(); void handleMouseEventWithDecoration(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e); void handleScreensChanged(); diff --git a/sync.profile b/sync.profile index c78827c2..766db22e 100644 --- a/sync.profile +++ b/sync.profile @@ -35,8 +35,10 @@ "^qwayland-text-input-unstable-v4-wip.h", "^qwayland-qt-text-input-method-unstable-v1.h", "^qwayland-touch-extension.h", + "^qwayland-viewporter.h", "^qwayland-wayland.h", "^qwayland-wp-primary-selection-unstable-v1.h", + "^qwayland-fractional-scale-v1.h", "^qwayland-xdg-output-unstable-v1.h", "^wayland-hardware-integration-client-protocol.h", "^wayland-pointer-gestures-unstable-v1-client-protocol.h", @@ -50,7 +52,9 @@ "^wayland-text-input-unstable-v4-wip-client-protocol.h", "^wayland-qt-text-input-method-unstable-v1-client-protocol.h", "^wayland-touch-extension-client-protocol.h", + "^wayland-viewporter-client-protocol.h", "^wayland-wayland-client-protocol.h", + "^wayland-fractional-scale-v1-client-protocol.h", "^wayland-wp-primary-selection-unstable-v1-client-protocol.h", "^wayland-xdg-output-unstable-v1-client-protocol.h", ], diff --git a/tests/auto/client/CMakeLists.txt b/tests/auto/client/CMakeLists.txt index 749e6b83..44cf3271 100644 --- a/tests/auto/client/CMakeLists.txt +++ b/tests/auto/client/CMakeLists.txt @@ -25,8 +25,10 @@ if (NOT WEBOS) add_subdirectory(xdgdecorationv1) add_subdirectory(xdgoutput) add_subdirectory(xdgshell) + add_subdirectory(scaling) endif() add_subdirectory(multithreaded) + if(QT_FEATURE_im) add_subdirectory(inputcontext) endif() diff --git a/tests/auto/client/scaling/CMakeLists.txt b/tests/auto/client/scaling/CMakeLists.txt new file mode 100644 index 00000000..a93f0c57 --- /dev/null +++ b/tests/auto/client/scaling/CMakeLists.txt @@ -0,0 +1,10 @@ +##################################################################### +## tst_scaling Test: +##################################################################### + +qt_internal_add_test(tst_scaling + SOURCES + tst_scaling.cpp + PUBLIC_LIBRARIES + SharedClientTest +) diff --git a/tests/auto/client/scaling/tst_scaling.cpp b/tests/auto/client/scaling/tst_scaling.cpp new file mode 100644 index 00000000..bceea92d --- /dev/null +++ b/tests/auto/client/scaling/tst_scaling.cpp @@ -0,0 +1,135 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "mockcompositor.h" +#include +#include +#include +#include +#include + +using namespace MockCompositor; + +class tst_scaling : public QObject, private DefaultCompositor +{ + Q_OBJECT +private slots: + void init(); + void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } + void scaledWindow(); + void roundingPolicy_data(); + void roundingPolicy(); + +}; + +void tst_scaling::init() +{ + setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1); +} + +void tst_scaling::scaledWindow() +{ + QRasterWindow window; + window.resize(100, 100); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QSignalSpy configureSpy(exec([=] { return xdgSurface(); }), &XdgSurface::configureCommitted); + QSignalSpy surfaceCommitSpy(exec([=] { return surface(); }), &Surface::commit); + + const QSize configureSize(100, 100); + + exec([=] { + QVERIFY(fractionalScale()); + fractionalScale()->send_preferred_scale(1.5 * 120); + xdgToplevel()->sendCompleteConfigure(configureSize); + }); + + QTRY_COMPARE(configureSpy.count(), 1); + QCOMPARE(window.devicePixelRatio(), 1.5); + + exec([=] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), QSize(150, 150)); + Viewport *vp = viewport(); + QVERIFY(vp); + QCOMPARE(vp->m_destination, QSize(100, 100)); + }); + + // resize the window + window.resize(200,200); + QCOMPARE(window.size(), QSize(200,200)); + + QVERIFY(surfaceCommitSpy.wait()); + exec([=] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), QSize(300, 300)); + Viewport *vp = viewport(); + QVERIFY(vp); + QCOMPARE(vp->m_destination, QSize(200, 200)); + }); + + // dynamic scale change + exec([=] { + QVERIFY(fractionalScale()); + fractionalScale()->send_preferred_scale(2.5 * 120); + }); + QTRY_COMPARE(window.devicePixelRatio(), 2.5); + QCOMPARE(window.size(), QSize(200,200)); + + QVERIFY(surfaceCommitSpy.wait()); + exec([=] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), QSize(500, 500)); + Viewport *vp = viewport(); + QVERIFY(vp); + QCOMPARE(vp->m_destination, QSize(200, 200)); + }); +} + +void tst_scaling::roundingPolicy_data() +{ + QTest::addColumn("windowSize"); + QTest::addColumn("scale"); + QTest::addColumn("expectedBufferSize"); + + QTest::newRow("1.125 - round down") << QSize(10, 10) << 1.125 << QSize(11,11); + QTest::newRow("1.25 - round up") << QSize(10, 10) << 1.25 << QSize(13,13); + QTest::newRow("1.5 - don't round") << QSize(10, 10) << 1.5 << QSize(15,15); +} + +void tst_scaling::roundingPolicy() +{ + QFETCH(QSize, windowSize); + QFETCH(qreal, scale); + QFETCH(QSize, expectedBufferSize); + + + QRasterWindow window; + window.resize(windowSize); + window.show(); + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); + + QSignalSpy surfaceCommitSpy(exec([=] { return surface(); }), &Surface::commit); + + exec([=] { + QVERIFY(fractionalScale()); + fractionalScale()->send_preferred_scale(scale * 120); + xdgToplevel()->sendCompleteConfigure(); + }); + + QVERIFY(surfaceCommitSpy.wait()); + + exec([=] { + Buffer *buffer = xdgToplevel()->surface()->m_committed.buffer; + QVERIFY(buffer); + QCOMPARE(buffer->size(), expectedBufferSize); + }); +} + + +QCOMPOSITOR_TEST_MAIN(tst_scaling) +#include "tst_scaling.moc" diff --git a/tests/auto/client/shared/CMakeLists.txt b/tests/auto/client/shared/CMakeLists.txt index 653927f0..8112ffb7 100644 --- a/tests/auto/client/shared/CMakeLists.txt +++ b/tests/auto/client/shared/CMakeLists.txt @@ -11,9 +11,11 @@ qt_manual_moc(moc_files corecompositor.h datadevice.h fullscreenshellv1.h + fractionalscalev1.h iviapplication.h textinput.h qttextinput.h + viewport.h xdgoutputv1.h xdgshell.h ) @@ -24,12 +26,14 @@ add_library(SharedClientTest coreprotocol.cpp coreprotocol.h datadevice.cpp datadevice.h fullscreenshellv1.cpp fullscreenshellv1.h + fractionalscalev1.cpp fractionalscalev1.h iviapplication.cpp iviapplication.h mockcompositor.cpp mockcompositor.h textinput.cpp textinput.h qttextinput.cpp qttextinput.h xdgoutputv1.cpp xdgoutputv1.h xdgshell.cpp xdgshell.h + viewport.cpp viewport.h ${moc_files} ) @@ -41,6 +45,8 @@ qt6_generate_wayland_protocol_server_sources(SharedClientTest ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/tablet-unstable-v2.xml ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/text-input-unstable-v2.xml ${PROJECT_SOURCE_DIR}/src/extensions/qt-text-input-method-unstable-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/fractional-scale-v1.xml + ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/viewporter.xml ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/wayland.xml ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/xdg-decoration-unstable-v1.xml ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/xdg-output-unstable-v1.xml diff --git a/tests/auto/client/shared/fractionalscalev1.cpp b/tests/auto/client/shared/fractionalscalev1.cpp new file mode 100644 index 00000000..5548c3d6 --- /dev/null +++ b/tests/auto/client/shared/fractionalscalev1.cpp @@ -0,0 +1,40 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "fractionalscalev1.h" + +namespace MockCompositor { + +FractionalScaleManager::FractionalScaleManager(CoreCompositor *compositor, int version) + : QtWaylandServer::wp_fractional_scale_manager_v1(compositor->m_display, version) +{ +} + +void FractionalScaleManager::wp_fractional_scale_manager_v1_get_fractional_scale(Resource *resource, uint32_t id, wl_resource *surface) +{ + auto *s = fromResource(surface); + auto *scaler = new FractionalScale(s, resource->client(), id, resource->version()); + connect(scaler, &QObject::destroyed, this, [this, scaler]() { + m_fractionalScales.removeOne(scaler); + }); + m_fractionalScales << scaler; +} + +FractionalScale::FractionalScale(Surface *surface, wl_client *client, int id, int version) + : QtWaylandServer::wp_fractional_scale_v1(client, id, version) + , m_surface(surface) +{ +} + +void FractionalScale::wp_fractional_scale_v1_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + delete this; +} + +void FractionalScale::wp_fractional_scale_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +} diff --git a/tests/auto/client/shared/fractionalscalev1.h b/tests/auto/client/shared/fractionalscalev1.h new file mode 100644 index 00000000..fd5483e9 --- /dev/null +++ b/tests/auto/client/shared/fractionalscalev1.h @@ -0,0 +1,39 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef MOCKCOMPOSITOR_FRACTIONALSCALE_H +#define MOCKCOMPOSITOR_FRACTIONALSCALE_H + +#include "coreprotocol.h" +#include + +namespace MockCompositor { + +class FractionalScale; + +class FractionalScaleManager : public Global, public QtWaylandServer::wp_fractional_scale_manager_v1 +{ + Q_OBJECT +public: + explicit FractionalScaleManager(CoreCompositor *compositor, int version = 1); + QList m_fractionalScales; + +protected: + void wp_fractional_scale_manager_v1_get_fractional_scale(Resource *resource, uint32_t id, wl_resource *surface) override; +}; + +class FractionalScale : public QObject, public QtWaylandServer::wp_fractional_scale_v1 +{ + Q_OBJECT +public: + explicit FractionalScale(Surface *surface, wl_client *client, int id, int version); + Surface *m_surface; + +protected: + void wp_fractional_scale_v1_destroy_resource(Resource *resource) override; + void wp_fractional_scale_v1_destroy(Resource *resource) override; +}; + +} + +#endif diff --git a/tests/auto/client/shared/mockcompositor.cpp b/tests/auto/client/shared/mockcompositor.cpp index 1266f776..71f3775a 100644 --- a/tests/auto/client/shared/mockcompositor.cpp +++ b/tests/auto/client/shared/mockcompositor.cpp @@ -21,6 +21,9 @@ DefaultCompositor::DefaultCompositor(CompositorType t) add(Seat::capability_pointer | Seat::capability_keyboard | Seat::capability_touch); add(); add(); + add(); + add(); + switch (m_type) { case CompositorType::Default: add(); diff --git a/tests/auto/client/shared/mockcompositor.h b/tests/auto/client/shared/mockcompositor.h index 3b70a430..6803a646 100644 --- a/tests/auto/client/shared/mockcompositor.h +++ b/tests/auto/client/shared/mockcompositor.h @@ -11,6 +11,8 @@ #include "fullscreenshellv1.h" #include "iviapplication.h" #include "xdgshell.h" +#include "viewport.h" +#include "fractionalscalev1.h" #include @@ -46,6 +48,8 @@ public: Keyboard *keyboard() { auto *seat = get(); Q_ASSERT(seat); return seat->m_keyboard; } FullScreenShellV1 *fullScreenShellV1() {return get();}; IviSurface *iviSurface(int i = 0) { return get()->m_iviSurfaces.value(i, nullptr); } + FractionalScale *fractionalScale(int i = 0) {return get()->m_fractionalScales.value(i, nullptr); } + Viewport *viewport(int i = 0) {return get()->m_viewports.value(i, nullptr); } uint sendXdgShellPing(); void xdgPingAndWaitForPong(); diff --git a/tests/auto/client/shared/viewport.cpp b/tests/auto/client/shared/viewport.cpp new file mode 100644 index 00000000..c1e763fe --- /dev/null +++ b/tests/auto/client/shared/viewport.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "viewport.h" + +namespace MockCompositor { + +Viewporter::Viewporter(CoreCompositor *compositor, int version) + : QtWaylandServer::wp_viewporter(compositor->m_display, version) +{ +} + +void Viewporter::wp_viewporter_get_viewport(Resource *resource, uint32_t id, wl_resource *surface) +{ + auto *s = fromResource(surface); + auto *viewport = new Viewport(s, resource->client(), id, resource->version()); + connect(viewport, &QObject::destroyed, this, [this, viewport]() { + m_viewports.removeOne(viewport); + }); + m_viewports << viewport; +} + +Viewport::Viewport(Surface *surface, wl_client *client, int id, int version) + : QtWaylandServer::wp_viewport(client, id, version) + , m_surface(surface) +{ +} + +void Viewport::wp_viewport_set_source(Resource *resource, wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) +{ + Q_UNUSED(resource) + m_source = QRectF(wl_fixed_to_double(x), + wl_fixed_to_double(y), + wl_fixed_to_double(width), + wl_fixed_to_double(height)); + Q_EMIT sourceChanged(); +} + +void Viewport::wp_viewport_set_destination(Resource *resource, int32_t width, int32_t height) +{ + Q_UNUSED(resource) + + m_destination = QSize(width, height); + Q_EMIT destinationChanged(); +} + +void Viewport::wp_viewport_destroy_resource(Resource *resource) +{ + Q_UNUSED(resource) + delete this; +} + +void Viewport::wp_viewport_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +} diff --git a/tests/auto/client/shared/viewport.h b/tests/auto/client/shared/viewport.h new file mode 100644 index 00000000..018e28e1 --- /dev/null +++ b/tests/auto/client/shared/viewport.h @@ -0,0 +1,50 @@ +// Copyright (C) 2022 David Edmundson +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef MOCKCOMPOSITOR_VIEWPORT_H +#define MOCKCOMPOSITOR_VIEWPORT_H + +#include "coreprotocol.h" +#include + +namespace MockCompositor { + +class Viewport; + +class Viewporter : public Global, public QtWaylandServer::wp_viewporter +{ + Q_OBJECT +public: + explicit Viewporter(CoreCompositor *compositor, int version = 1); + QList m_viewports; + +protected: + void wp_viewporter_get_viewport(Resource *resource, uint32_t id, wl_resource *surface) override; +}; + +class Viewport : public QObject, public QtWaylandServer::wp_viewport +{ + Q_OBJECT +public: + explicit Viewport(Surface *surface, wl_client *client, int id, int version); + + QRectF m_source; + QSize m_destination; + + Surface* m_surface; + +Q_SIGNALS: + void sourceChanged(); + void destinationChanged(); + +protected: + 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; + + void wp_viewport_destroy_resource(Resource *resource) override; + void wp_viewport_destroy(Resource *resource) override; +}; + +} + +#endif -- cgit v1.2.1