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 JARVIS Display
────────────── ──────────────
Renders the JARVIS UI overlay. Works in two modes: Renders the JARVIS UI overlay via X11.
X11 (development laptop): Development: python display.py
python display.py Kiosk: DISPLAY=:0 python display.py --fullscreen
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
""" """
import os import os
@@ -24,34 +13,17 @@ import math
import argparse import argparse
from datetime import datetime 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 = 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( _parser.add_argument(
'--fullscreen', '-fs', action='store_true', '--fullscreen', '-fs', action='store_true',
help='Run fullscreen under X11/Wayland') help='Run fullscreen under X11/Wayland')
_parser.add_argument('--width', type=int, default=1280, _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, _parser.add_argument('--height', type=int, default=800,
help='Screen / framebuffer height (default 800)') help='Screen height (default 800)')
_args, _ = _parser.parse_known_args() _args, _ = _parser.parse_known_args()
USE_FB: bool = _args.framebuffer or bool(os.environ.get('JARVIS_FB')) import pygame
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
# ── Colour palette ─────────────────────────────────────────────────────────── # ── Colour palette ───────────────────────────────────────────────────────────
BLACK = ( 0, 0, 0) BLACK = ( 0, 0, 0)
@@ -210,7 +182,7 @@ def main() -> None:
W, H = _args.width, _args.height 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) screen = pygame.display.set_mode((W, H), pygame.FULLSCREEN | pygame.NOFRAME)
else: else:
screen = pygame.display.set_mode((W, H)) screen = pygame.display.set_mode((W, H))
@@ -219,13 +191,13 @@ def main() -> None:
pygame.mouse.set_visible(False) pygame.mouse.set_visible(False)
fonts = { fonts = {
'large': _load_font(56), 'large': _load_font(38),
'small': _load_font(18), 'small': _load_font(13),
} }
# Layout anchors — all relative to screen size so they adapt to resolution # 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_rx = int(min(W, H) * 0.085) # half-width at cheek level
face_ry = int(min(W, H) * 0.135) # half-height crown→chin face_ry = int(min(W, H) * 0.100) # half-height crown→chin
face_cx = W // 2 face_cx = W // 2
face_cy = int(face_ry * 1.2) + 8 # nearly touches the top edge face_cy = int(face_ry * 1.2) + 8 # nearly touches the top edge
status_y = int(H * 0.82) status_y = int(H * 0.82)
@@ -261,10 +233,6 @@ def main() -> None:
if event.key in (pygame.K_ESCAPE, pygame.K_q): if event.key in (pygame.K_ESCAPE, pygame.K_q):
pygame.quit() pygame.quit()
return return
# Touch / mouse tap anywhere also quits in kiosk mode
if USE_FB and event.type == pygame.MOUSEBUTTONDOWN:
pygame.quit()
return
screen.fill(BLACK) screen.fill(BLACK)
if face_img: if face_img: