from __future__ import annotations import json import logging from pathlib import Path from uuid import uuid4 from PIL import Image, ImageDraw from app.persistence import atomic_write_bytes logger = logging.getLogger(__name__) STARTUP_BRIGHTNESS = 50 OPEN_EYE_SECONDS = 1.3 CLOSED_EYE_SECONDS = 0.2 BACKGROUND = (255, 255, 255) PINK = (255, 79, 163) _PROCESS_BOOT_ID = f"process-{uuid4()}" def render_startup_smile(*, eyes_closed: bool = False) -> Image.Image: image = Image.new("RGB", (64, 64), BACKGROUND) draw = ImageDraw.Draw(image) if eyes_closed: draw.rectangle((14, 23, 25, 26), fill=PINK) draw.rectangle((38, 23, 49, 26), fill=PINK) else: draw.rectangle((16, 18, 23, 29), fill=PINK) draw.rectangle((40, 18, 47, 29), fill=PINK) for box in ( (14, 36, 17, 39), (18, 40, 21, 43), (22, 44, 25, 47), (26, 48, 37, 51), (38, 44, 41, 47), (42, 40, 45, 43), (46, 36, 49, 39), ): draw.rectangle(box, fill=PINK) return image def read_boot_id() -> str: try: value = Path("/proc/sys/kernel/random/boot_id").read_text(encoding="ascii").strip() if value: return value except (OSError, UnicodeError): logger.info("Linux boot ID is unavailable; using the current process session") return _PROCESS_BOOT_ID class StartupSessionStore: def __init__(self, data_dir: Path, boot_id: str | None = None) -> None: self.path = data_dir / "startup_indicator.json" self.boot_id = boot_id or read_boot_id() def should_show(self) -> bool: if not self.path.exists(): return True try: raw = json.loads(self.path.read_text(encoding="utf-8")) if not isinstance(raw, dict): raise ValueError("startup indicator state must be an object") return raw.get("dismissed_boot_id") != self.boot_id except (OSError, ValueError, TypeError): logger.exception("Startup indicator state is invalid; showing the indicator") return True def mark_dismissed(self) -> None: atomic_write_bytes( self.path, ( json.dumps( {"dismissed_boot_id": self.boot_id}, ensure_ascii=False, indent=2, ) + "\n" ).encode("utf-8"), )