Fcitx
icontheme.cpp
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/stat.h>
10 #include <cstddef>
11 #include <cstdint>
12 #include <cstdlib>
13 #include <filesystem>
14 #include <fstream>
15 #include <functional>
16 #include <ios>
17 #include <limits>
18 #include <memory>
19 #include <stdexcept>
20 #include <string>
21 #include <string_view>
22 #include <system_error>
23 #include <tuple>
24 #include <unordered_set>
25 #include <utility>
26 #include <variant>
27 #include <vector>
28 #include <ranges>
29 #include "fcitx-config/iniparser.h"
30 #include "fcitx-config/marshallfunction.h"
31 #include "fcitx-config/rawconfig.h"
32 #include "fcitx-utils/environ.h"
33 #include "fcitx-utils/fs.h"
34 #include "fcitx-utils/i18nstring.h"
35 #include "fcitx-utils/macros.h"
39 #include "config.h" // IWYU pragma: keep
40 #include "misc_p.h"
41 
42 #ifdef HAVE_SYS_MMAN_H
43 #include <sys/mman.h>
44 #endif
45 
46 namespace {
47 
48 // helper type for the visitor
49 template <class... Ts>
50 struct VariantOverload : Ts... {
51  using Ts::operator()...;
52 };
53 } // namespace
54 
55 namespace fcitx {
56 
57 std::string pathToRoot(const RawConfig &config) {
58  std::string path;
59  const auto *pConfig = &config;
60  size_t length = 0;
61  while (pConfig) {
62  if (pConfig->parent() && length) {
63  length += 1; // For "/";
64  }
65  length += pConfig->name().size();
66  pConfig = pConfig->parent();
67  }
68 
69  pConfig = &config;
70  path.resize(length);
71  size_t currentLength = 0;
72  while (pConfig) {
73  if (pConfig->parent() && currentLength) {
74  currentLength += 1; // For "/";
75  path[length - currentLength] = '/';
76  }
77  const auto &seg = pConfig->name();
78  currentLength += seg.size();
79  path.replace(length - currentLength, seg.size(), seg);
80  pConfig = pConfig->parent();
81  }
82  return path;
83 }
84 
86 public:
88  : path_(pathToRoot(config)) {
89  if (path_.empty() || path_[0] == '/') {
90  throw std::invalid_argument("Invalid path.");
91  }
92 
93  if (auto subConfig = config.get("Size")) {
94  unmarshallOption(size_, *subConfig, false);
95  }
96  if (size_ <= 0) {
97  throw std::invalid_argument("Invalid size");
98  }
99 
100  if (auto subConfig = config.get("Scale")) {
101  unmarshallOption(scale_, *subConfig, false);
102  }
103  if (auto subConfig = config.get("Context")) {
104  unmarshallOption(context_, *subConfig, false);
105  }
106  if (auto subConfig = config.get("Type")) {
107  unmarshallOption(type_, *subConfig, false);
108  }
109  if (auto subConfig = config.get("MaxSize")) {
110  unmarshallOption(maxSize_, *subConfig, false);
111  }
112  if (auto subConfig = config.get("MinSize")) {
113  unmarshallOption(minSize_, *subConfig, false);
114  }
115  if (auto subConfig = config.get("Threshold")) {
116  unmarshallOption(threshold_, *subConfig, false);
117  }
118 
119  if (maxSize_ <= 0) {
120  maxSize_ = size_;
121  }
122 
123  if (minSize_ <= 0) {
124  minSize_ = size_;
125  }
126  }
127 
128  FCITX_INLINE_DEFINE_DEFAULT_DTOR_COPY_AND_MOVE_WITHOUT_SPEC(
130 
131  std::string path_;
132  int size_ = 0;
133  int scale_ = 1;
134  std::string context_;
135  IconThemeDirectoryType type_ = IconThemeDirectoryType::Threshold;
136  int maxSize_ = 0;
137  int minSize_ = 0;
138  int threshold_ = 2;
139 };
140 
141 IconThemeDirectory::IconThemeDirectory(const RawConfig &config)
142  : d_ptr(std::make_unique<IconThemeDirectoryPrivate>(config)) {}
143 
144 FCITX_DEFINE_DPTR_COPY_AND_DEFAULT_DTOR_AND_MOVE(IconThemeDirectory);
145 
146 bool IconThemeDirectory::matchesSize(int iconsize, int iconscale) const {
147  if (scale() != iconscale) {
148  return false;
149  }
150  switch (type()) {
151  case IconThemeDirectoryType::Fixed:
152  return iconsize == size();
153  case IconThemeDirectoryType::Scalable:
154  return minSize() <= iconsize && iconsize <= maxSize();
155  case IconThemeDirectoryType::Threshold:
156  return size() - threshold() <= iconsize &&
157  iconsize <= size() + threshold();
158  }
159  return false;
160 }
161 
162 int IconThemeDirectory::sizeDistance(int iconsize, int iconscale) const {
163  switch (type()) {
164  case IconThemeDirectoryType::Fixed:
165  return std::abs((size() * scale()) - (iconsize * iconscale));
166  case IconThemeDirectoryType::Scalable:
167  if (iconsize * iconscale < minSize() * scale()) {
168  return (minSize() * scale()) - (iconsize * iconscale);
169  }
170  if (iconsize * iconscale > maxSize() * scale()) {
171  return (iconsize * iconscale) - (maxSize() * scale());
172  }
173  return 0;
174  case IconThemeDirectoryType::Threshold:
175  if (iconsize * iconscale < (size() - threshold()) * scale()) {
176  return ((size() - threshold()) * scale()) - (iconsize * iconscale);
177  }
178  if (iconsize * iconscale > (size() + threshold()) * scale()) {
179  return (iconsize * iconscale) - ((size() - threshold()) * scale());
180  }
181  }
182  return 0;
183 }
184 
185 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, std::string, path);
186 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, int, size);
187 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, int, scale);
188 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, std::string,
189  context);
190 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory,
191  IconThemeDirectoryType, type);
192 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, int, maxSize);
193 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, int, minSize);
194 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, int, threshold);
195 
196 static uint32_t iconNameHash(const char *p) {
197  uint32_t h = static_cast<signed char>(*p);
198  for (p += 1; *p != '\0'; p++) {
199  h = (h << 5) - h + *p;
200  }
201  return h;
202 }
203 
205 public:
206  IconThemeCache(const std::filesystem::path &filename) {
207 #ifdef _WIN32
208  FCITX_UNUSED(filename);
209 #else
210  auto fd = StandardPaths::openPath(filename);
211  if (!fd.isValid()) {
212  return;
213  }
214  auto fileModifiedTime = fs::modifiedTime(filename);
215  if (fileModifiedTime == 0) {
216  return;
217  }
218  auto dirName = filename.parent_path();
219  auto dirModifiedTime = fs::modifiedTime(dirName);
220  if (dirModifiedTime == 0) {
221  return;
222  }
223  if (fileModifiedTime < dirModifiedTime) {
224  return;
225  }
226 
227  struct stat st;
228  if (fstat(fd.fd(), &st) < 0) {
229  return;
230  }
231 
232  memory_ = static_cast<uint8_t *>(
233  mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd.fd(), 0));
234  if (!memory_) {
235  // Failed to mmap is not critical here.
236  return;
237  }
238  size_ = st.st_size;
239 
240  if (readWord(0) != 1 || readWord(2) != 0) { // version major minor
241  return;
242  }
243 
244  isValid_ = true;
245 
246  // Check that all the directories are older than the cache
247  uint32_t dirListOffset = readDoubleWord(8);
248  uint32_t dirListLen = readDoubleWord(dirListOffset);
249  // We assume it won't be so long just in case we hit a corruptted file.
250  if (dirListLen > 4096) {
251  isValid_ = false;
252  return;
253  }
254  for (uint32_t i = 0; i < dirListLen; ++i) {
255  uint32_t offset = readDoubleWord(dirListOffset + 4 + (4 * i));
256  if (!isValid_ || offset >= size_) {
257  isValid_ = false;
258  return;
259  }
260  auto *dir = checkString(offset);
261  if (!dir) {
262  isValid_ = false;
263  return;
264  }
265  std::filesystem::path subdirName(dir);
266  if (subdirName.is_absolute()) {
267  isValid_ = false;
268  return;
269  }
270  auto subDirTime = fs::modifiedTime(dirName / subdirName);
271  if (fileModifiedTime < subDirTime) {
272  isValid_ = false;
273  return;
274  }
275  }
276 #endif
277  }
278 
279  IconThemeCache() = default;
280  IconThemeCache(IconThemeCache &&other) noexcept
281  : isValid_(other.isValid_), memory_(other.memory_), size_(other.size_) {
282  other.isValid_ = false;
283  other.memory_ = nullptr;
284  other.size_ = 0;
285  }
286 
287  IconThemeCache &operator=(IconThemeCache other) {
288  std::swap(other.isValid_, isValid_);
289  std::swap(other.size_, size_);
290  std::swap(other.memory_, memory_);
291  return *this;
292  }
293 
294  bool isValid() const { return isValid_; }
295 
296  ~IconThemeCache() {
297 #ifndef _WIN32
298  if (memory_) {
299  munmap(memory_, size_);
300  }
301 #endif
302  }
303 
304  uint16_t readWord(uint32_t offset) const {
305  if (offset > size_ - 2 || (offset % 2)) {
306  isValid_ = false;
307  return 0;
308  }
309  return memory_[offset + 1] | memory_[offset] << 8;
310  }
311  uint32_t readDoubleWord(unsigned int offset) const {
312  if (offset > size_ - 4 || (offset % 4)) {
313  isValid_ = false;
314  return 0;
315  }
316  return memory_[offset + 3] | memory_[offset + 2] << 8 |
317  memory_[offset + 1] << 16 | memory_[offset] << 24;
318  }
319 
320  char *checkString(uint32_t offset) const {
321  // assume string length is less than 1k.
322  for (uint32_t i = 0; i < 1024; i++) {
323  if (offset + i >= size_) {
324  return nullptr;
325  }
326  auto c = memory_[offset + i];
327  if (c == '\0') {
328  break;
329  }
330  }
331  return reinterpret_cast<char *>(memory_ + offset);
332  }
333 
334  std::unordered_set<std::string> lookup(const std::string &name) const;
335 
336  mutable bool isValid_ = false;
337  uint8_t *memory_ = nullptr;
338  size_t size_ = 0;
339 };
340 
341 std::unordered_set<std::string>
342 IconThemeCache::lookup(const std::string &name) const {
343  std::unordered_set<std::string> ret;
344  auto hash = iconNameHash(name.c_str());
345 
346  uint32_t hashOffset = readDoubleWord(4);
347  uint32_t hashBucketCount = readDoubleWord(hashOffset);
348 
349  if (!isValid_ || hashBucketCount == 0) {
350  isValid_ = false;
351  return ret;
352  }
353 
354  uint32_t bucketIndex = hash % hashBucketCount;
355  uint32_t bucketOffset = readDoubleWord(hashOffset + 4 + (bucketIndex * 4));
356  while (bucketOffset > 0 && bucketOffset <= size_ - 12) {
357  uint32_t nameOff = readDoubleWord(bucketOffset + 4);
358  auto *namePtr = checkString(nameOff);
359  if (nameOff < size_ && namePtr && name == namePtr) {
360  uint32_t dirListOffset = readDoubleWord(8);
361  uint32_t dirListLen = readDoubleWord(dirListOffset);
362 
363  uint32_t listOffset = readDoubleWord(bucketOffset + 8);
364  uint32_t listLen = readDoubleWord(listOffset);
365 
366  if (!isValid_ || listOffset + 4 + 8 * listLen > size_) {
367  isValid_ = false;
368  return ret;
369  }
370 
371  ret.reserve(listLen);
372  for (uint32_t j = 0; j < listLen && isValid_; ++j) {
373  uint32_t dirIndex = readWord(listOffset + 4 + (8 * j));
374  uint32_t o = readDoubleWord(dirListOffset + 4 + (dirIndex * 4));
375  if (!isValid_ || dirIndex >= dirListLen || o >= size_) {
376  isValid_ = false;
377  return ret;
378  }
379  if (auto *str = checkString(o)) {
380  ret.emplace(str);
381  } else {
382  return {};
383  }
384  }
385  return ret;
386  }
387  bucketOffset = readDoubleWord(bucketOffset);
388  }
389  return ret;
390 }
391 
392 class IconThemePrivate : QPtrHolder<IconTheme> {
393 public:
394  IconThemePrivate(IconTheme *q, const StandardPath &path)
395  : QPtrHolder(q), standardPath_(std::cref(path)) {
396  if (auto home = getEnvironment("HOME")) {
397  home_ = *home;
398  }
399  }
400  IconThemePrivate(IconTheme *q, const StandardPaths &path)
401  : QPtrHolder(q), standardPath_(path) {
402  if (auto home = getEnvironment("HOME")) {
403  home_ = *home;
404  }
405  }
406 
407  void parse(const RawConfig &config, IconTheme *parent) {
408  if (!parent) {
409  subThemeNames_.insert(internalName_);
410  }
411 
412  auto section = config.get("Icon Theme");
413  if (!section) {
414  // If it's top level theme, make it fallback to hicolor.
415  if (!parent) {
416  addInherit("hicolor");
417  }
418  return;
419  }
420 
421  if (auto nameSection = section->get("Name")) {
422  unmarshallOption(name_, *nameSection, false);
423  }
424 
425  if (auto commentSection = section->get("Comment")) {
426  unmarshallOption(comment_, *commentSection, false);
427  }
428 
429  auto parseDirectory = [&config,
430  section](const char *name,
431  std::vector<IconThemeDirectory> &dir) {
432  if (auto subConfig = section->get(name)) {
433  std::string directories;
434  unmarshallOption(directories, *subConfig, false);
435  for (const auto &directory :
436  stringutils::split(directories, ",")) {
437  if (auto directoryConfig = config.get(directory)) {
438  try {
439  dir.emplace_back(*directoryConfig);
440  } catch (...) {
441  }
442  }
443  }
444  }
445  };
446  parseDirectory("Directories", directories_);
447  parseDirectory("ScaledDirectories", scaledDirectories_);
448  // if (auto subConfig = section->get("Hidden")) {
449  // unmarshallOption(hidden_, *subConfig, false);
450  // }
451  if (auto subConfig = section->get("Example")) {
452  unmarshallOption(example_, *subConfig, false);
453  }
454  if (auto subConfig = section->get("Inherits")) {
455  std::string inherits;
456  unmarshallOption(inherits, *subConfig, false);
457  for (const auto &inherit : stringutils::split(inherits, ",")) {
458  if (!parent) {
459  addInherit(inherit);
460  } else {
461  parent->d_ptr->addInherit(inherit);
462  }
463  }
464  }
465 
466  // Always inherit hicolor.
467  if (!parent && !subThemeNames_.contains("hicolor")) {
468  addInherit("hicolor");
469  }
470  }
471 
472  void addInherit(const std::string &inherit) {
473  if (subThemeNames_.insert(inherit).second) {
474  try {
475  std::visit(VariantOverload{
476  [&](const StandardPath &path) {
477  inherits_.push_back(
478  IconTheme(inherit, q_ptr, path));
479  },
480  [&](const StandardPaths &path) {
481  inherits_.push_back(
482  IconTheme(inherit, q_ptr, path));
483  },
484  },
485  standardPath_);
486  } catch (...) {
487  }
488  }
489  }
490 
491  std::filesystem::path
492  findIcon(const std::string &icon, int size, int scale,
493  const std::vector<std::string> &extensions) const {
494  // Respect absolute path.
495  if (icon.empty() || icon[0] == '/') {
496  return icon;
497  }
498 
499  std::filesystem::path filename;
500  // If we're a empty theme, only look at fallback icon.
501  if (!internalName_.empty()) {
502  auto filename = findIconHelper(icon, size, scale, extensions);
503  if (!filename.empty()) {
504  return filename;
505  }
506  }
507  filename = lookupFallbackIcon(icon, extensions);
508 
509  // We tries to violate the spec same as LXDE, only lookup dash fallback
510  // after looking through all existing themes.
511  if (filename.empty()) {
512  auto dashPos = icon.rfind('-');
513  if (dashPos != std::string::npos) {
514  filename =
515  findIcon(icon.substr(0, dashPos), size, scale, extensions);
516  }
517  }
518  return filename;
519  }
520 
521  std::filesystem::path
522  findIconHelper(const std::string &icon, int size, int scale,
523  const std::vector<std::string> &extensions) const {
524  auto filename = lookupIcon(icon, size, scale, extensions);
525  if (!filename.empty()) {
526  return filename;
527  }
528 
529  for (const auto &inherit : inherits_) {
530  filename =
531  inherit.d_func()->lookupIcon(icon, size, scale, extensions);
532  if (!filename.empty()) {
533  return filename;
534  }
535  }
536  return {};
537  }
538 
539  std::filesystem::path
540  lookupIcon(const std::string &iconname, int size, int scale,
541  const std::vector<std::string> &extensions) const {
542 
543  auto checkDirectory =
544  [&extensions, &iconname](
545  const IconThemeDirectory &directory,
546  std::filesystem::path baseDir) -> std::filesystem::path {
547  baseDir = baseDir / directory.path();
548  std::error_code ec;
549  if (!std::filesystem::is_directory(baseDir, ec)) {
550  return {};
551  }
552 
553  for (const auto &ext : extensions) {
554  auto defaultPath = baseDir / (iconname + ext);
555  std::error_code ec;
556  if (std::filesystem::is_regular_file(defaultPath, ec)) {
557  return defaultPath;
558  }
559  }
560  return {};
561  };
562 
563  for (const auto &baseDir : baseDirs_) {
564  bool hasCache = false;
565  std::unordered_set<std::string> dirFilter;
566  if (baseDir.second.isValid()) {
567  dirFilter = baseDir.second.lookup(iconname);
568  hasCache = true;
569  }
570  for (const auto &directory : directories_) {
571  if ((hasCache && !dirFilter.contains(directory.path())) ||
572  !directory.matchesSize(size, scale)) {
573  continue;
574  }
575  auto path = checkDirectory(directory, baseDir.first);
576  if (!path.empty()) {
577  return path;
578  }
579  }
580 
581  if (scale != 1) {
582  for (const auto &directory : scaledDirectories_) {
583  if ((hasCache && !dirFilter.contains(directory.path())) ||
584  !directory.matchesSize(size, scale)) {
585  continue;
586  }
587  auto path = checkDirectory(directory, baseDir.first);
588  if (!path.empty()) {
589  return path;
590  }
591  }
592  }
593  }
594 
595  auto minSize = std::numeric_limits<int>::max();
596  std::filesystem::path closestFilename;
597 
598  for (const auto &baseDir : baseDirs_) {
599  bool hasCache = false;
600  std::unordered_set<std::string> dirFilter;
601  if (baseDir.second.isValid()) {
602  dirFilter = baseDir.second.lookup(iconname);
603  hasCache = true;
604  }
605 
606  auto checkDirectoryWithSize =
607  [&checkDirectory, &closestFilename, &dirFilter, hasCache, size,
608  scale, &minSize, &baseDir](const IconThemeDirectory &dir) {
609  if (hasCache && !dirFilter.contains(dir.path())) {
610  return;
611  }
612  auto distance = dir.sizeDistance(size, scale);
613  if (distance < minSize) {
614  if (auto path = checkDirectory(dir, baseDir.first);
615  !path.empty()) {
616  closestFilename = std::move(path);
617  minSize = distance;
618  }
619  }
620  };
621 
622  for (const auto &directory : directories_) {
623  checkDirectoryWithSize(directory);
624  }
625 
626  if (scale != 1) {
627  for (const auto &directory : scaledDirectories_) {
628  checkDirectoryWithSize(directory);
629  }
630  }
631  }
632 
633  return closestFilename;
634  }
635 
636  std::filesystem::path
637  lookupFallbackIcon(const std::string &iconname,
638  const std::vector<std::string> &extensions) const {
639  auto defaultBasePath = home_ / ".icons" / iconname;
640  for (const auto &ext : extensions) {
641  auto path = defaultBasePath;
642  path += ext;
643  std::error_code ec;
644  if (std::filesystem::is_regular_file(path, ec)) {
645  return path;
646  }
647  std::visit(
648  VariantOverload{
649  [&](const StandardPath &sppath) {
650  path = sppath.locate(
651  StandardPath::Type::Data,
652  (std::filesystem::path("icons") / (iconname + ext))
653  .string());
654  },
655  [&](const StandardPaths &sppath) {
656  path = sppath.locate(StandardPathsType::Data,
657  std::filesystem::path("icons") /
658  (iconname + ext));
659  },
660  },
661  standardPath_);
662  if (!path.empty()) {
663  return path;
664  }
665  }
666  return {};
667  }
668 
669  void addBaseDir(const std::filesystem::path &path) {
670  std::error_code ec;
671  if (!std::filesystem::is_directory(path, ec)) {
672  return;
673  }
674  baseDirs_.emplace_back(
675  std::piecewise_construct, std::forward_as_tuple(path),
676  std::forward_as_tuple(path / "icon-theme.cache"));
677  }
678 
679  void prepare() {
680  if (!home_.empty()) {
681  addBaseDir(home_ / ".icons" / internalName_);
682  }
683  if (auto userDir = std::visit(
684  VariantOverload{
685  [&](const StandardPath &sppath) {
686  return std::filesystem::path(
687  sppath.userDirectory(StandardPath::Type::Data));
688  },
689  [&](const StandardPaths &sppath) {
690  return sppath.userDirectory(StandardPathsType::Data);
691  },
692  },
693  standardPath_);
694  !userDir.empty()) {
695  addBaseDir(userDir / "icons" / internalName_);
696  }
697 
698  for (auto &dataDir : std::visit(
699  VariantOverload{
700  [&](const StandardPath &sppath) {
701  auto paths =
702  sppath.directories(StandardPath::Type::Data);
703  return std::vector<std::filesystem::path>(
704  paths.begin(), paths.end());
705  },
706  [&](const StandardPaths &sppath) {
707  auto dirs =
708  sppath.directories(StandardPathsType::Data);
709  return std::vector<std::filesystem::path>{dirs.begin(),
710  dirs.end()};
711  },
712  },
713  standardPath_)) {
714  addBaseDir(dataDir / "icons" / internalName_);
715  }
716  }
717 
718  std::filesystem::path home_;
719  std::string internalName_;
720  std::variant<std::reference_wrapper<const StandardPath>,
721  std::reference_wrapper<const StandardPaths>>
722  standardPath_;
723  I18NString name_;
724  I18NString comment_;
725  std::vector<IconTheme> inherits_;
726  std::vector<IconThemeDirectory> directories_;
727  std::vector<IconThemeDirectory> scaledDirectories_;
728  std::unordered_set<std::string> subThemeNames_;
729  std::vector<std::pair<std::filesystem::path, IconThemeCache>> baseDirs_;
730  // Not really useful for our usecase.
731  // bool hidden_;
732  std::string example_;
733 };
734 
735 IconTheme::IconTheme(const std::string &name, const StandardPath &standardPath)
736  : IconTheme(name, nullptr, standardPath) {}
737 
738 IconTheme::IconTheme(const std::string &name, IconTheme *parent,
739  const StandardPath &standardPath)
740  : IconTheme(standardPath) {
741  FCITX_D();
742  auto files = standardPath.openAll(
743  StandardPath::Type::Data,
744  (std::filesystem::path("icons") / name / "index.theme").string(),
745  O_RDONLY);
746 
747  RawConfig config;
748  for (auto &file : files | std::views::reverse) {
749  readFromIni(config, file.fd());
750  }
751  auto path = d->home_ / ".icons" / name / "index.theme";
752  auto fd = StandardPaths::openPath(path);
753  if (fd.fd() >= 0) {
754  readFromIni(config, fd.fd());
755  }
756 
757  d->parse(config, parent);
758  d->internalName_ = name;
759  d->prepare();
760 }
761 
762 IconTheme::IconTheme(const StandardPath &standardPath)
763  : d_ptr(std::make_unique<IconThemePrivate>(this, standardPath)) {}
764 
765 IconTheme::IconTheme(const std::string &name, const StandardPaths &standardPath)
766  : IconTheme(name, nullptr, standardPath) {}
767 
768 IconTheme::IconTheme(const std::string &name, IconTheme *parent,
769  const StandardPaths &standardPath)
770  : IconTheme(standardPath) {
771  FCITX_D();
772  auto files = standardPath.openAll(StandardPathsType::Data,
773  std::filesystem::path("icons") / name /
774  "index.theme");
775 
776  RawConfig config;
777  for (auto &file : files | std::views::reverse) {
778  readFromIni(config, file.fd());
779  }
780  auto path = d->home_ / ".icons" / name / "index.theme";
781  auto fd = StandardPaths::openPath(path);
782  if (fd.isValid()) {
783  readFromIni(config, fd.fd());
784  }
785 
786  d->parse(config, parent);
787  d->internalName_ = name;
788  d->prepare();
789 }
790 
791 IconTheme::IconTheme(const StandardPaths &standardPath)
792  : d_ptr(std::make_unique<IconThemePrivate>(this, standardPath)) {}
793 
794 FCITX_DEFINE_DEFAULT_DTOR_AND_MOVE(IconTheme);
795 
796 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme, std::string, internalName);
797 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme, I18NString, name);
798 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme, I18NString, comment);
799 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme, std::vector<IconTheme>,
800  inherits);
801 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme,
802  std::vector<IconThemeDirectory>,
803  directories);
804 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme,
805  std::vector<IconThemeDirectory>,
806  scaledDirectories);
807 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconTheme, std::string, example);
808 
809 std::string IconTheme::findIcon(const std::string &iconName,
810  unsigned int desiredSize, int scale,
811  const std::vector<std::string> &extensions) {
812  return std::as_const(*this)
813  .findIconPath(iconName, desiredSize, scale, extensions)
814  .string();
815 }
816 
817 std::string
818 IconTheme::findIcon(const std::string &iconName, unsigned int desiredSize,
819  int scale,
820  const std::vector<std::string> &extensions) const {
821  return findIconPath(iconName, desiredSize, scale, extensions).string();
822 }
823 
824 std::filesystem::path
825 IconTheme::findIconPath(const std::string &iconName, unsigned int desiredSize,
826  int scale,
827  const std::vector<std::string> &extensions) const {
828  FCITX_D();
829  return d->findIcon(iconName, desiredSize, scale, extensions);
830 }
831 
832 std::string getKdeTheme(int fd) {
833  RawConfig rawConfig;
834  readFromIni(rawConfig, fd);
835  if (auto icons = rawConfig.get("Icons")) {
836  if (auto theme = icons->get("Theme")) {
837  if (!theme->value().empty() &&
838  theme->value().find('/') == std::string::npos) {
839  return theme->value();
840  }
841  }
842  }
843  return "";
844 }
845 
846 std::string getGtkTheme(const std::filesystem::path &filename) {
847  // Evil Gtk2 use an non standard "ini-like" rc file.
848  // Grep it and try to find the line we want ourselves.
849  std::ifstream fin(filename, std::ios::in | std::ios::binary);
850  std::string line;
851  while (std::getline(fin, line)) {
852  auto tokens = stringutils::split(line, "=");
853  if (tokens.size() == 2 &&
854  stringutils::trim(tokens[0]) == "gtk-icon-theme-name") {
855  auto value = stringutils::trim(tokens[1]);
856  if (value.size() >= 2 && value.front() == '"' &&
857  value.back() == '"') {
858  value = value.substr(1, value.size() - 2);
859  }
860  if (!value.empty() && value.find('/') == std::string::npos) {
861  return value;
862  }
863  }
864  }
865  return "";
866 }
867 
868 std::string IconTheme::defaultIconThemeName() {
869  DesktopType desktopType = getDesktopType();
870  switch (desktopType) {
871  case DesktopType::KDE6:
872  case DesktopType::KDE5: {
873  auto files = StandardPaths::global().openAll(StandardPathsType::Config,
874  "kdeglobals");
875  for (auto &file : files) {
876  auto theme = getKdeTheme(file.fd());
877  if (!theme.empty()) {
878  return theme;
879  }
880  }
881 
882  return "breeze";
883  }
884  case DesktopType::KDE4: {
885  auto home = getEnvironment("HOME");
886  if (home && !home->empty()) {
887  std::vector<std::filesystem::path> files = {
888  std::filesystem::path(*home) / ".kde4/share/config/kdeglobals",
889  std::filesystem::path(*home) / ".kde/share/config/kdeglobals",
890  "/etc/kde4/kdeglobals"};
891  for (auto &file : files) {
892  auto fd = StandardPaths::openPath(file);
893  auto theme = getKdeTheme(fd.fd());
894  if (!theme.empty()) {
895  return theme;
896  }
897  }
898  }
899  return "oxygen";
900  }
901  default: {
902  auto files = StandardPaths::global().locateAll(
903  StandardPathsType::Config, "gtk-3.0/settings.ini");
904  for (auto &file : files) {
905  auto theme = getGtkTheme(file);
906  if (!theme.empty()) {
907  return theme;
908  }
909  }
910  auto theme = getGtkTheme("/etc/gtk-3.0/settings.ini");
911  if (!theme.empty()) {
912  return theme;
913  }
914  auto home = getEnvironment("HOME");
915  if (home && !home->empty()) {
916  std::vector<std::filesystem::path> files = {
917  std::filesystem::path(*home) / ".gtkrc-2.0",
918  "/etc/gtk-2.0/gtkrc"};
919  for (auto &file : files) {
920  auto theme = getGtkTheme(file);
921  if (!theme.empty()) {
922  return theme;
923  }
924  }
925  }
926  } break;
927  }
928 
929  if (desktopType == DesktopType::Unknown) {
930  return "Tango";
931  }
932  if (desktopType == DesktopType::GNOME) {
933  return "Adwaita";
934  }
935  return "gnome";
936 }
937 
938 /// Rename fcitx-* icon to org.fcitx.Fcitx5.fcitx-* if in flatpak
939 std::string IconTheme::iconName(const std::string &icon, bool inFlatpak) {
940  constexpr std::string_view fcitxIconPrefix = "fcitx";
941  if (inFlatpak && stringutils::startsWith(icon, fcitxIconPrefix)) {
942  // Map "fcitx" to org.fcitx.Fcitx5
943  // And map fcitx* to org.fcitx.Fcitx5.fcitx*
944  if (icon.size() == fcitxIconPrefix.size()) {
945  return "org.fcitx.Fcitx5";
946  }
947  return stringutils::concat("org.fcitx.Fcitx5.", icon);
948  }
949  return icon;
950 }
951 } // namespace fcitx
std::vector< UnixFD > openAll(StandardPathsType type, const std::filesystem::path &path, StandardPathsModes modes=StandardPathsMode::Default, std::vector< std::filesystem::path > *outPath=nullptr) const
Open the all matched and file for read.
Simple file system related API for checking file status.
size_t length(Iter start, Iter end)
Return the number UTF-8 characters in the string iterator range.
Definition: utf8.h:33
Definition: action.cpp:17
std::vector< StandardPathFile > openAll(Type type, const std::string &path, int flags) const
Open all files match the first [directory]/[path].
std::vector< std::string > split(std::string_view str, std::string_view delim, SplitBehavior behavior)
Split the string by delim.
New Utility classes to handle application specific path.
XDG icon specification helper.
std::string dirName(const std::string &path)
Get directory name of path.
Definition: fs.cpp:192
A implementation of freedesktop.org icont specification.
Definition: icontheme.h:58
Utility classes to handle XDG file path.
Utility class to open, locate, list files based on XDG standard.
Definition: standardpath.h:181
bool startsWith(std::string_view str, std::string_view prefix)
Check if a string starts with a prefix.
Definition: stringutils.cpp:86
String handle utilities.
int64_t modifiedTime(const std::filesystem::path &path)
Return modified time in seconds of given path.
Definition: fs.cpp:271
std::string trim(std::string_view str)
Trim the white space in str.
std::filesystem::path locate(StandardPathsType type, const std::filesystem::path &path, StandardPathsModes modes=StandardPathsMode::Default) const
Check if a file exists.