diff --git a/img/proleIcon.png b/img/proleIcon.png new file mode 100644 index 0000000..c417171 Binary files /dev/null and b/img/proleIcon.png differ diff --git a/install.py b/install.py index b8a8228..699d059 100755 --- a/install.py +++ b/install.py @@ -27,6 +27,14 @@ from installer.workstation import ( ) from installer import deploy as inst_deploy +# On macOS, set process name as early as possible so the menu bar shows 'Prole Installer' +if platform.system() == 'Darwin': + try: + from Foundation import NSProcessInfo + NSProcessInfo.processInfo().setProcessName_("Prole Installer") + except Exception: + pass + # Get the project root directory (kept local for clarity in this legacy entry) PROJECT_ROOT = Path(__file__).parent.absolute() @@ -90,8 +98,11 @@ class ProleInstaller: self._bg_item = None try: from PIL import Image, ImageTk # optional - # Use the full-frame background image per latest spec - bg_path = PROJECT_ROOT / 'img' / 'proleLogoSepia.png' + # Use the full-frame background image from config + try: + bg_path = inst_config.get_ui_background_image_path() + except Exception: + bg_path = PROJECT_ROOT / 'img' / 'proleLogoSepia.png' if bg_path.exists(): self._bg_pil = Image.open(str(bg_path)).convert('RGBA') @@ -190,16 +201,13 @@ class ProleInstaller: # ---------------- App identity (title, Dock icon) ---------------- def _set_app_identity(self): - """Set the app name shown by Tk and attempt to set the macOS Dock icon. + """Set the installer identity: process/menu name and Dock icon on macOS. Notes: - - tk appname affects Tk's internal application name and may improve - display in some OS integrations. On macOS, fully changing the menu bar - app name from "Python" typically requires running as a bundled app - with CFBundleName set, but we still set the Tk appname here. - - For the Dock icon on macOS, we try to use the ProleStatus.app icns - (capital P icon). If PyObjC (AppKit) isn't available, we fall back to - setting a Tk window icon from a PNG/GIF in img/. + - We set Tk's appname for consistency. + - On macOS, attempt to set the process/menu name to 'Prole Installer' + via NSProcessInfo if PyObjC is available. + - Prefer the Prole.app .icns from the built app; fallback to local PNG/GIF. """ # Set Tk application name try: @@ -207,11 +215,30 @@ class ProleInstaller: except Exception: pass - # macOS Dock icon via AppKit (preferred) + # macOS: set process/menu name and Dock icon via AppKit/Foundation if platform.system() == 'Darwin': + # Try to set the visible process name for the menu bar + try: + from Foundation import NSProcessInfo + NSProcessInfo.processInfo().setProcessName_("Prole Installer") + except Exception: + pass + + # Also try to retitle the first main menu item so the menu next to the Apple logo reads 'Prole Installer' + try: + from AppKit import NSApplication + app = NSApplication.sharedApplication() + main_menu = app.mainMenu() + if main_menu is not None and main_menu.numberOfItems() > 0: + first_item = main_menu.itemAtIndex_(0) + if first_item is not None: + first_item.setTitle_("Prole Installer") + except Exception: + pass + icns_candidates = [ - PROJECT_ROOT / 'proleStatus' / 'dist' / 'ProleStatus.app' / 'Contents' / 'Resources' / 'AppIcon.icns', - PROJECT_ROOT / 'proleStatus' / 'dist' / 'ProleStatus.app' / 'Contents' / 'Resources' / 'ProleStatus.icns', + PROJECT_ROOT / 'prole-app' / 'dist' / 'Prole.app' / 'Contents' / 'Resources' / 'AppIcon.icns', + PROJECT_ROOT / 'prole-app' / 'dist' / 'Prole.app' / 'Contents' / 'Resources' / 'Prole.icns', ] icns_path = next((p for p in icns_candidates if p.exists()), None) if icns_path is not None: @@ -226,10 +253,16 @@ class ProleInstaller: pass # Fallback: Tk icon from image assets (PNG/GIF) + # Prefer config-defined icon image + try: + cfg_icon = inst_config.get_ui_icon_image_path() + except Exception: + cfg_icon = PROJECT_ROOT / 'img' / 'proleIcon.png' img_candidates = [ - PROJECT_ROOT / 'img' / 'prole-type.gif', + cfg_icon, PROJECT_ROOT / 'img' / 'prole-type.png', - PROJECT_ROOT / 'img' / 'ProleStatus.png', + PROJECT_ROOT / 'img' / 'prole-type.gif', + PROJECT_ROOT / 'img' / 'Prole.png', PROJECT_ROOT / 'img' / 'proleLogoSepia.png', ] for p in img_candidates: @@ -600,7 +633,7 @@ class ProleInstaller: left = 56 if not hasattr(self, 'deploy_steps'): self.deploy_steps = [ - {'name': 'Build ProleStatus macOS app', 'status': 'pending'}, + {'name': 'Build Prole macOS app', 'status': 'pending'}, {'name': 'Build workstation Vagrant VM', 'status': 'pending'}, {'name': 'Check Docker is running', 'status': 'pending'}, {'name': 'Check or configure container registry', 'status': 'pending'}, @@ -664,7 +697,7 @@ class ProleInstaller: self.show_page(target) return if current_id == 'build': - # Treat Next as Build for ProleStatus + # Treat Next as Build for Prole self.perform_build() return if current_id == 'build_vm': @@ -949,7 +982,7 @@ class ProleInstaller: # Reuse existing deploy steps UI but on light theme self.deploy_steps = [ - {'name': 'Build ProleStatus macOS app', 'status': 'pending'}, + {'name': 'Build Prole macOS app', 'status': 'pending'}, {'name': 'Check Docker is running', 'status': 'pending'}, {'name': 'Check or configure container registry', 'status': 'pending'}, {'name': 'Ensure target cluster', 'status': 'pending'}, @@ -1033,7 +1066,7 @@ class ProleInstaller: # ---------------- Build integration ---------------- def perform_build(self): - """Run ProleStatus build in an embedded console (Scopped bash subprocess).""" + """Run Prole build in an embedded console (Scoped bash subprocess).""" # Prepare logs dir and file logs_dir = PROJECT_ROOT / 'logs' try: @@ -1570,7 +1603,7 @@ class ProleInstaller: if logp or vlog: self._render_paragraph('Build output was captured from the embedded console.', y=90) if logp: - self._render_paragraph('ProleStatus log:', y=120) + self._render_paragraph('Prole build log:', y=120) self._render_paragraph(logp, y=140) y_next = 180 if logp else 120 if vlog: @@ -1579,7 +1612,7 @@ class ProleInstaller: # clickable link link_y = (y_next + 56) if vlog else 200 if logp: - link1 = self.bg_canvas.create_text(56, link_y, anchor='nw', text='Open ProleStatus log', fill='#0a84ff', font=('Helvetica', 12, 'underline')) + link1 = self.bg_canvas.create_text(56, link_y, anchor='nw', text='Open Prole build log', fill='#0a84ff', font=('Helvetica', 12, 'underline')) self._canvas_items.append(link1) if vlog: link2 = self.bg_canvas.create_text(56, link_y + 28, anchor='nw', text='Open Vagrant log', fill='#0a84ff', font=('Helvetica', 12, 'underline')) @@ -1993,10 +2026,10 @@ echo "All dependencies installed successfully!" self.deploy_widgets['Ensure target cluster']['step']['name'] = f"Ensure target cluster ({env})" self.deploy_widgets['Ensure target cluster']['label'].master.master.children['!label'].configure(text=f"Ensure target cluster ({env})") - # Step 0: Build ProleStatus macOS app - self.update_deploy_step_status('Build ProleStatus macOS app', 'running') - self.build_prole_status_app() - self.update_deploy_step_status('Build ProleStatus macOS app', 'completed') + # Step 0: Build Prole macOS app + self.update_deploy_step_status('Build Prole macOS app', 'running') + self.build_prole_app() + self.update_deploy_step_status('Build Prole macOS app', 'completed') # Step 1: Check Docker self.update_deploy_step_status('Check Docker is running', 'running') @@ -2049,9 +2082,9 @@ echo "All dependencies installed successfully!" """Check if Xcode Command Line Tools are installed via deploy helper.""" return inst_deploy.check_xcode_tools() - def build_prole_status_app(self): + def build_prole_app(self): """Delegate building the native ProleStatus app to deploy helper.""" - return inst_deploy.build_prole_status_app(PROJECT_ROOT) + return inst_deploy.build_prole_app(PROJECT_ROOT) def check_docker_running(self): """Check if Docker is running""" diff --git a/installer/build.py b/installer/build.py index cc5d8d5..294bfef 100644 --- a/installer/build.py +++ b/installer/build.py @@ -1,7 +1,7 @@ """ Build helpers for the Prole installer (root-level package). -UI-agnostic command composition for the ProleStatus build. +UI-agnostic command composition for the Prole macOS app build. """ from __future__ import annotations @@ -9,11 +9,11 @@ from pathlib import Path def get_build_command(project_root: Path, env: str | None) -> str: - """Return the ProleStatus build command with an env comment suffix. + """Return the Prole app build command with an env comment suffix. Example output: - cd "/path/to/repo" && ./proleStatus/build.sh build # Dev + cd "/path/to/repo" && ./prole-app/build.sh build # Dev """ env_label = (env or "Dev").strip().title() prole_root = str(project_root) - return f"cd \"{prole_root}\" && ./proleStatus/build.sh build # {env_label}" + return f"cd \"{prole_root}\" && ./prole-app/build.sh build # {env_label}" diff --git a/installer/config.py b/installer/config.py index 9a52533..1682693 100644 --- a/installer/config.py +++ b/installer/config.py @@ -10,9 +10,82 @@ from pathlib import Path import platform import subprocess from typing import Tuple, Optional +import os + +# .properties loader (simple key=value, # comments) +def _load_properties(path: Path) -> dict: + props: dict[str, str] = {} + try: + text = path.read_text(encoding="utf-8") + except Exception: + return props + for line in text.splitlines(): + s = line.strip() + if not s or s.startswith("#"): + continue + if "=" in s: + k, v = s.split("=", 1) + k = k.strip() + v = v.strip() + if k: + props[k] = v + return props # Repository root: this file lives at /installer/config.py → parent is repo PROJECT_ROOT = Path(__file__).resolve().parents[1] +PROLE_APP_DIR = PROJECT_ROOT / "prole-app" +PROLE_PROPS_PATH = PROLE_APP_DIR / "prole.properties" + +_PROLE_PROPS_CACHE: Optional[dict] = None + +def get_properties() -> dict: + """Load and cache prole.properties from the repo (installer context). + + Order: + - Repo default at prole-app/prole.properties + - Optional env override: PROLE_PROPERTIES points to a file + """ + global _PROLE_PROPS_CACHE + if _PROLE_PROPS_CACHE is None: + props: dict[str, str] = {} + # repo default + if PROLE_PROPS_PATH.exists(): + props.update(_load_properties(PROLE_PROPS_PATH)) + # env override (absolute path) + env_path = os.environ.get("PROLE_PROPERTIES") + if env_path: + p = Path(env_path) + if p.exists(): + props.update(_load_properties(p)) + _PROLE_PROPS_CACHE = props + return dict(_PROLE_PROPS_CACHE) + +def get_config_value(key: str, default: Optional[str] = None) -> Optional[str]: + return get_properties().get(key, default) + +def get_ui_icon_image_path() -> Path: + """Return absolute path to the UI icon image (for installer fallback and builds). + + Defaults to img/proleIcon.png under repo root if not set or missing. + """ + rel = get_config_value("ui.icon", "img/proleIcon.png") or "img/proleIcon.png" + p = (PROJECT_ROOT / rel).resolve() + if p.exists(): + return p + # fallback + return (PROJECT_ROOT / "img/proleIcon.png").resolve() + +def get_ui_background_image_path() -> Path: + """Return absolute path to the UI background image. + + Defaults to img/proleLogoSepia.png under repo root if not set or missing. + """ + rel = get_config_value("ui.background", "img/proleLogoSepia.png") or "img/proleLogoSepia.png" + p = (PROJECT_ROOT / rel).resolve() + if p.exists(): + return p + # fallback + return (PROJECT_ROOT / "img/proleLogoSepia.png").resolve() def is_apple_silicon() -> bool: diff --git a/installer/deploy.py b/installer/deploy.py index 72e1978..5a555f4 100644 --- a/installer/deploy.py +++ b/installer/deploy.py @@ -1,7 +1,7 @@ """ Deploy/build helpers for the Prole installer (root-level package). -Encapsulates Xcode tools check and building the native ProleStatus app. +Encapsulates Xcode tools check and building the native Prole app. """ from __future__ import annotations @@ -21,37 +21,36 @@ def check_xcode_tools() -> bool: return False -def build_prole_status_app(project_root: Path) -> None: +def build_prole_app(project_root: Path) -> None: if platform.system() != "Darwin": raise Exception("Building the macOS app requires macOS.") if not check_xcode_tools(): raise Exception("Xcode Command Line Tools not found. Please run: xcode-select --install") - # Only use the top-level proleStatus project location; drop legacy - # references to nested paths under prole/ to keep the tree clean. - prole_status_dir = Path(project_root) / "proleStatus" - build_script = prole_status_dir / "build.sh" + # Use the top-level prole-app project directory + prole_app_dir = Path(project_root) / "prole-app" + build_script = prole_app_dir / "build.sh" if not build_script.exists(): raise Exception( f"Build script not found at {build_script}" ) - result = subprocess.run(["bash", str(build_script), "build"], cwd=prole_status_dir, capture_output=True, text=True) + result = subprocess.run(["bash", str(build_script), "build"], cwd=prole_app_dir, capture_output=True, text=True) if result.returncode != 0: - raise Exception(f"ProleStatus build failed: {result.stderr or result.stdout}") + raise Exception(f"Prole build failed: {result.stderr or result.stdout}") - app_path = prole_status_dir / "dist" / "ProleStatus.app" + app_path = prole_app_dir / "dist" / "Prole.app" if not app_path.exists(): - raise Exception("Build completed but ProleStatus.app was not found in dist/") + raise Exception("Build completed but Prole.app was not found in dist/") try: msg = ( - "ProleStatus.app has been built successfully.\n\n" + "Prole.app has been built successfully.\n\n" f"Location: {app_path}\n\n" "Would you like to copy it to /Applications?" ) - if messagebox.askyesno("ProleStatus Built", msg): - dest = Path("/Applications") / "ProleStatus.app" + if messagebox.askyesno("Prole Built", msg): + dest = Path("/Applications") / "Prole.app" subprocess.run(["cp", "-R", str(app_path), str(dest)], check=True) messagebox.showinfo("Copied", f"Copied to {dest}") except Exception as copy_err: diff --git a/proleStatus/README.md b/prole-app/README.md similarity index 84% rename from proleStatus/README.md rename to prole-app/README.md index 137c6aa..506c45b 100644 --- a/proleStatus/README.md +++ b/prole-app/README.md @@ -10,11 +10,11 @@ ##################################################### ``` -ProleStatus +Prole — macOS status and control surface for Prole endpoints, with a path to an integrated virtual workstation harness. Overview -- ProleStatus provides live reachability and latency signals for Prole service endpoints. It operates in two modes: a regular application window for situational awareness and a minimalist status‑bar overlay for persistent at‑a‑glance status. +- Prole provides live reachability and latency signals for Prole service endpoints. It operates in two modes: a regular application window for situational awareness and a minimalist status‑bar overlay for persistent at‑a‑glance status. - The application will evolve to include a virtual workstation surface to coordinate work across a network of Prole‑linked LLMs. The current scope is operational visibility and control of endpoints. Supported platform @@ -35,31 +35,31 @@ Build environment requirements - `plutil` (plist formatting, via xcrun if needed) Repository layout (subset) -- `proleStatus/` — macOS app sources and build system +- `prole-app/` — macOS app sources and build system - `Sources/` — Swift sources (AppKit) - `build.sh` — hermetic CLI build producing a `.app` bundle - `prole.properties` — default endpoint configuration (bundled into Resources) - - `dist/ProleStatus.app` — build output + - `dist/Prole.app` — build output Build script -- The build is driven by `proleStatus/build.sh`. Typical usage: +- The build is driven by `prole-app/build.sh`. Typical usage: ``` - cd proleStatus + cd prole-app ./build.sh build # build for host arch ./build.sh run # build (if needed) and open the app ./build.sh build-universal # produce a universal (arm64+x86_64) binary ./build.sh debug # run in foreground with verbose logs ./build.sh clean # remove build artifacts - ./build.sh package # zip dist/ProleStatus.app into dist/ProleStatus.zip + ./build.sh package # zip dist/Prole.app into dist/Prole.zip ``` What the script does - Compiles all Swift sources with `swiftc` (AppKit, Carbon, Network frameworks). - Generates `Contents/Info.plist` with `LSUIElement=false` so the app can present a standard menu when in Application Window mode. -- Generates an application icon (`ProleStatus.icns`) and a template status glyph as needed. +- Generates an application icon (`Prole.icns`) and a template status glyph as needed. - Copies resources: - `www/images/prole-type.gif` → `Contents/Resources/prole-type.gif` (for the startup tip splash) - - `proleStatus/prole.properties` → `Contents/Resources/prole.properties` + - `prole-app/prole.properties` → `Contents/Resources/prole.properties` - Performs ad‑hoc code signing of the `.app` bundle. Alternate build path (installer UI) @@ -67,7 +67,7 @@ Alternate build path (installer UI) ``` python3 install.py ``` -- Use the “Build ProleStatus macOS app” step. The installer will produce `proleStatus/dist/ProleStatus.app` and can optionally copy it to `/Applications`. +- Use the “Build Prole macOS app” step. The installer will produce `prole-app/dist/Prole.app` and can optionally copy it to `/Applications`. Run modes and controls - Modes: @@ -81,8 +81,8 @@ Run modes and controls Configuration - Endpoint configuration is provided via Java‑style `key=value` properties. Two locations are read at startup; the user override has precedence: - 1. Bundled defaults: `ProleStatus.app/Contents/Resources/prole.properties` - 2. User override (optional): `~/Library/Application Support/ProleStatus/prole.properties` + 1. Bundled defaults: `Prole.app/Contents/Resources/prole.properties` + 2. User override (optional): `~/Library/Application Support/Prole/prole.properties` - Default keys: - `svc.host`, `svc.port` - `k3s.retropie.host`, `k3s.retropie.port` @@ -116,7 +116,7 @@ Security & signing - The `.app` bundle is ad‑hoc signed by default. For distribution, replace with a Developer ID signature and notarize as appropriate for your environment. Roadmap -- Integration of a virtual workstation to orchestrate a network of Prole‑linked LLMs from within the ProleStatus surface. +- Integration of a virtual workstation to orchestrate a network of Prole‑linked LLMs from within the Prole surface. License - See the repository `LICENSE` file. diff --git a/proleStatus/Sources/AppDelegate.swift b/prole-app/Sources/AppDelegate.swift similarity index 100% rename from proleStatus/Sources/AppDelegate.swift rename to prole-app/Sources/AppDelegate.swift diff --git a/proleStatus/Sources/AppStatusView.swift b/prole-app/Sources/AppStatusView.swift similarity index 100% rename from proleStatus/Sources/AppStatusView.swift rename to prole-app/Sources/AppStatusView.swift diff --git a/proleStatus/Sources/Config.swift b/prole-app/Sources/Config.swift similarity index 85% rename from proleStatus/Sources/Config.swift rename to prole-app/Sources/Config.swift index f6c22c5..2fc52bb 100644 --- a/proleStatus/Sources/Config.swift +++ b/prole-app/Sources/Config.swift @@ -28,7 +28,7 @@ final class Config { // Load user override from Application Support let fm = FileManager.default if let appSupport = try? fm.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false) { - let dir = appSupport.appendingPathComponent("ProleStatus", isDirectory: true) + let dir = appSupport.appendingPathComponent("Prole", isDirectory: true) let url = dir.appendingPathComponent("prole.properties") if fm.fileExists(atPath: url.path) { merge(loadProperties(url: url)) @@ -68,4 +68,14 @@ final class Config { var localHost: String { string("k3d.local.host", default: "localhost") } var localPort: Int { int("k3d.local.port", default: 6443) } + + // UI asset config + // Background image file name relative to the app bundle Resources. If a path is provided, only the last component is used. + var uiBackground: String { + let raw = string("ui.background", default: "img/proleLogoSepia.png") + if let lastSlash = raw.lastIndex(of: "/") { + return String(raw[raw.index(after: lastSlash)...]) + } + return raw + } } diff --git a/proleStatus/Sources/Debug.swift b/prole-app/Sources/Debug.swift similarity index 100% rename from proleStatus/Sources/Debug.swift rename to prole-app/Sources/Debug.swift diff --git a/proleStatus/Sources/HotKeyManager.swift b/prole-app/Sources/HotKeyManager.swift similarity index 100% rename from proleStatus/Sources/HotKeyManager.swift rename to prole-app/Sources/HotKeyManager.swift diff --git a/proleStatus/Sources/MainWindowController.swift b/prole-app/Sources/MainWindowController.swift similarity index 100% rename from proleStatus/Sources/MainWindowController.swift rename to prole-app/Sources/MainWindowController.swift diff --git a/proleStatus/Sources/OverlayWindow.swift b/prole-app/Sources/OverlayWindow.swift similarity index 100% rename from proleStatus/Sources/OverlayWindow.swift rename to prole-app/Sources/OverlayWindow.swift diff --git a/proleStatus/Sources/ServiceChecker.swift b/prole-app/Sources/ServiceChecker.swift similarity index 100% rename from proleStatus/Sources/ServiceChecker.swift rename to prole-app/Sources/ServiceChecker.swift diff --git a/proleStatus/Sources/SplashTipWindowController.swift b/prole-app/Sources/SplashTipWindowController.swift similarity index 87% rename from proleStatus/Sources/SplashTipWindowController.swift rename to prole-app/Sources/SplashTipWindowController.swift index 6f44f56..7a8e0b4 100644 --- a/proleStatus/Sources/SplashTipWindowController.swift +++ b/prole-app/Sources/SplashTipWindowController.swift @@ -67,13 +67,24 @@ final class SplashTipWindowController: NSWindowController { counterLabel.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -4) ]) - // Load the animated GIF from bundle resources if present - if let url = Bundle.main.url(forResource: "prole-type", withExtension: "gif"), - let img = NSImage(contentsOf: url) { - imageView.image = img - } else { - // Fallback: show placeholder text if the gif is missing - let placeholder = NSTextField(labelWithString: "prole-type.gif not found") + // Load background/splash image from config (bundled in Resources) if present + let bgName = Config.shared.uiBackground + let candidates: [(String, String?)] = [ + (bgName, nil), + ("prole-type", "gif") + ] + var loaded = false + for (res, ext) in candidates { + if let url = Bundle.main.url(forResource: res, withExtension: ext), + let img = NSImage(contentsOf: url) { + imageView.image = img + loaded = true + break + } + } + if !loaded { + // Fallback: show placeholder text if the resource is missing + let placeholder = NSTextField(labelWithString: "Splash background not found") placeholder.textColor = .secondaryLabelColor placeholder.alignment = .center placeholder.translatesAutoresizingMaskIntoConstraints = false diff --git a/proleStatus/Sources/StatusItemController.swift b/prole-app/Sources/StatusItemController.swift similarity index 100% rename from proleStatus/Sources/StatusItemController.swift rename to prole-app/Sources/StatusItemController.swift diff --git a/proleStatus/Sources/StatusView.swift b/prole-app/Sources/StatusView.swift similarity index 100% rename from proleStatus/Sources/StatusView.swift rename to prole-app/Sources/StatusView.swift diff --git a/proleStatus/Sources/main.swift b/prole-app/Sources/main.swift similarity index 100% rename from proleStatus/Sources/main.swift rename to prole-app/Sources/main.swift diff --git a/proleStatus/build.sh b/prole-app/build.sh similarity index 86% rename from proleStatus/build.sh rename to prole-app/build.sh index 4b0cf56..6235aac 100755 --- a/proleStatus/build.sh +++ b/prole-app/build.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash set -euo pipefail -# ProleStatus build script — builds a macOS .app bundle without launching Xcode +# Prole build script — builds a macOS .app bundle without launching Xcode # Requirements: Xcode Command Line Tools (swiftc, codesign, plutil, lipo) -APP_NAME="ProleStatus" +APP_NAME="Prole" BUNDLE_ID="org.prole.${APP_NAME}" MIN_MACOS="12.0" @@ -50,6 +50,21 @@ ensure_dirs() { mkdir -p "$BUILD_DIR" "$DIST_DIR" "$MACOS_DIR" "$RESOURCES_DIR" } +# Read a key from prole.properties (very simple parser) +prop_get() { + local key="$1" + local file="$ROOT_DIR/prole.properties" + if [[ -f "$file" ]]; then + local line + line=$(grep -E "^${key}=" "$file" | tail -n1 || true) + if [[ -n "$line" ]]; then + echo "${line#*=}" + return 0 + fi + fi + return 1 +} + gen_statusbar_icon_png() { # Generates a small monochrome template PNG for the status bar (18x18) local out_png="$RESOURCES_DIR/statusIcon.png" @@ -89,15 +104,24 @@ SWIFT } gen_app_icns() { - # Create a basic .icns with a bold 'P' on transparent background + # Create an .icns for the app. Prefer a configured source image (ui.icon), + # otherwise fall back to generating a bold 'P'. local icon_name="${APP_NAME}" local iconset_dir="$BUILD_DIR/${icon_name}.iconset" rm -rf "$iconset_dir" mkdir -p "$iconset_dir" - # Generate a 1024 base PNG using AppKit so we don't depend on ImageMagick/Pillow local base_png="$BUILD_DIR/${icon_name}_1024.png" - /usr/bin/env xcrun swift -F /System/Library/PrivateFrameworks - <<'SWIFT' "$base_png" + + # Try configured ui.icon relative to repo root + local ui_icon + ui_icon="$(prop_get ui.icon || true)" + if [[ -n "$ui_icon" && -f "$PARENT_DIR/$ui_icon" ]]; then + # Use the provided icon image as base; if not already 1024x1024, sips will resize for each size + cp "$PARENT_DIR/$ui_icon" "$base_png" + else + # Generate a 1024 base PNG using AppKit so we don't depend on ImageMagick/Pillow + /usr/bin/env xcrun swift -F /System/Library/PrivateFrameworks - <<'SWIFT' "$base_png" import AppKit import Foundation @@ -137,6 +161,7 @@ guard let tiff = img.tiffRepresentation, } try! png.write(to: URL(fileURLWithPath: outPath)) SWIFT + fi # Create all iconset sizes from the base image for s in 16 32 128 256 512; do @@ -185,6 +210,19 @@ copy_extra_resources() { echo "[resources] prole-type.gif not found at $gif_src (skipping)" fi + # Copy configured background image (ui.background) into Resources, keeping basename + local ui_bg + ui_bg="$(prop_get ui.background || true)" + if [[ -n "$ui_bg" && -f "$PARENT_DIR/$ui_bg" ]]; then + mkdir -p "$RESOURCES_DIR" + local base + base="$(basename "$ui_bg")" + cp "$PARENT_DIR/$ui_bg" "$RESOURCES_DIR/$base" + echo "[resources] Copied background image ($base) into Resources" + else + echo "[resources] ui.background not set or file missing (skipping)" + fi + # Copy default properties file if present local props_src="$ROOT_DIR/prole.properties" if [[ -f "$props_src" ]]; then diff --git a/proleStatus/dist/ProleStatus.app/Contents/Info.plist b/prole-app/dist/ProleStatus.app/Contents/Info.plist similarity index 100% rename from proleStatus/dist/ProleStatus.app/Contents/Info.plist rename to prole-app/dist/ProleStatus.app/Contents/Info.plist diff --git a/proleStatus/dist/ProleStatus.app/Contents/MacOS/ProleStatus b/prole-app/dist/ProleStatus.app/Contents/MacOS/ProleStatus similarity index 100% rename from proleStatus/dist/ProleStatus.app/Contents/MacOS/ProleStatus rename to prole-app/dist/ProleStatus.app/Contents/MacOS/ProleStatus diff --git a/proleStatus/dist/ProleStatus.app/Contents/_CodeSignature/CodeResources b/prole-app/dist/ProleStatus.app/Contents/_CodeSignature/CodeResources similarity index 100% rename from proleStatus/dist/ProleStatus.app/Contents/_CodeSignature/CodeResources rename to prole-app/dist/ProleStatus.app/Contents/_CodeSignature/CodeResources diff --git a/prole-app/prole.properties b/prole-app/prole.properties new file mode 100644 index 0000000..2a27244 --- /dev/null +++ b/prole-app/prole.properties @@ -0,0 +1,33 @@ +# Prole default endpoints (bundled). Override in: +# ~/Library/Application Support/Prole/prole.properties +# using the same key=value format. + +# UI assets (relative to repo root when building; file name within app bundle at runtime) +# Icon image used for app icon generation and installer UI fallback +ui.icon=img/proleIcon.png +# Background image used by the app splash/tip and installer background +ui.background=img/proleLogoSepia.png + +# Core service +svc.host=svc.prole.org +svc.port=443 + +# k3s aggregate ? replace legacy raspberry with retropie +k3s.retropie.host=retropie.prole.org +k3s.retropie.port=6443 +# k3s.pi.host=pi.prole.org +# k3s.pi.port=6443 + +# local k3d +k3d.local.host=localhost +k3d.local.port=6443 + +# internal postgres +postgres.host=k3s.prole.org +postgres.port=5432 + +# Reserved for future cloud endpoints +# aws.eks.host= +# aws.eks.port= +# gcp.gke.host= +# gcp.gke.port= diff --git a/proleStatus/prole.properties b/proleStatus/prole.properties deleted file mode 100644 index cf9d310..0000000 --- a/proleStatus/prole.properties +++ /dev/null @@ -1,23 +0,0 @@ -# ProleStatus default endpoints (bundled). Override in: -# ~/Library/Application Support/ProleStatus/prole.properties -# using the same key=value format. - -# Core service -svc.host=svc.prole.org -svc.port=443 - -# k3s aggregate ? replace legacy raspberry with retropie -k3s.retropie.host=retropie.prole.org -k3s.retropie.port=6443 -# k3s.pi.host=pi.prole.org -# k3s.pi.port=6443 - -# local k3d -k3d.local.host=localhost -k3d.local.port=6443 - -# Reserved for future cloud endpoints -# aws.eks.host= -# aws.eks.port= -# gcp.gke.host= -# gcp.gke.port=