#!/usr/bin/env python3 """Build NexusOS xfwm4 theme from Mint-Y-Dark-Aqua base. - Copies all xfwm4 PNGs + themerc - Aqua pixels (close-active/pressed glyph) → NexusOS green via HSV hue swap - Dark Mint-Y greys repainted to NexusOS surface_bg_alt / border_strong - themerc title text colors updated to NexusOS palette """ from __future__ import annotations import colorsys import shutil import sys from pathlib import Path THEMES_ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(THEMES_ROOT)) import _palette as P # noqa: E402 from PIL import Image # noqa: E402 SRC = Path("/usr/share/themes/Mint-Y-Dark-Aqua/xfwm4") OUT = THEMES_ROOT / "NexusOS" / "xfwm4" # Aqua → green HSV swap (identical to gtk2 build) AQUA_RGB = (0x1F, 0x9E, 0xDE) AQUA_HSV = colorsys.rgb_to_hsv(*(c / 255 for c in AQUA_RGB)) GREEN_HSV = P.hex_to_hsv(P.BRAND_GREEN) S_SCALE = GREEN_HSV[1] / AQUA_HSV[1] V_SCALE = GREEN_HSV[2] / AQUA_HSV[2] HUE_TOLERANCE = 30 / 360 SAT_FLOOR = 0.18 # Gray repaints — direct RGB substitutions GRAY_REMAP = { (34, 34, 38): P.hex_to_rgb(P.SURFACE_BG_ALT), (46, 46, 46): P.hex_to_rgb(P.BORDER_STRONG), (36, 36, 39): P.hex_to_rgb(P.SURFACE_BG_ALT), (76, 76, 80): P.hex_to_rgb(P.BORDER_STRONG), (85, 85, 89): (130, 130, 134), (106, 106, 109): (150, 150, 154), } def hue_distance(a: float, b: float) -> float: d = abs(a - b) return min(d, 1 - d) def recolor_pixel(r: int, g: int, b: int, a: int) -> tuple[int, int, int, int]: if a < 8: return (r, g, b, a) # Direct gray remap if (r, g, b) in GRAY_REMAP: nr, ng, nb = GRAY_REMAP[(r, g, b)] return (nr, ng, nb, a) # Aqua → green h, s, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255) if s >= SAT_FLOOR and hue_distance(h, AQUA_HSV[0]) <= HUE_TOLERANCE: new_s = min(1.0, s * S_SCALE) new_v = min(1.0, v * V_SCALE) nr, ng, nb = colorsys.hsv_to_rgb(GREEN_HSV[0], new_s, new_v) return (round(nr * 255), round(ng * 255), round(nb * 255), a) return (r, g, b, a) def recolor_png(path: Path) -> int: im = Image.open(path).convert("RGBA") px = im.load() w, h = im.size changed = 0 for y in range(h): for x in range(w): r, g, b, a = px[x, y] new = recolor_pixel(r, g, b, a) if new != (r, g, b, a): px[x, y] = new changed += 1 if changed: im.save(path, optimize=True) return changed THEMERC_REPLACEMENTS = { "active_text_color=#e3e3e3": f"active_text_color=#{P.TEXT_PRIMARY}", "active_text_shadow_color=#e3e3e3": f"active_text_shadow_color=#{P.TEXT_PRIMARY}", "inactive_text_color=#acacac": f"inactive_text_color=#{P.TEXT_SECONDARY}", "inactive_text_shadow_color=#acacac": f"inactive_text_shadow_color=#{P.TEXT_SECONDARY}", } def patch_themerc(path: Path) -> None: text = path.read_text() for src, dst in THEMERC_REPLACEMENTS.items(): text = text.replace(src, dst) path.write_text(text) def main() -> None: if OUT.exists(): shutil.rmtree(OUT) shutil.copytree(SRC, OUT) print(f"Copied {SRC} → {OUT}") recolored = 0 for png in sorted(OUT.glob("*.png")): changed = recolor_png(png) if changed: recolored += 1 print(f"Recolored {recolored} PNGs") patch_themerc(OUT / "themerc") print("Patched themerc") if __name__ == "__main__": main()