Recogmize jack64 for finding the JACK library name
[openal-soft.git] / alc / alconfig.cpp
blob975a7ea6b949ef98d247400807fcfa32871b9dac
1 /**
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
21 #include "config.h"
23 #include "alconfig.h"
25 #ifdef _WIN32
26 #include <windows.h>
27 #include <shlobj.h>
28 #endif
29 #ifdef __APPLE__
30 #include <CoreFoundation/CoreFoundation.h>
31 #endif
33 #include <algorithm>
34 #include <array>
35 #include <cctype>
36 #include <cstdlib>
37 #include <filesystem>
38 #include <fstream>
39 #include <istream>
40 #include <limits>
41 #include <string>
42 #include <string_view>
43 #include <utility>
44 #include <vector>
46 #include "almalloc.h"
47 #include "alstring.h"
48 #include "core/helpers.h"
49 #include "core/logging.h"
50 #include "strutils.h"
52 #if 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;
58 #endif
60 namespace {
62 using namespace std::string_view_literals;
64 #if defined(_WIN32) && !defined(_GAMING_XBOX) && !ALSOFT_UWP
65 struct CoTaskMemDeleter {
66 void operator()(void *mem) const { CoTaskMemFree(mem); }
68 #endif
70 struct ConfigEntry {
71 std::string key;
72 std::string value;
74 std::vector<ConfigEntry> ConfOpts;
77 std::string &lstrip(std::string &line)
79 size_t pos{0};
80 while(pos < line.length() && std::isspace(line[pos]))
81 ++pos;
82 line.erase(0, pos);
83 return line;
86 bool readline(std::istream &f, std::string &output)
88 while(f.good() && f.peek() == '\n')
89 f.ignore();
91 return std::getline(f, output) && !output.empty();
94 std::string expdup(std::string_view str)
96 std::string output;
98 while(!str.empty())
100 if(auto nextpos = str.find('$'))
102 output += str.substr(0, nextpos);
103 if(nextpos == std::string_view::npos)
104 break;
106 str.remove_prefix(nextpos);
109 str.remove_prefix(1);
110 if(str.empty())
112 output += '$';
113 break;
115 if(str.front() == '$')
117 output += '$';
118 str.remove_prefix(1);
119 continue;
122 const bool hasbraces{str.front() == '{'};
123 if(hasbraces) str.remove_prefix(1);
125 size_t envend{0};
126 while(envend < str.size() && (std::isalnum(str[envend]) || str[envend] == '_'))
127 ++envend;
128 if(hasbraces && (envend == str.size() || str[envend] != '}'))
129 continue;
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()))
135 output += *envval;
138 return output;
141 void LoadConfigFromFile(std::istream &f)
143 std::string curSection;
144 std::string buffer;
146 while(readline(f, buffer))
148 if(lstrip(buffer).empty())
149 continue;
151 if(buffer[0] == '[')
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());
157 continue;
159 if(buffer[endpos+1] != '\0')
161 size_t last{endpos+1};
162 while(last < buffer.size() && std::isspace(buffer[last]))
163 ++last;
165 if(last < buffer.size() && buffer[last] != '#')
167 ERR(" config parse error: bad line \"%s\"\n", buffer.c_str());
168 continue;
172 auto section = std::string_view{buffer}.substr(1, endpos-1);
174 curSection.clear();
175 if(al::case_compare(section, "general"sv) != 0)
177 do {
178 auto nextp = section.find('%');
179 if(nextp == std::string_view::npos)
181 curSection += section;
182 break;
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')))
196 int b{0};
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] == '%')
214 curSection += '%';
215 section.remove_prefix(2);
217 else
219 curSection += '%';
220 section.remove_prefix(1);
222 } while(!section.empty());
225 continue;
228 auto cmtpos = std::min(buffer.find('#'), buffer.size());
229 while(cmtpos > 0 && std::isspace(buffer[cmtpos-1]))
230 --cmtpos;
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());
238 continue;
240 auto keypart = std::string_view{buffer}.substr(0, sep++);
241 while(!keypart.empty() && std::isspace(keypart.back()))
242 keypart.remove_suffix(1);
243 if(keypart.empty())
245 ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
246 continue;
248 auto valpart = std::string_view{buffer}.substr(sep);
249 while(!valpart.empty() && std::isspace(valpart.front()))
250 valpart.remove_prefix(1);
252 std::string fullKey;
253 if(!curSection.empty())
255 fullKey += curSection;
256 fullKey += '/';
258 fullKey += keypart;
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());
263 continue;
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())
283 if(!valpart.empty())
284 ent->value = expdup(valpart);
285 else
286 ConfOpts.erase(ent);
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{};
298 if(keyName.empty())
299 return emptyString;
301 std::string key;
302 if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0)
304 key = blockName;
305 key += '/';
307 if(!devName.empty())
309 key += devName;
310 key += '/';
312 key += keyName;
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())
320 return iter->value;
321 return emptyString;
324 if(devName.empty())
325 return emptyString;
326 return GetConfigValue({}, blockName, keyName);
329 } // namespace
332 #ifdef _WIN32
333 void ReadALConfig()
335 namespace fs = std::filesystem;
336 fs::path path;
338 #if !defined(_GAMING_XBOX)
340 #if !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))};
344 if(SUCCEEDED(hr))
346 const std::wstring_view buffer{bufstore.get()};
347 #else
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};
352 #endif
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);
361 #endif
363 path = fs::u8path(GetProcBinary().path);
364 if(!path.empty())
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"))
374 path = *confpath;
375 TRACE("Loading config %s...\n", path.u8string().c_str());
376 if(std::ifstream f{path}; f.is_open())
377 LoadConfigFromFile(f);
381 #else
383 void ReadALConfig()
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);
406 else
408 path = fs::path{confpaths}.lexically_normal();
409 confpaths.clear();
412 if(!path.is_absolute())
413 WARN("Ignoring XDG config dir: %s\n", path.u8string().c_str());
414 else
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);
424 #ifdef __APPLE__
425 CFBundleRef mainBundle = CFBundleGetMainBundle();
426 if(mainBundle)
428 CFURLRef configURL{CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""),
429 nullptr)};
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);
438 #endif
440 if(auto homedir = al::getenv("HOME"))
442 path = *homedir;
443 path /= ".alsoftrc";
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"))
452 path = *configdir;
453 path /= "alsoft.conf";
455 else
457 path.clear();
458 if(auto homedir = al::getenv("HOME"))
460 path = *homedir;
461 path /= ".config/alsoft.conf";
464 if(!path.empty())
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;
472 if(!path.empty())
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);
488 #endif
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())
494 return val;
495 return std::nullopt;
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));
503 return std::nullopt;
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));
511 return std::nullopt;
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);
519 return std::nullopt;
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;
528 return std::nullopt;
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;
537 return def;