Commit 403a04fe authored by Vlad Zahorodnii's avatar Vlad Zahorodnii
Browse files

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.
parent 73973641
......@@ -48,12 +48,7 @@ static PlatformCursorImage loadReferenceThemeCursor_helper(const KXcursorTheme &
return PlatformCursorImage();
QImage cursorImage = sprites.first().data();
QPoint cursorHotspot = sprites.first().hotspot();
return PlatformCursorImage(cursorImage, cursorHotspot);
return PlatformCursorImage(sprites.constFirst().data(), sprites.constFirst().hotspot());
static PlatformCursorImage loadReferenceThemeCursor(const QByteArray &name)
......@@ -1318,8 +1318,6 @@ bool WaylandCursorImage::loadThemeCursor_helper(const QByteArray &name, Image *c
cursorImage->image = sprites.first().data();
cursorImage->hotspot = sprites.first().hotspot();
return true;
......@@ -25,7 +25,6 @@ class KXcursorThemePrivate : public QSharedData
QMap<QByteArray, QVector<KXcursorSprite>> registry;
qreal devicePixelRatio = 1;
......@@ -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);
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);
......@@ -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
......@@ -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);
KXcursorTheme(const QMap<QByteArray, QVector<KXcursorSprite>> &registry);
QSharedDataPointer<KXcursorThemePrivate> d;
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment