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 <qt_ci_bot@qt-project.org>
Reviewed-by: David Edmundson <davidedmundson@kde.org>
This commit is contained in:
David Edmundson 2022-03-23 15:35:50 +00:00
parent f45244946c
commit d62c5761e8
21 changed files with 719 additions and 7 deletions

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="fractional_scale_v1">
<copyright>
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.
</copyright>
<description summary="Protocol for requesting fractional surface scales">
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.
</description>
<interface name="wp_fractional_scale_manager_v1" version="1">
<description summary="fractional surface scale information">
A global interface for requesting surfaces to use fractional scales.
</description>
<request name="destroy" type="destructor">
<description summary="unbind the fractional surface scale interface">
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.
</description>
</request>
<enum name="error">
<entry name="fractional_scale_exists" value="0"
summary="the surface already has a fractional_scale object associated"/>
</enum>
<request name="get_fractional_scale">
<description summary="extend surface interface for scale information">
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.
</description>
<arg name="id" type="new_id" interface="wp_fractional_scale_v1"
summary="the new surface scale info interface id"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface"/>
</request>
</interface>
<interface name="wp_fractional_scale_v1" version="1">
<description summary="fractional scale interface to a wl_surface">
An additional interface to a wl_surface object which allows the compositor
to inform the client of the preferred scale.
</description>
<request name="destroy" type="destructor">
<description summary="remove surface scale information for surface">
Destroy the fractional scale object. When this object is destroyed,
preferred_scale events will no longer be sent.
</description>
</request>
<event name="preferred_scale">
<description summary="notify of new preferred scale">
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.
</description>
<arg name="scale" type="uint" summary="the new preferred scale"/>
</event>
</interface>
</protocol>

View File

@ -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"
}
]

View File

@ -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

View File

@ -50,6 +50,8 @@
#include <QtWaylandClient/private/qwayland-text-input-unstable-v4-wip.h>
#include <QtWaylandClient/private/qwayland-wp-primary-selection-unstable-v1.h>
#include <QtWaylandClient/private/qwayland-qt-text-input-method-unstable-v1.h>
#include <QtWaylandClient/private/qwayland-fractional-scale-v1.h>
#include <QtWaylandClient/private/qwayland-viewporter.h>
#include <QtCore/private/qcore_unix_p.h>
@ -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));

View File

@ -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<QtWayland::zwp_text_input_manager_v4> mTextInputManagerv4;
QScopedPointer<QWaylandHardwareIntegration> mHardwareIntegration;
QScopedPointer<QWaylandXdgOutputManagerV1> mXdgOutputManager;
QScopedPointer<QtWayland::wp_viewporter> mViewporter;
QScopedPointer<QtWayland::wp_fractional_scale_manager_v1> mFractionalScaleManager;
int mFd = -1;
int mWritableNotificationFd = -1;
QList<RegistryGlobal> mGlobals;

View File

@ -0,0 +1,36 @@
// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org>
// 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

View File

@ -0,0 +1,50 @@
// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org>
// 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 <QtWaylandClient/private/qwayland-fractional-scale-v1.h>
#include <QtWaylandClient/qtwaylandclientglobal.h>
#include <QObject>
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

View File

@ -0,0 +1,35 @@
// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org>
// 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

View File

@ -0,0 +1,42 @@
// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org>
// 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 <QtWaylandClient/private/qwayland-viewporter.h>
#include <QtWaylandClient/qtwaylandclientglobal.h>
#include <QRect>
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

View File

@ -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 <QtCore/QFileInfo>
#include <QtCore/QPointer>
@ -29,6 +31,8 @@
#include <QtCore/QDebug>
#include <QtCore/QThread>
#include <QtWaylandClient/private/qwayland-fractional-scale-v1.h>
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<QWaylandWindow *>(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<QWaylandScreen *>(newScreen)->scale();
if (mFractionalScale)
return;
int scale = mLastReportedScreen->isPlaceholder() ? 1 : static_cast<QWaylandScreen *>(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"

View File

@ -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<QWaylandSurface> mSurface;
QScopedPointer<QWaylandFractionalScale> mFractionalScale;
QScopedPointer<QWaylandViewport> 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();

View File

@ -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()

View File

@ -0,0 +1,10 @@
#####################################################################
## tst_scaling Test:
#####################################################################
qt_internal_add_test(tst_scaling
SOURCES
tst_scaling.cpp
PUBLIC_LIBRARIES
SharedClientTest
)

View File

@ -0,0 +1,135 @@
// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org>
// 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 <QtOpenGL/QOpenGLWindow>
#include <QtGui/QRasterWindow>
#include <QtGui/qpa/qplatformnativeinterface.h>
#include <QtWaylandClient/private/wayland-wayland-client-protocol.h>
#include <QtWaylandClient/private/qwaylandwindow_p.h>
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<QSize>("windowSize");
QTest::addColumn<qreal>("scale");
QTest::addColumn<QSize>("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"

View File

@ -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

View File

@ -0,0 +1,40 @@
// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org>
// 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>(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);
}
}

View File

@ -0,0 +1,39 @@
// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org>
// 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 <qwayland-server-fractional-scale-v1.h>
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<FractionalScale *> 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

View File

@ -21,6 +21,9 @@ DefaultCompositor::DefaultCompositor(CompositorType t)
add<Seat>(Seat::capability_pointer | Seat::capability_keyboard | Seat::capability_touch);
add<WlShell>();
add<XdgWmBase>();
add<FractionalScaleManager>();
add<Viewporter>();
switch (m_type) {
case CompositorType::Default:
add<Shm>();

View File

@ -11,6 +11,8 @@
#include "fullscreenshellv1.h"
#include "iviapplication.h"
#include "xdgshell.h"
#include "viewport.h"
#include "fractionalscalev1.h"
#include <QtGui/QGuiApplication>
@ -46,6 +48,8 @@ public:
Keyboard *keyboard() { auto *seat = get<Seat>(); Q_ASSERT(seat); return seat->m_keyboard; }
FullScreenShellV1 *fullScreenShellV1() {return get<FullScreenShellV1>();};
IviSurface *iviSurface(int i = 0) { return get<IviApplication>()->m_iviSurfaces.value(i, nullptr); }
FractionalScale *fractionalScale(int i = 0) {return get<FractionalScaleManager>()->m_fractionalScales.value(i, nullptr); }
Viewport *viewport(int i = 0) {return get<Viewporter>()->m_viewports.value(i, nullptr); }
uint sendXdgShellPing();
void xdgPingAndWaitForPong();

View File

@ -0,0 +1,58 @@
// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org>
// 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>(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);
}
}

View File

@ -0,0 +1,50 @@
// Copyright (C) 2022 David Edmundson <davidedmundson@kde.org>
// 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 <qwayland-server-viewporter.h>
namespace MockCompositor {
class Viewport;
class Viewporter : public Global, public QtWaylandServer::wp_viewporter
{
Q_OBJECT
public:
explicit Viewporter(CoreCompositor *compositor, int version = 1);
QList<Viewport *> 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