diff --git a/src/lib/common/CMakeLists.txt b/src/lib/common/CMakeLists.txt index c0282b949..7b2afd024 100644 --- a/src/lib/common/CMakeLists.txt +++ b/src/lib/common/CMakeLists.txt @@ -16,10 +16,11 @@ add_library(common STATIC stdpre.h stdset.h stdvector.h + Settings.h + Settings.cpp ${CMAKE_CURRENT_BINARY_DIR}/constants.h ) -set_target_properties(common PROPERTIES LINKER_LANGUAGE CXX) target_link_libraries(common PUBLIC Qt6::Core) if(APPLE) diff --git a/src/lib/common/Settings.cpp b/src/lib/common/Settings.cpp new file mode 100644 index 000000000..35fc502d0 --- /dev/null +++ b/src/lib/common/Settings.cpp @@ -0,0 +1,158 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#include "Settings.h" + +#include + +Settings *Settings::instance() +{ + static Settings m; + return &m; +} + +void Settings::setSettingFile(const QString &settingsFile) +{ + if (instance()->m_portableSettingsFile == settingsFile) { + qDebug().noquote() << "settings file already in use"; + return; + } + + instance()->m_portableSettingsFile = settingsFile; + if (instance()->m_settings) + instance()->m_settings->deleteLater(); + instance()->m_settings = new QSettings(instance()->m_portableSettingsFile, QSettings::IniFormat); + qInfo().noquote() << "settings file:" << instance()->m_settings->fileName(); +} + +Settings::Settings(QObject *parent) : QObject(parent) +{ + m_portableSettingsFile = QStringLiteral("settings.ini"); + if (QFile(m_portableSettingsFile).exists()) { + m_settings = new QSettings(m_portableSettingsFile, QSettings::IniFormat); + qInfo().noquote() << "settings file:" << m_settings->fileName(); + return; + } + initSettings(); +} + +bool Settings::isPortableSettings() +{ + return (QFile(instance()->m_portableSettingsFile).exists()); +} + +void Settings::initSettings() +{ + if (m_settings) + m_settings->deleteLater(); + + const auto userScopeCheck = QSettings(UserSettingFile, QSettings::IniFormat).value(Core::Scope).toBool(); + const auto systemScopeCheck = QSettings(SystemSettingFile, QSettings::IniFormat).value(Core::Scope).toBool(); + const auto systemScope = (userScopeCheck && systemScopeCheck); + m_settings = new QSettings(systemScope ? SystemSettingFile : UserSettingFile, QSettings::IniFormat); + qInfo().noquote() << "settings file:" << m_settings->fileName(); +} + +void Settings::cleanSettings() +{ + const QStringList keys = m_settings->allKeys(); + for (const QString &key : keys) { + if (!m_validKeys.contains(key)) + m_settings->remove(key); + if (m_settings->value(key).toString().isEmpty() && !m_settings->value(key).isValid()) + m_settings->remove(key); + } +} + +QVariant Settings::defaultValue(const QString &key) +{ + if (key == Core::Scope) { + return false; + } + + return QVariant(); +} + +bool Settings::isWritable() +{ + return instance()->m_settings->isWritable(); +} + +bool Settings::isSystemScope() +{ + if (isPortableSettings()) { + return false; + } + return instance()->settingsFile() == SystemSettingFile; +} + +void Settings::setScope(bool systemScope) +{ + if (isPortableSettings()) { + return; + } + + if (systemScope == isSystemScope()) + return; + + const bool wasWritable = instance()->m_settings->isWritable(); + + QSettings userSettings(Settings::UserSettingFile, QSettings::IniFormat); + userSettings.setValue(Settings::Core::Scope, systemScope); + userSettings.sync(); + + QSettings systemSettings(Settings::SystemSettingFile, QSettings::IniFormat); + systemSettings.setValue(Settings::Core::Scope, systemScope); + systemSettings.sync(); + + instance()->initSettings(); + + const bool isWritable = instance()->m_settings->isWritable(); + + if (isWritable != wasWritable) + Q_EMIT instance()->writableChanged(isWritable); + + Q_EMIT instance()->scopeChanged(systemScope); +} + +const QString Settings::settingsFile() +{ + return instance()->m_settings->fileName(); +} + +const QString Settings::settingsPath() +{ + return QFileInfo(instance()->m_settings->fileName()).absolutePath(); +} + +void Settings::setValue(const QString &key, const QVariant &value) +{ + if (instance()->m_settings->value(key) == value) + return; + + if (!value.isValid()) + instance()->m_settings->remove(key); + else if (key == Core::Scope) { + instance()->setScope(value.value()); + } else { + instance()->m_settings->setValue(key, value); + } + + instance()->m_settings->sync(); + Q_EMIT instance()->settingsChanged(key); +} + +QVariant Settings::value(const QString &key) +{ + return instance()->m_settings->value(key, defaultValue(key)); +} + +void Settings::restoreDefaultSettings() +{ + for (const auto &key : m_validKeys) { + instance()->setValue(key, defaultValue(key)); + } +} diff --git a/src/lib/common/Settings.h b/src/lib/common/Settings.h new file mode 100644 index 000000000..a8ab66017 --- /dev/null +++ b/src/lib/common/Settings.h @@ -0,0 +1,67 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#pragma once + +#include + +#include + +#include "common/constants.h" + +class Settings : public QObject +{ + Q_OBJECT +public: +#if defined(Q_OS_WIN) + inline const static auto UserSettingFile = + QStringLiteral("%1/AppData/Local/%2/%3.conf").arg(QDir::homePath(), kAppName, kAppName); + inline const static auto SystemSettingFile = QStringLiteral("C:/ProgramData/%2/%3.conf").arg(kAppName, kAppName); +#elif defined(Q_OS_MAC) + inline const static auto UserSettingFile = + QStringLiteral("%1/Library/%2/%3.conf").arg(QDir::homePath(), kAppName, kAppName); + inline const static auto SystemSettingFile = QStringLiteral("/Libaray/%2/%3.conf").arg(kAppName, kAppName); +#else + inline const static auto UserSettingFile = + QStringLiteral("%1/.config/%2/%3.conf").arg(QDir::homePath(), kAppName, kAppName); + inline const static auto SystemSettingFile = QStringLiteral("/etc/%2/%3.conf").arg(kAppName, kAppName); +#endif + + struct Core + { + inline static const auto Scope = QStringLiteral("core/loadFromSystemScope"); + }; + + static Settings *instance(); + static void setSettingFile(const QString &settingsFile = QString()); + static void setValue(const QString &key = QString(), const QVariant &value = QVariant()); + static QVariant value(const QString &key = QString()); + static void restoreDefaultSettings(); + static QVariant defaultValue(const QString &key); + static bool isWritable(); + static bool isSystemScope(); + static void setScope(bool systemScope); + static const QString settingsFile(); + static const QString settingsPath(); + +signals: + void scopeChanged(bool isSystemScope); + void writableChanged(bool canWrite); + void settingsChanged(const QString key); + +private: + explicit Settings(QObject *parent = nullptr); + Settings *operator=(Settings &other) = delete; + Settings(const Settings &other) = delete; + ~Settings() = default; + static bool isPortableSettings(); + void cleanSettings(); + void initSettings(); + + QSettings *m_settings = nullptr; + QString m_portableSettingsFile = QStringLiteral("%1.conf").arg(kAppName); + inline static const QStringList m_validKeys = {Settings::Core::Scope}; +};