From 403a04fe24d718231c567ff3f260f3a62f00ff64 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Sun, 2 May 2021 13:37:45 +0300 Subject: [PATCH] wayland: Fix loading of HiDPI cursors Xcursors don't support hidpi so if a hidpi cursor is needed, kwin will scale the desired size by the scale factor and ask Xcursor helpers to load a theme with the given name and the size. However, the theme loading code doesn't take into account that Xcursor theme loading helpers may not return cursor sprites of size size * scale if the theme has no such a size. For example, if the cursor theme only provides 24, 36, and 48 sizes and kwin attempts to load cursors of size 48 with a scale factor of 2, we will get cursors of size 48 instead of 96. Unfortunately, this will result in the issue where the cursor shrinks when hovering decorations because kwin doesn't know that the effective scale factor (1) is different from the requested scale factor (2). In order to fix loading of HiDPI cursors, we need to approximate the effective scale factor of every cursor sprite as we load it. --- autotests/integration/pointer_input.cpp | 7 +--- src/pointer_input.cpp | 2 -- src/xcursortheme.cpp | 44 ++++++++++++++++--------- src/xcursortheme.h | 10 +++--- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/autotests/integration/pointer_input.cpp b/autotests/integration/pointer_input.cpp index 0ab348cd59..97e83a5a7b 100644 --- a/autotests/integration/pointer_input.cpp +++ b/autotests/integration/pointer_input.cpp @@ -48,12 +48,7 @@ static PlatformCursorImage loadReferenceThemeCursor_helper(const KXcursorTheme & return PlatformCursorImage(); } - QImage cursorImage = sprites.first().data(); - cursorImage.setDevicePixelRatio(theme.devicePixelRatio()); - - QPoint cursorHotspot = sprites.first().hotspot(); - - return PlatformCursorImage(cursorImage, cursorHotspot); + return PlatformCursorImage(sprites.constFirst().data(), sprites.constFirst().hotspot()); } static PlatformCursorImage loadReferenceThemeCursor(const QByteArray &name) diff --git a/src/pointer_input.cpp b/src/pointer_input.cpp index 70c36fd16d..bf59197d34 100644 --- a/src/pointer_input.cpp +++ b/src/pointer_input.cpp @@ -1318,8 +1318,6 @@ bool WaylandCursorImage::loadThemeCursor_helper(const QByteArray &name, Image *c } cursorImage->image = sprites.first().data(); - cursorImage->image.setDevicePixelRatio(m_cursorTheme.devicePixelRatio()); - cursorImage->hotspot = sprites.first().hotspot(); return true; diff --git a/src/xcursortheme.cpp b/src/xcursortheme.cpp index 58b91a1545..ca2078f710 100644 --- a/src/xcursortheme.cpp +++ b/src/xcursortheme.cpp @@ -25,7 +25,6 @@ class KXcursorThemePrivate : public QSharedData { public: QMap> registry; - qreal devicePixelRatio = 1; }; KXcursorSprite::KXcursorSprite() @@ -72,23 +71,33 @@ std::chrono::milliseconds KXcursorSprite::delay() const return d->delay; } +struct XcursorThemeClosure +{ + QMap> registry; + int desiredSize; +}; + static void load_callback(XcursorImages *images, void *data) { - KXcursorThemePrivate *themePrivate = static_cast(data); + XcursorThemeClosure *closure = static_cast(data); QVector sprites; for (int i = 0; i < images->nimage; ++i) { const XcursorImage *nativeCursorImage = images->images[i]; + const qreal scale = std::max(qreal(1), qreal(nativeCursorImage->size) / closure->desiredSize); const QPoint hotspot(nativeCursorImage->xhot, nativeCursorImage->yhot); const std::chrono::milliseconds delay(nativeCursorImage->delay); QImage data(nativeCursorImage->width, nativeCursorImage->height, QImage::Format_ARGB32_Premultiplied); + data.setDevicePixelRatio(scale); memcpy(data.bits(), nativeCursorImage->pixels, data.sizeInBytes()); - sprites.append(KXcursorSprite(data, hotspot / themePrivate->devicePixelRatio, delay)); + sprites.append(KXcursorSprite(data, hotspot / scale, delay)); } - themePrivate->registry.insert(images->name, sprites); + if (!sprites.isEmpty()) { + closure->registry.insert(images->name, sprites); + } XcursorImagesDestroy(images); } @@ -97,6 +106,12 @@ KXcursorTheme::KXcursorTheme() { } +KXcursorTheme::KXcursorTheme(const QMap> ®istry) + : KXcursorTheme() +{ + d->registry = registry; +} + KXcursorTheme::KXcursorTheme(const KXcursorTheme &other) : d(other.d) { @@ -112,11 +127,6 @@ KXcursorTheme &KXcursorTheme::operator=(const KXcursorTheme &other) return *this; } -qreal KXcursorTheme::devicePixelRatio() const -{ - return d->devicePixelRatio; -} - bool KXcursorTheme::isEmpty() const { return d->registry.isEmpty(); @@ -129,14 +139,18 @@ QVector KXcursorTheme::shape(const QByteArray &name) const KXcursorTheme KXcursorTheme::fromTheme(const QString &themeName, int size, qreal dpr) { - KXcursorTheme theme; - KXcursorThemePrivate *themePrivate = theme.d; - themePrivate->devicePixelRatio = dpr; + // Xcursors don't support HiDPI natively so we fake it by scaling the desired cursor + // size. The device pixel ratio argument acts only as a hint. The real scale factor + // of every cursor sprite will be computed in the loading closure. + XcursorThemeClosure closure; + closure.desiredSize = size; + xcursor_load_theme(themeName.toUtf8().constData(), size * dpr, load_callback, &closure); - const QByteArray nativeThemeName = themeName.toUtf8(); - xcursor_load_theme(nativeThemeName, size * dpr, load_callback, themePrivate); + if (closure.registry.isEmpty()) { + return KXcursorTheme(); + } - return theme; + return KXcursorTheme(closure.registry); } } // namespace KWin diff --git a/src/xcursortheme.h b/src/xcursortheme.h index ca3eb10b14..2d511182e8 100644 --- a/src/xcursortheme.h +++ b/src/xcursortheme.h @@ -99,11 +99,6 @@ public: */ KXcursorTheme &operator=(const KXcursorTheme &other); - /** - * Returns the ratio between device pixels and logical pixels for the Xcursor theme. - */ - qreal devicePixelRatio() const; - /** * Returns @c true if the Xcursor theme is empty; otherwise returns @c false. */ @@ -115,11 +110,14 @@ public: QVector shape(const QByteArray &name) const; /** - * Attempts to load the Xcursor theme with the given @a themeName and @a size. + * Loads the Xcursor theme with the given @ themeName and the desired @a size. + * The @a dpr specifies the desired scale factor. If no theme with the provided + * name exists, an empty KXcursorTheme is returned. */ static KXcursorTheme fromTheme(const QString &themeName, int size, qreal dpr); private: + KXcursorTheme(const QMap> ®istry); QSharedDataPointer d; };