Merge pull request #275 from SeedSigner/0.5.1-ram

SeedSigner OS App Changes (0.5.1-ram branch) into dev branch
This commit is contained in:
Nick Klockenga
2022-12-02 09:09:43 -05:00
committed by GitHub
10 changed files with 220 additions and 23 deletions
+5
View File
@@ -1,5 +1,6 @@
import logging
import traceback
import os
from embit.descriptor import Descriptor
from embit.psbt import PSBT
@@ -8,6 +9,7 @@ from typing import List
from seedsigner.gui.renderer import Renderer
from seedsigner.hardware.buttons import HardwareButtons
from seedsigner.hardware.microsd import MicroSD
from seedsigner.views.screensaver import ScreensaverScreen
from seedsigner.views.view import Destination, NotYetImplementedView, UnhandledExceptionView
@@ -126,6 +128,9 @@ class Controller(Singleton):
# TODO: Rename "storage" to something more indicative of its temp, in-memory state
controller.storage = SeedStorage()
controller.settings = Settings.get_instance()
controller.microsd = MicroSD.get_instance()
controller.microsd.start_detection()
# Store one working psbt in memory
controller.psbt = None
+68 -1
View File
@@ -58,6 +58,8 @@ class GUIConstants:
BUTTON_BACKGROUND_COLOR = "#2c2c2c"
BUTTON_HEIGHT = 32
BUTTON_SELECTED_FONT_COLOR = "black"
NOTIFICATION_COLOR = "#00f100"
@@ -102,7 +104,7 @@ class FontAwesomeIconConstants:
UNLOCK = "\uf09c"
QRCODE = "\uf029"
X = "\u0058"
SDCARD = "\uf7c2"
class SeedSignerCustomIconConstants:
@@ -568,6 +570,71 @@ class IconTextLine(BaseComponent):
self.icon.render()
@dataclass
class ToastOverlay(BaseComponent):
icon_name: str = None
color: str = None
label_text: str = None
def __post_init__(self):
super().__post_init__()
self.icon = Icon(
image_draw=self.image_draw,
canvas=self.canvas,
screen_x=20,
screen_y=190,
icon_name=self.icon_name,
icon_size=30,
icon_color=self.color
)
self.label = TextArea(
image_draw=self.image_draw,
canvas=self.canvas,
text=self.label_text,
font_size=19,
font_color=self.color,
edge_padding=0,
is_text_centered=False,
auto_line_break=False,
width=160,
height=20,
screen_x=55,
screen_y=195,
allow_text_overflow=False
)
def render(self):
import time
from seedsigner.controller import Controller
self.controller: Controller = Controller.get_instance()
self.current_screen = self.renderer.canvas.copy()
# Special case when screensaver is running
if self.controller.screensaver._is_running:
self.controller.buttons.override_ind = True
self.image_draw.rounded_rectangle(
( GUIConstants.EDGE_PADDING + 2, self.canvas_height - 60, self.canvas_width - GUIConstants.EDGE_PADDING - 2, self.canvas_width - GUIConstants.EDGE_PADDING - 2),
fill=GUIConstants.BACKGROUND_COLOR,
radius=8,
outline=self.color,
width=2,
)
self.icon.render()
self.label.render()
self.renderer.show_image()
t_end = time.time() + 3
while time.time() < t_end:
if self.controller.buttons.has_any_input():
break
self.renderer.show_image(self.current_screen)
@dataclass
class FormattedAddress(BaseComponent):
+36 -2
View File
@@ -10,8 +10,7 @@ from seedsigner.models.threads import BaseThread, ThreadsafeCounter
from seedsigner.models.encode_qr import EncodeQR
from seedsigner.models.settings import Settings, SettingsConstants
from ..components import (FontAwesomeIconConstants, GUIConstants, BaseComponent, Button, Icon, IconButton, LargeIconButton, SeedSignerCustomIconConstants, TopNav,
TextArea, load_image)
from ..components import (FontAwesomeIconConstants, GUIConstants, BaseComponent, Button, Icon, IconButton, LargeIconButton, SeedSignerCustomIconConstants, TopNav, TextArea, load_image, ToastOverlay)
from seedsigner.hardware.buttons import HardwareButtonsConstants, HardwareButtons
@@ -1113,3 +1112,38 @@ class KeyboardScreen(BaseTopNavScreen):
self.title = f"Roll {self.cursor_position + 1}"
"""
return False
class MicroSDToastScreen(BaseScreen):
"""
This screen is an overlay with special behavior with the ToastOverlay component. The ToastOverlay component overides all button
input and captures the existing screen content and stashes it to be restored once X second passes or any button is pressed. The
display method on this screen will not complete until after the ToastOverlay render method is complete it's takeover of the screen.
"""
def __init__(self, action):
self.action = action
self.toast = None
super().__init__()
def _run(self):
return
def _render(self):
from seedsigner.hardware.microsd import MicroSD
if self.action == MicroSD.ACTION__REMOVED:
self.toast = ToastOverlay(
icon_name=FontAwesomeIconConstants.SDCARD,
color=GUIConstants.NOTIFICATION_COLOR,
label_text="MicroSD removed"
)
elif self.action == MicroSD.ACTION__INSERTED:
self.toast = ToastOverlay(
icon_name=FontAwesomeIconConstants.SDCARD,
color=GUIConstants.NOTIFICATION_COLOR,
label_text="MicroSD inserted"
)
self.toast.render()
+55
View File
@@ -0,0 +1,55 @@
import time
from seedsigner.models.singleton import Singleton
from seedsigner.models.threads import BaseThread
from seedsigner.models.settings import Settings
#from seedsigner.views.view import MicroSDToastView
from seedsigner.gui.screens.screen import MicroSDToastScreen
class MicroSD(Singleton, BaseThread):
ACTION__INSERTED = "add"
ACTION__REMOVED = "remove"
settings_handler = None
@classmethod
def get_instance(cls):
# This is the only way to access the one and only instance
if cls._instance is None:
# Instantiate the one and only instance
microsd = cls.__new__(cls)
cls._instance = microsd
# explicitly call BaseThread __init__ since multiple class inheritance
BaseThread.__init__(microsd)
return cls._instance
def start_detection(self):
self.start()
def run(self):
import os
FIFO_PATH = "/tmp/mdev_fifo"
FIFO_MODE = 0o600
action = ""
# explicitly only microsd add/remove detection in seedsigner-os
if Settings.HOSTNAME == Settings.SEEDSIGNER_OS:
if os.path.exists(FIFO_PATH):
os.remove(FIFO_PATH)
os.mkfifo(FIFO_PATH, FIFO_MODE)
while self.keep_running:
with open(FIFO_PATH) as fifo:
action = fifo.read()
print(f"fifo message: {action}")
Settings.microsd_handler(action=action)
toastscreen = MicroSDToastScreen(action=action)
toastscreen.display()
+2 -2
View File
@@ -42,12 +42,12 @@ class QR:
else:
border_str = "3"
cmd = f"""qrencode -m {border_str} -s 3 -l L --foreground=000000 --background={background_color} -t PNG -o "/dev/shm/qrcode.png" "{str(data)}" """
cmd = f"""qrencode -m {border_str} -s 3 -l L --foreground=000000 --background={background_color} -t PNG -o "/tmp/qrcode.png" "{str(data)}" """
rv = subprocess.call(cmd, shell=True)
# if qrencode fails, fall back to only encoder
if rv != 0:
return self.qrimage(data,width,height,border)
img = Image.open("/dev/shm/qrcode.png").resize((width,height), Image.NEAREST).convert("RGBA")
img = Image.open("/tmp/qrcode.png").resize((width,height), Image.NEAREST).convert("RGBA")
return img
+35 -6
View File
@@ -4,15 +4,16 @@ import os
from typing import Any, List
from seedsigner.models.settings_definition import SettingsConstants, SettingsDefinition
from .singleton import Singleton
class Settings(Singleton):
SETTINGS_FILENAME = "settings.json"
HOSTNAME = os.uname()[1]
SEEDSIGNER_OS = "seedsigner-os"
SETTINGS_FILENAME = "/mnt/microsd/settings.json" if HOSTNAME == SEEDSIGNER_OS else "settings.json"
@classmethod
def get_instance(cls):
# This is the only way to access the one and only instance
@@ -100,9 +101,12 @@ class Settings(Singleton):
# Special handling for toggling persistence
if attr_name == SettingsConstants.SETTING__PERSISTENT_SETTINGS and value == SettingsConstants.OPTION__DISABLED:
os.remove(self.SETTINGS_FILENAME)
print(f"Removed {self.SETTINGS_FILENAME}")
try:
os.remove(self.SETTINGS_FILENAME)
print(f"Removed {self.SETTINGS_FILENAME}")
except:
print(f"{self.SETTINGS_FILENAME} not found to be removed")
self._data[attr_name] = value
self.save()
@@ -167,3 +171,28 @@ class Settings(Singleton):
return self._data[SettingsConstants.SETTING__DEBUG] == SettingsConstants.OPTION__ENABLED
def microsd_handler(action):
from seedsigner.hardware.microsd import MicroSD
if Settings.HOSTNAME == Settings.SEEDSIGNER_OS:
if action == MicroSD.ACTION__INSERTED:
# restore persistent settings back to defaults
entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__PERSISTENT_SETTINGS)
entry.selection_options = SettingsConstants.OPTIONS__ENABLED_DISABLED
entry.help_text = "Store Settings on SD card."
# if Settings file exists (meaning persistent settings was previously enabled), write out current settings to disk
if os.path.exists(Settings.SETTINGS_FILENAME):
# enable persistent settings first, then save
Settings.get_instance()._data[SettingsConstants.SETTING__PERSISTENT_SETTINGS] = SettingsConstants.OPTION__ENABLED
Settings.get_instance().save()
elif action == MicroSD.ACTION__REMOVED:
# set persistent settings to disabled value directly
Settings.get_instance()._data[SettingsConstants.SETTING__PERSISTENT_SETTINGS] = SettingsConstants.OPTION__DISABLED
# set persistent settings to only have disabled as an option, adding additional help text that microSD is removed
entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__PERSISTENT_SETTINGS)
entry.selection_options = SettingsConstants.OPTIONS__ONLY_DISABLED
entry.help_text = "MicroSD card is removed"
+11 -1
View File
@@ -13,6 +13,9 @@ class SettingsConstants:
(OPTION__ENABLED, "Enabled"),
(OPTION__DISABLED, "Disabled"),
]
OPTIONS__ONLY_DISABLED = [
(OPTION__DISABLED, "Disabled"),
]
OPTIONS__PROMPT_REQUIRED_DISABLED = [
(OPTION__PROMPT, "Prompt"),
(OPTION__REQUIRED, "Required"),
@@ -520,7 +523,14 @@ class SettingsDefinition:
if __name__ == "__main__":
import json
import os
output_file = "settings_definition.json"
hostname = os.uname()[1]
if hostname == "seedsigner-os":
output_file = "/mnt/microsd/settings_definition.json"
else:
output_file = "settings_definition.json"
with open(output_file, 'w') as json_file:
json.dump(SettingsDefinition.to_dict(), json_file, indent=4)
+1 -1
View File
@@ -142,7 +142,7 @@ class ScreensaverScreen(LogoScreen):
with self.renderer.lock:
try:
while True:
if self.buttons.has_any_input():
if self.buttons.has_any_input() or self.buttons.override_ind:
return self.stop()
# Must crop the image to the exact display size
+1 -3
View File
@@ -1,5 +1,6 @@
import hashlib
import os
import time
from embit.descriptor import Descriptor
@@ -53,8 +54,6 @@ class ToolsMenuView(View):
return Destination(ToolsAddressExplorerSelectSourceView)
"""****************************************************************************
Image entropy Views
****************************************************************************"""
@@ -420,7 +419,6 @@ class ToolsCalcFinalWordDoneView(View):
return Destination(SeedDiscardView)
"""****************************************************************************
Address Explorer Views
****************************************************************************"""
+6 -7
View File
@@ -1,7 +1,7 @@
from dataclasses import dataclass
from typing import List
from seedsigner.gui.components import FontAwesomeIconConstants
from seedsigner.gui.components import FontAwesomeIconConstants, GUIConstants
from seedsigner.gui.screens import RET_CODE__POWER_BUTTON
from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON, DireWarningScreen, LargeButtonScreen, PowerOffScreen, ResetScreen, WarningScreen
from seedsigner.models.threads import BaseThread
@@ -188,10 +188,9 @@ class RestartView(View):
# exiting.
time.sleep(0.25)
# Kill the SeedSigner process; systemd will automatically restart it.
# `.*` is a wildcard to detect either `python`` or `python3` and with or
# without the `-u` flag.
call("kill $(ps aux | grep '[p]ython.*main.py' | awk '{print $2}')", shell=True)
# Kill the SeedSigner process; Running the process again.
# `.*` is a wildcard to detect either `python`` or `python3`.
call("kill $(pidof python*) & python /opt/src/main.py", shell=True)
@@ -208,7 +207,7 @@ class PowerOffView(View):
from subprocess import call
while self.keep_running:
time.sleep(5)
call("sudo shutdown --poweroff now", shell=True)
call("poweroff", shell=True)
@@ -243,4 +242,4 @@ class UnhandledExceptionView(View):
allow_text_overflow=True, # Fit what we can, let the rest go off the edges
).display()
return Destination(MainMenuView, clear_history=True)
return Destination(MainMenuView, clear_history=True)