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:
parent
a932a68a40
commit
71f27bb45f
11 changed files with 556 additions and 5 deletions
|
|
@ -6,6 +6,7 @@ qml_module(caelestia-config
|
|||
configscope.cpp
|
||||
appearanceconfig.cpp
|
||||
tokens.cpp
|
||||
monitorconfigmanager.cpp
|
||||
backgroundconfig.hpp
|
||||
barconfig.hpp
|
||||
borderconfig.hpp
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
69
plugin/src/Caelestia/Config/monitorconfigmanager.cpp
Normal file
69
plugin/src/Caelestia/Config/monitorconfigmanager.cpp
Normal 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
|
||||
38
plugin/src/Caelestia/Config/monitorconfigmanager.hpp
Normal file
38
plugin/src/Caelestia/Config/monitorconfigmanager.hpp
Normal 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
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue