Review everything shipped this weekend, and fix what the reviewers caught
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user