In Quickshell, singletons (like Weather.qml and NetworkUsage.qml) run in a volatile QML engine. This leads to two specific issues:
- Weather resets: On boot or shell reload, the weather widget displays
0°C / No weatheruntil the next HTTP fetch completes. - Network usage resets: Total download/upload metrics in the performance panel fall back to 0 bytes because initial session bytes (calculated from
/proc/net/dev) are cleared.
Quickshell provides the FileView type for reading files, but it is read-only and its .text property must be accessed as a function: text().
To write cache updates to disk safely in background threads without blocking QML animations, I employ a one-liner Python process launched asynchronously via Quickshell.execDetached().
Here is how the caching mechanisms are structured for both components:
1. Asynchronous Write & Read Loop (Weather)
Inside Weather.qml, the JSON string retrieved from the API is written asynchronously using Python. On component completion, FileView loads the cached data before the new network request triggers:
// Writing to cache
Quickshell.execDetached(["python3", "-c",
"import sys; open(sys.argv[1], 'w').write(sys.argv[2])",
cachePath, jsonPayload
]);
// Reading from cache
FileView {
id: cacheReader
source: "file://" + cachePath
onTextChanged: {
const data = JSON.parse(text());
root.cc = data.cc;
root.forecast = data.forecast;
}
}
2. High I/O Avoidance & Monthly Cycles (Network Usage)
Since /proc/net/dev updates every second, writing network usage to disk immediately would cause high disk write amplification. To solve this:
- I use a 30-second Timer to periodically dump network statistics to
network_usage.json. - A monthly validation check is implemented. The cache is only validated if the stored date matches the current system month (
YYYY-MM), implementing automatic monthly resets.
// Inside NetworkUsage.qml
Timer {
interval: 30000 // 30 seconds
repeat: true
running: true
onTriggered: {
let serialized = {
month: getCurrentMonthString(),
downloadTotal: root.downloadTotal,
uploadTotal: root.uploadTotal
};
saveCache(JSON.stringify(serialized));
}
}