Initial commit: NexusOS project + desktop theme baseline
Captures the known-good state after theme consolidation and consistency reconciliation: - NexusOS GTK/xfwm4/icon theme assets (assets/themes, management/Mint-Y-Nexus) - Tightened right-click menus, visible separators, no menu icons - assets/themes/install-theme.sh: idempotent restore of all wiring (symlinks, xfconf xsettings+xfwm4, GTK 3/4 settings.ini) - .gitignore excludes venv/ollama/models/runtime/db Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@@ -0,0 +1,30 @@
|
|||||||
|
# Large machine-specific / regenerable trees (never commit)
|
||||||
|
# Promethean/ = 16G ROCm venv (recreate from requirements*.txt)
|
||||||
|
# ollama/ = 7.4G bundled Ollama binary; models/ = 4.9G Ollama blobs
|
||||||
|
Promethean/
|
||||||
|
ollama/
|
||||||
|
models/
|
||||||
|
interface/web/node_modules/
|
||||||
|
|
||||||
|
# Runtime state & user data
|
||||||
|
runtime/
|
||||||
|
data/uploads/
|
||||||
|
data/exports/
|
||||||
|
*.db
|
||||||
|
*.db-wal
|
||||||
|
*.db-shm
|
||||||
|
|
||||||
|
# Build / cache artifacts
|
||||||
|
interface/web/dist/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
|
||||||
|
# Editor / DE / local cruft
|
||||||
|
.directory
|
||||||
|
*.bak
|
||||||
|
*.bak-*
|
||||||
|
*.tar.gz
|
||||||
|
|
||||||
|
# Local-only Claude config (machine-specific)
|
||||||
|
.claude/settings.local.json
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## What is NexusOS
|
||||||
|
|
||||||
|
NexusOS is a local AI assistant platform. It runs a Python/FastAPI backend (Synapse) that interfaces with a locally bundled Ollama instance, a dedicated memory microservice, and a React/Vite frontend. Everything runs on `localhost` with no external AI API calls — the LLM is served via Ollama.
|
||||||
|
|
||||||
|
## Running the Project
|
||||||
|
|
||||||
|
**Full stack (recommended):**
|
||||||
|
```bash
|
||||||
|
./launch_nexus.sh
|
||||||
|
```
|
||||||
|
This activates the `Promethean` venv, starts the memory service on port 8001, the Synapse backend on port 8000, and the Vite frontend (default port 5173).
|
||||||
|
|
||||||
|
**Individual services via CLI:**
|
||||||
|
```bash
|
||||||
|
# From nexus-core/ with Promethean venv active:
|
||||||
|
source Promethean/bin/activate
|
||||||
|
|
||||||
|
# Backend
|
||||||
|
uvicorn synapse.main:sio_app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
|
||||||
|
# Memory service
|
||||||
|
uvicorn synapse.memory.service:app --host 0.0.0.0 --port 8001 --reload
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
cd interface/web && npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**Management CLI** (start/stop individual services with PID tracking):
|
||||||
|
```bash
|
||||||
|
./management/nexus-cli.sh start # starts backend + frontend
|
||||||
|
./management/nexus-cli.sh stop
|
||||||
|
./management/nexus-cli.sh start_backend / start_frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
**Frontend lint:**
|
||||||
|
```bash
|
||||||
|
cd interface/web && npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
**Frontend build:**
|
||||||
|
```bash
|
||||||
|
cd interface/web && npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Python venv
|
||||||
|
All Python code runs inside `Promethean/` (a local venv). Always activate it before running backend commands: `source Promethean/bin/activate`. The AMD ROCm PyTorch stack is installed here (`requirements.txt`).
|
||||||
|
|
||||||
|
### Synapse Backend (`synapse/`)
|
||||||
|
FastAPI app at `synapse/main.py`. Key responsibilities:
|
||||||
|
- `/chat` and `/chat/stream` — chat with Ollama; streaming uses SSE. After each exchange the stream endpoint calls the Memory Service to auto-extract persistent facts.
|
||||||
|
- `/playbooks` — CRUD for playbooks stored in SQLite via `synapse/playbooks/store.py`.
|
||||||
|
- `/memory` — CRUD for persistent facts (proxies the same SQLite store as the memory service).
|
||||||
|
- `/models` — lists, pulls, and deletes Ollama models by proxying `ollama/bin/ollama`'s HTTP API.
|
||||||
|
- `/conversations` — persists and retrieves full chat history from SQLite.
|
||||||
|
|
||||||
|
**System prompt assembly** (in `main.py` `chat_stream_endpoint`): the final system prompt is built by layering the active playbook instructions → reference playbook context → persistent memory facts → relevant past conversation snippets retrieved by `store.search_conversations`.
|
||||||
|
|
||||||
|
### Memory Service (`synapse/memory/`)
|
||||||
|
A separate FastAPI app on port 8001. `service.py` exposes `/memories/extract` which calls `extractor.py` — an Ollama prompt that decides whether to persist a new fact from a conversation exchange. The main Synapse backend calls this asynchronously after each streaming response. Both services share the same SQLite database (`synapse/memory/memory.db`).
|
||||||
|
|
||||||
|
### Playbook System (`synapse/playbooks/` + `synapse/playbook_manager.py`)
|
||||||
|
Playbooks are ordered records (title, goal, instructions, tags) stored in SQLite. The **first** playbook by order is the active system prompt; all subsequent playbooks are injected as reference context. `PlaybookManager` is the thin class the backend uses to retrieve and render them.
|
||||||
|
|
||||||
|
### Ollama (`ollama/bin/ollama`)
|
||||||
|
A bundled Ollama binary lives at `ollama/bin/ollama`. `OllamaManager` in `synapse/ollama_manager.py` manages its lifecycle (start/stop/health-check) and selects the best available model. GPU detection uses Vulkan (`vulkaninfo`) to prefer discrete AMD/NVIDIA GPUs. The Ollama HTTP API is at `http://127.0.0.1:11434` (overridable via `OLLAMA_HOST` env var).
|
||||||
|
|
||||||
|
### Frontend (`interface/web/`)
|
||||||
|
React 19 + Vite. No routing library — `App.jsx` manages page state in a single `currentPage` useState. All API calls hit `http://localhost:8000` (configured in `src/config.js`). Pages: Chatbot, Playbook editor, Conversation History, Models, Memory, Settings.
|
||||||
|
|
||||||
|
### Persistent Storage
|
||||||
|
All data lands in `synapse/memory/memory.db` (SQLite, WAL mode). Tables: memory facts, conversations, messages, playbooks, app settings. `synapse/memory/store.py` (`PersistentMemoryStore`) owns the schema and all queries. `nexus_config.py` defines all paths; it also ensures all required directories exist on import.
|
||||||
|
|
||||||
|
### Logs & Runtime State
|
||||||
|
- `runtime/backend.log`, `runtime/frontend.log` — service stdout
|
||||||
|
- `runtime/logs/ollama.log`, `runtime/logs/chat.log`
|
||||||
|
- `runtime/pids/backend.pid`, `runtime/pids/frontend.pid` — used by the management CLI
|
||||||
|
|
||||||
|
## Key Config
|
||||||
|
|
||||||
|
| Concern | Location |
|
||||||
|
|---|---|
|
||||||
|
| Ollama host | `OLLAMA_HOST` env var (default `http://127.0.0.1:11434`) |
|
||||||
|
| All filesystem paths | `synapse/nexus_config.py` `Settings` class |
|
||||||
|
| Frontend API base URL | `interface/web/src/config.js` |
|
||||||
|
| Python dependencies (AMD) | `requirements.txt` / `amd_requirements.txt` |
|
||||||
|
| Python dependencies (base) | `requirements-base.txt` |
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# --- Force AMD/ROCm Priority ---
|
||||||
|
--index-url https://download.pytorch.org/whl/rocm7.2
|
||||||
|
--extra-index-url https://pypi.org/simple
|
||||||
|
|
||||||
|
-r requirements-base.txt
|
||||||
|
|
||||||
|
# GPU Compute Stack
|
||||||
|
torch
|
||||||
|
torchaudio
|
||||||
|
torchvision
|
||||||
|
After Width: | Height: | Size: 2.1 MiB |
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Bakes a real, dereferenced copy of the NexusOS Plymouth theme into the
|
||||||
|
# initramfs. The theme files live in nexus-core and are exposed at
|
||||||
|
# /usr/share/plymouth/themes/my-custom-logo via a symlink into /home, which
|
||||||
|
# the stock initramfs plymouth hook copies verbatim -> it dangles at early
|
||||||
|
# boot (before /home is mounted) and Plymouth falls back to text mode.
|
||||||
|
# This hook runs after the stock "plymouth" hook and replaces that dangling
|
||||||
|
# symlink with the actual theme files.
|
||||||
|
|
||||||
|
PREREQS="plymouth"
|
||||||
|
prereqs() { echo "$PREREQS"; }
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
prereqs)
|
||||||
|
prereqs
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
. /usr/share/initramfs-tools/hook-functions
|
||||||
|
|
||||||
|
THEME_SRC="/home/jon/nexus-core/assets/boot/my-custom-logo"
|
||||||
|
THEME_DEST="${DESTDIR}/usr/share/plymouth/themes/my-custom-logo"
|
||||||
|
|
||||||
|
[ -d "${THEME_SRC}" ] || exit 0
|
||||||
|
|
||||||
|
rm -rf "${THEME_DEST}"
|
||||||
|
mkdir -p "${THEME_DEST}"
|
||||||
|
cp -aL "${THEME_SRC}/." "${THEME_DEST}/"
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 151 B |
|
After Width: | Height: | Size: 469 B |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 494 B |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,54 @@
|
|||||||
|
[Plymouth Theme]
|
||||||
|
Name=My Custom Logo
|
||||||
|
Description=Custom boot logo with shimmer effect
|
||||||
|
ModuleName=two-step
|
||||||
|
|
||||||
|
[two-step]
|
||||||
|
Font=Cantarell 12
|
||||||
|
TitleFont=Cantarell Light 30
|
||||||
|
ImageDir=/usr/share/plymouth/themes/my-custom-logo
|
||||||
|
DialogHorizontalAlignment=.5
|
||||||
|
DialogVerticalAlignment=.382
|
||||||
|
TitleHorizontalAlignment=.5
|
||||||
|
TitleVerticalAlignment=.382
|
||||||
|
HorizontalAlignment=.5
|
||||||
|
VerticalAlignment=.5
|
||||||
|
WatermarkHorizontalAlignment=.5
|
||||||
|
WatermarkVerticalAlignment=.96
|
||||||
|
Transition=none
|
||||||
|
TransitionDuration=0.0
|
||||||
|
BackgroundStartColor=0x000000
|
||||||
|
BackgroundEndColor=0x000000
|
||||||
|
ProgressBarBackgroundColor=0x606060
|
||||||
|
ProgressBarForegroundColor=0xffffff
|
||||||
|
MessageBelowAnimation=true
|
||||||
|
|
||||||
|
[boot-up]
|
||||||
|
UseEndAnimation=false
|
||||||
|
|
||||||
|
[shutdown]
|
||||||
|
UseEndAnimation=false
|
||||||
|
|
||||||
|
[reboot]
|
||||||
|
UseEndAnimation=false
|
||||||
|
|
||||||
|
[updates]
|
||||||
|
SuppressMessages=true
|
||||||
|
ProgressBarShowPercentComplete=true
|
||||||
|
UseProgressBar=true
|
||||||
|
Title=Installing Updates...
|
||||||
|
SubTitle=Do not turn off your computer
|
||||||
|
|
||||||
|
[system-upgrade]
|
||||||
|
SuppressMessages=true
|
||||||
|
ProgressBarShowPercentComplete=true
|
||||||
|
UseProgressBar=true
|
||||||
|
Title=Upgrading System...
|
||||||
|
SubTitle=Do not turn off your computer
|
||||||
|
|
||||||
|
[firmware-upgrade]
|
||||||
|
SuppressMessages=true
|
||||||
|
ProgressBarShowPercentComplete=true
|
||||||
|
UseProgressBar=true
|
||||||
|
Title=Upgrading Firmware...
|
||||||
|
SubTitle=Do not turn off your computer
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
@@ -0,0 +1,24 @@
|
|||||||
|
# Theme Asset Credits
|
||||||
|
|
||||||
|
The NexusOS theme is assembled from original work plus assets adapted from the following upstream projects. All upstream sources are GPL-3.0 (or compatible), so the NexusOS theme inherits GPL-3.0.
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### WhiteSur GTK Theme — `gtk-2.0/`, `xfwm4/`
|
||||||
|
- **Project**: https://github.com/vinceliuice/WhiteSur-gtk-theme
|
||||||
|
- **Author**: Vince Liu (vinceliuice)
|
||||||
|
- **License**: GPL-3.0
|
||||||
|
- **Variant used as base**: `WhiteSur-Dark-purple`
|
||||||
|
- **What we use**: The entire `gtk-2.0/` and `xfwm4/` directories are cloned verbatim from WhiteSur-Dark-purple. We rely on these for macOS-style window controls (stoplight buttons) and the GTK2 application skin.
|
||||||
|
|
||||||
|
### Mint-Y Icon Theme — `NexusOS-icons/` (folder geometry reference)
|
||||||
|
- **Project**: https://github.com/linuxmint/mint-y-icons
|
||||||
|
- **Author**: Linux Mint team
|
||||||
|
- **License**: GPL-3.0
|
||||||
|
- **What we use**: Visual reference only for the folder shape language (flat folder with tab + lip). No PNGs are copied from Mint-Y; our folder set is regenerated from scratch via `NexusOS-icons-src/build.py` against a master SVG we authored.
|
||||||
|
|
||||||
|
## NexusOS-original content
|
||||||
|
- `gtk-3.0/*.css` — original work
|
||||||
|
- `NexusOS-icons-src/*` and the rendered output in `NexusOS-icons/places/` — original work
|
||||||
|
- The `n-small.png` badge composited into `folder-nexus-core` is the NexusOS logo at `nexus-core/assets/n-small.png`
|
||||||
|
- `index.theme` files in each theme directory — original work
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
NexusOS theme assets — attribution and license
|
||||||
|
================================================
|
||||||
|
|
||||||
|
The NexusOS GTK2 and xfwm4 themes are derived from Mint-Y-Dark-Aqua,
|
||||||
|
part of the mint-themes package shipped by Linux Mint.
|
||||||
|
|
||||||
|
Upstream:
|
||||||
|
https://github.com/linuxmint/mint-themes
|
||||||
|
Copyright (c) 2017-2024 Linux Mint <root@linuxmint.com>
|
||||||
|
Licensed under the GNU General Public License v3.0 or later.
|
||||||
|
|
||||||
|
Derivative work in this directory:
|
||||||
|
- NexusOS/gtk-2.0/ (PNGs recolored from Mint-Y-Dark-Aqua/gtk-2.0/)
|
||||||
|
- NexusOS/xfwm4/ (PNGs recolored from Mint-Y-Dark-Aqua/xfwm4/)
|
||||||
|
- NexusOS-gtk2-src/ (build script that performs the recolor)
|
||||||
|
- NexusOS-xfwm4-src/ (build script that performs the recolor)
|
||||||
|
|
||||||
|
As a derivative of GPLv3 material, these files are likewise distributed
|
||||||
|
under the terms of the GNU General Public License version 3 or later. See
|
||||||
|
https://www.gnu.org/licenses/gpl-3.0.html for the full license text.
|
||||||
|
|
||||||
|
The NexusOS GTK3 theme (NexusOS/gtk-3.0/) and the NexusOS icon set
|
||||||
|
(NexusOS-icons*/) are original works and not derived from Mint-Y.
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
#!/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()
|
||||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 882 B |
|
After Width: | Height: | Size: 955 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 921 B |
|
After Width: | Height: | Size: 1.2 KiB |