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
33 #include <CoreFoundation/CoreFoundation.h>
41 #include "alfstream.h"
43 #include "core/helpers.h"
44 #include "core/logging.h"
55 al::vector<ConfigEntry> ConfOpts;
58 std::string &lstrip(std::string &line)
61 while(pos < line.length() && std::isspace(line[pos]))
67 bool readline(std::istream &f, std::string &output)
69 while(f.good() && f.peek() == '\n')
72 return std::getline(f, output) && !output.empty();
75 std::string expdup(const char *str)
87 const char *next = std::strchr(str, '$');
89 addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
98 const char *next = std::strchr(str+1, '$');
100 addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
106 const bool hasbraces{(*str == '{')};
109 const char *envstart = str;
110 while(std::isalnum(*str) || *str == '_')
112 if(hasbraces && *str != '}')
114 const std::string envname{envstart, str};
117 envval = al::getenv(envname.c_str()).value_or(std::string{});
118 addstr = envval.data();
119 addstrlen = envval.length();
125 output.append(addstr, addstrlen);
131 void LoadConfigFromFile(std::istream &f)
133 std::string curSection;
136 while(readline(f, buffer))
138 if(lstrip(buffer).empty())
143 auto line = const_cast<char*>(buffer.data());
144 char *section = line+1;
147 endsection = std::strchr(section, ']');
148 if(!endsection || section == endsection)
150 ERR(" config parse error: bad line \"%s\"\n", line);
153 if(endsection[1] != 0)
155 char *end = endsection+1;
156 while(std::isspace(*end))
158 if(*end != 0 && *end != '#')
160 ERR(" config parse error: bad line \"%s\"\n", line);
167 if(al::strcasecmp(section, "general") != 0)
170 char *nextp = std::strchr(section, '%');
173 curSection += section;
177 curSection.append(section, nextp);
180 if(((section[1] >= '0' && section[1] <= '9') ||
181 (section[1] >= 'a' && section[1] <= 'f') ||
182 (section[1] >= 'A' && section[1] <= 'F')) &&
183 ((section[2] >= '0' && section[2] <= '9') ||
184 (section[2] >= 'a' && section[2] <= 'f') ||
185 (section[2] >= 'A' && section[2] <= 'F')))
188 if(section[1] >= '0' && section[1] <= '9')
189 b = (section[1]-'0') << 4;
190 else if(section[1] >= 'a' && section[1] <= 'f')
191 b = (section[1]-'a'+0xa) << 4;
192 else if(section[1] >= 'A' && section[1] <= 'F')
193 b = (section[1]-'A'+0x0a) << 4;
194 if(section[2] >= '0' && section[2] <= '9')
195 b |= (section[2]-'0');
196 else if(section[2] >= 'a' && section[2] <= 'f')
197 b |= (section[2]-'a'+0xa);
198 else if(section[2] >= 'A' && section[2] <= 'F')
199 b |= (section[2]-'A'+0x0a);
200 curSection += static_cast<char>(b);
203 else if(section[1] == '%')
213 } while(*section != 0);
219 auto cmtpos = std::min(buffer.find('#'), buffer.size());
220 while(cmtpos > 0 && std::isspace(buffer[cmtpos-1]))
222 if(!cmtpos) continue;
223 buffer.erase(cmtpos);
225 auto sep = buffer.find('=');
226 if(sep == std::string::npos)
228 ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
232 while(keyend > 0 && std::isspace(buffer[keyend-1]))
236 ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
239 while(sep < buffer.size() && std::isspace(buffer[sep]))
243 if(!curSection.empty())
245 fullKey += curSection;
248 fullKey += buffer.substr(0u, keyend);
250 std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}};
253 if((value.front() == '"' && value.back() == '"')
254 || (value.front() == '\'' && value.back() == '\''))
257 value.erase(value.begin());
261 TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str());
263 /* Check if we already have this option set */
264 auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
265 { return entry.key == fullKey; };
266 auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key);
267 if(ent != ConfOpts.end())
270 ent->value = expdup(value.c_str());
274 else if(!value.empty())
275 ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())});
277 ConfOpts.shrink_to_fit();
280 const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName)
286 if(blockName && al::strcasecmp(blockName, "general") != 0)
307 auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
308 [&key](const ConfigEntry &entry) -> bool
309 { return entry.key == key; });
310 if(iter != ConfOpts.cend())
312 TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
313 if(!iter->value.empty())
314 return iter->value.c_str();
320 TRACE("Key %s not found\n", key.c_str());
323 return GetConfigValue(nullptr, blockName, keyName);
332 WCHAR buffer[MAX_PATH];
333 if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
335 std::string filepath{wstr_to_utf8(buffer)};
336 filepath += "\\alsoft.ini";
338 TRACE("Loading config %s...\n", filepath.c_str());
339 al::ifstream f{filepath};
341 LoadConfigFromFile(f);
344 std::string ppath{GetProcBinary().path};
347 ppath += "\\alsoft.ini";
348 TRACE("Loading config %s...\n", ppath.c_str());
349 al::ifstream f{ppath};
351 LoadConfigFromFile(f);
354 if(auto confpath = al::getenv(L"ALSOFT_CONF"))
356 TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str());
357 al::ifstream f{*confpath};
359 LoadConfigFromFile(f);
367 const char *str{"/etc/openal/alsoft.conf"};
369 TRACE("Loading config %s...\n", str);
372 LoadConfigFromFile(f);
375 std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
376 /* Go through the list in reverse, since "the order of base directories
377 * denotes their importance; the first directory listed is the most
378 * important". Ergo, we need to load the settings from the later dirs
379 * first so that the settings in the earlier dirs override them.
382 while(!confpaths.empty())
384 auto next = confpaths.find_last_of(':');
385 if(next < confpaths.length())
387 fname = confpaths.substr(next+1);
388 confpaths.erase(next);
396 if(fname.empty() || fname.front() != '/')
397 WARN("Ignoring XDG config dir: %s\n", fname.c_str());
400 if(fname.back() != '/') fname += "/alsoft.conf";
401 else fname += "alsoft.conf";
403 TRACE("Loading config %s...\n", fname.c_str());
404 f = al::ifstream{fname};
406 LoadConfigFromFile(f);
412 CFBundleRef mainBundle = CFBundleGetMainBundle();
415 unsigned char fileName[PATH_MAX];
418 if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
419 CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
421 f = al::ifstream{reinterpret_cast<char*>(fileName)};
423 LoadConfigFromFile(f);
428 if(auto homedir = al::getenv("HOME"))
431 if(fname.back() != '/') fname += "/.alsoftrc";
432 else fname += ".alsoftrc";
434 TRACE("Loading config %s...\n", fname.c_str());
435 f = al::ifstream{fname};
437 LoadConfigFromFile(f);
440 if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
443 if(fname.back() != '/') fname += "/alsoft.conf";
444 else fname += "alsoft.conf";
449 if(auto homedir = al::getenv("HOME"))
452 if(fname.back() != '/') fname += "/.config/alsoft.conf";
453 else fname += ".config/alsoft.conf";
458 TRACE("Loading config %s...\n", fname.c_str());
459 f = al::ifstream{fname};
461 LoadConfigFromFile(f);
464 std::string ppath{GetProcBinary().path};
467 if(ppath.back() != '/') ppath += "/alsoft.conf";
468 else ppath += "alsoft.conf";
470 TRACE("Loading config %s...\n", ppath.c_str());
471 f = al::ifstream{ppath};
473 LoadConfigFromFile(f);
476 if(auto confname = al::getenv("ALSOFT_CONF"))
478 TRACE("Loading config %s...\n", confname->c_str());
479 f = al::ifstream{*confname};
481 LoadConfigFromFile(f);
486 al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
488 if(const char *val{GetConfigValue(devName, blockName, keyName)})
493 al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
495 if(const char *val{GetConfigValue(devName, blockName, keyName)})
496 return static_cast<int>(std::strtol(val, nullptr, 0));
500 al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
502 if(const char *val{GetConfigValue(devName, blockName, keyName)})
503 return static_cast<unsigned int>(std::strtoul(val, nullptr, 0));
507 al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
509 if(const char *val{GetConfigValue(devName, blockName, keyName)})
510 return std::strtof(val, nullptr);
514 al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
516 if(const char *val{GetConfigValue(devName, blockName, keyName)})
517 return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
518 || al::strcasecmp(val, "true")==0 || atoi(val) != 0;
522 bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def)
524 if(const char *val{GetConfigValue(devName, blockName, keyName)})
525 return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
526 || al::strcasecmp(val, "true") == 0 || atoi(val) != 0);