Shortcuts you invent, rules you write, gestures you own - all still just data

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-25 01:51:59 -04:00
parent f9e5d3f470
commit 06c53d6c21
48 changed files with 4749 additions and 140 deletions
@@ -652,6 +652,81 @@ Singleton {
root.applyOptions({ directScanoutPolicy: policy });
}
// ── Color filters ───────────────────────────────────────────────────────
// The stored preference is an enum; what the compositor wants is a shader
// path. That mapping cannot be a `hypr:` block on the schema entry -- the
// read-back would compare "grayscale" against a filename and fail every
// shape and sweep contract -- so it lives here, and hypr/looks.lua does the
// same lookup for the value the config carries at launch.
//
// The shaders are installed by the hypr directory symlink, so the path is
// the deployed one rather than the repository's.
readonly property string shaderDir:
(Quickshell.env("XDG_CONFIG_HOME") || `${Quickshell.env("HOME")}/.config`) + "/hypr/shaders"
readonly property var colorFilterShaders: ({
"grayscale": "grayscale.frag",
"protanopia": "protanopia.frag",
"deuteranopia": "deuteranopia.frag",
"tritanopia": "tritanopia.frag"
})
// "" for none, and for any value this build does not ship a shader for --
// an unknown filter turns the filter off rather than leaving the previous
// one on under a new name.
function colorFilterPath(name: string): string {
const file = root.colorFilterShaders[String(name)];
return file === undefined ? "" : `${root.shaderDir}/${file}`;
}
property string colorFilterPending: ""
Process {
id: colorFilterWrite
stdout: StdioCollector {
onStreamFinished: {
// `hyprctl eval` exits 0 on a Lua error and reports it on
// stdout, so the exit status proves nothing. Read it back.
if (this.text.indexOf("error:") >= 0) {
root.lastError = "The color filter could not be applied.";
return;
}
colorFilterVerify.exec(["hyprctl", "-j", "getoption", "decoration:screen_shader"]);
}
}
}
Process {
id: colorFilterVerify
stdout: StdioCollector {
onStreamFinished: {
try {
const observed = String(JSON.parse(this.text).str ?? "");
if (observed !== root.colorFilterPending) {
root.lastError = "The compositor did not take the color filter.";
return;
}
root.lastError = "";
} catch (error) {
root.lastError = "The compositor did not say whether the color filter applied.";
}
}
}
}
// Applies the filter to the running compositor. The preference is written
// by the row that calls this; nothing here stores anything.
function applyColorFilter(name: string): void {
if (colorFilterWrite.running)
return;
const path = root.colorFilterPath(name);
root.colorFilterPending = path;
colorFilterWrite.exec(["hyprctl", "eval",
`hl.config({ decoration = { screen_shader = "${path.replace(/["\\]/g, "")}" } })`]);
}
// Replays every compositor-owned preference in one batch at shell start, so
// a value the user changed in Settings survives a reboot even though the
// Lua config only reads the file once, at launch.