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
39 #include <CoreFoundation/CoreFoundation.h>
57 template<typename T0
, typename T1
>
58 ConfigEntry(T0
&& key_
, T1
&& val_
)
59 : key
{std::forward
<T0
>(key_
)}, value
{std::forward
<T1
>(val_
)}
62 std::vector
<ConfigEntry
> ConfOpts
;
65 std::string
&lstrip(std::string
&line
)
68 while(pos
< line
.length() && std::isspace(line
[pos
]))
74 bool readline(std::istream
&f
, std::string
&output
)
76 while(f
.good() && f
.peek() == '\n')
79 return std::getline(f
, output
) && !output
.empty();
82 std:: string
expdup(const char *str
)
93 const char *next
= std::strchr(str
, '$');
95 addstrlen
= next
? (size_t)(next
-str
) : std::strlen(str
);
104 const char *next
= std::strchr(str
+1, '$');
106 addstrlen
= next
? (size_t)(next
-str
) : std::strlen(str
);
112 bool hasbraces
{(*str
== '{')};
116 while((std::isalnum(*str
) || *str
== '_'))
119 if(hasbraces
&& *str
!= '}')
123 if((addstr
=std::getenv(envname
.c_str())) == nullptr)
125 addstrlen
= std::strlen(addstr
);
131 output
.append(addstr
, addstrlen
);
137 void LoadConfigFromFile(std::istream
&f
)
139 std::string curSection
;
142 while(readline(f
, buffer
))
144 while(!buffer
.empty() && std::isspace(buffer
.back()))
146 if(lstrip(buffer
).empty())
150 char *line
{&buffer
[0]};
154 char *section
= line
+1;
157 endsection
= std::strchr(section
, ']');
158 if(!endsection
|| section
== endsection
)
160 ERR("config parse error: bad line \"%s\"\n", line
);
163 if(endsection
[1] != 0)
165 char *end
= endsection
+1;
166 while(std::isspace(*end
))
168 if(*end
!= 0 && *end
!= '#')
170 ERR("config parse error: bad line \"%s\"\n", line
);
177 if(strcasecmp(section
, "general") != 0)
180 char *nextp
= std::strchr(section
, '%');
183 curSection
+= section
;
187 curSection
.append(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')))
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
);
213 else if(section
[1] == '%')
223 } while(*section
!= 0);
229 char *comment
{std::strchr(line
, '#')};
230 if(comment
) *(comment
++) = 0;
231 if(!line
[0]) continue;
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
241 if(std::strcmp(value
, "\"\"") == 0 || std::strcmp(value
, "''") == 0)
244 else if(sscanf(line
, "%255[^=] %255[=]", key
, value
) == 2)
246 /* Special case for 'key =' */
251 ERR("config parse error: malformed option line: \"%s\"\n\n", line
);
256 if(!curSection
.empty())
258 fullKey
+= curSection
;
262 while(!fullKey
.empty() && std::isspace(fullKey
.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
);
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();
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
};
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()};
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
};
319 LoadConfigFromFile(f
);
323 void ReadALConfig(void) noexcept
325 const char *str
{"/etc/openal/alsoft.conf"};
327 TRACE("Loading config %s...\n", str
);
330 LoadConfigFromFile(f
);
333 if(!(str
=getenv("XDG_CONFIG_DIRS")) || str
[0] == 0)
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.
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
);
356 if(fname
.empty() || fname
.front() != '/')
357 WARN("Ignoring XDG config dir: %s\n", fname
.c_str());
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
};
366 LoadConfigFromFile(f
);
372 CFBundleRef mainBundle
= CFBundleGetMainBundle();
375 unsigned char fileName
[PATH_MAX
];
378 if((configURL
=CFBundleCopyResourceURL(mainBundle
, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
379 CFURLGetFileSystemRepresentation(configURL
, true, fileName
, sizeof(fileName
)))
381 al::ifstream f
{reinterpret_cast<char*>(fileName
)};
383 LoadConfigFromFile(f
);
388 if((str
=getenv("HOME")) != nullptr && *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
};
397 LoadConfigFromFile(f
);
400 if((str
=getenv("XDG_CONFIG_HOME")) != nullptr && str
[0] != 0)
403 if(fname
.back() != '/') fname
+= "/alsoft.conf";
404 else fname
+= "alsoft.conf";
409 if((str
=getenv("HOME")) != nullptr && str
[0] != 0)
412 if(fname
.back() != '/') fname
+= "/.config/alsoft.conf";
413 else fname
+= ".config/alsoft.conf";
418 TRACE("Loading config %s...\n", fname
.c_str());
419 al::ifstream f
{fname
};
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
};
433 LoadConfigFromFile(f
);
436 if((str
=getenv("ALSOFT_CONF")) != nullptr && *str
)
438 TRACE("Loading config %s...\n", str
);
441 LoadConfigFromFile(f
);
446 void FreeALConfig(void)
451 const char *GetConfigValue(const char *devName
, const char *blockName
, const char *keyName
, const char *def
)
457 if(blockName
&& strcasecmp(blockName
, "general") != 0)
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();
492 TRACE("Key %s not found\n", key
.c_str());
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
, "");
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;
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);
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);
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);
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);
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);