Merge branch 'main' into feat/fullscreen-reveal-bar
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 18s
JS/TS / lint (pull_request) Successful in 18s
Python / fmt (pull_request) Successful in 34s
Python / lint (pull_request) Successful in 32s
Python / test (pull_request) Successful in 1m1s
Python / typecheck (pull_request) Successful in 1m21s
Rust / fmt (pull_request) Successful in 33s
C++ / build (pull_request) Successful in 2m17s
Rust / build (pull_request) Successful in 1m43s
Python / buildcheck (pull_request) Successful in 2m26s
Rust / clippy (pull_request) Successful in 1m39s
C++ / clang-tidy (pull_request) Successful in 7m58s
C++ / fmt (pull_request) Successful in 5s
JS/TS / fmt (pull_request) Successful in 18s
JS/TS / lint (pull_request) Successful in 18s
Python / fmt (pull_request) Successful in 34s
Python / lint (pull_request) Successful in 32s
Python / test (pull_request) Successful in 1m1s
Python / typecheck (pull_request) Successful in 1m21s
Rust / fmt (pull_request) Successful in 33s
C++ / build (pull_request) Successful in 2m17s
Rust / build (pull_request) Successful in 1m43s
Python / buildcheck (pull_request) Successful in 2m26s
Rust / clippy (pull_request) Successful in 1m39s
C++ / clang-tidy (pull_request) Successful in 7m58s
This commit is contained in:
+4
-4
@@ -126,7 +126,7 @@ MouseArea {
|
||||
CustomRect {
|
||||
id: item
|
||||
|
||||
readonly property bool active: modelData === root.active
|
||||
readonly property bool active: modelData === root?.active
|
||||
required property int index
|
||||
required property MenuItem modelData
|
||||
|
||||
@@ -163,19 +163,19 @@ MouseArea {
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurfaceVariant
|
||||
text: item.modelData.icon
|
||||
text: item.modelData?.icon ?? ""
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
color: item.active ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
|
||||
text: item.modelData.text
|
||||
text: item.modelData?.text ?? ""
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
active: item.modelData.trailingIcon.length > 0
|
||||
active: item.modelData?.trailingIcon.length > 0
|
||||
asynchronous: true
|
||||
visible: active
|
||||
|
||||
|
||||
@@ -318,8 +318,6 @@ Item {
|
||||
root.visibilities.sidebar = false;
|
||||
root.panels.popouts.hasCurrent = false;
|
||||
root.visibilities.launcher = false;
|
||||
} else {
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,6 @@ Singleton {
|
||||
|
||||
Config.dock.pinnedApps = pinnedApps;
|
||||
root.unpinnedOrder = visibleUnpinned.concat(root.unpinnedOrder.map(normalizeId).filter(id => !pinnedApps.includes(id) && !visibleUnpinned.includes(id)));
|
||||
Config.saveNoToast();
|
||||
}
|
||||
|
||||
function isPinned(appId) {
|
||||
|
||||
+8
-41
@@ -11,8 +11,8 @@ Singleton {
|
||||
id: root
|
||||
|
||||
property int availableUpdates: 0
|
||||
property string cmd: ""
|
||||
property bool commandReady
|
||||
property bool hasHelper
|
||||
property bool loaded
|
||||
property double now: Date.now()
|
||||
property var updates: ({})
|
||||
@@ -38,18 +38,12 @@ Singleton {
|
||||
}
|
||||
|
||||
function performPackageUpdate(pkg: string): void {
|
||||
if (root.cmd === "pacman")
|
||||
pkgUpdateProc.command = ["pkexec", root.cmd, "--noconfirm", "-Sy", pkg];
|
||||
else
|
||||
pkgUpdateProc.command = [root.cmd, "--noconfirm", "--sudo", "pkexec", "-Sy", pkg];
|
||||
pkgUpdateProc.command = ["pkexec", "pacman", "--noconfirm", "-Sy", pkg];
|
||||
pkgUpdateProc.running = true;
|
||||
}
|
||||
|
||||
function performSystemUpdate(): void {
|
||||
if (root.cmd === "pacman")
|
||||
sysUpdateProc.command = ["pkexec", root.cmd, "--noconfirm", "-Syu"];
|
||||
else
|
||||
sysUpdateProc.command = [root.cmd, "--noconfirm", "--sudo", "pkexec", "-Syu"];
|
||||
sysUpdateProc.command = ["pkexec", "pacman", "--noconfirm", "-Syu"];
|
||||
sysUpdateProc.running = true;
|
||||
}
|
||||
|
||||
@@ -87,7 +81,7 @@ Singleton {
|
||||
Process {
|
||||
id: cmdDetect
|
||||
|
||||
command: ["sh", "-c", "command -v checkupdates || command -v yay || command -v paru"]
|
||||
command: ["sh", "-c", "command -v checkupdates"]
|
||||
running: true
|
||||
|
||||
stdout: StdioCollector {
|
||||
@@ -96,47 +90,20 @@ Singleton {
|
||||
let helper;
|
||||
|
||||
if (cmd.length > 0) {
|
||||
helper = cmd.split("/").pop();
|
||||
helper = true;
|
||||
} else {
|
||||
helper = "pacman";
|
||||
}
|
||||
|
||||
if (helper === "checkupdates") {
|
||||
updatesProc.command = [helper];
|
||||
} else {
|
||||
updatesProc.command = [helper, "-Qu"];
|
||||
helper = false;
|
||||
}
|
||||
root.hasHelper = helper;
|
||||
root.commandReady = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: updateCmdDetect
|
||||
|
||||
command: ["sh", "-c", "command -v yay || command -v paru"]
|
||||
running: true
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const cmd = this.text.trim();
|
||||
let helper;
|
||||
|
||||
if (cmd.length > 0) {
|
||||
helper = cmd.split("/").pop();
|
||||
} else {
|
||||
helper = "pacman";
|
||||
}
|
||||
|
||||
root.cmd = helper;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: updatesProc
|
||||
|
||||
command: []
|
||||
command: root.hasHelper ? ["checkupdates"] : []
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
|
||||
@@ -78,8 +78,6 @@ CustomClippingRect {
|
||||
required property var modelData
|
||||
readonly property list<string> sections: modelData.update.split(" ")
|
||||
|
||||
// anchors.left: parent.left
|
||||
// anchors.right: parent.right
|
||||
color: Colors.tPalette.m3surfaceContainer
|
||||
implicitHeight: root.itemHeight
|
||||
implicitWidth: 600
|
||||
|
||||
@@ -82,7 +82,6 @@ Searcher {
|
||||
list.visibilities.launcher = false;
|
||||
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", variant]);
|
||||
Config.colors.schemeType = variant;
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,10 +70,7 @@ PageBase {
|
||||
}
|
||||
]
|
||||
|
||||
onSelected: item => {
|
||||
Config.screenshot.mode = item.value;
|
||||
Config.save();
|
||||
}
|
||||
onSelected: item => Config.screenshot.mode = item.value
|
||||
}
|
||||
|
||||
SectionHeader {
|
||||
|
||||
@@ -104,7 +104,6 @@ PageBase {
|
||||
onApplySettings: (start, end) => {
|
||||
Config.general.color.scheduleDarkStart = start;
|
||||
Config.general.color.scheduleDarkEnd = end;
|
||||
Config.save();
|
||||
ModeScheduler.checkStartup();
|
||||
PopupManager.requestClose();
|
||||
}
|
||||
@@ -146,7 +145,6 @@ PageBase {
|
||||
onApplySettings: (start, end) => {
|
||||
Config.general.color.scheduleHyprsunsetStart = start;
|
||||
Config.general.color.scheduleHyprsunsetEnd = end;
|
||||
Config.save();
|
||||
Hyprsunset.checkStartup();
|
||||
PopupManager.requestClose();
|
||||
}
|
||||
|
||||
@@ -95,15 +95,31 @@ QString Config::filePath() const {
|
||||
|
||||
void Config::loadSync() {
|
||||
m_loading = true;
|
||||
|
||||
QFile f(filePath());
|
||||
QJsonObject before;
|
||||
|
||||
bool existed = false;
|
||||
|
||||
if (f.open(QIODevice::ReadOnly)) {
|
||||
const auto doc = QJsonDocument::fromJson(f.readAll());
|
||||
if (doc.isObject()) loadFromJson(QJsonValue(doc.object()));
|
||||
if (doc.isObject()) {
|
||||
before = doc.object();
|
||||
existed = true;
|
||||
} else {
|
||||
qInfo() << "Config: existing config at" << filePath()
|
||||
<< "is empty or not a valid JSON object - using defaults";
|
||||
}
|
||||
} else {
|
||||
qInfo() << "Config: no existing config at" << filePath()
|
||||
<< "- using defaults";
|
||||
}
|
||||
|
||||
loadFromJson(QJsonValue(before));
|
||||
m_loading = false;
|
||||
|
||||
const auto after = toJson().toObject();
|
||||
if (!existed || after != before) saveNow();
|
||||
}
|
||||
|
||||
void Config::updateWatch() {
|
||||
@@ -148,27 +164,21 @@ void Config::loadAsync() {
|
||||
|
||||
if (f.open(QIODevice::ReadOnly)) {
|
||||
const auto doc = QJsonDocument::fromJson(f.readAll());
|
||||
const bool valid = doc.isObject();
|
||||
const QJsonObject before = valid ? doc.object() : QJsonObject();
|
||||
|
||||
if (doc.isObject()) {
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this, doc]() {
|
||||
loadFromJson(QJsonValue(doc.object()));
|
||||
m_loading = false;
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this, valid, before]() {
|
||||
loadFromJson(QJsonValue(before));
|
||||
m_loading = false;
|
||||
|
||||
if (m_reloadPending) m_reloadTimer.start();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
} else {
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this]() {
|
||||
m_loading = false;
|
||||
const auto after = toJson().toObject();
|
||||
if (!valid || after != before) saveNow();
|
||||
|
||||
if (m_reloadPending) m_reloadTimer.start();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
if (m_reloadPending) m_reloadTimer.start();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
} else {
|
||||
qInfo() << "Config: failed to reload from" << filePath()
|
||||
<< "- using in-memory values";
|
||||
|
||||
@@ -79,13 +79,14 @@ void ConfigList::loadFromJson(const QJsonValue& json) {
|
||||
if (!json.isArray()) {
|
||||
qCWarning(
|
||||
lcConfig,
|
||||
"Option '%s' must be a list, ignoring",
|
||||
"Option '%s' must be a list, resetting to default",
|
||||
qUtf8Printable(propertyPath()));
|
||||
m_rejectedJson = json;
|
||||
|
||||
resetToDefaults();
|
||||
materializeDefaults();
|
||||
return;
|
||||
}
|
||||
|
||||
m_rejectedJson = QJsonValue::Undefined;
|
||||
populate(json.toArray());
|
||||
m_loaded = true;
|
||||
}
|
||||
@@ -93,12 +94,11 @@ void ConfigList::loadFromJson(const QJsonValue& json) {
|
||||
QJsonValue ConfigList::toJson() const {
|
||||
if (m_loaded) return elementsToJson();
|
||||
|
||||
return m_rejectedJson;
|
||||
return QJsonValue::Undefined;
|
||||
}
|
||||
|
||||
void ConfigList::clearLoadedKeys() {
|
||||
m_loaded = false;
|
||||
m_rejectedJson = QJsonValue::Undefined;
|
||||
}
|
||||
|
||||
QStringList ConfigList::unknownKeys() const {
|
||||
@@ -113,6 +113,15 @@ QStringList ConfigList::unknownKeys() const {
|
||||
return keys;
|
||||
}
|
||||
|
||||
void ConfigList::materializeDefaults() {
|
||||
for (auto* const item : m_items)
|
||||
item->materializeDefaults();
|
||||
|
||||
if (m_global) return;
|
||||
|
||||
m_loaded = true;
|
||||
}
|
||||
|
||||
void ConfigList::resyncFromGlobal() {
|
||||
syncValuesFromGlobal();
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class ConfigList : public ConfigNode {
|
||||
[[nodiscard]] QJsonValue toJson() const override;
|
||||
void clearLoadedKeys() override;
|
||||
[[nodiscard]] QStringList unknownKeys() const override;
|
||||
void materializeDefaults() override;
|
||||
void resyncFromGlobal() override;
|
||||
|
||||
signals:
|
||||
@@ -71,7 +72,6 @@ class ConfigList : public ConfigNode {
|
||||
QJsonArray m_defaults;
|
||||
QList<ConfigObject*> m_items;
|
||||
bool m_loaded = false;
|
||||
QJsonValue m_rejectedJson = QJsonValue::Undefined;
|
||||
};
|
||||
|
||||
} // namespace ZShell::config
|
||||
@@ -103,7 +103,6 @@ class ConfigList : public ConfigNode {
|
||||
} \
|
||||
};
|
||||
|
||||
|
||||
#define CONFIG_LIST(Type, name, ...) \
|
||||
Q_PROPERTY(ZShell::config::Type* name READ name CONSTANT) \
|
||||
\
|
||||
@@ -115,6 +114,8 @@ class ConfigList : public ConfigNode {
|
||||
private: \
|
||||
Type* m_##name = new Type(this __VA_OPT__(, __VA_ARGS__));
|
||||
|
||||
#define LIST_ENTRY(id, enabled) \
|
||||
vmap({{"id", QString::fromUtf8(#id)}, {"enabled", enabled}})
|
||||
|
||||
namespace ZShell::config {
|
||||
|
||||
@@ -136,6 +137,3 @@ class ListEntry : public ConfigObject {
|
||||
CONFIG_LIST_TYPE(ListEntry, EntryList)
|
||||
|
||||
} // namespace ZShell::config
|
||||
|
||||
#define LIST_ENTRY(id, enabled) \
|
||||
vmap({{"id", QString::fromUtf8(#id)}, {"enabled", enabled}})
|
||||
|
||||
@@ -26,6 +26,8 @@ class ConfigNode : public QObject {
|
||||
[[nodiscard]] virtual QStringList unknownKeys() const = 0;
|
||||
[[nodiscard]] virtual QList<ConfigNode*> childNodes() const;
|
||||
|
||||
virtual void materializeDefaults() = 0;
|
||||
|
||||
void syncFromGlobal(ConfigNode* global);
|
||||
virtual void resyncFromGlobal() = 0;
|
||||
|
||||
|
||||
@@ -7,6 +7,51 @@
|
||||
|
||||
namespace ZShell::config {
|
||||
|
||||
namespace {
|
||||
|
||||
bool isStringArray(const QJsonArray& arr) {
|
||||
for (const auto& v : arr) {
|
||||
if (!v.isString()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool jsonValueMatchesType(const QJsonValue& val, QMetaType type) {
|
||||
switch (val.type()) {
|
||||
case QJsonValue::Bool:
|
||||
return type.id() == QMetaType::Bool;
|
||||
|
||||
case QJsonValue::Double:
|
||||
switch (type.id()) {
|
||||
case QMetaType::Int:
|
||||
case QMetaType::UInt:
|
||||
case QMetaType::LongLong:
|
||||
case QMetaType::ULongLong:
|
||||
case QMetaType::Double:
|
||||
case QMetaType::Float:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
case QJsonValue::String:
|
||||
return type.id() == QMetaType::QString;
|
||||
|
||||
case QJsonValue::Array:
|
||||
if (type.id() == QMetaType::QStringList)
|
||||
return isStringArray(val.toArray());
|
||||
return type.id() == QMetaType::QVariantList;
|
||||
|
||||
case QJsonValue::Object:
|
||||
case QJsonValue::Null:
|
||||
case QJsonValue::Undefined:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ConfigObject::ConfigObject(QObject* parent) : ConfigNode(parent) {}
|
||||
|
||||
void ConfigObject::loadFromJson(const QJsonValue& json) {
|
||||
@@ -14,6 +59,7 @@ void ConfigObject::loadFromJson(const QJsonValue& json) {
|
||||
const auto* meta = metaObject();
|
||||
|
||||
QSet<QString> known;
|
||||
QSet<QString> invalid;
|
||||
|
||||
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
|
||||
auto prop = meta->property(i);
|
||||
@@ -34,6 +80,15 @@ void ConfigObject::loadFromJson(const QJsonValue& json) {
|
||||
|
||||
if (!prop.isWritable()) continue;
|
||||
|
||||
if (!jsonValueMatchesType(jsonVal, prop.metaType())) {
|
||||
qWarning() << "Config: type mismatch for" << key << "in"
|
||||
<< meta->className() << "- expected"
|
||||
<< prop.metaType().name() << "got value" << jsonVal
|
||||
<< "- resetting to default";
|
||||
invalid.insert(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prop.metaType().id() == QMetaType::QStringList) {
|
||||
QStringList list;
|
||||
const auto jsonArr = jsonVal.toArray();
|
||||
@@ -56,10 +111,12 @@ void ConfigObject::loadFromJson(const QJsonValue& json) {
|
||||
|
||||
m_extras = {};
|
||||
for (auto it = obj.begin(); it != obj.end(); ++it) {
|
||||
if (!known.contains(it.key())) {
|
||||
if (!known.contains(it.key()) || invalid.contains(it.key())) {
|
||||
m_extras.insert(it.key(), it.value());
|
||||
}
|
||||
}
|
||||
|
||||
materializeDefaults();
|
||||
}
|
||||
|
||||
QJsonValue ConfigObject::toJson() const {
|
||||
@@ -104,9 +161,6 @@ QJsonValue ConfigObject::toJson() const {
|
||||
obj.insert(key, QJsonValue::fromVariant(value));
|
||||
}
|
||||
|
||||
for (auto it = m_extras.begin(); it != m_extras.end(); ++it)
|
||||
obj.insert(it.key(), it.value());
|
||||
|
||||
if (obj.isEmpty()) return QJsonValue::Undefined;
|
||||
|
||||
return obj;
|
||||
@@ -159,6 +213,26 @@ QList<ConfigNode*> ConfigObject::childNodes() const {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
void ConfigObject::materializeDefaults() {
|
||||
const auto* meta = metaObject();
|
||||
|
||||
for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) {
|
||||
const auto prop = meta->property(i);
|
||||
const auto key = QString::fromUtf8(prop.name());
|
||||
|
||||
if (auto* const node = prop.read(this).value<ConfigNode*>()) {
|
||||
node->materializeDefaults();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!prop.isWritable()) continue;
|
||||
|
||||
if (m_global) continue;
|
||||
|
||||
m_loadedKeys.insert(key);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigObject::syncValuesFromGlobal() {
|
||||
const auto* meta = metaObject();
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ class ConfigObject : public ConfigNode {
|
||||
void clearLoadedKeys() override;
|
||||
[[nodiscard]] QStringList unknownKeys() const override;
|
||||
[[nodiscard]] QList<ConfigNode*> childNodes() const override;
|
||||
void materializeDefaults() override;
|
||||
void resyncFromGlobal() override;
|
||||
|
||||
[[nodiscard]] virtual QStringList identityKeys() const;
|
||||
|
||||
@@ -113,7 +113,6 @@ Singleton {
|
||||
function setMode(mode: string): void {
|
||||
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]);
|
||||
Config.general.color.mode = mode;
|
||||
Config.save();
|
||||
}
|
||||
|
||||
function swapRG(c: color): color {
|
||||
|
||||
Reference in New Issue
Block a user