]> git.tdb.fi Git - ext/openal.git/blob - alc/alconfig.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / alc / alconfig.cpp
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.
8  *
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.
13  *
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
19  */
20
21 #include "config.h"
22
23 #include "alconfig.h"
24
25 #include <cstdlib>
26 #include <cctype>
27 #include <cstring>
28 #ifdef _WIN32
29 #include <windows.h>
30 #include <shlobj.h>
31 #endif
32 #ifdef __APPLE__
33 #include <CoreFoundation/CoreFoundation.h>
34 #endif
35
36 #include <algorithm>
37 #include <cstdio>
38 #include <string>
39 #include <utility>
40
41 #include "alfstream.h"
42 #include "alstring.h"
43 #include "core/helpers.h"
44 #include "core/logging.h"
45 #include "strutils.h"
46 #include "vector.h"
47
48
49 namespace {
50
51 struct ConfigEntry {
52     std::string key;
53     std::string value;
54 };
55 al::vector<ConfigEntry> ConfOpts;
56
57
58 std::string &lstrip(std::string &line)
59 {
60     size_t pos{0};
61     while(pos < line.length() && std::isspace(line[pos]))
62         ++pos;
63     line.erase(0, pos);
64     return line;
65 }
66
67 bool readline(std::istream &f, std::string &output)
68 {
69     while(f.good() && f.peek() == '\n')
70         f.ignore();
71
72     return std::getline(f, output) && !output.empty();
73 }
74
75 std::string expdup(const char *str)
76 {
77     std::string output;
78
79     std::string envval;
80     while(*str != '\0')
81     {
82         const char *addstr;
83         size_t addstrlen;
84
85         if(str[0] != '$')
86         {
87             const char *next = std::strchr(str, '$');
88             addstr = str;
89             addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
90
91             str += addstrlen;
92         }
93         else
94         {
95             str++;
96             if(*str == '$')
97             {
98                 const char *next = std::strchr(str+1, '$');
99                 addstr = str;
100                 addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
101
102                 str += addstrlen;
103             }
104             else
105             {
106                 const bool hasbraces{(*str == '{')};
107
108                 if(hasbraces) str++;
109                 const char *envstart = str;
110                 while(std::isalnum(*str) || *str == '_')
111                     ++str;
112                 if(hasbraces && *str != '}')
113                     continue;
114                 const std::string envname{envstart, str};
115                 if(hasbraces) str++;
116
117                 envval = al::getenv(envname.c_str()).value_or(std::string{});
118                 addstr = envval.data();
119                 addstrlen = envval.length();
120             }
121         }
122         if(addstrlen == 0)
123             continue;
124
125         output.append(addstr, addstrlen);
126     }
127
128     return output;
129 }
130
131 void LoadConfigFromFile(std::istream &f)
132 {
133     std::string curSection;
134     std::string buffer;
135
136     while(readline(f, buffer))
137     {
138         if(lstrip(buffer).empty())
139             continue;
140
141         if(buffer[0] == '[')
142         {
143             auto line = const_cast<char*>(buffer.data());
144             char *section = line+1;
145             char *endsection;
146
147             endsection = std::strchr(section, ']');
148             if(!endsection || section == endsection)
149             {
150                 ERR(" config parse error: bad line \"%s\"\n", line);
151                 continue;
152             }
153             if(endsection[1] != 0)
154             {
155                 char *end = endsection+1;
156                 while(std::isspace(*end))
157                     ++end;
158                 if(*end != 0 && *end != '#')
159                 {
160                     ERR(" config parse error: bad line \"%s\"\n", line);
161                     continue;
162                 }
163             }
164             *endsection = 0;
165
166             curSection.clear();
167             if(al::strcasecmp(section, "general") != 0)
168             {
169                 do {
170                     char *nextp = std::strchr(section, '%');
171                     if(!nextp)
172                     {
173                         curSection += section;
174                         break;
175                     }
176
177                     curSection.append(section, nextp);
178                     section = nextp;
179
180                     if(((section[1] >= '0' && section[1] <= '9') ||
181                         (section[1] >= 'a' && section[1] <= 'f') ||
182                         (section[1] >= 'A' && section[1] <= 'F')) &&
183                        ((section[2] >= '0' && section[2] <= '9') ||
184                         (section[2] >= 'a' && section[2] <= 'f') ||
185                         (section[2] >= 'A' && section[2] <= 'F')))
186                     {
187                         int b{0};
188                         if(section[1] >= '0' && section[1] <= '9')
189                             b = (section[1]-'0') << 4;
190                         else if(section[1] >= 'a' && section[1] <= 'f')
191                             b = (section[1]-'a'+0xa) << 4;
192                         else if(section[1] >= 'A' && section[1] <= 'F')
193                             b = (section[1]-'A'+0x0a) << 4;
194                         if(section[2] >= '0' && section[2] <= '9')
195                             b |= (section[2]-'0');
196                         else if(section[2] >= 'a' && section[2] <= 'f')
197                             b |= (section[2]-'a'+0xa);
198                         else if(section[2] >= 'A' && section[2] <= 'F')
199                             b |= (section[2]-'A'+0x0a);
200                         curSection += static_cast<char>(b);
201                         section += 3;
202                     }
203                     else if(section[1] == '%')
204                     {
205                         curSection += '%';
206                         section += 2;
207                     }
208                     else
209                     {
210                         curSection += '%';
211                         section += 1;
212                     }
213                 } while(*section != 0);
214             }
215
216             continue;
217         }
218
219         auto cmtpos = std::min(buffer.find('#'), buffer.size());
220         while(cmtpos > 0 && std::isspace(buffer[cmtpos-1]))
221             --cmtpos;
222         if(!cmtpos) continue;
223         buffer.erase(cmtpos);
224
225         auto sep = buffer.find('=');
226         if(sep == std::string::npos)
227         {
228             ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
229             continue;
230         }
231         auto keyend = sep++;
232         while(keyend > 0 && std::isspace(buffer[keyend-1]))
233             --keyend;
234         if(!keyend)
235         {
236             ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
237             continue;
238         }
239         while(sep < buffer.size() && std::isspace(buffer[sep]))
240             sep++;
241
242         std::string fullKey;
243         if(!curSection.empty())
244         {
245             fullKey += curSection;
246             fullKey += '/';
247         }
248         fullKey += buffer.substr(0u, keyend);
249
250         std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}};
251         if(value.size() > 1)
252         {
253             if((value.front() == '"' && value.back() == '"')
254                 || (value.front() == '\'' && value.back() == '\''))
255             {
256                 value.pop_back();
257                 value.erase(value.begin());
258             }
259         }
260
261         TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str());
262
263         /* Check if we already have this option set */
264         auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
265         { return entry.key == fullKey; };
266         auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key);
267         if(ent != ConfOpts.end())
268         {
269             if(!value.empty())
270                 ent->value = expdup(value.c_str());
271             else
272                 ConfOpts.erase(ent);
273         }
274         else if(!value.empty())
275             ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())});
276     }
277     ConfOpts.shrink_to_fit();
278 }
279
280 const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName)
281 {
282     if(!keyName)
283         return nullptr;
284
285     std::string key;
286     if(blockName && al::strcasecmp(blockName, "general") != 0)
287     {
288         key = blockName;
289         if(devName)
290         {
291             key += '/';
292             key += devName;
293         }
294         key += '/';
295         key += keyName;
296     }
297     else
298     {
299         if(devName)
300         {
301             key = devName;
302             key += '/';
303         }
304         key += keyName;
305     }
306
307     auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
308         [&key](const ConfigEntry &entry) -> bool
309         { return entry.key == key; });
310     if(iter != ConfOpts.cend())
311     {
312         TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
313         if(!iter->value.empty())
314             return iter->value.c_str();
315         return nullptr;
316     }
317
318     if(!devName)
319     {
320         TRACE("Key %s not found\n", key.c_str());
321         return nullptr;
322     }
323     return GetConfigValue(nullptr, blockName, keyName);
324 }
325
326 } // namespace
327
328
329 #ifdef _WIN32
330 void ReadALConfig()
331 {
332     WCHAR buffer[MAX_PATH];
333     if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
334     {
335         std::string filepath{wstr_to_utf8(buffer)};
336         filepath += "\\alsoft.ini";
337
338         TRACE("Loading config %s...\n", filepath.c_str());
339         al::ifstream f{filepath};
340         if(f.is_open())
341             LoadConfigFromFile(f);
342     }
343
344     std::string ppath{GetProcBinary().path};
345     if(!ppath.empty())
346     {
347         ppath += "\\alsoft.ini";
348         TRACE("Loading config %s...\n", ppath.c_str());
349         al::ifstream f{ppath};
350         if(f.is_open())
351             LoadConfigFromFile(f);
352     }
353
354     if(auto confpath = al::getenv(L"ALSOFT_CONF"))
355     {
356         TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str());
357         al::ifstream f{*confpath};
358         if(f.is_open())
359             LoadConfigFromFile(f);
360     }
361 }
362
363 #else
364
365 void ReadALConfig()
366 {
367     const char *str{"/etc/openal/alsoft.conf"};
368
369     TRACE("Loading config %s...\n", str);
370     al::ifstream f{str};
371     if(f.is_open())
372         LoadConfigFromFile(f);
373     f.close();
374
375     std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
376     /* Go through the list in reverse, since "the order of base directories
377      * denotes their importance; the first directory listed is the most
378      * important". Ergo, we need to load the settings from the later dirs
379      * first so that the settings in the earlier dirs override them.
380      */
381     std::string fname;
382     while(!confpaths.empty())
383     {
384         auto next = confpaths.find_last_of(':');
385         if(next < confpaths.length())
386         {
387             fname = confpaths.substr(next+1);
388             confpaths.erase(next);
389         }
390         else
391         {
392             fname = confpaths;
393             confpaths.clear();
394         }
395
396         if(fname.empty() || fname.front() != '/')
397             WARN("Ignoring XDG config dir: %s\n", fname.c_str());
398         else
399         {
400             if(fname.back() != '/') fname += "/alsoft.conf";
401             else fname += "alsoft.conf";
402
403             TRACE("Loading config %s...\n", fname.c_str());
404             f = al::ifstream{fname};
405             if(f.is_open())
406                 LoadConfigFromFile(f);
407         }
408         fname.clear();
409     }
410
411 #ifdef __APPLE__
412     CFBundleRef mainBundle = CFBundleGetMainBundle();
413     if(mainBundle)
414     {
415         unsigned char fileName[PATH_MAX];
416         CFURLRef configURL;
417
418         if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
419            CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
420         {
421             f = al::ifstream{reinterpret_cast<char*>(fileName)};
422             if(f.is_open())
423                 LoadConfigFromFile(f);
424         }
425     }
426 #endif
427
428     if(auto homedir = al::getenv("HOME"))
429     {
430         fname = *homedir;
431         if(fname.back() != '/') fname += "/.alsoftrc";
432         else fname += ".alsoftrc";
433
434         TRACE("Loading config %s...\n", fname.c_str());
435         f = al::ifstream{fname};
436         if(f.is_open())
437             LoadConfigFromFile(f);
438     }
439
440     if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
441     {
442         fname = *configdir;
443         if(fname.back() != '/') fname += "/alsoft.conf";
444         else fname += "alsoft.conf";
445     }
446     else
447     {
448         fname.clear();
449         if(auto homedir = al::getenv("HOME"))
450         {
451             fname = *homedir;
452             if(fname.back() != '/') fname += "/.config/alsoft.conf";
453             else fname += ".config/alsoft.conf";
454         }
455     }
456     if(!fname.empty())
457     {
458         TRACE("Loading config %s...\n", fname.c_str());
459         f = al::ifstream{fname};
460         if(f.is_open())
461             LoadConfigFromFile(f);
462     }
463
464     std::string ppath{GetProcBinary().path};
465     if(!ppath.empty())
466     {
467         if(ppath.back() != '/') ppath += "/alsoft.conf";
468         else ppath += "alsoft.conf";
469
470         TRACE("Loading config %s...\n", ppath.c_str());
471         f = al::ifstream{ppath};
472         if(f.is_open())
473             LoadConfigFromFile(f);
474     }
475
476     if(auto confname = al::getenv("ALSOFT_CONF"))
477     {
478         TRACE("Loading config %s...\n", confname->c_str());
479         f = al::ifstream{*confname};
480         if(f.is_open())
481             LoadConfigFromFile(f);
482     }
483 }
484 #endif
485
486 al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
487 {
488     if(const char *val{GetConfigValue(devName, blockName, keyName)})
489         return val;
490     return al::nullopt;
491 }
492
493 al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
494 {
495     if(const char *val{GetConfigValue(devName, blockName, keyName)})
496         return static_cast<int>(std::strtol(val, nullptr, 0));
497     return al::nullopt;
498 }
499
500 al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
501 {
502     if(const char *val{GetConfigValue(devName, blockName, keyName)})
503         return static_cast<unsigned int>(std::strtoul(val, nullptr, 0));
504     return al::nullopt;
505 }
506
507 al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
508 {
509     if(const char *val{GetConfigValue(devName, blockName, keyName)})
510         return std::strtof(val, nullptr);
511     return al::nullopt;
512 }
513
514 al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
515 {
516     if(const char *val{GetConfigValue(devName, blockName, keyName)})
517         return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
518             || al::strcasecmp(val, "true")==0 || atoi(val) != 0;
519     return al::nullopt;
520 }
521
522 bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def)
523 {
524     if(const char *val{GetConfigValue(devName, blockName, keyName)})
525         return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
526             || al::strcasecmp(val, "true") == 0 || atoi(val) != 0);
527     return def;
528 }