Adds "Version" option in Settings; pulls git branch or hash in local dev
What changed, and why it matters
This commit adds a new 'Version' menu in Settings and updates the opening splash screen to show the software version. It is a straightforward user-interface feature with no security relevance.
No security action required; this is a benign UI feature.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces a VERSION constant at module level, a get_display_version() helper that returns ‘v0.8.6’ on SeedSigner OS or reads the local .git/HEAD branch/hash during development, a new VersionScreen, and a VersionView wired into the settings menu. The screensaver splash screen now calls get_display_version() instead of reading Controller.VERSION directly. No cryptographic, network, input-validation, or privilege changes are present.
Changed components
src/seedsigner/controller.pysrc/seedsigner/gui/screens/settings_screens.pysrc/seedsigner/views/screensaver.pysrc/seedsigner/views/settings_views.pyInspect captured patch +92 / −4
diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py
index 1e2f6d4..a151d3f 100644
--- a/src/seedsigner/controller.py
+++ b/src/seedsigner/controller.py
@@ -1,4 +1,5 @@
import logging
+import os
import time
import traceback
@@ -21,6 +22,9 @@ from seedsigner.views.view import Destination
logger = logging.getLogger(__name__)
+VERSION = "0.8.6"
+
+
class BackStack(list[Destination]):
def __repr__(self):
@@ -100,8 +104,6 @@ class Controller(Singleton):
rather than at the top in order avoid circular imports.
"""
- VERSION = "0.8.6"
-
# Declare class member vars with type hints to enable richer IDE support throughout
# the code.
_storage: SeedStorage = None # TODO: Rename "storage" to something more indicative of its temp, in-memory state
@@ -475,3 +477,27 @@ class Controller(Singleton):
exception_msg,
]
return Destination(UnhandledExceptionView, view_args={"error": error}, clear_history=True)
+
+
+ def get_display_version(self) -> str:
+ """
+ Returns a user-friendly version string for display in the UI.
+ """
+ name = f"v{VERSION}"
+ if Settings.HOSTNAME == Settings.SEEDSIGNER_OS:
+ return name
+
+ # We're in local dev; pull info from .git/HEAD if we can
+ git_HEAD_path = os.path.join(os.getcwd(), "..", ".git", "HEAD")
+
+ if os.path.exists(git_HEAD_path):
+ with open(git_HEAD_path, "r") as f:
+ git_ref = f.read().strip()
+ if git_ref.startswith("ref:"):
+ # get the branch name
+ name = git_ref.split("/")[-1]
+ else:
+ # get the commit hash
+ name = git_ref[:7]
+
+ return name
diff --git a/src/seedsigner/gui/screens/settings_screens.py b/src/seedsigner/gui/screens/settings_screens.py
index 48cdc7d..9d26cc0 100644
--- a/src/seedsigner/gui/screens/settings_screens.py
+++ b/src/seedsigner/gui/screens/settings_screens.py
@@ -310,6 +310,44 @@ class DonateScreen(BaseTopNavScreen):
+@dataclass
+class VersionScreen(BaseTopNavScreen):
+ version: str = None
+
+ def __post_init__(self):
+ self.title = _("Version")
+ super().__post_init__()
+
+ font_name = GUIConstants.FIXED_WIDTH_FONT_NAME
+ font_size = GUIConstants.get_top_nav_title_font_size() + 6
+ font = Fonts.get_font(font_name, font_size)
+ (left, char_height, char_width, bottom) = font.getbbox("X", anchor="ls")
+
+ if len(self.version) * char_width > self.canvas_width - 2*GUIConstants.EDGE_PADDING:
+ max_chars_width = int((self.canvas_width - 2*GUIConstants.EDGE_PADDING) / char_width)
+ # Add as many line breaks as needed for the version string to fit
+ wrapped_version = []
+ for i in range(0, len(self.version), max_chars_width):
+ if i + max_chars_width < len(self.version):
+ wrapped_version.append(self.version[i:i+max_chars_width])
+ else:
+ wrapped_version.append(self.version[i:])
+ self.version = "\n".join(wrapped_version)
+
+ # Center the version vertically
+ char_height *= -1 # due to the "ls" (baseline) anchor, height is negative
+ screen_y = int((self.canvas_height - self.top_nav.height) / 2) + self.top_nav.height - char_height*len(wrapped_version)
+
+ self.components.append(TextArea(
+ text=self.version,
+ font_name=font_name,
+ font_size=font_size,
+ font_color=GUIConstants.ACCENT_COLOR,
+ screen_y=screen_y,
+ ))
+
+
+
@dataclass
class SettingsQRConfirmationScreen(ButtonListScreen):
config_name: str = None
diff --git a/src/seedsigner/views/screensaver.py b/src/seedsigner/views/screensaver.py
index 8b2970d..c8c51c7 100644
--- a/src/seedsigner/views/screensaver.py
+++ b/src/seedsigner/views/screensaver.py
@@ -97,13 +97,17 @@ class OpeningSplashScreen(LogoScreen):
# Display version num below SeedSigner logo
font = Fonts.get_font(GUIConstants.get_body_font_name(), GUIConstants.get_top_nav_title_font_size())
- version = f"v{controller.VERSION}"
+ version = controller.get_display_version()
# The logo png is 240x240, but the actual logo is 70px tall, vertically centered
logo_height = 70
version_x = int(self.renderer.canvas_width/2)
version_y = int(self.canvas_height/2) + int(logo_height/2) + logo_offset_y + GUIConstants.COMPONENT_PADDING
- self.renderer.draw.text(xy=(version_x, version_y), text=version, font=font, fill=GUIConstants.ACCENT_COLOR, anchor="mt")
+ version_max_chars = 20
+ self.renderer.draw.text(xy=(version_x, version_y), text=version[:version_max_chars], font=font, fill=GUIConstants.ACCENT_COLOR, anchor="mt")
+ if len(version) > version_max_chars:
+ # Squeeze a second version display line in if needed
+ self.renderer.draw.text(xy=(version_x, version_y + GUIConstants.get_top_nav_title_font_size()), text=version[version_max_chars:], font=font, fill=GUIConstants.ACCENT_COLOR, anchor="mt")
if not self.renderer.is_screenshot_generator:
self.renderer.show_image()
diff --git a/src/seedsigner/views/settings_views.py b/src/seedsigner/views/settings_views.py
index cbf66e0..df03e43 100644
--- a/src/seedsigner/views/settings_views.py
+++ b/src/seedsigner/views/settings_views.py
@@ -17,6 +17,7 @@ class SettingsMenuView(View):
HARDWARE = ButtonOption("Hardware", right_icon_name=SeedSignerIconConstants.CHEVRON_RIGHT)
IO_TEST = ButtonOption("I/O test")
DONATE = ButtonOption("Donate")
+ VERSION = ButtonOption("Version")
def __init__(self, visibility: str = SettingsConstants.VISIBILITY__GENERAL, selected_attr: str = None, initial_scroll: int = 0):
super().__init__()
@@ -49,6 +50,7 @@ class SettingsMenuView(View):
button_data.append(self.IO_TEST)
button_data.append(self.DONATE)
+ button_data.append(self.VERSION)
elif self.visibility == SettingsConstants.VISIBILITY__ADVANCED:
title = _("Advanced")
@@ -96,6 +98,9 @@ class SettingsMenuView(View):
elif button_data[selected_menu_num] == self.DONATE:
return Destination(DonateView)
+
+ elif button_data[selected_menu_num] == self.VERSION:
+ return Destination(VersionView)
elif settings_entries[selected_menu_num].attr_name == SettingsConstants.SETTING__LOCALE:
return Destination(LocaleSelectionView)
@@ -315,3 +320,18 @@ class DonateView(View):
self.run_screen(settings_screens.DonateScreen)
return Destination(SettingsMenuView)
+
+
+
+class VersionView(View):
+ def run(self):
+ from seedsigner.controller import Controller
+ controller = Controller.get_instance()
+ version = controller.get_display_version()
+
+ self.run_screen(
+ settings_screens.VersionScreen,
+ version=version
+ )
+
+ return Destination(SettingsMenuView)
\ No newline at end of file
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.