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
23 #define _WIN32_IE 0x501
25 #define _WIN32_IE 0x400
41 #include <CoreFoundation/CoreFoundation.h>
49 #include "alfstream.h"
63 al::vector
<ConfigEntry
> ConfOpts
;
66 std::string
&lstrip(std::string
&line
)
69 while(pos
< line
.length() && std::isspace(line
[pos
]))
75 bool readline(std::istream
&f
, std::string
&output
)
77 while(f
.good() && f
.peek() == '\n')
80 return std::getline(f
, output
) && !output
.empty();
83 std::string
expdup(const char *str
)
95 const char *next
= std::strchr(str
, '$');
97 addstrlen
= next
? static_cast<size_t>(next
-str
) : std::strlen(str
);
106 const char *next
= std::strchr(str
+1, '$');
108 addstrlen
= next
? static_cast<size_t>(next
-str
) : std::strlen(str
);
114 const bool hasbraces
{(*str
== '{')};
117 const char *envstart
= str
;
118 while(std::isalnum(*str
) || *str
== '_')
120 if(hasbraces
&& *str
!= '}')
122 const std::string envname
{envstart
, str
};
125 envval
= al::getenv(envname
.c_str()).value_or(std::string
{});
126 addstr
= envval
.data();
127 addstrlen
= envval
.length();
133 output
.append(addstr
, addstrlen
);
139 void LoadConfigFromFile(std::istream
&f
)
141 std::string curSection
;
144 while(readline(f
, buffer
))
146 if(lstrip(buffer
).empty())
151 char *line
{&buffer
[0]};
152 char *section
= line
+1;
155 endsection
= std::strchr(section
, ']');
156 if(!endsection
|| section
== endsection
)
158 ERR(" config parse error: bad line \"%s\"\n", line
);
161 if(endsection
[1] != 0)
163 char *end
= endsection
+1;
164 while(std::isspace(*end
))
166 if(*end
!= 0 && *end
!= '#')
168 ERR(" config parse error: bad line \"%s\"\n", line
);
175 if(al::strcasecmp(section
, "general") != 0)
178 char *nextp
= std::strchr(section
, '%');
181 curSection
+= section
;
185 curSection
.append(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')))
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
);
211 else if(section
[1] == '%')
221 } while(*section
!= 0);
227 auto cmtpos
= buffer
.find('#');
228 if(cmtpos
!= std::string::npos
)
229 buffer
.resize(cmtpos
);
230 while(!buffer
.empty() && std::isspace(buffer
.back()))
232 if(buffer
.empty()) continue;
234 const char *line
{&buffer
[0]};
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
243 if(std::strcmp(value
, "\"\"") == 0 || std::strcmp(value
, "''") == 0)
246 else if(std::sscanf(line
, "%255[^=] %255[=]", key
, value
) == 2)
248 /* Special case for 'key =' */
253 ERR(" config parse error: malformed option line: \"%s\"\n\n", line
);
258 if(!curSection
.empty())
260 fullKey
+= curSection
;
264 while(!fullKey
.empty() && std::isspace(fullKey
.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())
277 ent
->value
= expdup(value
);
282 ConfOpts
.emplace_back(ConfigEntry
{std::move(fullKey
), expdup(value
)});
284 ConfOpts
.shrink_to_fit();
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
};
302 LoadConfigFromFile(f
);
305 std::string ppath
{GetProcBinary().path
};
308 ppath
+= "\\alsoft.ini";
309 TRACE("Loading config %s...\n", ppath
.c_str());
310 al::ifstream f
{ppath
};
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
};
320 LoadConfigFromFile(f
);
328 const char *str
{"/etc/openal/alsoft.conf"};
330 TRACE("Loading config %s...\n", str
);
333 LoadConfigFromFile(f
);
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.
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
);
357 if(fname
.empty() || fname
.front() != '/')
358 WARN("Ignoring XDG config dir: %s\n", fname
.c_str());
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
};
367 LoadConfigFromFile(f
);
373 CFBundleRef mainBundle
= CFBundleGetMainBundle();
376 unsigned char fileName
[PATH_MAX
];
379 if((configURL
=CFBundleCopyResourceURL(mainBundle
, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
380 CFURLGetFileSystemRepresentation(configURL
, true, fileName
, sizeof(fileName
)))
382 f
= al::ifstream
{reinterpret_cast<char*>(fileName
)};
384 LoadConfigFromFile(f
);
389 if(auto homedir
= al::getenv("HOME"))
392 if(fname
.back() != '/') fname
+= "/.alsoftrc";
393 else fname
+= ".alsoftrc";
395 TRACE("Loading config %s...\n", fname
.c_str());
396 f
= al::ifstream
{fname
};
398 LoadConfigFromFile(f
);
401 if(auto configdir
= al::getenv("XDG_CONFIG_HOME"))
404 if(fname
.back() != '/') fname
+= "/alsoft.conf";
405 else fname
+= "alsoft.conf";
410 if(auto homedir
= al::getenv("HOME"))
413 if(fname
.back() != '/') fname
+= "/.config/alsoft.conf";
414 else fname
+= ".config/alsoft.conf";
419 TRACE("Loading config %s...\n", fname
.c_str());
420 f
= al::ifstream
{fname
};
422 LoadConfigFromFile(f
);
425 std::string ppath
{GetProcBinary().path
};
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
};
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
};
442 LoadConfigFromFile(f
);
447 const char *GetConfigValue(const char *devName
, const char *blockName
, const char *keyName
, const char *def
)
453 if(blockName
&& al::strcasecmp(blockName
, "general") != 0)
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();
488 TRACE("Key %s not found\n", key
.c_str());
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
, "");
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);