Protect live state during restore and reset
This commit is contained in:
@@ -41,6 +41,7 @@ Singleton {
|
||||
property bool revertVerificationActive: false
|
||||
property int operationGeneration: 0
|
||||
property int revertGeneration: -1
|
||||
property bool externalChangeBlocked: false
|
||||
property int secondsLeft: 0
|
||||
|
||||
readonly property bool awaitingConfirmation: root.pendingOutput !== ""
|
||||
@@ -271,6 +272,10 @@ Singleton {
|
||||
// Applies immediately and starts the countdown. Nothing is stored yet: the
|
||||
// settings file is only written by confirm().
|
||||
function apply(output: string, mode: string, scale: real, transform: int): bool {
|
||||
if (root.externalChangeBlocked) {
|
||||
root.lastError = "Wait for Settings to finish restoring before changing a display.";
|
||||
return false;
|
||||
}
|
||||
if (root.busy) {
|
||||
root.lastError = "Wait for the current display operation to finish.";
|
||||
return false;
|
||||
|
||||
@@ -38,6 +38,10 @@ Singleton {
|
||||
property var initializeHome: function(ids) { HomePreferences.initialize(ids); }
|
||||
property var aliasHome: function(id, alias) { HomePreferences.setAlias(id, alias); }
|
||||
property var reloadDesktop: function() { DesktopPreferences.reload(); }
|
||||
property var readDisplays: function() { return DesktopPreferences.get("displays"); }
|
||||
property var protectDisplays: function(value) { return DesktopPreferences.set("displays", value); }
|
||||
property var displayBusy: function() { return Displays.busy || Displays.awaitingConfirmation; }
|
||||
property var setDisplayBlocked: function(blocked) { Displays.externalChangeBlocked = blocked; }
|
||||
property var applyCompositor: function() { SystemSettings.applyPersistedDisplayPolicy(); }
|
||||
property var reloadKeybinds: function() { Keybinds.applyReload(); }
|
||||
property var keybindsReloading: function() { return Keybinds.reloading; }
|
||||
@@ -47,6 +51,7 @@ Singleton {
|
||||
}
|
||||
property var applyWallpaper: function(path) { Wallpaper.set(path); }
|
||||
property var reloadShell: function() { Quickshell.reload(false); }
|
||||
property var protectedDisplays: ({})
|
||||
|
||||
readonly property bool busy: listQuery.running || actionRun.running
|
||||
|| applyRestoredState.running || settleReload.running
|
||||
@@ -81,6 +86,10 @@ Singleton {
|
||||
root.lastError = actionRun.restoring
|
||||
? "That snapshot could not be restored."
|
||||
: "The settings could not be backed up.";
|
||||
if (actionRun.restoring) {
|
||||
root.setDisplayBlocked(false);
|
||||
root.protectedDisplays = ({});
|
||||
}
|
||||
return;
|
||||
}
|
||||
root.lastAction = actionRun.restoring ? "restored" : "saved";
|
||||
@@ -89,6 +98,10 @@ Singleton {
|
||||
root.lastError = homeReloaded
|
||||
? ""
|
||||
: "Desktop settings were restored, but Home favourites could not be reloaded.";
|
||||
if (!homeReloaded) {
|
||||
root.setDisplayBlocked(false);
|
||||
root.protectedDisplays = ({});
|
||||
}
|
||||
} else
|
||||
root.lastError = "";
|
||||
root.refresh();
|
||||
@@ -124,6 +137,8 @@ Singleton {
|
||||
// from leaving restored Home state stale indefinitely.
|
||||
if ((!root.keybindsReloading() && !root.systemBusy()) || attempts >= 30) {
|
||||
stop();
|
||||
root.setDisplayBlocked(false);
|
||||
root.protectedDisplays = ({});
|
||||
root.reloadShell();
|
||||
}
|
||||
}
|
||||
@@ -162,6 +177,8 @@ Singleton {
|
||||
if (!root.reloadHomeState(text))
|
||||
return false;
|
||||
root.reloadDesktop();
|
||||
if (!root.protectDisplays(root.protectedDisplays))
|
||||
return false;
|
||||
applyRestoredState.restart();
|
||||
return true;
|
||||
}
|
||||
@@ -222,10 +239,18 @@ Singleton {
|
||||
function restore(name: string): bool {
|
||||
if (actionRun.running)
|
||||
return false;
|
||||
if (root.displayBusy()) {
|
||||
root.lastError = "Finish the current display change before restoring settings.";
|
||||
return false;
|
||||
}
|
||||
if (!root.snapshots.some(snapshot => snapshot.name === name)) {
|
||||
root.lastError = "That snapshot is not in the list.";
|
||||
return false;
|
||||
}
|
||||
const currentDisplays = root.readDisplays();
|
||||
root.protectedDisplays = JSON.parse(JSON.stringify(
|
||||
currentDisplays && typeof currentDisplays === "object" ? currentDisplays : {}));
|
||||
root.setDisplayBlocked(true);
|
||||
actionRun.restoring = true;
|
||||
actionRun.exec([root.helperPath, "restore", name]);
|
||||
return true;
|
||||
|
||||
@@ -33,6 +33,16 @@ Singleton {
|
||||
property string quickshellVersion: "0.3.0"
|
||||
property string lastError: ""
|
||||
|
||||
// Explicit seams keep reset sequencing testable without changing the live
|
||||
// keymap, wallpaper, or display from an isolated contract harness.
|
||||
property var displayBusy: function() { return Displays.busy || Displays.awaitingConfirmation; }
|
||||
property var readDisplays: function() { return DesktopPreferences.get("displays"); }
|
||||
property var protectDisplays: function(value) { return DesktopPreferences.set("displays", value); }
|
||||
property var setDisplayBlocked: function(blocked) { Displays.externalChangeBlocked = blocked; }
|
||||
property var reloadKeybinds: function() { Keybinds.applyReload(); }
|
||||
property var keybindsReloading: function() { return Keybinds.reloading; }
|
||||
property var applyWallpaper: function(path) { Wallpaper.set(path); }
|
||||
|
||||
readonly property bool busy: monitorQuery.running || serviceQuery.running || versionQuery.running
|
||||
|| configWrite.running || configVerify.running || bluebubblesQuery.running
|
||||
|
||||
@@ -389,8 +399,22 @@ Singleton {
|
||||
//
|
||||
// Compositor-backed values are re-applied afterwards, since resetting the
|
||||
// stored value does not by itself tell Hyprland anything.
|
||||
function restoreDefaults(): void {
|
||||
function restoreDefaults(): bool {
|
||||
if (root.displayBusy()) {
|
||||
root.lastError = "Finish the current display change before restoring defaults.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentDisplays = root.readDisplays();
|
||||
const protectedDisplays = JSON.parse(JSON.stringify(
|
||||
currentDisplays && typeof currentDisplays === "object" ? currentDisplays : {}));
|
||||
root.setDisplayBlocked(true);
|
||||
DesktopPreferences.resetDesktopDefaults();
|
||||
if (!root.protectDisplays(protectedDisplays)) {
|
||||
root.setDisplayBlocked(false);
|
||||
root.lastError = "The current display setting could not be protected during reset.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Home accessories keep their own store (panama-home.json), so a reset
|
||||
// that only cleared the schema store would silently leave a customised
|
||||
@@ -401,12 +425,33 @@ Singleton {
|
||||
HomePreferences.resetHomeDefaults();
|
||||
|
||||
resettleTimer.restart();
|
||||
return true;
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: resettleTimer
|
||||
interval: 60
|
||||
onTriggered: root.applyPersistedDisplayPolicy()
|
||||
onTriggered: {
|
||||
root.applyPersistedDisplayPolicy();
|
||||
root.reloadKeybinds();
|
||||
root.applyWallpaper(String(DesktopPreferences.get("wallpaperPath") ?? ""));
|
||||
resetRelease.attempts = 0;
|
||||
resetRelease.restart();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: resetRelease
|
||||
property int attempts: 0
|
||||
interval: 100
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
attempts++;
|
||||
if ((!root.keybindsReloading() && !root.busy) || attempts >= 50) {
|
||||
stop();
|
||||
root.setDisplayBlocked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setAutoHdr(enabled: bool): void {
|
||||
|
||||
@@ -33,6 +33,7 @@ Singleton {
|
||||
property bool scanning: false
|
||||
|
||||
readonly property string configured: DesktopPreferences.get("wallpaperPath")
|
||||
readonly property string shippedPath: `${Quickshell.env("HOME")}/Pictures/Wallpapers/faroe_islands.jpg`
|
||||
|
||||
// Directories searched for wallpapers, in order. Screenshots are
|
||||
// deliberately excluded: a folder of 300 screenshots is not a wallpaper
|
||||
@@ -85,6 +86,7 @@ Singleton {
|
||||
id: apply
|
||||
|
||||
property string requested: ""
|
||||
property string storedValue: ""
|
||||
property var remaining: []
|
||||
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
@@ -100,7 +102,7 @@ Singleton {
|
||||
return;
|
||||
}
|
||||
root.lastError = "";
|
||||
DesktopPreferences.set("wallpaperPath", apply.requested);
|
||||
DesktopPreferences.set("wallpaperPath", apply.storedValue);
|
||||
root.refreshActive();
|
||||
}
|
||||
}
|
||||
@@ -140,19 +142,16 @@ Singleton {
|
||||
// Applies to every connected output. Returns false when the path is not one
|
||||
// the schema will accept, so a caller can report the refusal.
|
||||
function set(path: string): bool {
|
||||
if (PreferenceSchema.coerce("wallpaperPath", path) === undefined) {
|
||||
const effectivePath = path === "" ? root.shippedPath : path;
|
||||
if (PreferenceSchema.coerce("wallpaperPath", effectivePath) === undefined) {
|
||||
root.lastError = "That file path cannot be used as a wallpaper.";
|
||||
return false;
|
||||
}
|
||||
if (apply.running)
|
||||
return false;
|
||||
|
||||
apply.requested = path;
|
||||
// "" clears the preference without touching what is on screen.
|
||||
if (path === "") {
|
||||
DesktopPreferences.set("wallpaperPath", "");
|
||||
return true;
|
||||
}
|
||||
apply.requested = effectivePath;
|
||||
apply.storedValue = path;
|
||||
|
||||
const outputs = Quickshell.screens.map(screen => screen.name).filter(name => !!name);
|
||||
if (outputs.length === 0) {
|
||||
@@ -161,7 +160,7 @@ Singleton {
|
||||
}
|
||||
|
||||
apply.remaining = outputs.slice(1);
|
||||
apply.exec(["hyprctl", "hyprpaper", "wallpaper", `${outputs[0]},${path}`]);
|
||||
apply.exec(["hyprctl", "hyprpaper", "wallpaper", `${outputs[0]},${effectivePath}`]);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user