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

178 lines
6.9 KiB
QML

import Quickshell
import Quickshell.Io
import QtQuick
import qs.config
import qs.services
ShellRoot {
id: root
property var resetCalls: []
property var appliedBatches: []
property bool displayBlocked: false
// The held-open safety snapshot: `pendingSnapshotDone` is the completion
// callback restoreDefaults handed over, waiting for the contract to answer.
property var pendingSnapshotDone: null
property bool snapshotRefusesToStart: false
function recordReset(name: string): void {
const next = root.resetCalls.slice();
next.push(name);
root.resetCalls = next;
}
Component.onCompleted: {
// Keep compositor verification entirely inside the isolated harness.
// Production applyOptions is covered separately by the Hyprland write
// contract; this seam proves commit/reset routing without changing the
// desktop that is running the test.
if (Quickshell.env("PANAMA_SETTINGS_TEST_ISOLATE_COMPOSITOR") === "1") {
SystemSettings.compositorApplyOverride = function(requested) {
const batches = root.appliedBatches.slice();
batches.push(requested);
root.appliedBatches = batches;
for (const key in requested)
DesktopPreferences.set(key, requested[key]);
return true;
};
}
SystemSettings.displayBusy = function() { return false; };
SystemSettings.readDisplays = function() { return DesktopPreferences.get("displays"); };
SystemSettings.protectDisplays = function(value) {
root.recordReset("display.protect");
return DesktopPreferences.set("displays", value);
};
SystemSettings.setDisplayBlocked = function(blocked) {
root.recordReset("display.block:" + blocked);
root.displayBlocked = blocked;
};
// The real snapshot is another process reading settings.json, so the
// stub is held open rather than answering inline: the contract drives
// its completion by hand and can therefore prove that nothing is wiped
// in the window between "snapshot started" and "snapshot finished",
// and that a failed snapshot leaves the stores alone.
SystemSettings.takeSafetySnapshot = function(done) {
root.recordReset("snapshot.start");
if (root.snapshotRefusesToStart)
return false;
root.pendingSnapshotDone = done;
return true;
};
SystemSettings.reloadKeybinds = function() { root.recordReset("keybinds.reload"); };
SystemSettings.keybindsReloading = function() { return false; };
SystemSettings.applyWallpaper = function(path) { root.recordReset("wallpaper.set:" + path); };
SystemSettings.regenerateLock = function() { root.recordReset("lock.regenerate"); };
SystemSettings.lockBusy = function() { return false; };
}
IpcHandler {
target: "settings-system-test"
function refresh(): void { SystemSettings.refresh(); }
// One batch, matching how the shell applies persisted policy. Three
// separate setters would be refused as overlapping writes, since each
// one is only complete after its value has been read back.
function apply(autoHdr: bool, vrr: int, directScanout: int): void {
SystemSettings.applyOptions({
autoHdr: autoHdr,
vrrPolicy: vrr,
directScanoutPolicy: directScanout
});
}
// Applies an arbitrary batch of schema keys, so the contract can cover
// every getoption answer shape -- int, bool, float, str, and the css
// box that gaps read back as -- not just the display policies.
function applyJson(payload: string): bool {
return SystemSettings.applyOptions(JSON.parse(payload));
}
// The routing entry point the settings rows use: a compositor-backed
// key must be applied and verified before it is stored, a local one is
// written directly. The contract checks both halves.
function commit(key: string, payload: string): bool {
return SystemSettings.commitPreference(key, JSON.parse(payload));
}
function stored(key: string): string {
return JSON.stringify(DesktopPreferences.get(key));
}
function seedHome(): void {
HomePreferences.favorites = [{ id: "light.contract_probe", alias: "Probe" }];
HomePreferences.initialized = true;
}
function homeState(): string {
return JSON.stringify({
count: HomePreferences.favorites.length,
initialized: HomePreferences.initialized
});
}
function restoreDefaults(): bool {
root.resetCalls = [];
return SystemSettings.restoreDefaults();
}
// Whether the next safety snapshot is allowed to start at all -- the
// "a backup is already running" case, which must refuse the reset
// outright rather than proceed without one.
function snapshotCanStart(allowed: bool): void {
root.snapshotRefusesToStart = !allowed;
}
// Answer the snapshot that restoreDefaults is waiting on. Everything
// the reset does must happen after this and not before.
function finishSnapshot(success: bool): bool {
const done = root.pendingSnapshotDone;
if (!done)
return false;
root.pendingSnapshotDone = null;
root.recordReset("snapshot.finish:" + success);
done(success);
return true;
}
function resetPending(): bool {
return SystemSettings.resetPending;
}
function clearError(): void {
SystemSettings.lastError = "";
}
function resetState(): string {
return JSON.stringify({
calls: root.resetCalls,
displayBlocked: root.displayBlocked,
appliedBatches: root.appliedBatches
});
}
function applyState(): string {
return JSON.stringify(root.appliedBatches);
}
function panelAllowed(panel: string): bool {
return SystemSettings.isGnomePanelAllowed(panel);
}
function status(): string {
return JSON.stringify({
monitorName: SystemSettings.monitorName,
monitorDescription: SystemSettings.monitorDescription,
width: SystemSettings.monitorWidth,
height: SystemSettings.monitorHeight,
scale: SystemSettings.monitorScale,
format: SystemSettings.monitorFormat,
busy: SystemSettings.busy,
lastError: SystemSettings.lastError
});
}
}
}