|
|
- /**
- * 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 <cstdlib>
- #include <cctype>
- #include <cstring>
- #ifdef _WIN32_IE
- #include <windows.h>
- #include <shlobj.h>
- #endif
- #ifdef __APPLE__
- #include <CoreFoundation/CoreFoundation.h>
- #endif
-
- #include <vector>
- #include <string>
- #include <algorithm>
-
- #include "alMain.h"
- #include "alconfig.h"
- #include "logging.h"
- #include "compat.h"
-
-
- namespace {
-
- struct ConfigEntry {
- std::string key;
- std::string value;
-
- template<typename T0, typename T1>
- ConfigEntry(T0&& key_, T1&& val_)
- : key{std::forward<T0>(key_)}, value{std::forward<T1>(val_)}
- { }
- };
- 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;
-
- 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
- {
- bool hasbraces{(*str == '{')};
- if(hasbraces) str++;
-
- std::string envname;
- while((std::isalnum(*str) || *str == '_'))
- envname += *(str++);
-
- if(hasbraces && *str != '}')
- continue;
-
- if(hasbraces) str++;
- if((addstr=std::getenv(envname.c_str())) == nullptr)
- continue;
- addstrlen = std::strlen(addstr);
- }
- }
- 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))
- {
- while(!buffer.empty() && std::isspace(buffer.back()))
- buffer.pop_back();
- if(lstrip(buffer).empty())
- continue;
-
- buffer.push_back(0);
- char *line{&buffer[0]};
-
- if(line[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(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')))
- {
- unsigned char 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;
- }
-
- char *comment{std::strchr(line, '#')};
- if(comment) *(comment++) = 0;
- if(!line[0]) continue;
-
- 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(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();
-
- /* 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())
- ent->value = expdup(value);
- else
- {
- ConfOpts.emplace_back(std::move(fullKey), expdup(value));
- ent = ConfOpts.end()-1;
- }
-
- TRACE("found '%s' = '%s'\n", ent->key.c_str(), ent->value.c_str());
- }
- ConfOpts.shrink_to_fit();
- }
-
- } // namespace
-
-
- #ifdef _WIN32
- void ReadALConfig(void) noexcept
- {
- 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);
- }
-
- const WCHAR *str{_wgetenv(L"ALSOFT_CONF")};
- if(str != nullptr && *str)
- {
- std::string filepath{wstr_to_utf8(str)};
-
- TRACE("Loading config %s...\n", filepath.c_str());
- al::ifstream f{filepath};
- if(f.is_open())
- LoadConfigFromFile(f);
- }
- }
- #else
- void ReadALConfig(void) noexcept
- {
- 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();
-
- if(!(str=getenv("XDG_CONFIG_DIRS")) || str[0] == 0)
- str = "/etc/xdg";
- std::string confpaths = str;
- /* 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());
- al::ifstream f{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)))
- {
- al::ifstream f{reinterpret_cast<char*>(fileName)};
- if(f.is_open())
- LoadConfigFromFile(f);
- }
- }
- #endif
-
- if((str=getenv("HOME")) != nullptr && *str)
- {
- fname = str;
- if(fname.back() != '/') fname += "/.alsoftrc";
- else fname += ".alsoftrc";
-
- TRACE("Loading config %s...\n", fname.c_str());
- al::ifstream f{fname};
- if(f.is_open())
- LoadConfigFromFile(f);
- }
-
- if((str=getenv("XDG_CONFIG_HOME")) != nullptr && str[0] != 0)
- {
- fname = str;
- if(fname.back() != '/') fname += "/alsoft.conf";
- else fname += "alsoft.conf";
- }
- else
- {
- fname.clear();
- if((str=getenv("HOME")) != nullptr && str[0] != 0)
- {
- fname = str;
- if(fname.back() != '/') fname += "/.config/alsoft.conf";
- else fname += ".config/alsoft.conf";
- }
- }
- if(!fname.empty())
- {
- TRACE("Loading config %s...\n", fname.c_str());
- al::ifstream f{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());
- al::ifstream f{ppath};
- if(f.is_open())
- LoadConfigFromFile(f);
- }
-
- if((str=getenv("ALSOFT_CONF")) != nullptr && *str)
- {
- TRACE("Loading config %s...\n", str);
- al::ifstream f{str};
- 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 && 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;
- }
-
- int ConfigValueStr(const char *devName, const char *blockName, const char *keyName, const char **ret)
- {
- const char *val = GetConfigValue(devName, blockName, keyName, "");
- if(!val[0]) return 0;
-
- *ret = val;
- return 1;
- }
-
- int ConfigValueInt(const char *devName, const char *blockName, const char *keyName, int *ret)
- {
- const char *val = GetConfigValue(devName, blockName, keyName, "");
- if(!val[0]) return 0;
-
- *ret = std::strtol(val, nullptr, 0);
- return 1;
- }
-
- int ConfigValueUInt(const char *devName, const char *blockName, const char *keyName, unsigned int *ret)
- {
- const char *val = GetConfigValue(devName, blockName, keyName, "");
- if(!val[0]) return 0;
-
- *ret = std::strtoul(val, nullptr, 0);
- return 1;
- }
-
- int ConfigValueFloat(const char *devName, const char *blockName, const char *keyName, float *ret)
- {
- const char *val = GetConfigValue(devName, blockName, keyName, "");
- if(!val[0]) return 0;
-
- *ret = std::strtof(val, nullptr);
- return 1;
- }
-
- int ConfigValueBool(const char *devName, const char *blockName, const char *keyName, int *ret)
- {
- const char *val = GetConfigValue(devName, blockName, keyName, "");
- if(!val[0]) return 0;
-
- *ret = (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 ||
- strcasecmp(val, "on") == 0 || atoi(val) != 0);
- return 1;
- }
-
- 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 (strcasecmp(val, "true") == 0 || strcasecmp(val, "yes") == 0 ||
- strcasecmp(val, "on") == 0 || atoi(val) != 0);
- }
|