Finish the wonderland: System told truthfully, in eight tabs instead of ten
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -15,6 +15,7 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import "DisplayLayout.js" as DisplayLayout
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -23,7 +24,6 @@ Singleton {
|
||||
|
||||
property var snapshots: []
|
||||
property string lastError: ""
|
||||
property string lastAction: ""
|
||||
|
||||
// Narrow service boundaries keep restore sequencing explicit and make it
|
||||
// possible to verify the real handler in an isolated shell without ever
|
||||
@@ -84,6 +84,9 @@ Singleton {
|
||||
Process {
|
||||
id: actionRun
|
||||
property bool restoring: false
|
||||
// Which verb is in flight, so a failure can name what failed. A
|
||||
// restore has its own flag because it also drives the display handoff.
|
||||
property string doneAction: "saved"
|
||||
property string outputText: ""
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: actionRun.outputText = this.text
|
||||
@@ -93,7 +96,9 @@ Singleton {
|
||||
if (exitCode !== 0) {
|
||||
root.lastError = actionRun.restoring
|
||||
? "That snapshot could not be restored."
|
||||
: "The settings could not be backed up.";
|
||||
: actionRun.doneAction === "deleted"
|
||||
? "That snapshot could not be deleted."
|
||||
: "The settings could not be backed up.";
|
||||
if (actionRun.restoring) {
|
||||
root.setDisplayBlocked(false);
|
||||
root.protectedDisplays = ({});
|
||||
@@ -101,7 +106,6 @@ Singleton {
|
||||
}
|
||||
return;
|
||||
}
|
||||
root.lastAction = actionRun.restoring ? "restored" : "saved";
|
||||
if (actionRun.restoring) {
|
||||
const restoreAccepted = root.handleRestoreOutput(actionRun.outputText);
|
||||
if (restoreAccepted)
|
||||
@@ -209,9 +213,39 @@ Singleton {
|
||||
if (actionRun.running)
|
||||
return;
|
||||
actionRun.restoring = false;
|
||||
actionRun.doneAction = "saved";
|
||||
actionRun.exec([root.helperPath, "save", root.serializeHomeState()]);
|
||||
}
|
||||
|
||||
// A snapshot with a name on it. The name is passed through untouched --
|
||||
// the helper owns what a usable name is, and a second sanitiser here would
|
||||
// be a second answer to that question, guaranteed to disagree eventually.
|
||||
function create(name: string): void {
|
||||
if (actionRun.running)
|
||||
return;
|
||||
actionRun.restoring = false;
|
||||
actionRun.doneAction = "saved";
|
||||
actionRun.exec([root.helperPath, "create", String(name ?? ""),
|
||||
root.serializeHomeState()]);
|
||||
}
|
||||
|
||||
// Matched against the list rather than trusted, exactly as restore() does:
|
||||
// no caller-supplied name reaches the helper even though it validates as
|
||||
// well. This is the only verb that destroys a snapshot, so the page is
|
||||
// expected to confirm before calling it.
|
||||
function deleteBackup(name: string): bool {
|
||||
if (actionRun.running)
|
||||
return false;
|
||||
if (!root.snapshots.some(snapshot => snapshot.name === name)) {
|
||||
root.lastError = "That snapshot is not in the list.";
|
||||
return false;
|
||||
}
|
||||
actionRun.restoring = false;
|
||||
actionRun.doneAction = "deleted";
|
||||
actionRun.exec([root.helperPath, "delete", name]);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function serializeHomeState(): string {
|
||||
const current = root.readHomeState();
|
||||
@@ -250,6 +284,34 @@ Singleton {
|
||||
applyRestoredState.restart();
|
||||
}
|
||||
|
||||
// The colour, depth and mirror fields a stored arrangement may carry beyond
|
||||
// its geometry. Optional exactly as Displays.isPersistedLayoutEntry has
|
||||
// them: an arrangement written before these existed still restores, and a
|
||||
// field that is present but invalid refuses the whole entry rather than
|
||||
// being guessed at.
|
||||
//
|
||||
// Leaving them out was a real loss, not a cosmetic one. The restored record
|
||||
// is built on top of the LIVE layout, so a snapshot's colour profile,
|
||||
// bit depth and SDR levels were silently replaced by whatever the display
|
||||
// is showing right now -- and layoutsEqual, comparing only geometry, then
|
||||
// judged the two identical and skipped the apply that would have put them
|
||||
// back. A snapshot taken in HDR restored to whatever was on screen.
|
||||
readonly property var storedDisplayFields:
|
||||
["vrrMode", "colorProfile", "bitdepth", "sdrBrightness", "sdrSaturation", "mirrorOf"]
|
||||
|
||||
function validStoredField(field: string, value: var): bool {
|
||||
switch (field) {
|
||||
case "vrrMode": return DisplayLayout.validVrrMode(value);
|
||||
case "colorProfile": return DisplayLayout.validColorProfile(value);
|
||||
case "bitdepth": return DisplayLayout.validBitdepth(value);
|
||||
case "sdrBrightness": return DisplayLayout.validSdrBrightness(value);
|
||||
case "sdrSaturation": return DisplayLayout.validSdrSaturation(value);
|
||||
case "mirrorOf": return typeof value === "string"
|
||||
&& (value === "" || /^[A-Za-z0-9_.-]+$/.test(value));
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
function layoutFromStoredDisplays(stored: var): var {
|
||||
if (!stored || typeof stored !== "object")
|
||||
return null;
|
||||
@@ -267,7 +329,7 @@ Singleton {
|
||||
|| !Number.isInteger(entry.x) || !Number.isInteger(entry.y)
|
||||
|| typeof entry.primary !== "boolean")
|
||||
return null;
|
||||
layout.push(Object.assign({}, live, {
|
||||
const record = Object.assign({}, live, {
|
||||
width: Number(match[1]),
|
||||
height: Number(match[2]),
|
||||
refreshRate: Number(match[3]),
|
||||
@@ -277,7 +339,15 @@ Singleton {
|
||||
x: entry.x,
|
||||
y: entry.y,
|
||||
primary: entry.primary
|
||||
}));
|
||||
});
|
||||
for (const field of root.storedDisplayFields) {
|
||||
if (entry[field] === undefined)
|
||||
continue;
|
||||
if (!root.validStoredField(field, entry[field]))
|
||||
return null;
|
||||
record[field] = entry[field];
|
||||
}
|
||||
layout.push(record);
|
||||
}
|
||||
return layout.filter(record => record.primary).length === 1 ? layout : null;
|
||||
}
|
||||
@@ -285,11 +355,23 @@ Singleton {
|
||||
function layoutsEqual(left: var, right: var): bool {
|
||||
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length)
|
||||
return false;
|
||||
const fields = ["name", "mode", "scale", "transform", "x", "y", "primary"];
|
||||
const fields = ["name", "mode", "scale", "transform", "x", "y", "primary",
|
||||
"vrrMode", "colorProfile", "bitdepth", "mirrorOf"];
|
||||
// Compared with a tolerance rather than by identity, the same way
|
||||
// Displays.storedFieldsDiffer does: these come back from the compositor
|
||||
// as floats, and 1.0 read back as 0.9999999 is not a change anybody made.
|
||||
const approximate = ["sdrBrightness", "sdrSaturation"];
|
||||
const a = Array.from(left).sort((x, y) => x.name.localeCompare(y.name));
|
||||
const b = Array.from(right).sort((x, y) => x.name.localeCompare(y.name));
|
||||
return a.every((record, index) => fields.every(
|
||||
field => record[field] === b[index][field]));
|
||||
field => record[field] === b[index][field])
|
||||
&& approximate.every(field => {
|
||||
const one = record[field];
|
||||
const other = b[index][field];
|
||||
if (one === undefined || other === undefined)
|
||||
return one === other;
|
||||
return Math.abs(Number(one) - Number(other)) < 0.001;
|
||||
}));
|
||||
}
|
||||
|
||||
function failDisplayRestore(message: string): bool {
|
||||
|
||||
Reference in New Issue
Block a user