Don't inline the utf8 converters
[openal-soft.git] / alc / alconfig.cpp
blobede39156d8be7c2a2e56e1e5edda562d3518efcd
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 "alconfig.h"
33 #include <cstdlib>
34 #include <cctype>
35 #include <cstring>
36 #ifdef _WIN32_IE
37 #include <windows.h>
38 #include <shlobj.h>
39 #endif
40 #ifdef __APPLE__
41 #include <CoreFoundation/CoreFoundation.h>
42 #endif
44 #include <algorithm>
45 #include <cstdio>
46 #include <string>
47 #include <utility>
49 #include "alfstream.h"
50 #include "alstring.h"
51 #include "compat.h"
52 #include "logging.h"
53 #include "strutils.h"
54 #include "vector.h"
57 namespace {
59 struct ConfigEntry {
60 std::string key;
61 std::string value;
63 al::vector<ConfigEntry> ConfOpts;
66 std::string &lstrip(std::string &line)
68 size_t pos{0};
69 while(pos < line.length() && std::isspace(line[pos]))
70 ++pos;
71 line.erase(0, pos);
72 return line;
75 bool readline(std::istream &f, std::string &output)
77 while(f.good() && f.peek() == '\n')
78 f.ignore();
80 return std::getline(f, output) && !output.empty();
83 std::string expdup(const char *str)
85 std::string output;
87 std::string envval;
88 while(*str != '\0')
90 const char *addstr;
91 size_t addstrlen;
93 if(str[0] != '$')
95 const char *next = std::strchr(str, '$');
96 addstr = str;
97 addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
99 str += addstrlen;
101 else
103 str++;
104 if(*str == '$')
106 const char *next = std::strchr(str+1, '$');
107 addstr = str;
108 addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
110 str += addstrlen;
112 else
114 const bool hasbraces{(*str == '{')};
116 if(hasbraces) str++;
117 const char *envstart = str;
118 while(std::isalnum(*str) || *str == '_')
119 ++str;
120 if(hasbraces && *str != '}')
121 continue;
122 const std::string envname{envstart, str};
123 if(hasbraces) str++;
125 envval = al::getenv(envname.c_str()).value_or(std::string{});
126 addstr = envval.data();
127 addstrlen = envval.length();
130 if(addstrlen == 0)
131 continue;
133 output.append(addstr, addstrlen);
136 return output;
139 void LoadConfigFromFile(std::istream &f)
141 std::string curSection;
142 std::string buffer;
144 while(readline(f, buffer))
146 if(lstrip(buffer).empty())
147 continue;
149 if(buffer[0] == '[')
151 char *line{&buffer[0]};
152 char *section = line+1;
153 char *endsection;
155 endsection = std::strchr(section, ']');
156 if(!endsection || section == endsection)
158 ERR(" config parse error: bad line \"%s\"\n", line);
159 continue;
161 if(endsection[1] != 0)
163 char *end = endsection+1;
164 while(std::isspace(*end))
165 ++end;
166 if(*end != 0 && *end != '#')
168 ERR(" config parse error: bad line \"%s\"\n", line);
169 continue;
172 *endsection = 0;
174 curSection.clear();
175 if(al::strcasecmp(section, "general") != 0)
177 do {
178 char *nextp = std::strchr(section, '%');
179 if(!nextp)
181 curSection += section;
182 break;
185 curSection.append(section, nextp);
186 section = nextp;
188 if(((section[1] >= '0' && section[1] <= '9') ||
189 (section[1] >= 'a' && section[1] <= 'f') ||
190 (section[1] >= 'A' && section[1] <= 'F')) &&
191 ((section[2] >= '0' && section[2] <= '9') ||
192 (section[2] >= 'a' && section[2] <= 'f') ||
193 (section[2] >= 'A' && section[2] <= 'F')))
195 int b{0};
196 if(section[1] >= '0' && section[1] <= '9')
197 b = (section[1]-'0') << 4;
198 else if(section[1] >= 'a' && section[1] <= 'f')
199 b = (section[1]-'a'+0xa) << 4;
200 else if(section[1] >= 'A' && section[1] <= 'F')
201 b = (section[1]-'A'+0x0a) << 4;
202 if(section[2] >= '0' && section[2] <= '9')
203 b |= (section[2]-'0');
204 else if(section[2] >= 'a' && section[2] <= 'f')
205 b |= (section[2]-'a'+0xa);
206 else if(section[2] >= 'A' && section[2] <= 'F')
207 b |= (section[2]-'A'+0x0a);
208 curSection += static_cast<char>(b);
209 section += 3;
211 else if(section[1] == '%')
213 curSection += '%';
214 section += 2;
216 else
218 curSection += '%';
219 section += 1;
221 } while(*section != 0);
224 continue;
227 auto cmtpos = buffer.find('#');
228 if(cmtpos != std::string::npos)
229 buffer.resize(cmtpos);
230 while(!buffer.empty() && std::isspace(buffer.back()))
231 buffer.pop_back();
232 if(buffer.empty()) continue;
234 const char *line{&buffer[0]};
235 char key[256]{};
236 char value[256]{};
237 if(std::sscanf(line, "%255[^=] = \"%255[^\"]\"", key, value) == 2 ||
238 std::sscanf(line, "%255[^=] = '%255[^\']'", key, value) == 2 ||
239 std::sscanf(line, "%255[^=] = %255[^\n]", key, value) == 2)
241 /* sscanf doesn't handle '' or "" as empty values, so clip it
242 * manually. */
243 if(std::strcmp(value, "\"\"") == 0 || std::strcmp(value, "''") == 0)
244 value[0] = 0;
246 else if(std::sscanf(line, "%255[^=] %255[=]", key, value) == 2)
248 /* Special case for 'key =' */
249 value[0] = 0;
251 else
253 ERR(" config parse error: malformed option line: \"%s\"\n\n", line);
254 continue;
257 std::string fullKey;
258 if(!curSection.empty())
260 fullKey += curSection;
261 fullKey += '/';
263 fullKey += key;
264 while(!fullKey.empty() && std::isspace(fullKey.back()))
265 fullKey.pop_back();
267 TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value);
269 /* Check if we already have this option set */
270 auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(),
271 [&fullKey](const ConfigEntry &entry) -> bool
272 { return entry.key == fullKey; }
274 if(ent != ConfOpts.end())
276 if(value[0])
277 ent->value = expdup(value);
278 else
279 ConfOpts.erase(ent);
281 else if(value[0])
282 ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value)});
284 ConfOpts.shrink_to_fit();
287 } // namespace
290 #ifdef _WIN32
291 void ReadALConfig()
293 WCHAR buffer[MAX_PATH];
294 if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
296 std::string filepath{wstr_to_utf8(buffer)};
297 filepath += "\\alsoft.ini";
299 TRACE("Loading config %s...\n", filepath.c_str());
300 al::ifstream f{filepath};
301 if(f.is_open())
302 LoadConfigFromFile(f);
305 std::string ppath{GetProcBinary().path};
306 if(!ppath.empty())
308 ppath += "\\alsoft.ini";
309 TRACE("Loading config %s...\n", ppath.c_str());
310 al::ifstream f{ppath};
311 if(f.is_open())
312 LoadConfigFromFile(f);
315 if(auto confpath = al::getenv(L"ALSOFT_CONF"))
317 TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str());
318 al::ifstream f{*confpath};
319 if(f.is_open())
320 LoadConfigFromFile(f);
324 #else
326 void ReadALConfig()
328 const char *str{"/etc/openal/alsoft.conf"};
330 TRACE("Loading config %s...\n", str);
331 al::ifstream f{str};
332 if(f.is_open())
333 LoadConfigFromFile(f);
334 f.close();
336 std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
337 /* Go through the list in reverse, since "the order of base directories
338 * denotes their importance; the first directory listed is the most
339 * important". Ergo, we need to load the settings from the later dirs
340 * first so that the settings in the earlier dirs override them.
342 std::string fname;
343 while(!confpaths.empty())
345 auto next = confpaths.find_last_of(':');
346 if(next < confpaths.length())
348 fname = confpaths.substr(next+1);
349 confpaths.erase(next);
351 else
353 fname = confpaths;
354 confpaths.clear();
357 if(fname.empty() || fname.front() != '/')
358 WARN("Ignoring XDG config dir: %s\n", fname.c_str());
359 else
361 if(fname.back() != '/') fname += "/alsoft.conf";
362 else fname += "alsoft.conf";
364 TRACE("Loading config %s...\n", fname.c_str());
365 f = al::ifstream{fname};
366 if(f.is_open())
367 LoadConfigFromFile(f);
369 fname.clear();
372 #ifdef __APPLE__
373 CFBundleRef mainBundle = CFBundleGetMainBundle();
374 if(mainBundle)
376 unsigned char fileName[PATH_MAX];
377 CFURLRef configURL;
379 if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
380 CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
382 f = al::ifstream{reinterpret_cast<char*>(fileName)};
383 if(f.is_open())
384 LoadConfigFromFile(f);
387 #endif
389 if(auto homedir = al::getenv("HOME"))
391 fname = *homedir;
392 if(fname.back() != '/') fname += "/.alsoftrc";
393 else fname += ".alsoftrc";
395 TRACE("Loading config %s...\n", fname.c_str());
396 f = al::ifstream{fname};
397 if(f.is_open())
398 LoadConfigFromFile(f);
401 if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
403 fname = *configdir;
404 if(fname.back() != '/') fname += "/alsoft.conf";
405 else fname += "alsoft.conf";
407 else
409 fname.clear();
410 if(auto homedir = al::getenv("HOME"))
412 fname = *homedir;
413 if(fname.back() != '/') fname += "/.config/alsoft.conf";
414 else fname += ".config/alsoft.conf";
417 if(!fname.empty())
419 TRACE("Loading config %s...\n", fname.c_str());
420 f = al::ifstream{fname};
421 if(f.is_open())
422 LoadConfigFromFile(f);
425 std::string ppath{GetProcBinary().path};
426 if(!ppath.empty())
428 if(ppath.back() != '/') ppath += "/alsoft.conf";
429 else ppath += "alsoft.conf";
431 TRACE("Loading config %s...\n", ppath.c_str());
432 f = al::ifstream{ppath};
433 if(f.is_open())
434 LoadConfigFromFile(f);
437 if(auto confname = al::getenv("ALSOFT_CONF"))
439 TRACE("Loading config %s...\n", confname->c_str());
440 f = al::ifstream{*confname};
441 if(f.is_open())
442 LoadConfigFromFile(f);
445 #endif
447 const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def)
449 if(!keyName)
450 return def;
452 std::string key;
453 if(blockName && al::strcasecmp(blockName, "general") != 0)
455 key = blockName;
456 if(devName)
458 key += '/';
459 key += devName;
461 key += '/';
462 key += keyName;
464 else
466 if(devName)
468 key = devName;
469 key += '/';
471 key += keyName;
474 auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
475 [&key](const ConfigEntry &entry) -> bool
476 { return entry.key == key; }
478 if(iter != ConfOpts.cend())
480 TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
481 if(!iter->value.empty())
482 return iter->value.c_str();
483 return def;
486 if(!devName)
488 TRACE("Key %s not found\n", key.c_str());
489 return def;
491 return GetConfigValue(nullptr, blockName, keyName, def);
494 int ConfigValueExists(const char *devName, const char *blockName, const char *keyName)
496 const char *val = GetConfigValue(devName, blockName, keyName, "");
497 return val[0] != 0;
500 al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
502 const char *val = GetConfigValue(devName, blockName, keyName, "");
503 if(!val[0]) return al::nullopt;
505 return al::make_optional<std::string>(val);
508 al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
510 const char *val = GetConfigValue(devName, blockName, keyName, "");
511 if(!val[0]) return al::nullopt;
513 return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
516 al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
518 const char *val = GetConfigValue(devName, blockName, keyName, "");
519 if(!val[0]) return al::nullopt;
521 return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
524 al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
526 const char *val = GetConfigValue(devName, blockName, keyName, "");
527 if(!val[0]) return al::nullopt;
529 return al::make_optional(std::strtof(val, nullptr));
532 al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
534 const char *val = GetConfigValue(devName, blockName, keyName, "");
535 if(!val[0]) return al::nullopt;
537 return al::make_optional(
538 al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
539 al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
542 int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def)
544 const char *val = GetConfigValue(devName, blockName, keyName, "");
546 if(!val[0]) return def != 0;
547 return (al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
548 al::strcasecmp(val, "on") == 0 || atoi(val) != 0);