feat: Change to local user ini (not native)

Windows registry is horrible to deal with and makes it impractical to pass settings to the Core when run via daemon on Windows.

- Pass settings path to Core when launched via daemon
- Introduce portable mode detection logic on Windows
- Generalize `m_settingsFile` use
- Reduce #ifdef size for Settings ctor path logic
This commit is contained in:
Nick Bolton 2025-10-09 14:56:34 +01:00 committed by Chris Rizzitello
parent 3ece50e292
commit bb1394ceeb
7 changed files with 56 additions and 36 deletions

View file

@ -20,45 +20,42 @@ Settings *Settings::instance()
void Settings::setSettingFile(const QString &settingsFile) void Settings::setSettingFile(const QString &settingsFile)
{ {
if (instance()->m_portableSettingsFile == settingsFile) { if (instance()->settingsFile() == settingsFile) {
qDebug().noquote() << "settings file already in use"; qDebug("settings file already set, skipping");
return; return;
} }
instance()->m_portableSettingsFile = settingsFile;
if (instance()->m_settings) if (instance()->m_settings)
instance()->m_settings->deleteLater(); instance()->m_settings->deleteLater();
instance()->m_settings = new QSettings(instance()->m_portableSettingsFile, QSettings::IniFormat);
instance()->m_settingsProxy->load(instance()->m_portableSettingsFile); instance()->m_settings = new QSettings(settingsFile, QSettings::IniFormat);
qInfo().noquote() << "settings file:" << instance()->m_settings->fileName(); instance()->m_settingsProxy->load(settingsFile);
qInfo().noquote() << "settings file changed:" << instance()->m_settings->fileName();
} }
Settings::Settings(QObject *parent) : QObject(parent) Settings::Settings(QObject *parent) : QObject(parent)
{ {
QString fileToLoad; QString fileToLoad;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
m_portableSettingsFile = m_portableSettingsFile.arg(QCoreApplication::applicationDirPath(), kAppName); const auto portableFile = portableSettingsFile();
if (QFile(m_portableSettingsFile).exists()) { qDebug().noquote() << "checking for portable settings file at:" << portableFile;
fileToLoad = m_portableSettingsFile; if (QFile(portableFile).exists())
m_settings = new QSettings(fileToLoad, QSettings::IniFormat); fileToLoad = portableFile;
} else {
m_settings = new QSettings(QSettings::NativeFormat, QSettings::UserScope, kAppName, kAppName);
}
#else #else
if (!qEnvironmentVariable("XDG_CONFIG_HOME").isEmpty()) if (!qEnvironmentVariable("XDG_CONFIG_HOME").isEmpty())
fileToLoad = QStringLiteral("%1/%2/%2.conf").arg(qEnvironmentVariable("XDG_CONFIG_HOME"), kAppName); fileToLoad = QStringLiteral("%1/%2/%2.conf").arg(qEnvironmentVariable("XDG_CONFIG_HOME"), kAppName);
#endif
else if (QFile(UserSettingFile).exists()) else if (QFile(UserSettingFile).exists())
fileToLoad = UserSettingFile; fileToLoad = UserSettingFile;
else if (QFile(SystemSettingFile).exists()) else if (QFile(SystemSettingFile).exists())
fileToLoad = SystemSettingFile; fileToLoad = SystemSettingFile;
else else
fileToLoad = UserSettingFile; fileToLoad = UserSettingFile;
m_settings = new QSettings(fileToLoad, QSettings::IniFormat);
#endif
m_settings = new QSettings(fileToLoad, QSettings::IniFormat);
m_settingsProxy = std::make_shared<QSettingsProxy>(); m_settingsProxy = std::make_shared<QSettingsProxy>();
m_settingsProxy->load(fileToLoad); m_settingsProxy->load(fileToLoad);
qInfo().noquote() << "settings file:" << m_settings->fileName(); qInfo().noquote() << "initial settings file:" << m_settings->fileName();
} }
void Settings::cleanSettings() void Settings::cleanSettings()
@ -143,7 +140,7 @@ QVariant Settings::defaultValue(const QString &key)
return 4; // INFO return 4; // INFO
if (key == Daemon::Elevate) if (key == Daemon::Elevate)
return Settings::isNativeMode(); return !Settings::isPortableMode();
if (key == Core::UpdateUrl) if (key == Core::UpdateUrl)
return kUrlUpdateCheck; return kUrlUpdateCheck;
@ -155,10 +152,12 @@ QVariant Settings::defaultValue(const QString &key)
return 24800; return 24800;
if (key == Core::ProcessMode) { if (key == Core::ProcessMode) {
if (Settings::isNativeMode()) #ifdef Q_OS_WIN
if (!Settings::isPortableMode())
return Settings::ProcessMode::Service; return Settings::ProcessMode::Service;
else #endif
return Settings::ProcessMode::Desktop;
return Settings::ProcessMode::Desktop;
} }
if (key == Daemon::LogFile) { if (key == Daemon::LogFile) {
@ -196,14 +195,13 @@ QStringList Settings::validKeys()
bool Settings::isWritable() bool Settings::isWritable()
{ {
if (Settings::isNativeMode())
return true;
return instance()->m_settings->isWritable(); return instance()->m_settings->isWritable();
} }
bool Settings::isNativeMode() bool Settings::isPortableMode()
{ {
return instance()->m_settings->format() == QSettings::NativeFormat; // Enable portable mode only if the portable settings file exists in the expected location.
return QFile(portableSettingsFile()).exists();
} }
QString Settings::settingsFile() QString Settings::settingsFile()
@ -213,8 +211,11 @@ QString Settings::settingsFile()
QString Settings::settingsPath() QString Settings::settingsPath()
{ {
if (instance()->isNativeMode()) #ifdef Q_OS_WIN
if (!isPortableMode())
return SystemDir; return SystemDir;
#endif
return QFileInfo(instance()->m_settings->fileName()).absolutePath(); return QFileInfo(instance()->m_settings->fileName()).absolutePath();
} }
@ -263,3 +264,10 @@ void Settings::restoreDefaultSettings()
instance()->setValue(key, defaultValue(key)); instance()->setValue(key, defaultValue(key));
} }
} }
QString Settings::portableSettingsFile()
{
static const auto filename =
QStringLiteral("%1/settings/%2.conf").arg(QCoreApplication::applicationDirPath(), kAppName);
return filename;
}

View file

@ -19,7 +19,7 @@ class Settings : public QObject
Q_OBJECT Q_OBJECT
public: public:
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
inline const static auto UserDir = QStringLiteral("%1/AppData/Local/%2").arg(QDir::homePath(), kAppName); inline const static auto UserDir = QStringLiteral("%1/AppData/Roaming/%2").arg(QDir::homePath(), kAppName);
inline const static auto SystemDir = QStringLiteral("%1ProgramData/%2").arg(QDir::rootPath(), kAppName); inline const static auto SystemDir = QStringLiteral("%1ProgramData/%2").arg(QDir::rootPath(), kAppName);
#elif defined(Q_OS_MAC) #elif defined(Q_OS_MAC)
inline const static auto UserDir = QStringLiteral("%1/Library/%2").arg(QDir::homePath(), kAppName); inline const static auto UserDir = QStringLiteral("%1/Library/%2").arg(QDir::homePath(), kAppName);
@ -28,6 +28,7 @@ public:
inline const static auto UserDir = QStringLiteral("%1/.config/%2").arg(QDir::homePath(), kAppName); inline const static auto UserDir = QStringLiteral("%1/.config/%2").arg(QDir::homePath(), kAppName);
inline const static auto SystemDir = QStringLiteral("/etc/%1").arg(kAppName); inline const static auto SystemDir = QStringLiteral("/etc/%1").arg(kAppName);
#endif #endif
inline const static auto UserSettingFile = QStringLiteral("%1/%2.conf").arg(UserDir, kAppName); inline const static auto UserSettingFile = QStringLiteral("%1/%2.conf").arg(UserDir, kAppName);
inline const static auto SystemSettingFile = QStringLiteral("%1/%2.conf").arg(SystemDir, kAppName); inline const static auto SystemSettingFile = QStringLiteral("%1/%2.conf").arg(SystemDir, kAppName);
@ -120,7 +121,7 @@ public:
static void restoreDefaultSettings(); static void restoreDefaultSettings();
static QVariant defaultValue(const QString &key); static QVariant defaultValue(const QString &key);
static bool isWritable(); static bool isWritable();
static bool isNativeMode(); static bool isPortableMode();
static QString settingsFile(); static QString settingsFile();
static QString settingsPath(); static QString settingsPath();
static QString tlsDir(); static QString tlsDir();
@ -132,6 +133,7 @@ public:
static void save(bool emitSaving = true); static void save(bool emitSaving = true);
static QStringList validKeys(); static QStringList validKeys();
static int logLevelToInt(const QString &level = "INFO"); static int logLevelToInt(const QString &level = "INFO");
static QString portableSettingsFile();
Q_SIGNALS: Q_SIGNALS:
void settingsChanged(const QString key); void settingsChanged(const QString key);
@ -145,7 +147,6 @@ private:
void cleanSettings(); void cleanSettings();
QSettings *m_settings = nullptr; QSettings *m_settings = nullptr;
QString m_portableSettingsFile = QStringLiteral("%1/settings/%2.conf");
std::shared_ptr<QSettingsProxy> m_settingsProxy; std::shared_ptr<QSettingsProxy> m_settingsProxy;
// clang-format off // clang-format off

View file

@ -44,7 +44,7 @@ void clearSettings(bool enableRestart)
profileDir.removeRecursively(); profileDir.removeRecursively();
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
if (!Settings::isNativeMode()) { if (Settings::isPortableMode()) {
// make a new empty portable settings file // make a new empty portable settings file
if (profileDir.mkpath(Settings::settingsPath())) { if (profileDir.mkpath(Settings::settingsPath())) {
QFile file(Settings::settingsFile()); QFile file(Settings::settingsFile());

View file

@ -349,6 +349,7 @@ void CoreProcess::start(std::optional<ProcessMode> processModeOption)
if (processMode == ProcessMode::Desktop) { if (processMode == ProcessMode::Desktop) {
startForegroundProcess(args); startForegroundProcess(args);
} else if (processMode == ProcessMode::Service) { } else if (processMode == ProcessMode::Service) {
args.append({QStringLiteral("--settings"), Settings::settingsFile()});
startProcessFromDaemon(args); startProcessFromDaemon(args);
} }

View file

@ -271,8 +271,9 @@ void SettingsDialog::updateControls()
ui->comboTlsKeyLength->setEnabled(writable); ui->comboTlsKeyLength->setEnabled(writable);
ui->cbCloseToTray->setEnabled(writable); ui->cbCloseToTray->setEnabled(writable);
// Handle enable and disable of service items // Portable mode only ever applies to Windows.
if (Settings::isNativeMode()) { // Daemon options should only be available on Windows when *not* in portable mode.
if (!Settings::isPortableMode()) {
ui->groupService->setEnabled(writable); ui->groupService->setEnabled(writable);
ui->cbElevateDaemon->setEnabled(writable && serviceChecked); ui->cbElevateDaemon->setEnabled(writable && serviceChecked);
} else if (ui->groupService->isVisibleTo(ui->tabAdvanced)) { } else if (ui->groupService->isVisibleTo(ui->tabAdvanced)) {

View file

@ -16,7 +16,7 @@ void SettingsTests::initTestCase()
oldSettings.remove(); oldSettings.remove();
} }
void SettingsTests::setSettingsFile() void SettingsTests::setSettingFile()
{ {
Settings::setSettingFile(m_settingsFile); Settings::setSettingFile(m_settingsFile);
} }

View file

@ -14,7 +14,7 @@ class SettingsTests : public QObject
private Q_SLOTS: private Q_SLOTS:
void initTestCase(); void initTestCase();
// Test are run in order top to bottom // Test are run in order top to bottom
void setSettingsFile(); void setSettingFile();
void settingsFile(); void settingsFile();
void settingsPath(); void settingsPath();
void tlsDir(); void tlsDir();
@ -24,9 +24,18 @@ private Q_SLOTS:
void checkValidSettings(); void checkValidSettings();
private: private:
inline static const QString m_settingsPath = QStringLiteral("tmp/test"); inline static const QString m_settingsPathTemp = QStringLiteral("tmp/test");
inline static const QString m_settingsFile = QStringLiteral("%1/Deskflow.conf").arg(m_settingsPath); inline static const QString m_settingsFile = QStringLiteral("%1/Deskflow.conf").arg(m_settingsPathTemp);
inline static const QString m_expectedTlsDir = QStringLiteral("tmp/test/%1").arg(kTlsDirName);
// Gotcha: On Windows non-portable mode, additional config files such as TLS config are saved
// in 'Program Data' and are not stored in the same place as the settings file.
#ifdef Q_OS_WIN
inline static const QString m_settingsPath = Settings::SystemDir;
#else
inline static const QString m_settingsPath = m_settingsPathTemp;
#endif
inline static const QString m_expectedTlsDir = QStringLiteral("%1/%2").arg(m_settingsPath, kTlsDirName);
inline static const QString m_expectedTlsLocalDB = inline static const QString m_expectedTlsLocalDB =
QStringLiteral("%1/%2").arg(m_expectedTlsDir, kTlsFingerprintLocalFilename); QStringLiteral("%1/%2").arg(m_expectedTlsDir, kTlsFingerprintLocalFilename);
inline static const QString m_expectedTlsServerDB = inline static const QString m_expectedTlsServerDB =