aboutsummaryrefslogtreecommitdiffstats
path: root/appearance/Steps.qml
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-29 11:56:15 +0200
committerDanilo M. <danix@danix.xyz>2026-09-29 11:56:15 +0200
commit2d7b18d52ee7e7f1aac132180b7eb3b951735668 (patch)
tree735e3a35be639b987800c9ab5e6646504ae3b0cf /appearance/Steps.qml
parent9b83656e3e3d164fc75bbf679cf32357375660c4 (diff)
downloadquickshell-2d7b18d52ee7e7f1aac132180b7eb3b951735668.tar.gz
quickshell-2d7b18d52ee7e7f1aac132180b7eb3b951735668.zip
feat(appearance): steps window, icon regen, cursor out of hypr-theme
StepsWindow shows unified-desktop-theme's pending manual steps, one tab per consumer, centred on DP-3 on the top layer with on-demand focus so it can stay open while commands are pasted elsewhere. udt-steps writes the files and calls the new `steps` IPC verb; indented lines become command blocks with a copy button. The Theme tab pipes install.sh's output into it. The icon switch now also reruns udt-accent (SDDM power icons) and waybar-theme-udt's install.sh (taskbar theme, module icons), chained after the gsettings write both read. Cursor changes write ~/.config/udt/cursor.lua instead of editing hypr-theme's tracked environment.lua. The theme notice and the Udt.qml comment claimed install.sh reloads nothing; it reloads Hyprland, waybar, kitty and conky now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'appearance/Steps.qml')
-rw-r--r--appearance/Steps.qml106
1 files changed, 106 insertions, 0 deletions
diff --git a/appearance/Steps.qml b/appearance/Steps.qml
new file mode 100644
index 0000000..5bab158
--- /dev/null
+++ b/appearance/Steps.qml
@@ -0,0 +1,106 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+pragma Singleton
+
+import Quickshell
+import Quickshell.Io
+import QtQuick
+
+// The manual steps unified-desktop-theme cannot run itself: root copies into
+// /boot and /usr/share/sddm, the finish on the homepage host, and the last
+// install.sh log. Each is a file in ~/.cache/udt/steps/, written by
+// udt-steps, which then calls this shell's `steps` IPC verb. Nothing is
+// watched: the poke is the signal, and a shell started later finds the files
+// on its first reload.
+Singleton {
+ id: root
+
+ readonly property string dir: `${Quickshell.env("HOME")}/.cache/udt/steps`
+
+ // [{ name, blocks: [{ cmd, text }] }], sorted by name.
+ property var tabs: []
+ signal loaded
+
+ // A line indented two spaces is a command, and a run of them is one block
+ // with its own copy button. Blank lines split prose into paragraphs. The
+ // format is what the producers already print to a terminal.
+ function parse(text) {
+ const out = [];
+ const lines = text.split("\n");
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ const cmd = line.startsWith(" ");
+ const last = out.length ? out[out.length - 1] : null;
+ if (!cmd && line.trim() === "") {
+ if (last) last.closed = true;
+ continue;
+ }
+ const body = cmd ? line.slice(2) : line;
+ if (last && last.cmd === cmd && !last.closed) last.lines.push(body);
+ else out.push({ cmd: cmd, lines: [body], closed: false });
+ }
+ return out.map(b => ({ cmd: b.cmd, text: b.lines.join("\n") }));
+ }
+
+ function commands(tab) {
+ return tab.blocks.filter(b => b.cmd).map(b => b.text).join("\n");
+ }
+
+ function copy(text) {
+ Quickshell.execDetached(["wl-copy", "--", text]);
+ }
+
+ function refresh() {
+ listProc.running = false;
+ listProc.running = true;
+ }
+
+ function dismiss(name) {
+ rmProc.command = ["rm", "-f", `${root.dir}/${name}`];
+ rmProc.running = true;
+ }
+
+ // One pass over every file, each prefixed by an ASCII record separator
+ // and its name. The names are udt-steps' [a-z0-9-], and its temporary
+ // files are dotfiles, which the glob skips.
+ Process {
+ id: listProc
+ command: ["sh", "-c",
+ `cd ${JSON.stringify(root.dir)} 2>/dev/null || exit 0; ` +
+ `for f in *; do [ -f "$f" ] && printf '\\036%s\\n' "$f" && cat "$f"; done`]
+ stdout: StdioCollector {
+ onStreamFinished: {
+ root.tabs = text.split("\x1e").filter(s => s.length).map(rec => {
+ const nl = rec.indexOf("\n");
+ return { name: rec.slice(0, nl), blocks: root.parse(rec.slice(nl + 1)) };
+ });
+ root.loaded();
+ }
+ }
+ }
+
+ Process {
+ id: rmProc
+ onExited: root.refresh()
+ }
+
+ function selftest(): string {
+ const b = root.parse("Intro line\nstill intro\n cmd one\n cmd two\n\nNext para\n cmd three\n");
+ if (b.length !== 4 || b[0].cmd || b[0].text !== "Intro line\nstill intro" ||
+ !b[1].cmd || b[1].text !== "cmd one\ncmd two" || b[2].text !== "Next para" ||
+ b[3].text !== "cmd three")
+ return `SELFTEST Steps FAIL: ${JSON.stringify(b)}`;
+ if (root.commands({ blocks: b }) !== "cmd one\ncmd two\ncmd three")
+ return "SELFTEST Steps FAIL: commands";
+ return "SELFTEST Steps PASS";
+ }
+}