mumble-voip_mumble/src/mumble/Settings.cpp

1324 lines
46 KiB
C++

// Copyright 2007-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "Settings.h"
#include "AudioInput.h"
#include "Cert.h"
#include "EnumStringConversions.h"
#include "EnvUtils.h"
#include "JSONSerialization.h"
#include "Log.h"
#include "SSL.h"
#include "SettingsKeys.h"
#include "SettingsMacros.h"
#include "Global.h"
#if defined(Q_OS_WIN)
# include "GlobalShortcut_win.h"
#endif
#include "../../overlay/overlay.h"
#include <QByteArray>
#include <QDataStream>
#include <QFileInfo>
#include <QImageReader>
#include <QMessageBox>
#include <QProcessEnvironment>
#include <QRegularExpression>
#include <QSettings>
#include <QStandardPaths>
#include <QSystemTrayIcon>
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
# include <QOperatingSystemVersion>
#endif
#include <boost/typeof/typeof.hpp>
#include <algorithm>
#include <cassert>
#include <cstring>
#include <fstream>
#include <limits>
#include <memory>
#include <nlohmann/json.hpp>
constexpr const char *BACKUP_FILE_EXTENSION = ".back";
#ifdef Q_OS_WINDOWS
constexpr const char *REGISTRY_ID = ":::::REGISTRY:::::";
#endif
const QPoint Settings::UNSPECIFIED_POSITION =
QPoint(std::numeric_limits< int >::min(), std::numeric_limits< int >::max());
bool Shortcut::isServerSpecific() const {
if (qvData.canConvert< ShortcutTarget >()) {
const ShortcutTarget &sc = qvariant_cast< ShortcutTarget >(qvData);
return sc.isServerSpecific();
}
if (qvData.canConvert< ChannelTarget >()) {
return true;
}
return false;
}
bool Shortcut::operator<(const Shortcut &other) const {
return (iIndex < other.iIndex);
}
bool Shortcut::operator==(const Shortcut &other) const {
return (iIndex == other.iIndex) && (qlButtons == other.qlButtons) && (qvData == other.qvData)
&& (bSuppress == other.bSuppress);
}
bool ShortcutTarget::isServerSpecific() const {
return !bCurrentSelection && !bUsers && iChannel >= 0;
}
bool ShortcutTarget::operator==(const ShortcutTarget &o) const {
if ((bUsers != o.bUsers) || (bForceCenter != o.bForceCenter) || (bCurrentSelection != o.bCurrentSelection))
return false;
if (bUsers)
return (qlUsers == o.qlUsers) && (qlSessions == o.qlSessions);
else
return (iChannel == o.iChannel) && (bLinks == o.bLinks) && (bChildren == o.bChildren) && (qsGroup == o.qsGroup);
}
quint32 qHash(const ShortcutTarget &t) {
quint32 h = t.bForceCenter ? 0x55555555 : 0xaaaaaaaa;
if (t.bCurrentSelection) {
h ^= 0x20000000;
}
if (t.bUsers) {
foreach (unsigned int u, t.qlSessions)
h ^= u;
} else {
h ^= static_cast< unsigned int >(t.iChannel);
if (t.bLinks)
h ^= 0x80000000;
if (t.bChildren)
h ^= 0x40000000;
h ^= qHash(t.qsGroup);
h = ~h;
}
return h;
}
quint32 qHash(const QList< ShortcutTarget > &l) {
quint32 h = static_cast< quint32 >(l.count());
foreach (const ShortcutTarget &st, l)
h ^= qHash(st);
return h;
}
bool operator==(const PluginSetting &lhs, const PluginSetting &rhs) {
return lhs.enabled == rhs.enabled && lhs.positionalDataEnabled == rhs.positionalDataEnabled
&& lhs.allowKeyboardMonitoring == rhs.allowKeyboardMonitoring && lhs.path == rhs.path;
}
bool operator!=(const PluginSetting &lhs, const PluginSetting &rhs) {
return !(lhs == rhs);
}
bool operator==(const OverlaySettings &lhs, const OverlaySettings &rhs) {
#define PROCESS(category, key, variable) \
if (lhs.variable != rhs.variable) { \
return false; \
}
PROCESS_ALL_OVERLAY_SETTINGS
#undef PROCESS
return true;
}
bool operator!=(const OverlaySettings &lhs, const OverlaySettings &rhs) {
return !(lhs == rhs);
}
void Settings::save(const QString &path) const {
// Our saving procedure is a 4-step process:
// 1. Write the settings that are to be saved to a temporary file
// 2. Check if a settings backup exists and if it does, delete it
// 3. If an older settings file already exists, rename it to now be the backup
// 4. Move the temporary file to the correct location to make sure it now serves as the proper settings file
// All of this is an attempt at making the system as resistant to interruptions as possible.
if (!path.endsWith(".json")) {
throw std::runtime_error("Expected settings file to have \".json\" extension");
}
nlohmann::json settingsJSON = *this;
QFile tmpFile(QString::fromLatin1("%1/mumble_settings.json.tmp")
.arg(QStandardPaths::writableLocation(QStandardPaths::TempLocation)));
{
// The separate scope makes sure, the stream is closed again after the write has finished
std::ofstream stream(tmpFile.fileName().toUtf8());
stream << settingsJSON.dump(4) << std::endl;
}
QFile targetFile(path);
QFile backupFile(path + BACKUP_FILE_EXTENSION);
if (targetFile.exists()) {
if (!createdSettingsBackup) {
// Create a backup of the settings
if (backupFile.exists()) {
// We first have to delete our backup
if (!backupFile.remove()) {
qWarning("Failed at deleting settings backup file: %s", qUtf8Printable(backupFile.errorString()));
}
}
// Turn the current settings file into the backup file
if (!targetFile.rename(backupFile.fileName())) {
qWarning("Failed at renaming settings file to backup file: %s",
qUtf8Printable(targetFile.errorString()));
}
createdSettingsBackup = true;
} else {
// The current instance of Mumble has already created a settings backup while it was running. Thus
// performing another backup now would actually delete the backup we created before. Thus we only delete the
// current settings file in order to overwrite it below.
if (!targetFile.remove()) {
qWarning("Failed at deleting settings file: %s", qUtf8Printable(targetFile.errorString()));
}
}
} else {
QFileInfo info(targetFile);
if (!info.dir().exists()) {
if (!info.dir().mkpath(".")) {
qWarning("Failed to create directory for settings at %s", qUtf8Printable(info.dir().absolutePath()));
}
}
}
// Move the temp-file into place
if (!tmpFile.rename(path)) {
qWarning("Failed at moving settings from %s to %s - reason: %s", qUtf8Printable(tmpFile.fileName()),
qUtf8Printable(path), qUtf8Printable(tmpFile.errorString()));
}
}
void Settings::save() const {
if (settingsLocation.isEmpty()) {
save(findSettingsLocation());
} else {
save(settingsLocation);
}
}
void Settings::load(const QString &path) {
if (path.endsWith(QLatin1String(BACKUP_FILE_EXTENSION))) {
// Trim away the backup extension
settingsLocation = path.left(path.size() - static_cast< int >(std::strlen(BACKUP_FILE_EXTENSION)));
} else {
settingsLocation = path;
}
std::ifstream stream(path.toUtf8());
nlohmann::json settingsJSON;
try {
stream >> settingsJSON;
settingsJSON.get_to(*this);
if (!mumbleQuitNormally) {
// These settings were saved without Mumble quitting normally afterwards. In order to prevent loading
// settings that are causing crashes, we check if we can load the backup instead.
if (!path.endsWith(QLatin1String(BACKUP_FILE_EXTENSION))) {
QString backupPath = path + BACKUP_FILE_EXTENSION;
QFileInfo backupInfo(backupPath);
if (backupInfo.exists() && backupInfo.isFile()) {
QMessageBox msgBox;
msgBox.setWindowTitle(QObject::tr("Potentially broken settings"));
msgBox.setText(QObject::tr("Load backup settings?"));
msgBox.setInformativeText(QObject::tr(
"It seems that Mumble did not perform a normal shutdown. If you did not intentionally kill the "
"application, this could mean that the used settings caused a crash. "
"Do you want to load the setting's backup instead?"));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
msgBox.setIcon(QMessageBox::Question);
if (msgBox.exec() == QMessageBox::Yes) {
// Load the backup instead
qWarning() << "Loading backup settings from" << backupPath;
load(backupPath);
}
}
} else {
// This is already the backup we are loading
QMessageBox msgBox;
msgBox.setWindowTitle(QObject::tr("Potentially broken settings"));
msgBox.setText(QObject::tr("The backed-up settings also seem to have been saved without Mumble exiting "
"normally (potentially indicating a crash)."));
msgBox.setInformativeText(
QObject::tr(
"If you experience repeated crashes with these settings, you might have to manually delete the "
"settings files at <pre>%1</pre> and <pre>%2</pre> in order to reset all settings to their "
"default value.")
.arg(path.left(path.size() - static_cast< int >(std::strlen(BACKUP_FILE_EXTENSION))))
.arg(path));
msgBox.setIcon(QMessageBox::Warning);
msgBox.exec();
}
}
} catch (const nlohmann::json::parse_error &e) {
qWarning() << "Failed to load settings from" << path << "due to invalid format: " << e.what();
if (!path.endsWith(QLatin1String(BACKUP_FILE_EXTENSION)) && QFileInfo(path + BACKUP_FILE_EXTENSION).exists()) {
qWarning() << "Falling back to backup" << path + BACKUP_FILE_EXTENSION;
load(path + BACKUP_FILE_EXTENSION);
}
}
// Always reset this flag to false
mumbleQuitNormally = false;
}
void Settings::load() {
bool foundExisting = false;
QString settingsPath = findSettingsLocation(false, &foundExisting);
if (foundExisting) {
// If we found a regular settings file, then use that and be done with it
qInfo() << "Loading settings from" << settingsPath;
load(settingsPath);
} else if (QFileInfo(settingsPath + BACKUP_FILE_EXTENSION).exists()) {
// Load backup settings instead
qInfo() << "Loading backup settings from" << settingsPath + BACKUP_FILE_EXTENSION;
load(settingsPath + BACKUP_FILE_EXTENSION);
} else {
// Otherwise check for a legacy settings file and if that is found, load settings from there
QString legacySettingsPath = findSettingsLocation(true, &foundExisting);
if (foundExisting) {
qInfo() << "Loading settings from legacy settings file" << legacySettingsPath;
legacyLoad(legacySettingsPath);
}
#ifdef Q_OS_WIN
else {
// On Windows, we previously used the registry, so if we did not find an old config file (which would
// probably be very archaic), we can check the registry
qInfo() << "Loading legacy settings from the registry";
legacyLoad(QLatin1String(REGISTRY_ID));
}
#endif
}
}
QDataStream &operator<<(QDataStream &qds, const ShortcutTarget &st) {
// Start by the version of this setting. This is needed to make sure we can stay compatible
// with older versions (aka don't break existing shortcuts when updating the implementation)
qds << QString::fromLatin1("v2");
qds << st.bCurrentSelection << st.bUsers << st.bForceCenter;
if (st.bCurrentSelection) {
return qds << st.bLinks << st.bChildren;
} else if (st.bUsers) {
return qds << st.qlUsers;
} else {
return qds << st.iChannel << st.qsGroup << st.bLinks << st.bChildren;
}
}
QDataStream &operator>>(QDataStream &qds, ShortcutTarget &st) {
// Check for presence of a leading version string
QString versionString;
QIODevice *device = qds.device();
if (device) {
// Qt's way of serializing the stream requires us to read a few characters into
// the stream in order to get across some leading zeros and other meta stuff.
char buf[16];
// Init buf
for (unsigned int i = 0; i < sizeof(buf); i++) {
buf[i] = 0;
}
qint64 read = device->peek(buf, sizeof(buf));
for (unsigned int i = 0; i < read; i++) {
if (buf[i] >= 31) {
if (buf[i] == 'v') {
qds >> versionString;
} else {
break;
}
}
}
} else {
qCritical("Settings: Unable to determine version of setting for ShortcutTarget");
}
if (versionString == QLatin1String("v2")) {
qds >> st.bCurrentSelection;
}
qds >> st.bUsers >> st.bForceCenter;
if (st.bCurrentSelection) {
return qds >> st.bLinks >> st.bChildren;
} else if (st.bUsers) {
return qds >> st.qlUsers;
} else {
return qds >> st.iChannel >> st.qsGroup >> st.bLinks >> st.bChildren;
}
}
QDataStream &operator<<(QDataStream &qds, const ChannelTarget &target) {
return qds << target.channelID;
}
QDataStream &operator>>(QDataStream &qds, ChannelTarget &target) {
return qds >> target.channelID;
}
bool operator==(const ChannelTarget &lhs, const ChannelTarget &rhs) {
return lhs.channelID == rhs.channelID;
}
bool operator<(const ChannelTarget &lhs, const ChannelTarget &rhs) {
return lhs.channelID < rhs.channelID;
}
quint32 qHash(const ChannelTarget &target) {
return qHash(target.channelID);
}
const QString Settings::cqsDefaultPushClickOn = QLatin1String(":/on.ogg");
const QString Settings::cqsDefaultPushClickOff = QLatin1String(":/off.ogg");
const QString Settings::cqsDefaultMuteCue = QLatin1String(":/off.ogg");
OverlaySettings::OverlaySettings() {
#ifdef Q_OS_MACOS
qsStyle = QLatin1String("Cleanlooks");
#endif
qcUserName[Settings::Passive] = QColor(170, 170, 170);
qcUserName[Settings::MutedTalking] = QColor(170, 170, 170);
qcUserName[Settings::Talking] = QColor(255, 255, 255);
qcUserName[Settings::Whispering] = QColor(128, 255, 128);
qcUserName[Settings::Shouting] = QColor(255, 128, 255);
setPreset();
qfFps = qfUserName;
}
void OverlaySettings::setPreset(const OverlayPresets preset) {
switch (preset) {
case LargeSquareAvatar:
uiColumns = 2;
fUserName = 0.75f;
fChannel = 0.75f;
fMutedDeafened = 0.5f;
fAvatar = 1.0f;
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
qfUserName = QFont(QLatin1String("Verdana"), 20);
#else
qfUserName = QFont(QLatin1String("Arial"), 20);
#endif
qfChannel = qfUserName;
fUser[Settings::Passive] = 0.5f;
fUser[Settings::MutedTalking] = 0.5f;
fUser[Settings::Talking] = (7.0f / 8.0f);
fUser[Settings::Whispering] = (7.0f / 8.0f);
fUser[Settings::Shouting] = (7.0f / 8.0f);
qrfUserName = QRectF(-0.0625f, 0.101563f - 0.0625f, 0.125f, 0.023438f);
qrfChannel = QRectF(-0.03125f, -0.0625f, 0.09375f, 0.015625f);
qrfMutedDeafened = QRectF(-0.0625f, -0.0625f, 0.0625f, 0.0625f);
qrfAvatar = QRectF(-0.0625f, -0.0625f, 0.125f, 0.125f);
fBoxPenWidth = (1.f / 256.0f);
fBoxPad = (1.f / 256.0f);
bUserName = true;
bChannel = true;
bMutedDeafened = true;
bAvatar = true;
bBox = false;
qaUserName = Qt::AlignCenter;
qaMutedDeafened = Qt::AlignLeft | Qt::AlignTop;
qaAvatar = Qt::AlignCenter;
qaChannel = Qt::AlignCenter;
break;
case AvatarAndName:
default:
uiColumns = 1;
fUserName = 1.0f;
fChannel = (7.0f / 8.0f);
fMutedDeafened = (7.0f / 8.0f);
fAvatar = 1.0f;
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
qfUserName = QFont(QLatin1String("Verdana"), 20);
#else
qfUserName = QFont(QLatin1String("Arial"), 20);
#endif
qfChannel = qfUserName;
fUser[Settings::Passive] = 0.5f;
fUser[Settings::MutedTalking] = 0.5f;
fUser[Settings::Talking] = (7.0f / 8.0f);
fUser[Settings::Whispering] = (7.0f / 8.0f);
fUser[Settings::Shouting] = (7.0f / 8.0f);
qrfUserName = QRectF(0.015625f, -0.015625f, 0.250f, 0.03125f);
qrfChannel = QRectF(0.03125f, -0.015625f, 0.1875f, 0.015625f);
qrfMutedDeafened = QRectF(0.234375f, -0.015625f, 0.03125f, 0.03125f);
qrfAvatar = QRectF(-0.03125f, -0.015625f, 0.03125f, 0.03125f);
fBoxPenWidth = 0.0f;
fBoxPad = (1.f / 256.0f);
bUserName = true;
bChannel = false;
bMutedDeafened = true;
bAvatar = true;
bBox = true;
qaUserName = Qt::AlignLeft | Qt::AlignVCenter;
qaMutedDeafened = Qt::AlignRight | Qt::AlignVCenter;
qaAvatar = Qt::AlignCenter;
qaChannel = Qt::AlignLeft | Qt::AlignTop;
break;
}
}
Settings::Settings() {
#ifndef NDEBUG
verifySettingsKeys();
#endif
#if defined(Q_OS_WIN)
GlobalShortcutWin::registerMetaTypes();
#endif
qRegisterMetaType< ShortcutTarget >("ShortcutTarget");
qRegisterMetaTypeStreamOperators< ShortcutTarget >("ShortcutTarget");
qRegisterMetaType< ChannelTarget >("ChannelTarget");
qRegisterMetaTypeStreamOperators< ChannelTarget >("ChannelTarget");
qRegisterMetaType< QVariant >("QVariant");
qRegisterMetaType< PluginSetting >("PluginSetting");
qRegisterMetaTypeStreamOperators< PluginSetting >("PluginSetting");
qRegisterMetaType< Search::SearchDialog::UserAction >("SearchDialog::UserAction");
qRegisterMetaType< Search::SearchDialog::ChannelAction >("SearchDialog::ChannelAction");
#ifdef Q_OS_MACOS
// The echo cancellation feature on macOS is experimental and known to be able to cause problems
// (e.g. muting the user instead of only cancelling echo - https://github.com/mumble-voip/mumble/issues/4912)
// Therefore we disable it by default until the issues are fixed.
echoOption = EchoCancelOptionID::DISABLED;
#endif
#ifdef Q_OS_WIN
// Don't enable minimize to tray by default on Windows >= 7
# if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
// Since Qt 5.9 QOperatingSystemVersion is preferred over QSysInfo::WinVersion
bHideInTray = QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows7;
# else
const QSysInfo::WinVersion winVer = QSysInfo::windowsVersion();
bHideInTray = (winVer < QSysInfo::WV_WINDOWS7);
# endif
#else
const bool isUnityDesktop =
QProcessEnvironment::systemEnvironment().value(QLatin1String("XDG_CURRENT_DESKTOP")) == QLatin1String("Unity");
bHideInTray = !isUnityDesktop && QSystemTrayIcon::isSystemTrayAvailable();
#endif
#ifdef NO_UPDATE_CHECK
bUpdateCheck = false;
bPluginCheck = false;
#endif
#if defined(AUDIO_TEST)
lmLoopMode = Server;
#endif
#ifdef Q_OS_LINUX
if (EnvUtils::waylandIsUsed()) {
// Due to the issues we're currently having on Wayland, we disable shortcuts by default
bShortcutEnable = false;
}
#endif
for (int i = Log::firstMsgType; i <= Log::lastMsgType; ++i) {
qmMessages.insert(i,
Settings::LogConsole | Settings::LogBalloon | Settings::LogTTS | Settings::LogMessageLimit);
qmMessageSounds.insert(i, QString());
}
qmMessageSounds[Log::CriticalError] = QLatin1String(":/Critical.ogg");
qmMessageSounds[Log::PermissionDenied] = QLatin1String(":/PermissionDenied.ogg");
qmMessageSounds[Log::SelfMute] = QLatin1String(":/SelfMutedDeafened.ogg");
qmMessageSounds[Log::SelfUnmute] = QLatin1String(":/SelfMutedDeafened.ogg");
qmMessageSounds[Log::SelfDeaf] = QLatin1String(":/SelfMutedDeafened.ogg");
qmMessageSounds[Log::SelfUndeaf] = QLatin1String(":/SelfMutedDeafened.ogg");
qmMessageSounds[Log::ServerConnected] = QLatin1String(":/ServerConnected.ogg");
qmMessageSounds[Log::ServerDisconnected] = QLatin1String(":/ServerDisconnected.ogg");
qmMessageSounds[Log::TextMessage] = QLatin1String(":/TextMessage.ogg");
qmMessageSounds[Log::PrivateTextMessage] = qmMessageSounds[Log::TextMessage];
qmMessageSounds[Log::ChannelJoin] = QLatin1String(":/UserJoinedChannel.ogg");
qmMessageSounds[Log::ChannelLeave] = QLatin1String(":/UserLeftChannel.ogg");
qmMessageSounds[Log::ChannelJoinConnect] = qmMessageSounds[Log::ChannelJoin];
qmMessageSounds[Log::ChannelLeaveDisconnect] = qmMessageSounds[Log::ChannelLeave];
qmMessageSounds[Log::YouMutedOther] = QLatin1String(":/UserMutedYouOrByYou.ogg");
qmMessageSounds[Log::YouMuted] = QLatin1String(":/UserMutedYouOrByYou.ogg");
qmMessageSounds[Log::YouKicked] = QLatin1String(":/UserKickedYouOrByYou.ogg");
qmMessageSounds[Log::Recording] = QLatin1String(":/RecordingStateChanged.ogg");
qmMessages[Log::DebugInfo] = Settings::LogConsole;
qmMessages[Log::Warning] = Settings::LogConsole | Settings::LogBalloon;
qmMessages[Log::Information] = Settings::LogConsole;
qmMessages[Log::UserJoin] = Settings::LogConsole;
qmMessages[Log::UserLeave] = Settings::LogConsole;
qmMessages[Log::UserKicked] = Settings::LogConsole;
qmMessages[Log::OtherSelfMute] = Settings::LogConsole;
qmMessages[Log::OtherMutedOther] = Settings::LogConsole;
qmMessages[Log::UserRenamed] = Settings::LogConsole;
qmMessages[Log::PluginMessage] = Settings::LogConsole;
}
bool Settings::doEcho() const {
if (AudioInputRegistrar::qmNew) {
AudioInputRegistrar *air = AudioInputRegistrar::qmNew->value(qsAudioInput);
if (air) {
if ((Global::get().s.echoOption != EchoCancelOptionID::DISABLED)
&& air->canEcho(Global::get().s.echoOption, qsAudioOutput))
return true;
}
}
return false;
}
bool Settings::doPositionalAudio() const {
return bPositionalAudio;
}
void OverlaySettings::savePresets(const QString &filename) {
nlohmann::json settingsJSON = *this;
settingsJSON.erase(SettingsKeys::OVERLAY_ENABLE_KEY);
settingsJSON.erase(SettingsKeys::OVERLAY_WHITELIST_KEY);
settingsJSON.erase(SettingsKeys::OVERLAY_WHITELIST_EXCLUDE_KEY);
settingsJSON.erase(SettingsKeys::OVERLAY_BLACKLIST_KEY);
settingsJSON.erase(SettingsKeys::OVERLAY_BLACKLIST_EXCLUDE_KEY);
settingsJSON.erase(SettingsKeys::OVERLAY_LAUNCHERS_KEY);
settingsJSON.erase(SettingsKeys::OVERLAY_LAUNCHERS_EXCLUDE_KEY);
std::ofstream stream(filename.toUtf8());
stream << settingsJSON.dump(4) << std::endl;
}
void OverlaySettings::load(const QString &filename) {
std::ifstream stream(filename.toUtf8());
nlohmann::json settingsJSON;
try {
stream >> settingsJSON;
settingsJSON.get_to(*this);
} catch (const nlohmann::json::parse_error &e) {
qWarning() << "Failed to load overlay settings due to invalid format: " << e.what();
}
}
#include BOOST_TYPEOF_INCREMENT_REGISTRATION_GROUP()
BOOST_TYPEOF_REGISTER_TYPE(Qt::Alignment)
BOOST_TYPEOF_REGISTER_TYPE(Settings::AudioTransmit)
BOOST_TYPEOF_REGISTER_TYPE(Settings::VADSource)
BOOST_TYPEOF_REGISTER_TYPE(Settings::LoopMode)
BOOST_TYPEOF_REGISTER_TYPE(Settings::OverlayShow)
BOOST_TYPEOF_REGISTER_TYPE(Settings::ProxyType)
BOOST_TYPEOF_REGISTER_TYPE(Settings::ChannelExpand)
BOOST_TYPEOF_REGISTER_TYPE(Settings::ChannelDrag)
BOOST_TYPEOF_REGISTER_TYPE(Settings::ServerShow)
BOOST_TYPEOF_REGISTER_TYPE(Settings::NoiseCancel)
BOOST_TYPEOF_REGISTER_TYPE(Settings::WindowLayout)
BOOST_TYPEOF_REGISTER_TYPE(Settings::AlwaysOnTopBehaviour)
BOOST_TYPEOF_REGISTER_TYPE(Settings::RecordingMode)
BOOST_TYPEOF_REGISTER_TYPE(QString)
BOOST_TYPEOF_REGISTER_TYPE(QByteArray)
BOOST_TYPEOF_REGISTER_TYPE(QColor)
BOOST_TYPEOF_REGISTER_TYPE(QVariant)
BOOST_TYPEOF_REGISTER_TYPE(QFont)
BOOST_TYPEOF_REGISTER_TYPE(EchoCancelOptionID)
BOOST_TYPEOF_REGISTER_TEMPLATE(QList, 1)
#define LOAD(var, name) var = qvariant_cast< BOOST_TYPEOF(var) >(settings_ptr->value(QLatin1String(name), var))
#define LOADENUM(var, name) \
var = static_cast< BOOST_TYPEOF(var) >(settings_ptr->value(QLatin1String(name), var).toInt())
#define LOADFLAG(var, name) \
var = static_cast< BOOST_TYPEOF(var) >(settings_ptr->value(QLatin1String(name), static_cast< int >(var)).toInt())
#define DEPRECATED(name) \
do { \
} while (0)
// Workaround for mumble-voip/mumble#2638.
//
// Qt previously expected to be able to write
// NUL bytes in strings in plists. This is no
// longer possible, which causes Qt to write
// incomplete strings to the preferences plist.
// These are of the form "@Variant(", and, for
// Mumble, typically happen for float values.
//
// We detect this bad value and avoid loading
// it. This causes such settings to fall back
// to their defaults, instead of being set to
// a zero value.
#ifdef Q_OS_MACOS
# undef LOAD
# define LOAD(var, name) \
do { \
if (settings_ptr->value(QLatin1String(name)).toString() != QLatin1String("@Variant(")) { \
var = qvariant_cast< BOOST_TYPEOF(var) >(settings_ptr->value(QLatin1String(name), var)); \
} \
} while (0)
#endif
void OverlaySettings::legacyLoad(QSettings *settings_ptr) {
LOAD(bEnable, "enable");
LOADENUM(osShow, "show");
LOAD(bAlwaysSelf, "alwaysself");
LOAD(uiActiveTime, "activetime");
LOADENUM(osSort, "sort");
LOAD(fX, "x");
LOAD(fY, "y");
LOAD(fZoom, "zoom");
LOAD(uiColumns, "columns");
settings_ptr->beginReadArray(QLatin1String("states"));
for (unsigned int i = 0; i < 4; ++i) {
settings_ptr->setArrayIndex(static_cast< int >(i));
LOAD(qcUserName[i], "color");
LOAD(fUser[i], "opacity");
}
settings_ptr->endArray();
LOAD(qfUserName, "userfont");
LOAD(qfChannel, "channelfont");
LOAD(qcChannel, "channelcolor");
LOAD(qfFps, "fpsfont");
LOAD(qcFps, "fpscolor");
LOAD(fBoxPad, "padding");
LOAD(fBoxPenWidth, "penwidth");
LOAD(qcBoxPen, "pencolor");
LOAD(qcBoxFill, "fillcolor");
LOAD(bUserName, "usershow");
LOAD(bChannel, "channelshow");
LOAD(bMutedDeafened, "mutedshow");
LOAD(bAvatar, "avatarshow");
LOAD(bBox, "boxshow");
LOAD(bFps, "fpsshow");
LOAD(bTime, "timeshow");
LOAD(fUserName, "useropacity");
LOAD(fChannel, "channelopacity");
LOAD(fMutedDeafened, "mutedopacity");
LOAD(fAvatar, "avataropacity");
LOAD(fFps, "fpsopacity");
LOAD(qrfUserName, "userrect");
LOAD(qrfChannel, "channelrect");
LOAD(qrfMutedDeafened, "mutedrect");
LOAD(qrfAvatar, "avatarrect");
LOAD(qrfFps, "fpsrect");
LOAD(qrfTime, "timerect");
LOADFLAG(qaUserName, "useralign");
LOADFLAG(qaChannel, "channelalign");
LOADFLAG(qaMutedDeafened, "mutedalign");
LOADFLAG(qaAvatar, "avataralign");
LOADENUM(oemOverlayExcludeMode, "mode");
LOAD(qslLaunchers, "launchers");
LOAD(qslLaunchersExclude, "launchersexclude");
LOAD(qslWhitelist, "whitelist");
LOAD(qslWhitelistExclude, "whitelistexclude");
LOAD(qslPaths, "paths");
LOAD(qslPathsExclude, "pathsexclude");
LOAD(qslBlacklist, "blacklist");
LOAD(qslBlacklistExclude, "blacklistexclude");
}
void Settings::legacyLoad(const QString &path) {
std::unique_ptr< QSettings > settings_ptr;
QSettings::Format format = QSettings::IniFormat;
#ifdef Q_OS_WINDOWS
if (path == QLatin1String(REGISTRY_ID)) {
// Search the registry
settings_ptr = std::make_unique< QSettings >();
} else
#endif
settings_ptr = std::make_unique< QSettings >(path.isEmpty() ? findSettingsLocation(true) : path, format);
LOAD(qsDatabaseLocation, "databaselocation");
LOAD(bMute, "audio/mute");
LOAD(bDeaf, "audio/deaf");
LOADENUM(atTransmit, "audio/transmit");
LOAD(uiDoublePush, "audio/doublepush");
LOAD(pttHold, "audio/ptthold");
LOAD(audioCueEnabledPTT, "audio/pushclick");
LOAD(qsTxAudioCueOn, "audio/pushclickon");
LOAD(qsTxAudioCueOff, "audio/pushclickoff");
LOAD(bTxMuteCue, "audio/mutecue");
LOAD(qsTxMuteCue, "audio/mutecuepath");
LOAD(iQuality, "audio/quality");
LOAD(iMinLoudness, "audio/loudness");
LOAD(fVolume, "audio/volume");
LOAD(fOtherVolume, "audio/othervolume");
LOAD(bAttenuateOthers, "audio/attenuateothers");
LOAD(bAttenuateOthersOnTalk, "audio/attenuateothersontalk");
LOAD(bAttenuateUsersOnPrioritySpeak, "audio/attenuateusersonpriorityspeak");
LOAD(bOnlyAttenuateSameOutput, "audio/onlyattenuatesameoutput");
LOAD(bAttenuateLoopbacks, "audio/attenuateloopbacks");
LOADENUM(vsVAD, "audio/vadsource");
LOAD(fVADmin, "audio/vadmin");
LOAD(fVADmax, "audio/vadmax");
int oldNoiseSuppress = 0;
LOAD(oldNoiseSuppress, "audio/noisesupress");
LOAD(iSpeexNoiseCancelStrength, "audio/speexNoiseCancelStrength");
// Select the most negative of the 2 (one is expected to be zero as it is
// unset). This is for compatibility as we have renamed the setting at some point.
iSpeexNoiseCancelStrength = std::min(oldNoiseSuppress, iSpeexNoiseCancelStrength);
LOADENUM(noiseCancelMode, "audio/noiseCancelMode");
#ifndef USE_RENAMENOISE
if (noiseCancelMode == NoiseCancelRNN || noiseCancelMode == NoiseCancelBoth) {
// Use Speex instead as this Mumble build was built without support for ReNameNoise
noiseCancelMode = NoiseCancelSpeex;
}
#endif
LOAD(bAllowLowDelay, "audio/allowlowdelay");
LOAD(uiAudioInputChannelMask, "audio/inputchannelmask");
LOAD(iVoiceHold, "audio/voicehold");
LOAD(iOutputDelay, "audio/outputdelay");
// Idle auto actions
LOAD(iIdleTime, "audio/idletime");
LOADENUM(iaeIdleAction, "audio/idleaction");
LOAD(bUndoIdleActionUponActivity, "audio/undoidleactionuponactivity");
LOAD(fAudioMinDistance, "audio/mindistance");
LOAD(fAudioMaxDistance, "audio/maxdistance");
LOAD(fAudioMaxDistVolume, "audio/maxdistancevolume");
LOAD(fAudioBloom, "audio/bloom");
DEPRECATED("audio/echo");
DEPRECATED("audio/echomulti");
LOAD(bExclusiveInput, "audio/exclusiveinput");
LOAD(bExclusiveOutput, "audio/exclusiveoutput");
LOAD(bPositionalAudio, "audio/positional");
LOAD(bPositionalHeadphone, "audio/headphone");
LOAD(qsAudioInput, "audio/input");
LOAD(qsAudioOutput, "audio/output");
LOAD(bWhisperFriends, "audio/whisperfriends");
LOAD(iMessageLimitUserThreshold, "audio/messagelimitusers");
LOAD(bTransmitPosition, "audio/postransmit");
if (settings_ptr->contains("audio/echooptionid")) {
// Load the new echo cancel option instead
LOADFLAG(echoOption, "audio/echooptionid");
} else {
#ifndef Q_OS_MACOS
// Compatibility layer for overtaking the old (now deprecated) settings
// This block should only be called once at the first start of the new Mumble version
// As echo cancellation was not available on macOS before, we don't have to run this compatibility
// code on macOS (instead simply use the new default as set in the constructor).
if (settings_ptr->contains("audio/echo")) {
bool deprecatedEcho = false;
bool deprecatedEchoMulti = false;
LOAD(deprecatedEcho, "audio/echo");
LOAD(deprecatedEchoMulti, "audio/echomulti");
if (deprecatedEcho) {
if (deprecatedEchoMulti) {
echoOption = EchoCancelOptionID::SPEEX_MULTICHANNEL;
} else {
echoOption = EchoCancelOptionID::SPEEX_MIXED;
}
} else {
echoOption = EchoCancelOptionID::DISABLED;
}
}
#endif
}
LOAD(iJitterBufferSize, "net/jitterbuffer");
LOAD(iFramesPerPacket, "net/framesperpacket");
LOAD(bASIOEnable, "asio/enable");
LOAD(qsASIOclass, "asio/class");
LOAD(qlASIOmic, "asio/mic");
LOAD(qlASIOspeaker, "asio/speaker");
LOAD(qsWASAPIInput, "wasapi/input");
LOAD(qsWASAPIOutput, "wasapi/output");
LOAD(qsWASAPIRole, "wasapi/role");
LOAD(qsALSAInput, "alsa/input");
LOAD(qsALSAOutput, "alsa/output");
LOAD(pipeWireInput, "pipewire/input");
LOAD(pipeWireOutput, "pipewire/output");
LOAD(qsPulseAudioInput, "pulseaudio/input");
LOAD(qsPulseAudioOutput, "pulseaudio/output");
LOAD(qsJackAudioOutput, "jack/output");
LOAD(bJackStartServer, "jack/startserver");
LOAD(bJackAutoConnect, "jack/autoconnect");
LOAD(qsOSSInput, "oss/input");
LOAD(qsOSSOutput, "oss/output");
LOAD(qsCoreAudioInput, "coreaudio/input");
LOAD(qsCoreAudioOutput, "coreaudio/output");
LOAD(iPortAudioInput, "portaudio/input");
LOAD(iPortAudioOutput, "portaudio/output");
LOAD(bTTS, "tts/enable");
LOAD(iTTSVolume, "tts/volume");
LOAD(iTTSThreshold, "tts/threshold");
LOAD(bTTSMessageReadBack, "tts/readback");
LOAD(bTTSNoScope, "tts/noscope");
LOAD(bTTSNoAuthor, "tts/noauthor");
LOAD(qsTTSLanguage, "tts/language");
// Network settings
LOAD(bTCPCompat, "net/tcponly");
LOAD(bQoS, "net/qos");
LOAD(bReconnect, "net/reconnect");
LOAD(bAutoConnect, "net/autoconnect");
LOAD(bSuppressIdentity, "net/suppress");
LOADENUM(ptProxyType, "net/proxytype");
LOAD(qsProxyHost, "net/proxyhost");
LOAD(usProxyPort, "net/proxyport");
LOAD(qsProxyUsername, "net/proxyusername");
LOAD(qsProxyPassword, "net/proxypassword");
DEPRECATED("net/maximagesize");
LOAD(iMaxImageWidth, "net/maximagewidth");
LOAD(iMaxImageHeight, "net/maximageheight");
LOAD(qsServicePrefix, "net/serviceprefix");
LOAD(iMaxInFlightTCPPings, "net/maxinflighttcppings");
LOAD(iPingIntervalMsec, "net/pingintervalmsec");
LOAD(iConnectionTimeoutDurationMsec, "net/connectiontimeoutdurationmsec");
LOAD(bUdpForceTcpAddr, "net/udpforcetcpaddr");
// Network settings - SSL
LOAD(qsSslCiphers, "net/sslciphers");
// Privacy settings
LOAD(bHideOS, "privacy/hideos");
LOAD(qsLanguage, "ui/language");
LOAD(themeName, "ui/theme");
LOAD(themeStyleName, "ui/themestyle");
LOADENUM(ceExpand, "ui/expand");
LOADENUM(ceChannelDrag, "ui/drag");
LOADENUM(ceUserDrag, "ui/userdrag");
LOADENUM(aotbAlwaysOnTop, "ui/alwaysontop");
LOAD(bEnableDeveloperMenu, "ui/developermenu");
LOAD(bLockLayout, "ui/locklayout");
LOAD(bMinimalView, "ui/minimalview");
LOAD(bHideFrame, "ui/hideframe");
LOAD(bUserTop, "ui/usertop");
LOAD(qbaMainWindowGeometry, "ui/geometry");
LOAD(qbaMainWindowState, "ui/state");
LOAD(qbaMinimalViewGeometry, "ui/minimalviewgeometry");
LOAD(qbaMinimalViewState, "ui/minimalviewstate");
LOAD(qbaConfigGeometry, "ui/ConfigGeometry");
LOADENUM(wlWindowLayout, "ui/WindowLayout");
LOAD(qbaHeaderState, "ui/header");
LOAD(qsUsername, "ui/username");
LOAD(qsLastServer, "ui/server");
LOADENUM(ssFilter, "ui/serverfilter");
LOAD(bUpdateCheck, "ui/updatecheck");
LOAD(bPluginCheck, "ui/plugincheck");
LOAD(bPluginAutoUpdate, "ui/pluginAutoUpdate");
LOAD(bHideInTray, "ui/hidetray");
LOAD(bStateInTray, "ui/stateintray");
LOAD(bUsage, "ui/usage");
LOAD(bShowUserCount, "ui/showusercount");
LOAD(bShowVolumeAdjustments, "ui/showVolumeAdjustments");
LOAD(bShowNicknamesOnly, "ui/showNicknamesOnly");
LOAD(bChatBarUseSelection, "ui/chatbaruseselection");
LOAD(bFilterHidesEmptyChannels, "ui/filterhidesemptychannels");
LOAD(bFilterActive, "ui/filteractive");
LOAD(qsImagePath, "ui/imagepath");
LOAD(bShowContextMenuInMenuBar, "ui/showcontextmenuinmenubar");
LOAD(qbaConnectDialogGeometry, "ui/connect/geometry");
LOAD(qbaConnectDialogHeader, "ui/connect/header");
LOAD(bShowTransmitModeComboBox, "ui/transmitmodecombobox");
LOAD(bHighContrast, "ui/HighContrast");
LOAD(iMaxLogBlocks, "ui/MaxLogBlocks");
LOAD(bLog24HourClock, "ui/24HourClock");
LOAD(iChatMessageMargins, "ui/ChatMessageMargins");
LOAD(bDisablePublicList, "ui/disablepubliclist");
if (settings_ptr->contains("ui/askonquit")) {
// Compatibility layer for overtaking the old (now deprecated) settings
// This block should only be called once at the first start of the new Mumble version
bool deprecatedAskOnQuit = true;
LOAD(deprecatedAskOnQuit, "ui/askonquit");
quitBehavior = deprecatedAskOnQuit ? QuitBehavior::ALWAYS_ASK : QuitBehavior::ALWAYS_QUIT;
}
// TalkingUI
LOAD(qpTalkingUI_Position, "ui/talkingUIPosition");
LOAD(bShowTalkingUI, "ui/showTalkingUI");
LOAD(bTalkingUI_LocalUserStaysVisible, "ui/talkingUI_LocalUserStaysVisible");
LOAD(bTalkingUI_AbbreviateChannelNames, "ui/talkingUI_AbbreviateChannelNames");
LOAD(bTalkingUI_AbbreviateCurrentChannel, "ui/talkingUI_AbbreviateCurrentChannel");
LOAD(bTalkingUI_ShowLocalListeners, "ui/talkingUI_ShowLocalListeners");
LOAD(iTalkingUI_RelativeFontSize, "ui/talkingUI_RelativeFontSize");
LOAD(iTalkingUI_SilentUserLifeTime, "ui/talkingUI_SilentUserLifeTime");
LOAD(iTalkingUI_ChannelHierarchyDepth, "ui/talkingUI_ChannelHierarchieDepth");
LOAD(iTalkingUI_MaxChannelNameLength, "ui/talkingUI_MaxChannelNameLength");
LOAD(iTalkingUI_PrefixCharCount, "ui/talkingUI_PrefixCharCount");
LOAD(iTalkingUI_PostfixCharCount, "ui/talkingUI_PostfixCharCount");
LOAD(qsTalkingUI_AbbreviationReplacement, "ui/talkingUI_AbbreviationReplacement");
// Load the old setting first in case it is set and then load the actual setting
LOAD(qsHierarchyChannelSeparator, "ui/talkingUI_ChannelSeparator");
LOAD(qsHierarchyChannelSeparator, "ui/hierarchy_channelSeparator");
LOAD(manualPlugin_silentUserDisplaytime, "ui/manualPlugin_silentUserDisplaytime");
// PTT Button window
LOAD(bShowPTTButtonWindow, "ui/showpttbuttonwindow");
LOAD(qbaPTTButtonWindowGeometry, "ui/pttbuttonwindowgeometry");
// Recording
LOAD(qsRecordingPath, "recording/path");
LOAD(qsRecordingFile, "recording/file");
LOADENUM(rmRecordingMode, "recording/mode");
LOAD(iRecordingFormat, "recording/format");
// Special configuration options not exposed to UI
LOAD(disableConnectDialogEditing, "ui/disableconnectdialogediting");
LOAD(bPingServersDialogViewed, "consent/pingserversdialogviewed");
// OverlayPrivateWin
LOAD(iOverlayWinHelperRestartCooldownMsec, "overlay_win/helper/restart_cooldown_msec");
LOAD(bOverlayWinHelperX86Enable, "overlay_win/helper/x86/enable");
LOAD(bOverlayWinHelperX64Enable, "overlay_win/helper/x64/enable");
// LCD
LOAD(iLCDUserViewMinColWidth, "lcd/userview/mincolwidth");
LOAD(iLCDUserViewSplitterWidth, "lcd/userview/splitterwidth");
QByteArray qba = qvariant_cast< QByteArray >(settings_ptr->value(QLatin1String("net/certificate")));
if (!qba.isEmpty())
kpCertificate = CertWizard::importCert(qba);
LOAD(bShortcutEnable, "shortcut/enable");
LOAD(bSuppressMacEventTapWarning, "shortcut/mac/suppresswarning");
LOAD(bEnableEvdev, "shortcut/linux/evdev/enable");
LOAD(bEnableXInput2, "shortcut/x11/xinput2/enable");
LOAD(bEnableGKey, "shortcut/gkey");
LOAD(bEnableXboxInput, "shortcut/windows/xbox/enable");
LOAD(bEnableUIAccess, "shortcut/windows/uiaccess/enable");
// Search options
LOAD(searchForUsers, "search/search_for_users");
LOAD(searchForChannels, "search/search_for_channels");
LOAD(searchCaseSensitive, "search/search_case_sensitive");
LOAD(searchAsRegex, "search/search_as_regex");
LOAD(searchOptionsShown, "search/search_options_shown");
LOADFLAG(searchUserAction, "search/search_user_action");
LOADFLAG(searchChannelAction, "search/search_channel_action");
LOAD(searchDialogPosition, "search/search_dialog_position");
int nshorts = settings_ptr->beginReadArray(QLatin1String("shortcuts"));
for (int i = 0; i < nshorts; i++) {
settings_ptr->setArrayIndex(i);
Shortcut s;
s.iIndex = -2;
LOAD(s.iIndex, "index");
LOAD(s.qlButtons, "keys");
LOAD(s.bSuppress, "suppress");
s.qvData = settings_ptr->value(QLatin1String("data"));
if (s.iIndex >= -1)
qlShortcuts << s;
}
settings_ptr->endArray();
settings_ptr->beginReadArray(QLatin1String("messages"));
for (QMap< int, quint32 >::const_iterator it = qmMessages.constBegin(); it != qmMessages.constEnd(); ++it) {
settings_ptr->setArrayIndex(it.key());
LOAD(qmMessages[it.key()], "log");
}
settings_ptr->endArray();
settings_ptr->beginReadArray(QLatin1String("messagesounds"));
for (QMap< int, QString >::const_iterator it = qmMessageSounds.constBegin(); it != qmMessageSounds.constEnd();
++it) {
settings_ptr->setArrayIndex(it.key());
LOAD(qmMessageSounds[it.key()], "logsound");
}
settings_ptr->endArray();
settings_ptr->beginGroup(QLatin1String("lcd/devices"));
foreach (const QString &d, settings_ptr->childKeys()) {
qmLCDDevices.insert(d, settings_ptr->value(d, true).toBool());
}
settings_ptr->endGroup();
// Plugins
settings_ptr->beginGroup(QLatin1String("plugins"));
foreach (const QString &pluginKey, settings_ptr->childGroups()) {
QString pluginHash;
if (pluginKey.contains(QLatin1String("_"))) {
// The key contains the filename as well as the hash
int index = pluginKey.lastIndexOf(QLatin1String("_"));
pluginHash = pluginKey.right(pluginKey.size() - index - 1);
} else {
pluginHash = pluginKey;
}
PluginSetting pluginSettings;
pluginSettings.path = settings_ptr->value(pluginKey + QLatin1String("/path")).toString();
pluginSettings.allowKeyboardMonitoring =
settings_ptr->value(pluginKey + QLatin1String("/allowKeyboardMonitoring")).toBool();
pluginSettings.enabled = settings_ptr->value(pluginKey + QLatin1String("/enabled")).toBool();
pluginSettings.positionalDataEnabled =
settings_ptr->value(pluginKey + QLatin1String("/positionalDataEnabled")).toBool();
qhPluginSettings.insert(pluginHash, pluginSettings);
}
settings_ptr->endGroup();
settings_ptr->beginGroup(QLatin1String("overlay"));
os.legacyLoad(settings_ptr.get());
settings_ptr->endGroup();
// This field previously populated Settings::uiUpdateCounter, which no longer exists. We require it though, in order
// to determine whether we have to perform some migration work.
unsigned int configVersion = 0;
LOAD(configVersion, "lastupdate");
audioWizardShown = true;
switch (configVersion) {
case 0:
// Previous version was pre 1.2.3 or Mumble is run for the first time
audioWizardShown = false;
// Fallthrough
case 1:
// Previous versions used old idle action style, convert it
if (iIdleTime == 5 * 60) { // New default
iaeIdleAction = Settings::Nothing;
} else {
iIdleTime =
static_cast< unsigned int >(60 * qRound(Global::get().s.iIdleTime / 60.)); // Round to minutes
iaeIdleAction = Settings::Deafen; // Old behavior
}
// Fallthrough
#ifdef Q_OS_WIN
case 2: {
QList< Shortcut > &shortcuts = qlShortcuts;
const QList< Shortcut > migratedShortcuts = GlobalShortcutWin::migrateSettings(shortcuts);
if (shortcuts.size() > migratedShortcuts.size()) {
const uint32_t num = shortcuts.size() - migratedShortcuts.size();
QMessageBox::warning(
nullptr, QObject::tr("Shortcuts migration incomplete"),
QObject::tr("Unfortunately %1 shortcut(s) could not be migrated.\nYou can register them again.")
.arg(num));
}
shortcuts = migratedShortcuts;
}
#endif
}
}
void Settings::migratePluginSettings(const MigratedPath &path) {
std::vector< PluginSetting > migratedSettings;
auto it = qhPluginSettings.begin();
while (it != qhPluginSettings.end()) {
if (it.value().path.startsWith(path.oldPath)) {
// Migrate the settings for this plugin
it.value().path.replace(path.oldPath, path.newPath);
// Move setting out of hash-map
migratedSettings.push_back(std::move(it.value()));
// Remove this entry
it = qhPluginSettings.erase(it);
} else {
it++;
}
}
// Back-insert all entries that have been migrated
for (const PluginSetting &current : migratedSettings) {
qhPluginSettings.insert(
QLatin1String(QCryptographicHash::hash(current.path.toUtf8(), QCryptographicHash::Sha1).toHex()),
std::move(current));
}
}
QDataStream &operator>>(QDataStream &arch, PluginSetting &setting) {
arch >> setting.enabled;
arch >> setting.positionalDataEnabled;
arch >> setting.allowKeyboardMonitoring;
return arch;
}
QDataStream &operator<<(QDataStream &arch, const PluginSetting &setting) {
arch << setting.enabled;
arch << setting.positionalDataEnabled;
arch << setting.allowKeyboardMonitoring;
return arch;
}
#undef LOAD
#undef LOADENUM
#undef LOADFLAG
#undef DEPRECATED
#undef SAVE
#undef SAVEFLAG
void Settings::verifySettingsKeys() const {
std::vector< const char * > categoryNames;
const char *currentCategoryName;
#define PROCESS(category, key, variable) currentCategoryName = #category;
#define INTERMEDIATE_OPERATION categoryNames.push_back(currentCategoryName);
PROCESS_ALL_SETTINGS_WITH_INTERMEDIATE_OPERATION
PROCESS_ALL_OVERLAY_SETTINGS_WITH_INTERMEDIATE_OPERATION
// Assert that all entries in categoryNames are unique
std::sort(categoryNames.begin(), categoryNames.end());
assert(std::unique(categoryNames.begin(), categoryNames.end(),
[](const char *lhs, const char *rhs) { return std::strcmp(lhs, rhs) == 0; })
== categoryNames.end());
#undef PROCESS
#undef INTERMEDIATE_OPERATION
std::vector< std::string > keyNames;
#define PROCESS(category, key, variable) keyNames.push_back(#key);
#define INTERMEDIATE_OPERATION \
std::sort(keyNames.begin(), keyNames.end()); \
assert(std::unique(keyNames.begin(), keyNames.end()) == keyNames.end()); \
keyNames.clear();
PROCESS_ALL_SETTINGS_WITH_INTERMEDIATE_OPERATION
PROCESS_ALL_OVERLAY_SETTINGS_WITH_INTERMEDIATE_OPERATION
#undef PROCESS
#undef INTERMEDIATE_OPERATION
std::vector< std::string > variableNames;
#define PROCESS(category, key, variable) variableNames.push_back(#variable);
PROCESS_ALL_SETTINGS
std::sort(variableNames.begin(), variableNames.end());
assert(std::unique(variableNames.begin(), variableNames.end()) == variableNames.end());
variableNames.clear();
PROCESS_ALL_OVERLAY_SETTINGS
std::sort(variableNames.begin(), variableNames.end());
assert(std::unique(variableNames.begin(), variableNames.end()) == variableNames.end());
#undef PROCESS
}
QString Settings::findSettingsLocation(bool legacy, bool *foundExistingFile) const {
// In order to make sure we'll find (mostly legacy) settings files, even if they end up being in a slightly
// different dir than we currently expect, we construct a search path list that we'll traverse while searching for
// the settings file. In case we find a suitable settings file within the search path, then we'll continue to use
// this path instead of creating a new one (in the location that we currently think is best to use).
QStringList paths;
paths << QCoreApplication::instance()->applicationDirPath();
paths << QStandardPaths::writableLocation(QStandardPaths::DataLocation);
paths << QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
paths << QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
paths << QFileInfo(QSettings().fileName()).dir().absolutePath();
QStringList settingsFileNames = legacy ? QStringList({ QStringLiteral("mumble.conf"), QStringLiteral("Mumble.conf"),
QStringLiteral("mumble.ini"), QStringLiteral("Mumble.ini") })
: QStringList({ QStringLiteral("mumble_settings.json") });
QString chosenPath;
for (const QString &settingsFileName : settingsFileNames) {
for (const QString &currentPath : paths) {
QFile settingsFile(QString::fromLatin1("%1/%2").arg(currentPath).arg(settingsFileName));
if (settingsFile.exists() && settingsFile.permissions().testFlag(QFile::WriteUser)) {
// Found existing settings file -> use that
chosenPath = QFileInfo(settingsFile).absoluteFilePath();
break;
}
}
}
if (foundExistingFile) {
*foundExistingFile = !chosenPath.isEmpty();
}
if (chosenPath.isEmpty()) {
// We were unable to find an existing settings file -> Fall back to a default location
chosenPath = QString::fromLatin1("%1/%2")
.arg(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation))
.arg(settingsFileNames[0]);
// Note: QStandardPaths::AppConfigLocation will return a directory of the style <root>/<org>/<application> where
// <root> is the path to the general config related directory on the respective OS, <org> is the name of our
// organization and <application> is our application's name. In our case (at the time of writing this) <org> =
// <application> = Mumble, leading to a doubly nested "Mumble" directory. This should only be a cosmetic issue
// though.
}
return chosenPath;
}