Finish the wonderland: System told truthfully, in eight tabs instead of ten

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-24 23:31:52 -04:00
parent 9ffaf45a4d
commit be0e55214b
57 changed files with 5040 additions and 925 deletions
@@ -33,16 +33,100 @@ Singleton {
// so the UI can stop claiming the new locale is already in use.
property bool pendingRestart: false
// Guards read the Process objects directly; a derived binding is stale
// inside the handler that changes it. See DefaultApps.qml.
readonly property bool busy: apply.running || applyCategory.running || root.scanning
readonly property string currentLabel: {
const match = root.locales.find(locale => locale.value === root.current);
return match ? match.label : root.current;
}
// ── Per-category formats ────────────────────────────────────────────────
//
// Reading in one language while writing dates, numbers and currency the way
// your country does is the ordinary case. Each category is an OVERRIDE, and
// its absence -- "" here -- means "match language", which is not the same
// as an override that happens to equal LANG today: the two diverge the
// moment the language changes.
//
// Same restart discipline as the language itself. localectl only affects
// programs started afterwards, so an accepted change sets pendingRestart
// and the page says a sign-out is needed rather than claiming the new
// format is already in use.
readonly property var categories:
["LC_TIME", "LC_NUMERIC", "LC_MONETARY", "LC_MEASUREMENT", "LC_PAPER"]
property var categoryValues: ({})
// Bumped whenever an override is read or accepted, and read at the top of
// categoryValue() so a binding built on that call has something to
// invalidate. A bare function call captures no dependency and every reader
// would go stale -- see DesktopPreferences.get() for the same reason.
property int categoryRevision: 0
// "" means match language. Anything else is an installed locale name.
function categoryValue(category: string): string {
root.categoryRevision;
return String(root.categoryValues[category] ?? "");
}
function categoryLabel(category: string): string {
const value = root.categoryValue(category);
if (value === "")
return "Match language";
const match = root.locales.find(locale => locale.value === value);
return match ? match.label : value;
}
// Pass "" to remove the override. Refused for anything that is not one of
// the five categories: this reaches a privileged command, and the caller
// does not get to name the variable being written.
function setCategory(category: string, locale: string): bool {
if (root.categories.indexOf(category) < 0)
return false;
if (locale !== "" && !root.locales.some(entry => entry.value === locale))
return false;
if (root.categoryValue(category) === locale)
return true;
// A click while a change is still applying is queued rather than
// fired: assigning running = true to a running Process is a no-op, so
// a second setCategory would be silently dropped and applyCategory's
// handler would then adopt it as though it had been applied. Same
// queue discipline as set() above.
root.requestedCategories = Object.assign({}, root.requestedCategories);
root.requestedCategories[category] = locale;
if (!applyCategory.running)
root._applyNextCategory();
return true;
}
property var requestedCategories: ({})
function _applyNextCategory(): void {
const keys = Object.keys(root.requestedCategories);
if (keys.length === 0)
return;
const category = keys[0];
const value = String(root.requestedCategories[category]);
const remaining = Object.assign({}, root.requestedCategories);
delete remaining[category];
root.requestedCategories = remaining;
applyCategory.pendingCategory = category;
applyCategory.pendingValue = value;
applyCategory.command = [root.helperPath, "set", category, value];
applyCategory.running = true;
}
function refresh(): void {
if (root.scanning)
return;
root.scanning = true;
readCurrent.running = true;
readCategories.running = true;
list.running = true;
}
@@ -95,6 +179,49 @@ Singleton {
}
}
Process {
id: readCategories
command: [root.helperPath, "overrides"]
stdout: StdioCollector {
onStreamFinished: {
try {
const parsed = JSON.parse(this.text);
root.categoryValues = (parsed && typeof parsed === "object") ? parsed : ({});
} catch (error) {
root.categoryValues = ({});
console.warn("SystemLocale: could not parse the format overrides:", error);
}
root.categoryRevision += 1;
}
}
}
Process {
id: applyCategory
property string pendingCategory: ""
property string pendingValue: ""
// A refused change -- polkit dismissed, or a locale the system does not
// have -- must not move the UI. The value is only adopted on a zero
// exit, exactly as the language apply above does.
onExited: code => {
if (code === 0) {
const next = Object.assign({}, root.categoryValues);
next[applyCategory.pendingCategory] = applyCategory.pendingValue;
root.categoryValues = next;
root.categoryRevision += 1;
root.pendingRestart = true;
root.lastError = "";
} else {
root.lastError = "The system did not accept that format. It may have needed a password.";
}
applyCategory.pendingCategory = "";
applyCategory.pendingValue = "";
root._applyNextCategory();
}
}
Process {
id: apply