Integrate complete display layouts
This commit is contained in:
@@ -83,6 +83,12 @@ clock, date, user name, and password-field presentation. **Power** owns when
|
|||||||
the session locks, while **Privacy** keeps only the established timing mirrors
|
the session locks, while **Privacy** keeps only the established timing mirrors
|
||||||
above. Visual controls must not be copied onto either page.
|
above. Visual controls must not be copied onto either page.
|
||||||
|
|
||||||
|
**Displays** is the sole owner of mode, scale, rotation, arrangement, and primary role.
|
||||||
|
Those values form one safety transaction: every connected output
|
||||||
|
is applied, verified, confirmed, or restored together. Other pages may link to
|
||||||
|
Displays, but must never expose a second geometry control or persist a partial
|
||||||
|
layout.
|
||||||
|
|
||||||
Mirrors must remain the same schema-backed control, never a second preference
|
Mirrors must remain the same schema-backed control, never a second preference
|
||||||
or a copied default. Additions to this table require a concrete discoverability
|
or a copied default. Additions to this table require a concrete discoverability
|
||||||
reason and an update to `tests/quickshell/settings-ownership-contract.sh`.
|
reason and an update to `tests/quickshell/settings-ownership-contract.sh`.
|
||||||
|
|||||||
@@ -365,8 +365,8 @@ Singleton {
|
|||||||
|
|
||||||
// Applies immediately and starts the countdown. Nothing is stored yet: the
|
// Applies immediately and starts the countdown. Nothing is stored yet: the
|
||||||
// complete connected layout is only written by confirm().
|
// complete connected layout is only written by confirm().
|
||||||
function applyLayout(layout: var): bool {
|
function applyLayout(layout: var, protectedOperation: bool): bool {
|
||||||
if (root.externalChangeBlocked) {
|
if (root.externalChangeBlocked && protectedOperation !== true) {
|
||||||
root.lastError = "Wait for Settings to finish restoring before changing a display.";
|
root.lastError = "Wait for Settings to finish restoring before changing a display.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -397,6 +397,13 @@ Singleton {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Settings restore holds the external-change lock while it proves a
|
||||||
|
// snapshot. This narrow entry point authorizes that one transaction while
|
||||||
|
// keeping every user-facing control blocked until restore settles.
|
||||||
|
function applyProtectedLayout(layout: var): bool {
|
||||||
|
return root.applyLayout(layout, true);
|
||||||
|
}
|
||||||
|
|
||||||
function makePrimary(output: string): bool {
|
function makePrimary(output: string): bool {
|
||||||
const layout = root.currentLayout();
|
const layout = root.currentLayout();
|
||||||
if (!layout.some(record => record.name === output)) {
|
if (!layout.some(record => record.name === output)) {
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ Singleton {
|
|||||||
property var readDisplays: function() { return DesktopPreferences.get("displays"); }
|
property var readDisplays: function() { return DesktopPreferences.get("displays"); }
|
||||||
property var protectDisplays: function(value) { return DesktopPreferences.set("displays", value); }
|
property var protectDisplays: function(value) { return DesktopPreferences.set("displays", value); }
|
||||||
property var displayBusy: function() { return Displays.busy || Displays.awaitingConfirmation; }
|
property var displayBusy: function() { return Displays.busy || Displays.awaitingConfirmation; }
|
||||||
|
property var readLiveDisplayLayout: function() { return Displays.currentLayout(); }
|
||||||
|
property var applyDisplayLayout: function(layout) { return Displays.applyProtectedLayout(layout); }
|
||||||
|
property var displayCanConfirm: function() { return Displays.canConfirm; }
|
||||||
|
property var confirmDisplayLayout: function() { return Displays.confirm(); }
|
||||||
property var setDisplayBlocked: function(blocked) { Displays.externalChangeBlocked = blocked; }
|
property var setDisplayBlocked: function(blocked) { Displays.externalChangeBlocked = blocked; }
|
||||||
property var applyCompositor: function() { SystemSettings.applyPersistedDisplayPolicy(); }
|
property var applyCompositor: function() { SystemSettings.applyPersistedDisplayPolicy(); }
|
||||||
property var reloadKeybinds: function() { Keybinds.applyReload(); }
|
property var reloadKeybinds: function() { Keybinds.applyReload(); }
|
||||||
@@ -52,9 +56,11 @@ Singleton {
|
|||||||
property var lockBusy: function() { return LockScreen.busy; }
|
property var lockBusy: function() { return LockScreen.busy; }
|
||||||
property var reloadShell: function() { Quickshell.reload(false); }
|
property var reloadShell: function() { Quickshell.reload(false); }
|
||||||
property var protectedDisplays: ({})
|
property var protectedDisplays: ({})
|
||||||
|
property var protectedDisplayLayout: []
|
||||||
|
property var pendingRestoredLayout: null
|
||||||
|
|
||||||
readonly property bool busy: listQuery.running || actionRun.running
|
readonly property bool busy: listQuery.running || actionRun.running
|
||||||
|| applyRestoredState.running || settleReload.running
|
|| settleDisplayRestore.running || applyRestoredState.running || settleReload.running
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: listQuery
|
id: listQuery
|
||||||
@@ -89,18 +95,21 @@ Singleton {
|
|||||||
if (actionRun.restoring) {
|
if (actionRun.restoring) {
|
||||||
root.setDisplayBlocked(false);
|
root.setDisplayBlocked(false);
|
||||||
root.protectedDisplays = ({});
|
root.protectedDisplays = ({});
|
||||||
|
root.protectedDisplayLayout = [];
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
root.lastAction = actionRun.restoring ? "restored" : "saved";
|
root.lastAction = actionRun.restoring ? "restored" : "saved";
|
||||||
if (actionRun.restoring) {
|
if (actionRun.restoring) {
|
||||||
const homeReloaded = root.handleRestoreOutput(actionRun.outputText);
|
const restoreAccepted = root.handleRestoreOutput(actionRun.outputText);
|
||||||
root.lastError = homeReloaded
|
if (restoreAccepted)
|
||||||
? ""
|
root.lastError = "";
|
||||||
: "Desktop settings were restored, but Home favourites could not be reloaded.";
|
else if (root.lastError === "")
|
||||||
if (!homeReloaded) {
|
root.lastError = "Desktop settings were restored, but Home favourites could not be reloaded.";
|
||||||
|
if (!restoreAccepted) {
|
||||||
root.setDisplayBlocked(false);
|
root.setDisplayBlocked(false);
|
||||||
root.protectedDisplays = ({});
|
root.protectedDisplays = ({});
|
||||||
|
root.protectedDisplayLayout = [];
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
root.lastError = "";
|
root.lastError = "";
|
||||||
@@ -108,6 +117,28 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: settleDisplayRestore
|
||||||
|
property int attempts: 0
|
||||||
|
interval: 100
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
attempts++;
|
||||||
|
if (root.displayCanConfirm()) {
|
||||||
|
stop();
|
||||||
|
if (!root.confirmDisplayLayout()) {
|
||||||
|
root.failDisplayRestore("The restored display layout could not be confirmed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.pendingRestoredLayout = null;
|
||||||
|
root.beginRestoredStateReplay();
|
||||||
|
} else if (!root.displayBusy() || attempts >= 180) {
|
||||||
|
stop();
|
||||||
|
root.failDisplayRestore("The restored display layout could not be verified.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: applyRestoredState
|
id: applyRestoredState
|
||||||
interval: 80
|
interval: 80
|
||||||
@@ -141,6 +172,7 @@ Singleton {
|
|||||||
stop();
|
stop();
|
||||||
root.setDisplayBlocked(false);
|
root.setDisplayBlocked(false);
|
||||||
root.protectedDisplays = ({});
|
root.protectedDisplays = ({});
|
||||||
|
root.protectedDisplayLayout = [];
|
||||||
root.reloadShell();
|
root.reloadShell();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,11 +211,76 @@ Singleton {
|
|||||||
if (!root.reloadHomeState(text))
|
if (!root.reloadHomeState(text))
|
||||||
return false;
|
return false;
|
||||||
root.reloadDesktop();
|
root.reloadDesktop();
|
||||||
if (!root.protectDisplays(root.protectedDisplays))
|
const restoredLayout = root.layoutFromStoredDisplays(root.readDisplays());
|
||||||
return false;
|
if (restoredLayout === null || root.layoutsEqual(
|
||||||
applyRestoredState.restart();
|
restoredLayout, root.protectedDisplayLayout)) {
|
||||||
|
root.beginRestoredStateReplay();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
root.pendingRestoredLayout = restoredLayout;
|
||||||
|
if (!root.applyDisplayLayout(restoredLayout))
|
||||||
|
return root.failDisplayRestore("The restored display layout was rejected.");
|
||||||
|
settleDisplayRestore.attempts = 0;
|
||||||
|
settleDisplayRestore.restart();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function beginRestoredStateReplay(): void {
|
||||||
|
applyRestoredState.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function layoutFromStoredDisplays(stored: var): var {
|
||||||
|
if (!stored || typeof stored !== "object")
|
||||||
|
return null;
|
||||||
|
const current = root.readLiveDisplayLayout();
|
||||||
|
if (!Array.isArray(current) || current.length === 0)
|
||||||
|
return null;
|
||||||
|
const layout = [];
|
||||||
|
for (const live of current) {
|
||||||
|
const entry = stored[live.name];
|
||||||
|
const match = String(entry?.mode ?? "").match(
|
||||||
|
/^(\d+)x(\d+)@(\d+(?:\.\d+)?)$/);
|
||||||
|
if (!entry || !match || !Number.isFinite(entry.scale) || entry.scale <= 0
|
||||||
|
|| !Number.isInteger(entry.transform)
|
||||||
|
|| entry.transform < 0 || entry.transform > 3
|
||||||
|
|| !Number.isInteger(entry.x) || !Number.isInteger(entry.y)
|
||||||
|
|| typeof entry.primary !== "boolean")
|
||||||
|
return null;
|
||||||
|
layout.push(Object.assign({}, live, {
|
||||||
|
width: Number(match[1]),
|
||||||
|
height: Number(match[2]),
|
||||||
|
refreshRate: Number(match[3]),
|
||||||
|
mode: entry.mode,
|
||||||
|
scale: entry.scale,
|
||||||
|
transform: entry.transform,
|
||||||
|
x: entry.x,
|
||||||
|
y: entry.y,
|
||||||
|
primary: entry.primary
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return layout.filter(record => record.primary).length === 1 ? layout : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function failDisplayRestore(message: string): bool {
|
||||||
|
root.pendingRestoredLayout = null;
|
||||||
|
if (!root.protectDisplays(root.protectedDisplays))
|
||||||
|
message += " The original display preference also could not be restored.";
|
||||||
|
root.setDisplayBlocked(false);
|
||||||
|
root.protectedDisplays = ({});
|
||||||
|
root.protectedDisplayLayout = [];
|
||||||
|
root.lastError = message;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Restore output carries the canonical Home state. Reconstructing through
|
// Restore output carries the canonical Home state. Reconstructing through
|
||||||
// these methods keeps validation and persistence inside HomePreferences;
|
// these methods keeps validation and persistence inside HomePreferences;
|
||||||
@@ -252,6 +349,8 @@ Singleton {
|
|||||||
const currentDisplays = root.readDisplays();
|
const currentDisplays = root.readDisplays();
|
||||||
root.protectedDisplays = JSON.parse(JSON.stringify(
|
root.protectedDisplays = JSON.parse(JSON.stringify(
|
||||||
currentDisplays && typeof currentDisplays === "object" ? currentDisplays : {}));
|
currentDisplays && typeof currentDisplays === "object" ? currentDisplays : {}));
|
||||||
|
root.protectedDisplayLayout = JSON.parse(JSON.stringify(
|
||||||
|
root.readLiveDisplayLayout() ?? []));
|
||||||
root.setDisplayBlocked(true);
|
root.setDisplayBlocked(true);
|
||||||
actionRun.restoring = true;
|
actionRun.restoring = true;
|
||||||
actionRun.exec([root.helperPath, "restore", name]);
|
actionRun.exec([root.helperPath, "restore", name]);
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ Singleton {
|
|||||||
{ label: "Copy health report", detail: "Copy a redacted Panama doctor report", page: "services" },
|
{ label: "Copy health report", detail: "Copy a redacted Panama doctor report", page: "services" },
|
||||||
{ label: "Lock screen background", detail: "Choose a blurred desktop, wallpaper, or solid colour", page: "appearance" },
|
{ label: "Lock screen background", detail: "Choose a blurred desktop, wallpaper, or solid colour", page: "appearance" },
|
||||||
{ label: "Password field", detail: "Choose whether the empty lock-screen field stays visible", page: "appearance" },
|
{ label: "Password field", detail: "Choose whether the empty lock-screen field stays visible", page: "appearance" },
|
||||||
{ label: "Per-display wallpaper", detail: "Assign a different image to each connected display", page: "appearance" }
|
{ label: "Per-display wallpaper", detail: "Assign a different image to each connected display", page: "appearance" },
|
||||||
|
{ label: "Arrange displays", detail: "Drag connected displays into their physical positions", page: "displays" },
|
||||||
|
{ label: "Monitor position", detail: "Set where each display sits in the desktop", page: "displays" },
|
||||||
|
{ label: "Primary display", detail: "Choose the display that anchors the desktop", page: "displays" }
|
||||||
]
|
]
|
||||||
|
|
||||||
function pageFor(group: string): string {
|
function pageFor(group: string): string {
|
||||||
|
|||||||
@@ -509,16 +509,13 @@ Singleton {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentDisplays = root.readDisplays();
|
|
||||||
const protectedDisplays = JSON.parse(JSON.stringify(
|
|
||||||
currentDisplays && typeof currentDisplays === "object" ? currentDisplays : {}));
|
|
||||||
root.setDisplayBlocked(true);
|
root.setDisplayBlocked(true);
|
||||||
DesktopPreferences.resetDesktopDefaults();
|
DesktopPreferences.resetDesktopDefaults();
|
||||||
if (!root.protectDisplays(protectedDisplays)) {
|
|
||||||
root.setDisplayBlocked(false);
|
// Do not apply geometry during a reset: doing so would need the same
|
||||||
root.lastError = "The current display setting could not be protected during reset.";
|
// visible confirmation transaction as the Displays page. Clearing the
|
||||||
return false;
|
// stored records is still important, though, so the next session uses
|
||||||
}
|
// Panama's shipped DP-2 placement and automatic placement elsewhere.
|
||||||
|
|
||||||
// Home accessories keep their own store (panama-home.json), so a reset
|
// Home accessories keep their own store (panama-home.json), so a reset
|
||||||
// that only cleared the schema store would silently leave a customised
|
// that only cleared the schema store would silently leave a customised
|
||||||
|
|||||||
@@ -15,7 +15,21 @@ ShellRoot {
|
|||||||
property var homeFavorites: []
|
property var homeFavorites: []
|
||||||
property bool displayOperationBusy: false
|
property bool displayOperationBusy: false
|
||||||
property bool displayBlocked: false
|
property bool displayBlocked: false
|
||||||
property var displayGeneration: ({ "DP-2": { mode: "4500x3000@60", scale: 1.5, transform: 0 } })
|
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 {
|
function record(name: string): void {
|
||||||
const next = root.calls.slice();
|
const next = root.calls.slice();
|
||||||
@@ -45,7 +59,10 @@ ShellRoot {
|
|||||||
root.homeFavorites = root.homeFavorites.map(favorite =>
|
root.homeFavorites = root.homeFavorites.map(favorite =>
|
||||||
favorite.id === id ? { id: id, alias: alias } : favorite);
|
favorite.id === id ? { id: id, alias: alias } : favorite);
|
||||||
};
|
};
|
||||||
SettingsBackup.reloadDesktop = function() { root.record("desktop.reload"); };
|
SettingsBackup.reloadDesktop = function() {
|
||||||
|
root.record("desktop.reload");
|
||||||
|
root.displayGeneration = JSON.parse(JSON.stringify(root.restoredDisplays));
|
||||||
|
};
|
||||||
SettingsBackup.readDisplays = function() { return root.displayGeneration; };
|
SettingsBackup.readDisplays = function() { return root.displayGeneration; };
|
||||||
SettingsBackup.protectDisplays = function(value) {
|
SettingsBackup.protectDisplays = function(value) {
|
||||||
root.record("display.protect:" + JSON.stringify(value));
|
root.record("display.protect:" + JSON.stringify(value));
|
||||||
@@ -53,6 +70,25 @@ ShellRoot {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
SettingsBackup.displayBusy = function() { return root.displayOperationBusy; };
|
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) {
|
SettingsBackup.setDisplayBlocked = function(blocked) {
|
||||||
root.record("display.block:" + blocked);
|
root.record("display.block:" + blocked);
|
||||||
root.displayBlocked = blocked;
|
root.displayBlocked = blocked;
|
||||||
@@ -77,7 +113,15 @@ ShellRoot {
|
|||||||
root.homeFavorites = [];
|
root.homeFavorites = [];
|
||||||
root.displayOperationBusy = false;
|
root.displayOperationBusy = false;
|
||||||
root.displayBlocked = 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.protectedDisplays = root.displayGeneration;
|
||||||
|
SettingsBackup.protectedDisplayLayout = root.liveLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
function apply(output: string): bool {
|
function apply(output: string): bool {
|
||||||
@@ -90,6 +134,11 @@ ShellRoot {
|
|||||||
return SettingsBackup.restore("settings-20260818-010203004.json");
|
return SettingsBackup.restore("settings-20260818-010203004.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyDisplayFailure(output: string): bool {
|
||||||
|
root.displayApplyAccepted = false;
|
||||||
|
return SettingsBackup.handleRestoreOutput(output);
|
||||||
|
}
|
||||||
|
|
||||||
function status(): string {
|
function status(): string {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
calls: root.calls,
|
calls: root.calls,
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ for mapping in \
|
|||||||
'HomePreferences.setAlias(id, alias);' \
|
'HomePreferences.setAlias(id, alias);' \
|
||||||
'DesktopPreferences.reload();' \
|
'DesktopPreferences.reload();' \
|
||||||
'DesktopPreferences.set("displays", value);' \
|
'DesktopPreferences.set("displays", value);' \
|
||||||
|
'Displays.currentLayout();' \
|
||||||
|
'Displays.applyProtectedLayout(layout);' \
|
||||||
|
'Displays.canConfirm;' \
|
||||||
|
'Displays.confirm();' \
|
||||||
'Displays.externalChangeBlocked = blocked;' \
|
'Displays.externalChangeBlocked = blocked;' \
|
||||||
'SystemSettings.applyPersistedDisplayPolicy();' \
|
'SystemSettings.applyPersistedDisplayPolicy();' \
|
||||||
'Keybinds.applyReload();' \
|
'Keybinds.applyReload();' \
|
||||||
@@ -85,7 +89,8 @@ jq -e '
|
|||||||
"home.alias:light.desk=Desk",
|
"home.alias:light.desk=Desk",
|
||||||
"home.alias:light.office=Office",
|
"home.alias:light.office=Office",
|
||||||
"desktop.reload",
|
"desktop.reload",
|
||||||
"display.protect:{\"DP-2\":{\"mode\":\"4500x3000@60\",\"scale\":1.5,\"transform\":0}}",
|
"display.apply:[{\"name\":\"DP-2\",\"width\":4500,\"height\":3000,\"refreshRate\":60,\"mode\":\"4500x3000@60\",\"scale\":1.5,\"transform\":0,\"x\":-2560,\"y\":0,\"primary\":false},{\"name\":\"HDMI-A-1\",\"width\":2560,\"height\":1440,\"refreshRate\":60,\"mode\":\"2560x1440@60\",\"scale\":1,\"transform\":0,\"x\":0,\"y\":0,\"primary\":true}]",
|
||||||
|
"display.confirm",
|
||||||
"system.apply",
|
"system.apply",
|
||||||
"keybinds.reload",
|
"keybinds.reload",
|
||||||
"wallpaper.apply-policy",
|
"wallpaper.apply-policy",
|
||||||
@@ -124,7 +129,8 @@ jq -e '
|
|||||||
.calls == [
|
.calls == [
|
||||||
"home.reset",
|
"home.reset",
|
||||||
"desktop.reload",
|
"desktop.reload",
|
||||||
"display.protect:{\"DP-2\":{\"mode\":\"4500x3000@60\",\"scale\":1.5,\"transform\":0}}",
|
"display.apply:[{\"name\":\"DP-2\",\"width\":4500,\"height\":3000,\"refreshRate\":60,\"mode\":\"4500x3000@60\",\"scale\":1.5,\"transform\":0,\"x\":-2560,\"y\":0,\"primary\":false},{\"name\":\"HDMI-A-1\",\"width\":2560,\"height\":1440,\"refreshRate\":60,\"mode\":\"2560x1440@60\",\"scale\":1,\"transform\":0,\"x\":0,\"y\":0,\"primary\":true}]",
|
||||||
|
"display.confirm",
|
||||||
"system.apply",
|
"system.apply",
|
||||||
"keybinds.reload",
|
"keybinds.reload",
|
||||||
"wallpaper.apply-policy",
|
"wallpaper.apply-policy",
|
||||||
@@ -136,6 +142,25 @@ jq -e '
|
|||||||
and .favorites == []
|
and .favorites == []
|
||||||
' <<<"$status" >/dev/null || fail "absent Home handoff was wrong: $status"
|
' <<<"$status" >/dev/null || fail "absent Home handoff was wrong: $status"
|
||||||
|
|
||||||
|
# If the restored complete layout is rejected before it can be verified, the
|
||||||
|
# original persisted layout is put back and the shell is not reloaded over an
|
||||||
|
# unproven display state.
|
||||||
|
qs_test ipc call settings-backup-behavior reset >/dev/null
|
||||||
|
[[ "$(qs_test ipc call settings-backup-behavior applyDisplayFailure "$payload")" == "false" ]] \
|
||||||
|
|| fail 'a rejected restored display layout was reported as successful'
|
||||||
|
status="$(qs_test ipc call settings-backup-behavior status)"
|
||||||
|
jq -e '.calls == [
|
||||||
|
"home.reset",
|
||||||
|
"home.initialize:light.desk,light.office",
|
||||||
|
"home.alias:light.desk=Desk",
|
||||||
|
"home.alias:light.office=Office",
|
||||||
|
"desktop.reload",
|
||||||
|
"display.apply:[{\"name\":\"DP-2\",\"width\":4500,\"height\":3000,\"refreshRate\":60,\"mode\":\"4500x3000@60\",\"scale\":1.5,\"transform\":0,\"x\":-2560,\"y\":0,\"primary\":false},{\"name\":\"HDMI-A-1\",\"width\":2560,\"height\":1440,\"refreshRate\":60,\"mode\":\"2560x1440@60\",\"scale\":1,\"transform\":0,\"x\":0,\"y\":0,\"primary\":true}]",
|
||||||
|
"display.protect:{\"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}}",
|
||||||
|
"display.block:false"
|
||||||
|
] and (.lastError | contains("display layout"))' <<<"$status" >/dev/null \
|
||||||
|
|| fail "rejected display restore did not retain the original layout: $status"
|
||||||
|
|
||||||
# Restore refuses before launching the helper while a display apply/recovery is
|
# Restore refuses before launching the helper while a display apply/recovery is
|
||||||
# active, so no snapshot can race the confirmation boundary.
|
# active, so no snapshot can race the confirmation boundary.
|
||||||
qs_test ipc call settings-backup-behavior reset >/dev/null
|
qs_test ipc call settings-backup-behavior reset >/dev/null
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ rg -Fq 'HomePreferences.resetHomeDefaults();' "$system_settings" \
|
|||||||
if rg -q 'HomePreferences\.(favorites|initialized)\s*=' "$system_settings"; then
|
if rg -q 'HomePreferences\.(favorites|initialized)\s*=' "$system_settings"; then
|
||||||
fail 'restoreDefaults mutates Home aliases instead of using resetHomeDefaults'
|
fail 'restoreDefaults mutates Home aliases instead of using resetHomeDefaults'
|
||||||
fi
|
fi
|
||||||
rg -Fq 'root.protectDisplays(protectedDisplays)' "$system_settings" \
|
rg -Fq 'DesktopPreferences.resetDesktopDefaults();' "$system_settings" \
|
||||||
|| fail 'restoreDefaults can apply unconfirmed display geometry during reload'
|
|| fail 'restoreDefaults does not clear confirmed display layout fields'
|
||||||
rg -Fq 'Keybinds.applyReload();' "$system_settings" \
|
rg -Fq 'Keybinds.applyReload();' "$system_settings" \
|
||||||
|| fail 'restoreDefaults does not replay shipped keybindings'
|
|| fail 'restoreDefaults does not replay shipped keybindings'
|
||||||
rg -Fq 'root.applyWallpaper(String(DesktopPreferences.get("wallpaperPath") ?? ""));' "$system_settings" \
|
rg -Fq 'root.applyWallpaper(String(DesktopPreferences.get("wallpaperPath") ?? ""));' "$system_settings" \
|
||||||
@@ -120,7 +120,7 @@ before="$(qs_for_harness ipc call settings-system-test stored windowRounding)"
|
|||||||
# ── Reset spans every store, not just the schema one ─────────────────────────
|
# ── Reset spans every store, not just the schema one ─────────────────────────
|
||||||
qs_for_harness ipc call settings-system-test seedHome >/dev/null
|
qs_for_harness ipc call settings-system-test seedHome >/dev/null
|
||||||
qs_for_harness ipc call settings-system-test commit dockHideDelayMs 900 >/dev/null
|
qs_for_harness ipc call settings-system-test commit dockHideDelayMs 900 >/dev/null
|
||||||
display_fixture='{"DP-2":{"mode":"4500x3000@60","scale":1.5,"transform":0}}'
|
display_fixture='{"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}}'
|
||||||
[[ "$(qs_for_harness ipc call settings-system-test commit displays "$display_fixture")" == "true" ]] \
|
[[ "$(qs_for_harness ipc call settings-system-test commit displays "$display_fixture")" == "true" ]] \
|
||||||
|| fail 'the protected display fixture did not apply'
|
|| fail 'the protected display fixture did not apply'
|
||||||
sleep 0.4
|
sleep 0.4
|
||||||
@@ -138,7 +138,6 @@ sleep 0.6
|
|||||||
reset_state="$(qs_for_harness ipc call settings-system-test resetState)"
|
reset_state="$(qs_for_harness ipc call settings-system-test resetState)"
|
||||||
jq -e '.calls == [
|
jq -e '.calls == [
|
||||||
"display.block:true",
|
"display.block:true",
|
||||||
"display.protect",
|
|
||||||
"keybinds.reload",
|
"keybinds.reload",
|
||||||
"wallpaper.set:",
|
"wallpaper.set:",
|
||||||
"lock.regenerate",
|
"lock.regenerate",
|
||||||
@@ -164,8 +163,8 @@ for default_case in \
|
|||||||
[[ "$(qs_for_harness ipc call settings-system-test stored "$key")" == "$expected" ]] \
|
[[ "$(qs_for_harness ipc call settings-system-test stored "$key")" == "$expected" ]] \
|
||||||
|| fail "reset did not restore $key to $expected"
|
|| fail "reset did not restore $key to $expected"
|
||||||
done
|
done
|
||||||
[[ "$(qs_for_harness ipc call settings-system-test stored displays | jq -cS .)" == "$(jq -cS . <<<"$display_fixture")" ]] \
|
[[ "$(qs_for_harness ipc call settings-system-test stored displays | jq -cS .)" == '{}' ]] \
|
||||||
|| fail 'reset replaced confirmed display geometry without confirmation'
|
|| fail 'reset retained confirmed arrangement fields instead of returning startup to shipped placement'
|
||||||
|
|
||||||
home_after="$(qs_for_harness ipc call settings-system-test homeState)"
|
home_after="$(qs_for_harness ipc call settings-system-test homeState)"
|
||||||
jq -e '.count == 0 and .initialized == false' <<<"$home_after" >/dev/null \
|
jq -e '.count == 0 and .initialized == false' <<<"$home_after" >/dev/null \
|
||||||
|
|||||||
@@ -102,7 +102,8 @@ for needle in \
|
|||||||
'`inactiveOpacity`' \
|
'`inactiveOpacity`' \
|
||||||
'`lockMinutes`' \
|
'`lockMinutes`' \
|
||||||
'`lockOnSleep`' \
|
'`lockOnSleep`' \
|
||||||
'scheme-relative role'; do
|
'scheme-relative role' \
|
||||||
|
'mode, scale, rotation, arrangement, and primary role'; do
|
||||||
rg -Fq "$needle" "$readme" || fail "README is missing $needle"
|
rg -Fq "$needle" "$readme" || fail "README is missing $needle"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ password field|Password field|appearance
|
|||||||
slideshow|Wallpaper mode|appearance
|
slideshow|Wallpaper mode|appearance
|
||||||
shuffle|Shuffle|appearance
|
shuffle|Shuffle|appearance
|
||||||
per-display wallpaper|Per-display wallpaper|appearance
|
per-display wallpaper|Per-display wallpaper|appearance
|
||||||
|
arrange displays|Arrange displays|displays
|
||||||
|
monitor position|Monitor position|displays
|
||||||
|
primary display|Primary display|displays
|
||||||
CASES
|
CASES
|
||||||
|
|
||||||
! rg -Fq 'Startup & Services' "$repo_dir/config/dot/quickshell/services/SettingsSearch.qml" \
|
! rg -Fq 'Startup & Services' "$repo_dir/config/dot/quickshell/services/SettingsSearch.qml" \
|
||||||
|
|||||||
Reference in New Issue
Block a user