99#include < fstream>
1010#include < sys/inotify.h>
1111#include < sys/poll.h>
12+ #include < unordered_set>
1213
1314#include < hyprutils/string/String.hpp>
1415#include < hyprutils/os/Process.hpp>
1718using namespace Hyprutils ::String;
1819using namespace Hyprutils ::OS ;
1920
20- static std::optional<std::string> readFileAsString (const std::string & path) {
21+ static std::optional<std::string> readFileAsString (const std::filesystem::path & path) {
2122 std::error_code ec;
2223
2324 if (!std::filesystem::exists (path, ec) || ec)
2425 return std::nullopt ;
2526
26- std::ifstream file (path);
27+ std::ifstream file (path. string () );
2728 if (!file.good ())
2829 return std::nullopt ;
2930
@@ -96,25 +97,19 @@ static std::filesystem::path resolvePath(const std::string& p) {
9697 return std::filesystem::path (HOME ) / p.substr (2 );
9798}
9899
99- static const std::array<std::filesystem::path, 3 > DESKTOP_ENTRY_PATHS = {" /usr/local/share/applications" , " /usr/share/applications" , resolvePath (" ~/.local/share/applications" )};
100-
101100CDesktopFinder::CDesktopFinder () : m_inotifyFd(inotify_init()), m_entryFrequencyCache(makeUnique<CEntryCache>(" desktop" )) {
102- const auto ENV = getenv (" XDG_DATA_DIRS" );
103- if (!ENV )
104- return ;
105-
106- CConstVarList paths (ENV , 0 , ' :' , false );
107-
108- for (const auto & p : paths) {
109- const std::filesystem::path PTH = std::filesystem::path (p) / " applications" ;
110- std::error_code ec;
111- if (!std::filesystem::exists (PTH , ec) || ec)
112- continue ;
113-
114- if (std::ranges::contains (DESKTOP_ENTRY_PATHS , PTH ))
115- continue ;
116-
117- m_envPaths.emplace_back (PTH );
101+ if (const auto DATA_HOME = getenv (" XDG_DATA_HOME" ))
102+ m_envPaths.emplace_back (std::filesystem::path (DATA_HOME ) / " applications" );
103+ else
104+ m_envPaths.emplace_back (resolvePath (" ~/.local/share/applications" ));
105+
106+ if (const auto DATA_DIRS = getenv (" XDG_DATA_DIRS" )) {
107+ CConstVarList paths (DATA_DIRS , 0 , ' :' , false );
108+ for (const auto & p : paths)
109+ m_envPaths.emplace_back (std::filesystem::path (p) / " applications" );
110+ } else {
111+ m_envPaths.emplace_back (" /usr/local/share/applications" );
112+ m_envPaths.emplace_back (" /usr/share/applications" );
118113 }
119114}
120115
@@ -134,26 +129,42 @@ void CDesktopFinder::recache() {
134129 m_desktopEntryCache.clear ();
135130 m_desktopEntryCacheGeneric.clear ();
136131
137- auto cachePath = [this ](const std::string& p) {
132+ std::unordered_set<std::string> desktopFileIds;
133+ std::unordered_set<std::filesystem::path> directories;
134+
135+ std::function<void (const std::filesystem::path&, const std::filesystem::path&)> cacheDirectory;
136+ cacheDirectory = [this , &cacheDirectory, &desktopFileIds, &directories](const std::filesystem::path& base, const std::filesystem::path& p) {
138137 std::error_code ec;
139- auto it = std::filesystem::directory_iterator (resolvePath (p), ec);
140- if (ec)
138+ auto canonicalPath = std::filesystem::canonical (p, ec);
139+ if (ec || !directories.insert (canonicalPath).second ) {
140+ Debug::log (TRACE , " desktop: skipping {}, does not exist / already visited" , p.string ());
141141 return ;
142+ }
143+ auto it = std::filesystem::directory_iterator (p, ec);
144+ if (ec) return ;
142145 for (const auto & e : it) {
143- if (!e.is_regular_file (ec) || ec)
144- continue ;
145-
146- cacheEntry (e.path ().string ());
146+ auto status = e.status (ec);
147+ if (ec) continue ;
148+ if (std::filesystem::is_regular_file (status)) {
149+ auto relDesktopFilePath = e.path ().lexically_relative (base);
150+ if (relDesktopFilePath.extension () != " .desktop" ) {
151+ Debug::log (TRACE , " desktop: skipping non-desktop file at {}" , e.path ().string ());
152+ continue ;
153+ }
154+ auto desktopFileId = relDesktopFilePath.string ();
155+ std::ranges::replace (desktopFileId, ' /' , ' -' );
156+ if (desktopFileIds.insert (desktopFileId).second )
157+ cacheEntry (e.path ());
158+ else Debug::log (TRACE , " desktop: skipping entry at {}, already cached desktopFileId {}" , e.path ().string (), desktopFileId);
159+ } else if (std::filesystem::is_directory (status))
160+ cacheDirectory (base, e.path ());
147161 }
148162
149- m_desktopEntryPaths.emplace_back (resolvePath (p) );
163+ m_desktopEntryPaths.emplace_back (p );
150164 };
151165
152- for (const auto & PATH : DESKTOP_ENTRY_PATHS ) {
153- cachePath (PATH );
154- }
155166 for (const auto & PATH : m_envPaths) {
156- cachePath ( PATH );
167+ cacheDirectory ( PATH , PATH );
157168 }
158169}
159170
@@ -185,8 +196,8 @@ void CDesktopFinder::replantWatch() {
185196 }
186197}
187198
188- void CDesktopFinder::cacheEntry (const std::string & path) {
189- Debug::log (TRACE , " desktop: caching entry {}" , path);
199+ void CDesktopFinder::cacheEntry (const std::filesystem::path & path) {
200+ Debug::log (TRACE , " desktop: caching entry at {}" , path. string () );
190201
191202 const auto READ_RESULT = readFileAsString (path);
192203
@@ -228,7 +239,7 @@ void CDesktopFinder::cacheEntry(const std::string& path) {
228239 const auto NODISPLAY = extract (" NoDisplay" ) == " true" ;
229240
230241 if (EXEC .empty () || NAME .empty () || NODISPLAY ) {
231- Debug::log (TRACE , " Skipping entry, empty name / exec / NoDisplay" );
242+ Debug::log (TRACE , " desktop: skipping entry, empty name / exec / NoDisplay" );
232243 return ;
233244 }
234245
@@ -241,7 +252,7 @@ void CDesktopFinder::cacheEntry(const std::string& path) {
241252 e->m_frequency = m_entryFrequencyCache->getCachedEntry (e->m_fuzzable );
242253 m_desktopEntryCacheGeneric.emplace_back (e);
243254
244- Debug::log (TRACE , " Cached: {} with icon {} and exec line of \" {}\" " , NAME , ICON , EXEC );
255+ Debug::log (TRACE , " desktop: cached {} with icon {} and exec line of \" {}\" " , NAME , ICON , EXEC );
245256}
246257
247258std::vector<SFinderResult> CDesktopFinder::getResultsForQuery (const std::string& query) {
0 commit comments