diff --git a/autotests/integration/pointer_input.cpp b/autotests/integration/pointer_input.cpp index 6b03583578..97c58887fd 100644 --- a/autotests/integration/pointer_input.cpp +++ b/autotests/integration/pointer_input.cpp @@ -56,9 +56,7 @@ static PlatformCursorImage loadReferenceThemeCursor(const QByteArray &name) { const Cursor *pointerCursor = Cursors::self()->mouse(); - const KXcursorTheme theme = KXcursorTheme::fromTheme(pointerCursor->themeName(), - pointerCursor->themeSize(), - screens()->maxScale()); + const KXcursorTheme theme(pointerCursor->themeName(), pointerCursor->themeSize(), screens()->maxScale()); if (theme.isEmpty()) { return PlatformCursorImage(); } diff --git a/src/3rdparty/xcursor.c b/src/3rdparty/xcursor.c index d86c5956ad..1d8a12009e 100644 --- a/src/3rdparty/xcursor.c +++ b/src/3rdparty/xcursor.c @@ -238,7 +238,6 @@ XcursorImagesCreate (int size) return NULL; images->nimage = 0; images->images = (XcursorImage **) (images + 1); - images->name = NULL; return images; } @@ -252,30 +251,9 @@ XcursorImagesDestroy (XcursorImages *images) for (n = 0; n < images->nimage; n++) XcursorImageDestroy (images->images[n]); - if (images->name) - free (images->name); free (images); } -static void -XcursorImagesSetName (XcursorImages *images, const char *name) -{ - char *new; - - if (!images || !name) - return; - - new = malloc (strlen (name) + 1); - - if (!new) - return; - - strcpy (new, name); - if (images->name) - free (images->name); - images->name = new; -} - static XcursorBool _XcursorReadUInt (XcursorFile *file, XcursorUInt *u) { @@ -598,416 +576,19 @@ _XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file) file->seek = _XcursorStdioFileSeek; } -static XcursorImages * -XcursorFileLoadImages (FILE *file, int size) +XcursorImages * +XcursorFileLoadImages (const char *file, int size) { XcursorFile f; + XcursorImages *images; - if (!file) + FILE *fp = fopen(file, "r"); + if (!fp) return NULL; - _XcursorStdioFileInitialize (file, &f); - return XcursorXcFileLoadImages (&f, size); -} + _XcursorStdioFileInitialize (fp, &f); + images = XcursorXcFileLoadImages (&f, size); + fclose(fp); -/* - * From libXcursor/src/library.c - */ - -#ifndef ICONDIR -#define ICONDIR "/usr/X11R6/lib/X11/icons" -#endif - -#ifndef XCURSORPATH -#define XCURSORPATH "~/.icons:/usr/share/icons:/usr/share/pixmaps:~/.cursors:/usr/share/cursors/xorg-x11:"ICONDIR -#endif - -#define XDG_DATA_HOME_FALLBACK "~/.local/share" -#define CURSORDIR "/icons" - -/** Get search path for cursor themes - * - * This function builds the list of directories to look for cursor - * themes in. The format is PATH-like: directories are separated by - * colons. - * - * The memory block returned by this function is allocated on the heap - * and must be freed by the caller. - */ -static char * -XcursorLibraryPath (void) -{ - const char *env_var; - char *path = NULL; - int pathlen = 0; - - env_var = getenv ("XCURSOR_PATH"); - if (env_var) - { - path = strdup (env_var); - } - else - { - env_var = getenv ("XDG_DATA_HOME"); - if (env_var) { - pathlen = strlen (env_var) + strlen (CURSORDIR ":" XCURSORPATH) + 1; - path = malloc (pathlen); - snprintf (path, pathlen, "%s%s", env_var, - CURSORDIR ":" XCURSORPATH); - } - else - { - path = strdup (XDG_DATA_HOME_FALLBACK CURSORDIR ":" XCURSORPATH); - } - } - return path; -} - -static void -_XcursorAddPathElt (char *path, const char *elt, int len) -{ - int pathlen = strlen (path); - - /* append / if the path doesn't currently have one */ - if (path[0] == '\0' || path[pathlen - 1] != '/') - { - strcat (path, "/"); - pathlen++; - } - if (len == -1) - len = strlen (elt); - /* strip leading slashes */ - while (len && elt[0] == '/') - { - elt++; - len--; - } - strncpy (path + pathlen, elt, len); - path[pathlen + len] = '\0'; -} - -static char * -_XcursorBuildThemeDir (const char *dir, const char *theme) -{ - const char *colon; - const char *tcolon; - char *full; - char *home; - int dirlen; - int homelen; - int themelen; - int len; - - if (!dir || !theme) - return NULL; - - colon = strchr (dir, ':'); - if (!colon) - colon = dir + strlen (dir); - - dirlen = colon - dir; - - tcolon = strchr (theme, ':'); - if (!tcolon) - tcolon = theme + strlen (theme); - - themelen = tcolon - theme; - - home = NULL; - homelen = 0; - if (*dir == '~') - { - home = getenv ("HOME"); - if (!home) - return NULL; - homelen = strlen (home); - dir++; - dirlen--; - } - - /* - * add space for any needed directory separators, one per component, - * and one for the trailing null - */ - len = 1 + homelen + 1 + dirlen + 1 + themelen + 1; - - full = malloc (len); - if (!full) - return NULL; - full[0] = '\0'; - - if (home) - _XcursorAddPathElt (full, home, -1); - _XcursorAddPathElt (full, dir, dirlen); - _XcursorAddPathElt (full, theme, themelen); - return full; -} - -static char * -_XcursorBuildFullname (const char *dir, const char *subdir, const char *file) -{ - char *full; - - if (!dir || !subdir || !file) - return NULL; - - full = malloc (strlen (dir) + 1 + strlen (subdir) + 1 + strlen (file) + 1); - if (!full) - return NULL; - full[0] = '\0'; - _XcursorAddPathElt (full, dir, -1); - _XcursorAddPathElt (full, subdir, -1); - _XcursorAddPathElt (full, file, -1); - return full; -} - -static const char * -_XcursorNextPath (const char *path) -{ - char *colon = strchr (path, ':'); - - if (!colon) - return NULL; - return colon + 1; -} - -#define XcursorWhite(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') -#define XcursorSep(c) ((c) == ';' || (c) == ',') - -static char * -_XcursorThemeInherits (const char *full) -{ - char line[8192]; - char *result = NULL; - FILE *f; - - if (!full) - return NULL; - - f = fopen (full, "r"); - if (f) - { - while (fgets (line, sizeof (line), f)) - { - if (!strncmp (line, "Inherits", 8)) - { - char *l = line + 8; - char *r; - while (*l == ' ') l++; - if (*l != '=') continue; - l++; - while (*l == ' ') l++; - result = malloc (strlen (l) + 1); - if (result) - { - r = result; - while (*l) - { - while (XcursorSep(*l) || XcursorWhite (*l)) l++; - if (!*l) - break; - if (r != result) - *r++ = ':'; - while (*l && !XcursorWhite(*l) && - !XcursorSep(*l)) - *r++ = *l++; - } - *r++ = '\0'; - } - break; - } - } - fclose (f); - } - return result; -} - -static FILE * -XcursorScanTheme (const char *theme, const char *name) -{ - FILE *f = NULL; - char *full; - char *dir; - const char *path; - char *inherits = NULL; - const char *i; - char *xcursor_path; - - if (!theme || !name) - return NULL; - - /* - * Scan this theme - */ - xcursor_path = XcursorLibraryPath (); - for (path = xcursor_path; - path && f == NULL; - path = _XcursorNextPath (path)) - { - dir = _XcursorBuildThemeDir (path, theme); - if (dir) - { - full = _XcursorBuildFullname (dir, "cursors", name); - if (full) - { - f = fopen (full, "r"); - free (full); - } - if (!f && !inherits) - { - full = _XcursorBuildFullname (dir, "", "index.theme"); - if (full) - { - inherits = _XcursorThemeInherits (full); - free (full); - } - } - free (dir); - } - } - /* - * Recurse to scan inherited themes - */ - for (i = inherits; i && f == NULL; i = _XcursorNextPath (i)) - f = XcursorScanTheme (i, name); - if (inherits != NULL) - free (inherits); - free (xcursor_path); - return f; -} - -XcursorImages * -XcursorLibraryLoadImages (const char *file, const char *theme, int size) -{ - FILE *f = NULL; - XcursorImages *images = NULL; - - if (!file) - return NULL; - - if (theme) - f = XcursorScanTheme (theme, file); - if (!f) - f = XcursorScanTheme ("default", file); - if (f) - { - images = XcursorFileLoadImages (f, size); - if (images) - XcursorImagesSetName (images, file); - fclose (f); - } return images; } - -static void -load_all_cursors_from_dir(const char *path, int size, - void (*load_callback)(XcursorImages *, void *), - void *user_data) -{ - FILE *f; - DIR *dir = opendir(path); - struct dirent *ent; - char *full; - XcursorImages *images; - - if (!dir) - return; - - for(ent = readdir(dir); ent; ent = readdir(dir)) { -#ifdef _DIRENT_HAVE_D_TYPE - if (ent->d_type != DT_UNKNOWN && - (ent->d_type != DT_REG && ent->d_type != DT_LNK)) - continue; -#endif - - full = _XcursorBuildFullname(path, "", ent->d_name); - if (!full) - continue; - - f = fopen(full, "r"); - if (!f) { - free(full); - continue; - } - - images = XcursorFileLoadImages(f, size); - - if (images) { - XcursorImagesSetName(images, ent->d_name); - load_callback(images, user_data); - } - - fclose (f); - free(full); - } - - closedir(dir); -} - -/** Load all the cursor of a theme - * - * This function loads all the cursor images of a given theme and its - * inherited themes. Each cursor is loaded into an XcursorImages object - * which is passed to the caller's load callback. If a cursor appears - * more than once across all the inherited themes, the load callback - * will be called multiple times, with possibly different XcursorImages - * object which have the same name. The user is expected to destroy the - * XcursorImages objects passed to the callback with - * XcursorImagesDestroy(). - * - * \param theme The name of theme that should be loaded - * \param size The desired size of the cursor images - * \param load_callback A callback function that will be called - * for each cursor loaded. The first parameter is the XcursorImages - * object representing the loaded cursor and the second is a pointer - * to data provided by the user. - * \param user_data The data that should be passed to the load callback - */ -void -xcursor_load_theme(const char *theme, int size, - void (*load_callback)(XcursorImages *, void *), - void *user_data) -{ - char *full, *dir; - char *inherits = NULL; - const char *path, *i; - char *xcursor_path; - - if (!theme) - theme = "default"; - - xcursor_path = XcursorLibraryPath(); - for (path = xcursor_path; - path; - path = _XcursorNextPath(path)) { - dir = _XcursorBuildThemeDir(path, theme); - if (!dir) - continue; - - full = _XcursorBuildFullname(dir, "cursors", ""); - - if (full) { - load_all_cursors_from_dir(full, size, load_callback, - user_data); - free(full); - } - - if (!inherits) { - full = _XcursorBuildFullname(dir, "", "index.theme"); - if (full) { - inherits = _XcursorThemeInherits(full); - free(full); - } - } - - free(dir); - } - - for (i = inherits; i; i = _XcursorNextPath(i)) - xcursor_load_theme(i, size, load_callback, user_data); - - if (inherits) - free(inherits); - - free (xcursor_path); -} diff --git a/src/3rdparty/xcursor.h b/src/3rdparty/xcursor.h index eca6db1bdb..78f377d506 100644 --- a/src/3rdparty/xcursor.h +++ b/src/3rdparty/xcursor.h @@ -55,20 +55,14 @@ typedef struct _XcursorImage { typedef struct _XcursorImages { int nimage; /* number of images */ XcursorImage **images; /* array of XcursorImage pointers */ - char *name; /* name used to load images */ } XcursorImages; XcursorImages * -XcursorLibraryLoadImages (const char *file, const char *theme, int size); +XcursorFileLoadImages (const char *file, int size); void XcursorImagesDestroy (XcursorImages *images); -void -xcursor_load_theme(const char *theme, int size, - void (*load_callback)(XcursorImages *, void *), - void *user_data); - #ifdef __cplusplus } #endif diff --git a/src/pointer_input.cpp b/src/pointer_input.cpp index 88091e88bb..21ef9b9893 100644 --- a/src/pointer_input.cpp +++ b/src/pointer_input.cpp @@ -1262,14 +1262,12 @@ bool WaylandCursorImage::ensureCursorTheme() const Cursor *pointerCursor = Cursors::self()->mouse(); const qreal targetDevicePixelRatio = screens()->maxScale(); - m_cursorTheme = KXcursorTheme::fromTheme(pointerCursor->themeName(), pointerCursor->themeSize(), - targetDevicePixelRatio); + m_cursorTheme = KXcursorTheme(pointerCursor->themeName(), pointerCursor->themeSize(), targetDevicePixelRatio); if (!m_cursorTheme.isEmpty()) { return true; } - m_cursorTheme = KXcursorTheme::fromTheme(Cursor::defaultThemeName(), Cursor::defaultThemeSize(), - targetDevicePixelRatio); + m_cursorTheme = KXcursorTheme(Cursor::defaultThemeName(), Cursor::defaultThemeSize(), targetDevicePixelRatio); if (!m_cursorTheme.isEmpty()) { return true; } diff --git a/src/xcursortheme.cpp b/src/xcursortheme.cpp index ca2078f710..5017deac2e 100644 --- a/src/xcursortheme.cpp +++ b/src/xcursortheme.cpp @@ -7,8 +7,13 @@ #include "xcursortheme.h" #include "3rdparty/xcursor.h" -#include +#include +#include + +#include +#include #include +#include namespace KWin { @@ -24,7 +29,10 @@ public: class KXcursorThemePrivate : public QSharedData { public: - QMap> registry; + void load(const QString &themeName, int size, qreal devicePixelRatio); + void loadCursors(const QString &packagePath, int size, qreal devicePixelRatio); + + QHash> registry; }; KXcursorSprite::KXcursorSprite() @@ -71,20 +79,17 @@ std::chrono::milliseconds KXcursorSprite::delay() const return d->delay; } -struct XcursorThemeClosure +static QVector loadCursor(const QString &filePath, int desiredSize, qreal devicePixelRatio) { - QMap> registry; - int desiredSize; -}; + XcursorImages *images = XcursorFileLoadImages(QFile::encodeName(filePath), desiredSize * devicePixelRatio); + if (!images) { + return {}; + } -static void load_callback(XcursorImages *images, void *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 qreal scale = std::max(qreal(1), qreal(nativeCursorImage->size) / desiredSize); const QPoint hotspot(nativeCursorImage->xhot, nativeCursorImage->yhot); const std::chrono::milliseconds delay(nativeCursorImage->delay); @@ -95,10 +100,80 @@ static void load_callback(XcursorImages *images, void *data) sprites.append(KXcursorSprite(data, hotspot / scale, delay)); } - if (!sprites.isEmpty()) { - closure->registry.insert(images->name, sprites); - } XcursorImagesDestroy(images); + return sprites; +} + +void KXcursorThemePrivate::loadCursors(const QString &packagePath, int size, qreal devicePixelRatio) +{ + const QDir dir(packagePath); + QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot); + std::partition(entries.begin(), entries.end(), [](const QFileInfo &fileInfo) { + return !fileInfo.isSymLink(); + }); + + for (const QFileInfo &entry : std::as_const(entries)) { + const QByteArray shape = QFile::encodeName(entry.fileName()); + if (registry.contains(shape)) { + continue; + } + if (entry.isSymLink()) { + const QFileInfo symLinkInfo(entry.symLinkTarget()); + if (symLinkInfo.absolutePath() == entry.absolutePath()) { + const auto sprites = registry.value(QFile::encodeName(symLinkInfo.fileName())); + if (!sprites.isEmpty()) { + registry.insert(shape, sprites); + continue; + } + } + } + const QVector sprites = loadCursor(entry.absoluteFilePath(), size, devicePixelRatio); + if (!sprites.isEmpty()) { + registry.insert(shape, sprites); + } + } +} + +static QStringList searchPaths() +{ + static QStringList paths; + if (paths.isEmpty()) { + if (const QString env = qEnvironmentVariable("XCURSOR_PATH"); !env.isEmpty()) { + paths.append(env.split(':', Qt::SkipEmptyParts)); + } else { + const QString home = QDir::homePath(); + if (!home.isEmpty()) { + paths.append(home + QLatin1String("/.icons")); + } + const QStringList dataDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); + for (const QString &dataDir : dataDirs) { + paths.append(dataDir + QLatin1String("/icons")); + } + } + } + return paths; +} + +void KXcursorThemePrivate::load(const QString &themeName, int size, qreal devicePixelRatio) +{ + const QStringList paths = searchPaths(); + QStringList inherits; + + for (const QString &path : paths) { + const QDir dir(path + QLatin1Char('/') + themeName); + if (!dir.exists()) { + continue; + } + loadCursors(dir.filePath(QStringLiteral("cursors")), size, devicePixelRatio); + if (inherits.isEmpty()) { + const KConfig config(dir.filePath(QStringLiteral("index.theme")), KConfig::NoGlobals); + inherits << KConfigGroup(&config, "Icon Theme").readEntry("Inherits", QStringList()); + } + } + + for (const QString &inherit : inherits) { + load(inherit, size, devicePixelRatio); + } } KXcursorTheme::KXcursorTheme() @@ -106,10 +181,10 @@ KXcursorTheme::KXcursorTheme() { } -KXcursorTheme::KXcursorTheme(const QMap> ®istry) - : KXcursorTheme() +KXcursorTheme::KXcursorTheme(const QString &themeName, int size, qreal devicePixelRatio) + : d(new KXcursorThemePrivate) { - d->registry = registry; + d->load(themeName, size, devicePixelRatio); } KXcursorTheme::KXcursorTheme(const KXcursorTheme &other) @@ -137,20 +212,4 @@ QVector KXcursorTheme::shape(const QByteArray &name) const return d->registry.value(name); } -KXcursorTheme KXcursorTheme::fromTheme(const QString &themeName, int size, qreal 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); - - if (closure.registry.isEmpty()) { - return KXcursorTheme(); - } - - return KXcursorTheme(closure.registry); -} - } // namespace KWin diff --git a/src/xcursortheme.h b/src/xcursortheme.h index 2d511182e8..8005749825 100644 --- a/src/xcursortheme.h +++ b/src/xcursortheme.h @@ -84,6 +84,13 @@ public: */ KXcursorTheme(); + /** + * 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, the cursor theme will be empty. + */ + KXcursorTheme(const QString &theme, int size, qreal devicePixelRatio); + /** * Constructs a copy of the KXcursorTheme object @a other. */ @@ -109,15 +116,7 @@ public: */ QVector shape(const QByteArray &name) const; - /** - * 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; };