138 lines
3.6 KiB
C++
138 lines
3.6 KiB
C++
#include "Config.hpp"
|
|
|
|
#include <QFile>
|
|
#include <QSaveFile>
|
|
#include <QJsonDocument>
|
|
#include <QStandardPaths>
|
|
#include <QDir>
|
|
#include <QDebug>
|
|
#include <QFutureWatcher>
|
|
|
|
Config::Config(QObject* parent) : ConfigSection(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_overview = new Overview(this);
|
|
m_screenshot = new Screenshot(this);
|
|
m_services = new Services(this);
|
|
m_sidebar = new Sidebar(this);
|
|
m_utilities = new Utilities(this);
|
|
wireSignals(); // forwards every child's changed() up to our own changed()
|
|
|
|
connect(this, &ConfigSection::changed, this, &Config::scheduleSave);
|
|
|
|
m_saveTimer.setSingleShot(true);
|
|
m_saveTimer.setInterval(
|
|
300); // debounce: coalesce rapid-fire edits (e.g. dragging a slider)
|
|
connect(&m_saveTimer, &QTimer::timeout, this, &Config::flushAsync);
|
|
|
|
loadSync();
|
|
m_firstLoadDone = true;
|
|
}
|
|
|
|
Config* Config::create(QQmlEngine*, QJSEngine*) {
|
|
// The QML engine takes ownership of singletons created this way.
|
|
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; // suppress scheduleSave() while properties are set from disk
|
|
QFile f(filePath());
|
|
if (f.open(QIODevice::ReadOnly)) {
|
|
const auto doc = QJsonDocument::fromJson(f.readAll());
|
|
if (doc.isObject()) fromJson(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]() {
|
|
fromJson(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(); // restarts the window if it's already running
|
|
}
|
|
|
|
void Config::flushAsync() {
|
|
auto future = QtConcurrent::run([this]() {
|
|
const QByteArray data = QJsonDocument(toJson()).toJson(QJsonDocument::Indented);
|
|
writeAtomically(data);
|
|
});
|
|
}
|
|
|
|
void Config::saveNow() {
|
|
m_saveTimer.stop();
|
|
flushAsync();
|
|
}
|
|
|
|
void Config::writeAtomically(const QByteArray& data) {
|
|
// QSaveFile writes to a temp file next to the target and atomically
|
|
// renames it into place on commit() - a crash or power loss mid-write
|
|
// can never leave config.json half-written.
|
|
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();
|
|
}
|