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.
This commit is contained in:
Vlad Zahorodnii 2021-05-02 13:37:45 +03:00
parent 7397364115
commit 403a04fe24
4 changed files with 34 additions and 29 deletions

View file

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

View file

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

View file

@ -25,7 +25,6 @@ class KXcursorThemePrivate : public QSharedData
{
public:
QMap<QByteArray, QVector<KXcursorSprite>> registry;
qreal devicePixelRatio = 1;
};
KXcursorSprite::KXcursorSprite()
@ -72,23 +71,33 @@ std::chrono::milliseconds KXcursorSprite::delay() const
return d->delay;
}
struct XcursorThemeClosure
{
QMap<QByteArray, QVector<KXcursorSprite>> registry;
int desiredSize;
};
static void load_callback(XcursorImages *images, void *data)
{
KXcursorThemePrivate *themePrivate = static_cast<KXcursorThemePrivate *>(data);
XcursorThemeClosure *closure = static_cast<XcursorThemeClosure *>(data);
QVector<KXcursorSprite> 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<QByteArray, QVector<KXcursorSprite>> &registry)
: 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<KXcursorSprite> 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

View file

@ -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<KXcursorSprite> 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<QByteArray, QVector<KXcursorSprite>> &registry);
QSharedDataPointer<KXcursorThemePrivate> d;
};