Skip to content

Commit

Permalink
Merge branch 'generic_keyboardscreen' into final_word_v2
Browse files Browse the repository at this point in the history
  • Loading branch information
kdmukai committed Apr 24, 2022
2 parents f6e20a4 + 16c8044 commit 6c1c847
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 296 deletions.
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,16 @@ The quickest and easiest way to install the software is to download the most rec
After downloading the .zip file, extract the seedsigner .img file, and write it to a MicroSD card (at least 4GB in size or larger). Then install the MicroSD in the assembled hardware and off you go. If your goal is a more trustless installation, you can follow the [manual installation instructions](docs/manual_installation.md).

## Verifying Your Software
You can verify the data integrity and authenticity of the latest release with as little as three commands (though moving forward you will have to replace the version in the following commands with the version number you are attempting to validate). This process assumes that you have navigated to a folder where you have these four relevant files present:
You can verify the data integrity and authenticity of the latest release with as little as three commands. This process assumes that you know [how to navigate on a terminal](https://terminalcheatsheet.com/guides/navigate-terminal) and have navigated to the folder where you have these four relevant files present: (This will most likely be your Downloads folder.)

* seedsigner_pubkey.gpg (from the main folder of this repo)
* seedsigner_0_4_5.img.zip (from the software release)
* seedsigner_0_4_5.img.zip.sha256 (from the software release)
* seedsigner_0_4_5.img.zip.sha256.sig (from the software release)
* seedsigner_0_4_6.img.zip (from the software release)
* seedsigner_0_4_6.img.zip.sha256 (from the software release)
* seedsigner_0_4_6.img.zip.sha256.sig (from the software release)

This process also assumes you are running the commands from a system where both GPG and shasum are installed and working.
**Note:** The specific version number of the files in your folder might not match the above exactly, but their overall format and amount should be the same.

This process also assumes you are running the commands from a system where both [GPG](https://gnupg.org/download/index.html) and [shasum](https://command-not-found.com/shasum) are installed and working.

First make sure that the public key is present in your keychain:
```
Expand All @@ -101,8 +103,10 @@ key <...> not changed

Now you can verify the authenticity of the small text file containing the release's SHA256 hash with the command:
```
gpg --verify seedsigner_0_4_5.img.zip.sha256.sig
gpg --verify seedsigner_0_*_*.img.zip.sha256.sig
```
**Note:** The `*`s in the command above allow the terminal to auto-populate the command with the version number you have in the folder you are in. It should be copied and pasted as is.

The reponse to this command should include the text:
```
Good signature from "seedsigner <[email protected]>" [unknown]
Expand All @@ -111,11 +115,11 @@ The previous command validates that aforementioned small text file was signed us

The last step is to make sure the .zip file that you've downloaded, and that contains the released software, is a perfect match to the software that was published by the holder of the private key in the last step. The command for this step is:
```
shasum -a 256 -c seedsigner_0_4_5.img.zip.sha256
shasum -a 256 -c seedsigner_0_*_*.img.zip.sha256
```
The reponse to this command should include the text:
```
seedsigner_0_4_5.img.zip: OK
seedsigner_0_4_6.img.zip: OK
```

There are other steps you can take to verify the software, including examining the hash value in the .sha256 text file, but this one has been documented here because it seems the simplest for most people to follow. Please recognize that this process can only validate the software to the extent that the entity that first published the key is an honest actor, and assumes the private key has remained uncompromised and is not being used by a malicious actor.
Expand Down
2 changes: 1 addition & 1 deletion src/seedsigner/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class Controller(Singleton):
rather than at the top in order avoid circular imports.
"""

VERSION = "0.5.0-rc1"
VERSION = "0.5.0"

# Declare class member vars with type hints to enable richer IDE support throughout
# the code.
Expand Down
207 changes: 206 additions & 1 deletion src/seedsigner/gui/screens/screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
from dataclasses import dataclass
from PIL import Image, ImageDraw, ImageColor
from typing import Any, List, Tuple
from seedsigner.gui.keyboard import Keyboard, TextEntryDisplay
from seedsigner.gui.renderer import Renderer

from seedsigner.models.threads import BaseThread
from seedsigner.models.encode_qr import EncodeQR
from seedsigner.models.settings import Settings, SettingsConstants

from ..components import (GUIConstants, BaseComponent, Button, Icon, LargeIconButton, SeedSignerCustomIconConstants, TopNav,
from ..components import (FontAwesomeIconConstants, GUIConstants, BaseComponent, Button, Icon, IconButton, LargeIconButton, SeedSignerCustomIconConstants, TopNav,
TextArea, load_image)

from seedsigner.hardware.buttons import HardwareButtonsConstants, HardwareButtons
Expand Down Expand Up @@ -871,3 +872,207 @@ def __post_init__(self):
screen_y=self.top_nav.height,
height=self.canvas_height - self.top_nav.height,
))



@dataclass
class KeyboardScreen(BaseTopNavScreen):
"""
Generalized Screen for a single Keyboard layout writing user input to a
TextEntryDisplay.
Args:
* rows
* cols
* keyboard_font_name
* keyboard_font_size: Specify `None` to auto-size to Key height.
* keys_charset: Specify the chars displayed on the keys of the keyboard.
* keys_to_values: Optional mapping from key_charset to input value (e.g. dice icon to digit).
* return_after_n_chars: exits and returns the user's input after n characters.
* show_save_button: Render a KEY3 soft button for save & exit
* initial_value: initialize the TextEntryDisplay with an existing string
"""
rows: int = None
cols: int = None
keyboard_font_name: str = GUIConstants.FIXED_WIDTH_EMPHASIS_FONT_NAME
keyboard_font_size: int = GUIConstants.TOP_NAV_TITLE_FONT_SIZE + 2
keys_charset: str = None
keys_to_values: dict = None
return_after_n_chars: int = None
show_save_button: bool = False
initial_value: str = ""

def __post_init__(self):
super().__post_init__()

if self.initial_value:
self.user_input = self.initial_value
else:
self.user_input = ""

# Set up the keyboard params
if self.show_save_button:
right_panel_buttons_width = 60
hw_button_x = self.canvas_width - right_panel_buttons_width + GUIConstants.COMPONENT_PADDING
hw_button_y = int(self.canvas_height - GUIConstants.BUTTON_HEIGHT) / 2 + 60

self.keyboard_width = self.canvas_width - (GUIConstants.EDGE_PADDING + GUIConstants.COMPONENT_PADDING + right_panel_buttons_width - GUIConstants.COMPONENT_PADDING)

# Render the right button panel (only has a Key3 "Save" button)
self.save_button = IconButton(
icon_name=FontAwesomeIconConstants.SOLID_CIRCLE_CHECK,
icon_color=GUIConstants.SUCCESS_COLOR,
width=right_panel_buttons_width,
screen_x=hw_button_x,
screen_y=hw_button_y,
)
self.components.append(self.save_button)
else:
self.keyboard_width = self.canvas_width - 2*GUIConstants.EDGE_PADDING

text_entry_display_y = self.top_nav.height
text_entry_display_height = 30

keyboard_start_y = text_entry_display_y + text_entry_display_height + GUIConstants.COMPONENT_PADDING
button_height = int((self.canvas_height - GUIConstants.EDGE_PADDING - text_entry_display_y - text_entry_display_height - GUIConstants.COMPONENT_PADDING - (self.rows - 1) * 2) / self.rows)
if self.keyboard_font_size:
font_size = self.keyboard_font_size
else:
# Scale with button height
font_size = button_height - GUIConstants.COMPONENT_PADDING
self.keyboard_digits = Keyboard(
draw=self.renderer.draw,
charset=self.keys_charset,
font_name=self.keyboard_font_name,
font_size=font_size,
rows=self.rows,
cols=self.cols,
rect=(
GUIConstants.EDGE_PADDING,
keyboard_start_y,
GUIConstants.EDGE_PADDING + self.keyboard_width,
keyboard_start_y + self.rows * button_height + (self.rows - 1) * 2
),
auto_wrap=[Keyboard.WRAP_LEFT, Keyboard.WRAP_RIGHT],
render_now=False
)
self.keyboard_digits.set_selected_key(selected_letter=self.keys_charset[0])

self.text_entry_display = TextEntryDisplay(
canvas=self.renderer.canvas,
rect=(
GUIConstants.EDGE_PADDING,
text_entry_display_y,
self.canvas_width - GUIConstants.EDGE_PADDING,
text_entry_display_y + text_entry_display_height
),
cursor_mode=TextEntryDisplay.CURSOR_MODE__BAR,
is_centered=False,
cur_text=self.initial_value,
)


def _render(self):
super()._render()

self.keyboard_digits.render_keys()
self.text_entry_display.render()

self.renderer.show_image()


def _run(self):
self.cursor_position = len(self.user_input)

# Start the interactive update loop
while True:
input = self.hw_inputs.wait_for(
HardwareButtonsConstants.KEYS__LEFT_RIGHT_UP_DOWN + [HardwareButtonsConstants.KEY_PRESS, HardwareButtonsConstants.KEY3],
check_release=True,
release_keys=[HardwareButtonsConstants.KEY_PRESS, HardwareButtonsConstants.KEY3]
)

# Check possible exit conditions
if self.top_nav.is_selected and input == HardwareButtonsConstants.KEY_PRESS:
return RET_CODE__BACK_BUTTON

elif self.show_save_button and input == HardwareButtonsConstants.KEY3:
# Save!
# First show the save button reacting to the click
self.save_button.is_selected = True
self.save_button.render()
self.renderer.show_image()

# Then return the input to the View
if len(self.user_input) > 0:
return self.user_input.strip()

# Process normal input
if input in [HardwareButtonsConstants.KEY_UP, HardwareButtonsConstants.KEY_DOWN] and self.top_nav.is_selected:
# We're navigating off the previous button
self.top_nav.is_selected = False
self.top_nav.render_buttons()

# Override the actual input w/an ENTER signal for the Keyboard
if input == HardwareButtonsConstants.KEY_DOWN:
input = Keyboard.ENTER_TOP
else:
input = Keyboard.ENTER_BOTTOM
elif input in [HardwareButtonsConstants.KEY_LEFT, HardwareButtonsConstants.KEY_RIGHT] and self.top_nav.is_selected:
# ignore
continue

ret_val = self.keyboard_digits.update_from_input(input)

# Now process the result from the keyboard
if ret_val in Keyboard.EXIT_DIRECTIONS:
self.top_nav.is_selected = True
self.top_nav.render_buttons()

elif ret_val in Keyboard.ADDITIONAL_KEYS and input == HardwareButtonsConstants.KEY_PRESS:
if ret_val == Keyboard.KEY_BACKSPACE["code"]:
if len(self.user_input) > 0:
self.user_input = self.user_input[:-1]
self.cursor_position -= 1

elif input == HardwareButtonsConstants.KEY_PRESS and ret_val not in Keyboard.ADDITIONAL_KEYS:
# User has locked in the current letter
if self.keys_to_values:
# Map the Key display char to its output value (e.g. dice icon to digit)
ret_val = self.keys_to_values[ret_val]
self.user_input += ret_val
self.cursor_position += 1

if self.cursor_position == self.return_after_n_chars:
return self.user_input

# Render a new TextArea over the TopNav title bar
if self.update_title():
TextArea(
text=self.title,
font_name=GUIConstants.TOP_NAV_TITLE_FONT_NAME,
font_size=GUIConstants.TOP_NAV_TITLE_FONT_SIZE,
height=self.top_nav.height,
).render()
self.top_nav.render_buttons()

elif input in HardwareButtonsConstants.KEYS__LEFT_RIGHT_UP_DOWN:
# Live joystick movement; haven't locked this new letter in yet.
# Leave current spot blank for now. Only update the active keyboard keys
# when a selection has been locked in (KEY_PRESS) or removed ("del").
pass

# Render the text entry display and cursor block
self.text_entry_display.render(self.user_input)

self.renderer.show_image()


def update_title(self) -> bool:
"""
Optionally update the self.title after each completed key input.
e.g. to increment the dice roll count:
self.title = f"Roll {self.cursor_position + 1}"
"""
return False
Loading

0 comments on commit 6c1c847

Please sign in to comment.