From 14ca98a6b32b69768efadb8ecb5ebc35a1970dd1 Mon Sep 17 00:00:00 2001 From: nosedam Date: Wed, 28 Aug 2024 22:29:07 -0300 Subject: [PATCH 1/9] feat: add export functionality for binary and decimal --- src/seedsigner/helpers/seed_transformers.py | 15 +++++++ src/seedsigner/views/seed_views.py | 50 ++++++++++++++++----- 2 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 src/seedsigner/helpers/seed_transformers.py diff --git a/src/seedsigner/helpers/seed_transformers.py b/src/seedsigner/helpers/seed_transformers.py new file mode 100644 index 00000000..4c7e4fad --- /dev/null +++ b/src/seedsigner/helpers/seed_transformers.py @@ -0,0 +1,15 @@ +from embit import bip39 + +def convert_word_to_11_bits(word: str) -> str: + wordlist = bip39.WORDLIST + if word not in wordlist: + raise ValueError(f"Word '{word}' is not in the dictionary") + + return f'{bip39.WORDLIST.index(word):011b}' + +def convert_word_to_decimal(word: str) -> str: + wordlist = bip39.WORDLIST + if word not in wordlist: + raise ValueError(f"Word '{word}' is not in the dictionary") + + return str(bip39.WORDLIST.index(word)) \ No newline at end of file diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 9483c111..5bbdb805 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -3,6 +3,8 @@ import random import time +from collections.abc import Callable + from binascii import hexlify from embit import bip39 from embit.descriptor import Descriptor @@ -12,6 +14,7 @@ from seedsigner.controller import Controller from seedsigner.gui.components import FontAwesomeIconConstants, SeedSignerIconConstants from seedsigner.helpers import embit_utils +from seedsigner.helpers import seed_transformers from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, WarningScreen, DireWarningScreen, seed_screens) from seedsigner.gui.screens.screen import LargeIconStatusScreen, QRDisplayScreen @@ -611,6 +614,8 @@ def run(self): class SeedBackupView(View): VIEW_WORDS = "View Seed Words" + VIEW_BINARY = "View Seed Binary" + VIEW_DECIMAL = "View Seed Decimal" EXPORT_SEEDQR = "Export as SeedQR" def __init__(self, seed_num): @@ -620,7 +625,7 @@ def __init__(self, seed_num): def run(self): - button_data = [self.VIEW_WORDS] + button_data = [self.VIEW_WORDS, self.VIEW_BINARY, self.VIEW_DECIMAL] if self.seed.seedqr_supported: button_data.append(self.EXPORT_SEEDQR) @@ -641,6 +646,12 @@ def run(self): elif button_data[selected_menu_num] == self.EXPORT_SEEDQR: return Destination(SeedTranscribeSeedQRFormatView, view_args={"seed_num": self.seed_num}) + elif button_data[selected_menu_num] == self.VIEW_BINARY: + return Destination(SeedWordsView, view_args={"seed_num": self.seed_num, "transformer": seed_transformers.convert_word_to_11_bits}) + + elif button_data[selected_menu_num] == self.VIEW_DECIMAL: + return Destination(SeedWordsView, view_args={"seed_num": self.seed_num, "transformer": seed_transformers.convert_word_to_decimal}) + """**************************************************************************** @@ -1023,7 +1034,7 @@ def run(self): class SeedWordsView(View): - def __init__(self, seed_num: int, bip85_data: dict = None, page_index: int = 0): + def __init__(self, seed_num: int, bip85_data: dict = None, page_index: int = 0, transformer: Callable[[str], str] = None): super().__init__() self.seed_num = seed_num if self.seed_num is None: @@ -1033,6 +1044,8 @@ def __init__(self, seed_num: int, bip85_data: dict = None, page_index: int = 0): self.bip85_data = bip85_data self.page_index = page_index + self.transformer = transformer + def run(self): NEXT = "Next" @@ -1049,6 +1062,9 @@ def run(self): title = "Seed Words" words = mnemonic[self.page_index*words_per_page:(self.page_index + 1)*words_per_page] + if self.transformer: + words = [self.transformer(word) for word in words] + button_data = [] num_pages = int(len(mnemonic)/words_per_page) if self.page_index < num_pages - 1 or self.seed_num is None: @@ -1071,19 +1087,19 @@ def run(self): if self.seed_num is None and self.page_index == num_pages - 1: return Destination( SeedWordsBackupTestPromptView, - view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data), + view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data, transformer=self.transformer), ) else: return Destination( SeedWordsView, - view_args=dict(seed_num=self.seed_num, page_index=self.page_index + 1, bip85_data=self.bip85_data) + view_args=dict(seed_num=self.seed_num, page_index=self.page_index + 1, bip85_data=self.bip85_data, transformer=self.transformer) ) elif button_data[selected_menu_num] == DONE: # Must clear history to avoid BACK button returning to private info return Destination( SeedWordsBackupTestPromptView, - view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data), + view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data, transformer=self.transformer), ) @@ -1198,10 +1214,11 @@ def run(self): Seed Words Backup Test ****************************************************************************""" class SeedWordsBackupTestPromptView(View): - def __init__(self, seed_num: int, bip85_data: dict = None): + def __init__(self, seed_num: int, bip85_data: dict = None, transformer: Callable[[str], str] = None): super().__init__() self.seed_num = seed_num self.bip85_data = bip85_data + self.transformer = transformer def run(self): @@ -1215,7 +1232,7 @@ def run(self): if button_data[selected_menu_num] == VERIFY: return Destination( SeedWordsBackupTestView, - view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data), + view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data, transformer=self.transformer), ) elif button_data[selected_menu_num] == SKIP: @@ -1227,7 +1244,7 @@ def run(self): class SeedWordsBackupTestView(View): - def __init__(self, seed_num: int, bip85_data: dict = None, confirmed_list: List[bool] = None, cur_index: int = None): + def __init__(self, seed_num: int, bip85_data: dict = None, confirmed_list: List[bool] = None, cur_index: int = None, transformer: Callable[[str], str] = None): super().__init__() self.seed_num = seed_num if self.seed_num is None: @@ -1247,6 +1264,8 @@ def __init__(self, seed_num: int, bip85_data: dict = None, confirmed_list: List[ self.cur_index = cur_index + self.transformer = transformer + def run(self): if self.cur_index is None: @@ -1259,6 +1278,12 @@ def run(self): fake_word2 = bip39.WORDLIST[int(random.random() * 2047)] fake_word3 = bip39.WORDLIST[int(random.random() * 2047)] + if self.transformer: + real_word = self.transformer(real_word) + fake_word1 = self.transformer(fake_word1) + fake_word2 = self.transformer(fake_word2) + fake_word3 = self.transformer(fake_word3) + button_data = [real_word, fake_word1, fake_word2, fake_word3] random.shuffle(button_data) @@ -1282,7 +1307,7 @@ def run(self): # Continue testing the remaining words return Destination( SeedWordsBackupTestView, - view_args=dict(seed_num=self.seed_num, confirmed_list=self.confirmed_list, bip85_data=self.bip85_data), + view_args=dict(seed_num=self.seed_num, confirmed_list=self.confirmed_list, bip85_data=self.bip85_data, transformer=self.transformer), ) else: @@ -1295,19 +1320,21 @@ def run(self): cur_index=self.cur_index, wrong_word=button_data[selected_menu_num], confirmed_list=self.confirmed_list, + transformer=self.transformer ) ) class SeedWordsBackupTestMistakeView(View): - def __init__(self, seed_num: int, bip85_data: dict = None, cur_index: int = None, wrong_word: str = None, confirmed_list: List[bool] = None): + def __init__(self, seed_num: int, bip85_data: dict = None, cur_index: int = None, wrong_word: str = None, confirmed_list: List[bool] = None, transformer: Callable[[str], str] = None): super().__init__() self.seed_num = seed_num self.bip85_data = bip85_data self.cur_index = cur_index self.wrong_word = wrong_word self.confirmed_list = confirmed_list + self.transformer = transformer def run(self): @@ -1326,7 +1353,7 @@ def run(self): if button_data[selected_menu_num] == REVIEW: return Destination( SeedWordsView, - view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data), + view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data, transformer=self.transformer), ) elif button_data[selected_menu_num] == RETRY: @@ -1337,6 +1364,7 @@ def run(self): confirmed_list=self.confirmed_list, cur_index=self.cur_index, bip85_data=self.bip85_data, + transformer=self.transformer, ) ) From 46cc9c7de30b3fe552619c579897e82cdd1ec858 Mon Sep 17 00:00:00 2001 From: nosedam Date: Wed, 28 Aug 2024 22:51:13 -0300 Subject: [PATCH 2/9] refactor: rename seed format transformers --- ...formers.py => seed_format_transformers.py} | 13 ++++- src/seedsigner/views/seed_views.py | 52 +++++++++---------- 2 files changed, 38 insertions(+), 27 deletions(-) rename src/seedsigner/helpers/{seed_transformers.py => seed_format_transformers.py} (55%) diff --git a/src/seedsigner/helpers/seed_transformers.py b/src/seedsigner/helpers/seed_format_transformers.py similarity index 55% rename from src/seedsigner/helpers/seed_transformers.py rename to src/seedsigner/helpers/seed_format_transformers.py index 4c7e4fad..3646fbef 100644 --- a/src/seedsigner/helpers/seed_transformers.py +++ b/src/seedsigner/helpers/seed_format_transformers.py @@ -12,4 +12,15 @@ def convert_word_to_decimal(word: str) -> str: if word not in wordlist: raise ValueError(f"Word '{word}' is not in the dictionary") - return str(bip39.WORDLIST.index(word)) \ No newline at end of file + return str(bip39.WORDLIST.index(word)) + +def convert_11_bits_to_word(bits: str) -> str: + wordlist = bip39.WORDLIST + if len(bits) != 11: + raise ValueError("Input must be 11 bits") + + return wordlist[int(bits, 2)] + +def convert_decimal_to_word(decimal: str) -> str: + wordlist = bip39.WORDLIST + return wordlist[int(decimal)] \ No newline at end of file diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 5bbdb805..679f99dc 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -14,7 +14,7 @@ from seedsigner.controller import Controller from seedsigner.gui.components import FontAwesomeIconConstants, SeedSignerIconConstants from seedsigner.helpers import embit_utils -from seedsigner.helpers import seed_transformers +from seedsigner.helpers import seed_format_transformers from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, WarningScreen, DireWarningScreen, seed_screens) from seedsigner.gui.screens.screen import LargeIconStatusScreen, QRDisplayScreen @@ -647,10 +647,10 @@ def run(self): return Destination(SeedTranscribeSeedQRFormatView, view_args={"seed_num": self.seed_num}) elif button_data[selected_menu_num] == self.VIEW_BINARY: - return Destination(SeedWordsView, view_args={"seed_num": self.seed_num, "transformer": seed_transformers.convert_word_to_11_bits}) + return Destination(SeedWordsView, view_args={"seed_num": self.seed_num, "transformer": seed_format_transformers.convert_word_to_11_bits}) elif button_data[selected_menu_num] == self.VIEW_DECIMAL: - return Destination(SeedWordsView, view_args={"seed_num": self.seed_num, "transformer": seed_transformers.convert_word_to_decimal}) + return Destination(SeedWordsView, view_args={"seed_num": self.seed_num, "transformer": seed_format_transformers.convert_word_to_decimal}) @@ -1034,7 +1034,7 @@ def run(self): class SeedWordsView(View): - def __init__(self, seed_num: int, bip85_data: dict = None, page_index: int = 0, transformer: Callable[[str], str] = None): + def __init__(self, seed_num: int, bip85_data: dict = None, page_index: int = 0, seed_format_transformer: Callable[[str], str] = None): super().__init__() self.seed_num = seed_num if self.seed_num is None: @@ -1044,7 +1044,7 @@ def __init__(self, seed_num: int, bip85_data: dict = None, page_index: int = 0, self.bip85_data = bip85_data self.page_index = page_index - self.transformer = transformer + self.seed_format_transformer = seed_format_transformer def run(self): @@ -1062,8 +1062,8 @@ def run(self): title = "Seed Words" words = mnemonic[self.page_index*words_per_page:(self.page_index + 1)*words_per_page] - if self.transformer: - words = [self.transformer(word) for word in words] + if self.seed_format_transformer: + words = [self.seed_format_transformer(word) for word in words] button_data = [] num_pages = int(len(mnemonic)/words_per_page) @@ -1087,19 +1087,19 @@ def run(self): if self.seed_num is None and self.page_index == num_pages - 1: return Destination( SeedWordsBackupTestPromptView, - view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data, transformer=self.transformer), + view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data, seed_format_transformer=self.seed_format_transformer), ) else: return Destination( SeedWordsView, - view_args=dict(seed_num=self.seed_num, page_index=self.page_index + 1, bip85_data=self.bip85_data, transformer=self.transformer) + view_args=dict(seed_num=self.seed_num, page_index=self.page_index + 1, bip85_data=self.bip85_data, seed_format_transformer=self.seed_format_transformer) ) elif button_data[selected_menu_num] == DONE: # Must clear history to avoid BACK button returning to private info return Destination( SeedWordsBackupTestPromptView, - view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data, transformer=self.transformer), + view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data, seed_format_transformer=self.seed_format_transformer), ) @@ -1214,11 +1214,11 @@ def run(self): Seed Words Backup Test ****************************************************************************""" class SeedWordsBackupTestPromptView(View): - def __init__(self, seed_num: int, bip85_data: dict = None, transformer: Callable[[str], str] = None): + def __init__(self, seed_num: int, bip85_data: dict = None, seed_format_transformer: Callable[[str], str] = None): super().__init__() self.seed_num = seed_num self.bip85_data = bip85_data - self.transformer = transformer + self.seed_format_transformer = seed_format_transformer def run(self): @@ -1232,7 +1232,7 @@ def run(self): if button_data[selected_menu_num] == VERIFY: return Destination( SeedWordsBackupTestView, - view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data, transformer=self.transformer), + view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data, seed_format_transformer=self.seed_format_transformer), ) elif button_data[selected_menu_num] == SKIP: @@ -1244,7 +1244,7 @@ def run(self): class SeedWordsBackupTestView(View): - def __init__(self, seed_num: int, bip85_data: dict = None, confirmed_list: List[bool] = None, cur_index: int = None, transformer: Callable[[str], str] = None): + def __init__(self, seed_num: int, bip85_data: dict = None, confirmed_list: List[bool] = None, cur_index: int = None, seed_format_transformer: Callable[[str], str] = None): super().__init__() self.seed_num = seed_num if self.seed_num is None: @@ -1264,7 +1264,7 @@ def __init__(self, seed_num: int, bip85_data: dict = None, confirmed_list: List[ self.cur_index = cur_index - self.transformer = transformer + self.seed_format_transformer = seed_format_transformer def run(self): @@ -1278,11 +1278,11 @@ def run(self): fake_word2 = bip39.WORDLIST[int(random.random() * 2047)] fake_word3 = bip39.WORDLIST[int(random.random() * 2047)] - if self.transformer: - real_word = self.transformer(real_word) - fake_word1 = self.transformer(fake_word1) - fake_word2 = self.transformer(fake_word2) - fake_word3 = self.transformer(fake_word3) + if self.seed_format_transformer: + real_word = self.seed_format_transformer(real_word) + fake_word1 = self.seed_format_transformer(fake_word1) + fake_word2 = self.seed_format_transformer(fake_word2) + fake_word3 = self.seed_format_transformer(fake_word3) button_data = [real_word, fake_word1, fake_word2, fake_word3] random.shuffle(button_data) @@ -1307,7 +1307,7 @@ def run(self): # Continue testing the remaining words return Destination( SeedWordsBackupTestView, - view_args=dict(seed_num=self.seed_num, confirmed_list=self.confirmed_list, bip85_data=self.bip85_data, transformer=self.transformer), + view_args=dict(seed_num=self.seed_num, confirmed_list=self.confirmed_list, bip85_data=self.bip85_data, seed_format_transformer=self.seed_format_transformer), ) else: @@ -1320,21 +1320,21 @@ def run(self): cur_index=self.cur_index, wrong_word=button_data[selected_menu_num], confirmed_list=self.confirmed_list, - transformer=self.transformer + seed_format_transformer=self.seed_format_transformer ) ) class SeedWordsBackupTestMistakeView(View): - def __init__(self, seed_num: int, bip85_data: dict = None, cur_index: int = None, wrong_word: str = None, confirmed_list: List[bool] = None, transformer: Callable[[str], str] = None): + def __init__(self, seed_num: int, bip85_data: dict = None, cur_index: int = None, wrong_word: str = None, confirmed_list: List[bool] = None, seed_format_transformer: Callable[[str], str] = None): super().__init__() self.seed_num = seed_num self.bip85_data = bip85_data self.cur_index = cur_index self.wrong_word = wrong_word self.confirmed_list = confirmed_list - self.transformer = transformer + self.seed_format_transformer = seed_format_transformer def run(self): @@ -1353,7 +1353,7 @@ def run(self): if button_data[selected_menu_num] == REVIEW: return Destination( SeedWordsView, - view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data, transformer=self.transformer), + view_args=dict(seed_num=self.seed_num, bip85_data=self.bip85_data, seed_format_transformer=self.seed_format_transformer), ) elif button_data[selected_menu_num] == RETRY: @@ -1364,7 +1364,7 @@ def run(self): confirmed_list=self.confirmed_list, cur_index=self.cur_index, bip85_data=self.bip85_data, - transformer=self.transformer, + seed_format_transformer=self.seed_format_transformer, ) ) From 8fd377cb916670363c45e0495e19e82fa5df644d Mon Sep 17 00:00:00 2001 From: nosedam Date: Thu, 5 Sep 2024 22:52:22 -0300 Subject: [PATCH 3/9] feat: add decimal seed import functionality --- src/seedsigner/gui/screens/seed_screens.py | 200 ++++++++++++++++++++- src/seedsigner/views/seed_views.py | 60 ++++++- 2 files changed, 244 insertions(+), 16 deletions(-) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index 64fb7ea9..85490427 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -17,18 +17,24 @@ from seedsigner.gui.keyboard import Keyboard, TextEntryDisplay from seedsigner.hardware.buttons import HardwareButtons, HardwareButtonsConstants +import seedsigner.helpers.seed_format_transformers as seed_format_transformers + logger = logging.getLogger(__name__) @dataclass class SeedMnemonicEntryScreen(BaseTopNavScreen): - initial_letters: list = None + current_word: str = None wordlist: list = None + possible_alphabet: str = "abcdefghijklmnopqrstuvwxyz" def __post_init__(self): super().__post_init__() - self.possible_alphabet = "abcdefghijklmnopqrstuvwxyz" + if not self.current_word: + self.initial_letters = [self.possible_alphabet[0]] + else: + self.initial_letters = list(self.current_word) # Set up the keyboard params self.keyboard_width = 128 @@ -119,7 +125,6 @@ def __post_init__(self): self.word_font_height = -1 * top self.matches_list_row_height = self.word_font_height + GUIConstants.COMPONENT_PADDING - def calc_possible_alphabet(self, new_letter = False): if (self.letters and len(self.letters) > 1 and new_letter == False) or (len(self.letters) > 0 and new_letter == True): search_letters = self.letters[:] @@ -137,12 +142,10 @@ def calc_possible_alphabet(self, new_letter = False): self.possible_alphabet = "abcdefghijklmnopqrstuvwxyz" self.possible_words = [] - def calc_possible_words(self): self.possible_words = [i for i in self.wordlist if i.startswith("".join(self.letters).strip())] self.selected_possible_words_index = 0 - def render_possible_matches(self, highlight_word=None): """ Internal helper method to render the KEY 1, 2, 3 word candidates. (has access to all vars in the parent's context) @@ -229,7 +232,6 @@ def render_possible_matches(self, highlight_word=None): self.matches_list_up_button.render() self.matches_list_down_button.render() - def _render(self): super()._render() self.keyboard.render_keys() @@ -238,7 +240,6 @@ def _render(self): self.renderer.show_image() - def _run(self): while True: input = self.hw_inputs.wait_for( @@ -403,7 +404,192 @@ def _run(self): # Now issue one call to send the pixels to the screen self.renderer.show_image() +@dataclass +class SeedMnemonicDecimalEntryScreen(BaseTopNavScreen): + current_word: str = None + wordlist: list = None + possible_alphabet: str = "1234567890" + + def __post_init__(self): + super().__post_init__() + + if not self.current_word: + self.initial_letters = [self.possible_alphabet[0]] + else: + self.initial_letters = list(seed_format_transformers.convert_word_to_decimal("".join(self.current_word))) + + self.keyboard_width = 200 + text_entry_display_y = self.top_nav.height + text_entry_display_height = 30 + + self.arrow_up_is_active = False + self.arrow_down_is_active = False + + self.keyboard = Keyboard( + draw=self.image_draw, + charset=self.possible_alphabet, + rows=3, + cols=4, + rect=( + GUIConstants.EDGE_PADDING, + text_entry_display_y + text_entry_display_height + 6, + GUIConstants.EDGE_PADDING + self.keyboard_width, + self.canvas_height + ), + auto_wrap=[Keyboard.WRAP_LEFT, Keyboard.WRAP_RIGHT] + ) + + self.decimal_seed_word_length = 4 + + self.text_entry_display = TextEntryDisplay( + canvas=self.canvas, + rect=( + GUIConstants.EDGE_PADDING, + text_entry_display_y, + GUIConstants.EDGE_PADDING + (GUIConstants.BUTTON_FONT_SIZE * self.decimal_seed_word_length), + text_entry_display_y + text_entry_display_height + ), + is_centered=False, + cur_text="".join(self.initial_letters) + ) + + self.letters = self.initial_letters + + if len(self.letters) > 1: + self.letters.append(" ") # "Lock in" the last letter as if KEY_PRESS + self.calc_possible_alphabet() + self.keyboard.update_active_keys(active_keys=self.possible_alphabet) + self.keyboard.set_selected_key(selected_letter=self.letters[-2]) + else: + self.keyboard.set_selected_key(selected_letter=self.letters[-1]) + + def calc_possible_alphabet(self): + valid_characters = [] + for character in "1234567890": + prediction_number = int("".join(self.letters).strip() + character) + if prediction_number <= 2047: + valid_characters.append(character) + self.possible_alphabet = "".join(valid_characters) + + def _render(self): + super()._render() + self.keyboard.render_keys() + self.text_entry_display.render() + self.renderer.show_image() + + def _run(self): + while True: + final_selection = None + + input = self.hw_inputs.wait_for( + HardwareButtonsConstants.ALL_KEYS, + check_release=True, + release_keys=[HardwareButtonsConstants.KEY_PRESS, HardwareButtonsConstants.KEY2] + ) + + if self.is_input_in_top_nav: + if input == HardwareButtonsConstants.KEY_PRESS: + # User clicked the "back" arrow + return RET_CODE__BACK_BUTTON + + elif input == HardwareButtonsConstants.KEY_UP: + input = Keyboard.ENTER_BOTTOM + self.is_input_in_top_nav = False + # Re-render it without the highlight + self.top_nav.left_button.is_selected = False + self.top_nav.left_button.render() + + elif input == HardwareButtonsConstants.KEY_DOWN: + input = Keyboard.ENTER_TOP + self.is_input_in_top_nav = False + # Re-render it without the highlight + self.top_nav.left_button.is_selected = False + self.top_nav.left_button.render() + + elif input in [HardwareButtonsConstants.KEY_RIGHT, HardwareButtonsConstants.KEY_LEFT]: + # no action in this context + continue + + ret_val = self.keyboard.update_from_input(input) + + if ret_val in Keyboard.EXIT_DIRECTIONS: + self.is_input_in_top_nav = True + self.top_nav.left_button.is_selected = True + self.top_nav.left_button.render() + + elif ret_val in Keyboard.ADDITIONAL_KEYS: + if input == HardwareButtonsConstants.KEY_PRESS and ret_val == Keyboard.KEY_BACKSPACE["code"]: + self.letters = self.letters[:-2] + self.letters.append(" ") + + self.calc_possible_alphabet() + self.keyboard.update_active_keys(active_keys=self.possible_alphabet) + self.keyboard.render_keys() + + elif ret_val == Keyboard.KEY_BACKSPACE["code"]: + # We're just hovering over DEL but haven't clicked. Show blank (" ") + # in the live text entry display at the top. + self.letters = self.letters[:-1] + self.letters.append(" ") + + elif input == HardwareButtonsConstants.KEY_PRESS and ret_val in self.possible_alphabet: + # User has locked in the current letter + if self.letters[-1] != " ": + # We'll save that locked in letter next but for now update the + # live text entry display with blank (" ") so that we don't try + # to autocalc matches against a second copy of the letter they + # just selected. e.g. They KEY_PRESS on "s" to build "mus". If + # we advance the live block cursor AND display "s" in it, the + # current word would then be "muss" with no matches. If "mus" + # can get us to our match, we don't want it to disappear right + # as we KEY_PRESS. + self.letters.append(" ") + else: + # clicked same letter twice in a row. Because of the above, an + # immediate second click of the same letter would lock in "ap " + # (note the space) instead of "app". So we replace that trailing + # space with the correct repeated letter and then, as above, + # append a trailing blank. + self.letters = self.letters[:-1] + self.letters.append(ret_val) + self.letters.append(" ") + + self.calc_possible_alphabet() + self.keyboard.update_active_keys(active_keys=self.possible_alphabet) + self.keyboard.render_keys() + + if len(self.letters) > (self.decimal_seed_word_length): + final_selection = True + + + elif input in HardwareButtonsConstants.KEYS__LEFT_RIGHT_UP_DOWN \ + or input in (Keyboard.ENTER_TOP, Keyboard.ENTER_BOTTOM): + if not len(self.letters) > self.decimal_seed_word_length: + self.letters = self.letters[:-1] + self.letters.append(ret_val) + + # Has the user made a final selection of a candidate word? + if input == HardwareButtonsConstants.KEY2: + final_selection = True + + if final_selection: + current_entered_value = "".join(self.letters[:-1]) + if current_entered_value != "": + current_entered_value_int = int(current_entered_value) + if (current_entered_value_int < 0 or current_entered_value_int > 2047): + raise ValueError("Invalid word number (must be between 0 and 2047)") + self.text_entry_display.cur_text = str(current_entered_value_int) + self.text_entry_display.render() + self.renderer.show_image() + + return self.wordlist[current_entered_value_int] + + # Render the text entry display and cursor block + self.text_entry_display.cur_text = ''.join(self.letters) + self.text_entry_display.render() + # Now issue one call to send the pixels to the screen + self.renderer.show_image() @dataclass class SeedFinalizeScreen(ButtonListScreen): diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 679f99dc..680b642b 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -174,6 +174,7 @@ class LoadSeedView(View): SEED_QR = (" Scan a SeedQR", SeedSignerIconConstants.QRCODE) TYPE_12WORD = ("Enter 12-word seed", FontAwesomeIconConstants.KEYBOARD) TYPE_24WORD = ("Enter 24-word seed", FontAwesomeIconConstants.KEYBOARD) + OTHER_FORMATS = ("Other seed formats", FontAwesomeIconConstants.KEYBOARD) TYPE_ELECTRUM = ("Enter Electrum seed", FontAwesomeIconConstants.KEYBOARD) CREATE = (" Create a seed", SeedSignerIconConstants.PLUS) @@ -182,6 +183,7 @@ def run(self): self.SEED_QR, self.TYPE_12WORD, self.TYPE_24WORD, + self.OTHER_FORMATS, ] if self.settings.get_value(SettingsConstants.SETTING__ELECTRUM_SEEDS) == SettingsConstants.OPTION__ENABLED: @@ -211,6 +213,9 @@ def run(self): self.controller.storage.init_pending_mnemonic(num_words=24) return Destination(SeedMnemonicEntryView) + elif button_data[selected_menu_num] == self.OTHER_FORMATS: + return Destination(LoadOtherFormatSeedView) + elif button_data[selected_menu_num] == self.TYPE_ELECTRUM: return Destination(SeedElectrumMnemonicStartView) @@ -218,21 +223,59 @@ def run(self): from .tools_views import ToolsMenuView return Destination(ToolsMenuView) +class LoadOtherFormatSeedView(View): + TYPE_12WORD_BINARY = ("12-word binary", FontAwesomeIconConstants.KEYBOARD) + TYPE_24WORD_BINARY = ("24-word binary", FontAwesomeIconConstants.KEYBOARD) + TYPE_12WORD_DECIMAL = ("12-word decimal", FontAwesomeIconConstants.KEYBOARD) + TYPE_24WORD_DECIMAL = ("24-word decimal", FontAwesomeIconConstants.KEYBOARD) + def run(self): + button_data = [ + self.TYPE_12WORD_BINARY, + self.TYPE_24WORD_BINARY, + self.TYPE_12WORD_DECIMAL, + self.TYPE_24WORD_DECIMAL, + ] + + selected_menu_num = self.run_screen( + ButtonListScreen, + title="Load A Seed", + is_button_text_centered=False, + button_data=button_data + ) + + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + + elif button_data[selected_menu_num] == self.TYPE_12WORD_BINARY: + self.controller.storage.init_pending_mnemonic(num_words=12) + return Destination(SeedMnemonicEntryView) + + elif button_data[selected_menu_num] == self.TYPE_24WORD_BINARY: + self.controller.storage.init_pending_mnemonic(num_words=24) + return Destination(SeedMnemonicEntryView) + + elif button_data[selected_menu_num] == self.TYPE_12WORD_DECIMAL: + self.controller.storage.init_pending_mnemonic(num_words=12) + return Destination(SeedMnemonicEntryView, view_args={"entry_screen_cls": seed_screens.SeedMnemonicDecimalEntryScreen}) + + elif button_data[selected_menu_num] == self.TYPE_24WORD_DECIMAL: + self.controller.storage.init_pending_mnemonic(num_words=24) + return Destination(SeedMnemonicEntryView, view_args={"entry_screen_cls": seed_screens.SeedMnemonicDecimalEntryScreen}) class SeedMnemonicEntryView(View): - def __init__(self, cur_word_index: int = 0, is_calc_final_word: bool=False): + def __init__(self, cur_word_index: int = 0, is_calc_final_word: bool=False, entry_screen_cls=seed_screens.SeedMnemonicEntryScreen): super().__init__() self.cur_word_index = cur_word_index self.cur_word = self.controller.storage.get_pending_mnemonic_word(cur_word_index) self.is_calc_final_word = is_calc_final_word - + self.entry_screen_cls = entry_screen_cls def run(self): ret = self.run_screen( - seed_screens.SeedMnemonicEntryScreen, + self.entry_screen_cls, title=f"Seed Word #{self.cur_word_index + 1}", # Human-readable 1-indexing! - initial_letters=list(self.cur_word) if self.cur_word else ["a"], + current_word=self.cur_word, wordlist=Seed.get_wordlist(wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE)), ) @@ -263,7 +306,8 @@ def run(self): SeedMnemonicEntryView, view_args={ "cur_word_index": self.cur_word_index + 1, - "is_calc_final_word": self.is_calc_final_word + "is_calc_final_word": self.is_calc_final_word, + "entry_screen_cls": self.entry_screen_cls, } ) else: @@ -275,8 +319,6 @@ def run(self): return Destination(SeedFinalizeView) - - class SeedMnemonicInvalidView(View): EDIT = "Review & Edit" DISCARD = ("Discard", None, None, "red") @@ -647,10 +689,10 @@ def run(self): return Destination(SeedTranscribeSeedQRFormatView, view_args={"seed_num": self.seed_num}) elif button_data[selected_menu_num] == self.VIEW_BINARY: - return Destination(SeedWordsView, view_args={"seed_num": self.seed_num, "transformer": seed_format_transformers.convert_word_to_11_bits}) + return Destination(SeedWordsView, view_args={"seed_num": self.seed_num, "seed_format_transformer": seed_format_transformers.convert_word_to_11_bits}) elif button_data[selected_menu_num] == self.VIEW_DECIMAL: - return Destination(SeedWordsView, view_args={"seed_num": self.seed_num, "transformer": seed_format_transformers.convert_word_to_decimal}) + return Destination(SeedWordsView, view_args={"seed_num": self.seed_num, "seed_format_transformer": seed_format_transformers.convert_word_to_decimal}) From 3044d839ddd397534713019d7df5c402579ab53f Mon Sep 17 00:00:00 2001 From: nosedam Date: Fri, 6 Sep 2024 22:38:58 -0300 Subject: [PATCH 4/9] feat: add binary import option --- src/seedsigner/gui/screens/seed_screens.py | 174 +++++++++++++++++- .../helpers/seed_format_transformers.py | 2 +- src/seedsigner/views/seed_views.py | 4 +- 3 files changed, 176 insertions(+), 4 deletions(-) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index 85490427..f7572543 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -582,7 +582,179 @@ def _run(self): self.text_entry_display.render() self.renderer.show_image() - return self.wordlist[current_entered_value_int] + return seed_format_transformers.convert_decimal_to_word(current_entered_value_int) + + # Render the text entry display and cursor block + self.text_entry_display.cur_text = ''.join(self.letters) + self.text_entry_display.render() + + # Now issue one call to send the pixels to the screen + self.renderer.show_image() + +@dataclass +class SeedMnemonicBinaryEntryScreen(BaseTopNavScreen): + current_word: str = None + wordlist: list = None + possible_alphabet: str = "01" + + def __post_init__(self): + super().__post_init__() + + if not self.current_word: + self.initial_letters = [self.possible_alphabet[0]] + else: + self.initial_letters = list(seed_format_transformers.convert_word_to_11_bits("".join(self.current_word))) + + self.keyboard_width = 200 + text_entry_display_y = self.top_nav.height + text_entry_display_height = 30 + + self.arrow_up_is_active = False + self.arrow_down_is_active = False + + self.keyboard = Keyboard( + draw=self.image_draw, + charset=self.possible_alphabet, + rows=2, + cols=2, + rect=( + GUIConstants.EDGE_PADDING, + text_entry_display_y + text_entry_display_height + 6, + GUIConstants.EDGE_PADDING + self.keyboard_width, + self.canvas_height + ), + auto_wrap=[Keyboard.WRAP_LEFT, Keyboard.WRAP_RIGHT] + ) + + self.binary_seed_word_length = 11 + + self.text_entry_display = TextEntryDisplay( + canvas=self.canvas, + rect=( + GUIConstants.EDGE_PADDING, + text_entry_display_y, + GUIConstants.EDGE_PADDING + (GUIConstants.BUTTON_FONT_SIZE * self.binary_seed_word_length), + text_entry_display_y + text_entry_display_height + ), + is_centered=False, + cur_text="".join(self.initial_letters) + ) + + self.letters = self.initial_letters + + if len(self.letters) > 1: + self.letters.append(" ") # "Lock in" the last letter as if KEY_PRESS + self.calc_possible_alphabet() + self.keyboard.update_active_keys(active_keys=self.possible_alphabet) + self.keyboard.set_selected_key(selected_letter=self.letters[-2]) + else: + self.keyboard.set_selected_key(selected_letter=self.letters[-1]) + + def calc_possible_alphabet(self): + if (len(self.letters) > self.binary_seed_word_length): + self.possible_alphabet = "" + return + self.possible_alphabet = "01" + + def _render(self): + super()._render() + self.keyboard.render_keys() + self.text_entry_display.render() + self.renderer.show_image() + + def _run(self): + while True: + final_selection = None + + input = self.hw_inputs.wait_for( + HardwareButtonsConstants.ALL_KEYS, + check_release=True, + release_keys=[HardwareButtonsConstants.KEY_PRESS, HardwareButtonsConstants.KEY2] + ) + + if self.is_input_in_top_nav: + if input == HardwareButtonsConstants.KEY_PRESS: + # User clicked the "back" arrow + return RET_CODE__BACK_BUTTON + + elif input == HardwareButtonsConstants.KEY_UP: + input = Keyboard.ENTER_BOTTOM + self.is_input_in_top_nav = False + # Re-render it without the highlight + self.top_nav.left_button.is_selected = False + self.top_nav.left_button.render() + + elif input == HardwareButtonsConstants.KEY_DOWN: + input = Keyboard.ENTER_TOP + self.is_input_in_top_nav = False + # Re-render it without the highlight + self.top_nav.left_button.is_selected = False + self.top_nav.left_button.render() + + elif input in [HardwareButtonsConstants.KEY_RIGHT, HardwareButtonsConstants.KEY_LEFT]: + # no action in this context + continue + + ret_val = self.keyboard.update_from_input(input) + + if ret_val in Keyboard.EXIT_DIRECTIONS: + self.is_input_in_top_nav = True + self.top_nav.left_button.is_selected = True + self.top_nav.left_button.render() + + elif ret_val in Keyboard.ADDITIONAL_KEYS: + if input == HardwareButtonsConstants.KEY_PRESS and ret_val == Keyboard.KEY_BACKSPACE["code"]: + self.letters = self.letters[:-2] + self.letters.append(" ") + + self.calc_possible_alphabet() + self.keyboard.update_active_keys(active_keys=self.possible_alphabet) + self.keyboard.render_keys() + + elif ret_val == Keyboard.KEY_BACKSPACE["code"]: + # We're just hovering over DEL but haven't clicked. Show blank (" ") + # in the live text entry display at the top. + self.letters = self.letters[:-1] + self.letters.append(" ") + + elif input == HardwareButtonsConstants.KEY_PRESS and ret_val in self.possible_alphabet: + if self.letters[-1] != " ": + self.letters.append(" ") + else: + self.letters = self.letters[:-1] + self.letters.append(ret_val) + self.letters.append(" ") + + self.calc_possible_alphabet() + self.keyboard.update_active_keys(active_keys=self.possible_alphabet) + self.keyboard.render_keys() + + if len(self.letters) > (self.binary_seed_word_length): + final_selection = True + + + elif input in HardwareButtonsConstants.KEYS__LEFT_RIGHT_UP_DOWN \ + or input in (Keyboard.ENTER_TOP, Keyboard.ENTER_BOTTOM): + if not len(self.letters) >= self.binary_seed_word_length: + self.letters = self.letters[:-1] + self.letters.append(ret_val) + + # Has the user made a final selection of a candidate word? + if input == HardwareButtonsConstants.KEY2 and len(self.letters) > self.binary_seed_word_length: + final_selection = True + + if final_selection: + print("FINAL SELECTION") + print(self.letters) + current_entered_value = "".join(self.letters[:-1]) + if current_entered_value != "": + if len(current_entered_value) != self.binary_seed_word_length: + raise ValueError("Invalid word binary (must have 11 characters)") + self.text_entry_display.cur_text = current_entered_value + self.text_entry_display.render() + self.renderer.show_image() + + return seed_format_transformers.convert_11_bits_to_word(current_entered_value) # Render the text entry display and cursor block self.text_entry_display.cur_text = ''.join(self.letters) diff --git a/src/seedsigner/helpers/seed_format_transformers.py b/src/seedsigner/helpers/seed_format_transformers.py index 3646fbef..1c53d22d 100644 --- a/src/seedsigner/helpers/seed_format_transformers.py +++ b/src/seedsigner/helpers/seed_format_transformers.py @@ -21,6 +21,6 @@ def convert_11_bits_to_word(bits: str) -> str: return wordlist[int(bits, 2)] -def convert_decimal_to_word(decimal: str) -> str: +def convert_decimal_to_word(decimal: str | int) -> str: wordlist = bip39.WORDLIST return wordlist[int(decimal)] \ No newline at end of file diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 680b642b..97be1d19 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -249,11 +249,11 @@ def run(self): elif button_data[selected_menu_num] == self.TYPE_12WORD_BINARY: self.controller.storage.init_pending_mnemonic(num_words=12) - return Destination(SeedMnemonicEntryView) + return Destination(SeedMnemonicEntryView, view_args={"entry_screen_cls": seed_screens.SeedMnemonicBinaryEntryScreen}) elif button_data[selected_menu_num] == self.TYPE_24WORD_BINARY: self.controller.storage.init_pending_mnemonic(num_words=24) - return Destination(SeedMnemonicEntryView) + return Destination(SeedMnemonicEntryView, view_args={"entry_screen_cls": seed_screens.SeedMnemonicBinaryEntryScreen}) elif button_data[selected_menu_num] == self.TYPE_12WORD_DECIMAL: self.controller.storage.init_pending_mnemonic(num_words=12) From cf09f525dad0618417f7dbff70163807f3637e95 Mon Sep 17 00:00:00 2001 From: nosedam Date: Fri, 6 Sep 2024 22:49:11 -0300 Subject: [PATCH 5/9] fix: review details view on other seeds formats --- src/seedsigner/views/seed_views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 97be1d19..2e5a6fc9 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -315,7 +315,7 @@ def run(self): try: self.controller.storage.convert_pending_mnemonic_to_pending_seed() except InvalidSeedException: - return Destination(SeedMnemonicInvalidView) + return Destination(SeedMnemonicInvalidView, view_args={"entry_screen_cls": self.entry_screen_cls}) return Destination(SeedFinalizeView) @@ -323,9 +323,10 @@ class SeedMnemonicInvalidView(View): EDIT = "Review & Edit" DISCARD = ("Discard", None, None, "red") - def __init__(self): + def __init__(self, entry_screen_cls=seed_screens.SeedMnemonicEntryScreen): super().__init__() self.mnemonic: List[str] = self.controller.storage.pending_mnemonic + self.entry_screen_cls = entry_screen_cls def run(self): @@ -340,7 +341,7 @@ def run(self): ) if button_data[selected_menu_num] == self.EDIT: - return Destination(SeedMnemonicEntryView, view_args={"cur_word_index": 0}) + return Destination(SeedMnemonicEntryView, view_args={"cur_word_index": 0, "entry_screen_cls": self.entry_screen_cls}) elif button_data[selected_menu_num] == self.DISCARD: self.controller.storage.discard_pending_mnemonic() From c83c0cc312de654647431e5b344b297b8f264616 Mon Sep 17 00:00:00 2001 From: nosedam Date: Thu, 12 Sep 2024 22:15:08 -0300 Subject: [PATCH 6/9] feat: add binary keyboard buttons for aditional hardware buttons --- src/seedsigner/gui/components.py | 2 + src/seedsigner/gui/screens/seed_screens.py | 79 +++++++++++++++------- 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/seedsigner/gui/components.py b/src/seedsigner/gui/components.py index 9193e451..1a422d99 100644 --- a/src/seedsigner/gui/components.py +++ b/src/seedsigner/gui/components.py @@ -100,6 +100,8 @@ class FontAwesomeIconConstants: SQUARE_CARET_UP = "\uf151" UNLOCK = "\uf09c" X = "\u0058" + NUMBER_0 = "\u0030" + NUMBER_1 = "\u0031" class SeedSignerIconConstants: diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index f7572543..9040e989 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -422,9 +422,6 @@ def __post_init__(self): text_entry_display_y = self.top_nav.height text_entry_display_height = 30 - self.arrow_up_is_active = False - self.arrow_down_is_active = False - self.keyboard = Keyboard( draw=self.image_draw, charset=self.possible_alphabet, @@ -535,21 +532,8 @@ def _run(self): elif input == HardwareButtonsConstants.KEY_PRESS and ret_val in self.possible_alphabet: # User has locked in the current letter if self.letters[-1] != " ": - # We'll save that locked in letter next but for now update the - # live text entry display with blank (" ") so that we don't try - # to autocalc matches against a second copy of the letter they - # just selected. e.g. They KEY_PRESS on "s" to build "mus". If - # we advance the live block cursor AND display "s" in it, the - # current word would then be "muss" with no matches. If "mus" - # can get us to our match, we don't want it to disappear right - # as we KEY_PRESS. self.letters.append(" ") else: - # clicked same letter twice in a row. Because of the above, an - # immediate second click of the same letter would lock in "ap " - # (note the space) instead of "app". So we replace that trailing - # space with the correct repeated letter and then, as above, - # append a trailing blank. self.letters = self.letters[:-1] self.letters.append(ret_val) self.letters.append(" ") @@ -605,13 +589,10 @@ def __post_init__(self): else: self.initial_letters = list(seed_format_transformers.convert_word_to_11_bits("".join(self.current_word))) - self.keyboard_width = 200 + self.keyboard_width = 180 text_entry_display_y = self.top_nav.height text_entry_display_height = 30 - self.arrow_up_is_active = False - self.arrow_down_is_active = False - self.keyboard = Keyboard( draw=self.image_draw, charset=self.possible_alphabet, @@ -633,13 +614,42 @@ def __post_init__(self): rect=( GUIConstants.EDGE_PADDING, text_entry_display_y, - GUIConstants.EDGE_PADDING + (GUIConstants.BUTTON_FONT_SIZE * self.binary_seed_word_length), + GUIConstants.EDGE_PADDING + (GUIConstants.BODY_FONT_MIN_SIZE * self.binary_seed_word_length), text_entry_display_y + text_entry_display_height ), is_centered=False, cur_text="".join(self.initial_letters) ) + self.highlighted_row_y = int((self.canvas_height - GUIConstants.BUTTON_HEIGHT)/2) + arrow_button_width = GUIConstants.BUTTON_HEIGHT + GUIConstants.EDGE_PADDING + arrow_button_height = int(0.75*GUIConstants.BUTTON_HEIGHT) + + # Button '1' for KEY1 + self.one_button = IconButton( + icon_name=FontAwesomeIconConstants.NUMBER_1, + icon_size=GUIConstants.ICON_FONT_SIZE, + is_text_centered=False, + screen_x=self.canvas_width - arrow_button_width + GUIConstants.COMPONENT_PADDING, + screen_y=self.highlighted_row_y - 3*GUIConstants.COMPONENT_PADDING - GUIConstants.BUTTON_HEIGHT, + width=arrow_button_width, + height=arrow_button_height + 1, + ) + + # Button '0' for KEY3 + self.zero_button = IconButton( + icon_name=FontAwesomeIconConstants.NUMBER_0, + icon_size=GUIConstants.ICON_FONT_SIZE, + is_text_centered=False, + screen_x=self.canvas_width - arrow_button_width + GUIConstants.COMPONENT_PADDING, + screen_y=self.highlighted_row_y + GUIConstants.BUTTON_HEIGHT + 3*GUIConstants.COMPONENT_PADDING, + width=arrow_button_width, + height=arrow_button_height + 1, + ) + + self.one_button.render() + self.zero_button.render() + self.letters = self.initial_letters if len(self.letters) > 1: @@ -660,6 +670,8 @@ def _render(self): super()._render() self.keyboard.render_keys() self.text_entry_display.render() + self.one_button.render() + self.zero_button.render() self.renderer.show_image() def _run(self): @@ -739,13 +751,34 @@ def _run(self): self.letters = self.letters[:-1] self.letters.append(ret_val) + if input == HardwareButtonsConstants.KEY1: + self.letters = self.letters[:-1] + self.letters.append("1") + self.letters.append(" ") + + self.calc_possible_alphabet() + self.keyboard.update_active_keys(active_keys=self.possible_alphabet) + self.keyboard.render_keys() + + if len(self.letters) > (self.binary_seed_word_length): + final_selection = True + + if input == HardwareButtonsConstants.KEY3: + self.letters = self.letters[:-1] + self.letters.append("0") + self.letters.append(" ") + + self.calc_possible_alphabet() + self.keyboard.update_active_keys(active_keys=self.possible_alphabet) + self.keyboard.render_keys() + + if len(self.letters) > (self.binary_seed_word_length): + final_selection = True # Has the user made a final selection of a candidate word? if input == HardwareButtonsConstants.KEY2 and len(self.letters) > self.binary_seed_word_length: final_selection = True if final_selection: - print("FINAL SELECTION") - print(self.letters) current_entered_value = "".join(self.letters[:-1]) if current_entered_value != "": if len(current_entered_value) != self.binary_seed_word_length: From ffac18ca7a9bacab403af1b4ae838db5672d0238 Mon Sep 17 00:00:00 2001 From: nosedam Date: Thu, 12 Sep 2024 22:40:34 -0300 Subject: [PATCH 7/9] refactor: rename variables --- src/seedsigner/gui/screens/seed_screens.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index 9040e989..cb8e4c6d 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -622,18 +622,18 @@ def __post_init__(self): ) self.highlighted_row_y = int((self.canvas_height - GUIConstants.BUTTON_HEIGHT)/2) - arrow_button_width = GUIConstants.BUTTON_HEIGHT + GUIConstants.EDGE_PADDING - arrow_button_height = int(0.75*GUIConstants.BUTTON_HEIGHT) + binary_buttons_width = GUIConstants.BUTTON_HEIGHT + GUIConstants.EDGE_PADDING + binary_buttons_height = int(0.75*GUIConstants.BUTTON_HEIGHT) # Button '1' for KEY1 self.one_button = IconButton( icon_name=FontAwesomeIconConstants.NUMBER_1, icon_size=GUIConstants.ICON_FONT_SIZE, is_text_centered=False, - screen_x=self.canvas_width - arrow_button_width + GUIConstants.COMPONENT_PADDING, + screen_x=self.canvas_width - binary_buttons_width + GUIConstants.COMPONENT_PADDING, screen_y=self.highlighted_row_y - 3*GUIConstants.COMPONENT_PADDING - GUIConstants.BUTTON_HEIGHT, - width=arrow_button_width, - height=arrow_button_height + 1, + width=binary_buttons_width, + height=binary_buttons_height + 1, ) # Button '0' for KEY3 @@ -641,10 +641,10 @@ def __post_init__(self): icon_name=FontAwesomeIconConstants.NUMBER_0, icon_size=GUIConstants.ICON_FONT_SIZE, is_text_centered=False, - screen_x=self.canvas_width - arrow_button_width + GUIConstants.COMPONENT_PADDING, + screen_x=self.canvas_width - binary_buttons_width + GUIConstants.COMPONENT_PADDING, screen_y=self.highlighted_row_y + GUIConstants.BUTTON_HEIGHT + 3*GUIConstants.COMPONENT_PADDING, - width=arrow_button_width, - height=arrow_button_height + 1, + width=binary_buttons_width, + height=binary_buttons_height + 1, ) self.one_button.render() From bd32a997cc312cb0d591564ca5c3580a07778be2 Mon Sep 17 00:00:00 2001 From: nosedam Date: Sat, 14 Sep 2024 00:15:12 -0300 Subject: [PATCH 8/9] feat: format --- src/seedsigner/gui/screens/seed_screens.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index cb8e4c6d..4872c5c7 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -125,6 +125,7 @@ def __post_init__(self): self.word_font_height = -1 * top self.matches_list_row_height = self.word_font_height + GUIConstants.COMPONENT_PADDING + def calc_possible_alphabet(self, new_letter = False): if (self.letters and len(self.letters) > 1 and new_letter == False) or (len(self.letters) > 0 and new_letter == True): search_letters = self.letters[:] @@ -142,10 +143,12 @@ def calc_possible_alphabet(self, new_letter = False): self.possible_alphabet = "abcdefghijklmnopqrstuvwxyz" self.possible_words = [] + def calc_possible_words(self): self.possible_words = [i for i in self.wordlist if i.startswith("".join(self.letters).strip())] self.selected_possible_words_index = 0 + def render_possible_matches(self, highlight_word=None): """ Internal helper method to render the KEY 1, 2, 3 word candidates. (has access to all vars in the parent's context) @@ -232,6 +235,7 @@ def render_possible_matches(self, highlight_word=None): self.matches_list_up_button.render() self.matches_list_down_button.render() + def _render(self): super()._render() self.keyboard.render_keys() @@ -240,6 +244,7 @@ def _render(self): self.renderer.show_image() + def _run(self): while True: input = self.hw_inputs.wait_for( @@ -404,6 +409,7 @@ def _run(self): # Now issue one call to send the pixels to the screen self.renderer.show_image() + @dataclass class SeedMnemonicDecimalEntryScreen(BaseTopNavScreen): current_word: str = None From 5a38b80cfc24c6452c7b3ff31bd45bdf3fa1a409 Mon Sep 17 00:00:00 2001 From: nosedam Date: Tue, 29 Oct 2024 22:29:52 -0300 Subject: [PATCH 9/9] feat: add colors to binary view export to prevent confusion --- src/seedsigner/gui/screens/seed_screens.py | 29 ++++++++++++++++------ src/seedsigner/views/seed_views.py | 1 + 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/seedsigner/gui/screens/seed_screens.py b/src/seedsigner/gui/screens/seed_screens.py index 4872c5c7..554935ca 100644 --- a/src/seedsigner/gui/screens/seed_screens.py +++ b/src/seedsigner/gui/screens/seed_screens.py @@ -852,6 +852,27 @@ class SeedWordsScreen(WarningEdgesMixin, ButtonListScreen): num_pages: int = 3 is_bottom_list: bool = True status_color: str = GUIConstants.DIRE_WARNING_COLOR + colorize_words: bool = False + + def draw_seed_word(self, draw, word, number_box_x, number_box_width, font, supersampling_factor, baseline_y, colorize_words): + if colorize_words: + for i, char in enumerate(word): + current_color = [GUIConstants.BODY_FONT_COLOR, GUIConstants.DIRE_WARNING_COLOR, GUIConstants.ACCENT_COLOR][int(i / 3) % 3] + draw.text( + ((GUIConstants.LIST_ITEM_PADDING * supersampling_factor) * i + number_box_x + number_box_width + (GUIConstants.COMPONENT_PADDING * supersampling_factor) + (i * GUIConstants.COMPONENT_PADDING * supersampling_factor), baseline_y), + font=font, + text=char, + fill=current_color, + anchor="ls", + ) + else: + draw.text( + (number_box_x + number_box_width + (GUIConstants.COMPONENT_PADDING * supersampling_factor), baseline_y), + font=font, + text=word, + fill=GUIConstants.BODY_FONT_COLOR, + anchor="ls", # Left, baSeline + ) def __post_init__(self): @@ -909,13 +930,7 @@ def __post_init__(self): ) # Now draw the word - draw.text( - (number_box_x + number_box_width + (GUIConstants.COMPONENT_PADDING * supersampling_factor), baseline_y), - font=font, - text=word, - fill=GUIConstants.BODY_FONT_COLOR, - anchor="ls", # Left, baSeline - ) + self.draw_seed_word(draw, word, number_box_x, number_box_width, font, supersampling_factor, baseline_y, self.colorize_words) number_box_y += number_box_height + (int(1.5*GUIConstants.COMPONENT_PADDING) * supersampling_factor) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 2e5a6fc9..e362a2f7 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -1121,6 +1121,7 @@ def run(self): page_index=self.page_index, num_pages=num_pages, button_data=button_data, + colorize_words=self.seed_format_transformer == seed_format_transformers.convert_word_to_11_bits ).display() if selected_menu_num == RET_CODE__BACK_BUTTON: