Close display verification races
This commit is contained in:
@@ -36,6 +36,19 @@ ShellRoot {
|
||||
return Displays.apply(monitor.name, mode, scale, monitor.transform);
|
||||
}
|
||||
|
||||
function refreshIdentityFixture(): string {
|
||||
const modes = Displays.normaliseModes([
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
]);
|
||||
const monitor = { width: 1920, height: 1080, refreshRate: 59.94 };
|
||||
return JSON.stringify({
|
||||
count: modes.length,
|
||||
modes: modes.map(mode => mode.mode),
|
||||
selected: modes.filter(mode => Displays.modeIsCurrent(monitor, mode)).map(mode => mode.mode)
|
||||
});
|
||||
}
|
||||
|
||||
function applyBad(kind: string): bool {
|
||||
const monitor = Displays.monitors[0];
|
||||
if (!monitor) return false;
|
||||
|
||||
@@ -64,9 +64,8 @@ Column {
|
||||
|
||||
required property var modelData
|
||||
|
||||
readonly property bool selected: root.monitor
|
||||
&& resolution.isCurrent
|
||||
&& Math.round(root.monitor.refreshRate) === rate.modelData.refresh
|
||||
readonly property bool selected: resolution.isCurrent
|
||||
&& Displays.modeIsCurrent(root.monitor, rate.modelData)
|
||||
|
||||
implicitWidth: Math.max(74, rateCaption.implicitWidth + 22)
|
||||
implicitHeight: 30
|
||||
|
||||
@@ -127,14 +127,16 @@ SettingsPage {
|
||||
TextRow {
|
||||
label: "Color mode"
|
||||
detail: "Wide-gamut SDR at 10-bit. Full-time HDR is left to the Hyprland config: it currently breaks screenshots, OBS, and the lock screen's blurred background."
|
||||
value: SystemSettings.colorPreset || "wide"
|
||||
value: root.monitor
|
||||
? `${root.monitor.colorPreset || "standard"} · ${root.monitor.currentFormat || "detecting format"}`
|
||||
: "Detecting"
|
||||
}
|
||||
TextRow {
|
||||
label: "Variable refresh"
|
||||
detail: SystemSettings.monitorVrrActive
|
||||
? "Active for current fullscreen content"
|
||||
: "Ready when game or video content requests it"
|
||||
value: SystemSettings.monitorVrrActive ? "Active" : "Standby"
|
||||
detail: root.monitor && root.monitor.vrr
|
||||
? "Active on this output for current fullscreen content"
|
||||
: "This output is ready when game or video content requests it"
|
||||
value: root.monitor && root.monitor.vrr ? "Active" : "Standby"
|
||||
divider: Displays.isOverridden(root.monitor ? root.monitor.name : "")
|
||||
}
|
||||
ActionRow {
|
||||
|
||||
@@ -38,6 +38,9 @@ Singleton {
|
||||
property bool revertQueued: false
|
||||
property var revertExpected: null
|
||||
property string revertReason: ""
|
||||
property bool revertVerificationActive: false
|
||||
property int operationGeneration: 0
|
||||
property int revertGeneration: -1
|
||||
property int secondsLeft: 0
|
||||
|
||||
readonly property bool awaitingConfirmation: root.pendingOutput !== ""
|
||||
@@ -63,13 +66,16 @@ Singleton {
|
||||
|
||||
Process {
|
||||
id: query
|
||||
property int generation: 0
|
||||
command: ["hyprctl", "-j", "monitors"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: root.parse(this.text)
|
||||
onStreamFinished: root.parse(this.text, query.generation)
|
||||
}
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode !== 0)
|
||||
root.lastError = "Could not read the connected displays.";
|
||||
if (root.revertQueued && !applyRun.running && root.awaitingConfirmation)
|
||||
root.performRevert();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +102,7 @@ Singleton {
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
// Exit status is advisory only. Hyprland's Lua bridge can report
|
||||
// success without applying a value, so exact readback decides.
|
||||
root.revertVerificationActive = true;
|
||||
revertVerifyTimer.attempts = 0;
|
||||
revertVerifyTimer.restart();
|
||||
}
|
||||
@@ -104,11 +111,13 @@ Singleton {
|
||||
Component.onCompleted: root.refresh()
|
||||
|
||||
function refresh(): void {
|
||||
if (!query.running)
|
||||
if (!query.running) {
|
||||
query.generation = root.operationGeneration;
|
||||
query.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
function parse(text: string): void {
|
||||
function parse(text: string, generation: int): void {
|
||||
try {
|
||||
const raw = JSON.parse(text);
|
||||
root.monitors = raw.map(monitor => {
|
||||
@@ -129,6 +138,9 @@ Singleton {
|
||||
mode: current?.mode ?? `${width}x${height}@${refreshRate}`,
|
||||
scale: monitor.scale ?? 1,
|
||||
transform: monitor.transform ?? 0,
|
||||
currentFormat: monitor.currentFormat ?? "",
|
||||
colorPreset: monitor.colorManagementPreset ?? "",
|
||||
vrr: monitor.vrr === true,
|
||||
modes: modes
|
||||
};
|
||||
});
|
||||
@@ -137,9 +149,13 @@ Singleton {
|
||||
root.pendingVerified = true;
|
||||
verifyTimer.stop();
|
||||
root.lastError = "";
|
||||
} else if (root.revertExpected
|
||||
} else if (root.revertVerificationActive
|
||||
&& generation === root.revertGeneration
|
||||
&& root.revertExpected
|
||||
&& root.matchesRequest(root.monitorNamed(root.revertExpected.output), root.revertExpected)) {
|
||||
revertVerifyTimer.stop();
|
||||
root.revertVerificationActive = false;
|
||||
root.revertGeneration = -1;
|
||||
root.revertExpected = null;
|
||||
if (root.revertReason === "")
|
||||
root.lastError = "";
|
||||
@@ -240,6 +256,13 @@ Singleton {
|
||||
&& monitor.transform === requested.transform;
|
||||
}
|
||||
|
||||
function modeIsCurrent(monitor: var, candidate: var): bool {
|
||||
return !!monitor && !!candidate
|
||||
&& monitor.width === candidate.width
|
||||
&& monitor.height === candidate.height
|
||||
&& Math.abs(monitor.refreshRate - candidate.refresh) < 0.01;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -275,6 +298,7 @@ Singleton {
|
||||
scale: monitor.scale,
|
||||
transform: monitor.transform
|
||||
};
|
||||
root.operationGeneration++;
|
||||
root.pendingRequested = {
|
||||
output: output,
|
||||
mode: mode,
|
||||
@@ -348,7 +372,7 @@ Singleton {
|
||||
root.revertReason = message;
|
||||
if (message !== "")
|
||||
root.lastError = message;
|
||||
if (applyRun.running) {
|
||||
if (applyRun.running || query.running) {
|
||||
root.revertQueued = true;
|
||||
return;
|
||||
}
|
||||
@@ -357,7 +381,10 @@ Singleton {
|
||||
|
||||
function performRevert(): void {
|
||||
const previous = root.pendingPrevious;
|
||||
root.operationGeneration++;
|
||||
root.revertGeneration = root.operationGeneration;
|
||||
root.revertExpected = previous;
|
||||
root.revertVerificationActive = false;
|
||||
root.clearPending();
|
||||
if (previous) {
|
||||
revertRun.exec(["hyprctl", "eval",
|
||||
@@ -405,6 +432,8 @@ Singleton {
|
||||
attempts++;
|
||||
if (attempts > 25) {
|
||||
stop();
|
||||
root.revertVerificationActive = false;
|
||||
root.revertGeneration = -1;
|
||||
root.revertExpected = null;
|
||||
root.revertReason = "";
|
||||
root.lastError = "The previous display setting could not be verified. Open Displays and restore it manually.";
|
||||
|
||||
Reference in New Issue
Block a user