Keep Settings recovery isolated and display-safe
This commit is contained in:
@@ -577,17 +577,22 @@ def command_restore(arguments: list[str]) -> None:
|
||||
# restore followed by `hyprctl reload` must not bypass that safety boundary.
|
||||
# Preserve the currently confirmed generation when it is readable, and
|
||||
# otherwise remove the snapshot's geometry so startup uses shipped policy.
|
||||
if desktop_present and desktop_data is not None:
|
||||
try:
|
||||
current_desktop = read_json(SETTINGS, "The current settings file") \
|
||||
if is_present(SETTINGS) else {}
|
||||
except BackupError:
|
||||
current_desktop = {}
|
||||
|
||||
if isinstance(current_desktop, dict) and "displays" in current_desktop:
|
||||
# Even a Home-only snapshot must retain the confirmed monitor layout.
|
||||
# In that case the restored desktop file contains only the protected
|
||||
# geometry; every ordinary desktop preference remains absent/default.
|
||||
desktop_present = True
|
||||
desktop_data = dict(desktop_data) if desktop_data is not None else {}
|
||||
desktop_data["displays"] = current_desktop["displays"]
|
||||
elif desktop_present and desktop_data is not None:
|
||||
desktop_data = dict(desktop_data)
|
||||
try:
|
||||
current_desktop = read_json(SETTINGS, "The current settings file") \
|
||||
if is_present(SETTINGS) else {}
|
||||
except BackupError:
|
||||
current_desktop = {}
|
||||
if "displays" in current_desktop:
|
||||
desktop_data["displays"] = current_desktop["displays"]
|
||||
else:
|
||||
desktop_data.pop("displays", None)
|
||||
desktop_data.pop("displays", None)
|
||||
|
||||
# Restoring remains undoable, but a corrupt current file must not prevent a
|
||||
# known-good snapshot from recovering the desktop.
|
||||
|
||||
@@ -206,6 +206,11 @@ Singleton {
|
||||
}
|
||||
|
||||
// ── Applying options ────────────────────────────────────────────────────
|
||||
// Test harnesses may replace the external compositor boundary while still
|
||||
// exercising validation, commit routing, persistence, and reset replay.
|
||||
// Production leaves this unset and always uses the verified Hyprland path.
|
||||
property var compositorApplyOverride: null
|
||||
|
||||
// `values` maps schema keys to values, e.g. { vrrPolicy: 3, gapsOut: 12 }.
|
||||
// The whole batch is validated before anything is sent, so one bad value
|
||||
// rejects the batch rather than half-applying it.
|
||||
@@ -227,6 +232,9 @@ Singleton {
|
||||
if (Object.keys(requested).length === 0)
|
||||
return false;
|
||||
|
||||
if (root.compositorApplyOverride !== null)
|
||||
return root.compositorApplyOverride(requested);
|
||||
|
||||
// A write in flight is queued rather than refused. Options are applied
|
||||
// and verified one batch at a time, but the callers are a settings UI
|
||||
// and a startup replay of every compositor-backed preference -- they
|
||||
|
||||
@@ -9,6 +9,7 @@ ShellRoot {
|
||||
id: root
|
||||
|
||||
property var resetCalls: []
|
||||
property var appliedBatches: []
|
||||
property bool displayBlocked: false
|
||||
|
||||
function recordReset(name: string): void {
|
||||
@@ -18,6 +19,18 @@ ShellRoot {
|
||||
}
|
||||
|
||||
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.
|
||||
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) {
|
||||
@@ -85,7 +98,15 @@ ShellRoot {
|
||||
}
|
||||
|
||||
function resetState(): string {
|
||||
return JSON.stringify({ calls: root.resetCalls, displayBlocked: root.displayBlocked });
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user