KWin
Loading...
Searching...
No Matches
xcursortheme.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "xcursortheme.h"
8#include "3rdparty/xcursor.h"
9
10#include <KConfig>
11#include <KConfigGroup>
12
13#include <QDir>
14#include <QFile>
15#include <QSet>
16#include <QSharedData>
17#include <QStack>
18#include <QStandardPaths>
19
20namespace KWin
21{
22
23class KXcursorSpritePrivate : public QSharedData
24{
25public:
26 QImage data;
27 QPoint hotspot;
28 std::chrono::milliseconds delay;
29};
30
31class KXcursorThemePrivate : public QSharedData
32{
33public:
34 void load(const QString &themeName, int size, qreal devicePixelRatio);
35 void loadCursors(const QString &packagePath, int size, qreal devicePixelRatio);
36
37 QHash<QByteArray, QList<KXcursorSprite>> registry;
38};
39
44
46 : d(other.d)
47{
48}
49
53
55{
56 d = other.d;
57 return *this;
58}
59
60KXcursorSprite::KXcursorSprite(const QImage &data, const QPoint &hotspot,
61 const std::chrono::milliseconds &delay)
63{
64 d->data = data;
65 d->hotspot = hotspot;
66 d->delay = delay;
67}
68
70{
71 return d->data;
72}
73
75{
76 return d->hotspot;
77}
78
79std::chrono::milliseconds KXcursorSprite::delay() const
80{
81 return d->delay;
82}
83
84static QList<KXcursorSprite> loadCursor(const QString &filePath, int desiredSize, qreal devicePixelRatio)
85{
86 XcursorImages *images = XcursorFileLoadImages(QFile::encodeName(filePath), desiredSize * devicePixelRatio);
87 if (!images) {
88 return {};
89 }
90
91 QList<KXcursorSprite> sprites;
92 for (int i = 0; i < images->nimage; ++i) {
93 const XcursorImage *nativeCursorImage = images->images[i];
94 const qreal scale = std::max(qreal(1), qreal(nativeCursorImage->size) / desiredSize);
95 const QPoint hotspot(nativeCursorImage->xhot, nativeCursorImage->yhot);
96 const std::chrono::milliseconds delay(nativeCursorImage->delay);
97
98 QImage data(nativeCursorImage->width, nativeCursorImage->height, QImage::Format_ARGB32_Premultiplied);
99 data.setDevicePixelRatio(scale);
100 memcpy(data.bits(), nativeCursorImage->pixels, data.sizeInBytes());
101
102 sprites.append(KXcursorSprite(data, hotspot / scale, delay));
103 }
104
105 XcursorImagesDestroy(images);
106 return sprites;
107}
108
109void KXcursorThemePrivate::loadCursors(const QString &packagePath, int size, qreal devicePixelRatio)
110{
111 const QDir dir(packagePath);
112 QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
113 std::partition(entries.begin(), entries.end(), [](const QFileInfo &fileInfo) {
114 return !fileInfo.isSymLink();
115 });
116
117 for (const QFileInfo &entry : std::as_const(entries)) {
118 const QByteArray shape = QFile::encodeName(entry.fileName());
119 if (registry.contains(shape)) {
120 continue;
121 }
122 if (entry.isSymLink()) {
123 const QFileInfo symLinkInfo(entry.symLinkTarget());
124 if (symLinkInfo.absolutePath() == entry.absolutePath()) {
125 const auto sprites = registry.value(QFile::encodeName(symLinkInfo.fileName()));
126 if (!sprites.isEmpty()) {
127 registry.insert(shape, sprites);
128 continue;
129 }
130 }
131 }
132 const QList<KXcursorSprite> sprites = loadCursor(entry.absoluteFilePath(), size, devicePixelRatio);
133 if (!sprites.isEmpty()) {
134 registry.insert(shape, sprites);
135 }
136 }
137}
138
139static QStringList searchPaths()
140{
141 static QStringList paths;
142 if (paths.isEmpty()) {
143 if (const QString env = qEnvironmentVariable("XCURSOR_PATH"); !env.isEmpty()) {
144 paths.append(env.split(':', Qt::SkipEmptyParts));
145 } else {
146 const QString home = QDir::homePath();
147 if (!home.isEmpty()) {
148 paths.append(home + QLatin1String("/.icons"));
149 }
150 const QStringList dataDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
151 for (const QString &dataDir : dataDirs) {
152 paths.append(dataDir + QLatin1String("/icons"));
153 }
154 }
155 }
156 return paths;
157}
158
159void KXcursorThemePrivate::load(const QString &themeName, int size, qreal devicePixelRatio)
160{
161 const QStringList paths = searchPaths();
162
163 QStack<QString> stack;
164 QSet<QString> loaded;
165
166 stack.push(themeName);
167
168 while (!stack.isEmpty()) {
169 const QString themeName = stack.pop();
170 if (loaded.contains(themeName)) {
171 continue;
172 }
173
174 QStringList inherits;
175
176 for (const QString &path : paths) {
177 const QDir dir(path + QLatin1Char('/') + themeName);
178 if (!dir.exists()) {
179 continue;
180 }
181 loadCursors(dir.filePath(QStringLiteral("cursors")), size, devicePixelRatio);
182 if (inherits.isEmpty()) {
183 const KConfig config(dir.filePath(QStringLiteral("index.theme")), KConfig::NoGlobals);
184 inherits << KConfigGroup(&config, QStringLiteral("Icon Theme")).readEntry("Inherits", QStringList());
185 }
186 }
187
188 loaded.insert(themeName);
189 for (auto it = inherits.crbegin(); it != inherits.crend(); ++it) {
190 stack.push(*it);
191 }
192 }
193}
194
199
200KXcursorTheme::KXcursorTheme(const QString &themeName, int size, qreal devicePixelRatio)
201 : d(new KXcursorThemePrivate)
202{
203 d->load(themeName, size, devicePixelRatio);
204}
205
207 : d(other.d)
208{
209}
210
214
216{
217 d = other.d;
218 return *this;
219}
220
222{
223 return d == other.d;
224}
225
227{
228 return !(*this == other);
229}
230
232{
233 return d->registry.isEmpty();
234}
235
236QList<KXcursorSprite> KXcursorTheme::shape(const QByteArray &name) const
237{
238 return d->registry.value(name);
239}
240
241} // namespace KWin
QImage data() const
std::chrono::milliseconds delay() const
QPoint hotspot() const
KXcursorSprite & operator=(const KXcursorSprite &other)
std::chrono::milliseconds delay
bool operator==(const KXcursorTheme &other)
QList< KXcursorSprite > shape(const QByteArray &name) const
bool operator!=(const KXcursorTheme &other)
KXcursorTheme & operator=(const KXcursorTheme &other)
void loadCursors(const QString &packagePath, int size, qreal devicePixelRatio)
void load(const QString &themeName, int size, qreal devicePixelRatio)
QHash< QByteArray, QList< KXcursorSprite > > registry
XcursorUInt delay
Definition xcursor.h:48
XcursorDim width
Definition xcursor.h:44
XcursorDim yhot
Definition xcursor.h:47
XcursorDim xhot
Definition xcursor.h:46
XcursorPixel * pixels
Definition xcursor.h:49
XcursorDim size
Definition xcursor.h:43
XcursorDim height
Definition xcursor.h:45
XcursorImage ** images
Definition xcursor.h:57
void XcursorImagesDestroy(XcursorImages *images)
Definition xcursor.c:245
XcursorImages * XcursorFileLoadImages(const char *file, int size)
Definition xcursor.c:580