blob: d3fb4e26248ab62b110eb5ecf86c4167226ff965 (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
pragma Singleton
pragma ComponentBehavior: Bound
import "root:/modules/common"
import "root:/modules/common/functions/file_utils.js" as FileUtils
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/object_utils.js" as ObjectUtils
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import Qt.labs.platform
/**
* Loads and manages the shell configuration file.
* The config file is by default at XDG_CONFIG_HOME/quickshell/config.json.
* Automatically reloaded when the file changes, but does not provide a way to save changes.
*/
Singleton {
id: root
property string filePath: Directories.shellConfigPath
property bool firstLoad: true
function loadConfig() {
configFileView.reload()
}
function applyConfig(fileContent) {
try {
const json = JSON.parse(fileContent);
// Extract font configuration if it exists
let fontConfig = null;
let configForOptions = {};
// Copy all properties except font to configForOptions
for (let key in json) {
if (key !== "font") {
configForOptions[key] = json[key];
} else {
fontConfig = json[key];
}
}
// Apply the non-font configuration to ConfigOptions
ObjectUtils.applyToQtObject(ConfigOptions, configForOptions);
// Apply font configuration to Appearance if it exists
if (fontConfig && typeof Appearance !== 'undefined') {
if (fontConfig.family && Appearance.font && Appearance.font.family) {
ObjectUtils.applyToQtObject(Appearance.font.family, fontConfig.family);
}
if (fontConfig.pixelSize && Appearance.font && Appearance.font.pixelSize) {
ObjectUtils.applyToQtObject(Appearance.font.pixelSize, fontConfig.pixelSize);
}
}
if (root.firstLoad) {
root.firstLoad = false;
} else {
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`)
}
} catch (e) {
console.error("[ConfigLoader] Error reading file:", e);
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
return;
}
}
function setLiveConfigValue(nestedKey, value) {
let keys = nestedKey.split(".");
let targetObject = ConfigOptions;
// Check if this is a font-related configuration
if (keys[0] === "font") {
targetObject = Appearance;
}
let obj = targetObject;
let parents = [obj];
// Traverse and collect parent objects
for (let i = 0; i < keys.length - 1; ++i) {
if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") {
obj[keys[i]] = {};
}
obj = obj[keys[i]];
parents.push(obj);
}
// Convert value to correct type using JSON.parse when safe
let convertedValue = value;
if (typeof value === "string") {
let trimmed = value.trim();
if (trimmed === "true" || trimmed === "false" || !isNaN(Number(trimmed))) {
try {
convertedValue = JSON.parse(trimmed);
} catch (e) {
convertedValue = value;
}
}
}
console.log(`[ConfigLoader] Setting live config value: ${nestedKey} = ${convertedValue}`);
obj[keys[keys.length - 1]] = convertedValue;
}
function saveConfig() {
const plainConfig = ObjectUtils.toPlainObject(ConfigOptions);
Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(JSON.stringify(plainConfig, null, 2))}' > '${root.filePath}'`)
}
Timer {
id: delayedFileRead
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
repeat: false
running: false
onTriggered: {
root.applyConfig(configFileView.text())
}
}
FileView {
id: configFileView
path: Qt.resolvedUrl(root.filePath)
watchChanges: true
onFileChanged: {
console.log("[ConfigLoader] File changed, reloading...")
this.reload()
delayedFileRead.start()
}
onLoadedChanged: {
const fileContent = configFileView.text()
root.applyConfig(fileContent)
}
onLoadFailed: (error) => {
if(error == FileViewError.FileNotFound) {
console.log("[ConfigLoader] File not found, creating new file.")
root.saveConfig()
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration created")}" "${root.filePath}"`)
} else {
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
}
}
}
}
|