3d1168ff8c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
279 lines
10 KiB
Python
Executable File
279 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
NexusOS action-icon generator.
|
|
|
|
Builds the icons the XFCE logoff dialog (xfce4-session-logout) and the
|
|
Whisker menu command buttons request — shutdown / reboot / log-out /
|
|
suspend / hibernate / switch-user / lock-screen / settings — in the
|
|
NexusOS palette (white glyph on a deep-purple disc with a lime ring).
|
|
|
|
Emits one SVG per icon into actions/, rasterizes to
|
|
~/.icons/NexusOS/<size>/actions/<name>.png at 16/22/24/32/48/64/128,
|
|
then writes the alias copies (xfsm-*, system-suspend-hibernate, and the
|
|
settings-manager app-icon names that Whisker's Settings button uses).
|
|
|
|
Run: python3 build_actions.py # SVGs + PNGs + aliases
|
|
python3 build_actions.py --svg-only # just rewrite SVGs
|
|
"""
|
|
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
ACTIONS = ROOT / "actions"
|
|
ICONS_OUT = Path.home() / ".icons" / "NexusOS"
|
|
SIZES = [16, 22, 24, 32, 48, 64, 128]
|
|
|
|
# ── colors (mirror gtk-3.0/colors.css) ────────────────────────────────
|
|
BODY = "#5e0066" # brand_purple_dark — disc fill
|
|
RING = "#8cc63f" # brand_green — disc ring
|
|
GLYPH = "#f2f2f2" # text_primary — glyph
|
|
|
|
# ── master: purple disc + lime ring, viewBox 0 0 128 128 ──────────────
|
|
# Glyph area: a ~70px box centered on (64, 64).
|
|
DISC = f' <circle cx="64" cy="64" r="52" fill="{BODY}" ' \
|
|
f'stroke="{RING}" stroke-width="6"/>\n'
|
|
|
|
|
|
def _gear_glyph() -> str:
|
|
"""Settings cog: 8 teeth on a white disc with a punched centre hole."""
|
|
teeth = "".join(
|
|
f' <rect x="57" y="27" width="14" height="21" rx="2" '
|
|
f'transform="rotate({k * 45} 64 64)" fill="{GLYPH}"/>\n'
|
|
for k in range(8)
|
|
)
|
|
return (
|
|
" <!-- Settings cog -->\n"
|
|
+ teeth
|
|
+ f' <circle cx="64" cy="64" r="25" fill="{GLYPH}"/>\n'
|
|
+ f' <circle cx="64" cy="64" r="10" fill="{BODY}"/>\n'
|
|
)
|
|
|
|
|
|
# ── glyph fragments — white, centered on (64, 64) ─────────────────────
|
|
GLYPHS = {
|
|
"system-shutdown": f"""\
|
|
<!-- Power symbol: broken ring (gap at top) + stem -->
|
|
<path d="M 55 51 A 22 22 0 1 0 73 51" fill="none" stroke="{GLYPH}"
|
|
stroke-width="10" stroke-linecap="round"/>
|
|
<rect x="59" y="35" width="10" height="31" rx="5" fill="{GLYPH}"/>
|
|
""",
|
|
|
|
"system-reboot": f"""\
|
|
<!-- Circular arrow -->
|
|
<path d="M 64 40 A 24 24 0 1 1 41 71" fill="none" stroke="{GLYPH}"
|
|
stroke-width="9" stroke-linecap="round"/>
|
|
<path d="M 50 32 L 70 40 L 56 56 Z" fill="{GLYPH}"/>
|
|
""",
|
|
|
|
"system-log-out": f"""\
|
|
<!-- Doorway bracket, open on the right -->
|
|
<path d="M 72 34 L 42 34 L 42 94 L 72 94" fill="none" stroke="{GLYPH}"
|
|
stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<!-- Arrow exiting to the right -->
|
|
<path d="M 56 64 L 92 64" fill="none" stroke="{GLYPH}"
|
|
stroke-width="9" stroke-linecap="round"/>
|
|
<path d="M 80 50 L 96 64 L 80 78" fill="none" stroke="{GLYPH}"
|
|
stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
|
|
""",
|
|
|
|
"system-suspend": f"""\
|
|
<!-- Crescent moon: big disc minus a disc shifted right -->
|
|
<path d="M 74 33 A 33 33 0 1 0 74 95 A 33 33 0 0 1 74 33 Z"
|
|
fill="{GLYPH}"/>
|
|
""",
|
|
|
|
"system-hibernate": f"""\
|
|
<!-- Crescent moon + Z -->
|
|
<path d="M 66 39 A 28 28 0 1 0 66 91 A 28 28 0 0 1 66 39 Z"
|
|
fill="{GLYPH}"/>
|
|
<path d="M 78 34 L 99 34 L 78 56 L 99 56" fill="none" stroke="{GLYPH}"
|
|
stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
|
""",
|
|
|
|
"system-switch-user": f"""\
|
|
<!-- Two person silhouettes -->
|
|
<circle cx="78" cy="56" r="13" fill="{GLYPH}"/>
|
|
<path d="M 56 96 Q 56 72 78 72 Q 100 72 100 96 Z" fill="{GLYPH}"/>
|
|
<circle cx="48" cy="60" r="12" fill="{GLYPH}"/>
|
|
<path d="M 28 96 Q 28 76 48 76 Q 60 76 65 84 L 56 88
|
|
Q 56 84 56 96 Z" fill="{GLYPH}"/>
|
|
""",
|
|
|
|
"system-lock-screen": f"""\
|
|
<!-- Padlock -->
|
|
<path d="M 52 64 L 52 50 A 12 12 0 0 1 76 50 L 76 64" fill="none"
|
|
stroke="{GLYPH}" stroke-width="8" stroke-linecap="round"/>
|
|
<rect x="40" y="62" width="48" height="40" rx="7" fill="{GLYPH}"/>
|
|
<circle cx="64" cy="78" r="6" fill="{BODY}"/>
|
|
<rect x="61" y="80" width="6" height="13" rx="3" fill="{BODY}"/>
|
|
""",
|
|
|
|
"preferences-desktop": _gear_glyph(),
|
|
|
|
# ── Whisker menu application-category glyphs ──────────────────────
|
|
"applications-internet": f"""\
|
|
<!-- Globe -->
|
|
<circle cx="64" cy="64" r="28" fill="none" stroke="{GLYPH}" stroke-width="7"/>
|
|
<ellipse cx="64" cy="64" rx="12" ry="28" fill="none" stroke="{GLYPH}" stroke-width="7"/>
|
|
<line x1="38" y1="55" x2="90" y2="55" stroke="{GLYPH}" stroke-width="7" stroke-linecap="round"/>
|
|
<line x1="38" y1="73" x2="90" y2="73" stroke="{GLYPH}" stroke-width="7" stroke-linecap="round"/>
|
|
""",
|
|
|
|
"applications-development": f"""\
|
|
<!-- Code chevrons + slash -->
|
|
<path d="M 52 42 L 34 64 L 52 86" fill="none" stroke="{GLYPH}"
|
|
stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<path d="M 76 42 L 94 64 L 76 86" fill="none" stroke="{GLYPH}"
|
|
stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<line x1="71" y1="38" x2="57" y2="90" stroke="{GLYPH}"
|
|
stroke-width="8" stroke-linecap="round"/>
|
|
""",
|
|
|
|
"applications-multimedia": f"""\
|
|
<!-- Play triangle -->
|
|
<path d="M 51 40 L 51 88 L 92 64 Z" fill="{GLYPH}"/>
|
|
""",
|
|
|
|
"applications-office": f"""\
|
|
<!-- Document with folded corner + text lines -->
|
|
<path d="M 44 32 L 74 32 L 86 44 L 86 96 L 44 96 Z" fill="{GLYPH}"/>
|
|
<path d="M 74 32 L 74 44 L 86 44 Z" fill="{BODY}"/>
|
|
<line x1="52" y1="58" x2="78" y2="58" stroke="{BODY}" stroke-width="5" stroke-linecap="round"/>
|
|
<line x1="52" y1="70" x2="78" y2="70" stroke="{BODY}" stroke-width="5" stroke-linecap="round"/>
|
|
<line x1="52" y1="82" x2="70" y2="82" stroke="{BODY}" stroke-width="5" stroke-linecap="round"/>
|
|
""",
|
|
|
|
"applications-graphics": f"""\
|
|
<!-- Framed picture: sun + mountains -->
|
|
<rect x="34" y="40" width="60" height="48" rx="5" fill="none" stroke="{GLYPH}" stroke-width="7"/>
|
|
<circle cx="51" cy="55" r="6" fill="{GLYPH}"/>
|
|
<path d="M 40 84 L 56 63 L 67 74 L 78 58 L 88 84 Z" fill="{GLYPH}"/>
|
|
""",
|
|
|
|
"applications-system": f"""\
|
|
<!-- Monitor -->
|
|
<rect x="32" y="36" width="64" height="44" rx="5" fill="{GLYPH}"/>
|
|
<rect x="40" y="44" width="48" height="28" rx="2" fill="{BODY}"/>
|
|
<rect x="56" y="80" width="16" height="9" fill="{GLYPH}"/>
|
|
<rect x="44" y="89" width="40" height="7" rx="3" fill="{GLYPH}"/>
|
|
""",
|
|
|
|
"applications-utilities": f"""\
|
|
<!-- Open-end wrench -->
|
|
<g transform="rotate(45 64 64)">
|
|
<rect x="58" y="44" width="12" height="44" rx="5" fill="{GLYPH}"/>
|
|
<circle cx="64" cy="44" r="14" fill="{GLYPH}"/>
|
|
<rect x="57" y="27" width="14" height="17" fill="{BODY}"/>
|
|
</g>
|
|
""",
|
|
|
|
"applications-games": f"""\
|
|
<!-- Gamepad -->
|
|
<rect x="36" y="50" width="56" height="30" rx="15" fill="{GLYPH}"/>
|
|
<rect x="48" y="62.5" width="16" height="5" rx="1" fill="{BODY}"/>
|
|
<rect x="53.5" y="57" width="5" height="16" rx="1" fill="{BODY}"/>
|
|
<circle cx="76" cy="60" r="3.5" fill="{BODY}"/>
|
|
<circle cx="84" cy="68" r="3.5" fill="{BODY}"/>
|
|
""",
|
|
|
|
"applications-science": f"""\
|
|
<!-- Erlenmeyer flask -->
|
|
<path d="M 56 34 L 56 58 L 38 88 Q 35 95 43 95 L 85 95 Q 93 95 90 88 L 72 58 L 72 34"
|
|
fill="none" stroke="{GLYPH}" stroke-width="7" stroke-linejoin="round"/>
|
|
<line x1="51" y1="34" x2="77" y2="34" stroke="{GLYPH}" stroke-width="8" stroke-linecap="round"/>
|
|
<path d="M 51 76 L 77 76 L 86 88 Q 89 93 83 93 L 45 93 Q 39 93 42 88 Z" fill="{GLYPH}"/>
|
|
""",
|
|
}
|
|
|
|
# Extra filenames that render the same art (XFCE's xfsm-* lookup names,
|
|
# the hybrid-sleep alias, and the Whisker command-button names).
|
|
ALIASES = {
|
|
"xfsm-logout": "system-log-out",
|
|
"xfsm-reboot": "system-reboot",
|
|
"xfsm-shutdown": "system-shutdown",
|
|
"xfsm-suspend": "system-suspend",
|
|
"xfsm-hibernate": "system-hibernate",
|
|
"xfsm-switch-user": "system-switch-user",
|
|
"system-suspend-hibernate": "system-suspend",
|
|
"xfsm-lock": "system-lock-screen",
|
|
"preferences-system": "preferences-desktop",
|
|
"org.xfce.settings.manager": "preferences-desktop",
|
|
"xfce4-settings-manager": "preferences-desktop",
|
|
# XFCE's "Accessories" menu uses xfce-accessories.directory
|
|
# (Icon=applications-accessories), NOT the freedesktop Utility name, so
|
|
# it needs the same wrench/tools badge as applications-utilities.
|
|
"applications-accessories": "applications-utilities",
|
|
}
|
|
|
|
|
|
def svg_for(glyph: str) -> str:
|
|
return (
|
|
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
|
'<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" '
|
|
'viewBox="0 0 128 128">\n'
|
|
f"{DISC}{glyph}</svg>\n"
|
|
)
|
|
|
|
|
|
def write_svgs() -> list[str]:
|
|
ACTIONS.mkdir(parents=True, exist_ok=True)
|
|
for name, glyph in GLYPHS.items():
|
|
(ACTIONS / f"{name}.svg").write_text(svg_for(glyph))
|
|
return list(GLYPHS)
|
|
|
|
|
|
def render_pngs(names: list[str]) -> None:
|
|
for name in names:
|
|
svg = ACTIONS / f"{name}.svg"
|
|
for size in SIZES:
|
|
out_dir = ICONS_OUT / f"{size}x{size}" / "actions"
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
out = out_dir / f"{name}.png"
|
|
subprocess.run(
|
|
[
|
|
"inkscape",
|
|
str(svg),
|
|
"--export-type=png",
|
|
f"--export-filename={out}",
|
|
f"--export-width={size}",
|
|
f"--export-height={size}",
|
|
],
|
|
check=True,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
)
|
|
print(f" rendered {name} @ {size}")
|
|
|
|
|
|
def write_aliases() -> None:
|
|
for alias, src in ALIASES.items():
|
|
for size in SIZES:
|
|
d = ICONS_OUT / f"{size}x{size}" / "actions"
|
|
src_png = d / f"{src}.png"
|
|
if src_png.exists():
|
|
shutil.copy2(src_png, d / f"{alias}.png")
|
|
print(f" alias {alias} -> {src}")
|
|
|
|
|
|
def main() -> None:
|
|
svg_only = "--svg-only" in sys.argv
|
|
names = write_svgs()
|
|
print(f"Wrote {len(names)} SVGs into {ACTIONS}")
|
|
if svg_only:
|
|
return
|
|
render_pngs(names)
|
|
write_aliases()
|
|
subprocess.run(
|
|
["gtk-update-icon-cache", "-f", str(ICONS_OUT)],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
)
|
|
print("\nDone. Reload: xfdesktop --reload & xfce4-panel -r")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|