21 #include <string_view> 22 #include <system_error> 24 #include <unordered_set> 29 #include "fcitx-config/iniparser.h" 30 #include "fcitx-config/marshallfunction.h" 31 #include "fcitx-config/rawconfig.h" 32 #include "fcitx-utils/environ.h" 34 #include "fcitx-utils/i18nstring.h" 35 #include "fcitx-utils/macros.h" 42 #ifdef HAVE_SYS_MMAN_H 49 template <
class... Ts>
50 struct VariantOverload : Ts... {
51 using Ts::operator()...;
57 std::string pathToRoot(
const RawConfig &config) {
59 const auto *pConfig = &config;
62 if (pConfig->parent() &&
length) {
65 length += pConfig->name().size();
66 pConfig = pConfig->parent();
71 size_t currentLength = 0;
73 if (pConfig->parent() && currentLength) {
75 path[length - currentLength] =
'/';
77 const auto &seg = pConfig->name();
78 currentLength += seg.size();
79 path.replace(length - currentLength, seg.size(), seg);
80 pConfig = pConfig->parent();
88 : path_(pathToRoot(config)) {
89 if (path_.empty() || path_[0] ==
'/') {
90 throw std::invalid_argument(
"Invalid path.");
93 if (
auto subConfig = config.get(
"Size")) {
94 unmarshallOption(size_, *subConfig,
false);
97 throw std::invalid_argument(
"Invalid size");
100 if (
auto subConfig = config.get(
"Scale")) {
101 unmarshallOption(scale_, *subConfig,
false);
103 if (
auto subConfig = config.get(
"Context")) {
104 unmarshallOption(context_, *subConfig,
false);
106 if (
auto subConfig = config.get(
"Type")) {
107 unmarshallOption(type_, *subConfig,
false);
109 if (
auto subConfig = config.get(
"MaxSize")) {
110 unmarshallOption(maxSize_, *subConfig,
false);
112 if (
auto subConfig = config.get(
"MinSize")) {
113 unmarshallOption(minSize_, *subConfig,
false);
115 if (
auto subConfig = config.get(
"Threshold")) {
116 unmarshallOption(threshold_, *subConfig,
false);
128 FCITX_INLINE_DEFINE_DEFAULT_DTOR_COPY_AND_MOVE_WITHOUT_SPEC(
134 std::string context_;
135 IconThemeDirectoryType type_ = IconThemeDirectoryType::Threshold;
141 IconThemeDirectory::IconThemeDirectory(
const RawConfig &config)
142 : d_ptr(std::make_unique<IconThemeDirectoryPrivate>(config)) {}
146 bool IconThemeDirectory::matchesSize(
int iconsize,
int iconscale)
const {
147 if (scale() != iconscale) {
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();
162 int IconThemeDirectory::sizeDistance(
int iconsize,
int iconscale)
const {
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);
170 if (iconsize * iconscale > maxSize() * scale()) {
171 return (iconsize * iconscale) - (maxSize() * scale());
174 case IconThemeDirectoryType::Threshold:
175 if (iconsize * iconscale < (size() - threshold()) * scale()) {
176 return ((size() - threshold()) * scale()) - (iconsize * iconscale);
178 if (iconsize * iconscale > (size() + threshold()) * scale()) {
179 return (iconsize * iconscale) - ((size() - threshold()) * scale());
191 IconThemeDirectoryType, type);
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;
208 FCITX_UNUSED(filename);
210 auto fd = StandardPaths::openPath(filename);
215 if (fileModifiedTime == 0) {
218 auto dirName = filename.parent_path();
220 if (dirModifiedTime == 0) {
223 if (fileModifiedTime < dirModifiedTime) {
228 if (fstat(fd.fd(), &st) < 0) {
232 memory_ =
static_cast<uint8_t *
>(
233 mmap(
nullptr, st.st_size, PROT_READ, MAP_SHARED, fd.fd(), 0));
240 if (readWord(0) != 1 || readWord(2) != 0) {
247 uint32_t dirListOffset = readDoubleWord(8);
248 uint32_t dirListLen = readDoubleWord(dirListOffset);
250 if (dirListLen > 4096) {
254 for (uint32_t i = 0; i < dirListLen; ++i) {
255 uint32_t offset = readDoubleWord(dirListOffset + 4 + (4 * i));
256 if (!isValid_ || offset >= size_) {
260 auto *dir = checkString(offset);
265 std::filesystem::path subdirName(dir);
266 if (subdirName.is_absolute()) {
271 if (fileModifiedTime < subDirTime) {
281 : isValid_(other.isValid_), memory_(other.memory_), size_(other.size_) {
282 other.isValid_ =
false;
283 other.memory_ =
nullptr;
288 std::swap(other.isValid_, isValid_);
289 std::swap(other.size_, size_);
290 std::swap(other.memory_, memory_);
294 bool isValid()
const {
return isValid_; }
299 munmap(memory_, size_);
304 uint16_t readWord(uint32_t offset)
const {
305 if (offset > size_ - 2 || (offset % 2)) {
309 return memory_[offset + 1] | memory_[offset] << 8;
311 uint32_t readDoubleWord(
unsigned int offset)
const {
312 if (offset > size_ - 4 || (offset % 4)) {
316 return memory_[offset + 3] | memory_[offset + 2] << 8 |
317 memory_[offset + 1] << 16 | memory_[offset] << 24;
320 char *checkString(uint32_t offset)
const {
322 for (uint32_t i = 0; i < 1024; i++) {
323 if (offset + i >= size_) {
326 auto c = memory_[offset + i];
331 return reinterpret_cast<char *
>(memory_ + offset);
334 std::unordered_set<std::string> lookup(
const std::string &name)
const;
336 mutable bool isValid_ =
false;
337 uint8_t *memory_ =
nullptr;
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());
346 uint32_t hashOffset = readDoubleWord(4);
347 uint32_t hashBucketCount = readDoubleWord(hashOffset);
349 if (!isValid_ || hashBucketCount == 0) {
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);
363 uint32_t listOffset = readDoubleWord(bucketOffset + 8);
364 uint32_t listLen = readDoubleWord(listOffset);
366 if (!isValid_ || listOffset + 4 + 8 * listLen > size_) {
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_) {
379 if (
auto *str = checkString(o)) {
387 bucketOffset = readDoubleWord(bucketOffset);
395 :
QPtrHolder(q), standardPath_(std::cref(path)) {
396 if (
auto home = getEnvironment(
"HOME")) {
402 if (
auto home = getEnvironment(
"HOME")) {
409 subThemeNames_.insert(internalName_);
412 auto section = config.get(
"Icon Theme");
416 addInherit(
"hicolor");
421 if (
auto nameSection = section->get(
"Name")) {
422 unmarshallOption(name_, *nameSection,
false);
425 if (
auto commentSection = section->get(
"Comment")) {
426 unmarshallOption(comment_, *commentSection,
false);
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 :
437 if (
auto directoryConfig = config.get(directory)) {
439 dir.emplace_back(*directoryConfig);
446 parseDirectory(
"Directories", directories_);
447 parseDirectory(
"ScaledDirectories", scaledDirectories_);
451 if (
auto subConfig = section->get(
"Example")) {
452 unmarshallOption(example_, *subConfig,
false);
454 if (
auto subConfig = section->get(
"Inherits")) {
455 std::string inherits;
456 unmarshallOption(inherits, *subConfig,
false);
461 parent->d_ptr->addInherit(inherit);
467 if (!parent && !subThemeNames_.contains(
"hicolor")) {
468 addInherit(
"hicolor");
472 void addInherit(
const std::string &inherit) {
473 if (subThemeNames_.insert(inherit).second) {
475 std::visit(VariantOverload{
491 std::filesystem::path
492 findIcon(
const std::string &icon,
int size,
int scale,
493 const std::vector<std::string> &extensions)
const {
495 if (icon.empty() || icon[0] ==
'/') {
499 std::filesystem::path filename;
501 if (!internalName_.empty()) {
502 auto filename = findIconHelper(icon, size, scale, extensions);
503 if (!filename.empty()) {
507 filename = lookupFallbackIcon(icon, extensions);
511 if (filename.empty()) {
512 auto dashPos = icon.rfind(
'-');
513 if (dashPos != std::string::npos) {
515 findIcon(icon.substr(0, dashPos), size, scale, extensions);
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()) {
529 for (
const auto &inherit : inherits_) {
531 inherit.d_func()->lookupIcon(icon, size, scale, extensions);
532 if (!filename.empty()) {
539 std::filesystem::path
540 lookupIcon(
const std::string &iconname,
int size,
int scale,
541 const std::vector<std::string> &extensions)
const {
543 auto checkDirectory =
544 [&extensions, &iconname](
546 std::filesystem::path baseDir) -> std::filesystem::path {
547 baseDir = baseDir / directory.path();
549 if (!std::filesystem::is_directory(baseDir, ec)) {
553 for (
const auto &ext : extensions) {
554 auto defaultPath = baseDir / (iconname + ext);
556 if (std::filesystem::is_regular_file(defaultPath, ec)) {
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);
570 for (
const auto &directory : directories_) {
571 if ((hasCache && !dirFilter.contains(directory.path())) ||
572 !directory.matchesSize(size, scale)) {
575 auto path = checkDirectory(directory, baseDir.first);
582 for (
const auto &directory : scaledDirectories_) {
583 if ((hasCache && !dirFilter.contains(directory.path())) ||
584 !directory.matchesSize(size, scale)) {
587 auto path = checkDirectory(directory, baseDir.first);
595 auto minSize = std::numeric_limits<int>::max();
596 std::filesystem::path closestFilename;
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);
606 auto checkDirectoryWithSize =
607 [&checkDirectory, &closestFilename, &dirFilter, hasCache, size,
609 if (hasCache && !dirFilter.contains(dir.path())) {
612 auto distance = dir.sizeDistance(size, scale);
613 if (distance < minSize) {
614 if (
auto path = checkDirectory(dir, baseDir.first);
616 closestFilename = std::move(path);
622 for (
const auto &directory : directories_) {
623 checkDirectoryWithSize(directory);
627 for (
const auto &directory : scaledDirectories_) {
628 checkDirectoryWithSize(directory);
633 return closestFilename;
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;
644 if (std::filesystem::is_regular_file(path, ec)) {
651 StandardPath::Type::Data,
652 (std::filesystem::path(
"icons") / (iconname + ext))
656 path = sppath.
locate(StandardPathsType::Data,
657 std::filesystem::path(
"icons") /
669 void addBaseDir(
const std::filesystem::path &path) {
671 if (!std::filesystem::is_directory(path, ec)) {
674 baseDirs_.emplace_back(
675 std::piecewise_construct, std::forward_as_tuple(path),
676 std::forward_as_tuple(path /
"icon-theme.cache"));
680 if (!home_.empty()) {
681 addBaseDir(home_ /
".icons" / internalName_);
683 if (
auto userDir = std::visit(
686 return std::filesystem::path(
687 sppath.userDirectory(StandardPath::Type::Data));
690 return sppath.userDirectory(StandardPathsType::Data);
695 addBaseDir(userDir /
"icons" / internalName_);
698 for (
auto &dataDir : std::visit(
702 sppath.directories(StandardPath::Type::Data);
703 return std::vector<std::filesystem::path>(
704 paths.begin(), paths.end());
708 sppath.directories(StandardPathsType::Data);
709 return std::vector<std::filesystem::path>{dirs.begin(),
714 addBaseDir(dataDir /
"icons" / internalName_);
718 std::filesystem::path home_;
719 std::string internalName_;
720 std::variant<std::reference_wrapper<const StandardPath>,
721 std::reference_wrapper<const StandardPaths>>
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_;
732 std::string example_;
735 IconTheme::IconTheme(
const std::string &name,
const StandardPath &standardPath)
736 :
IconTheme(name,
nullptr, standardPath) {}
738 IconTheme::IconTheme(
const std::string &name,
IconTheme *parent,
742 auto files = standardPath.
openAll(
743 StandardPath::Type::Data,
744 (std::filesystem::path(
"icons") / name /
"index.theme").
string(),
748 for (
auto &file : files | std::views::reverse) {
749 readFromIni(config, file.fd());
751 auto path = d->home_ /
".icons" / name /
"index.theme";
752 auto fd = StandardPaths::openPath(path);
754 readFromIni(config, fd.fd());
757 d->parse(config, parent);
758 d->internalName_ = name;
763 : d_ptr(std::make_unique<IconThemePrivate>(
this, standardPath)) {}
765 IconTheme::IconTheme(
const std::string &name,
const StandardPaths &standardPath)
766 :
IconTheme(name,
nullptr, standardPath) {}
768 IconTheme::IconTheme(
const std::string &name,
IconTheme *parent,
772 auto files = standardPath.
openAll(StandardPathsType::Data,
773 std::filesystem::path(
"icons") / name /
777 for (
auto &file : files | std::views::reverse) {
778 readFromIni(config, file.fd());
780 auto path = d->home_ /
".icons" / name /
"index.theme";
781 auto fd = StandardPaths::openPath(path);
783 readFromIni(config, fd.fd());
786 d->parse(config, parent);
787 d->internalName_ = name;
792 : d_ptr(std::make_unique<IconThemePrivate>(
this, standardPath)) {}
794 FCITX_DEFINE_DEFAULT_DTOR_AND_MOVE(
IconTheme);
796 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(
IconTheme, std::string, internalName);
799 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(
IconTheme, std::vector<IconTheme>,
801 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(
IconTheme,
802 std::vector<IconThemeDirectory>,
804 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(
IconTheme,
805 std::vector<IconThemeDirectory>,
807 FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(
IconTheme, std::string, example);
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)
818 IconTheme::findIcon(
const std::string &iconName,
unsigned int desiredSize,
820 const std::vector<std::string> &extensions)
const {
821 return findIconPath(iconName, desiredSize, scale, extensions).string();
824 std::filesystem::path
825 IconTheme::findIconPath(
const std::string &iconName,
unsigned int desiredSize,
827 const std::vector<std::string> &extensions)
const {
829 return d->findIcon(iconName, desiredSize, scale, extensions);
832 std::string getKdeTheme(
int fd) {
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();
846 std::string getGtkTheme(
const std::filesystem::path &filename) {
849 std::ifstream fin(filename, std::ios::in | std::ios::binary);
851 while (std::getline(fin, line)) {
853 if (tokens.size() == 2 &&
856 if (value.size() >= 2 && value.front() ==
'"' &&
857 value.back() ==
'"') {
858 value = value.substr(1, value.size() - 2);
860 if (!value.empty() && value.find(
'/') == std::string::npos) {
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,
875 for (
auto &file : files) {
876 auto theme = getKdeTheme(file.fd());
877 if (!theme.empty()) {
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()) {
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()) {
910 auto theme = getGtkTheme(
"/etc/gtk-3.0/settings.ini");
911 if (!theme.empty()) {
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()) {
929 if (desktopType == DesktopType::Unknown) {
932 if (desktopType == DesktopType::GNOME) {
939 std::string IconTheme::iconName(
const std::string &icon,
bool inFlatpak) {
940 constexpr std::string_view fcitxIconPrefix =
"fcitx";
944 if (icon.size() == fcitxIconPrefix.size()) {
945 return "org.fcitx.Fcitx5";
947 return stringutils::concat(
"org.fcitx.Fcitx5.", icon);
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.
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.
A implementation of freedesktop.org icont specification.
Utility classes to handle XDG file path.
Utility class to open, locate, list files based on XDG standard.
bool startsWith(std::string_view str, std::string_view prefix)
Check if a string starts with a prefix.
int64_t modifiedTime(const std::filesystem::path &path)
Return modified time in seconds of given path.
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.