Review everything shipped this weekend, and fix what the reviewers caught

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-24 13:29:42 -04:00
parent ffce48964e
commit 07db1068f1
42 changed files with 959 additions and 219 deletions
+83 -20
View File
@@ -156,7 +156,12 @@ 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;
//
// performRevert already armed the timer; restarting it here gives
// the readback its full window from the moment the command actually
// returned, rather than from the moment it was issued.
if (!root.revertVerificationActive)
return;
revertVerifyTimer.ticks = 0;
revertVerifyTimer.restart();
}
@@ -177,9 +182,16 @@ Singleton {
}
// Nothing in flight, so a display genuinely arrived or left. Give it
// back the arrangement it was last confirmed with -- see restoreStored.
root.restoreDeferrals = 0;
restoreDebounce.restart();
}
// How many times the pending restore has been put off because something was
// in flight. Reset by every fresh hotplug and by every decision actually
// acted on; see restoreStored.
property int restoreDeferrals: 0
readonly property int restoreDeferralLimit: 20
// Docking and undocking should not cost you your arrangement.
//
// hypr/monitors.lua applies the stored per-output entries, but only when
@@ -207,12 +219,18 @@ Singleton {
// says so, which is recoverable. Silence would not be.
// The decision, with no side effects, so it can be tested without driving
// a real compositor. Returns one of:
// { action: "none" } nothing stored, or already correct
// { action: "apply", layout } restore this
// { action: "refuse" } stored arrangement does not fit
// { action: "none", reason: "settled" } nothing stored, or already correct
// { action: "none", reason: "unavailable" } cannot decide yet, ask again
// { action: "apply", layout } restore this
// { action: "refuse" } stored arrangement does not fit
//
// The two "none" answers are not the same answer. "Nothing to do" is final;
// "not now" is a question that has to be asked again, and a dock changes the
// topology and the readback at once, so "not now" is the common case exactly
// when the restore matters most.
function plannedRestore(): var {
if (root.busy || root.awaitingConfirmation || root.monitors.length === 0)
return { action: "none" };
return { action: "none", reason: "unavailable" };
const stored = DesktopPreferences.get("displays");
const persisted = stored && typeof stored === "object" ? stored : {};
@@ -248,7 +266,7 @@ Singleton {
}
if (!changed)
return { action: "none" };
return { action: "none", reason: "settled" };
// Exactly one primary, on a display that is actually here. Undocking
// takes the primary away, and a layout with none is one
@@ -271,8 +289,21 @@ Singleton {
function restoreStored(): void {
const plan = root.plannedRestore();
if (plan.action === "none")
if (plan.action === "none") {
// Deferred, not dropped: a query or an apply in flight is a "ask me
// again", and dropping it meant the arrangement stayed lost until
// somebody opened Settings and applied it by hand. Bounded so a
// stuck operation cannot leave this rescheduling itself all session
// -- twenty tries outlasts a full confirmation countdown.
if (plan.reason === "unavailable"
&& root.restoreDeferrals < root.restoreDeferralLimit) {
root.restoreDeferrals += 1;
restoreDebounce.restart();
}
return;
}
root.restoreDeferrals = 0;
if (plan.action === "refuse") {
StatusEvents.publish({
@@ -428,13 +459,20 @@ Singleton {
// The framebuffer format is the only honest report of the bit depth in
// effect: asking for 10-bit and getting it are different things, and a
// panel that cannot carry the link rate quietly stays at 8. Formats outside
// this map are read as "unknown", never as a mismatch.
// panel that cannot carry the link rate quietly stays at 8.
//
// Matched on the channel widths rather than on the two names Panama happens
// to have seen. The channel order is the compositor's business -- XRGB,
// XBGR and ARGB all carry ten bits per channel in 2101010 and eight in 8888
// -- and pinning the whole string reported an output as "unknown" for the
// one part of it that says nothing about depth. Anything else is still read
// as unknown, which is never treated as a mismatch.
function formatBitdepth(format: string): int {
if (format === "XRGB8888")
return 8;
if (format === "XRGB2101010")
const text = String(format);
if (/2101010$/.test(text))
return 10;
if (/8888$/.test(text))
return 8;
return 0;
}
@@ -544,7 +582,7 @@ Singleton {
const saved = root.savedEntry(monitor.name);
const live = DisplayLayout.validColorProfile(monitor.colorPreset)
? monitor.colorPreset : "auto";
return {
const record = {
name: monitor.name,
width: monitor.width,
height: monitor.height,
@@ -560,15 +598,25 @@ Singleton {
// Keeping the stored policy stops one apply from pinning the
// display to whatever automatic happened to pick today.
colorProfile: saved && saved.colorProfile === "auto" ? "auto" : live,
bitdepth: monitor.bitdepth !== 0
? monitor.bitdepth
: (saved && DisplayLayout.validBitdepth(saved.bitdepth) ? saved.bitdepth : 8),
sdrBrightness: DisplayLayout.validSdrBrightness(monitor.sdrBrightness)
? monitor.sdrBrightness : 1.0,
sdrSaturation: DisplayLayout.validSdrSaturation(monitor.sdrSaturation)
? monitor.sdrSaturation : 1.0,
mirrorOf: typeof monitor.mirrorOf === "string" ? monitor.mirrorOf : ""
};
// The bit depth is the one field with no honest default. A
// framebuffer format Panama cannot read means the depth is unknown,
// and filling in 8 turns that ignorance into a request: monitorRule
// would write `bitdepth = 8` at a panel that may well be running at
// 10, and confirm() would store the guess as though it were read.
// Left off instead, which every consumer already understands as "no
// opinion" -- the rule omits the key, the compositor keeps what it
// has, and isPersistedLayoutEntry accepts an entry without one.
if (monitor.bitdepth !== 0)
record.bitdepth = monitor.bitdepth;
else if (saved && DisplayLayout.validBitdepth(saved.bitdepth))
record.bitdepth = saved.bitdepth;
return record;
});
}
@@ -847,7 +895,7 @@ Singleton {
const stored = DesktopPreferences.get("displays");
const next = Object.assign({}, (stored && typeof stored === "object") ? stored : {});
for (const record of root.pendingRequestedLayout) {
next[record.name] = {
const entry = {
mode: record.mode,
scale: record.scale,
transform: record.transform,
@@ -856,11 +904,16 @@ Singleton {
primary: record.primary,
vrrMode: record.vrrMode,
colorProfile: record.colorProfile,
bitdepth: record.bitdepth,
sdrBrightness: record.sdrBrightness,
sdrSaturation: record.sdrSaturation,
mirrorOf: record.mirrorOf
};
// Absent rather than guessed, as currentLayout leaves it. A stored
// 8 that nobody read is one the next start would push at the display
// as a request.
if (DisplayLayout.validBitdepth(record.bitdepth))
entry.bitdepth = record.bitdepth;
next[record.name] = entry;
}
if (!DesktopPreferences.set("displays", next)) {
root.lastError = "That display setting could not be saved. Revert it and try again.";
@@ -926,10 +979,20 @@ Singleton {
root.revertExpectedLayout = previous.length > 0 ? previous : null;
root.revertVerificationActive = false;
root.clearPending();
if (previous.length > 0)
if (previous.length > 0) {
// Armed here, before the push, and not only from revertRun.onExited.
// A hyprctl that fails to start never emits `exited`, and the
// verification timer is the only thing that ever clears
// revertExpectedLayout -- which `busy` counts. So the one failure
// that most needs a way out used to latch the whole Displays page
// busy for the rest of the session, with no error to say why.
root.revertVerificationActive = true;
revertVerifyTimer.ticks = 0;
revertVerifyTimer.restart();
root.pushLayout(previous, revertRun);
else
} else {
root.lastError = root.revertReason;
}
}
// Clears any stored override for an output so it returns to the value