🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

553 lines
15 KiB

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