Refactor `DisplayDriver` to be a true parent class; add Factory
What changed, and why it matters
This commit is a routine internal code reorganization. It restructures how the SeedSigner app creates and manages its screen drivers, turning the existing display controller into a parent class and adding a factory pattern. There is no indication this change fixes a security bug or introduces a security vulnerability.
No security action required. Review as normal code-quality change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors DisplayDriver from a wrapper class into an abstract dataclass base class, moves instantiation logic into a new DisplayDriverFactory, and updates ST7789 drivers to inherit from DisplayDriver. It also adds a cleanup() hook called in Renderer when replacing an existing display instance. The changes are architectural and do not alter security boundaries, input handling, cryptography, or network behavior.
Changed components
src/seedsigner/gui/renderer.pysrc/seedsigner/hardware/displays/display_driver.pysrc/seedsigner/hardware/displays/ST7789.pysrc/seedsigner/hardware/displays/st7789_mpy.pyInspect captured patch +96 / −63
diff --git a/src/seedsigner/gui/renderer.py b/src/seedsigner/gui/renderer.py
index d0e2381..e572964 100644
--- a/src/seedsigner/gui/renderer.py
+++ b/src/seedsigner/gui/renderer.py
@@ -1,9 +1,7 @@
from PIL import Image, ImageDraw
from threading import Lock
-# from seedsigner.hardware.st7789_mpy import ST7789
-from seedsigner.hardware.displays.display_driver import ALL_DISPLAY_TYPES, DISPLAY_TYPE__ILI9341, DISPLAY_TYPE__ILI9486, DISPLAY_TYPE__ST7789, DisplayDriver
-from seedsigner.hardware.displays.ili9341 import ILI9341, ILI9341_TFTWIDTH, ILI9341_TFTHEIGHT
+from seedsigner.hardware.displays.display_driver import ALL_DISPLAY_TYPES, DISPLAY_TYPE__ILI9341, DISPLAY_TYPE__ILI9486, DISPLAY_TYPE__ST7789, DisplayDriverFactory
from seedsigner.models.settings import Settings
from seedsigner.models.settings_definition import SettingsConstants
from seedsigner.models.singleton import ConfigurableSingleton
@@ -45,7 +43,12 @@ class Renderer(ConfigurableSingleton):
raise Exception(f"Invalid display type: {self.display_type}")
width, height = display_config.split("_")[1].split("x")
- self.disp = DisplayDriver(self.display_type, width=int(width), height=int(height))
+
+ if self.disp:
+ # Existing instances might need to close resources like pwm
+ self.disp.cleanup()
+
+ self.disp = DisplayDriverFactory.instantiate_display_driver(self.display_type, width=int(width), height=int(height))
if Settings.get_instance().get_value(SettingsConstants.SETTING__DISPLAY_COLOR_INVERTED, default_if_none=True) == SettingsConstants.OPTION__ENABLED:
self.disp.invert()
diff --git a/src/seedsigner/hardware/displays/ST7789.py b/src/seedsigner/hardware/displays/ST7789.py
index 67d9175..b399aa6 100644
--- a/src/seedsigner/hardware/displays/ST7789.py
+++ b/src/seedsigner/hardware/displays/ST7789.py
@@ -2,16 +2,23 @@ import spidev
import RPi.GPIO as GPIO
import time
import array
+from dataclasses import dataclass
+from seedsigner.hardware.displays.display_driver import DisplayDriver
-class ST7789(object):
- """class for ST7789 240*240 1.3inch OLED displays."""
- def __init__(self):
- self.width = 240
- self.height = 240
+@dataclass
+class ST7789(DisplayDriver):
+ """
+ The original SeedSigner display driver.
+ Note that self._width and self._height are provided by the parent DisplayDriver class
+ and are set during instantiation via the DisplayDriverFactory.
+
+ class for ST7789 240*240 1.3inch OLED displays.
+ """
+ def __post_init__(self):
#Initialize DC RST pin
self._dc = 22
self._rst = 13
diff --git a/src/seedsigner/hardware/displays/display_driver.py b/src/seedsigner/hardware/displays/display_driver.py
index a41bb32..9903476 100644
--- a/src/seedsigner/hardware/displays/display_driver.py
+++ b/src/seedsigner/hardware/displays/display_driver.py
@@ -1,41 +1,19 @@
+from dataclasses import dataclass
+
+
DISPLAY_TYPE__ST7789 = "st7789"
DISPLAY_TYPE__ILI9341 = "ili9341"
DISPLAY_TYPE__ILI9486 = "ili9486"
ALL_DISPLAY_TYPES = [DISPLAY_TYPE__ST7789, DISPLAY_TYPE__ILI9341, DISPLAY_TYPE__ILI9486]
+
+@dataclass
class DisplayDriver:
- def __init__(self, display_type: str = DISPLAY_TYPE__ST7789, width: int = None, height: int = None):
- if display_type not in ALL_DISPLAY_TYPES:
- raise ValueError(f"Invalid display type: {display_type}")
- self.display_type = display_type
-
- if self.display_type == DISPLAY_TYPE__ST7789:
- if width not in [240, 320] or height != 240:
- raise ValueError("ST7789 display only supports 240x240 or 320x240 resolutions")
+ _width: int
+ _height: int
- if width == 240:
- # TODO: For now the original ST7789 driver has to be used for 240x240.
- # The mpy version below renders incorrectly (almost like each row of pixels
- # is one pixel short, so the entire screen exhibits a diagonal skew).
- from seedsigner.hardware.displays.ST7789 import ST7789
- self.display = ST7789()
-
- elif width == 320:
- from seedsigner.hardware.displays.st7789_mpy import ST7789
- # Have to swap width and height; screen is natively 240x320
- self.display = ST7789(width=height, height=width)
-
- elif self.display_type == DISPLAY_TYPE__ILI9341:
- from seedsigner.hardware.displays.ili9341 import ILI9341
- self.display = ILI9341()
- self.display.begin()
-
- elif self.display_type == DISPLAY_TYPE__ILI9486:
- # TODO: improve performance of ili9486 driver
- raise Exception("ILI9486 display not implemented yet")
-
def __str__(self):
return f"DisplayDriver(display_type={self.display_type}, width={self.width}, height={self.height})"
@@ -43,18 +21,63 @@ class DisplayDriver:
@property
def width(self):
- return self.display.width
+ return self._width
@property
def height(self):
- return self.display.height
+ return self._height
def invert(self, enabled: bool = True):
"""Invert how the display interprets colors"""
- self.display.invert(enabled)
+ raise Exception("Must be implemented in child class")
def show_image(self, image, x_start: int = 0, y_start: int = 0):
- self.display.show_image(image, x_start, y_start)
\ No newline at end of file
+ raise Exception("Must be implemented in child class")
+
+
+ def cleanup(self):
+ """Cleanup resources related to the display driver."""
+ pass
+
+
+
+class DisplayDriverFactory:
+ """
+ Manages all logic related to instantiating display drivers based on type and resolution.
+
+ Imports for specific display drivers are done within this class to avoid circular imports.
+ """
+
+ @classmethod
+ def instantiate_display_driver(cls, display_type: str = DISPLAY_TYPE__ST7789, width: int = None, height: int = None) -> DisplayDriver:
+ if display_type not in ALL_DISPLAY_TYPES:
+ raise ValueError(f"Invalid display type: {display_type}")
+
+ if display_type == DISPLAY_TYPE__ST7789:
+ if width not in [240, 320] or height != 240:
+ raise ValueError("ST7789 display only supports 240x240 or 320x240 resolutions")
+
+ if width == 240:
+ # TODO: For now the original ST7789 driver has to be used for 240x240.
+ # The mpy version below renders incorrectly (almost like each row of pixels
+ # is one pixel short, so the entire screen exhibits a diagonal skew).
+ from seedsigner.hardware.displays.ST7789 import ST7789 as original_ST7789
+ return original_ST7789(_width=width, _height=height)
+
+ elif width == 320:
+ from seedsigner.hardware.displays.st7789_mpy import ST7789 as mpy_ST7789
+ # Have to swap width and height; screen is natively 240x320
+ return mpy_ST7789(_width=height, _height=width)
+
+ elif display_type == DISPLAY_TYPE__ILI9341:
+ from seedsigner.hardware.displays.ili9341 import ILI9341
+ display = ILI9341(_width=width, _height=height)
+ display.begin()
+ return display
+
+ elif display_type == DISPLAY_TYPE__ILI9486:
+ # TODO: improve performance of ili9486 driver
+ raise Exception("ILI9486 display not implemented yet")
diff --git a/src/seedsigner/hardware/displays/st7789_mpy.py b/src/seedsigner/hardware/displays/st7789_mpy.py
index 5a7bd5f..2aad703 100644
--- a/src/seedsigner/hardware/displays/st7789_mpy.py
+++ b/src/seedsigner/hardware/displays/st7789_mpy.py
@@ -54,8 +54,11 @@ import array
import spidev
import RPi.GPIO as GPIO
+from dataclasses import dataclass
from math import sin, cos
+from seedsigner.hardware.displays.display_driver import DisplayDriver
+
#
# This allows sphinx to build the docs
#
@@ -228,7 +231,9 @@ def color565(red, green=0, blue=0):
return (red & 0xF8) << 8 | (green & 0xFC) << 3 | blue >> 3
-class ST7789:
+
+@dataclass
+class ST7789(DisplayDriver):
"""
ST7789 driver class
@@ -262,20 +267,15 @@ class ST7789:
"""
- def __init__(
- self,
- # spi,
- width,
- height,
- reset=13,
- dc=22,
- cs=None,
- backlight=18,
- rotation=1,
- color_order=BGR,
- custom_init=None,
- custom_rotations=None,
- ):
+ def __post_init__(self):
+ reset=13
+ dc=22
+ cs=None
+ backlight=18
+ rotation=1
+ color_order=BGR
+ custom_init=None
+ custom_rotations=None
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
@@ -290,20 +290,20 @@ class ST7789:
"""
Initialize display.
"""
- self.rotations = custom_rotations or self._find_rotations(width, height)
+ self.rotations = custom_rotations or self._find_rotations(self.width, self.height)
if not self.rotations:
supported_displays = ", ".join(
[f"{display[0]}x{display[1]}" for display in _SUPPORTED_DISPLAYS]
)
raise ValueError(
- f"Unsupported {width}x{height} display. Supported displays: {supported_displays}"
+ f"Unsupported {self.width}x{self.height} display. Supported displays: {supported_displays}"
)
if dc is None:
raise ValueError("dc pin is required.")
- self.physical_width = self.width = width
- self.physical_height = self.height = height
+ self.physical_width = self.width
+ self.physical_height = self.height
self.xstart = 0
self.ystart = 0
self.spi = spi
@@ -445,8 +445,8 @@ class ST7789:
self._rotation = rotation
(
madctl,
- self.width,
- self.height,
+ self._width, # have to use the settable internal vars
+ self._height, # have to use the settable internal vars
self.xstart,
self.ystart,
self.needs_swap,
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.