/** * 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 #include #include #ifdef _WIN32_IE #include #include #endif #ifdef __APPLE__ #include #endif #include #include #include #include "alMain.h" #include "alconfig.h" #include "logging.h" #include "compat.h" namespace { struct ConfigEntry { std::string key; std::string value; template ConfigEntry(T0&& key_, T1&& val_) : key{std::forward(key_)}, value{std::forward(val_)} { } }; al::vector 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(next-str) : std::strlen(str); str += addstrlen; } else { str++; if(*str == '$') { const char *next = std::strchr(str+1, '$'); addstr = str; addstrlen = next ? static_cast(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(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(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); }