diff options
Diffstat (limited to 'alc/alconfig.cpp')
-rw-r--r-- | alc/alconfig.cpp | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/alc/alconfig.cpp b/alc/alconfig.cpp new file mode 100644 index 00000000..ede39156 --- /dev/null +++ b/alc/alconfig.cpp @@ -0,0 +1,549 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#ifdef _WIN32 +#ifdef __MINGW32__ +#define _WIN32_IE 0x501 +#else +#define _WIN32_IE 0x400 +#endif +#endif + +#include "config.h" + +#include "alconfig.h" + +#include <cstdlib> +#include <cctype> +#include <cstring> +#ifdef _WIN32_IE +#include <windows.h> +#include <shlobj.h> +#endif +#ifdef __APPLE__ +#include <CoreFoundation/CoreFoundation.h> +#endif + +#include <algorithm> +#include <cstdio> +#include <string> +#include <utility> + +#include "alfstream.h" +#include "alstring.h" +#include "compat.h" +#include "logging.h" +#include "strutils.h" +#include "vector.h" + + +namespace { + +struct ConfigEntry { + std::string key; + std::string value; +}; +al::vector<ConfigEntry> ConfOpts; + + +std::string &lstrip(std::string &line) +{ + size_t pos{0}; + while(pos < line.length() && std::isspace(line[pos])) + ++pos; + line.erase(0, pos); + return line; +} + +bool readline(std::istream &f, std::string &output) +{ + while(f.good() && f.peek() == '\n') + f.ignore(); + + return std::getline(f, output) && !output.empty(); +} + +std::string expdup(const char *str) +{ + std::string output; + + std::string envval; + while(*str != '\0') + { + const char *addstr; + size_t addstrlen; + + if(str[0] != '$') + { + const char *next = std::strchr(str, '$'); + addstr = str; + addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str); + + str += addstrlen; + } + else + { + str++; + if(*str == '$') + { + const char *next = std::strchr(str+1, '$'); + addstr = str; + addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str); + + str += addstrlen; + } + else + { + const bool hasbraces{(*str == '{')}; + + if(hasbraces) str++; + const char *envstart = str; + while(std::isalnum(*str) || *str == '_') + ++str; + if(hasbraces && *str != '}') + continue; + const std::string envname{envstart, str}; + if(hasbraces) str++; + + envval = al::getenv(envname.c_str()).value_or(std::string{}); + addstr = envval.data(); + addstrlen = envval.length(); + } + } + if(addstrlen == 0) + continue; + + output.append(addstr, addstrlen); + } + + return output; +} + +void LoadConfigFromFile(std::istream &f) +{ + std::string curSection; + std::string buffer; + + while(readline(f, buffer)) + { + if(lstrip(buffer).empty()) + continue; + + if(buffer[0] == '[') + { + char *line{&buffer[0]}; + char *section = line+1; + char *endsection; + + endsection = std::strchr(section, ']'); + if(!endsection || section == endsection) + { + ERR(" config parse error: bad line \"%s\"\n", line); + continue; + } + if(endsection[1] != 0) + { + char *end = endsection+1; + while(std::isspace(*end)) + ++end; + if(*end != 0 && *end != '#') + { + ERR(" config parse error: bad line \"%s\"\n", line); + continue; + } + } + *endsection = 0; + + curSection.clear(); + if(al::strcasecmp(section, "general") != 0) + { + do { + char *nextp = std::strchr(section, '%'); + if(!nextp) + { + curSection += section; + break; + } + + curSection.append(section, nextp); + section = nextp; + + if(((section[1] >= '0' && section[1] <= '9') || + (section[1] >= 'a' && section[1] <= 'f') || + (section[1] >= 'A' && section[1] <= 'F')) && + ((section[2] >= '0' && section[2] <= '9') || + (section[2] >= 'a' && section[2] <= 'f') || + (section[2] >= 'A' && section[2] <= 'F'))) + { + int b{0}; + if(section[1] >= '0' && section[1] <= '9') + b = (section[1]-'0') << 4; + else if(section[1] >= 'a' && section[1] <= 'f') + b = (section[1]-'a'+0xa) << 4; + else if(section[1] >= 'A' && section[1] <= 'F') + b = (section[1]-'A'+0x0a) << 4; + if(section[2] >= '0' && section[2] <= '9') + b |= (section[2]-'0'); + else if(section[2] >= 'a' && section[2] <= 'f') + b |= (section[2]-'a'+0xa); + else if(section[2] >= 'A' && section[2] <= 'F') + b |= (section[2]-'A'+0x0a); + curSection += static_cast<char>(b); + section += 3; + } + else if(section[1] == '%') + { + curSection += '%'; + section += 2; + } + else + { + curSection += '%'; + section += 1; + } + } while(*section != 0); + } + + continue; + } + + auto cmtpos = buffer.find('#'); + if(cmtpos != std::string::npos) + buffer.resize(cmtpos); + while(!buffer.empty() && std::isspace(buffer.back())) + buffer.pop_back(); + if(buffer.empty()) continue; + + const char *line{&buffer[0]}; + char key[256]{}; + char value[256]{}; + if(std::sscanf(line, "%255[^=] = \"%255[^\"]\"", key, value) == 2 || + std::sscanf(line, "%255[^=] = '%255[^\']'", key, value) == 2 || + std::sscanf(line, "%255[^=] = %255[^\n]", key, value) == 2) + { + /* sscanf doesn't handle '' or "" as empty values, so clip it + * manually. */ + if(std::strcmp(value, "\"\"") == 0 || std::strcmp(value, "''") == 0) + value[0] = 0; + } + else if(std::sscanf(line, "%255[^=] %255[=]", key, value) == 2) + { + /* Special case for 'key =' */ + value[0] = 0; + } + else + { + ERR(" config parse error: malformed option line: \"%s\"\n\n", line); + continue; + } + + std::string fullKey; + if(!curSection.empty()) + { + fullKey += curSection; + fullKey += '/'; + } + fullKey += key; + while(!fullKey.empty() && std::isspace(fullKey.back())) + fullKey.pop_back(); + + TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value); + + /* Check if we already have this option set */ + auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), + [&fullKey](const ConfigEntry &entry) -> bool + { return entry.key == fullKey; } + ); + if(ent != ConfOpts.end()) + { + if(value[0]) + ent->value = expdup(value); + else + ConfOpts.erase(ent); + } + else if(value[0]) + ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value)}); + } + ConfOpts.shrink_to_fit(); +} + +} // namespace + + +#ifdef _WIN32 +void ReadALConfig() +{ + WCHAR buffer[MAX_PATH]; + if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE) + { + std::string filepath{wstr_to_utf8(buffer)}; + filepath += "\\alsoft.ini"; + + TRACE("Loading config %s...\n", filepath.c_str()); + al::ifstream f{filepath}; + if(f.is_open()) + LoadConfigFromFile(f); + } + + std::string ppath{GetProcBinary().path}; + if(!ppath.empty()) + { + ppath += "\\alsoft.ini"; + TRACE("Loading config %s...\n", ppath.c_str()); + al::ifstream f{ppath}; + if(f.is_open()) + LoadConfigFromFile(f); + } + + if(auto confpath = al::getenv(L"ALSOFT_CONF")) + { + TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str()); + al::ifstream f{*confpath}; + if(f.is_open()) + LoadConfigFromFile(f); + } +} + +#else + +void ReadALConfig() +{ + const char *str{"/etc/openal/alsoft.conf"}; + + TRACE("Loading config %s...\n", str); + al::ifstream f{str}; + if(f.is_open()) + LoadConfigFromFile(f); + f.close(); + + std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")}; + /* Go through the list in reverse, since "the order of base directories + * denotes their importance; the first directory listed is the most + * important". Ergo, we need to load the settings from the later dirs + * first so that the settings in the earlier dirs override them. + */ + std::string fname; + while(!confpaths.empty()) + { + auto next = confpaths.find_last_of(':'); + if(next < confpaths.length()) + { + fname = confpaths.substr(next+1); + confpaths.erase(next); + } + else + { + fname = confpaths; + confpaths.clear(); + } + + if(fname.empty() || fname.front() != '/') + WARN("Ignoring XDG config dir: %s\n", fname.c_str()); + else + { + if(fname.back() != '/') fname += "/alsoft.conf"; + else fname += "alsoft.conf"; + + TRACE("Loading config %s...\n", fname.c_str()); + f = al::ifstream{fname}; + if(f.is_open()) + LoadConfigFromFile(f); + } + fname.clear(); + } + +#ifdef __APPLE__ + CFBundleRef mainBundle = CFBundleGetMainBundle(); + if(mainBundle) + { + unsigned char fileName[PATH_MAX]; + CFURLRef configURL; + + if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) && + CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName))) + { + f = al::ifstream{reinterpret_cast<char*>(fileName)}; + if(f.is_open()) + LoadConfigFromFile(f); + } + } +#endif + + if(auto homedir = al::getenv("HOME")) + { + fname = *homedir; + if(fname.back() != '/') fname += "/.alsoftrc"; + else fname += ".alsoftrc"; + + TRACE("Loading config %s...\n", fname.c_str()); + f = al::ifstream{fname}; + if(f.is_open()) + LoadConfigFromFile(f); + } + + if(auto configdir = al::getenv("XDG_CONFIG_HOME")) + { + fname = *configdir; + if(fname.back() != '/') fname += "/alsoft.conf"; + else fname += "alsoft.conf"; + } + else + { + fname.clear(); + if(auto homedir = al::getenv("HOME")) + { + fname = *homedir; + if(fname.back() != '/') fname += "/.config/alsoft.conf"; + else fname += ".config/alsoft.conf"; + } + } + if(!fname.empty()) + { + TRACE("Loading config %s...\n", fname.c_str()); + f = al::ifstream{fname}; + if(f.is_open()) + LoadConfigFromFile(f); + } + + std::string ppath{GetProcBinary().path}; + if(!ppath.empty()) + { + if(ppath.back() != '/') ppath += "/alsoft.conf"; + else ppath += "alsoft.conf"; + + TRACE("Loading config %s...\n", ppath.c_str()); + f = al::ifstream{ppath}; + if(f.is_open()) + LoadConfigFromFile(f); + } + + if(auto confname = al::getenv("ALSOFT_CONF")) + { + TRACE("Loading config %s...\n", confname->c_str()); + f = al::ifstream{*confname}; + if(f.is_open()) + LoadConfigFromFile(f); + } +} +#endif + +const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def) +{ + if(!keyName) + return def; + + std::string key; + if(blockName && al::strcasecmp(blockName, "general") != 0) + { + key = blockName; + if(devName) + { + key += '/'; + key += devName; + } + key += '/'; + key += keyName; + } + else + { + if(devName) + { + key = devName; + key += '/'; + } + key += keyName; + } + + auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(), + [&key](const ConfigEntry &entry) -> bool + { return entry.key == key; } + ); + if(iter != ConfOpts.cend()) + { + TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str()); + if(!iter->value.empty()) + return iter->value.c_str(); + return def; + } + + if(!devName) + { + TRACE("Key %s not found\n", key.c_str()); + return def; + } + return GetConfigValue(nullptr, blockName, keyName, def); +} + +int ConfigValueExists(const char *devName, const char *blockName, const char *keyName) +{ + const char *val = GetConfigValue(devName, blockName, keyName, ""); + return val[0] != 0; +} + +al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName) +{ + const char *val = GetConfigValue(devName, blockName, keyName, ""); + if(!val[0]) return al::nullopt; + + return al::make_optional<std::string>(val); +} + +al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName) +{ + const char *val = GetConfigValue(devName, blockName, keyName, ""); + if(!val[0]) return al::nullopt; + + return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0))); +} + +al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName) +{ + const char *val = GetConfigValue(devName, blockName, keyName, ""); + if(!val[0]) return al::nullopt; + + return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0))); +} + +al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName) +{ + const char *val = GetConfigValue(devName, blockName, keyName, ""); + if(!val[0]) return al::nullopt; + + return al::make_optional(std::strtof(val, nullptr)); +} + +al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName) +{ + const char *val = GetConfigValue(devName, blockName, keyName, ""); + if(!val[0]) return al::nullopt; + + return al::make_optional( + al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 || + al::strcasecmp(val, "on") == 0 || atoi(val) != 0); +} + +int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def) +{ + const char *val = GetConfigValue(devName, blockName, keyName, ""); + + if(!val[0]) return def != 0; + return (al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 || + al::strcasecmp(val, "on") == 0 || atoi(val) != 0); +} |