#!/usr/bin/env python3 """Build NexusOS GTK2 theme from Mint-Y-Dark-Aqua base. - Copies .rc files + assets/ from /usr/share/themes/Mint-Y-Dark-Aqua/gtk-2.0/ - Recolors PNGs containing the Aqua accent (#1f9ede) to NexusOS green (#8cc63f) - Rewrites the gtk-color-scheme block in gtkrc to NexusOS palette - Replaces remaining aqua hex literals in .rc files """ 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/gtk-2.0") OUT = THEMES_ROOT / "NexusOS" / "gtk-2.0" 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) HUE_TOLERANCE = 30 / 360 SAT_FLOOR = 0.18 S_SCALE = GREEN_HSV[1] / AQUA_HSV[1] V_SCALE = GREEN_HSV[2] / AQUA_HSV[2] 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) h, s, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255) if s < SAT_FLOOR or hue_distance(h, AQUA_HSV[0]) > HUE_TOLERANCE: return (r, g, b, a) 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) def recolor_png(path: Path) -> int: """Recolor aqua pixels in-place. Returns count of pixels changed.""" 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 # ── gtkrc color-scheme block ────────────────────────────────────────── NEW_COLOR_SCHEME = ( f"base_color:#{P.BASE_BG}\\n" f"fg_color:#{P.TEXT_PRIMARY}\\n" f"tooltip_fg_color:#{P.TEXT_PRIMARY}\\n" f"selected_bg_color:#{P.BRAND_PURPLE_DARK}\\n" f"selected_fg_color:#{P.TEXT_ON_SELECTION}\\n" f"text_color:#{P.TEXT_PRIMARY}\\n" f"bg_color:#{P.SURFACE_BG}\\n" f"insensitive_bg_color:#{P.SURFACE_BG}\\n" f"insensitive_fg_color:#{P.TEXT_DISABLED}\\n" f"notebook_bg:#{P.BASE_BG}\\n" f"dark_sidebar_bg:#{P.SURFACE_BG_ALT}\\n" f"tooltip_bg_color:#{P.OVERLAY_BG}\\n" f"link_color:#{P.BRAND_GREEN_LIGHT}\\n" f"menu_bg:#{P.SURFACE_BG}\\n" f"menu_separator_color:#{P.BORDER}" ) def patch_gtkrc(path: Path) -> None: text = path.read_text() out_lines = [] for line in text.splitlines(): if line.startswith("gtk-color-scheme"): out_lines.append(f'gtk-color-scheme = "{NEW_COLOR_SCHEME}"') else: out_lines.append(line) path.write_text("\n".join(out_lines) + "\n") # ── Remaining hex literals in .rc files ─────────────────────────────── # Captured from Mint-Y-Dark-Aqua; map source hex → NexusOS replacement. RC_HEX_REPLACEMENTS = { # accent / focus / selection "#1f9ede": f"#{P.BRAND_GREEN}", "#1F9EDE": f"#{P.BRAND_GREEN}", "#5294E2": f"#{P.BRAND_GREEN_LIGHT}", # link_color fallback if quoted literal "#5294e2": f"#{P.BRAND_GREEN_LIGHT}", # Mint-Y greys we want to nudge onto our surface family "#2b2b2b": f"#{P.SURFACE_BG_ALT}", "#383838": f"#{P.SURFACE_BG}", "#404040": f"#{P.BASE_BG}", "#3e3e3e": f"#{P.SURFACE_BG}", "#dadada": f"#{P.TEXT_PRIMARY}", "#DADADA": f"#{P.TEXT_PRIMARY}", "#d3d3d3": f"#{P.TEXT_PRIMARY}", "#D3D3D3": f"#{P.TEXT_PRIMARY}", } def patch_rc(path: Path) -> int: text = path.read_text() count = 0 for src, dst in RC_HEX_REPLACEMENTS.items(): n = text.count(src) if n: text = text.replace(src, dst) count += n path.write_text(text) return count def main() -> None: if OUT.exists(): shutil.rmtree(OUT) shutil.copytree(SRC, OUT) print(f"Copied {SRC} → {OUT}") # Recolor PNGs recolored = 0 for png in sorted((OUT / "assets").glob("*.png")): changed = recolor_png(png) if changed: recolored += 1 print(f" recolored {png.name}: {changed}px") print(f"Recolored {recolored} PNGs") # Patch rc files patched_total = 0 for rc in sorted(OUT.glob("*.rc")): n = patch_rc(rc) if n: print(f" patched {rc.name}: {n} replacements") patched_total += n gtkrc = OUT / "gtkrc" patch_rc(gtkrc) patch_gtkrc(gtkrc) print(f"Rewrote gtkrc color scheme; total .rc hex replacements: {patched_total}") if __name__ == "__main__": main()