diff --git a/src/finders/desktop/DesktopFinder.cpp b/src/finders/desktop/DesktopFinder.cpp index 99ec0e9..cb8b8cd 100644 --- a/src/finders/desktop/DesktopFinder.cpp +++ b/src/finders/desktop/DesktopFinder.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -17,13 +18,13 @@ using namespace Hyprutils::String; using namespace Hyprutils::OS; -static std::optional readFileAsString(const std::string& path) { +static std::optional readFileAsString(const std::filesystem::path& path) { std::error_code ec; if (!std::filesystem::exists(path, ec) || ec) return std::nullopt; - std::ifstream file(path); + std::ifstream file(path.string()); if (!file.good()) return std::nullopt; @@ -92,25 +93,19 @@ static std::filesystem::path resolvePath(const std::string& p) { return std::filesystem::path(HOME) / p.substr(2); } -static const std::array DESKTOP_ENTRY_PATHS = {"/usr/local/share/applications", "/usr/share/applications", resolvePath("~/.local/share/applications")}; - CDesktopFinder::CDesktopFinder() : m_inotifyFd(inotify_init()), m_entryFrequencyCache(makeUnique("desktop")) { - const auto ENV = getenv("XDG_DATA_DIRS"); - if (!ENV) - return; - - CConstVarList paths(ENV, 0, ':', false); - - for (const auto& p : paths) { - const std::filesystem::path PTH = std::filesystem::path(p) / "applications"; - std::error_code ec; - if (!std::filesystem::exists(PTH, ec) || ec) - continue; - - if (std::ranges::contains(DESKTOP_ENTRY_PATHS, PTH)) - continue; - - m_envPaths.emplace_back(PTH); + if (const auto DATA_HOME = getenv("XDG_DATA_HOME")) + m_envPaths.emplace_back(std::filesystem::path(DATA_HOME) / "applications"); + else + m_envPaths.emplace_back(resolvePath("~/.local/share/applications")); + + if (const auto DATA_DIRS = getenv("XDG_DATA_DIRS")) { + CConstVarList paths(DATA_DIRS, 0, ':', false); + for (const auto& p : paths) + m_envPaths.emplace_back(std::filesystem::path(p) / "applications"); + } else { + m_envPaths.emplace_back("/usr/local/share/applications"); + m_envPaths.emplace_back("/usr/share/applications"); } } @@ -130,26 +125,42 @@ void CDesktopFinder::recache() { m_desktopEntryCache.clear(); m_desktopEntryCacheGeneric.clear(); - auto cachePath = [this](const std::string& p) { + std::unordered_set desktopFileIds; + std::unordered_set directories; + + std::function cacheDirectory; + cacheDirectory = [this, &cacheDirectory, &desktopFileIds, &directories](const std::filesystem::path& base, const std::filesystem::path& p) { std::error_code ec; - auto it = std::filesystem::directory_iterator(resolvePath(p), ec); - if (ec) + auto canonicalPath = std::filesystem::canonical(p, ec); + if (ec || !directories.insert(canonicalPath).second) { + Debug::log(TRACE, "desktop: skipping {}, does not exist / already visited", p.string()); return; + } + auto it = std::filesystem::directory_iterator(p, ec); + if (ec) return; for (const auto& e : it) { - if (!e.is_regular_file(ec) || ec) - continue; - - cacheEntry(e.path().string()); + auto status = e.status(ec); + if (ec) continue; + if (std::filesystem::is_regular_file(status)) { + auto relDesktopFilePath = e.path().lexically_relative(base); + if (relDesktopFilePath.extension() != ".desktop") { + Debug::log(TRACE, "desktop: skipping non-desktop file at {}", e.path().string()); + continue; + } + auto desktopFileId = relDesktopFilePath.string(); + std::ranges::replace(desktopFileId, '/', '-'); + if (desktopFileIds.insert(desktopFileId).second) + cacheEntry(e.path()); + else Debug::log(TRACE, "desktop: skipping entry at {}, already cached desktopFileId {}", e.path().string(), desktopFileId); + } else if (std::filesystem::is_directory(status)) + cacheDirectory(base, e.path()); } - m_desktopEntryPaths.emplace_back(resolvePath(p)); + m_desktopEntryPaths.emplace_back(p); }; - for (const auto& PATH : DESKTOP_ENTRY_PATHS) { - cachePath(PATH); - } for (const auto& PATH : m_envPaths) { - cachePath(PATH); + cacheDirectory(PATH, PATH); } } @@ -181,8 +192,8 @@ void CDesktopFinder::replantWatch() { } } -void CDesktopFinder::cacheEntry(const std::string& path) { - Debug::log(TRACE, "desktop: caching entry {}", path); +void CDesktopFinder::cacheEntry(const std::filesystem::path& path) { + Debug::log(TRACE, "desktop: caching entry at {}", path.string()); const auto READ_RESULT = readFileAsString(path); @@ -224,7 +235,7 @@ void CDesktopFinder::cacheEntry(const std::string& path) { const auto NODISPLAY = extract("NoDisplay") == "true"; if (EXEC.empty() || NAME.empty() || NODISPLAY) { - Debug::log(TRACE, "Skipping entry, empty name / exec / NoDisplay"); + Debug::log(TRACE, "desktop: skipping entry, empty name / exec / NoDisplay"); return; } @@ -237,7 +248,7 @@ void CDesktopFinder::cacheEntry(const std::string& path) { e->m_frequency = m_entryFrequencyCache->getCachedEntry(e->m_fuzzable); m_desktopEntryCacheGeneric.emplace_back(e); - Debug::log(TRACE, "Cached: {} with icon {} and exec line of \"{}\"", NAME, ICON, EXEC); + Debug::log(TRACE, "desktop: cached {} with icon {} and exec line of \"{}\"", NAME, ICON, EXEC); } std::vector CDesktopFinder::getResultsForQuery(const std::string& query) { diff --git a/src/finders/desktop/DesktopFinder.hpp b/src/finders/desktop/DesktopFinder.hpp index 78e3fc7..e89e950 100644 --- a/src/finders/desktop/DesktopFinder.hpp +++ b/src/finders/desktop/DesktopFinder.hpp @@ -3,6 +3,7 @@ #include "../IFinder.hpp" #include +#include class CDesktopEntry; class CEntryCache; @@ -20,19 +21,19 @@ class CDesktopFinder : public IFinder { void onInotifyEvent(); private: - std::vector> m_desktopEntryCache; - std::vector> m_desktopEntryCacheGeneric; + std::vector> m_desktopEntryCache; + std::vector> m_desktopEntryCacheGeneric; - std::vector m_desktopEntryPaths; - std::vector m_watches; + std::vector m_desktopEntryPaths; + std::vector m_watches; - std::vector m_envPaths; + std::vector m_envPaths; - UP m_entryFrequencyCache; + UP m_entryFrequencyCache; - void cacheEntry(const std::string& path); - void replantWatch(); - void recache(); + void cacheEntry(const std::filesystem::path& path); + void replantWatch(); + void recache(); friend class CDesktopEntry; };