alconfig.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. /**
  2. * OpenAL cross platform audio library
  3. * Copyright (C) 1999-2007 by authors.
  4. * This library is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU Library General Public
  6. * License as published by the Free Software Foundation; either
  7. * version 2 of the License, or (at your option) any later version.
  8. *
  9. * This library is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * Library General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Library General Public
  15. * License along with this library; if not, write to the
  16. * Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. * Or go to http://www.gnu.org/copyleft/lgpl.html
  19. */
  20. #include "config.h"
  21. #include "alconfig.h"
  22. #ifdef _WIN32
  23. #include <windows.h>
  24. #include <shlobj.h>
  25. #endif
  26. #ifdef __APPLE__
  27. #include <CoreFoundation/CoreFoundation.h>
  28. #endif
  29. #include <algorithm>
  30. #include <array>
  31. #include <cctype>
  32. #include <cstdlib>
  33. #include <filesystem>
  34. #include <fstream>
  35. #include <istream>
  36. #include <limits>
  37. #include <string>
  38. #include <string_view>
  39. #include <utility>
  40. #include <vector>
  41. #include "almalloc.h"
  42. #include "alstring.h"
  43. #include "core/helpers.h"
  44. #include "core/logging.h"
  45. #include "strutils.h"
  46. #if defined(ALSOFT_UWP)
  47. #include <winrt/Windows.Media.Core.h> // !!This is important!!
  48. #include <winrt/Windows.Storage.h>
  49. #include <winrt/Windows.Foundation.h>
  50. #include <winrt/Windows.Foundation.Collections.h>
  51. using namespace winrt;
  52. #endif
  53. namespace {
  54. using namespace std::string_view_literals;
  55. #if defined(_WIN32) && !defined(_GAMING_XBOX) && !defined(ALSOFT_UWP)
  56. struct CoTaskMemDeleter {
  57. void operator()(void *mem) const { CoTaskMemFree(mem); }
  58. };
  59. #endif
  60. struct ConfigEntry {
  61. std::string key;
  62. std::string value;
  63. };
  64. std::vector<ConfigEntry> ConfOpts;
  65. std::string &lstrip(std::string &line)
  66. {
  67. size_t pos{0};
  68. while(pos < line.length() && std::isspace(line[pos]))
  69. ++pos;
  70. line.erase(0, pos);
  71. return line;
  72. }
  73. bool readline(std::istream &f, std::string &output)
  74. {
  75. while(f.good() && f.peek() == '\n')
  76. f.ignore();
  77. return std::getline(f, output) && !output.empty();
  78. }
  79. std::string expdup(std::string_view str)
  80. {
  81. std::string output;
  82. while(!str.empty())
  83. {
  84. if(auto nextpos = str.find('$'))
  85. {
  86. output += str.substr(0, nextpos);
  87. if(nextpos == std::string_view::npos)
  88. break;
  89. str.remove_prefix(nextpos);
  90. }
  91. str.remove_prefix(1);
  92. if(str.empty())
  93. {
  94. output += '$';
  95. break;
  96. }
  97. if(str.front() == '$')
  98. {
  99. output += '$';
  100. str.remove_prefix(1);
  101. continue;
  102. }
  103. const bool hasbraces{str.front() == '{'};
  104. if(hasbraces) str.remove_prefix(1);
  105. size_t envend{0};
  106. while(envend < str.size() && (std::isalnum(str[envend]) || str[envend] == '_'))
  107. ++envend;
  108. if(hasbraces && (envend == str.size() || str[envend] != '}'))
  109. continue;
  110. const std::string envname{str.substr(0, envend)};
  111. if(hasbraces) ++envend;
  112. str.remove_prefix(envend);
  113. if(auto envval = al::getenv(envname.c_str()))
  114. output += *envval;
  115. }
  116. return output;
  117. }
  118. void LoadConfigFromFile(std::istream &f)
  119. {
  120. std::string curSection;
  121. std::string buffer;
  122. while(readline(f, buffer))
  123. {
  124. if(lstrip(buffer).empty())
  125. continue;
  126. if(buffer[0] == '[')
  127. {
  128. auto endpos = buffer.find(']', 1);
  129. if(endpos == 1 || endpos == std::string::npos)
  130. {
  131. ERR(" config parse error: bad line \"%s\"\n", buffer.c_str());
  132. continue;
  133. }
  134. if(buffer[endpos+1] != '\0')
  135. {
  136. size_t last{endpos+1};
  137. while(last < buffer.size() && std::isspace(buffer[last]))
  138. ++last;
  139. if(last < buffer.size() && buffer[last] != '#')
  140. {
  141. ERR(" config parse error: bad line \"%s\"\n", buffer.c_str());
  142. continue;
  143. }
  144. }
  145. auto section = std::string_view{buffer}.substr(1, endpos-1);
  146. curSection.clear();
  147. if(al::case_compare(section, "general"sv) != 0)
  148. {
  149. do {
  150. auto nextp = section.find('%');
  151. if(nextp == std::string_view::npos)
  152. {
  153. curSection += section;
  154. break;
  155. }
  156. curSection += section.substr(0, nextp);
  157. section.remove_prefix(nextp);
  158. if(section.size() > 2 &&
  159. ((section[1] >= '0' && section[1] <= '9') ||
  160. (section[1] >= 'a' && section[1] <= 'f') ||
  161. (section[1] >= 'A' && section[1] <= 'F')) &&
  162. ((section[2] >= '0' && section[2] <= '9') ||
  163. (section[2] >= 'a' && section[2] <= 'f') ||
  164. (section[2] >= 'A' && section[2] <= 'F')))
  165. {
  166. int b{0};
  167. if(section[1] >= '0' && section[1] <= '9')
  168. b = (section[1]-'0') << 4;
  169. else if(section[1] >= 'a' && section[1] <= 'f')
  170. b = (section[1]-'a'+0xa) << 4;
  171. else if(section[1] >= 'A' && section[1] <= 'F')
  172. b = (section[1]-'A'+0x0a) << 4;
  173. if(section[2] >= '0' && section[2] <= '9')
  174. b |= (section[2]-'0');
  175. else if(section[2] >= 'a' && section[2] <= 'f')
  176. b |= (section[2]-'a'+0xa);
  177. else if(section[2] >= 'A' && section[2] <= 'F')
  178. b |= (section[2]-'A'+0x0a);
  179. curSection += static_cast<char>(b);
  180. section.remove_prefix(3);
  181. }
  182. else if(section.size() > 1 && section[1] == '%')
  183. {
  184. curSection += '%';
  185. section.remove_prefix(2);
  186. }
  187. else
  188. {
  189. curSection += '%';
  190. section.remove_prefix(1);
  191. }
  192. } while(!section.empty());
  193. }
  194. continue;
  195. }
  196. auto cmtpos = std::min(buffer.find('#'), buffer.size());
  197. while(cmtpos > 0 && std::isspace(buffer[cmtpos-1]))
  198. --cmtpos;
  199. if(!cmtpos) continue;
  200. buffer.erase(cmtpos);
  201. auto sep = buffer.find('=');
  202. if(sep == std::string::npos)
  203. {
  204. ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
  205. continue;
  206. }
  207. auto keypart = std::string_view{buffer}.substr(0, sep++);
  208. while(!keypart.empty() && std::isspace(keypart.back()))
  209. keypart.remove_suffix(1);
  210. if(keypart.empty())
  211. {
  212. ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
  213. continue;
  214. }
  215. auto valpart = std::string_view{buffer}.substr(sep);
  216. while(!valpart.empty() && std::isspace(valpart.front()))
  217. valpart.remove_prefix(1);
  218. std::string fullKey;
  219. if(!curSection.empty())
  220. {
  221. fullKey += curSection;
  222. fullKey += '/';
  223. }
  224. fullKey += keypart;
  225. if(valpart.size() > size_t{std::numeric_limits<int>::max()})
  226. {
  227. ERR(" config parse error: value too long in line \"%s\"\n", buffer.c_str());
  228. continue;
  229. }
  230. if(valpart.size() > 1)
  231. {
  232. if((valpart.front() == '"' && valpart.back() == '"')
  233. || (valpart.front() == '\'' && valpart.back() == '\''))
  234. {
  235. valpart.remove_prefix(1);
  236. valpart.remove_suffix(1);
  237. }
  238. }
  239. TRACE(" setting '%s' = '%.*s'\n", fullKey.c_str(), al::sizei(valpart), valpart.data());
  240. /* Check if we already have this option set */
  241. auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
  242. { return entry.key == fullKey; };
  243. auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key);
  244. if(ent != ConfOpts.end())
  245. {
  246. if(!valpart.empty())
  247. ent->value = expdup(valpart);
  248. else
  249. ConfOpts.erase(ent);
  250. }
  251. else if(!valpart.empty())
  252. ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(valpart)});
  253. }
  254. ConfOpts.shrink_to_fit();
  255. }
  256. const char *GetConfigValue(const std::string_view devName, const std::string_view blockName,
  257. const std::string_view keyName)
  258. {
  259. if(keyName.empty())
  260. return nullptr;
  261. std::string key;
  262. if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0)
  263. {
  264. key = blockName;
  265. key += '/';
  266. }
  267. if(!devName.empty())
  268. {
  269. key += devName;
  270. key += '/';
  271. }
  272. key += keyName;
  273. auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
  274. [&key](const ConfigEntry &entry) -> bool { return entry.key == key; });
  275. if(iter != ConfOpts.cend())
  276. {
  277. TRACE("Found option %s = \"%s\"\n", key.c_str(), iter->value.c_str());
  278. if(!iter->value.empty())
  279. return iter->value.c_str();
  280. return nullptr;
  281. }
  282. if(devName.empty())
  283. return nullptr;
  284. return GetConfigValue({}, blockName, keyName);
  285. }
  286. } // namespace
  287. #ifdef _WIN32
  288. void ReadALConfig()
  289. {
  290. namespace fs = std::filesystem;
  291. fs::path path;
  292. #if !defined(_GAMING_XBOX)
  293. {
  294. #if !defined(ALSOFT_UWP)
  295. std::unique_ptr<WCHAR,CoTaskMemDeleter> bufstore;
  296. const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND,
  297. nullptr, al::out_ptr(bufstore))};
  298. if(SUCCEEDED(hr))
  299. {
  300. const std::wstring_view buffer{bufstore.get()};
  301. #else
  302. winrt::Windows::Storage::ApplicationDataContainer localSettings = winrt::Windows::Storage::ApplicationData::Current().LocalSettings();
  303. auto bufstore = Windows::Storage::ApplicationData::Current().RoamingFolder().Path();
  304. std::wstring_view buffer{bufstore};
  305. {
  306. #endif
  307. path = fs::path{buffer};
  308. path /= L"alsoft.ini";
  309. TRACE("Loading config %s...\n", path.u8string().c_str());
  310. if(std::ifstream f{path}; f.is_open())
  311. LoadConfigFromFile(f);
  312. }
  313. }
  314. #endif
  315. path = fs::u8path(GetProcBinary().path);
  316. if(!path.empty())
  317. {
  318. path /= "alsoft.ini";
  319. TRACE("Loading config %s...\n", path.u8string().c_str());
  320. if(std::ifstream f{path}; f.is_open())
  321. LoadConfigFromFile(f);
  322. }
  323. if(auto confpath = al::getenv(L"ALSOFT_CONF"))
  324. {
  325. path = *confpath;
  326. TRACE("Loading config %s...\n", path.u8string().c_str());
  327. if(std::ifstream f{path}; f.is_open())
  328. LoadConfigFromFile(f);
  329. }
  330. }
  331. #else
  332. void ReadALConfig()
  333. {
  334. namespace fs = std::filesystem;
  335. fs::path path{"/etc/openal/alsoft.conf"};
  336. TRACE("Loading config %s...\n", path.u8string().c_str());
  337. if(std::ifstream f{path}; f.is_open())
  338. LoadConfigFromFile(f);
  339. std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
  340. /* Go through the list in reverse, since "the order of base directories
  341. * denotes their importance; the first directory listed is the most
  342. * important". Ergo, we need to load the settings from the later dirs
  343. * first so that the settings in the earlier dirs override them.
  344. */
  345. while(!confpaths.empty())
  346. {
  347. auto next = confpaths.rfind(':');
  348. if(next < confpaths.length())
  349. {
  350. path = fs::path{std::string_view{confpaths}.substr(next+1)}.lexically_normal();
  351. confpaths.erase(next);
  352. }
  353. else
  354. {
  355. path = fs::path{confpaths}.lexically_normal();
  356. confpaths.clear();
  357. }
  358. if(!path.is_absolute())
  359. WARN("Ignoring XDG config dir: %s\n", path.u8string().c_str());
  360. else
  361. {
  362. path /= "alsoft.conf";
  363. TRACE("Loading config %s...\n", path.u8string().c_str());
  364. if(std::ifstream f{path}; f.is_open())
  365. LoadConfigFromFile(f);
  366. }
  367. }
  368. #ifdef __APPLE__
  369. CFBundleRef mainBundle = CFBundleGetMainBundle();
  370. if(mainBundle)
  371. {
  372. CFURLRef configURL{CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""),
  373. nullptr)};
  374. std::array<unsigned char,PATH_MAX> fileName{};
  375. if(configURL && CFURLGetFileSystemRepresentation(configURL, true, fileName.data(), fileName.size()))
  376. {
  377. if(std::ifstream f{reinterpret_cast<char*>(fileName.data())}; f.is_open())
  378. LoadConfigFromFile(f);
  379. }
  380. }
  381. #endif
  382. if(auto homedir = al::getenv("HOME"))
  383. {
  384. path = *homedir;
  385. path /= ".alsoftrc";
  386. TRACE("Loading config %s...\n", path.u8string().c_str());
  387. if(std::ifstream f{path}; f.is_open())
  388. LoadConfigFromFile(f);
  389. }
  390. if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
  391. {
  392. path = *configdir;
  393. path /= "alsoft.conf";
  394. }
  395. else
  396. {
  397. path.clear();
  398. if(auto homedir = al::getenv("HOME"))
  399. {
  400. path = *homedir;
  401. path /= ".config/alsoft.conf";
  402. }
  403. }
  404. if(!path.empty())
  405. {
  406. TRACE("Loading config %s...\n", path.u8string().c_str());
  407. if(std::ifstream f{path}; f.is_open())
  408. LoadConfigFromFile(f);
  409. }
  410. path = GetProcBinary().path;
  411. if(!path.empty())
  412. {
  413. path /= "alsoft.conf";
  414. TRACE("Loading config %s...\n", path.u8string().c_str());
  415. if(std::ifstream f{path}; f.is_open())
  416. LoadConfigFromFile(f);
  417. }
  418. if(auto confname = al::getenv("ALSOFT_CONF"))
  419. {
  420. TRACE("Loading config %s...\n", confname->c_str());
  421. if(std::ifstream f{*confname}; f.is_open())
  422. LoadConfigFromFile(f);
  423. }
  424. }
  425. #endif
  426. std::optional<std::string> ConfigValueStr(const std::string_view devName,
  427. const std::string_view blockName, const std::string_view keyName)
  428. {
  429. if(const char *val{GetConfigValue(devName, blockName, keyName)})
  430. return val;
  431. return std::nullopt;
  432. }
  433. std::optional<int> ConfigValueInt(const std::string_view devName, const std::string_view blockName,
  434. const std::string_view keyName)
  435. {
  436. if(const char *val{GetConfigValue(devName, blockName, keyName)})
  437. return static_cast<int>(std::strtol(val, nullptr, 0));
  438. return std::nullopt;
  439. }
  440. std::optional<unsigned int> ConfigValueUInt(const std::string_view devName,
  441. const std::string_view blockName, const std::string_view keyName)
  442. {
  443. if(const char *val{GetConfigValue(devName, blockName, keyName)})
  444. return static_cast<unsigned int>(std::strtoul(val, nullptr, 0));
  445. return std::nullopt;
  446. }
  447. std::optional<float> ConfigValueFloat(const std::string_view devName,
  448. const std::string_view blockName, const std::string_view keyName)
  449. {
  450. if(const char *val{GetConfigValue(devName, blockName, keyName)})
  451. return std::strtof(val, nullptr);
  452. return std::nullopt;
  453. }
  454. std::optional<bool> ConfigValueBool(const std::string_view devName,
  455. const std::string_view blockName, const std::string_view keyName)
  456. {
  457. if(const char *val{GetConfigValue(devName, blockName, keyName)})
  458. return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
  459. || al::strcasecmp(val, "true") == 0 || atoi(val) != 0;
  460. return std::nullopt;
  461. }
  462. bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName,
  463. const std::string_view keyName, bool def)
  464. {
  465. if(const char *val{GetConfigValue(devName, blockName, keyName)})
  466. return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
  467. || al::strcasecmp(val, "true") == 0 || atoi(val) != 0;
  468. return def;
  469. }