This commit is contained in:
David Rice
2026-04-16 18:56:21 +01:00
parent 27ed9c9e40
commit 9ccc02a2c9

View File

@@ -2,21 +2,10 @@
"""
JARVIS Display
──────────────
Renders the JARVIS UI overlay. Works in two modes:
Renders the JARVIS UI overlay via X11.
X11 (development laptop):
python display.py
Linux framebuffer (device no windowing system):
python display.py --framebuffer
# or
JARVIS_FB=1 python display.py
Framebuffer prerequisites on Debian (device):
sudo apt install python3-pygame
sudo usermod -aG video $USER # then log out/in
# If fbdev fails (common on newer kernels) try KMS/DRM:
SDL_VIDEODRIVER=kmsdrm python display.py --framebuffer
Development: python display.py
Kiosk: DISPLAY=:0 python display.py --fullscreen
"""
import os
@@ -24,34 +13,17 @@ import math
import argparse
from datetime import datetime
# ── CLI / env-var flags ─────────────────────────────────────────────────────
# These must be parsed BEFORE pygame is imported so we can set SDL env vars
# in time for pygame.init() to pick them up.
_parser = argparse.ArgumentParser(description='JARVIS display', add_help=True)
_parser.add_argument(
'--framebuffer', '-fb', action='store_true',
help='Render to Linux framebuffer instead of an X11/Wayland window')
_parser.add_argument(
'--fullscreen', '-fs', action='store_true',
help='Run fullscreen under X11/Wayland')
_parser.add_argument('--width', type=int, default=1280,
help='Screen / framebuffer width (default 1280)')
help='Screen width (default 1280)')
_parser.add_argument('--height', type=int, default=800,
help='Screen / framebuffer height (default 800)')
help='Screen height (default 800)')
_args, _ = _parser.parse_known_args()
USE_FB: bool = _args.framebuffer or bool(os.environ.get('JARVIS_FB'))
if USE_FB:
# SDL2 env vars must be set before pygame.display.init()
os.environ.setdefault('SDL_VIDEODRIVER', 'kmsdrm')
os.environ.setdefault('SDL_RENDER_DRIVER', 'software')
os.environ.setdefault('SDL_FBDEV', os.environ.get('JARVIS_FBDEV', '/dev/fb0'))
# Prevent SDL trying to open a mouse device on the framebuffer
os.environ.setdefault('SDL_NOMOUSE', '1')
import pygame # noqa: E402 — import after env setup
import pygame
# ── Colour palette ───────────────────────────────────────────────────────────
BLACK = ( 0, 0, 0)
@@ -210,7 +182,7 @@ def main() -> None:
W, H = _args.width, _args.height
if USE_FB or _args.fullscreen:
if _args.fullscreen:
screen = pygame.display.set_mode((W, H), pygame.FULLSCREEN | pygame.NOFRAME)
else:
screen = pygame.display.set_mode((W, H))
@@ -219,13 +191,13 @@ def main() -> None:
pygame.mouse.set_visible(False)
fonts = {
'large': _load_font(56),
'small': _load_font(18),
'large': _load_font(38),
'small': _load_font(13),
}
# Layout anchors — all relative to screen size so they adapt to resolution
face_rx = int(min(W, H) * 0.115) # half-width at cheek level
face_ry = int(min(W, H) * 0.135) # half-height crown→chin
face_rx = int(min(W, H) * 0.085) # half-width at cheek level
face_ry = int(min(W, H) * 0.100) # half-height crown→chin
face_cx = W // 2
face_cy = int(face_ry * 1.2) + 8 # nearly touches the top edge
status_y = int(H * 0.82)
@@ -261,10 +233,6 @@ def main() -> None:
if event.key in (pygame.K_ESCAPE, pygame.K_q):
pygame.quit()
return
# Touch / mouse tap anywhere also quits in kiosk mode
if USE_FB and event.type == pygame.MOUSEBUTTONDOWN:
pygame.quit()
return
screen.fill(BLACK)
if face_img: