From 6fb11166c5755fcfe3be70bad263f69f02962843 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Wed, 10 Dec 2025 08:47:15 -0600 Subject: [PATCH 01/14] Remove dead code and debugging code --- src/seedsigner/gui/screens/scan_screens.py | 28 +++------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/src/seedsigner/gui/screens/scan_screens.py b/src/seedsigner/gui/screens/scan_screens.py index c54fa576..44f89436 100644 --- a/src/seedsigner/gui/screens/scan_screens.py +++ b/src/seedsigner/gui/screens/scan_screens.py @@ -62,7 +62,6 @@ class ScanScreen(BaseScreen): self.camera.start_video_stream_mode(resolution=self.resolution, framerate=self.framerate, format="rgb") self.frames_decode_status = ThreadsafeCounter() - self.frames_decoded_counter = ThreadsafeCounter() self.threads.append(ScanScreen.LivePreviewThread( decoder=self.decoder, @@ -70,12 +69,11 @@ class ScanScreen(BaseScreen): instructions_text=self.instructions_text, render_rect=self.render_rect, frame_decode_status=self.frames_decode_status, - frames_decoded_counter=self.frames_decoded_counter, )) class LivePreviewThread(BaseThread): - def __init__(self, decoder: DecodeQR, renderer: renderer.Renderer, instructions_text: str, render_rect: tuple[int,int,int,int], frame_decode_status: ThreadsafeCounter, frames_decoded_counter: ThreadsafeCounter): + def __init__(self, decoder: DecodeQR, renderer: renderer.Renderer, instructions_text: str, render_rect: tuple[int,int,int,int], frame_decode_status: ThreadsafeCounter): from seedsigner.hardware.camera import Camera self.camera = Camera.get_instance() @@ -87,8 +85,6 @@ class ScanScreen(BaseScreen): else: self.render_rect = (0, 0, self.renderer.canvas_width, self.renderer.canvas_height) self.frame_decode_status = frame_decode_status - self.frames_decoded_counter = frames_decoded_counter - self.last_frame_decoded_count = self.frames_decoded_counter.cur_count self.render_width = self.render_rect[2] - self.render_rect[0] self.render_height = self.render_rect[3] - self.render_rect[1] self.decoder_fps = "0.0" @@ -97,39 +93,23 @@ class ScanScreen(BaseScreen): def run(self): - from timeit import default_timer as timer - instructions_font = Fonts.get_font(GUIConstants.get_body_font_name(), GUIConstants.get_button_font_size()) # pre-calculate how big the animated QR percent display can be - left, top, right, bottom = instructions_font.getbbox("100%") + (left, top, right, bottom) = instructions_font.getbbox("100%") progress_text_width = right - left - start_time = time.time() num_frames = 0 - debug = False - show_framerate = False # enable for debugging / testing while self.keep_running: frame = self.camera.read_video_stream(as_image=True) if frame is not None: num_frames += 1 - cur_time = time.time() - cur_fps = num_frames / (cur_time - start_time) scan_text = None progress_percentage = self.decoder.get_percent_complete() if progress_percentage == 0: # We've just started scanning, no results yet - if show_framerate: - scan_text = f"{cur_fps:0.2f} | {self.decoder_fps}" - else: - scan_text = self.instructions_text - - elif debug: - # Special debugging output for animated QRs - scan_text = f"{self.decoder.get_percent_complete()}% | {self.decoder.get_percent_complete(weight_mixed_frames=True)}% (new)" - if show_framerate: - scan_text += f" {cur_fps:0.2f} | {self.decoder_fps}" + scan_text = self.instructions_text with self.renderer.lock: # Use nearest neighbor resizing for max speed @@ -216,7 +196,6 @@ class ScanScreen(BaseScreen): # Render the dot to indicate successful QR frame read indicator_size = 10 - self.last_frame_decoded_count = self.frames_decoded_counter.cur_count status_color_map = { ScanScreen.FRAME__ADDED_PART: GUIConstants.SUCCESS_COLOR, ScanScreen.FRAME__REPEATED_PART: GUIConstants.INACTIVE_COLOR, @@ -267,7 +246,6 @@ class ScanScreen(BaseScreen): self.camera.stop_video_stream_mode() break - self.frames_decoded_counter.increment() # Notify the live preview thread how our most recent decode went if status == DecodeQRStatus.FALSE: # Did not find anything to decode in the current frame From dd6277b8d8f2dcaeeb0069bc90478dc047e8e485 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:03:36 -0600 Subject: [PATCH 02/14] Remove dead code --- src/seedsigner/gui/screens/screen.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/seedsigner/gui/screens/screen.py b/src/seedsigner/gui/screens/screen.py index 0a159a33..4278cb99 100644 --- a/src/seedsigner/gui/screens/screen.py +++ b/src/seedsigner/gui/screens/screen.py @@ -371,7 +371,6 @@ class ButtonListScreen(BaseTopNavScreen): if self.has_scroll_arrows: self.arrow_half_width = 10 - self.cur_scroll_y = self.scroll_y_initial_offset if self.scroll_y_initial_offset is not None else 0 self.up_arrow_img = Image.new("RGBA", size=(2 * self.arrow_half_width, 8), color="black") self.up_arrow_img_y = self.top_nav.height - 12 arrow_draw = ImageDraw.Draw(self.up_arrow_img) @@ -1072,21 +1071,6 @@ class ResetScreen(BaseTopNavScreen): -@dataclass -class PowerOffScreen(BaseTopNavScreen): - def __post_init__(self): - self.title = _("Powering Off") - self.show_back_button = False - super().__post_init__() - - self.components.append(TextArea( - text=_("Please wait about 30 seconds before disconnecting power."), - screen_y=self.top_nav.height, - height=self.canvas_height - self.top_nav.height, - )) - - - @dataclass class PowerOffNotRequiredScreen(BaseTopNavScreen): def __post_init__(self): From 0736e066e2504f0383c23a51a46b58de387218f5 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:36:55 -0600 Subject: [PATCH 03/14] Remove dead code --- src/seedsigner/helpers/mnemonic_generation.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/seedsigner/helpers/mnemonic_generation.py b/src/seedsigner/helpers/mnemonic_generation.py index b3e90680..444c3fed 100644 --- a/src/seedsigner/helpers/mnemonic_generation.py +++ b/src/seedsigner/helpers/mnemonic_generation.py @@ -110,14 +110,3 @@ def get_partial_final_word(coin_flips: str, wordlist_language_code: str = Settin wordlist_index = int(binary_string, 2) return Seed.get_wordlist(wordlist_language_code)[wordlist_index] - - - -# Note: This currently isn't being used since we're now chaining hashed bytes for the -# image-based entropy and aren't just ingesting a single image. -def generate_mnemonic_from_image(image, wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) -> list[str]: - import hashlib - hash = hashlib.sha256(image.tobytes()) - - # Return as a list - return bip39.mnemonic_from_bytes(hash.digest(), wordlist=Seed.get_wordlist(wordlist_language_code)).split() From 1769938c51067beba77ec59fc6df7a57f42415c4 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Sun, 14 Dec 2025 06:55:15 -0600 Subject: [PATCH 04/14] Initial Hindi support --- src/seedsigner/gui/components.py | 9 ++++++--- src/seedsigner/models/settings_definition.py | 2 +- .../fonts/NotoSansDevanagari-Regular.ttf | Bin 0 -> 221084 bytes 3 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 src/seedsigner/resources/fonts/NotoSansDevanagari-Regular.ttf diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index d9a2e0b4..5096cd7a 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -52,7 +52,7 @@ class GUIConstants: "default": "OpenSans-Regular", SettingsConstants.LOCALE__ARABIC: "NotoSansAR-Regular", SettingsConstants.LOCALE__CHINESE_SIMPLIFIED: "NotoSansSC-Regular", - # SettingsConstants.LOCALE__CHINESE_TRADITIONAL: "NotoSansTC-Regular", + SettingsConstants.LOCALE__HINDI: "NotoSansDevanagari-Regular", SettingsConstants.LOCALE__JAPANESE: "NotoSansJP-Regular", SettingsConstants.LOCALE__KOREAN: "NotoSansKR-Regular", SettingsConstants.LOCALE__PERSIAN: "NotoSansAR-Regular", # Uses the Arabic font @@ -63,9 +63,10 @@ class GUIConstants: TOP_NAV_TITLE_FONT_NAME["default"] = "OpenSans-SemiBold" TOP_NAV_TITLE_FONT_SIZE = { "default": 20, + SettingsConstants.LOCALE__CHINESE_SIMPLIFIED: 23, # Some chars won't render below 23px + SettingsConstants.LOCALE__HINDI: 22, SettingsConstants.LOCALE__JAPANESE: 22, # Titles won't render below 22px SettingsConstants.LOCALE__KOREAN: 23, # Titles won't render below 23px - SettingsConstants.LOCALE__CHINESE_SIMPLIFIED: 23, # Some chars won't render below 23px } TOP_NAV_HEIGHT = 48 TOP_NAV_BUTTON_SIZE = 32 @@ -73,9 +74,10 @@ class GUIConstants: BODY_FONT_NAME = BASE_LOCALE_FONTS.copy() BODY_FONT_SIZE = { "default": 17, + SettingsConstants.LOCALE__CHINESE_SIMPLIFIED: 18, + SettingsConstants.LOCALE__HINDI: 18, SettingsConstants.LOCALE__JAPANESE: 18, SettingsConstants.LOCALE__KOREAN: 18, - SettingsConstants.LOCALE__CHINESE_SIMPLIFIED: 18, } BODY_FONT_MAX_SIZE = TOP_NAV_TITLE_FONT_SIZE["default"] BODY_FONT_MIN_SIZE = 15 @@ -94,6 +96,7 @@ class GUIConstants: "default": 18, "ar": 18, "fa": 18, + SettingsConstants.LOCALE__HINDI: 20, SettingsConstants.LOCALE__JAPANESE: 20, SettingsConstants.LOCALE__KOREAN: 20, SettingsConstants.LOCALE__CHINESE_SIMPLIFIED: 20, diff --git a/src/seedsigner/models/settings_definition.py b/src/seedsigner/models/settings_definition.py index 2a6a1d7a..90660f5c 100644 --- a/src/seedsigner/models/settings_definition.py +++ b/src/seedsigner/models/settings_definition.py @@ -129,6 +129,7 @@ class SettingsConstants: # --------- Beta languages ------------------------------------------------------ LOCALE__CHINESE_SIMPLIFIED: "(beta) 简体中文 (Chinese Simplified)", + LOCALE__HINDI: "(beta) हिन्दी (Hindi)", LOCALE__JAPANESE: "(beta) 日本語 (Japanese)", LOCALE__KOREAN: "(beta) 한국어 (Korean)", LOCALE__THAI: "(beta) ไทย (Thai)", @@ -149,7 +150,6 @@ class SettingsConstants: # LOCALE__GUJARATI: "ગુજરાતી (Gujarati)", LOCALE__HAUSA: "Hausa", # LOCALE__HEBREW: "עברית (Hebrew)", - # LOCALE__HINDI: "हिन्दी (Hindi)", LOCALE__CROATIAN: "Hrvatski", LOCALE__INDONESIAN: "Indonesia", LOCALE__JAVANESE: "Jawa (Javanese)", diff --git a/src/seedsigner/resources/fonts/NotoSansDevanagari-Regular.ttf b/src/seedsigner/resources/fonts/NotoSansDevanagari-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ab3f43341721c903f3eaec8e4c386792577485ad GIT binary patch literal 221084 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 z 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

YjBfPl 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 Date: Mon, 15 Dec 2025 17:54:10 -0600 Subject: [PATCH 05/14] Remove sleeps that cause delays exiting thread --- src/seedsigner/gui/components.py | 44 ++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index d9a2e0b4..ac0805cd 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -623,7 +623,7 @@ class TextArea(BaseComponent): self.horizontal_scroll_position = 0 self.scroll_increment_sign = 1 # flip to negative to scroll text to the right - self.renderer = Renderer.get_instance() + self.renderer = Renderer.get_instance() def stop_scrolling(self): @@ -645,11 +645,23 @@ class TextArea(BaseComponent): """ max_scroll = self.rendered_text_img.width - self.visible_width + # The scrolling holds / pauses at the start and end of the text line. These + # vars track when we started holding and how long we should hold. + hold_started_at = None + cur_hold_duration = None + while self.keep_running: if not self.scrolling_active: time.sleep(0.1) continue + if cur_hold_duration: + hold_time_elapsed = time.time() - hold_started_at + if hold_time_elapsed < cur_hold_duration: + # Still holding; skip scrolling logic + time.sleep(0.1) + continue + with self.renderer.lock: if not self.scrolling_active: # We were stopped while waiting for the lock @@ -659,30 +671,40 @@ class TextArea(BaseComponent): self.renderer.canvas.paste(img, (self.screen_x, self.screen_y - self.scroll_y)) self.renderer.show_image() - if self.horizontal_scroll_position == 0: - # Pause on initial (left-justified) position... - time.sleep(self.begin_hold_secs) + if hold_started_at is not None: + # If we're here, we've held long enough; reset the vars and resume + # scrolling. + hold_started_at = None + cur_hold_duration = None + + elif self.horizontal_scroll_position == 0: + # Pause on initial (left-justified) position... + hold_started_at = time.time() + cur_hold_duration = self.begin_hold_secs + + # Next scroll direction will be left + self.scroll_increment_sign = 1 # Don't count those pause seconds last_render_time = None - - # Scroll the text left - self.scroll_increment_sign = 1 + continue elif self.horizontal_scroll_position == max_scroll: # ...and slight pause at end of scroll - time.sleep(self.end_hold_secs) + hold_started_at = time.time() + cur_hold_duration = self.end_hold_secs # Don't count those pause seconds last_render_time = None - # Scroll the text right + # Scroll will be to the right self.scroll_increment_sign = -1 - + continue + else: # No need to CPU limit when running in its own thread? time.sleep(0.02) - + next_render_time = time.time() if not last_render_time: From e3c6b6397aaca96d351643d6ac55ba484365aee5 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:18:11 -0600 Subject: [PATCH 06/14] Eliminate unnecessary scrolling for short text --- src/seedsigner/gui/components.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index ac0805cd..dffbf828 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -643,8 +643,24 @@ class TextArea(BaseComponent): 50px/sec creates a slight ghosting / doubling effect that impedes readability. 45px/sec is better but still perceptually a bit stuttery. """ + def _render_text(): + img = self.rendered_text_img.crop((self.horizontal_scroll_position, 0, self.horizontal_scroll_position + self.visible_width, self.rendered_text_img.height)) + self.renderer.canvas.paste(img, (self.screen_x, self.screen_y - self.scroll_y)) + self.renderer.show_image() + max_scroll = self.rendered_text_img.width - self.visible_width + # Technically, the math might say that we need to scroll for 1px, but it's + # not worth having the text twitch back and forth by such a small amount. + min_scrollable_diff = 2 + + if max_scroll < min_scrollable_diff: + # No scrolling needed. Render once and exit the thread. + with self.renderer.lock: + _render_text() + self.stop() + return + # The scrolling holds / pauses at the start and end of the text line. These # vars track when we started holding and how long we should hold. hold_started_at = None @@ -667,9 +683,8 @@ class TextArea(BaseComponent): # We were stopped while waiting for the lock continue - img = self.rendered_text_img.crop((self.horizontal_scroll_position, 0, self.horizontal_scroll_position + self.visible_width, self.rendered_text_img.height)) - self.renderer.canvas.paste(img, (self.screen_x, self.screen_y - self.scroll_y)) - self.renderer.show_image() + # Render the latest scroll update + _render_text() if hold_started_at is not None: # If we're here, we've held long enough; reset the vars and resume From fec9fb4324234ab2e507cb0fb765706551a8ed15 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Mon, 15 Dec 2025 18:37:42 -0600 Subject: [PATCH 07/14] Relocate logic for min scroll threshold --- src/seedsigner/gui/components.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index dffbf828..05079691 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -440,7 +440,13 @@ class TextArea(BaseComponent): # Guaranteed to be a single line of text, possibly wider than self.width self.text_lines = [{"text": self.text, "text_width": full_text_width}] self.text_width = full_text_width - if self.text_width > self.visible_width: + + # Technically, the math says that we should enable scrolling for as little 1px + # beyond the available width, but it's not worth having the text twitch back + # and forth by such a small amount. + min_scrollable_diff = 2 + + if self.text_width > self.visible_width + min_scrollable_diff: # We'll have to left justify the text and scroll it (if scrolling is enabled, # otherwise it'll just run off the right edge). self.is_text_centered = False @@ -590,6 +596,7 @@ class TextArea(BaseComponent): self.horizontal_text_scroll_thread: TextArea.HorizontalTextScrollThread = None if self.is_horizontal_scrolling_enabled: + print(f"CREATING scrollable thread for \"{self.text}\"") self.horizontal_text_scroll_thread = TextArea.HorizontalTextScrollThread( rendered_text_img=self.rendered_text_img, screen_x=self.screen_x + self.min_text_x, @@ -644,23 +651,17 @@ class TextArea(BaseComponent): readability. 45px/sec is better but still perceptually a bit stuttery. """ def _render_text(): - img = self.rendered_text_img.crop((self.horizontal_scroll_position, 0, self.horizontal_scroll_position + self.visible_width, self.rendered_text_img.height)) + img = self.rendered_text_img.crop( + ( + self.horizontal_scroll_position, 0, # (x,y) top left + self.horizontal_scroll_position + self.visible_width, self.rendered_text_img.height # (x,y) bottom right + ) + ) self.renderer.canvas.paste(img, (self.screen_x, self.screen_y - self.scroll_y)) self.renderer.show_image() max_scroll = self.rendered_text_img.width - self.visible_width - # Technically, the math might say that we need to scroll for 1px, but it's - # not worth having the text twitch back and forth by such a small amount. - min_scrollable_diff = 2 - - if max_scroll < min_scrollable_diff: - # No scrolling needed. Render once and exit the thread. - with self.renderer.lock: - _render_text() - self.stop() - return - # The scrolling holds / pauses at the start and end of the text line. These # vars track when we started holding and how long we should hold. hold_started_at = None From b98faa0afe0108a4700592ffbf2fbc511a9c3245 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Mon, 15 Dec 2025 19:15:17 -0600 Subject: [PATCH 08/14] Cleanup --- src/seedsigner/gui/components.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index 05079691..d3012e54 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -596,7 +596,6 @@ class TextArea(BaseComponent): self.horizontal_text_scroll_thread: TextArea.HorizontalTextScrollThread = None if self.is_horizontal_scrolling_enabled: - print(f"CREATING scrollable thread for \"{self.text}\"") self.horizontal_text_scroll_thread = TextArea.HorizontalTextScrollThread( rendered_text_img=self.rendered_text_img, screen_x=self.screen_x + self.min_text_x, @@ -650,20 +649,10 @@ class TextArea(BaseComponent): 50px/sec creates a slight ghosting / doubling effect that impedes readability. 45px/sec is better but still perceptually a bit stuttery. """ - def _render_text(): - img = self.rendered_text_img.crop( - ( - self.horizontal_scroll_position, 0, # (x,y) top left - self.horizontal_scroll_position + self.visible_width, self.rendered_text_img.height # (x,y) bottom right - ) - ) - self.renderer.canvas.paste(img, (self.screen_x, self.screen_y - self.scroll_y)) - self.renderer.show_image() - max_scroll = self.rendered_text_img.width - self.visible_width # The scrolling holds / pauses at the start and end of the text line. These - # vars track when we started holding and how long we should hold. + # vars track when we started holding and how long we should hold for. hold_started_at = None cur_hold_duration = None @@ -672,7 +661,7 @@ class TextArea(BaseComponent): time.sleep(0.1) continue - if cur_hold_duration: + if cur_hold_duration is not None: hold_time_elapsed = time.time() - hold_started_at if hold_time_elapsed < cur_hold_duration: # Still holding; skip scrolling logic @@ -685,7 +674,9 @@ class TextArea(BaseComponent): continue # Render the latest scroll update - _render_text() + img = self.rendered_text_img.crop((self.horizontal_scroll_position, 0, self.horizontal_scroll_position + self.visible_width, self.rendered_text_img.height)) + self.renderer.canvas.paste(img, (self.screen_x, self.screen_y - self.scroll_y)) + self.renderer.show_image() if hold_started_at is not None: # If we're here, we've held long enough; reset the vars and resume From e88d0e45e51c58c158f00934334ea891ddf58118 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Tue, 16 Dec 2025 09:10:47 -0600 Subject: [PATCH 09/14] Refactor scrolling loop for better clarity; more comments --- src/seedsigner/gui/components.py | 111 +++++++++++++++++-------------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index d3012e54..622db599 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -441,10 +441,10 @@ class TextArea(BaseComponent): self.text_lines = [{"text": self.text, "text_width": full_text_width}] self.text_width = full_text_width - # Technically, the math says that we should enable scrolling for as little 1px - # beyond the available width, but it's not worth having the text twitch back - # and forth by such a small amount. - min_scrollable_diff = 2 + # Technically, the math says that we should enable scrolling for as little as + # 1px beyond the available width, but it's not worth having the text twitch + # back and forth by such a small amount. + min_scrollable_diff = 1 if self.text_width > self.visible_width + min_scrollable_diff: # We'll have to left justify the text and scroll it (if scrolling is enabled, @@ -650,9 +650,10 @@ class TextArea(BaseComponent): readability. 45px/sec is better but still perceptually a bit stuttery. """ max_scroll = self.rendered_text_img.width - self.visible_width + last_render_time = None - # The scrolling holds / pauses at the start and end of the text line. These - # vars track when we started holding and how long we should hold for. + # The scrolling pauses at the start and end of the text line. These vars track + # when we started holding and how long we should hold for. hold_started_at = None cur_hold_duration = None @@ -662,55 +663,43 @@ class TextArea(BaseComponent): continue if cur_hold_duration is not None: + # We're currently holding; see if we've held long enough hold_time_elapsed = time.time() - hold_started_at if hold_time_elapsed < cur_hold_duration: - # Still holding; skip scrolling logic + # Still have to hold longer; skip scrolling logic time.sleep(0.1) continue - - with self.renderer.lock: - if not self.scrolling_active: - # We were stopped while waiting for the lock - continue - - # Render the latest scroll update - img = self.rendered_text_img.crop((self.horizontal_scroll_position, 0, self.horizontal_scroll_position + self.visible_width, self.rendered_text_img.height)) - self.renderer.canvas.paste(img, (self.screen_x, self.screen_y - self.scroll_y)) - self.renderer.show_image() - - if hold_started_at is not None: - # If we're here, we've held long enough; reset the vars and resume - # scrolling. - hold_started_at = None - cur_hold_duration = None - - elif self.horizontal_scroll_position == 0: - # Pause on initial (left-justified) position... - hold_started_at = time.time() - cur_hold_duration = self.begin_hold_secs - - # Next scroll direction will be left - self.scroll_increment_sign = 1 - - # Don't count those pause seconds - last_render_time = None - continue - - elif self.horizontal_scroll_position == max_scroll: - # ...and slight pause at end of scroll - hold_started_at = time.time() - cur_hold_duration = self.end_hold_secs - - # Don't count those pause seconds - last_render_time = None - - # Scroll will be to the right - self.scroll_increment_sign = -1 - continue + else: + # We've held long enough; reset the vars and resume scrolling + hold_started_at = None + cur_hold_duration = None else: - # No need to CPU limit when running in its own thread? - time.sleep(0.02) + # We're not holding, but if we've reached either end, we need to start + # holding. + if self.horizontal_scroll_position == 0: + # Pause on initial (left-justified) position... + hold_started_at = time.time() + cur_hold_duration = self.begin_hold_secs + + # Next scroll direction will be left + self.scroll_increment_sign = 1 + + # Don't count those pause seconds + last_render_time = None + continue + + elif self.horizontal_scroll_position == max_scroll: + # ...and slight pause at end of scroll + hold_started_at = time.time() + cur_hold_duration = self.end_hold_secs + + # Don't count those pause seconds + last_render_time = None + + # Scroll will be to the right + self.scroll_increment_sign = -1 + continue next_render_time = time.time() @@ -719,13 +708,33 @@ class TextArea(BaseComponent): # "get off zero" for the real increment calc logic to kick in. scroll_position_increment = 1 * self.scroll_increment_sign else: + # Calculate how far to scroll based on time elapsed since last render scroll_position_increment = int(self.horizontal_scroll_speed * (next_render_time - last_render_time) * self.scroll_increment_sign) + # Only render an update if we're going to move at least 1px if abs(scroll_position_increment) > 0: - self.horizontal_scroll_position += scroll_position_increment - self.horizontal_scroll_position = max(0, min(self.horizontal_scroll_position, max_scroll)) + # max: Ensure we don't scroll past left edge (0) + # min: Ensure we don't scroll past right edge (max_scroll) + self.horizontal_scroll_position = max( + 0, + min(self.horizontal_scroll_position + scroll_position_increment, max_scroll) + ) + + # Render the scroll update + with self.renderer.lock: + if not self.scrolling_active: + # We were stopped while waiting for the lock + continue + + # The pre-rendered text img slides within a cropping window + img = self.rendered_text_img.crop((self.horizontal_scroll_position, 0, self.horizontal_scroll_position + self.visible_width, self.rendered_text_img.height)) + self.renderer.canvas.paste(img, (self.screen_x, self.screen_y - self.scroll_y)) + self.renderer.show_image() last_render_time = next_render_time + + # No need to CPU limit when running in its own thread? + time.sleep(0.02) else: # Wait to accumulate more time before scrolling pass From 9b855c0180796cab9e2d1a2454fd435a75550b09 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:01:59 -0600 Subject: [PATCH 10/14] Light loop refactor; improved comments --- src/seedsigner/gui/components.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index 622db599..9cc5cff6 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -713,8 +713,8 @@ class TextArea(BaseComponent): # Only render an update if we're going to move at least 1px if abs(scroll_position_increment) > 0: - # max: Ensure we don't scroll past left edge (0) - # min: Ensure we don't scroll past right edge (max_scroll) + # max: Don't over-scroll when returning to the left edge (0) + # min: Don't over-scroll when revealing the right edge (max_scroll) self.horizontal_scroll_position = max( 0, min(self.horizontal_scroll_position + scroll_position_increment, max_scroll) @@ -733,13 +733,14 @@ class TextArea(BaseComponent): last_render_time = next_render_time - # No need to CPU limit when running in its own thread? - time.sleep(0.02) else: - # Wait to accumulate more time before scrolling + # Wait to accumulate more time so we can scroll at least 1px pass + # Free up the processor for a bit each loop + time.sleep(0.02) + def render(self): """ Even if we need to animate for scrolling, all instances should explicitly render From b90e3d9ebd36761aceb8bc2e4ea24d4d15513bc7 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Sun, 14 Dec 2025 05:51:33 -0600 Subject: [PATCH 11/14] Replace the "coordinator" setting with "xpub_qr_type" --- l10n/messages.pot | 70 ++++++++++++++++---- src/seedsigner/models/encode_qr.py | 6 +- src/seedsigner/models/settings_definition.py | 49 +++++++------- src/seedsigner/views/seed_views.py | 48 +++++++------- tests/screenshot_generator/generator.py | 12 ++-- tests/test_controller.py | 36 ---------- tests/test_encodepsbtqr.py | 4 +- tests/test_flows_seed.py | 42 ++++++------ tests/test_flows_settings.py | 3 +- tests/test_settings.py | 26 ++++++-- 10 files changed, 159 insertions(+), 137 deletions(-) diff --git a/l10n/messages.pot b/l10n/messages.pot index 080ca8a1..b87cb99b 100644 --- a/l10n/messages.pot +++ b/l10n/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: seedsigner 0.8.6\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-11-21 14:39-0600\n" +"POT-Creation-Date: 2025-12-14 05:46-0600\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -597,6 +597,10 @@ msgstr "" msgid "Next {}" msgstr "" +#: src/seedsigner/hardware/camera.py +msgid "Camera error. Check camera connections." +msgstr "" + #: src/seedsigner/models/settings_definition.py msgid "Enabled" msgstr "" @@ -613,6 +617,21 @@ msgstr "" msgid "Required" msgstr "" +#. QR code format option; "default" = this is the format most wallets use +#: src/seedsigner/models/settings_definition.py +msgid "Animated (default)" +msgstr "" + +#. QR code format option (static = single frame, not animated) +#: src/seedsigner/models/settings_definition.py +msgid "Static" +msgstr "" + +#. QR code format option: old format that Specter Desktop used to use +#: src/seedsigner/models/settings_definition.py +msgid "Specter legacy" +msgstr "" + #: src/seedsigner/models/settings_definition.py msgid "BTC" msgstr "" @@ -718,10 +737,6 @@ msgstr "" msgid "Persistent settings" msgstr "" -#: src/seedsigner/models/settings_definition.py -msgid "Coordinator software" -msgstr "" - #: src/seedsigner/models/settings_definition.py msgid "Denomination display" msgstr "" @@ -746,6 +761,10 @@ msgstr "" msgid "Script types" msgstr "" +#: src/seedsigner/models/settings_definition.py +msgid "Xpub QR format" +msgstr "" + #: src/seedsigner/models/settings_definition.py msgid "Show xpub details" msgstr "" @@ -853,7 +872,7 @@ msgid "" "addresses." msgstr "" -#: src/seedsigner/views/psbt_views.py +#: src/seedsigner/views/psbt_views.py src/seedsigner/views/view.py msgid "Continue" msgstr "" @@ -1003,9 +1022,8 @@ msgstr "" msgid "QRCode is invalid or is a data format not yet supported." msgstr "" -#: src/seedsigner/views/scan_views.py src/seedsigner/views/seed_views.py -#: src/seedsigner/views/view.py -msgid "Done" +#: src/seedsigner/views/scan_views.py src/seedsigner/views/view.py +msgid "Back to Main Menu" msgstr "" #. This is on the opening splash screen, displayed above the HRF logo @@ -1074,6 +1092,10 @@ msgstr "" msgid "Checksum failure; not a valid seed phrase." msgstr "" +#: src/seedsigner/views/seed_views.py +msgid "Done" +msgstr "" + #: src/seedsigner/views/seed_views.py msgid "Edit passphrase" msgstr "" @@ -1159,6 +1181,10 @@ msgstr "" msgid "Export Xpub" msgstr "" +#: src/seedsigner/views/seed_views.py +msgid "Xpub QR Format" +msgstr "" + #: src/seedsigner/views/seed_views.py msgid "Xpub can be used to view all future transactions." msgstr "" @@ -1321,10 +1347,6 @@ msgstr "" msgid "Cancel" msgstr "" -#: src/seedsigner/views/seed_views.py -msgid "Can't validate a single sig addr without specifying a seed" -msgstr "" - #: src/seedsigner/views/seed_views.py msgid "Return to transaction" msgstr "" @@ -1518,6 +1540,18 @@ msgstr "" msgid "System Error" msgstr "" +#: src/seedsigner/views/view.py +msgid "Hardware Error" +msgstr "" + +#: src/seedsigner/views/view.py +msgid "Cannot access camera" +msgstr "" + +#: src/seedsigner/views/view.py +msgid "Disconnect power and check for a loose camera connection." +msgstr "" + #: src/seedsigner/views/view.py msgid "Update setting" msgstr "" @@ -1532,3 +1566,13 @@ msgstr "" msgid "Option Disabled" msgstr "" +#: src/seedsigner/views/view.py +msgid "Action Required" +msgstr "" + +#: src/seedsigner/views/view.py +msgid "" +"You must remove the\n" +"MicroSD card to continue." +msgstr "" + diff --git a/src/seedsigner/models/encode_qr.py b/src/seedsigner/models/encode_qr.py index 37aeb788..86be8c66 100644 --- a/src/seedsigner/models/encode_qr.py +++ b/src/seedsigner/models/encode_qr.py @@ -238,7 +238,11 @@ class BaseSimpleAnimatedQREncoder(BaseQrEncoder): @dataclass -class SpecterXPubQrEncoder(BaseSimpleAnimatedQREncoder, BaseXpubQrEncoder): +class SpecterLegacyXPubQrEncoder(BaseSimpleAnimatedQREncoder, BaseXpubQrEncoder): + """ + Legacy "pXofY" format. Included here for compatibility with much older versions of + Specter Desktop. Can probably eventually be removed. + """ @property def qr_max_fragment_size(self): density_mapping = { diff --git a/src/seedsigner/models/settings_definition.py b/src/seedsigner/models/settings_definition.py index 2a6a1d7a..42d68666 100644 --- a/src/seedsigner/models/settings_definition.py +++ b/src/seedsigner/models/settings_definition.py @@ -38,17 +38,18 @@ class SettingsConstants: ] # User-facing selection options - COORDINATOR__BLUE_WALLET = "bw" - COORDINATOR__NUNCHUK = "nun" - COORDINATOR__SPARROW = "spa" - COORDINATOR__SPECTER_DESKTOP = "spd" - COORDINATOR__KEEPER = "kpr" - ALL_COORDINATORS = [ - (COORDINATOR__BLUE_WALLET, "BlueWallet"), - (COORDINATOR__NUNCHUK, "Nunchuk"), - (COORDINATOR__SPARROW, "Sparrow"), - (COORDINATOR__SPECTER_DESKTOP, "Specter Desktop"), - (COORDINATOR__KEEPER, "Keeper"), + XPUB_QR_FORMAT__UR_CRYPTO_ACCOUNT = "urca" + XPUB_QR_FORMAT__STATIC = "sta" + XPUB_QR_FORMAT__SPECTER_LEGACY = "spl" + ALL_XPUB_QR_FORMATS = [ + # TRANSLATOR_NOTE: QR code format option; "default" = this is the format most wallets use + (XPUB_QR_FORMAT__UR_CRYPTO_ACCOUNT, _mft("Animated (default)")), + + # TRANSLATOR_NOTE: QR code format option (static = single frame, not animated) + (XPUB_QR_FORMAT__STATIC, _mft("Static")), + + # TRANSLATOR_NOTE: QR code format option: old format that Specter Desktop used to use + (XPUB_QR_FORMAT__SPECTER_LEGACY, _mft("Specter legacy")), ] # Over-specifying current and possible future locales to reduce/eliminate main repo @@ -326,7 +327,7 @@ class SettingsConstants: SETTING__LOCALE = "locale" SETTING__WORDLIST_LANGUAGE = "wordlist_language" SETTING__PERSISTENT_SETTINGS = "persistent_settings" - SETTING__COORDINATORS = "coordinators" + SETTING__XPUB_QR_FORMAT = "xpub_qr" SETTING__BTC_DENOMINATION = "denomination" SETTING__DISPLAY_CONFIGURATION = "display_config" @@ -569,19 +570,6 @@ class SettingsDefinition: help_text=SettingsConstants.PERSISTENT_SETTINGS__SD_INSERTED__HELP_TEXT, default_value=SettingsConstants.OPTION__DISABLED), - SettingsEntry(category=SettingsConstants.CATEGORY__WALLET, - attr_name=SettingsConstants.SETTING__COORDINATORS, - abbreviated_name="coords", - display_name=_mft("Coordinator software"), - type=SettingsConstants.TYPE__MULTISELECT, - selection_options=SettingsConstants.ALL_COORDINATORS, - default_value=[ - SettingsConstants.COORDINATOR__BLUE_WALLET, - SettingsConstants.COORDINATOR__NUNCHUK, - SettingsConstants.COORDINATOR__SPARROW, - SettingsConstants.COORDINATOR__SPECTER_DESKTOP, - ]), - SettingsEntry(category=SettingsConstants.CATEGORY__SYSTEM, attr_name=SettingsConstants.SETTING__BTC_DENOMINATION, abbreviated_name="denom", @@ -632,6 +620,17 @@ class SettingsDefinition: selection_options=SettingsConstants.ALL_SCRIPT_TYPES, default_value=[SettingsConstants.NATIVE_SEGWIT, SettingsConstants.NESTED_SEGWIT, SettingsConstants.TAPROOT]), + SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES, + attr_name=SettingsConstants.SETTING__XPUB_QR_FORMAT, + display_name=_mft("Xpub QR format"), + visibility=SettingsConstants.VISIBILITY__ADVANCED, + type=SettingsConstants.TYPE__MULTISELECT, + selection_options=SettingsConstants.ALL_XPUB_QR_FORMATS, + default_value=[ + SettingsConstants.XPUB_QR_FORMAT__UR_CRYPTO_ACCOUNT, + SettingsConstants.XPUB_QR_FORMAT__STATIC, + ]), + SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES, attr_name=SettingsConstants.SETTING__XPUB_DETAILS, display_name=_mft("Show xpub details"), diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 175bd34c..a977767c 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -11,7 +11,7 @@ from seedsigner.gui.components import FontAwesomeIconConstants, SeedSignerIconCo from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, WarningScreen, DireWarningScreen, seed_screens) from seedsigner.gui.screens.screen import ButtonOption, ButtonOptionWithoutTranslation -from seedsigner.models.encode_qr import CompactSeedQrEncoder, GenericStaticQrEncoder, SeedQrEncoder, SpecterXPubQrEncoder, StaticXpubQrEncoder, UrXpubQrEncoder +from seedsigner.models.encode_qr import CompactSeedQrEncoder, GenericStaticQrEncoder, SeedQrEncoder, SpecterLegacyXPubQrEncoder, StaticXpubQrEncoder, UrXpubQrEncoder from seedsigner.models.qr_type import QRType from seedsigner.models.seed import Seed from seedsigner.models.settings import Settings, SettingsConstants @@ -720,7 +720,7 @@ class SeedExportXpubScriptTypeView(View): del args["sig_type"] return Destination(ToolsAddressExplorerAddressTypeView, view_args=args, skip_current_view=True) else: - return Destination(SeedExportXpubCoordinatorView, view_args=args, skip_current_view=True) + return Destination(SeedExportXpubQRFormatView, view_args=args, skip_current_view=True) title = _("Export Xpub") if self.controller.resume_main_flow == Controller.FLOW__ADDRESS_EXPLORER: @@ -755,7 +755,7 @@ class SeedExportXpubScriptTypeView(View): del args["sig_type"] return Destination(ToolsAddressExplorerAddressTypeView, view_args=args) else: - return Destination(SeedExportXpubCoordinatorView, view_args=args) + return Destination(SeedExportXpubQRFormatView, view_args=args) @@ -786,7 +786,7 @@ class SeedExportXpubCustomDerivationView(View): return Destination(ToolsAddressExplorerAddressTypeView, view_args=dict(seed_num=self.seed_num, script_type=self.script_type, custom_derivation=custom_derivation)) return Destination( - SeedExportXpubCoordinatorView, + SeedExportXpubQRFormatView, view_args={ "seed_num": self.seed_num, "sig_type": self.sig_type, @@ -797,7 +797,7 @@ class SeedExportXpubCustomDerivationView(View): -class SeedExportXpubCoordinatorView(View): +class SeedExportXpubQRFormatView(View): def __init__(self, seed_num: int, sig_type: str, script_type: str, custom_derivation: str = None): super().__init__() self.seed_num = seed_num @@ -813,41 +813,39 @@ class SeedExportXpubCoordinatorView(View): "script_type": self.script_type, "custom_derivation": self.custom_derivation, } - if len(self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)) == 1: + if len(self.settings.get_value(SettingsConstants.SETTING__XPUB_QR_FORMAT)) == 1: # Nothing to select; skip this screen - args["coordinator"] = self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)[0] + args["xpub_qr_format"] = self.settings.get_value(SettingsConstants.SETTING__XPUB_QR_FORMAT)[0] return Destination(SeedExportXpubWarningView, view_args=args, skip_current_view=True) button_data = [] - for display_name, setting_option in zip(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS), self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)): + for display_name, setting_option in zip(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__XPUB_QR_FORMAT), self.settings.get_value(SettingsConstants.SETTING__XPUB_QR_FORMAT)): button_data.append(ButtonOption(display_name, return_data=setting_option)) selected_menu_num = self.run_screen( ButtonListScreen, - title=_("Export Xpub"), + title=_("Xpub QR Format"), is_button_text_centered=False, button_data=button_data, + is_bottom_list=True, ) if selected_menu_num == RET_CODE__BACK_BUTTON: return Destination(BackStackView) - # coordinators_settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__COORDINATORS) - # selected_display_name = button_data[selected_menu_num] - # args["coordinator"] = coordinators_settings_entry.get_selection_option_value_by_display_name(selected_display_name) - args["coordinator"] = button_data[selected_menu_num].return_data + args["xpub_qr_format"] = button_data[selected_menu_num].return_data return Destination(SeedExportXpubWarningView, view_args=args) class SeedExportXpubWarningView(View): - def __init__(self, seed_num: int, sig_type: str, script_type: str, coordinator: str, custom_derivation: str): + def __init__(self, seed_num: int, sig_type: str, script_type: str, xpub_qr_format: str, custom_derivation: str): super().__init__() self.seed_num = seed_num self.sig_type = sig_type self.script_type = script_type - self.coordinator = coordinator + self.xpub_qr_format = xpub_qr_format self.custom_derivation = custom_derivation @@ -858,7 +856,7 @@ class SeedExportXpubWarningView(View): "seed_num": self.seed_num, "sig_type": self.sig_type, "script_type": self.script_type, - "coordinator": self.coordinator, + "xpub_qr_format": self.xpub_qr_format, "custom_derivation": self.custom_derivation, }, skip_current_view=True, # Prevent going BACK to WarningViews @@ -888,11 +886,11 @@ class SeedExportXpubDetailsView(View): Collects the user input from all the previous screens leading up to this and finally calculates the xpub and displays the summary view to the user. """ - def __init__(self, seed_num: int, sig_type: str, script_type: str, coordinator: str, custom_derivation: str): + def __init__(self, seed_num: int, sig_type: str, script_type: str, xpub_qr_format: str, custom_derivation: str): super().__init__() self.sig_type = sig_type self.script_type = script_type - self.coordinator = coordinator + self.xpub_qr_format = xpub_qr_format self.custom_derivation = custom_derivation self.seed_num = seed_num @@ -955,7 +953,7 @@ class SeedExportXpubDetailsView(View): return Destination( SeedExportXpubQRDisplayView, dict(seed_num=self.seed_num, - coordinator=self.coordinator, + xpub_qr_format=self.xpub_qr_format, derivation_path=derivation_path, sig_type=self.sig_type ) @@ -967,7 +965,7 @@ class SeedExportXpubDetailsView(View): class SeedExportXpubQRDisplayView(View): - def __init__(self, seed_num: int, coordinator: str, derivation_path: str, sig_type: str = SettingsConstants.SINGLE_SIG): + def __init__(self, seed_num: int, xpub_qr_format: str, derivation_path: str, sig_type: str = SettingsConstants.SINGLE_SIG): super().__init__() self.seed = self.controller.get_seed(seed_num) @@ -979,14 +977,14 @@ class SeedExportXpubQRDisplayView(View): sig_type=sig_type ) - if coordinator == SettingsConstants.COORDINATOR__SPECTER_DESKTOP: - self.qr_encoder = SpecterXPubQrEncoder(**encoder_args) - - elif coordinator in [SettingsConstants.COORDINATOR__BLUE_WALLET, - SettingsConstants.COORDINATOR__KEEPER]: + if xpub_qr_format == SettingsConstants.XPUB_QR_FORMAT__STATIC: self.qr_encoder = StaticXpubQrEncoder(**encoder_args) + elif xpub_qr_format == SettingsConstants.XPUB_QR_FORMAT__SPECTER_LEGACY: + self.qr_encoder = SpecterLegacyXPubQrEncoder(**encoder_args) + else: + # Default: UR crypto-address self.qr_encoder = UrXpubQrEncoder(**encoder_args) diff --git a/tests/screenshot_generator/generator.py b/tests/screenshot_generator/generator.py index e45373fc..209fb321 100644 --- a/tests/screenshot_generator/generator.py +++ b/tests/screenshot_generator/generator.py @@ -237,8 +237,8 @@ def generate_screenshots(locale): ) add_settings_entries(SettingsConstants.VISIBILITY__HARDWARE) - settingsqr_data_persistent = f"settings::v1 name=English_noob_mode persistent=E coords=spa,spd denom=thr network=M qr_density=M xpub_export=E sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E locale={locale}" - settingsqr_data_not_persistent = f"settings::v1 name=Mode_Ephemeral persistent=D coords=spa,spd denom=thr network=M qr_density=M xpub_export=E sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E locale={locale}" + settingsqr_data_persistent = f"settings::v1 name=English_noob_mode persistent=E xpub_qr=urca,sta denom=thr network=M qr_density=M xpub_export=E sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E locale={locale}" + settingsqr_data_not_persistent = f"settings::v1 name=Mode_Ephemeral persistent=D xpub_qr=urca,sta denom=thr network=M qr_density=M xpub_export=E sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E locale={locale}" # Set up screenshot-specific callbacks to inject data before the View is run and # reset data after the View is run. @@ -334,10 +334,10 @@ def generate_screenshots(locale): ScreenshotConfig(seed_views.SeedExportXpubSigTypeView, dict(seed_num=0)), ScreenshotConfig(seed_views.SeedExportXpubScriptTypeView, dict(seed_num=0, sig_type="msig")), ScreenshotConfig(seed_views.SeedExportXpubCustomDerivationView, dict(seed_num=0, sig_type="ss", script_type="")), - ScreenshotConfig(seed_views.SeedExportXpubCoordinatorView, dict(seed_num=0, sig_type="ss", script_type="nat")), - ScreenshotConfig(seed_views.SeedExportXpubWarningView, dict(seed_num=0, sig_type="msig", script_type="nes", coordinator="spd", custom_derivation="")), - ScreenshotConfig(seed_views.SeedExportXpubDetailsView, dict(seed_num=0, sig_type="ss", script_type="nat", coordinator="bw", custom_derivation="")), - ScreenshotConfig(SeedExportXpubQR_ScreenBrightnessView, dict(seed_num=0, coordinator="bw", derivation_path="m/84'/0'/0'")), + ScreenshotConfig(seed_views.SeedExportXpubQRFormatView, dict(seed_num=0, sig_type="ss", script_type="nat")), + ScreenshotConfig(seed_views.SeedExportXpubWarningView, dict(seed_num=0, sig_type="msig", script_type="nes", xpub_qr_format="urca", custom_derivation="")), + ScreenshotConfig(seed_views.SeedExportXpubDetailsView, dict(seed_num=0, sig_type="ss", script_type="nat", xpub_qr_format="urca", custom_derivation="")), + ScreenshotConfig(SeedExportXpubQR_ScreenBrightnessView, dict(seed_num=0, xpub_qr_format="urca", derivation_path="m/84'/0'/0'")), ScreenshotConfig(seed_views.SeedWordsWarningView, dict(seed_num=0)), ScreenshotConfig(seed_views.SeedWordsView, dict(seed_num=0)), diff --git a/tests/test_controller.py b/tests/test_controller.py index 0be99d0d..f414dc89 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -4,7 +4,6 @@ import pytest from base import BaseTest from seedsigner.controller import Controller -from seedsigner.models.settings_definition import SettingsConstants class TestController(BaseTest): @@ -80,38 +79,3 @@ class TestController(BaseTest): # ...get a new copy of the instance and confirm change controller = Controller.get_instance() assert controller.unverified_address == "123abc" - - - def test_missing_settings_get_defaults(self): - """ Should gracefully handle all missing fields from `settings.json` """ - - controller = Controller.get_instance() - - # Settings defaults - assert controller.settings.get_value(SettingsConstants.SETTING__LOCALE) == SettingsConstants.LOCALE__ENGLISH - assert controller.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE) == SettingsConstants.WORDLIST_LANGUAGE__ENGLISH - assert controller.settings.get_value(SettingsConstants.SETTING__PERSISTENT_SETTINGS) == SettingsConstants.OPTION__DISABLED - assert controller.settings.get_value(SettingsConstants.SETTING__COORDINATORS) == [i for i,j in SettingsConstants.ALL_COORDINATORS if i!="kpr"] - assert controller.settings.get_value(SettingsConstants.SETTING__BTC_DENOMINATION) == SettingsConstants.BTC_DENOMINATION__THRESHOLD - - # Advanced Settings defaults - assert controller.settings.get_value(SettingsConstants.SETTING__NETWORK) == SettingsConstants.MAINNET - assert controller.settings.get_value(SettingsConstants.SETTING__QR_DENSITY) == SettingsConstants.DENSITY__MEDIUM - assert controller.settings.get_value(SettingsConstants.SETTING__XPUB_EXPORT) == SettingsConstants.OPTION__ENABLED - assert controller.settings.get_value(SettingsConstants.SETTING__SIG_TYPES) == [i for i,j in SettingsConstants.ALL_SIG_TYPES] - assert controller.settings.get_value(SettingsConstants.SETTING__SCRIPT_TYPES) == [SettingsConstants.NATIVE_SEGWIT, SettingsConstants.NESTED_SEGWIT, SettingsConstants.TAPROOT] - assert controller.settings.get_value(SettingsConstants.SETTING__XPUB_DETAILS) == SettingsConstants.OPTION__ENABLED - assert controller.settings.get_value(SettingsConstants.SETTING__PASSPHRASE) == SettingsConstants.OPTION__ENABLED - assert controller.settings.get_value(SettingsConstants.SETTING__CAMERA_ROTATION) == SettingsConstants.CAMERA_ROTATION__180 - assert controller.settings.get_value(SettingsConstants.SETTING__COMPACT_SEEDQR) == SettingsConstants.OPTION__ENABLED - assert controller.settings.get_value(SettingsConstants.SETTING__BIP85_CHILD_SEEDS) == SettingsConstants.OPTION__DISABLED - assert controller.settings.get_value(SettingsConstants.SETTING__MESSAGE_SIGNING) == SettingsConstants.OPTION__DISABLED - assert controller.settings.get_value(SettingsConstants.SETTING__PRIVACY_WARNINGS) == SettingsConstants.OPTION__ENABLED - assert controller.settings.get_value(SettingsConstants.SETTING__DIRE_WARNINGS) == SettingsConstants.OPTION__ENABLED - assert controller.settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS) == SettingsConstants.OPTION__ENABLED - assert controller.settings.get_value(SettingsConstants.SETTING__PARTNER_LOGOS) == SettingsConstants.OPTION__ENABLED - assert controller.settings.get_value(SettingsConstants.SETTING__MICROSD_TOAST_TIMER) == SettingsConstants.MICROSD_TOAST_TIMER_FIVE_SECONDS - - # Hidden Settings defaults - assert controller.settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS) == 62 - diff --git a/tests/test_encodepsbtqr.py b/tests/test_encodepsbtqr.py index 2d864c12..3b33c0c3 100644 --- a/tests/test_encodepsbtqr.py +++ b/tests/test_encodepsbtqr.py @@ -1,4 +1,4 @@ -from seedsigner.models.encode_qr import CompactSeedQrEncoder, SeedQrEncoder, SpecterXPubQrEncoder, StaticXpubQrEncoder, UrPsbtQrEncoder, UrXpubQrEncoder +from seedsigner.models.encode_qr import CompactSeedQrEncoder, SeedQrEncoder, SpecterLegacyXPubQrEncoder, StaticXpubQrEncoder, UrPsbtQrEncoder, UrXpubQrEncoder from embit import psbt from binascii import a2b_base64 @@ -54,7 +54,7 @@ def test_xpub_qr(): def test_specter_xpub_qr(): mnemonic = "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash" - e = SpecterXPubQrEncoder(seed=Seed(mnemonic.split(" "), passphrase="pass"), network=SettingsConstants.TESTNET, derivation="m/48h/1h/0h/2h", qr_density=SettingsConstants.DENSITY__LOW) + e = SpecterLegacyXPubQrEncoder(seed=Seed(mnemonic.split(" "), passphrase="pass"), network=SettingsConstants.TESTNET, derivation="m/48h/1h/0h/2h", qr_density=SettingsConstants.DENSITY__LOW) assert e.next_part() == "p1of4 [c49122a5/48h/1h/0h/2h]Vpub5mXgECaX5yYDN" assert e.next_part() == "p2of4 c5VnUG4jVNptyEg65qUjuofWchQeuMWWiq8rcPBo" diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index 2b2339f1..29fe379b 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -195,7 +195,7 @@ class TestSeedFlows(FlowTest): FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=sig_selection), FlowStep(seed_views.SeedExportXpubScriptTypeView, button_data_selection=ButtonOption(script_tuple[1], return_data=script_tuple[0])), - FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=ButtonOption(coord_tuple[1], return_data=coord_tuple[0])), + FlowStep(seed_views.SeedExportXpubQRFormatView, button_data_selection=ButtonOption(coord_tuple[1], return_data=coord_tuple[0])), FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0), FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0), FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0), @@ -211,17 +211,17 @@ class TestSeedFlows(FlowTest): # these are lists of (constant_value, display_name) tuples sig_types: list[tuple[str, str]] = SettingsConstants.ALL_SIG_TYPES script_types: list[tuple[str, str]] = SettingsConstants.ALL_SCRIPT_TYPES - coordinators: list[tuple[str, str]] = SettingsConstants.ALL_COORDINATORS + xpub_qr_formats: list[tuple[str, str]] = SettingsConstants.ALL_XPUB_QR_FORMATS # enable non-defaults so they're available in views self.settings.set_value(SettingsConstants.SETTING__SIG_TYPES, [x for x,y in sig_types]) self.settings.set_value(SettingsConstants.SETTING__SCRIPT_TYPES, [x for x,y in script_types]) - self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [x for x,y in coordinators]) + self.settings.set_value(SettingsConstants.SETTING__XPUB_QR_FORMAT, [x for x,y in xpub_qr_formats]) - # exhaustively test flows thru standard sig_types, script_types, and coordinators + # exhaustively test flows thru standard sig_types, script_types, and xpub_qr_formats for sig_tuple in sig_types: for script_tuple in script_types: - for coord_tuple in coordinators: + for coord_tuple in xpub_qr_formats: # skip custom derivation if script_tuple[0] == SettingsConstants.CUSTOM_DERIVATION: continue @@ -245,17 +245,17 @@ class TestSeedFlows(FlowTest): # these are lists of (constant_value, display_name) tuples sig_types: list[tuple[str, str]] = SettingsConstants.ALL_SIG_TYPES script_types: list[tuple[str, str]] = SettingsConstants.ALL_SCRIPT_TYPES - coordinators: list[tuple[str, str]] = SettingsConstants.ALL_COORDINATORS + xpub_qr_formats: list[tuple[str, str]] = SettingsConstants.ALL_XPUB_QR_FORMATS # these are the disabled types that we will be testing disabled_sig = SettingsConstants.MULTISIG disabled_script = SettingsConstants.TAPROOT - disabled_coord = SettingsConstants.COORDINATOR__NUNCHUK + disabled_xpub_qr_format = SettingsConstants.XPUB_QR_FORMAT__SPECTER_LEGACY # enable all but our target disabled type self.settings.set_value(SettingsConstants.SETTING__SIG_TYPES, [x for x,y in sig_types if x!=disabled_sig]) self.settings.set_value(SettingsConstants.SETTING__SCRIPT_TYPES, [x for x,y in script_types if x!=disabled_script]) - self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [x for x,y in coordinators if x!=disabled_coord]) + self.settings.set_value(SettingsConstants.SETTING__XPUB_QR_FORMAT, [x for x,y in xpub_qr_formats if x!=disabled_xpub_qr_format]) # If multisig isn't an option, then the sig type selection is skipped altogether self.run_sequence( @@ -286,7 +286,7 @@ class TestSeedFlows(FlowTest): FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), FlowStep(seed_views.SeedExportXpubSigTypeView, is_redirect=True), FlowStep(seed_views.SeedExportXpubScriptTypeView, screen_return_value=0), - FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=disabled_coord), + FlowStep(seed_views.SeedExportXpubQRFormatView, button_data_selection=disabled_xpub_qr_format), ] ) @@ -307,8 +307,8 @@ class TestSeedFlows(FlowTest): SettingsConstants.CUSTOM_DERIVATION ]) - # Ensure that all coordinators are enabled - self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [x for x, y in SettingsConstants.ALL_COORDINATORS]) + # Ensure that all xpub_qr_formats are enabled + self.settings.set_value(SettingsConstants.SETTING__XPUB_QR_FORMAT, [x for x, y in SettingsConstants.ALL_XPUB_QR_FORMATS]) # Set up button_data selections sig_type = seed_views.SeedExportXpubSigTypeView.SINGLE_SIG @@ -316,9 +316,9 @@ class TestSeedFlows(FlowTest): custom_derivation = SettingsConstants.CUSTOM_DERIVATION script_type = ButtonOption(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__SCRIPT_TYPES)[2], return_data=custom_derivation) - specter = SettingsConstants.COORDINATOR__SPECTER_DESKTOP - assert SettingsConstants.ALL_COORDINATORS[3][0] == specter - coordinator = ButtonOption(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS)[3], return_data=specter) + specter_legacy = SettingsConstants.XPUB_QR_FORMAT__SPECTER_LEGACY + assert SettingsConstants.ALL_XPUB_QR_FORMATS[2][0] == specter_legacy + xpub_qr_format = ButtonOption(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__XPUB_QR_FORMAT)[2], return_data=specter_legacy) self.run_sequence( initial_destination_view_args=dict(seed_num=0), @@ -327,7 +327,7 @@ class TestSeedFlows(FlowTest): FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=sig_type), FlowStep(seed_views.SeedExportXpubScriptTypeView, button_data_selection=script_type), FlowStep(seed_views.SeedExportXpubCustomDerivationView, screen_return_value="m/0'/0'"), - FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=coordinator), + FlowStep(seed_views.SeedExportXpubQRFormatView, button_data_selection=xpub_qr_format), FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0), FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0), FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0), @@ -338,18 +338,18 @@ class TestSeedFlows(FlowTest): def test_export_xpub_skip_non_option_flow(self): """ - Export XPUB flows w/o user choices when no other options for sig_types, script_types, and/or coordinators + Export XPUB flows w/o user choices when no other options for sig_types, script_types, and/or xpub_qr_formats """ # Load a finalized Seed into the Controller mnemonic = "blush twice taste dawn feed second opinion lazy thumb play neglect impact".split() self.controller.storage.set_pending_seed(Seed(mnemonic=mnemonic)) self.controller.storage.finalize_pending_seed() - # exclusively set only one choice for each of sig_types, script_types and coordinators + # exclusively set only one choice for each of sig_types, script_types and xpub_qr_formats self.settings.update({ SettingsConstants.SETTING__SIG_TYPES: SettingsConstants.MULTISIG, SettingsConstants.SETTING__SCRIPT_TYPES: SettingsConstants.NESTED_SEGWIT, - SettingsConstants.SETTING__COORDINATORS: SettingsConstants.COORDINATOR__SPECTER_DESKTOP, + SettingsConstants.SETTING__XPUB_QR_FORMAT: SettingsConstants.XPUB_QR_FORMAT__UR_CRYPTO_ACCOUNT, }) self.run_sequence( @@ -358,7 +358,7 @@ class TestSeedFlows(FlowTest): FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), FlowStep(seed_views.SeedExportXpubSigTypeView, is_redirect=True), FlowStep(seed_views.SeedExportXpubScriptTypeView, is_redirect=True), - FlowStep(seed_views.SeedExportXpubCoordinatorView, is_redirect=True), + FlowStep(seed_views.SeedExportXpubQRFormatView, is_redirect=True), FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0), FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0), FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0), @@ -379,7 +379,7 @@ class TestSeedFlows(FlowTest): # Make sure all options are enabled self.settings.set_value(SettingsConstants.SETTING__SIG_TYPES, [x for x,y in SettingsConstants.ALL_SIG_TYPES]) self.settings.set_value(SettingsConstants.SETTING__SCRIPT_TYPES, [x for x,y in SettingsConstants.ALL_SCRIPT_TYPES]) - self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [x for x,y in SettingsConstants.ALL_COORDINATORS]) + self.settings.set_value(SettingsConstants.SETTING__XPUB_QR_FORMAT, [x for x,y in SettingsConstants.ALL_XPUB_QR_FORMATS]) self.run_sequence( initial_destination_view_args=dict(seed_num=0), @@ -389,7 +389,7 @@ class TestSeedFlows(FlowTest): # Skips past the script type options via redirect FlowStep(seed_views.SeedExportXpubScriptTypeView, is_redirect=True), - FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=ButtonOption(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS)[0], return_data=SettingsConstants.ALL_COORDINATORS[0][0])), + FlowStep(seed_views.SeedExportXpubQRFormatView, button_data_selection=ButtonOption(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__XPUB_QR_FORMAT)[0], return_data=SettingsConstants.ALL_XPUB_QR_FORMATS[0][0])), FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0), FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0), FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0), diff --git a/tests/test_flows_settings.py b/tests/test_flows_settings.py index 37adf006..ac2381a2 100644 --- a/tests/test_flows_settings.py +++ b/tests/test_flows_settings.py @@ -39,10 +39,11 @@ class TestSettingsFlows(FlowTest): def test_multiselect(self): """ Multiselect Settings options should stay in-place; requires BACK to exit. """ # Which option are we testing? - settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__COORDINATORS) + settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__XPUB_QR_FORMAT) self.run_sequence([ FlowStep(MainMenuView, button_data_selection=MainMenuView.SETTINGS), + FlowStep(settings_views.SettingsMenuView, button_data_selection=settings_views.SettingsMenuView.ADVANCED), FlowStep(settings_views.SettingsMenuView, button_data_selection=ButtonOption(settings_entry.display_name)), FlowStep(settings_views.SettingsEntryUpdateSelectionView, screen_return_value=0), # select/deselect first option FlowStep(settings_views.SettingsEntryUpdateSelectionView, screen_return_value=1), # select/deselect second option diff --git a/tests/test_settings.py b/tests/test_settings.py index 94ac626a..7b4a3cfb 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -1,7 +1,7 @@ import pytest from base import BaseTest from seedsigner.models.settings import InvalidSettingsQRData, Settings -from seedsigner.models.settings_definition import SettingsConstants +from seedsigner.models.settings_definition import SettingsConstants, SettingsDefinition @@ -14,6 +14,10 @@ class TestSettings(BaseTest): def test_reset_settings(self): """ BaseTest.reset_settings() should wipe out any previous Settings changes """ + settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__PERSISTENT_SETTINGS) + assert settings_entry.default_value == SettingsConstants.OPTION__DISABLED + + # Change the setting from its default settings = Settings.get_instance() settings.set_value(SettingsConstants.SETTING__PERSISTENT_SETTINGS, SettingsConstants.OPTION__ENABLED) assert settings.get_value(SettingsConstants.SETTING__PERSISTENT_SETTINGS) == SettingsConstants.OPTION__ENABLED @@ -23,18 +27,26 @@ class TestSettings(BaseTest): assert settings.get_value(SettingsConstants.SETTING__PERSISTENT_SETTINGS) == SettingsConstants.OPTION__DISABLED + def test_settings_defaults(self): + """ Settings should initialize to their default values """ + BaseTest.reset_settings() + settings = Settings.get_instance() + for settings_entry in SettingsDefinition.get_settings_entries(): + assert settings.get_value(settings_entry.attr_name) == settings_entry.default_value + + def test_parse_settingsqr_data(self): """ SettingsQR parser should successfully parse a valid settingsqr input string and return the resulting config_name and formatted settings_update_dict. """ settings_name = "Test SettingsQR" - settingsqr_data = f"""settings::v1 name={ settings_name.replace(" ", "_") } persistent=D coords=spa,spd denom=thr network=M qr_density=M xpub_export=E sigs=ss,ms scripts=nat,nes,tr xpub_details=E passphrase=E camera=180 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E""" + settingsqr_data = f"""settings::v1 name={ settings_name.replace(" ", "_") } persistent=D coords=spa,spd denom=thr network=M qr_density=M xpub_export=E sigs=ss,ms scripts=nat,nes,tr xpub_qr=urca,sta xpub_details=E passphrase=E camera=180 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E""" # First explicitly set settings that differ from the settingsqr_data self.settings.set_value(SettingsConstants.SETTING__COMPACT_SEEDQR, SettingsConstants.OPTION__DISABLED) self.settings.set_value(SettingsConstants.SETTING__DIRE_WARNINGS, SettingsConstants.OPTION__DISABLED) - self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [SettingsConstants.COORDINATOR__BLUE_WALLET, SettingsConstants.COORDINATOR__SPARROW]) + self.settings.set_value(SettingsConstants.SETTING__XPUB_QR_FORMAT, [SettingsConstants.XPUB_QR_FORMAT__STATIC, SettingsConstants.XPUB_QR_FORMAT__SPECTER_LEGACY]) # Now parse the settingsqr_data config_name, settings_update_dict = Settings.parse_settingsqr(settingsqr_data) @@ -45,10 +57,10 @@ class TestSettings(BaseTest): assert self.settings.get_value(SettingsConstants.SETTING__COMPACT_SEEDQR) == SettingsConstants.OPTION__ENABLED assert self.settings.get_value(SettingsConstants.SETTING__DIRE_WARNINGS) == SettingsConstants.OPTION__ENABLED - coordinators = self.settings.get_value(SettingsConstants.SETTING__COORDINATORS) - assert SettingsConstants.COORDINATOR__BLUE_WALLET not in coordinators - assert SettingsConstants.COORDINATOR__SPARROW in coordinators - assert SettingsConstants.COORDINATOR__SPECTER_DESKTOP in coordinators + xpub_qr_formats = self.settings.get_value(SettingsConstants.SETTING__XPUB_QR_FORMAT) + assert SettingsConstants.XPUB_QR_FORMAT__UR_CRYPTO_ACCOUNT in xpub_qr_formats + assert SettingsConstants.XPUB_QR_FORMAT__STATIC in xpub_qr_formats + assert SettingsConstants.XPUB_QR_FORMAT__SPECTER_LEGACY not in xpub_qr_formats def test_settingsqr_version(self): From c49f644f4edc49f0fe689a509f17f7a0260606b7 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:07:31 -0600 Subject: [PATCH 12/14] Remove last "coord" Settings references --- tests/test_flows_seed.py | 12 ++++++------ tests/test_flows_settings.py | 4 ++-- tests/test_settings.py | 2 +- tests/test_settingsqr_decoder.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index 29fe379b..23b50671 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -184,7 +184,7 @@ class TestSeedFlows(FlowTest): """ Selecting "Export XPUB" from the SeedOptionsView should enter the Export XPUB flow and end at the MainMenuView """ - def flowtest_standard_xpub(sig_tuple, script_tuple, coord_tuple): + def flowtest_standard_xpub(sig_tuple, script_tuple, xpub_qr_tuple): if sig_tuple[0] == SettingsConstants.SINGLE_SIG: sig_selection = seed_views.SeedExportXpubSigTypeView.SINGLE_SIG else: @@ -195,7 +195,7 @@ class TestSeedFlows(FlowTest): FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=sig_selection), FlowStep(seed_views.SeedExportXpubScriptTypeView, button_data_selection=ButtonOption(script_tuple[1], return_data=script_tuple[0])), - FlowStep(seed_views.SeedExportXpubQRFormatView, button_data_selection=ButtonOption(coord_tuple[1], return_data=coord_tuple[0])), + FlowStep(seed_views.SeedExportXpubQRFormatView, button_data_selection=ButtonOption(xpub_qr_tuple[1], return_data=xpub_qr_tuple[0])), FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0), FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0), FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0), @@ -221,7 +221,7 @@ class TestSeedFlows(FlowTest): # exhaustively test flows thru standard sig_types, script_types, and xpub_qr_formats for sig_tuple in sig_types: for script_tuple in script_types: - for coord_tuple in xpub_qr_formats: + for xpub_qr_tuple in xpub_qr_formats: # skip custom derivation if script_tuple[0] == SettingsConstants.CUSTOM_DERIVATION: continue @@ -229,13 +229,13 @@ class TestSeedFlows(FlowTest): elif sig_tuple[0] == SettingsConstants.MULTISIG and script_tuple[0] == SettingsConstants.TAPROOT: continue else: - print('\n\ntest_standard_xpubs(%s, %s, %s)' % (sig_tuple, script_tuple, coord_tuple)) - flowtest_standard_xpub(sig_tuple, script_tuple, coord_tuple) + print('\n\ntest_standard_xpubs(%s, %s, %s)' % (sig_tuple, script_tuple, xpub_qr_tuple)) + flowtest_standard_xpub(sig_tuple, script_tuple, xpub_qr_tuple) def test_export_xpub_disabled_not_available_flow(self): """ - If sig_type/script_type/coordinator disabled, then these options are not available + If sig_type/script_type/xpub_qr_format disabled, then these options are not available """ # Load a finalized Seed into the Controller mnemonic = "blush twice taste dawn feed second opinion lazy thumb play neglect impact".split() diff --git a/tests/test_flows_settings.py b/tests/test_flows_settings.py index ac2381a2..2dcc4e6d 100644 --- a/tests/test_flows_settings.py +++ b/tests/test_flows_settings.py @@ -79,11 +79,11 @@ class TestSettingsFlows(FlowTest): MainMenuView. """ def load_persistent_settingsqr_into_decoder(view: scan_views.ScanView): - settingsqr_data_persistent: str = "settings::v1 name=Total_noob_mode persistent=E coords=spa,spd denom=thr network=M qr_density=M xpub_export=E sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E" + settingsqr_data_persistent: str = "settings::v1 name=Total_noob_mode persistent=E xpub_qr=urca,sta denom=thr network=M qr_density=M xpub_export=E sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E" view.decoder.add_data(settingsqr_data_persistent) def load_not_persistent_settingsqr_into_decoder(view: scan_views.ScanView): - settingsqr_data_not_persistent: str = "settings::v1 name=Ephemeral_noob_mode persistent=D coords=spa,spd denom=thr network=M qr_density=M xpub_export=E sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E" + settingsqr_data_not_persistent: str = "settings::v1 name=Ephemeral_noob_mode persistent=D xpub_qr=urca,sta denom=thr network=M qr_density=M xpub_export=E sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E" view.decoder.add_data(settingsqr_data_not_persistent) def _run_test(initial_setting_state: str, load_settingsqr_into_decoder: Callable, expected_setting_state: str): diff --git a/tests/test_settings.py b/tests/test_settings.py index 7b4a3cfb..ec589c8f 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -41,7 +41,7 @@ class TestSettings(BaseTest): return the resulting config_name and formatted settings_update_dict. """ settings_name = "Test SettingsQR" - settingsqr_data = f"""settings::v1 name={ settings_name.replace(" ", "_") } persistent=D coords=spa,spd denom=thr network=M qr_density=M xpub_export=E sigs=ss,ms scripts=nat,nes,tr xpub_qr=urca,sta xpub_details=E passphrase=E camera=180 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E""" + settingsqr_data = f"""settings::v1 name={ settings_name.replace(" ", "_") } persistent=D xpub_qr=urca,sta denom=thr network=M qr_density=M xpub_export=E sigs=ss,ms scripts=nat,nes,tr xpub_qr=urca,sta xpub_details=E passphrase=E camera=180 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E""" # First explicitly set settings that differ from the settingsqr_data self.settings.set_value(SettingsConstants.SETTING__COMPACT_SEEDQR, SettingsConstants.OPTION__DISABLED) diff --git a/tests/test_settingsqr_decoder.py b/tests/test_settingsqr_decoder.py index bb124c4f..4f4136d4 100644 --- a/tests/test_settingsqr_decoder.py +++ b/tests/test_settingsqr_decoder.py @@ -9,7 +9,7 @@ class TestSettingsQRDecoder: with parsing the result. """ settings_name = "Test SettingsQR" - settings_qr_str = f"""settings::v1 name={ settings_name.replace(" ", "_") } persistent=D coords=spa,spd denom=thr network=M qr_density=M xpub_export=E sigs=ss,ms scripts=nat,nes,tr xpub_details=E passphrase=E camera=180 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E""" + settings_qr_str = f"""settings::v1 name={ settings_name.replace(" ", "_") } persistent=D xpub_qr=urca,sta denom=thr network=M qr_density=M xpub_export=E sigs=ss,ms scripts=nat,nes,tr xpub_details=E passphrase=E camera=180 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E""" # Now parse the settings_qr_str decoder = DecodeQR() From 9d6a93b9fbc4a572714e0a9260e38b0b175dd031 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Fri, 26 Dec 2025 07:21:13 -0600 Subject: [PATCH 13/14] Remove unnecessary error message --- src/seedsigner/hardware/camera.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/seedsigner/hardware/camera.py b/src/seedsigner/hardware/camera.py index 865e1cd1..44919fa4 100644 --- a/src/seedsigner/hardware/camera.py +++ b/src/seedsigner/hardware/camera.py @@ -9,9 +9,7 @@ from seedsigner.models.singleton import Singleton class CameraConnectionError(Exception): - def __init__(self, *args, **kwargs): - message = _("Camera error. Check camera connections.") - super().__init__(message) + pass From 3f9ec5c3c49898dcb5d613c46d10e3cfb8b201c3 Mon Sep 17 00:00:00 2001 From: kdmukai <934746+kdmukai@users.noreply.github.com> Date: Sun, 28 Dec 2025 08:43:53 -0600 Subject: [PATCH 14/14] Remove duplicated attr entry in test --- tests/test_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_settings.py b/tests/test_settings.py index ec589c8f..58e4951d 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -41,7 +41,7 @@ class TestSettings(BaseTest): return the resulting config_name and formatted settings_update_dict. """ settings_name = "Test SettingsQR" - settingsqr_data = f"""settings::v1 name={ settings_name.replace(" ", "_") } persistent=D xpub_qr=urca,sta denom=thr network=M qr_density=M xpub_export=E sigs=ss,ms scripts=nat,nes,tr xpub_qr=urca,sta xpub_details=E passphrase=E camera=180 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E""" + settingsqr_data = f"""settings::v1 name={ settings_name.replace(" ", "_") } persistent=D denom=thr network=M qr_density=M xpub_export=E sigs=ss,ms scripts=nat,nes,tr xpub_qr=urca,sta xpub_details=E passphrase=E camera=180 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E""" # First explicitly set settings that differ from the settingsqr_data self.settings.set_value(SettingsConstants.SETTING__COMPACT_SEEDQR, SettingsConstants.OPTION__DISABLED)