Replace a couple more almtx_lock/unlock calls
[openal-soft.git] / Alc / alconfig.cpp
blob73ea045896ddf588d5e3caeb31eb5ceb7a973309
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.
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.
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
21 #ifdef _WIN32
22 #ifdef __MINGW32__
23 #define _WIN32_IE 0x501
24 #else
25 #define _WIN32_IE 0x400
26 #endif
27 #endif
29 #include "config.h"
31 #include <cstdlib>
32 #include <cctype>
33 #include <cstring>
34 #ifdef _WIN32_IE
35 #include <windows.h>
36 #include <shlobj.h>
37 #endif
38 #ifdef __APPLE__
39 #include <CoreFoundation/CoreFoundation.h>
40 #endif
42 #include <vector>
43 #include <string>
44 #include <algorithm>
46 #include "alMain.h"
47 #include "alconfig.h"
48 #include "compat.h"
51 namespace {
53 struct ConfigEntry {
54 std::string key;
55 std::string value;
57 template<typename T0, typename T1>
58 ConfigEntry(T0&& key_, T1&& val_)
59 : key{std::forward<T0>(key_)}, value{std::forward<T1>(val_)}
60 { }
62 std::vector<ConfigEntry> ConfOpts;
65 std::string &lstrip(std::string &line)
67 size_t pos{0};
68 while(pos < line.length() && std::isspace(line[pos]))
69 ++pos;
70 line.erase(0, pos);
71 return line;
74 bool readline(std::istream &f, std::string &output)
76 while(f.good() && f.peek() == '\n')
77 f.ignore();
79 return std::getline(f, output) && !output.empty();
82 std:: string expdup(const char *str)
84 std::string output;
86 while(*str != '\0')
88 const char *addstr;
89 size_t addstrlen;
91 if(str[0] != '$')
93 const char *next = std::strchr(str, '$');
94 addstr = str;
95 addstrlen = next ? (size_t)(next-str) : std::strlen(str);
97 str += addstrlen;
99 else
101 str++;
102 if(*str == '$')
104 const char *next = std::strchr(str+1, '$');
105 addstr = str;
106 addstrlen = next ? (size_t)(next-str) : std::strlen(str);
108 str += addstrlen;
110 else
112 bool hasbraces{(*str == '{')};
113 if(hasbraces) str++;
115 std::string envname;
116 while((std::isalnum(*str) || *str == '_'))
117 envname += *(str++);
119 if(hasbraces && *str != '}')
120 continue;
122 if(hasbraces) str++;
123 if((addstr=std::getenv(envname.c_str())) == nullptr)
124 continue;
125 addstrlen = std::strlen(addstr);
128 if(addstrlen == 0)
129 continue;
131 output.append(addstr, addstrlen);
134 return output;
137 void LoadConfigFromFile(std::istream &f)
139 std::string curSection;
140 std::string buffer;
142 while(readline(f, buffer))
144 while(!buffer.empty() && std::isspace(buffer.back()))
145 buffer.pop_back();
146 if(lstrip(buffer).empty())
147 continue;
149 buffer.push_back(0);
150 char *line{&buffer[0]};
152 if(line[0] == '[')
154 char *section = line+1;
155 char *endsection;
157 endsection = std::strchr(section, ']');
158 if(!endsection || section == endsection)
160 ERR("config parse error: bad line \"%s\"\n", line);
161 continue;
163 if(endsection[1] != 0)
165 char *end = endsection+1;
166 while(std::isspace(*end))
167 ++end;
168 if(*end != 0 && *end != '#')
170 ERR("config parse error: bad line \"%s\"\n", line);
171 continue;
174 *endsection = 0;
176 curSection.clear();
177 if(strcasecmp(section, "general") != 0)
179 do {
180 char *nextp = std::strchr(section, '%');
181 if(!nextp)
183 curSection += section;
184 break;
187 curSection.append(section, nextp);
188 section = nextp;
190 if(((section[1] >= '0' && section[1] <= '9') ||
191 (section[1] >= 'a' && section[1] <= 'f') ||
192 (section[1] >= 'A' && section[1] <= 'F')) &&
193 ((section[2] >= '0' && section[2] <= '9') ||
194 (section[2] >= 'a' && section[2] <= 'f') ||
195 (section[2] >= 'A' && section[2] <= 'F')))
197 unsigned char b = 0;
198 if(section[1] >= '0' && section[1] <= '9')
199 b = (section[1]-'0') << 4;
200 else if(section[1] >= 'a' && section[1] <= 'f')
201 b = (section[1]-'a'+0xa) << 4;
202 else if(section[1] >= 'A' && section[1] <= 'F')
203 b = (section[1]-'A'+0x0a) << 4;
204 if(section[2] >= '0' && section[2] <= '9')
205 b |= (section[2]-'0');
206 else if(section[2] >= 'a' && section[2] <= 'f')
207 b |= (section[2]-'a'+0xa);
208 else if(section[2] >= 'A' && section[2] <= 'F')
209 b |= (section[2]-'A'+0x0a);
210 curSection += static_cast<char>(b);
211 section += 3;
213 else if(section[1] == '%')
215 curSection += '%';
216 section += 2;
218 else
220 curSection += '%';
221 section += 1;
223 } while(*section != 0);
226 continue;
229 char *comment{std::strchr(line, '#')};
230 if(comment) *(comment++) = 0;
231 if(!line[0]) continue;
233 char key[256]{};
234 char value[256]{};
235 if(std::sscanf(line, "%255[^=] = \"%255[^\"]\"", key, value) == 2 ||
236 std::sscanf(line, "%255[^=] = '%255[^\']'", key, value) == 2 ||
237 std::sscanf(line, "%255[^=] = %255[^\n]", key, value) == 2)
239 /* sscanf doesn't handle '' or "" as empty values, so clip it
240 * manually. */
241 if(std::strcmp(value, "\"\"") == 0 || std::strcmp(value, "''") == 0)
242 value[0] = 0;
244 else if(sscanf(line, "%255[^=] %255[=]", key, value) == 2)
246 /* Special case for 'key =' */
247 value[0] = 0;
249 else
251 ERR("config parse error: malformed option line: \"%s\"\n\n", line);
252 continue;
255 std::string fullKey;
256 if(!curSection.empty())
258 fullKey += curSection;
259 fullKey += '/';
261 fullKey += key;
262 while(!fullKey.empty() && std::isspace(fullKey.back()))
263 fullKey.pop_back();
265 /* Check if we already have this option set */
266 auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(),
267 [&fullKey](const ConfigEntry &entry) -> bool
268 { return entry.key == fullKey; }
270 if(ent != ConfOpts.end())
271 ent->value = expdup(value);
272 else
274 ConfOpts.emplace_back(std::move(fullKey), expdup(value));
275 ent = ConfOpts.end()-1;
278 TRACE("found '%s' = '%s'\n", ent->key.c_str(), ent->value.c_str());
280 ConfOpts.shrink_to_fit();
283 } // namespace
286 #ifdef _WIN32
287 void ReadALConfig(void) noexcept
289 WCHAR buffer[MAX_PATH];
290 if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
292 std::string filepath{wstr_to_utf8(buffer)};
293 filepath += "\\alsoft.ini";
295 TRACE("Loading config %s...\n", filepath.c_str());
296 al::ifstream f{filepath};
297 if(f.is_open())
298 LoadConfigFromFile(f);
301 PathNamePair ppath = GetProcBinary();
302 if(!ppath.path.empty())
304 ppath.path += "\\alsoft.ini";
305 TRACE("Loading config %s...\n", ppath.path.c_str());
306 al::ifstream f{ppath.path.c_str()};
307 if(f.is_open())
308 LoadConfigFromFile(f);
311 const WCHAR *str{_wgetenv(L"ALSOFT_CONF")};
312 if(str != nullptr && *str)
314 std::string filepath{wstr_to_utf8(str)};
316 TRACE("Loading config %s...\n", filepath.c_str());
317 al::ifstream f{filepath};
318 if(f.is_open())
319 LoadConfigFromFile(f);
322 #else
323 void ReadALConfig(void) noexcept
325 const char *str{"/etc/openal/alsoft.conf"};
327 TRACE("Loading config %s...\n", str);
328 al::ifstream f{str};
329 if(f.is_open())
330 LoadConfigFromFile(f);
331 f.close();
333 if(!(str=getenv("XDG_CONFIG_DIRS")) || str[0] == 0)
334 str = "/etc/xdg";
335 std::string confpaths = str;
336 /* Go through the list in reverse, since "the order of base directories
337 * denotes their importance; the first directory listed is the most
338 * important". Ergo, we need to load the settings from the later dirs
339 * first so that the settings in the earlier dirs override them.
341 std::string fname;
342 while(!confpaths.empty())
344 auto next = confpaths.find_last_of(':');
345 if(next < confpaths.length())
347 fname = confpaths.substr(next+1);
348 confpaths.erase(next);
350 else
352 fname = confpaths;
353 confpaths.clear();
356 if(fname.empty() || fname.front() != '/')
357 WARN("Ignoring XDG config dir: %s\n", fname.c_str());
358 else
360 if(fname.back() != '/') fname += "/alsoft.conf";
361 else fname += "alsoft.conf";
363 TRACE("Loading config %s...\n", fname.c_str());
364 al::ifstream f{fname};
365 if(f.is_open())
366 LoadConfigFromFile(f);
368 fname.clear();
371 #ifdef __APPLE__
372 CFBundleRef mainBundle = CFBundleGetMainBundle();
373 if(mainBundle)
375 unsigned char fileName[PATH_MAX];
376 CFURLRef configURL;
378 if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
379 CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
381 al::ifstream f{reinterpret_cast<char*>(fileName)};
382 if(f.is_open())
383 LoadConfigFromFile(f);
386 #endif
388 if((str=getenv("HOME")) != nullptr && *str)
390 fname = str;
391 if(fname.back() != '/') fname += "/.alsoftrc";
392 else fname += ".alsoftrc";
394 TRACE("Loading config %s...\n", fname.c_str());
395 al::ifstream f{fname};
396 if(f.is_open())
397 LoadConfigFromFile(f);
400 if((str=getenv("XDG_CONFIG_HOME")) != nullptr && str[0] != 0)
402 fname = str;
403 if(fname.back() != '/') fname += "/alsoft.conf";
404 else fname += "alsoft.conf";
406 else
408 fname.clear();
409 if((str=getenv("HOME")) != nullptr && str[0] != 0)
411 fname = str;
412 if(fname.back() != '/') fname += "/.config/alsoft.conf";
413 else fname += ".config/alsoft.conf";
416 if(!fname.empty())
418 TRACE("Loading config %s...\n", fname.c_str());
419 al::ifstream f{fname};
420 if(f.is_open())
421 LoadConfigFromFile(f);
424 PathNamePair ppath = GetProcBinary();
425 if(!ppath.path.empty())
427 if(ppath.path.back() != '/') ppath.path += "/alsoft.conf";
428 else ppath.path += "alsoft.conf";
430 TRACE("Loading config %s...\n", ppath.path.c_str());
431 al::ifstream f{ppath.path};
432 if(f.is_open())
433 LoadConfigFromFile(f);
436 if((str=getenv("ALSOFT_CONF")) != nullptr && *str)
438 TRACE("Loading config %s...\n", str);
439 al::ifstream f{str};
440 if(f.is_open())
441 LoadConfigFromFile(f);
444 #endif
446 void FreeALConfig(void)
448 ConfOpts.clear();
451 const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def)
453 if(!keyName)
454 return def;
456 std::string key;
457 if(blockName && strcasecmp(blockName, "general") != 0)
459 key = blockName;
460 if(devName)
462 key += '/';
463 key += devName;
465 key += '/';
466 key += keyName;
468 else
470 if(devName)
472 key = devName;
473 key += '/';
475 key += keyName;
478 auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
479 [&key](const ConfigEntry &entry) -> bool
480 { return entry.key == key; }
482 if(iter != ConfOpts.cend())
484 TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
485 if(!iter->value.empty())
486 return iter->value.c_str();
487 return def;
490 if(!devName)
492 TRACE("Key %s not found\n", key.c_str());
493 return def;
495 return GetConfigValue(nullptr, blockName, keyName, def);
498 int ConfigValueExists(const char *devName, const char *blockName, const char *keyName)
500 const char *val = GetConfigValue(devName, blockName, keyName, "");
501 return val[0] != 0;
504 int ConfigValueStr(const char *devName, const char *blockName, const char *keyName, const char **ret)
506 const char *val = GetConfigValue(devName, blockName, keyName, "");
507 if(!val[0]) return 0;
509 *ret = val;
510 return 1;
513 int ConfigValueInt(const char *devName, const char *blockName, const char *keyName, int *ret)
515 const char *val = GetConfigValue(devName, blockName, keyName, "");
516 if(!val[0]) return 0;
518 *ret = std::strtol(val, nullptr, 0);
519 return 1;
522 int ConfigValueUInt(const char *devName, const char *blockName, const char *keyName, unsigned int *ret)
524 const char *val = GetConfigValue(devName, blockName, keyName, "");
525 if(!val[0]) return 0;
527 *ret = std::strtoul(val, nullptr, 0);
528 return 1;
531 int ConfigValueFloat(const char *devName, const char *blockName, const char *keyName, float *ret)
533 const char *val = GetConfigValue(devName, blockName, keyName, "");
534 if(!val[0]) return 0;
536 *ret = std::strtof(val, nullptr);
537 return 1;
540 int ConfigValueBool(const char *devName, const char *blockName, const char *keyName, int *ret)
542 const char *val = GetConfigValue(devName, blockName, keyName, "");
543 if(!val[0]) return 0;
545 *ret = (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 ||
546 strcasecmp(val, "on") == 0 || atoi(val) != 0);
547 return 1;
550 int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def)
552 const char *val = GetConfigValue(devName, blockName, keyName, "");
554 if(!val[0]) return def != 0;
555 return (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 ||
556 strcasecmp(val, "on") == 0 || atoi(val) != 0);