Skip to content

Commit d0b3ba0

Browse files
committed
feat(pi): sync theme with hyprland changes
1 parent 82d18a8 commit d0b3ba0

3 files changed

Lines changed: 100 additions & 1 deletion

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import fs from "node:fs";
2+
import os from "node:os";
3+
import path from "node:path";
4+
5+
const CURRENT_THEME_FILE = path.join(os.homedir(), ".config", "hypr", "current-theme");
6+
const POLL_MS = 1000;
7+
8+
const THEME_ALIASES = {
9+
"wellpunk-dark": ["wellpunk-dark", "dark"],
10+
"wellpunk-light": ["wellpunk-light", "light"],
11+
tokyonight: ["tokyonight", "dark"],
12+
vantablack: ["wellpunk-dark", "dark"],
13+
black: ["wellpunk-dark", "dark"],
14+
dark: ["wellpunk-dark", "dark"],
15+
white: ["wellpunk-light", "light"],
16+
light: ["wellpunk-light", "light"],
17+
};
18+
19+
function readHyprTheme() {
20+
try {
21+
return fs.readFileSync(CURRENT_THEME_FILE, "utf8").trim();
22+
} catch {
23+
return undefined;
24+
}
25+
}
26+
27+
function resolvePiTheme(hyprTheme, ctx) {
28+
if (!hyprTheme) return undefined;
29+
30+
const available = new Set(ctx.ui.getAllThemes().map((theme) => theme.name));
31+
const candidates = THEME_ALIASES[hyprTheme] ?? [hyprTheme];
32+
33+
for (const candidate of candidates) {
34+
if (available.has(candidate)) return candidate;
35+
}
36+
37+
return undefined;
38+
}
39+
40+
export default function hyprThemeSync(pi) {
41+
let watcher = null;
42+
let interval = null;
43+
let lastHyprTheme;
44+
let lastPiTheme;
45+
let debounce = null;
46+
47+
pi.on("session_start", (_event, ctx) => {
48+
const apply = () => {
49+
const hyprTheme = readHyprTheme();
50+
const piTheme = resolvePiTheme(hyprTheme, ctx);
51+
52+
if (!piTheme || (hyprTheme === lastHyprTheme && piTheme === lastPiTheme)) {
53+
return;
54+
}
55+
56+
const result = ctx.ui.setTheme(piTheme);
57+
if (result.success) {
58+
lastHyprTheme = hyprTheme;
59+
lastPiTheme = piTheme;
60+
} else {
61+
ctx.ui.notify(`hypr-theme-sync: failed to apply ${piTheme}: ${result.error}`, "error");
62+
}
63+
};
64+
65+
const scheduleApply = () => {
66+
if (debounce) clearTimeout(debounce);
67+
debounce = setTimeout(() => {
68+
debounce = null;
69+
apply();
70+
}, 100);
71+
};
72+
73+
apply();
74+
75+
try {
76+
watcher = fs.watch(CURRENT_THEME_FILE, scheduleApply);
77+
} catch {
78+
// The file may not exist yet on first install. Polling below covers that case.
79+
}
80+
81+
interval = setInterval(apply, POLL_MS);
82+
});
83+
84+
pi.on("session_shutdown", () => {
85+
if (debounce) {
86+
clearTimeout(debounce);
87+
debounce = null;
88+
}
89+
if (watcher) {
90+
watcher.close();
91+
watcher = null;
92+
}
93+
if (interval) {
94+
clearInterval(interval);
95+
interval = null;
96+
}
97+
});
98+
}

config/pi/manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"agent/extensions/gpt-auto-router/README.md",
1818
"agent/extensions/gpt-auto-router/index.ts",
1919
"agent/extensions/guardrails.json",
20+
"agent/extensions/hypr-theme-sync.js",
2021
"agent/extensions/list-google-models.js",
2122
"agent/extensions/pi-config-backup/README.md",
2223
"agent/extensions/pi-config-backup/index.js",

setup/lib/theme_orchestrator.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ reload|kitty SIGUSR1
118118
reload|tmux source-file
119119
reload|zsh SIGUSR1
120120
reload|mako reload
121-
reload|pi-settings theme
121+
reload|pi hypr-theme-sync extension
122122
PLAN
123123
}
124124

0 commit comments

Comments
 (0)