Files
Panama/config/dot/quickshell/settings-backup-harness.qml
T

175 lines
7.8 KiB
QML

// Isolated behavioral harness for SettingsBackup's live restore handoff.
// Every external consumer is replaced before restore output is exercised, so
// this file never writes the real compositor, wallpaper, keymap, or shell.
import Quickshell
import Quickshell.Io
import QtQuick
import qs.services
ShellRoot {
id: root
property var calls: []
property bool homeInitialized: false
property var homeFavorites: []
property bool displayOperationBusy: false
property bool displayBlocked: false
property bool displayApplyAccepted: true
property bool displayConfirmationReady: false
property var originalDisplays: ({
"DP-2": { mode: "4500x3000@60", scale: 1.5, transform: 0, x: 0, y: 0, primary: true },
"HDMI-A-1": { mode: "2560x1440@60", scale: 1, transform: 0, x: 3000, y: 0, primary: false }
})
property var restoredDisplays: ({
"DP-2": { mode: "4500x3000@60", scale: 1.5, transform: 0, x: -2560, y: 0, primary: false },
"HDMI-A-1": { mode: "2560x1440@60", scale: 1, transform: 0, x: 0, y: 0, primary: true }
})
property var displayGeneration: originalDisplays
property var liveLayout: [
{ name: "DP-2", width: 4500, height: 3000, refreshRate: 60, mode: "4500x3000@60", scale: 1.5, transform: 0, x: 0, y: 0, primary: true },
{ name: "HDMI-A-1", width: 2560, height: 1440, refreshRate: 60, mode: "2560x1440@60", scale: 1, transform: 0, x: 3000, y: 0, primary: false }
]
function record(name: string): void {
const next = root.calls.slice();
next.push(name);
root.calls = next;
}
Component.onCompleted: {
SettingsBackup.readHomeState = function() {
return {
initialized: root.homeInitialized,
favorites: root.homeFavorites
};
};
SettingsBackup.resetHome = function() {
root.record("home.reset");
root.homeInitialized = false;
root.homeFavorites = [];
};
SettingsBackup.initializeHome = function(ids) {
root.record("home.initialize:" + ids.join(","));
root.homeInitialized = true;
root.homeFavorites = ids.map(id => ({ id: id, alias: "" }));
};
SettingsBackup.aliasHome = function(id, alias) {
root.record("home.alias:" + id + "=" + alias);
root.homeFavorites = root.homeFavorites.map(favorite =>
favorite.id === id ? { id: id, alias: alias } : favorite);
};
SettingsBackup.reloadDesktop = function() {
root.record("desktop.reload");
root.displayGeneration = JSON.parse(JSON.stringify(root.restoredDisplays));
};
SettingsBackup.readDisplays = function() { return root.displayGeneration; };
SettingsBackup.protectDisplays = function(value) {
root.record("display.protect:" + JSON.stringify(value));
root.displayGeneration = value;
return true;
};
SettingsBackup.displayBusy = function() { return root.displayOperationBusy; };
SettingsBackup.readLiveDisplayLayout = function() {
return root.liveLayout.map(record => Object.assign({}, record));
};
SettingsBackup.applyDisplayLayout = function(layout) {
root.record("display.apply:" + JSON.stringify(layout));
if (!root.displayApplyAccepted)
return false;
root.liveLayout = layout.map(record => Object.assign({}, record));
root.displayOperationBusy = true;
root.displayConfirmationReady = true;
return true;
};
SettingsBackup.displayCanConfirm = function() { return root.displayConfirmationReady; };
SettingsBackup.confirmDisplayLayout = function() {
root.record("display.confirm");
root.displayOperationBusy = false;
root.displayConfirmationReady = false;
return true;
};
SettingsBackup.setDisplayBlocked = function(blocked) {
root.record("display.block:" + blocked);
root.displayBlocked = blocked;
};
SettingsBackup.applyIdle = function() { root.record("idle.apply"); };
SettingsBackup.idleBusy = function() { return false; };
SettingsBackup.applyCompositor = function() { root.record("system.apply"); };
SettingsBackup.reloadKeybinds = function() { root.record("keybinds.reload"); };
SettingsBackup.keybindsReloading = function() { return false; };
SettingsBackup.systemBusy = function() { return false; };
SettingsBackup.applyWallpaperPolicy = function() { root.record("wallpaper.apply-policy"); };
SettingsBackup.wallpaperBusy = function() { return false; };
SettingsBackup.regenerateLock = function() { root.record("lock.regenerate"); };
SettingsBackup.lockBusy = function() { return false; };
SettingsBackup.reloadShell = function() { root.record("shell.reload"); };
}
IpcHandler {
target: "settings-backup-behavior"
function reset(): void {
root.calls = [];
root.homeInitialized = false;
root.homeFavorites = [];
root.displayOperationBusy = false;
root.displayBlocked = false;
root.displayApplyAccepted = true;
root.displayConfirmationReady = false;
root.displayGeneration = JSON.parse(JSON.stringify(root.originalDisplays));
root.liveLayout = [
{ name: "DP-2", width: 4500, height: 3000, refreshRate: 60, mode: "4500x3000@60", scale: 1.5, transform: 0, x: 0, y: 0, primary: true },
{ name: "HDMI-A-1", width: 2560, height: 1440, refreshRate: 60, mode: "2560x1440@60", scale: 1, transform: 0, x: 3000, y: 0, primary: false }
];
SettingsBackup.protectedDisplays = root.displayGeneration;
SettingsBackup.protectedDisplayLayout = root.liveLayout;
}
function apply(output: string): bool {
return SettingsBackup.handleRestoreOutput(output);
}
function restoreWhileDisplayBusy(): bool {
root.displayOperationBusy = true;
SettingsBackup.snapshots = [{ name: "settings-20260818-010203004.json" }];
return SettingsBackup.restore("settings-20260818-010203004.json");
}
function applyDisplayFailure(output: string): bool {
root.displayApplyAccepted = false;
return SettingsBackup.handleRestoreOutput(output);
}
// The two pure functions behind a display restore, exposed directly.
// Everything else here is exercised through handleRestoreOutput, but
// the colour half of a layout is invisible from there: a snapshot
// whose colour fields were dropped restores geometry perfectly and
// reports success, which is exactly why nothing caught it for as long
// as it did.
function layoutFor(storedJson: string): string {
return JSON.stringify(
SettingsBackup.layoutFromStoredDisplays(JSON.parse(storedJson)));
}
// Both layouts in one object rather than two arguments: the IPC client
// flattens a top-level JSON array into one argument per element, so a
// pair of two-monitor layouts arrives as four arguments.
function layoutsMatch(pairJson: string): bool {
const pair = JSON.parse(pairJson);
return SettingsBackup.layoutsEqual(pair.left, pair.right);
}
function status(): string {
return JSON.stringify({
calls: root.calls,
initialized: root.homeInitialized,
favorites: root.homeFavorites,
displayBlocked: root.displayBlocked,
displays: root.displayGeneration,
lastError: SettingsBackup.lastError
});
}
}
}