7 #define WIN32_LEAN_AND_MEAN
19 #include <system_error>
22 #include "alnumeric.h"
31 using namespace std::string_view_literals
;
33 std::mutex gSearchLock
;
35 void DirectorySearch(const std::filesystem::path
&path
, const std::string_view ext
,
36 std::vector
<std::string
> *const results
)
38 namespace fs
= std::filesystem
;
40 const auto base
= results
->size();
43 auto fpath
= path
.lexically_normal();
44 if(!fs::exists(fpath
))
47 TRACE("Searching %s for *%.*s\n", fpath
.u8string().c_str(), al::sizei(ext
), ext
.data());
48 for(auto&& dirent
: fs::directory_iterator
{fpath
})
50 auto&& entrypath
= dirent
.path();
51 if(!entrypath
.has_extension())
54 if(fs::status(entrypath
).type() == fs::file_type::regular
55 && al::case_compare(entrypath
.extension().u8string(), ext
) == 0)
56 results
->emplace_back(entrypath
.u8string());
59 catch(std::exception
& e
) {
60 ERR("Exception enumerating files: %s\n", e
.what());
63 const auto newlist
= al::span
{*results
}.subspan(base
);
64 std::sort(newlist
.begin(), newlist
.end());
65 for(const auto &name
: newlist
)
66 TRACE(" got %s\n", name
.c_str());
76 const PathNamePair
&GetProcBinary()
82 auto fullpath
= std::wstring(pathlen
, L
'\0');
83 DWORD len
{GetModuleFileNameW(nullptr, fullpath
.data(), pathlen
)};
84 while(len
== fullpath
.size())
89 /* pathlen overflow (more than 4 billion characters??) */
93 fullpath
.resize(pathlen
);
94 len
= GetModuleFileNameW(nullptr, fullpath
.data(), pathlen
);
98 ERR("Failed to get process name: error %lu\n", GetLastError());
99 return PathNamePair
{};
102 fullpath
.resize(len
);
104 const WCHAR
*exePath
{__wargv
[0]};
107 ERR("Failed to get process name: __wargv[0] == nullptr\n");
108 return PathNamePair
{};
110 std::wstring fullpath
{exePath
};
112 std::replace(fullpath
.begin(), fullpath
.end(), L
'/', L
'\\');
115 if(auto seppos
= fullpath
.rfind(L
'\\'); seppos
< fullpath
.size())
117 res
.path
= wstr_to_utf8(std::wstring_view
{fullpath
}.substr(0, seppos
));
118 res
.fname
= wstr_to_utf8(std::wstring_view
{fullpath
}.substr(seppos
+1));
121 res
.fname
= wstr_to_utf8(fullpath
);
123 TRACE("Got binary: %s, %s\n", res
.path
.c_str(), res
.fname
.c_str());
126 static const PathNamePair procbin
{get_procbin()};
132 #if !ALSOFT_UWP && !defined(_GAMING_XBOX)
133 struct CoTaskMemDeleter
{
134 void operator()(void *mem
) const { CoTaskMemFree(mem
); }
140 auto SearchDataFiles(const std::string_view ext
) -> std::vector
<std::string
>
142 auto srchlock
= std::lock_guard
{gSearchLock
};
144 /* Search the app-local directory. */
145 auto results
= std::vector
<std::string
>{};
146 if(auto localpath
= al::getenv(L
"ALSOFT_LOCAL_PATH"))
147 DirectorySearch(*localpath
, ext
, &results
);
148 else if(auto curpath
= std::filesystem::current_path(); !curpath
.empty())
149 DirectorySearch(curpath
, ext
, &results
);
154 auto SearchDataFiles(const std::string_view ext
, const std::string_view subdir
)
155 -> std::vector
<std::string
>
157 std::lock_guard
<std::mutex
> srchlock
{gSearchLock
};
159 /* If the path is absolute, use it directly. */
160 std::vector
<std::string
> results
;
161 auto path
= std::filesystem::u8path(subdir
);
162 if(path
.is_absolute())
164 DirectorySearch(path
, ext
, &results
);
168 #if !ALSOFT_UWP && !defined(_GAMING_XBOX)
169 /* Search the local and global data dirs. */
170 for(const auto &folderid
: std::array
{FOLDERID_RoamingAppData
, FOLDERID_ProgramData
})
172 std::unique_ptr
<WCHAR
,CoTaskMemDeleter
> buffer
;
173 const HRESULT hr
{SHGetKnownFolderPath(folderid
, KF_FLAG_DONT_UNEXPAND
, nullptr,
174 al::out_ptr(buffer
))};
175 if(FAILED(hr
) || !buffer
|| !*buffer
)
178 DirectorySearch(std::filesystem::path
{buffer
.get()}/path
, ext
, &results
);
190 if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL
))
191 ERR("Failed to set priority level for thread\n");
202 #include <sys/sysctl.h>
205 #include <FindDirectory.h>
207 #ifdef HAVE_PROC_PIDPATH
210 #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
215 #include <sys/resource.h>
217 #include "dbus_wrap.h"
219 #ifndef RLIMIT_RTTIME
220 #define RLIMIT_RTTIME 15
224 const PathNamePair
&GetProcBinary()
226 auto get_procbin
= []
228 std::string pathname
;
231 std::array
<int,4> mib
{{CTL_KERN
, KERN_PROC
, KERN_PROC_PATHNAME
, -1}};
232 if(sysctl(mib
.data(), mib
.size(), nullptr, &pathlen
, nullptr, 0) == -1)
233 WARN("Failed to sysctl kern.proc.pathname: %s\n",
234 std::generic_category().message(errno
).c_str());
237 auto procpath
= std::vector
<char>(pathlen
+1, '\0');
238 sysctl(mib
.data(), mib
.size(), procpath
.data(), &pathlen
, nullptr, 0);
239 pathname
= procpath
.data();
242 #ifdef HAVE_PROC_PIDPATH
245 std::array
<char,PROC_PIDPATHINFO_MAXSIZE
> procpath
{};
246 const pid_t pid
{getpid()};
247 if(proc_pidpath(pid
, procpath
.data(), procpath
.size()) < 1)
248 ERR("proc_pidpath(%d, ...) failed: %s\n", pid
,
249 std::generic_category().message(errno
).c_str());
251 pathname
= procpath
.data();
257 std::array
<char,PATH_MAX
> procpath
{};
258 if(find_path(B_APP_IMAGE_SYMBOL
, B_FIND_PATH_IMAGE_PATH
, NULL
, procpath
.data(), procpath
.size()) == B_OK
)
259 pathname
= procpath
.data();
265 const std::array SelfLinkNames
{
268 "/proc/curproc/exe"sv
,
269 "/proc/curproc/file"sv
,
272 for(const std::string_view name
: SelfLinkNames
)
275 if(!std::filesystem::exists(name
))
277 if(auto path
= std::filesystem::read_symlink(name
); !path
.empty())
279 pathname
= path
.u8string();
283 catch(std::exception
& e
) {
284 WARN("Exception getting symlink %.*s: %s\n", al::sizei(name
), name
.data(),
292 if(auto seppos
= pathname
.rfind('/'); seppos
< pathname
.size())
294 res
.path
= std::string_view
{pathname
}.substr(0, seppos
);
295 res
.fname
= std::string_view
{pathname
}.substr(seppos
+1);
298 res
.fname
= pathname
;
300 TRACE("Got binary: \"%s\", \"%s\"\n", res
.path
.c_str(), res
.fname
.c_str());
303 static const PathNamePair procbin
{get_procbin()};
307 auto SearchDataFiles(const std::string_view ext
) -> std::vector
<std::string
>
309 auto srchlock
= std::lock_guard
{gSearchLock
};
311 /* Search the app-local directory. */
312 auto results
= std::vector
<std::string
>{};
313 if(auto localpath
= al::getenv("ALSOFT_LOCAL_PATH"))
314 DirectorySearch(*localpath
, ext
, &results
);
315 else if(auto curpath
= std::filesystem::current_path(); !curpath
.empty())
316 DirectorySearch(curpath
, ext
, &results
);
321 auto SearchDataFiles(const std::string_view ext
, const std::string_view subdir
)
322 -> std::vector
<std::string
>
324 std::lock_guard
<std::mutex
> srchlock
{gSearchLock
};
326 std::vector
<std::string
> results
;
327 auto path
= std::filesystem::u8path(subdir
);
328 if(path
.is_absolute())
330 DirectorySearch(path
, ext
, &results
);
334 /* Search local data dir */
335 if(auto datapath
= al::getenv("XDG_DATA_HOME"))
336 DirectorySearch(std::filesystem::path
{*datapath
}/path
, ext
, &results
);
337 else if(auto homepath
= al::getenv("HOME"))
338 DirectorySearch(std::filesystem::path
{*homepath
}/".local/share"/path
, ext
, &results
);
340 /* Search global data dirs */
341 std::string datadirs
{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")};
344 while(curpos
< datadirs
.size())
346 size_t nextpos
{datadirs
.find(':', curpos
)};
348 std::string_view pathname
{(nextpos
!= std::string::npos
)
349 ? std::string_view
{datadirs
}.substr(curpos
, nextpos
++ - curpos
)
350 : std::string_view
{datadirs
}.substr(curpos
)};
353 if(!pathname
.empty())
354 DirectorySearch(std::filesystem::path
{pathname
}/path
, ext
, &results
);
357 #ifdef ALSOFT_INSTALL_DATADIR
358 /* Search the installation data directory */
359 if(auto instpath
= std::filesystem::path
{ALSOFT_INSTALL_DATADIR
}; !instpath
.empty())
360 DirectorySearch(instpath
/path
, ext
, &results
);
368 bool SetRTPriorityPthread(int prio
[[maybe_unused
]])
371 #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__)
372 /* Get the min and max priority for SCHED_RR. Limit the max priority to
373 * half, for now, to ensure the thread can't take the highest priority and
376 int rtmin
{sched_get_priority_min(SCHED_RR
)};
377 int rtmax
{sched_get_priority_max(SCHED_RR
)};
378 rtmax
= (rtmax
-rtmin
)/2 + rtmin
;
380 struct sched_param param
{};
381 param
.sched_priority
= std::clamp(prio
, rtmin
, rtmax
);
382 #ifdef SCHED_RESET_ON_FORK
383 err
= pthread_setschedparam(pthread_self(), SCHED_RR
|SCHED_RESET_ON_FORK
, ¶m
);
386 err
= pthread_setschedparam(pthread_self(), SCHED_RR
, ¶m
);
387 if(err
== 0) return true;
389 WARN("pthread_setschedparam failed: %s (%d)\n", std::generic_category().message(err
).c_str(),
394 bool SetRTPriorityRTKit(int prio
[[maybe_unused
]])
399 WARN("D-Bus not available\n");
403 dbus::ConnectionPtr conn
{dbus_bus_get(DBUS_BUS_SYSTEM
, &error
.get())};
406 WARN("D-Bus connection failed with %s: %s\n", error
->name
, error
->message
);
410 /* Don't stupidly exit if the connection dies while doing this. */
411 dbus_connection_set_exit_on_disconnect(conn
.get(), false);
414 int err
{rtkit_get_min_nice_level(conn
.get(), &nicemin
)};
418 ERR("Could not query RTKit: %s (%d)\n", std::generic_category().message(err
).c_str(), err
);
421 int rtmax
{rtkit_get_max_realtime_priority(conn
.get())};
422 TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax
, nicemin
);
424 auto limit_rttime
= [](DBusConnection
*c
) -> int
426 using ulonglong
= unsigned long long;
427 long long maxrttime
{rtkit_get_rttime_usec_max(c
)};
428 if(maxrttime
<= 0) return static_cast<int>(std::abs(maxrttime
));
429 const ulonglong umaxtime
{static_cast<ulonglong
>(maxrttime
)};
431 struct rlimit rlim
{};
432 if(getrlimit(RLIMIT_RTTIME
, &rlim
) != 0)
435 TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime
,
436 static_cast<ulonglong
>(rlim
.rlim_max
), static_cast<ulonglong
>(rlim
.rlim_cur
));
437 if(rlim
.rlim_max
> umaxtime
)
439 rlim
.rlim_max
= static_cast<rlim_t
>(std::min
<ulonglong
>(umaxtime
,
440 std::numeric_limits
<rlim_t
>::max()));
441 rlim
.rlim_cur
= std::min(rlim
.rlim_cur
, rlim
.rlim_max
);
442 if(setrlimit(RLIMIT_RTTIME
, &rlim
) != 0)
451 err
= limit_rttime(conn
.get());
453 WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n",
454 std::generic_category().message(err
).c_str(), err
);
457 /* Limit the maximum real-time priority to half. */
459 prio
= std::clamp(prio
, 1, rtmax
);
461 TRACE("Making real-time with priority %d (max: %d)\n", prio
, rtmax
);
462 err
= rtkit_make_realtime(conn
.get(), 0, prio
);
463 if(err
== 0) return true;
466 WARN("Failed to set real-time priority: %s (%d)\n",
467 std::generic_category().message(err
).c_str(), err
);
469 /* Don't try to set the niceness for non-Linux systems. Standard POSIX has
470 * niceness as a per-process attribute, while the intent here is for the
471 * audio processing thread only to get a priority boost. Currently only
472 * Linux is known to have per-thread niceness.
477 TRACE("Making high priority with niceness %d\n", nicemin
);
478 err
= rtkit_make_high_priority(conn
.get(), 0, nicemin
);
479 if(err
== 0) return true;
482 WARN("Failed to set high priority: %s (%d)\n",
483 std::generic_category().message(err
).c_str(), err
);
485 #endif /* __linux__ */
489 WARN("D-Bus not supported\n");
501 if(SetRTPriorityPthread(RTPrioLevel
))
503 if(SetRTPriorityRTKit(RTPrioLevel
))