feat: add per monitor configs

Per monitor configs go in a separate file in the monitor dir, e.g.
`monitors/<MONITOR_NAME>/shell.json`
This commit is contained in:
2 * r + 2 * t 2026-04-11 18:09:21 +10:00
parent a932a68a40
commit 71f27bb45f
11 changed files with 556 additions and 5 deletions

View file

@ -6,6 +6,7 @@ qml_module(caelestia-config
configscope.cpp
appearanceconfig.cpp
tokens.cpp
monitorconfigmanager.cpp
backgroundconfig.hpp
barconfig.hpp
borderconfig.hpp

View file

@ -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);

View file

@ -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;
};

View file

@ -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<ConfigObject*>()) {
auto* const subObj = value.value<ConfigObject*>();
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<ConfigObject*>();
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<ConfigObject*>();
if (subObj) {
auto globalVal = prop.read(global);
auto* globalSub = globalVal.value<ConfigObject*>();
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<ConfigObject*>();
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<QString, QVariant>& 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() {

View file

@ -2,9 +2,12 @@
#include <qfilesystemwatcher.h>
#include <qjsonobject.h>
#include <qmap.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qset.h>
#include <qtimer.h>
#include <qvariant.h>
// 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 <typename T> static bool updateMember(T& member, const T& value) {
@ -66,15 +84,30 @@ public:
return true;
}
Q_SIGNAL void propertiesChanged(const QMap<QString, QVariant>& changed);
protected:
void notifyPropertyChanged(const QString& name, const QVariant& value);
private:
void onFileChanged();
void onGlobalPropertiesChanged(const QMap<QString, QVariant>& 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<QString> m_loadedKeys;
QMap<QString, QVariant> m_pendingChanges;
QTimer* m_batchTimer = nullptr;
};
} // namespace caelestia::config

View file

@ -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<QQuickItem*>(object);

View file

@ -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

View file

@ -0,0 +1,69 @@
#include "monitorconfigmanager.hpp"
#include "config.hpp"
#include "tokens.hpp"
#include <qstandardpaths.h>
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

View file

@ -0,0 +1,38 @@
#pragma once
#include <qhash.h>
#include <qobject.h>
#include <qqmlengine.h>
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<QString, ScreenOverlay> m_overlays;
static MonitorConfigManager* s_instance;
};
} // namespace caelestia::config

View file

@ -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);

View file

@ -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;
};