summaryrefslogtreecommitdiffstats
path: root/src/corelib/io/qsettings.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/io/qsettings.cpp')
-rw-r--r--src/corelib/io/qsettings.cpp209
1 files changed, 127 insertions, 82 deletions
diff --git a/src/corelib/io/qsettings.cpp b/src/corelib/io/qsettings.cpp
index cf5a0c66c3..6934ca4404 100644
--- a/src/corelib/io/qsettings.cpp
+++ b/src/corelib/io/qsettings.cpp
@@ -44,7 +44,7 @@
# include <shlobj.h>
#endif
-#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID)
+#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID)
#define Q_XDG_PLATFORM
#endif
@@ -66,6 +66,7 @@
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+using namespace QtMiscUtils;
struct QConfFileCustomFormat
{
@@ -128,12 +129,12 @@ bool QConfFile::isWritable() const
{
QFileInfo fileInfo(name);
-#ifndef QT_NO_TEMPORARYFILE
+#if QT_CONFIG(temporaryfile)
if (fileInfo.exists()) {
#endif
QFile file(name);
return file.open(QFile::ReadWrite);
-#ifndef QT_NO_TEMPORARYFILE
+#if QT_CONFIG(temporaryfile)
} else {
// Create the directories to the file.
QDir dir(fileInfo.absolutePath());
@@ -211,9 +212,7 @@ namespace {
}
QChar *write(QChar *out, QLatin1StringView v)
{
- for (char ch : v)
- *out++ = QLatin1Char(ch);
- return out;
+ return QLatin1::convertToUnicode(out, v);
}
QChar *write(QChar *out, QStringView v)
{
@@ -271,7 +270,7 @@ QString QSettingsPrivate::normalizedKey(QAnyStringView key)
// see also qsettings_win.cpp and qsettings_mac.cpp
-#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) && !defined(Q_OS_WASM)
+#if !defined(Q_OS_WIN) && !defined(Q_OS_DARWIN) && !defined(Q_OS_WASM)
QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope,
const QString &organization, const QString &application)
{
@@ -343,7 +342,7 @@ void QSettingsPrivate::requestUpdate()
QStringList QSettingsPrivate::variantListToStringList(const QVariantList &l)
{
QStringList result;
- result.reserve(l.count());
+ result.reserve(l.size());
for (auto v : l)
result.append(variantToString(v));
return result;
@@ -358,7 +357,7 @@ QVariant QSettingsPrivate::stringListToVariantList(const QStringList &l)
if (str.startsWith(u'@')) {
if (str.size() < 2 || str.at(1) != u'@') {
QVariantList variantList;
- variantList.reserve(l.count());
+ variantList.reserve(l.size());
for (const auto &s : l)
variantList.append(stringToVariant(s));
return variantList;
@@ -515,8 +514,7 @@ void QSettingsPrivate::iniEscapedKey(const QString &key, QByteArray &result)
if (ch == '/') {
result += '\\';
- } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')
- || ch == '_' || ch == '-' || ch == '.') {
+ } else if (isAsciiLetterOrNumber(ch) || ch == '_' || ch == '-' || ch == '.') {
result += (char)ch;
} else if (ch <= 0xFF) {
result += '%';
@@ -560,7 +558,7 @@ bool QSettingsPrivate::iniUnescapedKey(QByteArrayView key, QString &result)
}
int numDigits = 2;
- int firstDigitPos = i + 1;
+ qsizetype firstDigitPos = i + 1;
ch = decoded.at(i + 1).unicode();
if (ch == 'U') {
@@ -608,10 +606,7 @@ void QSettingsPrivate::iniEscapedString(const QString &str, QByteArray &result)
if (ch == ';' || ch == ',' || ch == '=')
needsQuotes = true;
- if (escapeNextIfDigit
- && ((ch >= '0' && ch <= '9')
- || (ch >= 'a' && ch <= 'f')
- || (ch >= 'A' && ch <= 'F'))) {
+ if (escapeNextIfDigit && isHexDigit(ch)) {
result += "\\x" + QByteArray::number(ch, 16);
continue;
}
@@ -754,10 +749,10 @@ StNormal:
goto end;
ch = str.at(i);
- if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'))
+ if (isHexDigit(ch))
goto StHexEscape;
- } else if (ch >= '0' && ch <= '7') {
- escapeVal = ch - '0';
+ } else if (const int o = fromOct(ch); o != -1) {
+ escapeVal = o;
goto StOctEscape;
} else if (ch == '\n' || ch == '\r') {
if (i < str.size()) {
@@ -819,11 +814,9 @@ StHexEscape:
}
ch = str.at(i);
- if (ch >= 'a')
- ch -= 'a' - 'A';
- if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) {
+ if (const int h = fromHex(ch); h != -1) {
escapeVal <<= 4;
- escapeVal += QtMiscUtils::fromHex(ch);
+ escapeVal += h;
++i;
goto StHexEscape;
} else {
@@ -838,9 +831,9 @@ StOctEscape:
}
ch = str.at(i);
- if (ch >= '0' && ch <= '7') {
+ if (const int o = fromOct(ch); o != -1) {
escapeVal <<= 3;
- escapeVal += ch - '0';
+ escapeVal += o;
++i;
goto StOctEscape;
} else {
@@ -885,16 +878,26 @@ QStringList QSettingsPrivate::splitArgs(const QString &s, qsizetype idx)
void QConfFileSettingsPrivate::initFormat()
{
+#if defined(Q_OS_WASM)
+ extension = (format == QSettings::NativeFormat || format == QSettings::WebIndexedDBFormat)
+ ? ".conf"_L1
+ : ".ini"_L1;
+#else
extension = (format == QSettings::NativeFormat) ? ".conf"_L1 : ".ini"_L1;
+#endif
readFunc = nullptr;
writeFunc = nullptr;
-#if defined(Q_OS_MAC)
+#if defined(Q_OS_DARWIN)
caseSensitivity = (format == QSettings::NativeFormat) ? Qt::CaseSensitive : IniCaseSensitivity;
#else
caseSensitivity = IniCaseSensitivity;
#endif
+#if defined Q_OS_WASM
+ if (format > QSettings::IniFormat && format != QSettings::WebIndexedDBFormat) {
+#else
if (format > QSettings::IniFormat) {
+#endif
const auto locker = qt_scoped_lock(settingsGlobalMutex);
const CustomFormatVector *customFormatVector = customFormatVectorFunc();
@@ -912,7 +915,11 @@ void QConfFileSettingsPrivate::initFormat()
void QConfFileSettingsPrivate::initAccess()
{
if (!confFiles.isEmpty()) {
+#if defined Q_OS_WASM
+ if (format > QSettings::IniFormat && format != QSettings::WebIndexedDBFormat) {
+#else
if (format > QSettings::IniFormat) {
+#endif
if (!readFunc)
setStatus(QSettings::AccessError);
}
@@ -950,26 +957,43 @@ static inline int pathHashKey(QSettings::Format format, QSettings::Scope scope)
}
#ifndef Q_OS_WIN
-static QString make_user_path()
+static constexpr QChar sep = u'/';
+
+#if !defined(QSETTINGS_USE_QSTANDARDPATHS) || defined(Q_OS_ANDROID)
+static QString make_user_path_without_qstandard_paths()
{
- static constexpr QChar sep = u'/';
-#ifndef QSETTINGS_USE_QSTANDARDPATHS
- // Non XDG platforms (OS X, iOS, Android...) have used this code path erroneously
- // for some time now. Moving away from that would require migrating existing settings.
QByteArray env = qgetenv("XDG_CONFIG_HOME");
if (env.isEmpty()) {
return QDir::homePath() + "/.config/"_L1;
} else if (env.startsWith('/')) {
return QFile::decodeName(env) + sep;
- } else {
- return QDir::homePath() + sep + QFile::decodeName(env) + sep;
}
+
+ return QDir::homePath() + sep + QFile::decodeName(env) + sep;
+}
+#endif // !QSETTINGS_USE_QSTANDARDPATHS || Q_OS_ANDROID
+
+static QString make_user_path()
+{
+#ifndef QSETTINGS_USE_QSTANDARDPATHS
+ // Non XDG platforms (OS X, iOS, Android...) have used this code path erroneously
+ // for some time now. Moving away from that would require migrating existing settings.
+ // The migration has already been done for Android.
+ return make_user_path_without_qstandard_paths();
#else
- // When using a proper XDG platform, use QStandardPaths rather than the above hand-written code;
- // it makes the use of test mode from unit tests possible.
+
+#ifdef Q_OS_ANDROID
+ // If an old settings path exists, use it instead of creating a new one
+ QString ret = make_user_path_without_qstandard_paths();
+ if (QFile(ret).exists())
+ return ret;
+#endif // Q_OS_ANDROID
+
+ // When using a proper XDG platform or Android platform, use QStandardPaths rather than the
+ // above hand-written code. It makes the use of test mode from unit tests possible.
// Ideally all platforms should use this, but see above for the migration issue.
return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + sep;
-#endif
+#endif // !QSETTINGS_USE_QSTANDARDPATHS
}
#endif // !Q_OS_WIN
@@ -1005,7 +1029,7 @@ static std::unique_lock<QBasicMutex> initDefaultPaths(std::unique_lock<QBasicMut
const QString userPath = make_user_path();
pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::UserScope), Path(userPath, false));
pathHash->insert(pathHashKey(QSettings::IniFormat, QSettings::SystemScope), Path(systemPath, false));
-#ifndef Q_OS_MAC
+#ifndef Q_OS_DARWIN
pathHash->insert(pathHashKey(QSettings::NativeFormat, QSettings::UserScope), Path(userPath, false));
pathHash->insert(pathHashKey(QSettings::NativeFormat, QSettings::SystemScope), Path(systemPath, false));
#endif
@@ -1081,16 +1105,16 @@ QConfFileSettingsPrivate::QConfFileSettingsPrivate(QSettings::Format format,
QStringList paths;
if (!application.isEmpty()) {
paths.reserve(dirs.size() * 2);
- for (const auto &dir : qAsConst(dirs))
+ for (const auto &dir : std::as_const(dirs))
paths.append(dir + u'/' + appFile);
} else {
paths.reserve(dirs.size());
}
- for (const auto &dir : qAsConst(dirs))
+ for (const auto &dir : std::as_const(dirs))
paths.append(dir + u'/' + orgFile);
// Note: No check for existence of files is done intentionally.
- for (const auto &path : qAsConst(paths))
+ for (const auto &path : std::as_const(paths))
confFiles.append(QConfFile::fromName(path, false));
} else
#endif // Q_XDG_PLATFORM && !QT_NO_STANDARDPATHS
@@ -1100,9 +1124,7 @@ QConfFileSettingsPrivate::QConfFileSettingsPrivate(QSettings::Format format,
confFiles.append(QConfFile::fromName(systemPath.path + orgFile, false));
}
-#ifndef Q_OS_WASM // wasm needs to delay access until after file sync
initAccess();
-#endif
}
QConfFileSettingsPrivate::QConfFileSettingsPrivate(const QString &fileName,
@@ -1123,7 +1145,7 @@ QConfFileSettingsPrivate::~QConfFileSettingsPrivate()
ConfFileHash *usedHash = usedHashFunc();
ConfFileCache *unusedCache = unusedCacheFunc();
- for (auto conf_file : qAsConst(confFiles)) {
+ for (auto conf_file : std::as_const(confFiles)) {
if (!conf_file->ref.deref()) {
if (conf_file->size == 0) {
delete conf_file;
@@ -1197,7 +1219,7 @@ std::optional<QVariant> QConfFileSettingsPrivate::get(const QString &key) const
ParsedSettingsMap::const_iterator j;
bool found = false;
- for (auto confFile : qAsConst(confFiles)) {
+ for (auto confFile : std::as_const(confFiles)) {
const auto locker = qt_scoped_lock(confFile->mutex);
if (!confFile->addedKeys.isEmpty()) {
@@ -1226,7 +1248,7 @@ QStringList QConfFileSettingsPrivate::children(const QString &prefix, ChildSpec
QSettingsKey thePrefix(prefix, caseSensitivity);
qsizetype startPos = prefix.size();
- for (auto confFile : qAsConst(confFiles)) {
+ for (auto confFile : std::as_const(confFiles)) {
const auto locker = qt_scoped_lock(confFile->mutex);
if (thePrefix.isEmpty())
@@ -1234,17 +1256,17 @@ QStringList QConfFileSettingsPrivate::children(const QString &prefix, ChildSpec
else
ensureSectionParsed(confFile, thePrefix);
- auto j = const_cast<const ParsedSettingsMap *>(
- &confFile->originalKeys)->lowerBound( thePrefix);
- while (j != confFile->originalKeys.constEnd() && j.key().startsWith(thePrefix)) {
- if (!confFile->removedKeys.contains(j.key()))
- processChild(QStringView{j.key().originalCaseKey()}.sliced(startPos), spec, result);
- ++j;
+ const auto &originalKeys = confFile->originalKeys;
+ auto i = originalKeys.lowerBound(thePrefix);
+ while (i != originalKeys.end() && i.key().startsWith(thePrefix)) {
+ if (!confFile->removedKeys.contains(i.key()))
+ processChild(QStringView{i.key().originalCaseKey()}.sliced(startPos), spec, result);
+ ++i;
}
- j = const_cast<const ParsedSettingsMap *>(
- &confFile->addedKeys)->lowerBound(thePrefix);
- while (j != confFile->addedKeys.constEnd() && j.key().startsWith(thePrefix)) {
+ const auto &addedKeys = confFile->addedKeys;
+ auto j = addedKeys.lowerBound(thePrefix);
+ while (j != addedKeys.end() && j.key().startsWith(thePrefix)) {
processChild(QStringView{j.key().originalCaseKey()}.sliced(startPos), spec, result);
++j;
}
@@ -1277,7 +1299,7 @@ void QConfFileSettingsPrivate::sync()
// people probably won't be checking the status a whole lot, so in case of
// error we just try to go on and make the best of it
- for (auto confFile : qAsConst(confFiles)) {
+ for (auto confFile : std::as_const(confFiles)) {
const auto locker = qt_scoped_lock(confFile->mutex);
syncConfFile(confFile);
}
@@ -1299,7 +1321,11 @@ QString QConfFileSettingsPrivate::fileName() const
bool QConfFileSettingsPrivate::isWritable() const
{
+#if defined(Q_OS_WASM)
+ if (format > QSettings::IniFormat && format != QSettings::WebIndexedDBFormat && !writeFunc)
+#else
if (format > QSettings::IniFormat && !writeFunc)
+#endif
return false;
if (confFiles.isEmpty())
@@ -1312,13 +1338,13 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
{
bool readOnly = confFile->addedKeys.isEmpty() && confFile->removedKeys.isEmpty();
+ QFileInfo fileInfo(confFile->name);
/*
We can often optimize the read-only case, if the file on disk
hasn't changed.
*/
if (readOnly && confFile->size > 0) {
- QFileInfo fileInfo(confFile->name);
- if (confFile->size == fileInfo.size() && confFile->timeStamp == fileInfo.lastModified())
+ if (confFile->size == fileInfo.size() && confFile->timeStamp == fileInfo.lastModified(QTimeZone::UTC))
return;
}
@@ -1334,8 +1360,7 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
// On android and if it is a content URL put the lock file in a
// writable location to prevent permissions issues and invalid paths.
if (confFile->name.startsWith("content:"_L1))
- lockFileName = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
- + QFileInfo(lockFileName).fileName();
+ lockFileName = make_user_path() + QFileInfo(lockFileName).fileName();
# endif
/*
Use a lockfile in order to protect us against other QSettings instances
@@ -1355,13 +1380,13 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
We hold the lock. Let's reread the file if it has changed
since last time we read it.
*/
- QFileInfo fileInfo(confFile->name);
+ fileInfo.refresh();
bool mustReadFile = true;
bool createFile = !fileInfo.exists();
if (!readOnly)
mustReadFile = (confFile->size != fileInfo.size()
- || (confFile->size != 0 && confFile->timeStamp != fileInfo.lastModified()));
+ || (confFile->size != 0 && confFile->timeStamp != fileInfo.lastModified(QTimeZone::UTC)));
if (mustReadFile) {
confFile->unparsedIniSections.clear();
@@ -1379,7 +1404,14 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
*/
if (file.isReadable() && file.size() != 0) {
bool ok = false;
-#ifdef Q_OS_MAC
+
+#ifdef Q_OS_WASM
+ if (format == QSettings::WebIndexedDBFormat) {
+ QByteArray data = file.readAll();
+ ok = readIniFile(data, &confFile->unparsedIniSections);
+ } else
+#endif
+#ifdef Q_OS_DARWIN
if (format == QSettings::NativeFormat) {
QByteArray data = file.readAll();
ok = readPlistFile(data, &confFile->originalKeys);
@@ -1407,7 +1439,7 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
}
confFile->size = fileInfo.size();
- confFile->timeStamp = fileInfo.lastModified();
+ confFile->timeStamp = fileInfo.lastModified(QTimeZone::UTC);
}
/*
@@ -1435,7 +1467,12 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
return;
}
-#ifdef Q_OS_MAC
+#ifdef Q_OS_WASM
+ if (format == QSettings::WebIndexedDBFormat) {
+ ok = writeIniFile(sf, mergedKeys);
+ } else
+#endif
+#ifdef Q_OS_DARWIN
if (format == QSettings::NativeFormat) {
ok = writePlistFile(sf, mergedKeys);
} else
@@ -1464,9 +1501,9 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
confFile->addedKeys.clear();
confFile->removedKeys.clear();
- QFileInfo fileInfo(confFile->name);
+ fileInfo.refresh();
confFile->size = fileInfo.size();
- confFile->timeStamp = fileInfo.lastModified();
+ confFile->timeStamp = fileInfo.lastModified(QTimeZone::UTC);
// If we have created the file, apply the file perms
if (createFile) {
@@ -1481,6 +1518,8 @@ void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
}
}
+namespace SettingsImpl {
+
enum { Space = 0x1, Special = 0x2 };
static const char charTraits[256] =
@@ -1507,11 +1546,16 @@ static const char charTraits[256] =
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
+} // namespace SettingsImpl
+
+using SettingsImpl::charTraits;
+
bool QConfFileSettingsPrivate::readIniLine(QByteArrayView data, qsizetype &dataPos,
qsizetype &lineStart, qsizetype &lineLen,
qsizetype &equalsPos)
{
- qsizetype dataLen = data.length();
+ using namespace SettingsImpl;
+ qsizetype dataLen = data.size();
bool inQuotes = false;
equalsPos = -1;
@@ -1638,7 +1682,7 @@ bool QConfFileSettingsPrivate::readIniFile(QByteArrayView data,
++position;
}
- Q_ASSERT(lineStart == data.length());
+ Q_ASSERT(lineStart == data.size());
FLUSH_CURRENT_SECTION();
return ok;
@@ -2084,10 +2128,6 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
as QString. The numeric value can be recovered using \l QString::toInt(), \l
QString::toDouble() and related functions.
- The \l{tools/settingseditor}{Settings Editor} example lets you
- experiment with different settings location and with fallbacks
- turned on or off.
-
\section1 Restoring the State of a GUI Application
QSettings is often used to store the state of a GUI
@@ -2113,9 +2153,6 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
\codeline
\snippet settings/settings.cpp 21
- See the \l{mainwindows/application}{Application} example for a
- self-contained example that uses QSettings.
-
\section1 Accessing Settings from Multiple Threads or Processes Simultaneously
QSettings is \l{reentrant}. This means that you can use
@@ -2157,8 +2194,8 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
following files are used by default:
\list 1
- \li \c{$HOME/.config/MySoft/Star Runner.conf} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft/Star Runner.conf})
- \li \c{$HOME/.config/MySoft.conf} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft.conf})
+ \li \c{$HOME/.config/MySoft/Star Runner.conf}
+ \li \c{$HOME/.config/MySoft.conf}
\li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft/Star Runner.conf}
\li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft.conf}
\endlist
@@ -2195,8 +2232,8 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
used on Unix, \macos, and iOS:
\list 1
- \li \c{$HOME/.config/MySoft/Star Runner.ini} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft/Star Runner.ini})
- \li \c{$HOME/.config/MySoft.ini} (Qt for Embedded Linux: \c{$HOME/Settings/MySoft.ini})
+ \li \c{$HOME/.config/MySoft/Star Runner.ini}
+ \li \c{$HOME/.config/MySoft.ini}
\li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft/Star Runner.ini}
\li for each directory <dir> in $XDG_CONFIG_DIRS: \c{<dir>/MySoft.ini}
\endlist
@@ -2331,7 +2368,7 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
\endlist
- \sa QVariant, QSessionManager, {Settings Editor Example}, {Qt Widgets - Application Example}
+ \sa QVariant, QSessionManager
*/
/*! \enum QSettings::Status
@@ -2369,6 +2406,16 @@ void QConfFileSettingsPrivate::ensureSectionParsed(QConfFile *confFile,
lose the distinction between numeric data and the
strings used to encode them, so values written as
numbers shall be read back as QString.
+ \value WebLocalStorageFormat
+ WASM only: Store the settings in window.localStorage for the current
+ origin. If cookies are not allowed, this falls back to the INI format.
+ This provides up to 5MiB storage per origin, but access to it is
+ synchronous and JSPI is not required.
+ \value WebIndexedDBFormat
+ WASM only: Store the settings in an Indexed DB for the current
+ origin. If cookies are not allowed, this falls back to the INI format.
+ This requires JSPI, but provides more storage than
+ WebLocalStorageFormat.
\value InvalidFormat Special value returned by registerFormat().
\omitvalue CustomFormat1
@@ -3367,8 +3414,6 @@ QSettings::Format QSettings::defaultFormat()
\row \li SystemScope \li \c FOLDERID_ProgramData
\row \li{1,2} Unix \li{1,2} NativeFormat, IniFormat \li UserScope \li \c $HOME/.config
\row \li SystemScope \li \c /etc/xdg
- \row \li{1,2} Qt for Embedded Linux \li{1,2} NativeFormat, IniFormat \li UserScope \li \c $HOME/Settings
- \row \li SystemScope \li \c /etc/xdg
\row \li{1,2} \macos and iOS \li{1,2} IniFormat \li UserScope \li \c $HOME/.config
\row \li SystemScope \li \c /etc/xdg
\endtable