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
30 #include <CoreFoundation/CoreFoundation.h>
41 #include <string_view>
45 #include "alfstream.h"
48 #include "core/helpers.h"
49 #include "core/logging.h"
52 #if defined(ALSOFT_UWP)
53 #include <winrt/Windows.Media.Core.h> // !!This is important!!
54 #include <winrt/Windows.Storage.h>
55 #include <winrt/Windows.Foundation.h>
56 #include <winrt/Windows.Foundation.Collections.h>
57 using namespace winrt
;
62 using namespace std::string_view_literals
;
64 #if defined(_WIN32) && !defined(_GAMING_XBOX) && !defined(ALSOFT_UWP)
65 struct CoTaskMemDeleter
{
66 void operator()(void *mem
) const { CoTaskMemFree(mem
); }
74 std::vector
<ConfigEntry
> ConfOpts
;
77 std::string
&lstrip(std::string
&line
)
80 while(pos
< line
.length() && std::isspace(line
[pos
]))
86 bool readline(std::istream
&f
, std::string
&output
)
88 while(f
.good() && f
.peek() == '\n')
91 return std::getline(f
, output
) && !output
.empty();
94 std::string
expdup(std::string_view str
)
100 if(auto nextpos
= str
.find('$'))
102 output
+= str
.substr(0, nextpos
);
103 if(nextpos
== std::string_view::npos
)
106 str
.remove_prefix(nextpos
);
109 str
.remove_prefix(1);
115 if(str
.front() == '$')
118 str
.remove_prefix(1);
122 const bool hasbraces
{str
.front() == '{'};
123 if(hasbraces
) str
.remove_prefix(1);
126 while(envend
< str
.size() && (std::isalnum(str
[envend
]) || str
[envend
] == '_'))
128 if(hasbraces
&& (envend
== str
.size() || str
[envend
] != '}'))
130 const std::string envname
{str
.substr(0, envend
)};
131 if(hasbraces
) ++envend
;
132 str
.remove_prefix(envend
);
134 if(auto envval
= al::getenv(envname
.c_str()))
141 void LoadConfigFromFile(std::istream
&f
)
143 std::string curSection
;
146 while(readline(f
, buffer
))
148 if(lstrip(buffer
).empty())
153 auto endpos
= buffer
.find(']', 1);
154 if(endpos
== 1 || endpos
== std::string::npos
)
156 ERR(" config parse error: bad line \"%s\"\n", buffer
.c_str());
159 if(buffer
[endpos
+1] != '\0')
161 size_t last
{endpos
+1};
162 while(last
< buffer
.size() && std::isspace(buffer
[last
]))
165 if(last
< buffer
.size() && buffer
[last
] != '#')
167 ERR(" config parse error: bad line \"%s\"\n", buffer
.c_str());
172 auto section
= std::string_view
{buffer
}.substr(1, endpos
-1);
175 if(al::case_compare(section
, "general"sv
) != 0)
178 auto nextp
= section
.find('%');
179 if(nextp
== std::string_view::npos
)
181 curSection
+= section
;
185 curSection
+= section
.substr(0, nextp
);
186 section
.remove_prefix(nextp
);
188 if(section
.size() > 2 &&
189 ((section
[1] >= '0' && section
[1] <= '9') ||
190 (section
[1] >= 'a' && section
[1] <= 'f') ||
191 (section
[1] >= 'A' && section
[1] <= 'F')) &&
192 ((section
[2] >= '0' && section
[2] <= '9') ||
193 (section
[2] >= 'a' && section
[2] <= 'f') ||
194 (section
[2] >= 'A' && section
[2] <= 'F')))
197 if(section
[1] >= '0' && section
[1] <= '9')
198 b
= (section
[1]-'0') << 4;
199 else if(section
[1] >= 'a' && section
[1] <= 'f')
200 b
= (section
[1]-'a'+0xa) << 4;
201 else if(section
[1] >= 'A' && section
[1] <= 'F')
202 b
= (section
[1]-'A'+0x0a) << 4;
203 if(section
[2] >= '0' && section
[2] <= '9')
204 b
|= (section
[2]-'0');
205 else if(section
[2] >= 'a' && section
[2] <= 'f')
206 b
|= (section
[2]-'a'+0xa);
207 else if(section
[2] >= 'A' && section
[2] <= 'F')
208 b
|= (section
[2]-'A'+0x0a);
209 curSection
+= static_cast<char>(b
);
210 section
.remove_prefix(3);
212 else if(section
.size() > 1 && section
[1] == '%')
215 section
.remove_prefix(2);
220 section
.remove_prefix(1);
222 } while(!section
.empty());
228 auto cmtpos
= std::min(buffer
.find('#'), buffer
.size());
229 while(cmtpos
> 0 && std::isspace(buffer
[cmtpos
-1]))
231 if(!cmtpos
) continue;
232 buffer
.erase(cmtpos
);
234 auto sep
= buffer
.find('=');
235 if(sep
== std::string::npos
)
237 ERR(" config parse error: malformed option line: \"%s\"\n", buffer
.c_str());
240 auto keypart
= std::string_view
{buffer
}.substr(0, sep
++);
241 while(!keypart
.empty() && std::isspace(keypart
.back()))
242 keypart
.remove_suffix(1);
245 ERR(" config parse error: malformed option line: \"%s\"\n", buffer
.c_str());
248 auto valpart
= std::string_view
{buffer
}.substr(sep
);
249 while(!valpart
.empty() && std::isspace(valpart
.front()))
250 valpart
.remove_prefix(1);
253 if(!curSection
.empty())
255 fullKey
+= curSection
;
260 if(valpart
.size() > std::numeric_limits
<int>::max())
262 ERR(" config parse error: value too long in line \"%s\"\n", buffer
.c_str());
265 if(valpart
.size() > 1)
267 if((valpart
.front() == '"' && valpart
.back() == '"')
268 || (valpart
.front() == '\'' && valpart
.back() == '\''))
270 valpart
.remove_prefix(1);
271 valpart
.remove_suffix(1);
275 TRACE(" setting '%s' = '%.*s'\n", fullKey
.c_str(), al::sizei(valpart
), valpart
.data());
277 /* Check if we already have this option set */
278 auto find_key
= [&fullKey
](const ConfigEntry
&entry
) -> bool
279 { return entry
.key
== fullKey
; };
280 auto ent
= std::find_if(ConfOpts
.begin(), ConfOpts
.end(), find_key
);
281 if(ent
!= ConfOpts
.end())
284 ent
->value
= expdup(valpart
);
288 else if(!valpart
.empty())
289 ConfOpts
.emplace_back(ConfigEntry
{std::move(fullKey
), expdup(valpart
)});
291 ConfOpts
.shrink_to_fit();
294 const char *GetConfigValue(const std::string_view devName
, const std::string_view blockName
,
295 const std::string_view keyName
)
301 if(!blockName
.empty() && al::case_compare(blockName
, "general"sv
) != 0)
313 auto iter
= std::find_if(ConfOpts
.cbegin(), ConfOpts
.cend(),
314 [&key
](const ConfigEntry
&entry
) -> bool { return entry
.key
== key
; });
315 if(iter
!= ConfOpts
.cend())
317 TRACE("Found option %s = \"%s\"\n", key
.c_str(), iter
->value
.c_str());
318 if(!iter
->value
.empty())
319 return iter
->value
.c_str();
325 return GetConfigValue({}, blockName
, keyName
);
334 namespace fs
= std::filesystem
;
337 #if !defined(_GAMING_XBOX)
339 #if !defined(ALSOFT_UWP)
340 std::unique_ptr
<WCHAR
,CoTaskMemDeleter
> bufstore
;
341 const HRESULT hr
{SHGetKnownFolderPath(FOLDERID_RoamingAppData
, KF_FLAG_DONT_UNEXPAND
,
342 nullptr, al::out_ptr(bufstore
))};
345 const std::wstring_view buffer
{bufstore
.get()};
347 winrt::Windows::Storage::ApplicationDataContainer localSettings
= winrt::Windows::Storage::ApplicationData::Current().LocalSettings();
348 auto bufstore
= Windows::Storage::ApplicationData::Current().RoamingFolder().Path();
349 std::wstring_view buffer
{bufstore
};
352 path
= fs::path
{buffer
};
353 path
/= "alsoft.ini";
355 TRACE("Loading config %s...\n", path
.u8string().c_str());
356 if(al::ifstream f
{path
}; f
.is_open())
357 LoadConfigFromFile(f
);
362 path
= fs::u8path(GetProcBinary().path
);
365 path
/= "alsoft.ini";
366 TRACE("Loading config %s...\n", path
.u8string().c_str());
367 if(al::ifstream f
{path
}; f
.is_open())
368 LoadConfigFromFile(f
);
371 if(auto confpath
= al::getenv(L
"ALSOFT_CONF"))
374 TRACE("Loading config %s...\n", path
.u8string().c_str());
375 if(al::ifstream f
{path
}; f
.is_open())
376 LoadConfigFromFile(f
);
384 namespace fs
= std::filesystem
;
385 fs::path path
{"/etc/openal/alsoft.conf"};
387 TRACE("Loading config %s...\n", path
.u8string().c_str());
388 if(al::ifstream f
{path
}; f
.is_open())
389 LoadConfigFromFile(f
);
391 std::string confpaths
{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
392 /* Go through the list in reverse, since "the order of base directories
393 * denotes their importance; the first directory listed is the most
394 * important". Ergo, we need to load the settings from the later dirs
395 * first so that the settings in the earlier dirs override them.
397 while(!confpaths
.empty())
399 auto next
= confpaths
.rfind(':');
400 if(next
< confpaths
.length())
402 path
= fs::path
{std::string_view
{confpaths
}.substr(next
+1)}.lexically_normal();
403 confpaths
.erase(next
);
407 path
= fs::path
{confpaths
}.lexically_normal();
411 if(!path
.is_absolute())
412 WARN("Ignoring XDG config dir: %s\n", path
.u8string().c_str());
415 path
/= "alsoft.conf";
417 TRACE("Loading config %s...\n", path
.u8string().c_str());
418 if(al::ifstream f
{path
}; f
.is_open())
419 LoadConfigFromFile(f
);
424 CFBundleRef mainBundle
= CFBundleGetMainBundle();
427 CFURLRef configURL
{CFBundleCopyResourceURL(mainBundle
, CFSTR(".alsoftrc"), CFSTR(""),
430 std::array
<unsigned char,PATH_MAX
> fileName
{};
431 if(configURL
&& CFURLGetFileSystemRepresentation(configURL
, true, fileName
.data(), fileName
.size()))
433 if(al::ifstream f
{reinterpret_cast<char*>(fileName
.data())}; f
.is_open())
434 LoadConfigFromFile(f
);
439 if(auto homedir
= al::getenv("HOME"))
444 TRACE("Loading config %s...\n", path
.u8string().c_str());
445 if(al::ifstream f
{path
}; f
.is_open())
446 LoadConfigFromFile(f
);
449 if(auto configdir
= al::getenv("XDG_CONFIG_HOME"))
452 path
/= "alsoft.conf";
457 if(auto homedir
= al::getenv("HOME"))
460 path
/= ".config/alsoft.conf";
465 TRACE("Loading config %s...\n", path
.u8string().c_str());
466 if(al::ifstream f
{path
}; f
.is_open())
467 LoadConfigFromFile(f
);
470 path
= GetProcBinary().path
;
473 path
/= "alsoft.conf";
475 TRACE("Loading config %s...\n", path
.u8string().c_str());
476 if(al::ifstream f
{path
}; f
.is_open())
477 LoadConfigFromFile(f
);
480 if(auto confname
= al::getenv("ALSOFT_CONF"))
482 TRACE("Loading config %s...\n", confname
->c_str());
483 if(al::ifstream f
{*confname
}; f
.is_open())
484 LoadConfigFromFile(f
);
489 std::optional
<std::string
> ConfigValueStr(const std::string_view devName
,
490 const std::string_view blockName
, const std::string_view keyName
)
492 if(const char *val
{GetConfigValue(devName
, blockName
, keyName
)})
497 std::optional
<int> ConfigValueInt(const std::string_view devName
, const std::string_view blockName
,
498 const std::string_view keyName
)
500 if(const char *val
{GetConfigValue(devName
, blockName
, keyName
)})
501 return static_cast<int>(std::strtol(val
, nullptr, 0));
505 std::optional
<unsigned int> ConfigValueUInt(const std::string_view devName
,
506 const std::string_view blockName
, const std::string_view keyName
)
508 if(const char *val
{GetConfigValue(devName
, blockName
, keyName
)})
509 return static_cast<unsigned int>(std::strtoul(val
, nullptr, 0));
513 std::optional
<float> ConfigValueFloat(const std::string_view devName
,
514 const std::string_view blockName
, const std::string_view keyName
)
516 if(const char *val
{GetConfigValue(devName
, blockName
, keyName
)})
517 return std::strtof(val
, nullptr);
521 std::optional
<bool> ConfigValueBool(const std::string_view devName
,
522 const std::string_view blockName
, const std::string_view keyName
)
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;
530 bool GetConfigValueBool(const std::string_view devName
, const std::string_view blockName
,
531 const std::string_view keyName
, bool def
)
533 if(const char *val
{GetConfigValue(devName
, blockName
, keyName
)})
534 return al::strcasecmp(val
, "on") == 0 || al::strcasecmp(val
, "yes") == 0
535 || al::strcasecmp(val
, "true") == 0 || atoi(val
) != 0;