115 lines
4.3 KiB
QML
115 lines
4.3 KiB
QML
pragma Singleton
|
|
|
|
// What this desktop remembers about what you opened.
|
|
//
|
|
// Two traces -- the recent-files list and the thumbnail cache -- both of them a
|
|
// normal and useful part of a desktop rather than a problem. This measures them
|
|
// when asked and clears them when asked, and does neither on its own.
|
|
//
|
|
// Nothing here measures on startup. Walking a thumbnail cache costs real time
|
|
// on a machine that has browsed a large picture library, and a settings page
|
|
// that has not been opened has no business spending it. `measured` says whether
|
|
// there is an answer yet, so the card can show its own state honestly instead of
|
|
// showing a confident zero.
|
|
//
|
|
// Trash is deliberately absent. It is measured and emptied by Disks, and one
|
|
// trash implementation is the right number to have.
|
|
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import QtQuick
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
readonly property string helperPath: Quickshell.env("PANAMA_PRIVACY_HELPER")
|
|
|| Quickshell.shellDir + "/scripts/panama-privacy"
|
|
|
|
// real rather than int: a thumbnail cache on a machine with a large picture
|
|
// library goes past what a 32-bit int holds, and a byte count that wrapped
|
|
// negative is worse than no byte count.
|
|
property real recentsBytes: 0
|
|
property int recentsEntries: 0
|
|
property string recentsPath: ""
|
|
property bool recentsPresent: false
|
|
|
|
property real thumbnailsBytes: 0
|
|
property string thumbnailsPath: ""
|
|
property bool thumbnailsPresent: false
|
|
|
|
// False when the walk ran out of time, which makes the byte count a floor
|
|
// rather than an answer.
|
|
property bool thumbnailsComplete: true
|
|
|
|
// Whether there is an answer at all yet. Distinct from thumbnailsComplete,
|
|
// which is about the quality of an answer that exists.
|
|
property bool measured: false
|
|
|
|
property string lastError: ""
|
|
|
|
readonly property bool busy: reader.running || mutation.running
|
|
|
|
function measure(): void {
|
|
if (reader.running)
|
|
return;
|
|
reader.command = [root.helperPath, "traces"];
|
|
reader.running = true;
|
|
}
|
|
|
|
function absorb(text: string): void {
|
|
try {
|
|
const parsed = JSON.parse(text);
|
|
const recents = parsed.recents ?? ({});
|
|
const thumbnails = parsed.thumbnails ?? ({});
|
|
root.recentsBytes = Number(recents.bytes ?? 0);
|
|
root.recentsEntries = Number(recents.entries ?? 0);
|
|
root.recentsPath = String(recents.path ?? "");
|
|
root.recentsPresent = recents.present === true;
|
|
root.thumbnailsBytes = Number(thumbnails.bytes ?? 0);
|
|
root.thumbnailsPath = String(thumbnails.path ?? "");
|
|
root.thumbnailsPresent = thumbnails.present === true;
|
|
root.thumbnailsComplete = thumbnails.measured !== false;
|
|
root.lastError = String(parsed.error ?? "");
|
|
} catch (error) {
|
|
root.lastError = "Could not read what the desktop has remembered.";
|
|
console.warn("Traces: could not parse helper output:", error);
|
|
}
|
|
root.measured = true;
|
|
}
|
|
|
|
function run(verb: string): void {
|
|
if (mutation.running)
|
|
return;
|
|
root.lastError = "";
|
|
mutation.command = [root.helperPath, verb];
|
|
mutation.running = true;
|
|
}
|
|
|
|
// The list is emptied, never deleted: GTK recreates the file the moment
|
|
// anything opens a file anyway, and an empty valid document takes effect in
|
|
// every running application immediately.
|
|
function clearRecents(): void { root.run("clear-recents"); }
|
|
|
|
// The folder stays; what is inside it goes. The helper refuses to follow a
|
|
// link out of it.
|
|
function clearThumbnails(): void { root.run("clear-thumbnails"); }
|
|
|
|
Process {
|
|
id: reader
|
|
stdout: StdioCollector { onStreamFinished: root.absorb(this.text) }
|
|
stderr: StdioCollector {
|
|
onStreamFinished: if (this.text.trim() !== "") root.lastError = this.text.trim()
|
|
}
|
|
}
|
|
|
|
// Both clears answer with a freshly measured snapshot, so the card updates
|
|
// without a second read.
|
|
Process {
|
|
id: mutation
|
|
stdout: StdioCollector { onStreamFinished: root.absorb(this.text) }
|
|
stderr: StdioCollector {
|
|
onStreamFinished: if (this.text.trim() !== "") root.lastError = this.text.trim()
|
|
}
|
|
}
|
|
}
|