#include "config.hpp" #include "appearance.hpp" #include "background.hpp" #include "bar.hpp" #include "clipboard.hpp" #include "colors.hpp" #include "dashboard.hpp" #include "dock.hpp" #include "general.hpp" #include "launcher.hpp" #include "lock.hpp" #include "notifs.hpp" #include "osd.hpp" #include "screenshot.hpp" #include "services.hpp" #include "sidebar.hpp" #include "utilities.hpp" #include #include #include #include #include #include #include namespace ZShell::config { Config::Config(QObject* parent) : ConfigObject(parent) , m_appearance(new Appearance(this)) , m_background(new Background(this)) , m_bar(new Bar(this)) , m_clipboard(new Clipboard(this)) , m_colors(new Colors(this)) , m_dashboard(new Dashboard(this)) , m_dock(new Dock(this)) , m_general(new General(this)) , m_launcher(new Launcher(this)) , m_lock(new Lock(this)) , m_notifs(new Notifs(this)) , m_osd(new Osd(this)) , m_screenshot(new Screenshot(this)) , m_services(new Services(this)) , m_sidebar(new Sidebar(this)) , m_utilities(new Utilities(this)) { connect(this, &ConfigObject::propertiesChanged, this, &Config::scheduleSave); m_saveTimer.setSingleShot(true); m_saveTimer.setInterval(300); connect(&m_saveTimer, &QTimer::timeout, this, &Config::flushAsync); loadSync(); m_firstLoadDone = true; } Config* Config::create(QQmlEngine*, QJSEngine*) { return new Config(); } QString Config::filePath() const { const QString dir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/zshell"); QDir().mkpath(dir); return dir + QStringLiteral("/config.json"); } void Config::loadSync() { m_loading = true; QFile f(filePath()); if (f.open(QIODevice::ReadOnly)) { const auto doc = QJsonDocument::fromJson(f.readAll()); if (doc.isObject()) loadFromJson(QJsonValue(doc.object())); } else { qInfo() << "Config: no existing config at" << filePath() << "- using defaults"; } m_loading = false; } void Config::loadAsync() { m_loading = true; auto future = QtConcurrent::run([this]() { QFile f(filePath()); if (f.open(QIODevice::ReadOnly)) { const auto doc = QJsonDocument::fromJson(f.readAll()); if (doc.isObject()) { QMetaObject::invokeMethod( this, [this, doc]() { loadFromJson(QJsonValue(doc.object())); m_loading = false; }, Qt::QueuedConnection); } else { QMetaObject::invokeMethod( this, [this]() { m_loading = false; }, Qt::QueuedConnection); } } else { qInfo() << "Config: failed to reload from" << filePath() << "- using in-memory values"; QMetaObject::invokeMethod( this, [this]() { m_loading = false; }, Qt::QueuedConnection); } }); m_loadFuture = future; } void Config::load() { if (!m_firstLoadDone) { loadSync(); } else { loadAsync(); } } void Config::reloadAsync() { loadAsync(); } void Config::scheduleSave() { if (m_loading) return; m_saveTimer.start(); } void Config::flushAsync() { auto future = QtConcurrent::run([this]() { const QByteArray data = QJsonDocument(toJson().toObject()).toJson(QJsonDocument::Indented); writeAtomically(data); }); } void Config::saveNow() { m_saveTimer.stop(); flushAsync(); } void Config::writeAtomically(const QByteArray& data) { QSaveFile f(filePath()); if (!f.open(QIODevice::WriteOnly)) { qWarning() << "Config: failed to open for atomic write:" << f.errorString(); return; } f.write(data); if (!f.commit()) qWarning() << "Config: commit failed:" << f.errorString(); } }; // namespace ZShell::config