From df3524305f2379c7bb3c43ddb3cb47f1e4acd864 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Thu, 17 Sep 2026 08:56:12 -0400 Subject: [PATCH] docs(plugins): one display-size contract: display_manager.width/height CLAUDE.md (#580) says to read display_manager.width/height because matrix is None when hardware init fails; the development guide, the safety-harness doc and two DisplayManager docstrings still recommended matrix.width/height. The bundled starlark-apps plugin read matrix.width unguarded, so its magnify recommendation and frame scaling raised in fallback mode (e.g. after the Pi 5 hardware refusal). Co-Authored-By: Claude Opus 5 --- docs/PLUGIN_DEVELOPMENT_GUIDE.md | 6 ++++-- docs/plugin-safety-harness.md | 3 ++- plugin-repos/starlark-apps/manager.py | 14 +++++++------- src/display_manager.py | 14 +++++++++----- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/docs/PLUGIN_DEVELOPMENT_GUIDE.md b/docs/PLUGIN_DEVELOPMENT_GUIDE.md index a9db48c4..b4c8a28d 100644 --- a/docs/PLUGIN_DEVELOPMENT_GUIDE.md +++ b/docs/PLUGIN_DEVELOPMENT_GUIDE.md @@ -3,8 +3,10 @@ This guide explains how to set up a development workflow for plugins that are maintained in separate Git repositories while still being able to test them within the LEDMatrix project. > **Rendering guidance:** plugins should read the display size dynamically -> (`self.display_manager.matrix.width/height`) rather than hardcoding one -> panel. For plugins that want to *scale* their layout to any panel, the +> (`self.display_manager.width/height`) rather than hardcoding one +> panel. Don't read `display_manager.matrix.width/height`: `matrix` is +> `None` when hardware init fails, while the `width`/`height` properties +> fall back to the canvas size. For plugins that want to *scale* their layout to any panel, the > opt-in adaptive layout system ([ADAPTIVE_LAYOUT.md](ADAPTIVE_LAYOUT.md)) > provides the shared helpers — fonts, images, and composite layouts that > scale. Existing plugins keep their classic rendering unless they adopt diff --git a/docs/plugin-safety-harness.md b/docs/plugin-safety-harness.md index eb127f18..5a869ad2 100644 --- a/docs/plugin-safety-harness.md +++ b/docs/plugin-safety-harness.md @@ -10,7 +10,8 @@ plugin without breaking a size or screen you didn't think to test. There is **no fixed set of supported panel sizes** — an RGB matrix build can be any width/height and configuration (square, rectangle, 2×2, 4×4, 8×2, long strips, tall stacks). Plugins are expected to read dimensions dynamically -(`self.display_manager.matrix.width/height`) and lay themselves out +(`self.display_manager.width/height` — not `matrix.width/height`, since +`matrix` is `None` when hardware init fails) and lay themselves out accordingly, so a hardcoded coordinate or unscaled font shows up as a failure here. diff --git a/plugin-repos/starlark-apps/manager.py b/plugin-repos/starlark-apps/manager.py index b3f26118..5a810079 100644 --- a/plugin-repos/starlark-apps/manager.py +++ b/plugin-repos/starlark-apps/manager.py @@ -239,7 +239,7 @@ class StarlarkAppsPlugin(BasePlugin): # Calculate optimal magnification based on display size self.calculated_magnify = self._calculate_optimal_magnify() if self.calculated_magnify > 1: - self.logger.info(f"Display size: {self.display_manager.matrix.width}x{self.display_manager.matrix.height}, " + self.logger.info(f"Display size: {self.display_manager.width}x{self.display_manager.height}, " f"recommended magnify: {self.calculated_magnify}") # Load installed apps @@ -323,8 +323,8 @@ class StarlarkAppsPlugin(BasePlugin): Recommended magnify value (1-8) """ try: - display_width = self.display_manager.matrix.width - display_height = self.display_manager.matrix.height + display_width = self.display_manager.width + display_height = self.display_manager.height # Tronbyte native resolution NATIVE_WIDTH = 64 @@ -362,8 +362,8 @@ class StarlarkAppsPlugin(BasePlugin): Dictionary with recommendation details """ try: - display_width = self.display_manager.matrix.width - display_height = self.display_manager.matrix.height + display_width = self.display_manager.width + display_height = self.display_manager.height NATIVE_WIDTH = 64 NATIVE_HEIGHT = 32 @@ -852,8 +852,8 @@ class StarlarkAppsPlugin(BasePlugin): # Scale frames if needed if self.config.get("scale_output", True): - width = self.display_manager.matrix.width - height = self.display_manager.matrix.height + width = self.display_manager.width + height = self.display_manager.height # Get scaling method from config scale_method_str = self.config.get("scale_method", "nearest") diff --git a/src/display_manager.py b/src/display_manager.py index a0b105fa..3c44ea0c 100644 --- a/src/display_manager.py +++ b/src/display_manager.py @@ -91,9 +91,11 @@ class _LogicalMatrix: """Proxy that reports a logical (per-screen) size for a physical matrix. In double-sided mode the physical panel chain shows N identical copies of a - smaller logical screen. Plugins size themselves from ``matrix.width`` / - ``matrix.height`` (the documented convention, used at 30+ call sites), so - this proxy reports the logical dimensions while delegating every real + smaller logical screen. Plugins size themselves from + ``display_manager.width`` / ``height`` (the documented convention), which + defer to ``matrix.width`` / ``matrix.height`` -- and many older plugins read + ``matrix.width`` directly -- so this proxy reports the logical dimensions + while delegating every real operation — ``CreateFrameCanvas``, ``SwapOnVSync``, ``brightness``, ``Clear`` and so on — to the underlying physical matrix. The duplication itself happens once per frame in :meth:`DisplayManager.update_display`. @@ -682,8 +684,10 @@ class DisplayManager: def render_size(self, width: int, height: Optional[int] = None): """Temporarily present a smaller logical canvas to plugins. - Plugins lay out against ``display_manager.matrix.width`` (and the - ``width``/``height`` properties, which defer to it), so the only way to + Plugins lay out against the ``display_manager.width``/``height`` + properties (which defer to ``matrix.width`` when hardware is present, + and to the canvas when it is not; some older plugins read + ``matrix.width`` directly), so the only way to get a *narrower layout* rather than a cropped one is to tell the plugin the screen is narrower while it renders. Trimming after the fact cannot fix a forecast spread across five columns or a progress bar drawn at