From cbdd4a2952ec2898739cbe1d3dfc2588d96dd63c Mon Sep 17 00:00:00 2001 From: Spencer Grimes Date: Wed, 25 Feb 2026 00:21:36 -0600 Subject: [PATCH] Moving to offscreen Driver --- KH-clock.service | 4 ++-- display.py | 36 ++++++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/KH-clock.service b/KH-clock.service index 20ef6ae..9d6efe4 100644 --- a/KH-clock.service +++ b/KH-clock.service @@ -8,8 +8,8 @@ Type=simple User=pi WorkingDirectory=/opt/KH_Clock -# Tell SDL to render via KMS/DRM (required on Le Potato and similar ARM boards) -Environment=SDL_VIDEODRIVER=kmsdrm +# SDL renders offscreen; pixels are blit directly to /dev/fb0 (Amlogic kmsdrm is unsupported) +Environment=SDL_VIDEODRIVER=offscreen # Disable console blanking so the TV stays on ExecStartPre=/bin/sh -c 'echo -ne "\033[9;0]" > /dev/tty1' diff --git a/display.py b/display.py index 9fb5b70..c8397a5 100644 --- a/display.py +++ b/display.py @@ -1,3 +1,4 @@ +import mmap import os import threading import time @@ -147,7 +148,9 @@ class DisplayThread(threading.Thread): if self.dev_mode: os.environ.setdefault("SDL_VIDEODRIVER", "x11") else: - os.environ["SDL_VIDEODRIVER"] = "kmsdrm" + # SDL kmsdrm is incompatible with the Amlogic MESON DRM driver. + # Use offscreen rendering and blit pixels directly to /dev/fb0. + os.environ["SDL_VIDEODRIVER"] = "offscreen" try: pygame.init() @@ -166,6 +169,19 @@ class DisplayThread(threading.Thread): clock = pygame.time.Clock() screen_w, screen_h = screen.get_size() + # Open /dev/fb0 for direct pixel writes (framebuffer mode only) + fb0_mmap = None + fb0_file = None + if not self.dev_mode: + try: + bpp = int(Path("/sys/class/graphics/fb0/bits_per_pixel").read_text()) + fb0_file = open("/dev/fb0", "rb+") + fb0_mmap = mmap.mmap(fb0_file.fileno(), screen_w * screen_h * (bpp // 8)) + except Exception as e: + print(f"[display] Failed to open /dev/fb0: {e}") + pygame.quit() + return + clock_font_path = _find_font(self.config.clock_font_path, _CLOCK_FONT_CANDIDATES) msg_font_path = _find_font(self.config.message_font_path, _MESSAGE_FONT_CANDIDATES) @@ -205,9 +221,17 @@ class DisplayThread(threading.Thread): last_msg_text = None self._draw_clock(screen, clock_font, screen_w, screen_h) - pygame.display.flip() + if fb0_mmap is not None: + fb0_mmap.seek(0) + fb0_mmap.write(pygame.image.tostring(screen, "BGRA")) + else: + pygame.display.flip() clock.tick(self.config.fps) + if fb0_mmap: + fb0_mmap.close() + if fb0_file: + fb0_file.close() pygame.quit() def _create_surface(self) -> pygame.Surface: @@ -218,10 +242,10 @@ class DisplayThread(threading.Thread): pygame.display.set_caption("KH Clock [DEV]") return surface - flags = pygame.FULLSCREEN | pygame.NOFRAME - if self.config.width and self.config.height: - return pygame.display.set_mode((self.config.width, self.config.height), flags) - return pygame.display.set_mode((0, 0), flags) + # Read actual framebuffer dimensions from sysfs + size_str = Path("/sys/class/graphics/fb0/virtual_size").read_text().strip() + w, h = map(int, size_str.split(",")) + return pygame.display.set_mode((w, h)) @staticmethod def _draw_clock(