alconfig.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  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. #ifdef _WIN32
  21. #ifdef __MINGW32__
  22. #define _WIN32_IE 0x501
  23. #else
  24. #define _WIN32_IE 0x400
  25. #endif
  26. #endif
  27. #include "config.h"
  28. #include "alconfig.h"
  29. #include <cstdlib>
  30. #include <cctype>
  31. #include <cstring>
  32. #ifdef _WIN32_IE
  33. #include <windows.h>
  34. #include <shlobj.h>
  35. #endif
  36. #ifdef __APPLE__
  37. #include <CoreFoundation/CoreFoundation.h>
  38. #endif
  39. #include <algorithm>
  40. #include <cstdio>
  41. #include <string>
  42. #include <utility>
  43. #include "alfstream.h"
  44. #include "alstring.h"
  45. #include "compat.h"
  46. #include "logging.h"
  47. #include "strutils.h"
  48. #include "vector.h"
  49. namespace {
  50. struct ConfigEntry {
  51. std::string key;
  52. std::string value;
  53. };
  54. al::vector<ConfigEntry> ConfOpts;
  55. std::string &lstrip(std::string &line)
  56. {
  57. size_t pos{0};
  58. while(pos < line.length() && std::isspace(line[pos]))
  59. ++pos;
  60. line.erase(0, pos);
  61. return line;
  62. }
  63. bool readline(std::istream &f, std::string &output)
  64. {
  65. while(f.good() && f.peek() == '\n')
  66. f.ignore();
  67. return std::getline(f, output) && !output.empty();
  68. }
  69. std::string expdup(const char *str)
  70. {
  71. std::string output;
  72. std::string envval;
  73. while(*str != '\0')
  74. {
  75. const char *addstr;
  76. size_t addstrlen;
  77. if(str[0] != '$')
  78. {
  79. const char *next = std::strchr(str, '$');
  80. addstr = str;
  81. addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
  82. str += addstrlen;
  83. }
  84. else
  85. {
  86. str++;
  87. if(*str == '$')
  88. {
  89. const char *next = std::strchr(str+1, '$');
  90. addstr = str;
  91. addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
  92. str += addstrlen;
  93. }
  94. else
  95. {
  96. const bool hasbraces{(*str == '{')};
  97. if(hasbraces) str++;
  98. const char *envstart = str;
  99. while(std::isalnum(*str) || *str == '_')
  100. ++str;
  101. if(hasbraces && *str != '}')
  102. continue;
  103. const std::string envname{envstart, str};
  104. if(hasbraces) str++;
  105. envval = al::getenv(envname.c_str()).value_or(std::string{});
  106. addstr = envval.data();
  107. addstrlen = envval.length();
  108. }
  109. }
  110. if(addstrlen == 0)
  111. continue;
  112. output.append(addstr, addstrlen);
  113. }
  114. return output;
  115. }
  116. void LoadConfigFromFile(std::istream &f)
  117. {
  118. std::string curSection;
  119. std::string buffer;
  120. while(readline(f, buffer))
  121. {
  122. if(lstrip(buffer).empty())
  123. continue;
  124. if(buffer[0] == '[')
  125. {
  126. char *line{&buffer[0]};
  127. char *section = line+1;
  128. char *endsection;
  129. endsection = std::strchr(section, ']');
  130. if(!endsection || section == endsection)
  131. {
  132. ERR(" config parse error: bad line \"%s\"\n", line);
  133. continue;
  134. }
  135. if(endsection[1] != 0)
  136. {
  137. char *end = endsection+1;
  138. while(std::isspace(*end))
  139. ++end;
  140. if(*end != 0 && *end != '#')
  141. {
  142. ERR(" config parse error: bad line \"%s\"\n", line);
  143. continue;
  144. }
  145. }
  146. *endsection = 0;
  147. curSection.clear();
  148. if(al::strcasecmp(section, "general") != 0)
  149. {
  150. do {
  151. char *nextp = std::strchr(section, '%');
  152. if(!nextp)
  153. {
  154. curSection += section;
  155. break;
  156. }
  157. curSection.append(section, nextp);
  158. section = nextp;
  159. if(((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 += 3;
  181. }
  182. else if(section[1] == '%')
  183. {
  184. curSection += '%';
  185. section += 2;
  186. }
  187. else
  188. {
  189. curSection += '%';
  190. section += 1;
  191. }
  192. } while(*section != 0);
  193. }
  194. continue;
  195. }
  196. auto cmtpos = buffer.find('#');
  197. if(cmtpos != std::string::npos)
  198. buffer.resize(cmtpos);
  199. while(!buffer.empty() && std::isspace(buffer.back()))
  200. buffer.pop_back();
  201. if(buffer.empty()) continue;
  202. const char *line{&buffer[0]};
  203. char key[256]{};
  204. char value[256]{};
  205. if(std::sscanf(line, "%255[^=] = \"%255[^\"]\"", key, value) == 2 ||
  206. std::sscanf(line, "%255[^=] = '%255[^\']'", key, value) == 2 ||
  207. std::sscanf(line, "%255[^=] = %255[^\n]", key, value) == 2)
  208. {
  209. /* sscanf doesn't handle '' or "" as empty values, so clip it
  210. * manually. */
  211. if(std::strcmp(value, "\"\"") == 0 || std::strcmp(value, "''") == 0)
  212. value[0] = 0;
  213. }
  214. else if(std::sscanf(line, "%255[^=] %255[=]", key, value) == 2)
  215. {
  216. /* Special case for 'key =' */
  217. value[0] = 0;
  218. }
  219. else
  220. {
  221. ERR(" config parse error: malformed option line: \"%s\"\n\n", line);
  222. continue;
  223. }
  224. std::string fullKey;
  225. if(!curSection.empty())
  226. {
  227. fullKey += curSection;
  228. fullKey += '/';
  229. }
  230. fullKey += key;
  231. while(!fullKey.empty() && std::isspace(fullKey.back()))
  232. fullKey.pop_back();
  233. TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value);
  234. /* Check if we already have this option set */
  235. auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(),
  236. [&fullKey](const ConfigEntry &entry) -> bool
  237. { return entry.key == fullKey; }
  238. );
  239. if(ent != ConfOpts.end())
  240. {
  241. if(value[0])
  242. ent->value = expdup(value);
  243. else
  244. ConfOpts.erase(ent);
  245. }
  246. else if(value[0])
  247. ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value)});
  248. }
  249. ConfOpts.shrink_to_fit();
  250. }
  251. } // namespace
  252. #ifdef _WIN32
  253. void ReadALConfig()
  254. {
  255. WCHAR buffer[MAX_PATH];
  256. if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
  257. {
  258. std::string filepath{wstr_to_utf8(buffer)};
  259. filepath += "\\alsoft.ini";
  260. TRACE("Loading config %s...\n", filepath.c_str());
  261. al::ifstream f{filepath};
  262. if(f.is_open())
  263. LoadConfigFromFile(f);
  264. }
  265. std::string ppath{GetProcBinary().path};
  266. if(!ppath.empty())
  267. {
  268. ppath += "\\alsoft.ini";
  269. TRACE("Loading config %s...\n", ppath.c_str());
  270. al::ifstream f{ppath};
  271. if(f.is_open())
  272. LoadConfigFromFile(f);
  273. }
  274. if(auto confpath = al::getenv(L"ALSOFT_CONF"))
  275. {
  276. TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str());
  277. al::ifstream f{*confpath};
  278. if(f.is_open())
  279. LoadConfigFromFile(f);
  280. }
  281. }
  282. #else
  283. void ReadALConfig()
  284. {
  285. const char *str{"/etc/openal/alsoft.conf"};
  286. TRACE("Loading config %s...\n", str);
  287. al::ifstream f{str};
  288. if(f.is_open())
  289. LoadConfigFromFile(f);
  290. f.close();
  291. std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
  292. /* Go through the list in reverse, since "the order of base directories
  293. * denotes their importance; the first directory listed is the most
  294. * important". Ergo, we need to load the settings from the later dirs
  295. * first so that the settings in the earlier dirs override them.
  296. */
  297. std::string fname;
  298. while(!confpaths.empty())
  299. {
  300. auto next = confpaths.find_last_of(':');
  301. if(next < confpaths.length())
  302. {
  303. fname = confpaths.substr(next+1);
  304. confpaths.erase(next);
  305. }
  306. else
  307. {
  308. fname = confpaths;
  309. confpaths.clear();
  310. }
  311. if(fname.empty() || fname.front() != '/')
  312. WARN("Ignoring XDG config dir: %s\n", fname.c_str());
  313. else
  314. {
  315. if(fname.back() != '/') fname += "/alsoft.conf";
  316. else fname += "alsoft.conf";
  317. TRACE("Loading config %s...\n", fname.c_str());
  318. f = al::ifstream{fname};
  319. if(f.is_open())
  320. LoadConfigFromFile(f);
  321. }
  322. fname.clear();
  323. }
  324. #ifdef __APPLE__
  325. CFBundleRef mainBundle = CFBundleGetMainBundle();
  326. if(mainBundle)
  327. {
  328. unsigned char fileName[PATH_MAX];
  329. CFURLRef configURL;
  330. if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
  331. CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
  332. {
  333. f = al::ifstream{reinterpret_cast<char*>(fileName)};
  334. if(f.is_open())
  335. LoadConfigFromFile(f);
  336. }
  337. }
  338. #endif
  339. if(auto homedir = al::getenv("HOME"))
  340. {
  341. fname = *homedir;
  342. if(fname.back() != '/') fname += "/.alsoftrc";
  343. else fname += ".alsoftrc";
  344. TRACE("Loading config %s...\n", fname.c_str());
  345. f = al::ifstream{fname};
  346. if(f.is_open())
  347. LoadConfigFromFile(f);
  348. }
  349. if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
  350. {
  351. fname = *configdir;
  352. if(fname.back() != '/') fname += "/alsoft.conf";
  353. else fname += "alsoft.conf";
  354. }
  355. else
  356. {
  357. fname.clear();
  358. if(auto homedir = al::getenv("HOME"))
  359. {
  360. fname = *homedir;
  361. if(fname.back() != '/') fname += "/.config/alsoft.conf";
  362. else fname += ".config/alsoft.conf";
  363. }
  364. }
  365. if(!fname.empty())
  366. {
  367. TRACE("Loading config %s...\n", fname.c_str());
  368. f = al::ifstream{fname};
  369. if(f.is_open())
  370. LoadConfigFromFile(f);
  371. }
  372. std::string ppath{GetProcBinary().path};
  373. if(!ppath.empty())
  374. {
  375. if(ppath.back() != '/') ppath += "/alsoft.conf";
  376. else ppath += "alsoft.conf";
  377. TRACE("Loading config %s...\n", ppath.c_str());
  378. f = al::ifstream{ppath};
  379. if(f.is_open())
  380. LoadConfigFromFile(f);
  381. }
  382. if(auto confname = al::getenv("ALSOFT_CONF"))
  383. {
  384. TRACE("Loading config %s...\n", confname->c_str());
  385. f = al::ifstream{*confname};
  386. if(f.is_open())
  387. LoadConfigFromFile(f);
  388. }
  389. }
  390. #endif
  391. const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def)
  392. {
  393. if(!keyName)
  394. return def;
  395. std::string key;
  396. if(blockName && al::strcasecmp(blockName, "general") != 0)
  397. {
  398. key = blockName;
  399. if(devName)
  400. {
  401. key += '/';
  402. key += devName;
  403. }
  404. key += '/';
  405. key += keyName;
  406. }
  407. else
  408. {
  409. if(devName)
  410. {
  411. key = devName;
  412. key += '/';
  413. }
  414. key += keyName;
  415. }
  416. auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
  417. [&key](const ConfigEntry &entry) -> bool
  418. { return entry.key == key; }
  419. );
  420. if(iter != ConfOpts.cend())
  421. {
  422. TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
  423. if(!iter->value.empty())
  424. return iter->value.c_str();
  425. return def;
  426. }
  427. if(!devName)
  428. {
  429. TRACE("Key %s not found\n", key.c_str());
  430. return def;
  431. }
  432. return GetConfigValue(nullptr, blockName, keyName, def);
  433. }
  434. int ConfigValueExists(const char *devName, const char *blockName, const char *keyName)
  435. {
  436. const char *val = GetConfigValue(devName, blockName, keyName, "");
  437. return val[0] != 0;
  438. }
  439. al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
  440. {
  441. const char *val = GetConfigValue(devName, blockName, keyName, "");
  442. if(!val[0]) return al::nullopt;
  443. return al::make_optional<std::string>(val);
  444. }
  445. al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
  446. {
  447. const char *val = GetConfigValue(devName, blockName, keyName, "");
  448. if(!val[0]) return al::nullopt;
  449. return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
  450. }
  451. al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
  452. {
  453. const char *val = GetConfigValue(devName, blockName, keyName, "");
  454. if(!val[0]) return al::nullopt;
  455. return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
  456. }
  457. al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
  458. {
  459. const char *val = GetConfigValue(devName, blockName, keyName, "");
  460. if(!val[0]) return al::nullopt;
  461. return al::make_optional(std::strtof(val, nullptr));
  462. }
  463. al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
  464. {
  465. const char *val = GetConfigValue(devName, blockName, keyName, "");
  466. if(!val[0]) return al::nullopt;
  467. return al::make_optional(
  468. al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
  469. al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
  470. }
  471. int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def)
  472. {
  473. const char *val = GetConfigValue(devName, blockName, keyName, "");
  474. if(!val[0]) return def != 0;
  475. return (al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
  476. al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
  477. }