""" Application orchestrator — boot sequence, signal handling, main loop wiring. """ import sys import os import time import signal import atexit import termios import tty from engine import config, render from engine.terminal import ( RST, G_HI, G_MID, G_DIM, W_DIM, W_GHOST, CLR, CURSOR_OFF, CURSOR_ON, tw, slow_print, boot_ln, ) from engine.fetch import fetch_all, fetch_poetry, load_cache, save_cache from engine.ntfy import NtfyPoller from engine.mic import MicMonitor from engine.scroll import stream TITLE = [ " ███╗ ███╗ █████╗ ██╗███╗ ██╗██╗ ██╗███╗ ██╗███████╗", " ████╗ ████║██╔══██╗██║████╗ ██║██║ ██║████╗ ██║██╔════╝", " ██╔████╔██║███████║██║██╔██╗ ██║██║ ██║██╔██╗ ██║█████╗ ", " ██║╚██╔╝██║██╔══██║██║██║╚██╗██║██║ ██║██║╚██╗██║██╔══╝ ", " ██║ ╚═╝ ██║██║ ██║██║██║ ╚████║███████╗██║██║ ╚████║███████╗", " ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝", ] def _read_picker_key(): ch = sys.stdin.read(1) if ch == "\x03": return "interrupt" if ch in ("\r", "\n"): return "enter" if ch == "\x1b": c1 = sys.stdin.read(1) if c1 != "[": return None c2 = sys.stdin.read(1) if c2 == "A": return "up" if c2 == "B": return "down" return None if ch in ("k", "K"): return "up" if ch in ("j", "J"): return "down" if ch in ("q", "Q"): return "enter" return None def _normalize_preview_rows(rows): """Trim shared left padding and trailing spaces for stable on-screen previews.""" non_empty = [r for r in rows if r.strip()] if not non_empty: return [""] left_pad = min(len(r) - len(r.lstrip(" ")) for r in non_empty) out = [] for row in rows: if left_pad < len(row): out.append(row[left_pad:].rstrip()) else: out.append(row.rstrip()) return out def _draw_font_picker(faces, selected): w = tw() h = 24 try: h = os.get_terminal_size().lines except Exception: pass max_preview_w = max(24, w - 8) header_h = 6 footer_h = 3 preview_h = max(4, min(config.RENDER_H + 2, max(4, h // 2))) visible = max(1, h - header_h - preview_h - footer_h) top = max(0, selected - (visible // 2)) bottom = min(len(faces), top + visible) top = max(0, bottom - visible) print(CLR, end="") print(CURSOR_OFF, end="") print() print(f" {G_HI}FONT PICKER{RST}") print(f" {W_GHOST}{'─' * (w - 4)}{RST}") print(f" {W_DIM}{config.FONT_PATH[:max_preview_w]}{RST}") print(f" {W_GHOST}↑/↓ move · Enter select · q accept current{RST}") print() for pos in range(top, bottom): face = faces[pos] active = pos == selected pointer = "▶" if active else " " color = G_HI if active else W_DIM print(f" {color}{pointer} [{face['index']}] {face['name']}{RST}") if top > 0: print(f" {W_GHOST}… {top} above{RST}") if bottom < len(faces): print(f" {W_GHOST}… {len(faces) - bottom} below{RST}") print() print(f" {W_GHOST}{'─' * (w - 4)}{RST}") print(f" {W_DIM}Preview: {faces[selected]['name']}{RST}") preview_rows = faces[selected]["preview_rows"][:preview_h] for row in preview_rows: shown = row[:max_preview_w] print(f" {shown}") def pick_font_face(): """Interactive startup picker for selecting a face from a font file.""" if not config.FONT_PICKER: return print(CLR, end="") print(CURSOR_OFF, end="") print() print(f" {G_HI}FONT PICKER{RST}") print(f" {W_GHOST}{'─' * (tw() - 4)}{RST}") print(f" {W_DIM}{config.FONT_PATH}{RST}") print() try: faces = render.list_font_faces(config.FONT_PATH, max_faces=64) except Exception as exc: print(f" {G_DIM}> unable to load font file: {exc}{RST}") print(f" {W_GHOST}> startup aborted (font selection is required){RST}") time.sleep(1.4) sys.exit(1) face_ids = [face["index"] for face in faces] default = config.FONT_INDEX if config.FONT_INDEX in face_ids else faces[0]["index"] prepared = [] for face in faces: idx = face["index"] name = face["name"] try: fnt = render.load_font_face(config.FONT_PATH, idx) rows = _normalize_preview_rows(render.render_line(name, fnt)) except Exception: rows = ["(preview unavailable)"] prepared.append({"index": idx, "name": name, "preview_rows": rows}) selected = next((i for i, f in enumerate(prepared) if f["index"] == default), 0) if not sys.stdin.isatty(): selected_index = prepared[selected]["index"] config.set_font_selection(font_index=selected_index) render.clear_font_cache() print(f" {G_DIM}> using face {selected_index}{RST}") time.sleep(0.8) print(CLR, end="") print(CURSOR_OFF, end="") print() return fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: tty.setcbreak(fd) while True: _draw_font_picker(prepared, selected) key = _read_picker_key() if key == "up": selected = max(0, selected - 1) elif key == "down": selected = min(len(prepared) - 1, selected + 1) elif key == "enter": break elif key == "interrupt": raise KeyboardInterrupt finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) selected = prepared[selected]["index"] config.set_font_selection(font_index=selected) render.clear_font_cache() print(f" {G_DIM}> using face {selected}{RST}") time.sleep(0.8) print(CLR, end="") print(CURSOR_OFF, end="") print() def main(): atexit.register(lambda: print(CURSOR_ON, end="", flush=True)) def handle_sigint(*_): print(f"\n\n {G_DIM}> SIGNAL LOST{RST}") print(f" {W_GHOST}> connection terminated{RST}\n") sys.exit(0) signal.signal(signal.SIGINT, handle_sigint) w = tw() print(CLR, end="") print(CURSOR_OFF, end="") pick_font_face() w = tw() print() time.sleep(0.4) for ln in TITLE: print(f"{G_HI}{ln}{RST}") time.sleep(0.07) print() _subtitle = "literary consciousness stream" if config.MODE == 'poetry' else "digital consciousness stream" print(f" {W_DIM}v0.1 · {_subtitle}{RST}") print(f" {W_GHOST}{'─' * (w - 4)}{RST}") print() time.sleep(0.4) cached = load_cache() if '--refresh' not in sys.argv else None if cached: items = cached boot_ln("Cache", f"LOADED [{len(items)} SIGNALS]", True) elif config.MODE == 'poetry': slow_print(" > INITIALIZING LITERARY CORPUS...\n") time.sleep(0.2) print() items, linked, failed = fetch_poetry() print() print(f" {G_DIM}>{RST} {G_MID}{linked} TEXTS LOADED{RST} {W_GHOST}· {failed} DARK{RST}") print(f" {G_DIM}>{RST} {G_MID}{len(items)} STANZAS ACQUIRED{RST}") save_cache(items) else: slow_print(" > INITIALIZING FEED ARRAY...\n") time.sleep(0.2) print() items, linked, failed = fetch_all() print() print(f" {G_DIM}>{RST} {G_MID}{linked} SOURCES LINKED{RST} {W_GHOST}· {failed} DARK{RST}") print(f" {G_DIM}>{RST} {G_MID}{len(items)} SIGNALS ACQUIRED{RST}") save_cache(items) if not items: print(f"\n {W_DIM}> NO SIGNAL — check network{RST}") sys.exit(1) print() mic = MicMonitor(threshold_db=config.MIC_THRESHOLD_DB) mic_ok = mic.start() if mic.available: boot_ln("Microphone", "ACTIVE" if mic_ok else "OFFLINE · check System Settings → Privacy → Microphone", bool(mic_ok)) ntfy = NtfyPoller( config.NTFY_TOPIC, poll_interval=config.NTFY_POLL_INTERVAL, display_secs=config.MESSAGE_DISPLAY_SECS, ) ntfy_ok = ntfy.start() boot_ln("ntfy", "LISTENING" if ntfy_ok else "OFFLINE", ntfy_ok) if config.FIREHOSE: boot_ln("Firehose", "ENGAGED", True) time.sleep(0.4) slow_print(" > STREAMING...\n") time.sleep(0.2) print(f" {W_GHOST}{'─' * (w - 4)}{RST}") print() time.sleep(0.4) stream(items, ntfy, mic) print() print(f" {W_GHOST}{'─' * (tw() - 4)}{RST}") print(f" {G_DIM}> {config.HEADLINE_LIMIT} SIGNALS PROCESSED{RST}") print(f" {W_GHOST}> end of stream{RST}") print()