From 71f27bb45fe9c6d7de7411a74063f05596bc2d01 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Sat, 11 Apr 2026 18:09:21 +1000 Subject: [PATCH] feat: add per monitor configs Per monitor configs go in a separate file in the monitor dir, e.g. `monitors//shell.json` --- plugin/src/Caelestia/Config/CMakeLists.txt | 1 + plugin/src/Caelestia/Config/config.cpp | 63 ++++++- plugin/src/Caelestia/Config/config.hpp | 44 +++++ plugin/src/Caelestia/Config/configobject.cpp | 163 +++++++++++++++++- plugin/src/Caelestia/Config/configobject.hpp | 37 +++- plugin/src/Caelestia/Config/configscope.cpp | 32 ++++ plugin/src/Caelestia/Config/configscope.hpp | 28 +++ .../Caelestia/Config/monitorconfigmanager.cpp | 69 ++++++++ .../Caelestia/Config/monitorconfigmanager.hpp | 38 ++++ plugin/src/Caelestia/Config/tokens.cpp | 52 +++++- plugin/src/Caelestia/Config/tokens.hpp | 34 ++++ 11 files changed, 556 insertions(+), 5 deletions(-) create mode 100644 plugin/src/Caelestia/Config/monitorconfigmanager.cpp create mode 100644 plugin/src/Caelestia/Config/monitorconfigmanager.hpp diff --git a/plugin/src/Caelestia/Config/CMakeLists.txt b/plugin/src/Caelestia/Config/CMakeLists.txt index dcaf1e69..9d8a9d99 100644 --- a/plugin/src/Caelestia/Config/CMakeLists.txt +++ b/plugin/src/Caelestia/Config/CMakeLists.txt @@ -6,6 +6,7 @@ qml_module(caelestia-config configscope.cpp appearanceconfig.cpp tokens.cpp + monitorconfigmanager.cpp backgroundconfig.hpp barconfig.hpp borderconfig.hpp diff --git a/plugin/src/Caelestia/Config/config.cpp b/plugin/src/Caelestia/Config/config.cpp index 0a71fdc9..345655ec 100644 --- a/plugin/src/Caelestia/Config/config.cpp +++ b/plugin/src/Caelestia/Config/config.cpp @@ -41,6 +41,30 @@ GlobalConfig::GlobalConfig(QObject* parent) setupFileBackend(configDir() + QStringLiteral("shell.json")); } +GlobalConfig::GlobalConfig(GlobalConfig* fallback, const QString& filePath, QObject* parent) + : ConfigObject(parent) + , m_appearance(new AppearanceConfig(this)) + , m_general(new GeneralConfig(this)) + , m_background(new BackgroundConfig(this)) + , m_bar(new BarConfig(this)) + , m_border(new BorderConfig(this)) + , m_dashboard(new DashboardConfig(this)) + , m_controlCenter(new ControlCenterConfig(this)) + , m_launcher(new LauncherConfig(this)) + , m_notifs(new NotifsConfig(this)) + , m_osd(new OsdConfig(this)) + , m_session(new SessionConfig(this)) + , m_winfo(new WInfoConfig(this)) + , m_lock(new LockConfig(this)) + , m_utilities(new UtilitiesConfig(this)) + , m_sidebar(new SidebarConfig(this)) + , m_services(new ServiceConfig(this)) + , m_paths(new UserPaths(this)) { + setSparse(true); + setupFileBackend(filePath); + syncFromGlobal(fallback); +} + GlobalConfig::~GlobalConfig() { // Clear global instance s_instance = nullptr; @@ -67,7 +91,44 @@ void GlobalConfig::reload() { Config::Config(ConfigScope* scope, QObject* parent) : QObject(parent) - , m_scope(scope) {} + , m_scope(scope) { + connectScope(); +} + +void Config::connectScope() { + if (!m_scope) + return; + connect(m_scope, &ConfigScope::configChanged, this, &Config::sourceChanged); + connect(m_scope, &ConfigScope::tokensChanged, this, &Config::sourceChanged); +} + +// Helper: return per-monitor sub-object if scope exists, otherwise global +#define CONFIG_ATTACHED_GETTER(Type, name) \ + const Type* Config::name() const { \ + if (m_scope && m_scope->config()) \ + return m_scope->config()->name(); \ + return GlobalConfig::instance()->name(); \ + } + +CONFIG_ATTACHED_GETTER(AppearanceConfig, appearance) +CONFIG_ATTACHED_GETTER(GeneralConfig, general) +CONFIG_ATTACHED_GETTER(BackgroundConfig, background) +CONFIG_ATTACHED_GETTER(BarConfig, bar) +CONFIG_ATTACHED_GETTER(BorderConfig, border) +CONFIG_ATTACHED_GETTER(DashboardConfig, dashboard) +CONFIG_ATTACHED_GETTER(ControlCenterConfig, controlCenter) +CONFIG_ATTACHED_GETTER(LauncherConfig, launcher) +CONFIG_ATTACHED_GETTER(NotifsConfig, notifs) +CONFIG_ATTACHED_GETTER(OsdConfig, osd) +CONFIG_ATTACHED_GETTER(SessionConfig, session) +CONFIG_ATTACHED_GETTER(WInfoConfig, winfo) +CONFIG_ATTACHED_GETTER(LockConfig, lock) +CONFIG_ATTACHED_GETTER(UtilitiesConfig, utilities) +CONFIG_ATTACHED_GETTER(SidebarConfig, sidebar) +CONFIG_ATTACHED_GETTER(ServiceConfig, services) +CONFIG_ATTACHED_GETTER(UserPaths, paths) + +#undef CONFIG_ATTACHED_GETTER Config* Config::qmlAttachedProperties(QObject* object) { return new Config(ConfigScope::find(object), object); diff --git a/plugin/src/Caelestia/Config/config.hpp b/plugin/src/Caelestia/Config/config.hpp index 2f5fd570..f74c30b2 100644 --- a/plugin/src/Caelestia/Config/config.hpp +++ b/plugin/src/Caelestia/Config/config.hpp @@ -55,25 +55,69 @@ public: ~GlobalConfig() override; private: + friend class MonitorConfigManager; explicit GlobalConfig(QObject* parent = nullptr); + explicit GlobalConfig(GlobalConfig* fallback, const QString& filePath, QObject* parent); }; class ConfigScope; class Config : public QObject { Q_OBJECT + Q_MOC_INCLUDE("configscope.hpp") QML_ELEMENT QML_UNCREATABLE("") QML_ATTACHED(Config) + Q_PROPERTY(ConfigScope* scope READ scope NOTIFY sourceChanged) + Q_PROPERTY(const AppearanceConfig* appearance READ appearance NOTIFY sourceChanged) + Q_PROPERTY(const GeneralConfig* general READ general NOTIFY sourceChanged) + Q_PROPERTY(const BackgroundConfig* background READ background NOTIFY sourceChanged) + Q_PROPERTY(const BarConfig* bar READ bar NOTIFY sourceChanged) + Q_PROPERTY(const BorderConfig* border READ border NOTIFY sourceChanged) + Q_PROPERTY(const DashboardConfig* dashboard READ dashboard NOTIFY sourceChanged) + Q_PROPERTY(const ControlCenterConfig* controlCenter READ controlCenter NOTIFY sourceChanged) + Q_PROPERTY(const LauncherConfig* launcher READ launcher NOTIFY sourceChanged) + Q_PROPERTY(const NotifsConfig* notifs READ notifs NOTIFY sourceChanged) + Q_PROPERTY(const OsdConfig* osd READ osd NOTIFY sourceChanged) + Q_PROPERTY(const SessionConfig* session READ session NOTIFY sourceChanged) + Q_PROPERTY(const WInfoConfig* winfo READ winfo NOTIFY sourceChanged) + Q_PROPERTY(const LockConfig* lock READ lock NOTIFY sourceChanged) + Q_PROPERTY(const UtilitiesConfig* utilities READ utilities NOTIFY sourceChanged) + Q_PROPERTY(const SidebarConfig* sidebar READ sidebar NOTIFY sourceChanged) + Q_PROPERTY(const ServiceConfig* services READ services NOTIFY sourceChanged) + Q_PROPERTY(const UserPaths* paths READ paths NOTIFY sourceChanged) + public: explicit Config(ConfigScope* scope, QObject* parent = nullptr); [[nodiscard]] ConfigScope* scope() const { return m_scope; } + [[nodiscard]] const AppearanceConfig* appearance() const; + [[nodiscard]] const GeneralConfig* general() const; + [[nodiscard]] const BackgroundConfig* background() const; + [[nodiscard]] const BarConfig* bar() const; + [[nodiscard]] const BorderConfig* border() const; + [[nodiscard]] const DashboardConfig* dashboard() const; + [[nodiscard]] const ControlCenterConfig* controlCenter() const; + [[nodiscard]] const LauncherConfig* launcher() const; + [[nodiscard]] const NotifsConfig* notifs() const; + [[nodiscard]] const OsdConfig* osd() const; + [[nodiscard]] const SessionConfig* session() const; + [[nodiscard]] const WInfoConfig* winfo() const; + [[nodiscard]] const LockConfig* lock() const; + [[nodiscard]] const UtilitiesConfig* utilities() const; + [[nodiscard]] const SidebarConfig* sidebar() const; + [[nodiscard]] const ServiceConfig* services() const; + [[nodiscard]] const UserPaths* paths() const; + static Config* qmlAttachedProperties(QObject* object); + Q_SIGNAL void sourceChanged(); + private: + void connectScope(); + ConfigScope* m_scope; }; diff --git a/plugin/src/Caelestia/Config/configobject.cpp b/plugin/src/Caelestia/Config/configobject.cpp index cb6624a1..3682a00f 100644 --- a/plugin/src/Caelestia/Config/configobject.cpp +++ b/plugin/src/Caelestia/Config/configobject.cpp @@ -50,11 +50,13 @@ void ConfigObject::loadFromJson(const QJsonObject& obj) { for (const auto& v : jsonArr) list.append(v.toString()); prop.write(this, QVariant::fromValue(list)); + m_loadedKeys.insert(key); continue; } // For all other types, let Qt's variant conversion handle it prop.write(this, jsonVal.toVariant()); + m_loadedKeys.insert(key); } } @@ -108,6 +110,159 @@ QJsonObject ConfigObject::toJsonObject() const { return obj; } +QJsonObject ConfigObject::toSparseJsonObject() const { + QJsonObject obj; + const auto* meta = metaObject(); + + for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) { + const auto prop = meta->property(i); + + if (!prop.isReadable()) + continue; + + const auto key = QString::fromUtf8(prop.name()); + const auto value = prop.read(this); + + // Recurse into sub-objects — include only if they have loaded keys + if (value.canView()) { + auto* const subObj = value.value(); + if (subObj) { + auto subJson = subObj->toSparseJsonObject(); + if (!subJson.isEmpty()) + obj.insert(key, subJson); + } + continue; + } + + // Only include properties that were explicitly loaded + if (!m_loadedKeys.contains(key)) + continue; + + if (!prop.isWritable()) + continue; + + if (prop.metaType().id() == QMetaType::QStringList) { + QJsonArray arr; + const auto strList = value.toStringList(); + for (const auto& s : strList) + arr.append(s); + obj.insert(key, arr); + continue; + } + + if (prop.metaType().id() == QMetaType::QVariantList) { + obj.insert(key, QJsonArray::fromVariantList(value.toList())); + continue; + } + + obj.insert(key, QJsonValue::fromVariant(value)); + } + + return obj; +} + +void ConfigObject::clearLoadedKeys() { + m_loadedKeys.clear(); + + const auto* meta = metaObject(); + for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) { + auto prop = meta->property(i); + auto value = prop.read(this); + auto* subObj = value.value(); + if (subObj) + subObj->clearLoadedKeys(); + } +} + +void ConfigObject::syncFromGlobal(ConfigObject* global) { + m_global = global; + + // Connect batched change signal (single connection per ConfigObject pair) + connect(global, &ConfigObject::propertiesChanged, this, &ConfigObject::onGlobalPropertiesChanged); + + // Initial sync: copy all non-loaded property values from global + const auto* meta = metaObject(); + for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) { + auto prop = meta->property(i); + const auto key = QString::fromUtf8(prop.name()); + + auto current = prop.read(this); + auto* subObj = current.value(); + + if (subObj) { + auto globalVal = prop.read(global); + auto* globalSub = globalVal.value(); + if (globalSub) + subObj->syncFromGlobal(globalSub); + continue; + } + + if (!prop.isWritable()) + continue; + + if (!m_loadedKeys.contains(key)) + prop.write(this, prop.read(global)); + } +} + +void ConfigObject::resyncFromGlobal() { + if (!m_global) + return; + + const auto* meta = metaObject(); + for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) { + auto prop = meta->property(i); + const auto key = QString::fromUtf8(prop.name()); + + auto current = prop.read(this); + auto* subObj = current.value(); + + if (subObj) { + subObj->resyncFromGlobal(); + continue; + } + + if (!prop.isWritable()) + continue; + + if (!m_loadedKeys.contains(key)) + prop.write(this, prop.read(m_global)); + } +} + +void ConfigObject::onGlobalPropertiesChanged(const QMap& changed) { + for (auto it = changed.begin(); it != changed.end(); ++it) { + if (m_loadedKeys.contains(it.key())) + continue; + + int idx = metaObject()->indexOfProperty(it.key().toUtf8().constData()); + if (idx >= 0) + metaObject()->property(idx).write(this, it.value()); + } +} + +void ConfigObject::notifyPropertyChanged(const QString& name, const QVariant& value) { + m_pendingChanges.insert(name, value); + + if (!m_batchTimer) { + m_batchTimer = new QTimer(this); + m_batchTimer->setSingleShot(true); + m_batchTimer->setInterval(0); + connect(m_batchTimer, &QTimer::timeout, this, &ConfigObject::emitBatchedChanges); + } + + m_batchTimer->start(); +} + +void ConfigObject::emitBatchedChanges() { + if (m_pendingChanges.isEmpty()) + return; + + auto changes = std::move(m_pendingChanges); + m_pendingChanges.clear(); + Q_EMIT propertiesChanged(changes); +} + void ConfigObject::setupFileBackend(const QString& path) { m_filePath = path; @@ -126,7 +281,8 @@ void ConfigObject::setupFileBackend(const QString& path) { return; } - file.write(QJsonDocument(toJsonObject()).toJson(QJsonDocument::Indented)); + auto json = m_sparse ? toSparseJsonObject() : toJsonObject(); + file.write(QJsonDocument(json).toJson(QJsonDocument::Indented)); }); m_cooldownTimer->setSingleShot(true); @@ -167,7 +323,12 @@ void ConfigObject::reloadFromFile() { return; } + clearLoadedKeys(); loadFromJson(doc.object()); + + // Re-sync non-loaded properties from global after reload + if (m_global) + resyncFromGlobal(); } void ConfigObject::onFileChanged() { diff --git a/plugin/src/Caelestia/Config/configobject.hpp b/plugin/src/Caelestia/Config/configobject.hpp index bc8271a8..91c6c3f4 100644 --- a/plugin/src/Caelestia/Config/configobject.hpp +++ b/plugin/src/Caelestia/Config/configobject.hpp @@ -2,9 +2,12 @@ #include #include +#include #include #include +#include #include +#include // Declares a serialized config property with getter, setter (change-detected), signal, and member. #define CONFIG_PROPERTY(Type, name, ...) \ @@ -15,8 +18,10 @@ public: return m_##name; \ } \ void set_##name(const Type& val) { \ - if (caelestia::config::ConfigObject::updateMember(m_##name, val)) \ + if (caelestia::config::ConfigObject::updateMember(m_##name, val)) { \ Q_EMIT name##Changed(); \ + notifyPropertyChanged(QStringLiteral(#name), QVariant::fromValue(m_##name)); \ + } \ } \ Q_SIGNAL void name##Changed(); \ \ @@ -45,6 +50,7 @@ public: void loadFromJson(const QJsonObject& obj); [[nodiscard]] QJsonObject toJsonObject() const; + [[nodiscard]] QJsonObject toSparseJsonObject() const; // File-backed config support. Call setupFileBackend() to enable // automatic file watching, debounced saving, and reload. @@ -52,6 +58,18 @@ public: void saveToFile(); void reloadFromFile(); + // Per-monitor overlay support (Qt Resolve Mask pattern). + // Eagerly syncs non-overridden properties from a global ConfigObject. + void syncFromGlobal(ConfigObject* global); + void resyncFromGlobal(); + void clearLoadedKeys(); + + [[nodiscard]] bool isPropertyLoaded(const QString& name) const { return m_loadedKeys.contains(name); } + + [[nodiscard]] bool isSparse() const { return m_sparse; } + + void setSparse(bool sparse) { m_sparse = sparse; } + [[nodiscard]] bool recentlySaved() const { return m_recentlySaved; } template static bool updateMember(T& member, const T& value) { @@ -66,15 +84,30 @@ public: return true; } + Q_SIGNAL void propertiesChanged(const QMap& changed); + +protected: + void notifyPropertyChanged(const QString& name, const QVariant& value); + private: void onFileChanged(); + void onGlobalPropertiesChanged(const QMap& changed); + void emitBatchedChanges(); QString m_filePath; bool m_recentlySaved = false; - // These are heap-allocated only when setupFileBackend is called + bool m_sparse = false; + + // File backend (heap-allocated only when setupFileBackend is called) QFileSystemWatcher* m_watcher = nullptr; QTimer* m_saveTimer = nullptr; QTimer* m_cooldownTimer = nullptr; + + // Per-monitor overlay state + ConfigObject* m_global = nullptr; + QSet m_loadedKeys; + QMap m_pendingChanges; + QTimer* m_batchTimer = nullptr; }; } // namespace caelestia::config diff --git a/plugin/src/Caelestia/Config/configscope.cpp b/plugin/src/Caelestia/Config/configscope.cpp index e7e8452b..7b2074e3 100644 --- a/plugin/src/Caelestia/Config/configscope.cpp +++ b/plugin/src/Caelestia/Config/configscope.cpp @@ -1,10 +1,42 @@ #include "configscope.hpp" +#include "monitorconfigmanager.hpp" namespace caelestia::config { ConfigScope::ConfigScope(QQuickItem* parent) : QQuickItem(parent) {} +void ConfigScope::setScreen(const QString& screen) { + if (m_screen == screen) + return; + + m_screen = screen; + Q_EMIT screenChanged(); + resolveConfig(); +} + +void ConfigScope::resolveConfig() { + GlobalConfig* newConfig = nullptr; + TokenConfig* newTokens = nullptr; + + if (!m_screen.isEmpty()) { + if (auto* mgr = MonitorConfigManager::instance()) { + newConfig = mgr->configForScreen(m_screen); + newTokens = mgr->tokensForScreen(m_screen); + } + } + + if (m_config != newConfig) { + m_config = newConfig; + Q_EMIT configChanged(); + } + + if (m_tokens != newTokens) { + m_tokens = newTokens; + Q_EMIT tokensChanged(); + } +} + ConfigScope* ConfigScope::find(QObject* object) { auto* item = qobject_cast(object); diff --git a/plugin/src/Caelestia/Config/configscope.hpp b/plugin/src/Caelestia/Config/configscope.hpp index 94380c80..411e9034 100644 --- a/plugin/src/Caelestia/Config/configscope.hpp +++ b/plugin/src/Caelestia/Config/configscope.hpp @@ -5,14 +5,42 @@ namespace caelestia::config { +class GlobalConfig; +class TokenConfig; + class ConfigScope : public QQuickItem { Q_OBJECT + Q_MOC_INCLUDE("config.hpp") + Q_MOC_INCLUDE("tokens.hpp") QML_ELEMENT + Q_PROPERTY(QString screen READ screen WRITE setScreen NOTIFY screenChanged) + Q_PROPERTY(GlobalConfig* config READ config NOTIFY configChanged) + Q_PROPERTY(TokenConfig* tokens READ tokens NOTIFY tokensChanged) + public: explicit ConfigScope(QQuickItem* parent = nullptr); + [[nodiscard]] QString screen() const { return m_screen; } + + void setScreen(const QString& screen); + + [[nodiscard]] GlobalConfig* config() const { return m_config; } + + [[nodiscard]] TokenConfig* tokens() const { return m_tokens; } + static ConfigScope* find(QObject* object); + + Q_SIGNAL void screenChanged(); + Q_SIGNAL void configChanged(); + Q_SIGNAL void tokensChanged(); + +private: + void resolveConfig(); + + QString m_screen; + GlobalConfig* m_config = nullptr; + TokenConfig* m_tokens = nullptr; }; } // namespace caelestia::config diff --git a/plugin/src/Caelestia/Config/monitorconfigmanager.cpp b/plugin/src/Caelestia/Config/monitorconfigmanager.cpp new file mode 100644 index 00000000..d8b0561c --- /dev/null +++ b/plugin/src/Caelestia/Config/monitorconfigmanager.cpp @@ -0,0 +1,69 @@ +#include "monitorconfigmanager.hpp" +#include "config.hpp" +#include "tokens.hpp" + +#include + +namespace caelestia::config { + +MonitorConfigManager* MonitorConfigManager::s_instance = nullptr; + +namespace { + +QString monitorConfigDir(const QString& screen) { + return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + + QStringLiteral("/caelestia/monitors/") + screen + QStringLiteral("/"); +} + +} // namespace + +MonitorConfigManager::MonitorConfigManager(QObject* parent) + : QObject(parent) { + s_instance = this; +} + +MonitorConfigManager::~MonitorConfigManager() { + s_instance = nullptr; +} + +MonitorConfigManager* MonitorConfigManager::instance() { + return s_instance; +} + +MonitorConfigManager* MonitorConfigManager::create(QQmlEngine* engine, QJSEngine*) { + return new MonitorConfigManager(engine); +} + +GlobalConfig* MonitorConfigManager::configForScreen(const QString& screen) { + auto it = m_overlays.find(screen); + if (it != m_overlays.end() && it->config) + return it->config; + + auto* global = GlobalConfig::instance(); + if (!global) + return nullptr; + + auto dir = monitorConfigDir(screen); + auto* overlay = new GlobalConfig(global, dir + QStringLiteral("shell.json"), this); + + m_overlays[screen].config = overlay; + return overlay; +} + +TokenConfig* MonitorConfigManager::tokensForScreen(const QString& screen) { + auto it = m_overlays.find(screen); + if (it != m_overlays.end() && it->tokens) + return it->tokens; + + auto* global = TokenConfig::instance(); + if (!global) + return nullptr; + + auto dir = monitorConfigDir(screen); + auto* overlay = new TokenConfig(global, dir + QStringLiteral("shell-tokens.json"), this); + + m_overlays[screen].tokens = overlay; + return overlay; +} + +} // namespace caelestia::config diff --git a/plugin/src/Caelestia/Config/monitorconfigmanager.hpp b/plugin/src/Caelestia/Config/monitorconfigmanager.hpp new file mode 100644 index 00000000..2741b732 --- /dev/null +++ b/plugin/src/Caelestia/Config/monitorconfigmanager.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +namespace caelestia::config { + +class GlobalConfig; +class TokenConfig; + +class MonitorConfigManager : public QObject { + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +public: + static MonitorConfigManager* instance(); + static MonitorConfigManager* create(QQmlEngine*, QJSEngine*); + + [[nodiscard]] Q_INVOKABLE GlobalConfig* configForScreen(const QString& screen); + [[nodiscard]] Q_INVOKABLE TokenConfig* tokensForScreen(const QString& screen); + + ~MonitorConfigManager() override; + +private: + explicit MonitorConfigManager(QObject* parent = nullptr); + + struct ScreenOverlay { + GlobalConfig* config = nullptr; + TokenConfig* tokens = nullptr; + }; + + QHash m_overlays; + static MonitorConfigManager* s_instance; +}; + +} // namespace caelestia::config diff --git a/plugin/src/Caelestia/Config/tokens.cpp b/plugin/src/Caelestia/Config/tokens.cpp index c81567d6..8aab816e 100644 --- a/plugin/src/Caelestia/Config/tokens.cpp +++ b/plugin/src/Caelestia/Config/tokens.cpp @@ -46,6 +46,25 @@ TokenConfig::TokenConfig(QObject* parent) setupFileBackend(configDir() + QStringLiteral("shell-tokens.json")); } +TokenConfig::TokenConfig(TokenConfig* fallback, const QString& filePath, QObject* parent) + : ConfigObject(parent) + , m_appearance(new AppearanceTokens(this)) + , m_bar(new BarTokens(this)) + , m_dashboard(new DashboardTokens(this)) + , m_launcher(new LauncherTokens(this)) + , m_notifs(new NotifsTokens(this)) + , m_osd(new OsdTokens(this)) + , m_session(new SessionTokens(this)) + , m_sidebar(new SidebarTokens(this)) + , m_utilities(new UtilitiesTokens(this)) + , m_lock(new LockTokens(this)) + , m_winfo(new WInfoTokens(this)) + , m_controlCenter(new ControlCenterTokens(this)) { + setSparse(true); + setupFileBackend(filePath); + syncFromGlobal(fallback); +} + TokenConfig::~TokenConfig() { s_instance = nullptr; } @@ -70,7 +89,38 @@ void TokenConfig::reload() { Tokens::Tokens(ConfigScope* scope, QObject* parent) : QObject(parent) - , m_scope(scope) {} + , m_scope(scope) { + connectScope(); +} + +void Tokens::connectScope() { + if (!m_scope) + return; + connect(m_scope, &ConfigScope::tokensChanged, this, &Tokens::sourceChanged); + connect(m_scope, &ConfigScope::configChanged, this, &Tokens::sourceChanged); +} + +#define TOKENS_ATTACHED_GETTER(Type, name) \ + const Type* Tokens::name() const { \ + if (m_scope && m_scope->tokens()) \ + return m_scope->tokens()->name(); \ + return TokenConfig::instance()->name(); \ + } + +TOKENS_ATTACHED_GETTER(AppearanceTokens, appearance) +TOKENS_ATTACHED_GETTER(BarTokens, bar) +TOKENS_ATTACHED_GETTER(DashboardTokens, dashboard) +TOKENS_ATTACHED_GETTER(LauncherTokens, launcher) +TOKENS_ATTACHED_GETTER(NotifsTokens, notifs) +TOKENS_ATTACHED_GETTER(OsdTokens, osd) +TOKENS_ATTACHED_GETTER(SessionTokens, session) +TOKENS_ATTACHED_GETTER(SidebarTokens, sidebar) +TOKENS_ATTACHED_GETTER(UtilitiesTokens, utilities) +TOKENS_ATTACHED_GETTER(LockTokens, lock) +TOKENS_ATTACHED_GETTER(WInfoTokens, winfo) +TOKENS_ATTACHED_GETTER(ControlCenterTokens, controlCenter) + +#undef TOKENS_ATTACHED_GETTER Tokens* Tokens::qmlAttachedProperties(QObject* object) { return new Tokens(ConfigScope::find(object), object); diff --git a/plugin/src/Caelestia/Config/tokens.hpp b/plugin/src/Caelestia/Config/tokens.hpp index a3ea6f69..9ab1ddc3 100644 --- a/plugin/src/Caelestia/Config/tokens.hpp +++ b/plugin/src/Caelestia/Config/tokens.hpp @@ -317,23 +317,57 @@ public: ~TokenConfig() override; private: + friend class MonitorConfigManager; explicit TokenConfig(QObject* parent = nullptr); + explicit TokenConfig(TokenConfig* fallback, const QString& filePath, QObject* parent); }; class Tokens : public QObject { Q_OBJECT + Q_MOC_INCLUDE("configscope.hpp") QML_ELEMENT QML_UNCREATABLE("") QML_ATTACHED(Tokens) + Q_PROPERTY(ConfigScope* scope READ scope NOTIFY sourceChanged) + Q_PROPERTY(const AppearanceTokens* appearance READ appearance NOTIFY sourceChanged) + Q_PROPERTY(const BarTokens* bar READ bar NOTIFY sourceChanged) + Q_PROPERTY(const DashboardTokens* dashboard READ dashboard NOTIFY sourceChanged) + Q_PROPERTY(const LauncherTokens* launcher READ launcher NOTIFY sourceChanged) + Q_PROPERTY(const NotifsTokens* notifs READ notifs NOTIFY sourceChanged) + Q_PROPERTY(const OsdTokens* osd READ osd NOTIFY sourceChanged) + Q_PROPERTY(const SessionTokens* session READ session NOTIFY sourceChanged) + Q_PROPERTY(const SidebarTokens* sidebar READ sidebar NOTIFY sourceChanged) + Q_PROPERTY(const UtilitiesTokens* utilities READ utilities NOTIFY sourceChanged) + Q_PROPERTY(const LockTokens* lock READ lock NOTIFY sourceChanged) + Q_PROPERTY(const WInfoTokens* winfo READ winfo NOTIFY sourceChanged) + Q_PROPERTY(const ControlCenterTokens* controlCenter READ controlCenter NOTIFY sourceChanged) + public: explicit Tokens(ConfigScope* scope, QObject* parent = nullptr); [[nodiscard]] ConfigScope* scope() const { return m_scope; } + [[nodiscard]] const AppearanceTokens* appearance() const; + [[nodiscard]] const BarTokens* bar() const; + [[nodiscard]] const DashboardTokens* dashboard() const; + [[nodiscard]] const LauncherTokens* launcher() const; + [[nodiscard]] const NotifsTokens* notifs() const; + [[nodiscard]] const OsdTokens* osd() const; + [[nodiscard]] const SessionTokens* session() const; + [[nodiscard]] const SidebarTokens* sidebar() const; + [[nodiscard]] const UtilitiesTokens* utilities() const; + [[nodiscard]] const LockTokens* lock() const; + [[nodiscard]] const WInfoTokens* winfo() const; + [[nodiscard]] const ControlCenterTokens* controlCenter() const; + static Tokens* qmlAttachedProperties(QObject* object); + Q_SIGNAL void sourceChanged(); + private: + void connectScope(); + ConfigScope* m_scope; };