1 /*
2 * SPDX-FileCopyrightText: 2017-2017 CSSlayer <wengxt@gmail.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 */
7 #include "icontheme.h"
8 #include <fcntl.h>
9 #include <sys/mman.h>
10 #include <sys/stat.h>
11 #include <ctime>
12 #include <fstream>
13 #include <limits>
14 #include <stdexcept>
15 #include <unordered_set>
16 #include "fcitx-config/iniparser.h"
17 #include "fcitx-config/marshallfunction.h"
18 #include "fcitx-utils/fs.h"
19 #include "fcitx-utils/log.h"
20 #include "fcitx-utils/mtime_p.h"
21 #include "config.h"
22 #include "misc_p.h"
23
24 namespace fcitx {
25
pathToRoot(const RawConfig & config)26 std::string pathToRoot(const RawConfig &config) {
27 std::string path;
28 const auto *pConfig = &config;
29 size_t length = 0;
30 while (pConfig) {
31 if (pConfig->parent() && length) {
32 length += 1; // For "/";
33 }
34 length += pConfig->name().size();
35 pConfig = pConfig->parent();
36 }
37
38 pConfig = &config;
39 path.resize(length);
40 size_t currentLength = 0;
41 while (pConfig) {
42 if (pConfig->parent() && currentLength) {
43 currentLength += 1; // For "/";
44 path[length - currentLength] = '/';
45 }
46 const auto &seg = pConfig->name();
47 currentLength += seg.size();
48 path.replace(length - currentLength, seg.size(), seg);
49 pConfig = pConfig->parent();
50 }
51 return path;
52 }
53
54 class IconThemeDirectoryPrivate {
55 public:
IconThemeDirectoryPrivate(const RawConfig & config)56 IconThemeDirectoryPrivate(const RawConfig &config)
57 : path_(pathToRoot(config)) {
58 if (path_.empty() || path_[0] == '/') {
59 throw std::invalid_argument("Invalid path.");
60 }
61
62 if (auto subConfig = config.get("Size")) {
63 unmarshallOption(size_, *subConfig, false);
64 }
65 if (size_ <= 0) {
66 throw std::invalid_argument("Invalid size");
67 }
68
69 if (auto subConfig = config.get("Scale")) {
70 unmarshallOption(size_, *subConfig, false);
71 }
72 if (auto subConfig = config.get("Context")) {
73 unmarshallOption(context_, *subConfig, false);
74 }
75 if (auto subConfig = config.get("Type")) {
76 unmarshallOption(type_, *subConfig, false);
77 }
78 if (auto subConfig = config.get("MaxSize")) {
79 unmarshallOption(maxSize_, *subConfig, false);
80 }
81 if (auto subConfig = config.get("MinSize")) {
82 unmarshallOption(minSize_, *subConfig, false);
83 }
84 if (auto subConfig = config.get("Threshold")) {
85 unmarshallOption(threshold_, *subConfig, false);
86 }
87
88 if (maxSize_ <= 0) {
89 maxSize_ = size_;
90 }
91
92 if (minSize_ <= 0) {
93 minSize_ = size_;
94 }
95 }
96
97 FCITX_INLINE_DEFINE_DEFAULT_DTOR_COPY_AND_MOVE_WITHOUT_SPEC(
98 IconThemeDirectoryPrivate);
99
100 std::string path_;
101 int size_ = 0;
102 int scale_ = 1;
103 std::string context_;
104 IconThemeDirectoryType type_ = IconThemeDirectoryType::Threshold;
105 int maxSize_ = 0;
106 int minSize_ = 0;
107 int threshold_ = 2;
108 };
109
IconThemeDirectory(const RawConfig & config)110 IconThemeDirectory::IconThemeDirectory(const RawConfig &config)
111 : d_ptr(std::make_unique<IconThemeDirectoryPrivate>(config)) {}
112
113 FCITX_DEFINE_DPTR_COPY_AND_DEFAULT_DTOR_AND_MOVE(IconThemeDirectory);
114
matchesSize(int iconsize,int iconscale) const115 bool IconThemeDirectory::matchesSize(int iconsize, int iconscale) const {
116 if (scale() != iconscale) {
117 return false;
118 }
119 switch (type()) {
120 case IconThemeDirectoryType::Fixed:
121 return iconsize == size();
122 case IconThemeDirectoryType::Scalable:
123 return minSize() <= iconsize && iconsize <= maxSize();
124 case IconThemeDirectoryType::Threshold:
125 return size() - threshold() <= iconsize &&
126 iconsize <= size() + threshold();
127 }
128 return false;
129 }
130
sizeDistance(int iconsize,int iconscale) const131 int IconThemeDirectory::sizeDistance(int iconsize, int iconscale) const {
132 switch (type()) {
133 case IconThemeDirectoryType::Fixed:
134 return std::abs(size() * scale() - iconsize * iconscale);
135 case IconThemeDirectoryType::Scalable:
136 if (iconsize * iconscale < minSize() * scale()) {
137 return minSize() * scale() - iconsize * iconscale;
138 }
139 if (iconsize * iconscale > maxSize() * scale()) {
140 return iconsize * iconscale - maxSize() * scale();
141 }
142 return 0;
143 case IconThemeDirectoryType::Threshold:
144 if (iconsize * iconscale < (size() - threshold()) * scale()) {
145 return (size() - threshold()) * scale() - iconsize * iconscale;
146 }
147 if (iconsize * iconscale > (size() + threshold()) * scale()) {
148 return iconsize * iconscale - (size() - threshold()) * scale();
149 }
150 }
151 return 0;
152 }
153
154 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, std::string, path);
155 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, int, size);
156 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, int, scale);
157 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, std::string,
158 context);
159 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory,
160 IconThemeDirectoryType, type);
161 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, int, maxSize);
162 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, int, minSize);
163 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, int, threshold);
164
timespecLess(const Timespec & lhs,const Timespec & rhs)165 bool timespecLess(const Timespec &lhs, const Timespec &rhs) {
166 if (lhs.sec != rhs.sec) {
167 return lhs.sec < rhs.sec;
168 }
169 return lhs.nsec < rhs.nsec;
170 }
171
iconNameHash(const char * p)172 static uint32_t iconNameHash(const char *p) {
173 uint32_t h = static_cast<signed char>(*p);
174 for (p += 1; *p != '\0'; p++) {
175 h = (h << 5) - h + *p;
176 }
177 return h;
178 }
179
180 class IconThemeCache {
181 public:
IconThemeCache(const std::string & filename)182 IconThemeCache(const std::string &filename) {
183 auto fd = UnixFD::own(open(filename.c_str(), O_RDONLY));
184 if (!fd.isValid()) {
185 return;
186 }
187 struct stat st;
188 if (fstat(fd.fd(), &st) != 0) {
189 return;
190 }
191 struct stat dirSt;
192 auto dirName = fs::dirName(filename);
193 if (stat(dirName.c_str(), &dirSt) != 0) {
194 return;
195 }
196 if (timespecLess(modifiedTime(st), modifiedTime(dirSt))) {
197 return;
198 }
199
200 memory_ = static_cast<uint8_t *>(
201 mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd.fd(), 0));
202 if (!memory_) {
203 // Failed to mmap is not critical here.
204 return;
205 }
206 size_ = st.st_size;
207
208 if (readWord(0) != 1 || readWord(2) != 0) { // version major minor
209 return;
210 }
211
212 isValid_ = true;
213
214 // Check that all the directories are older than the cache
215 uint32_t dirListOffset = readDoubleWord(8);
216 uint32_t dirListLen = readDoubleWord(dirListOffset);
217 // We assume it won't be so long just in case we hit a corruptted file.
218 if (dirListLen > 4096) {
219 isValid_ = false;
220 return;
221 }
222 for (uint32_t i = 0; i < dirListLen; ++i) {
223 uint32_t offset = readDoubleWord(dirListOffset + 4 + 4 * i);
224 if (!isValid_ || offset >= size_) {
225 isValid_ = false;
226 return;
227 }
228 struct stat subDirSt;
229 auto *dir = checkString(offset);
230 if (!dir || stat(stringutils::joinPath(dirName, dir).c_str(),
231 &subDirSt) != 0) {
232 isValid_ = false;
233 return;
234 }
235 if (timespecLess(modifiedTime(st), modifiedTime(subDirSt))) {
236 isValid_ = false;
237 return;
238 }
239 }
240 }
241
242 IconThemeCache() = default;
IconThemeCache(IconThemeCache && other)243 IconThemeCache(IconThemeCache &&other) noexcept
244 : isValid_(other.isValid_), memory_(other.memory_), size_(other.size_) {
245 other.isValid_ = false;
246 other.memory_ = nullptr;
247 other.size_ = 0;
248 }
249
operator =(IconThemeCache other)250 IconThemeCache &operator=(IconThemeCache other) {
251 std::swap(other.isValid_, isValid_);
252 std::swap(other.size_, size_);
253 std::swap(other.memory_, memory_);
254 return *this;
255 }
256
isValid() const257 bool isValid() const { return isValid_; }
258
~IconThemeCache()259 ~IconThemeCache() {
260 if (memory_) {
261 munmap(memory_, size_);
262 }
263 }
264
readWord(uint32_t offset) const265 uint16_t readWord(uint32_t offset) const {
266 if (offset > size_ - 2 || (offset % 2)) {
267 isValid_ = false;
268 return 0;
269 }
270 return memory_[offset + 1] | memory_[offset] << 8;
271 }
readDoubleWord(unsigned int offset) const272 uint32_t readDoubleWord(unsigned int offset) const {
273 if (offset > size_ - 4 || (offset % 4)) {
274 isValid_ = false;
275 return 0;
276 }
277 return memory_[offset + 3] | memory_[offset + 2] << 8 |
278 memory_[offset + 1] << 16 | memory_[offset] << 24;
279 }
280
checkString(uint32_t offset) const281 char *checkString(uint32_t offset) const {
282 // assume string length is less than 1k.
283 for (uint32_t i = 0; i < 1024; i++) {
284 if (offset + i >= size_) {
285 return nullptr;
286 }
287 auto c = memory_[offset + i];
288 if (c == '\0') {
289 break;
290 }
291 }
292 return reinterpret_cast<char *>(memory_ + offset);
293 }
294
295 std::unordered_set<std::string> lookup(const std::string &name) const;
296
297 mutable bool isValid_ = false;
298 uint8_t *memory_ = nullptr;
299 size_t size_ = 0;
300 };
301
302 std::unordered_set<std::string>
lookup(const std::string & name) const303 IconThemeCache::lookup(const std::string &name) const {
304 std::unordered_set<std::string> ret;
305 auto hash = iconNameHash(name.c_str());
306
307 uint32_t hashOffset = readDoubleWord(4);
308 uint32_t hashBucketCount = readDoubleWord(hashOffset);
309
310 if (!isValid_ || hashBucketCount == 0) {
311 isValid_ = false;
312 return ret;
313 }
314
315 uint32_t bucketIndex = hash % hashBucketCount;
316 uint32_t bucketOffset = readDoubleWord(hashOffset + 4 + bucketIndex * 4);
317 while (bucketOffset > 0 && bucketOffset <= size_ - 12) {
318 uint32_t nameOff = readDoubleWord(bucketOffset + 4);
319 auto *namePtr = checkString(nameOff);
320 if (nameOff < size_ && namePtr && name == namePtr) {
321 uint32_t dirListOffset = readDoubleWord(8);
322 uint32_t dirListLen = readDoubleWord(dirListOffset);
323
324 uint32_t listOffset = readDoubleWord(bucketOffset + 8);
325 uint32_t listLen = readDoubleWord(listOffset);
326
327 if (!isValid_ || listOffset + 4 + 8 * listLen > size_) {
328 isValid_ = false;
329 return ret;
330 }
331
332 ret.reserve(listLen);
333 for (uint32_t j = 0; j < listLen && isValid_; ++j) {
334 uint32_t dirIndex = readWord(listOffset + 4 + 8 * j);
335 uint32_t o = readDoubleWord(dirListOffset + 4 + dirIndex * 4);
336 if (!isValid_ || dirIndex >= dirListLen || o >= size_) {
337 isValid_ = false;
338 return ret;
339 }
340 if (auto *str = checkString(o)) {
341 ret.emplace(str);
342 } else {
343 return {};
344 }
345 }
346 return ret;
347 }
348 bucketOffset = readDoubleWord(bucketOffset);
349 }
350 return ret;
351 }
352
353 class IconThemePrivate : QPtrHolder<IconTheme> {
354 public:
IconThemePrivate(IconTheme * q,const StandardPath & path)355 IconThemePrivate(IconTheme *q, const StandardPath &path)
356 : QPtrHolder(q), standardPath_(path) {
357 const char *home = getenv("HOME");
358 if (home) {
359 home_ = home;
360 }
361 }
362
parse(const RawConfig & config,IconTheme * parent)363 void parse(const RawConfig &config, IconTheme *parent) {
364 if (!parent) {
365 subThemeNames_.insert(internalName_);
366 }
367
368 auto section = config.get("Icon Theme");
369 if (!section) {
370 // If it's top level theme, make it fallback to hicolor.
371 if (!parent) {
372 addInherit("hicolor");
373 }
374 return;
375 }
376
377 if (auto nameSection = section->get("Name")) {
378 unmarshallOption(name_, *nameSection, false);
379 }
380
381 if (auto commentSection = section->get("Comment")) {
382 unmarshallOption(comment_, *commentSection, false);
383 }
384
385 auto parseDirectory = [&config,
386 section](const char *name,
387 std::vector<IconThemeDirectory> &dir) {
388 if (auto subConfig = section->get(name)) {
389 std::string directories;
390 unmarshallOption(directories, *subConfig, false);
391 for (const auto &directory :
392 stringutils::split(directories, ",")) {
393 if (auto directoryConfig = config.get(directory)) {
394 try {
395 dir.emplace_back(*directoryConfig);
396 } catch (...) {
397 }
398 }
399 }
400 }
401 };
402 parseDirectory("Directories", directories_);
403 parseDirectory("ScaledDirectories", scaledDirectories_);
404 // if (auto subConfig = section->get("Hidden")) {
405 // unmarshallOption(hidden_, *subConfig, false);
406 // }
407 if (auto subConfig = section->get("Example")) {
408 unmarshallOption(example_, *subConfig, false);
409 }
410 if (auto subConfig = section->get("Inherits")) {
411 std::string inherits;
412 unmarshallOption(inherits, *subConfig, false);
413 for (const auto &inherit : stringutils::split(inherits, ",")) {
414 if (!parent) {
415 addInherit(inherit);
416 } else {
417 parent->d_ptr->addInherit(inherit);
418 }
419 }
420 }
421
422 // Always inherit hicolor.
423 if (!parent && !subThemeNames_.count("hicolor")) {
424 addInherit("hicolor");
425 }
426 }
427
addInherit(const std::string & inherit)428 void addInherit(const std::string &inherit) {
429 if (subThemeNames_.insert(inherit).second) {
430 try {
431 inherits_.push_back(IconTheme(inherit, q_ptr, standardPath_));
432 } catch (...) {
433 }
434 }
435 }
436
findIcon(const std::string & icon,int size,int scale,const std::vector<std::string> & extensions) const437 std::string findIcon(const std::string &icon, int size, int scale,
438 const std::vector<std::string> &extensions) const {
439 // Respect absolute path.
440 if (icon.empty() || icon[0] == '/') {
441 return icon;
442 }
443
444 std::string filename;
445 // If we're a empty theme, only look at fallback icon.
446 if (!internalName_.empty()) {
447 auto filename = findIconHelper(icon, size, scale, extensions);
448 if (!filename.empty()) {
449 return filename;
450 }
451 }
452 filename = lookupFallbackIcon(icon, extensions);
453
454 // We tries to violate the spec same as LXDE, only lookup dash fallback
455 // after looking through all existing themes.
456 if (filename.empty()) {
457 auto dashPos = icon.rfind('-');
458 if (dashPos != std::string::npos) {
459 filename =
460 findIcon(icon.substr(0, dashPos), size, scale, extensions);
461 }
462 }
463 return filename;
464 }
465
466 std::string
findIconHelper(const std::string & icon,int size,int scale,const std::vector<std::string> & extensions) const467 findIconHelper(const std::string &icon, int size, int scale,
468 const std::vector<std::string> &extensions) const {
469 auto filename = lookupIcon(icon, size, scale, extensions);
470 if (!filename.empty()) {
471 return filename;
472 }
473
474 for (const auto &inherit : inherits_) {
475 filename =
476 inherit.d_func()->lookupIcon(icon, size, scale, extensions);
477 if (!filename.empty()) {
478 return filename;
479 }
480 }
481 return {};
482 }
483
lookupIcon(const std::string & iconname,int size,int scale,const std::vector<std::string> & extensions) const484 std::string lookupIcon(const std::string &iconname, int size, int scale,
485 const std::vector<std::string> &extensions) const {
486
487 auto checkDirectory = [&extensions,
488 &iconname](const IconThemeDirectory &directory,
489 std::string baseDir) -> std::string {
490 baseDir = stringutils::joinPath(baseDir, directory.path());
491 if (!fs::isdir(baseDir)) {
492 return {};
493 }
494
495 for (const auto &ext : extensions) {
496 auto defaultPath = stringutils::joinPath(baseDir, iconname);
497 defaultPath += ext;
498 if (fs::isreg(defaultPath)) {
499 return defaultPath;
500 }
501 }
502 return {};
503 };
504
505 for (const auto &baseDir : baseDirs_) {
506 bool hasCache = false;
507 std::unordered_set<std::string> dirFilter;
508 if (baseDir.second.isValid()) {
509 dirFilter = baseDir.second.lookup(iconname);
510 hasCache = true;
511 }
512 for (const auto &directory : directories_) {
513 if ((hasCache && !dirFilter.count(directory.path())) ||
514 !directory.matchesSize(size, scale)) {
515 continue;
516 }
517 auto path = checkDirectory(directory, baseDir.first);
518 if (!path.empty()) {
519 return path;
520 }
521 }
522
523 if (scale != 1) {
524 for (const auto &directory : scaledDirectories_) {
525 if ((hasCache && !dirFilter.count(directory.path())) ||
526 !directory.matchesSize(size, scale)) {
527 continue;
528 }
529 auto path = checkDirectory(directory, baseDir.first);
530 if (!path.empty()) {
531 return path;
532 }
533 }
534 }
535 }
536
537 auto minSize = std::numeric_limits<int>::max();
538 std::string closestFilename;
539
540 for (const auto &baseDir : baseDirs_) {
541 bool hasCache = false;
542 std::unordered_set<std::string> dirFilter;
543 if (baseDir.second.isValid()) {
544 dirFilter = baseDir.second.lookup(iconname);
545 hasCache = true;
546 }
547
548 auto checkDirectoryWithSize =
549 [&checkDirectory, &closestFilename, &dirFilter, hasCache, size,
550 scale, &minSize, &baseDir](const IconThemeDirectory &dir) {
551 if (hasCache && !dirFilter.count(dir.path())) {
552 return;
553 }
554 auto distance = dir.sizeDistance(size, scale);
555 if (distance < minSize) {
556 auto path = checkDirectory(dir, baseDir.first);
557 if (!path.empty()) {
558 closestFilename = path;
559 minSize = distance;
560 }
561 }
562 };
563
564 for (const auto &directory : directories_) {
565 checkDirectoryWithSize(directory);
566 }
567
568 if (scale != 1) {
569 for (const auto &directory : scaledDirectories_) {
570 checkDirectoryWithSize(directory);
571 }
572 }
573 }
574
575 return closestFilename;
576 }
577
578 std::string
lookupFallbackIcon(const std::string & iconname,const std::vector<std::string> & extensions) const579 lookupFallbackIcon(const std::string &iconname,
580 const std::vector<std::string> &extensions) const {
581 auto defaultBasePath = stringutils::joinPath(home_, ".icons", iconname);
582 for (const auto &ext : extensions) {
583 auto path = defaultBasePath + ext;
584 if (fs::isreg(path)) {
585 return path;
586 }
587 path = standardPath_.locate(
588 StandardPath::Type::Data,
589 stringutils::joinPath("icons", iconname + ext));
590 if (!path.empty()) {
591 return path;
592 }
593 }
594 return {};
595 }
596
addBaseDir(const std::string & path)597 void addBaseDir(const std::string &path) {
598 if (!fs::isdir(path)) {
599 return;
600 }
601 baseDirs_.emplace_back(std::piecewise_construct,
602 std::forward_as_tuple(path),
603 std::forward_as_tuple(stringutils::joinPath(
604 path, "icon-theme.cache")));
605 }
606
prepare()607 void prepare() {
608 if (!home_.empty()) {
609 addBaseDir(stringutils::joinPath(home_, ".icons", internalName_));
610 }
611 if (auto userDir =
612 standardPath_.userDirectory(StandardPath::Type::Data);
613 !userDir.empty()) {
614 addBaseDir(stringutils::joinPath(userDir, "icons", internalName_));
615 }
616
617 for (auto &dataDir :
618 standardPath_.directories(StandardPath::Type::Data)) {
619 addBaseDir(stringutils::joinPath(dataDir, "icons", internalName_));
620 }
621 }
622
623 std::string home_;
624 std::string internalName_;
625 const StandardPath &standardPath_;
626 I18NString name_;
627 I18NString comment_;
628 std::vector<IconTheme> inherits_;
629 std::vector<IconThemeDirectory> directories_;
630 std::vector<IconThemeDirectory> scaledDirectories_;
631 std::unordered_set<std::string> subThemeNames_;
632 std::vector<std::pair<std::string, IconThemeCache>> baseDirs_;
633 // Not really useful for our usecase.
634 // bool hidden_;
635 std::string example_;
636 };
637
IconTheme(const std::string & name,const StandardPath & standardPath)638 IconTheme::IconTheme(const std::string &name, const StandardPath &standardPath)
639 : IconTheme(name, nullptr, standardPath) {}
640
IconTheme(const std::string & name,IconTheme * parent,const StandardPath & standardPath)641 IconTheme::IconTheme(const std::string &name, IconTheme *parent,
642 const StandardPath &standardPath)
643 : IconTheme(standardPath) {
644 FCITX_D();
645 auto files = standardPath.openAll(
646 StandardPath::Type::Data,
647 stringutils::joinPath("icons", name, "index.theme"), O_RDONLY);
648
649 RawConfig config;
650 for (auto iter = files.rbegin(), end = files.rend(); iter != end; iter++) {
651 readFromIni(config, iter->fd());
652 }
653 auto path = stringutils::joinPath(d->home_, ".icons", name, "index.theme");
654 auto fd = UnixFD::own(open(path.c_str(), O_RDONLY));
655 if (fd.fd() >= 0) {
656 readFromIni(config, fd.fd());
657 }
658
659 d->parse(config, parent);
660 d->internalName_ = name;
661 d->prepare();
662 }
663
IconTheme(const StandardPath & standardPath)664 IconTheme::IconTheme(const StandardPath &standardPath)
665 : d_ptr(std::make_unique<IconThemePrivate>(this, standardPath)) {}
666
667 FCITX_DEFINE_DEFAULT_DTOR_AND_MOVE(IconTheme);
668
669 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme, std::string, internalName);
670 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme, I18NString, name);
671 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme, I18NString, comment);
672 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme, std::vector<IconTheme>,
673 inherits);
674 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme,
675 std::vector<IconThemeDirectory>,
676 directories);
677 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme,
678 std::vector<IconThemeDirectory>,
679 scaledDirectories);
680 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme, std::string, example);
681
findIcon(const std::string & iconName,unsigned int desiredSize,int scale,const std::vector<std::string> & extensions)682 std::string IconTheme::findIcon(const std::string &iconName,
683 unsigned int desiredSize, int scale,
684 const std::vector<std::string> &extensions) {
685 return std::as_const(*this).findIcon(iconName, desiredSize, scale,
686 extensions);
687 }
688
689 std::string
findIcon(const std::string & iconName,unsigned int desiredSize,int scale,const std::vector<std::string> & extensions) const690 IconTheme::findIcon(const std::string &iconName, unsigned int desiredSize,
691 int scale,
692 const std::vector<std::string> &extensions) const {
693 FCITX_D();
694 return d->findIcon(iconName, desiredSize, scale, extensions);
695 }
696
getKdeTheme(int fd)697 std::string getKdeTheme(int fd) {
698 RawConfig rawConfig;
699 readFromIni(rawConfig, fd);
700 if (auto icons = rawConfig.get("Icons")) {
701 if (auto theme = icons->get("Theme")) {
702 if (!theme->value().empty() &&
703 theme->value().find('/') == std::string::npos) {
704 return theme->value();
705 }
706 }
707 }
708 return "";
709 }
710
getGtkTheme(const std::string & filename)711 std::string getGtkTheme(const std::string &filename) {
712 // Evil Gtk2 use an non standard "ini-like" rc file.
713 // Grep it and try to find the line we want ourselves.
714 std::ifstream fin(filename, std::ios::in | std::ios::binary);
715 std::string line;
716 while (std::getline(fin, line)) {
717 auto tokens = stringutils::split(line, "=");
718 if (tokens.size() == 2 &&
719 stringutils::trim(tokens[0]) == "gtk-icon-theme-name") {
720 auto value = stringutils::trim(tokens[1]);
721 if (value.size() >= 2 && value.front() == '"' &&
722 value.back() == '"') {
723 value = value.substr(1, value.size() - 2);
724 }
725 if (!value.empty() && value.find('/') == std::string::npos) {
726 return value;
727 }
728 }
729 }
730 return "";
731 }
732
defaultIconThemeName()733 std::string IconTheme::defaultIconThemeName() {
734 DesktopType desktopType = getDesktopType();
735 switch (desktopType) {
736 case DesktopType::KDE5: {
737 auto files = StandardPath::global().openAll(StandardPath::Type::Config,
738 "kdeglobals", O_RDONLY);
739 for (auto &file : files) {
740 auto theme = getKdeTheme(file.fd());
741 if (!theme.empty()) {
742 return theme;
743 }
744 }
745
746 return "breeze";
747 }
748 case DesktopType::KDE4: {
749 const char *home = getenv("HOME");
750 if (home && home[0]) {
751 std::string files[] = {
752 stringutils::joinPath(home, ".kde4/share/config/kdeglobals"),
753 stringutils::joinPath(home, ".kde/share/config/kdeglobals"),
754 "/etc/kde4/kdeglobals"};
755 for (auto &file : files) {
756 auto fd = UnixFD::own(open(file.c_str(), O_RDONLY));
757 auto theme = getKdeTheme(fd.fd());
758 if (!theme.empty()) {
759 return theme;
760 }
761 }
762 }
763 return "oxygen";
764 }
765 default: {
766 auto files = StandardPath::global().locateAll(
767 StandardPath::Type::Config, "gtk-3.0/settings.ini");
768 for (auto &file : files) {
769 auto theme = getGtkTheme(file);
770 if (!theme.empty()) {
771 return theme;
772 }
773 }
774 auto theme = getGtkTheme("/etc/gtk-3.0/settings.ini");
775 if (!theme.empty()) {
776 return theme;
777 }
778 const char *home = getenv("HOME");
779 if (home && home[0]) {
780 std::string homeStr(home);
781 std::string files[] = {stringutils::joinPath(homeStr, ".gtkrc-2.0"),
782 "/etc/gtk-2.0/gtkrc"};
783 for (auto &file : files) {
784 auto theme = getGtkTheme(file);
785 if (!theme.empty()) {
786 return theme;
787 }
788 }
789 }
790 } break;
791 }
792
793 if (desktopType == DesktopType::Unknown) {
794 return "Tango";
795 } else if (desktopType == DesktopType::GNOME) {
796 return "Adwaita";
797 }
798 return "gnome";
799 }
800
801 /// Rename fcitx-* icon to org.fcitx.Fcitx5.fcitx-* if in flatpak
iconName(const std::string & icon,bool inFlatpak)802 std::string IconTheme::iconName(const std::string &icon, bool inFlatpak) {
803 #ifdef USE_FLATPAK_ICON
804 if (inFlatpak && stringutils::startsWith(icon, "fcitx-")) {
805 return stringutils::concat("org.fcitx.Fcitx5.", icon);
806 }
807 #else
808 FCITX_UNUSED(inFlatpak);
809 #endif
810 return icon;
811 }
812 } // namespace fcitx
813