alconfig.cpp 15 KB

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