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>
42 #include <string_view>
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() > size_t{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 auto GetConfigValue(const std::string_view devName
, const std::string_view blockName
,
295 const std::string_view keyName
) -> const std::string
&
297 static const auto emptyString
= std::string
{};
302 if(!blockName
.empty() && al::case_compare(blockName
, "general"sv
) != 0)
314 auto iter
= std::find_if(ConfOpts
.cbegin(), ConfOpts
.cend(),
315 [&key
](const ConfigEntry
&entry
) -> bool { return entry
.key
== key
; });
316 if(iter
!= ConfOpts
.cend())
318 TRACE("Found option %s = \"%s\"\n", key
.c_str(), iter
->value
.c_str());
319 if(!iter
->value
.empty())
326 return GetConfigValue({}, blockName
, keyName
);
335 namespace fs
= std::filesystem
;
338 #if !defined(_GAMING_XBOX)
340 #if !defined(ALSOFT_UWP)
341 std::unique_ptr
<WCHAR
,CoTaskMemDeleter
> bufstore
;
342 const HRESULT hr
{SHGetKnownFolderPath(FOLDERID_RoamingAppData
, KF_FLAG_DONT_UNEXPAND
,
343 nullptr, al::out_ptr(bufstore
))};
346 const std::wstring_view buffer
{bufstore
.get()};
348 winrt::Windows::Storage::ApplicationDataContainer localSettings
= winrt::Windows::Storage::ApplicationData::Current().LocalSettings();
349 auto bufstore
= Windows::Storage::ApplicationData::Current().RoamingFolder().Path();
350 std::wstring_view buffer
{bufstore
};
353 path
= fs::path
{buffer
};
354 path
/= L
"alsoft.ini";
356 TRACE("Loading config %s...\n", path
.u8string().c_str());
357 if(std::ifstream f
{path
}; f
.is_open())
358 LoadConfigFromFile(f
);
363 path
= fs::u8path(GetProcBinary().path
);
366 path
/= "alsoft.ini";
367 TRACE("Loading config %s...\n", path
.u8string().c_str());
368 if(std::ifstream f
{path
}; f
.is_open())
369 LoadConfigFromFile(f
);
372 if(auto confpath
= al::getenv(L
"ALSOFT_CONF"))
375 TRACE("Loading config %s...\n", path
.u8string().c_str());
376 if(std::ifstream f
{path
}; f
.is_open())
377 LoadConfigFromFile(f
);
385 namespace fs
= std::filesystem
;
386 fs::path path
{"/etc/openal/alsoft.conf"};
388 TRACE("Loading config %s...\n", path
.u8string().c_str());
389 if(std::ifstream f
{path
}; f
.is_open())
390 LoadConfigFromFile(f
);
392 std::string confpaths
{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
393 /* Go through the list in reverse, since "the order of base directories
394 * denotes their importance; the first directory listed is the most
395 * important". Ergo, we need to load the settings from the later dirs
396 * first so that the settings in the earlier dirs override them.
398 while(!confpaths
.empty())
400 auto next
= confpaths
.rfind(':');
401 if(next
< confpaths
.length())
403 path
= fs::path
{std::string_view
{confpaths
}.substr(next
+1)}.lexically_normal();
404 confpaths
.erase(next
);
408 path
= fs::path
{confpaths
}.lexically_normal();
412 if(!path
.is_absolute())
413 WARN("Ignoring XDG config dir: %s\n", path
.u8string().c_str());
416 path
/= "alsoft.conf";
418 TRACE("Loading config %s...\n", path
.u8string().c_str());
419 if(std::ifstream f
{path
}; f
.is_open())
420 LoadConfigFromFile(f
);
425 CFBundleRef mainBundle
= CFBundleGetMainBundle();
428 CFURLRef configURL
{CFBundleCopyResourceURL(mainBundle
, CFSTR(".alsoftrc"), CFSTR(""),
431 std::array
<unsigned char,PATH_MAX
> fileName
{};
432 if(configURL
&& CFURLGetFileSystemRepresentation(configURL
, true, fileName
.data(), fileName
.size()))
434 if(std::ifstream f
{reinterpret_cast<char*>(fileName
.data())}; f
.is_open())
435 LoadConfigFromFile(f
);
440 if(auto homedir
= al::getenv("HOME"))
445 TRACE("Loading config %s...\n", path
.u8string().c_str());
446 if(std::ifstream f
{path
}; f
.is_open())
447 LoadConfigFromFile(f
);
450 if(auto configdir
= al::getenv("XDG_CONFIG_HOME"))
453 path
/= "alsoft.conf";
458 if(auto homedir
= al::getenv("HOME"))
461 path
/= ".config/alsoft.conf";
466 TRACE("Loading config %s...\n", path
.u8string().c_str());
467 if(std::ifstream f
{path
}; f
.is_open())
468 LoadConfigFromFile(f
);
471 path
= GetProcBinary().path
;
474 path
/= "alsoft.conf";
476 TRACE("Loading config %s...\n", path
.u8string().c_str());
477 if(std::ifstream f
{path
}; f
.is_open())
478 LoadConfigFromFile(f
);
481 if(auto confname
= al::getenv("ALSOFT_CONF"))
483 TRACE("Loading config %s...\n", confname
->c_str());
484 if(std::ifstream f
{*confname
}; f
.is_open())
485 LoadConfigFromFile(f
);
490 std::optional
<std::string
> ConfigValueStr(const std::string_view devName
,
491 const std::string_view blockName
, const std::string_view keyName
)
493 if(auto&& val
= GetConfigValue(devName
, blockName
, keyName
); !val
.empty())
498 std::optional
<int> ConfigValueInt(const std::string_view devName
, const std::string_view blockName
,
499 const std::string_view keyName
)
501 if(auto&& val
= GetConfigValue(devName
, blockName
, keyName
); !val
.empty())
502 return static_cast<int>(std::stol(val
, nullptr, 0));
506 std::optional
<unsigned int> ConfigValueUInt(const std::string_view devName
,
507 const std::string_view blockName
, const std::string_view keyName
)
509 if(auto&& val
= GetConfigValue(devName
, blockName
, keyName
); !val
.empty())
510 return static_cast<unsigned int>(std::stoul(val
, nullptr, 0));
514 std::optional
<float> ConfigValueFloat(const std::string_view devName
,
515 const std::string_view blockName
, const std::string_view keyName
)
517 if(auto&& val
= GetConfigValue(devName
, blockName
, keyName
); !val
.empty())
518 return std::stof(val
);
522 std::optional
<bool> ConfigValueBool(const std::string_view devName
,
523 const std::string_view blockName
, const std::string_view keyName
)
525 if(auto&& val
= GetConfigValue(devName
, blockName
, keyName
); !val
.empty())
526 return al::case_compare(val
, "on"sv
) == 0 || al::case_compare(val
, "yes"sv
) == 0
527 || al::case_compare(val
, "true"sv
) == 0 || std::stoll(val
) != 0;
531 bool GetConfigValueBool(const std::string_view devName
, const std::string_view blockName
,
532 const std::string_view keyName
, bool def
)
534 if(auto&& val
= GetConfigValue(devName
, blockName
, keyName
); !val
.empty())
535 return al::case_compare(val
, "on") == 0 || al::case_compare(val
, "yes") == 0
536 || al::case_compare(val
, "true") == 0 || std::stoll(val
) != 0;