File indexing completed on 2024-11-10 04:57:24
0001 /* 0002 SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "xcursortheme.h" 0008 #include "3rdparty/xcursor.h" 0009 0010 #include <KConfig> 0011 #include <KConfigGroup> 0012 0013 #include <QDir> 0014 #include <QFile> 0015 #include <QSet> 0016 #include <QSharedData> 0017 #include <QStack> 0018 #include <QStandardPaths> 0019 0020 namespace KWin 0021 { 0022 0023 class KXcursorSpritePrivate : public QSharedData 0024 { 0025 public: 0026 QImage data; 0027 QPoint hotspot; 0028 std::chrono::milliseconds delay; 0029 }; 0030 0031 class KXcursorThemePrivate : public QSharedData 0032 { 0033 public: 0034 void load(const QString &themeName, int size, qreal devicePixelRatio); 0035 void loadCursors(const QString &packagePath, int size, qreal devicePixelRatio); 0036 0037 QHash<QByteArray, QList<KXcursorSprite>> registry; 0038 }; 0039 0040 KXcursorSprite::KXcursorSprite() 0041 : d(new KXcursorSpritePrivate) 0042 { 0043 } 0044 0045 KXcursorSprite::KXcursorSprite(const KXcursorSprite &other) 0046 : d(other.d) 0047 { 0048 } 0049 0050 KXcursorSprite::~KXcursorSprite() 0051 { 0052 } 0053 0054 KXcursorSprite &KXcursorSprite::operator=(const KXcursorSprite &other) 0055 { 0056 d = other.d; 0057 return *this; 0058 } 0059 0060 KXcursorSprite::KXcursorSprite(const QImage &data, const QPoint &hotspot, 0061 const std::chrono::milliseconds &delay) 0062 : d(new KXcursorSpritePrivate) 0063 { 0064 d->data = data; 0065 d->hotspot = hotspot; 0066 d->delay = delay; 0067 } 0068 0069 QImage KXcursorSprite::data() const 0070 { 0071 return d->data; 0072 } 0073 0074 QPoint KXcursorSprite::hotspot() const 0075 { 0076 return d->hotspot; 0077 } 0078 0079 std::chrono::milliseconds KXcursorSprite::delay() const 0080 { 0081 return d->delay; 0082 } 0083 0084 static QList<KXcursorSprite> loadCursor(const QString &filePath, int desiredSize, qreal devicePixelRatio) 0085 { 0086 XcursorImages *images = XcursorFileLoadImages(QFile::encodeName(filePath), desiredSize * devicePixelRatio); 0087 if (!images) { 0088 return {}; 0089 } 0090 0091 QList<KXcursorSprite> sprites; 0092 for (int i = 0; i < images->nimage; ++i) { 0093 const XcursorImage *nativeCursorImage = images->images[i]; 0094 const qreal scale = std::max(qreal(1), qreal(nativeCursorImage->size) / desiredSize); 0095 const QPoint hotspot(nativeCursorImage->xhot, nativeCursorImage->yhot); 0096 const std::chrono::milliseconds delay(nativeCursorImage->delay); 0097 0098 QImage data(nativeCursorImage->width, nativeCursorImage->height, QImage::Format_ARGB32_Premultiplied); 0099 data.setDevicePixelRatio(scale); 0100 memcpy(data.bits(), nativeCursorImage->pixels, data.sizeInBytes()); 0101 0102 sprites.append(KXcursorSprite(data, hotspot / scale, delay)); 0103 } 0104 0105 XcursorImagesDestroy(images); 0106 return sprites; 0107 } 0108 0109 void KXcursorThemePrivate::loadCursors(const QString &packagePath, int size, qreal devicePixelRatio) 0110 { 0111 const QDir dir(packagePath); 0112 QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot); 0113 std::partition(entries.begin(), entries.end(), [](const QFileInfo &fileInfo) { 0114 return !fileInfo.isSymLink(); 0115 }); 0116 0117 for (const QFileInfo &entry : std::as_const(entries)) { 0118 const QByteArray shape = QFile::encodeName(entry.fileName()); 0119 if (registry.contains(shape)) { 0120 continue; 0121 } 0122 if (entry.isSymLink()) { 0123 const QFileInfo symLinkInfo(entry.symLinkTarget()); 0124 if (symLinkInfo.absolutePath() == entry.absolutePath()) { 0125 const auto sprites = registry.value(QFile::encodeName(symLinkInfo.fileName())); 0126 if (!sprites.isEmpty()) { 0127 registry.insert(shape, sprites); 0128 continue; 0129 } 0130 } 0131 } 0132 const QList<KXcursorSprite> sprites = loadCursor(entry.absoluteFilePath(), size, devicePixelRatio); 0133 if (!sprites.isEmpty()) { 0134 registry.insert(shape, sprites); 0135 } 0136 } 0137 } 0138 0139 static QStringList searchPaths() 0140 { 0141 static QStringList paths; 0142 if (paths.isEmpty()) { 0143 if (const QString env = qEnvironmentVariable("XCURSOR_PATH"); !env.isEmpty()) { 0144 paths.append(env.split(':', Qt::SkipEmptyParts)); 0145 } else { 0146 const QString home = QDir::homePath(); 0147 if (!home.isEmpty()) { 0148 paths.append(home + QLatin1String("/.icons")); 0149 } 0150 const QStringList dataDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); 0151 for (const QString &dataDir : dataDirs) { 0152 paths.append(dataDir + QLatin1String("/icons")); 0153 } 0154 } 0155 } 0156 return paths; 0157 } 0158 0159 void KXcursorThemePrivate::load(const QString &themeName, int size, qreal devicePixelRatio) 0160 { 0161 const QStringList paths = searchPaths(); 0162 0163 QStack<QString> stack; 0164 QSet<QString> loaded; 0165 0166 stack.push(themeName); 0167 0168 while (!stack.isEmpty()) { 0169 const QString themeName = stack.pop(); 0170 if (loaded.contains(themeName)) { 0171 continue; 0172 } 0173 0174 QStringList inherits; 0175 0176 for (const QString &path : paths) { 0177 const QDir dir(path + QLatin1Char('/') + themeName); 0178 if (!dir.exists()) { 0179 continue; 0180 } 0181 loadCursors(dir.filePath(QStringLiteral("cursors")), size, devicePixelRatio); 0182 if (inherits.isEmpty()) { 0183 const KConfig config(dir.filePath(QStringLiteral("index.theme")), KConfig::NoGlobals); 0184 inherits << KConfigGroup(&config, QStringLiteral("Icon Theme")).readEntry("Inherits", QStringList()); 0185 } 0186 } 0187 0188 loaded.insert(themeName); 0189 for (auto it = inherits.crbegin(); it != inherits.crend(); ++it) { 0190 stack.push(*it); 0191 } 0192 } 0193 } 0194 0195 KXcursorTheme::KXcursorTheme() 0196 : d(new KXcursorThemePrivate) 0197 { 0198 } 0199 0200 KXcursorTheme::KXcursorTheme(const QString &themeName, int size, qreal devicePixelRatio) 0201 : d(new KXcursorThemePrivate) 0202 { 0203 d->load(themeName, size, devicePixelRatio); 0204 } 0205 0206 KXcursorTheme::KXcursorTheme(const KXcursorTheme &other) 0207 : d(other.d) 0208 { 0209 } 0210 0211 KXcursorTheme::~KXcursorTheme() 0212 { 0213 } 0214 0215 KXcursorTheme &KXcursorTheme::operator=(const KXcursorTheme &other) 0216 { 0217 d = other.d; 0218 return *this; 0219 } 0220 0221 bool KXcursorTheme::operator==(const KXcursorTheme &other) 0222 { 0223 return d == other.d; 0224 } 0225 0226 bool KXcursorTheme::operator!=(const KXcursorTheme &other) 0227 { 0228 return !(*this == other); 0229 } 0230 0231 bool KXcursorTheme::isEmpty() const 0232 { 0233 return d->registry.isEmpty(); 0234 } 0235 0236 QList<KXcursorSprite> KXcursorTheme::shape(const QByteArray &name) const 0237 { 0238 return d->registry.value(name); 0239 } 0240 0241 } // namespace KWin