Skip to content

Commit

Permalink
Merge branch 'dev' into seedsigner-icons-v2
Browse files Browse the repository at this point in the history
  • Loading branch information
easyuxd committed Aug 8, 2023
2 parents b16c0ee + 1dcc824 commit d27e9e3
Show file tree
Hide file tree
Showing 30 changed files with 314 additions and 169 deletions.
112 changes: 76 additions & 36 deletions src/seedsigner/controller.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
import time
import logging

import traceback
import os

from embit.descriptor import Descriptor
from embit.psbt import PSBT
from PIL.Image import Image
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

from .models import Seed, SeedStorage, Settings, Singleton, PSBTParser
from seedsigner.models.settings import Settings
from seedsigner.models.singleton import Singleton
from seedsigner.models.threads import BaseThread
from seedsigner.views.view import Destination


logger = logging.getLogger(__name__)



class BackStack(List[Destination]):
class BackStack(list[Destination]):
def __repr__(self):
if len(self) == 0:
return "[]"
Expand All @@ -40,6 +35,7 @@ class StopFlowBasedTest(Exception):
pass



class FlowBasedTestException(Exception):
"""
This is a special exception that is only raised by the test suite.
Expand All @@ -49,6 +45,36 @@ class FlowBasedTestException(Exception):



class BackgroundImportThread(BaseThread):
def run(self):
from importlib import import_module

# import seedsigner.hardware.buttons # slowly imports GPIO along the way

def time_import(module_name):
last = time.time()
import_module(module_name)
# print(time.time() - last, module_name)

time_import('embit')
time_import('seedsigner.helpers.embit_utils')

# Do costly initializations
time_import('seedsigner.models.seed_storage')
from seedsigner.models.seed_storage import SeedStorage
Controller.get_instance()._storage = SeedStorage()

# Get MainMenuView ready to respond quickly
time_import('seedsigner.views.scan_views')

time_import('seedsigner.views.seed_views')

time_import('seedsigner.views.tools_views')

time_import('seedsigner.views.settings_views')



class Controller(Singleton):
"""
The Controller is a globally available singleton that maintains SeedSigner state.
Expand All @@ -71,22 +97,20 @@ class Controller(Singleton):

# Declare class member vars with type hints to enable richer IDE support throughout
# the code.
buttons: HardwareButtons = None
storage: SeedStorage = None
_storage: 'SeedStorage' = None # TODO: Rename "storage" to something more indicative of its temp, in-memory state
settings: Settings = None
renderer: Renderer = None

# TODO: Refactor these flow-related attrs that survive across multiple Screens.
# TODO: Should all in-memory flow-related attrs get wiped on MainMenuView?
psbt: PSBT = None
psbt_seed: Seed = None
psbt_parser: PSBTParser = None
psbt: 'embit.psbt.PSBT' = None
psbt_seed: 'Seed' = None
psbt_parser: 'PSBTParser' = None

unverified_address = None

multisig_wallet_descriptor: Descriptor = None
multisig_wallet_descriptor: 'embit.descriptor.Descriptor' = None

image_entropy_preview_frames: List[Image] = None
image_entropy_preview_frames: list[Image] = None
image_entropy_final_image: Image = None

address_explorer_data: dict = None
Expand All @@ -102,7 +126,7 @@ class Controller(Singleton):
resume_main_flow: str = None

back_stack: BackStack = None
screensaver: ScreensaverScreen = None
screensaver: 'ScreensaverScreen' = None


@classmethod
Expand All @@ -127,6 +151,9 @@ def configure_instance(cls, disable_hardware=False):
each time you try to re-initialize a Controller.
"""
from seedsigner.gui.renderer import Renderer
from seedsigner.hardware.microsd import MicroSD

# Must be called before the first get_instance() call
if cls._instance:
raise Exception("Instance already configured")
Expand All @@ -135,15 +162,7 @@ def configure_instance(cls, disable_hardware=False):
controller = cls.__new__(cls)
cls._instance = controller

# Input Buttons
if disable_hardware:
controller.buttons = None
else:
controller.buttons = HardwareButtons.get_instance()

# models
# 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()
Expand All @@ -156,23 +175,33 @@ def configure_instance(cls, disable_hardware=False):
# Configure the Renderer
Renderer.configure_instance()

controller.screensaver = ScreensaverScreen(controller.buttons)

controller.back_stack = BackStack()

# Other behavior constants
controller.screensaver_activation_ms = 120 * 1000

background_import_thread = BackgroundImportThread()
background_import_thread.start()

return cls._instance


@property
def camera(self):
from .hardware.camera import Camera
return Camera.get_instance()


@property
def storage(self):
while not self._storage:
# Wait for the BackgroundImportThread to finish initializing the storage.
# This is a rare timing issue that likely only occurs in the test suite.
time.sleep(0.001)
return self._storage


def get_seed(self, seed_num: int) -> Seed:
def get_seed(self, seed_num: int) -> 'Seed':
if seed_num < len(self.storage.seeds):
return self.storage.seeds[seed_num]
else:
Expand All @@ -187,7 +216,6 @@ def discard_seed(self, seed_num: int):


def pop_prev_from_back_stack(self):
from .views import Destination
if len(self.back_stack) > 0:
# Pop the top View (which is the current View_cls)
self.back_stack.pop()
Expand All @@ -212,8 +240,7 @@ def start(self, initial_destination: Destination = None) -> None:
from .views import MainMenuView, BackStackView
from .views.screensaver import OpeningSplashScreen

opening_splash = OpeningSplashScreen()
opening_splash.start()
OpeningSplashScreen().start()

""" Class references can be stored as variables in python!
Expand Down Expand Up @@ -288,6 +315,7 @@ def run(self):

if not next_destination:
# Should only happen during dev when you hit an unimplemented option
from seedsigner.views.view import NotYetImplementedView
next_destination = Destination(NotYetImplementedView)

if next_destination.skip_current_view:
Expand Down Expand Up @@ -320,15 +348,26 @@ def run(self):
print("-" * 30)

finally:
if self.screensaver.is_running:
from seedsigner.gui.renderer import Renderer
if self.is_screensaver_running:
self.screensaver.stop()

# Clear the screen when exiting
print("Clearing screen, exiting")
Renderer.get_instance().display_blank_screen()


@property
def is_screensaver_running(self):
return self.screensaver is not None and self.screensaver.is_running


def start_screensaver(self):
if not self.screensaver:
# Do a lazy/late import and instantiation to reduce Controller initial startup time
from seedsigner.views.screensaver import ScreensaverScreen
from seedsigner.hardware.buttons import HardwareButtons
self.screensaver = ScreensaverScreen(HardwareButtons.get_instance())
self.screensaver.start()


Expand All @@ -342,6 +381,7 @@ def handle_exception(self, e) -> Destination:
* python file, line num, method name
* Exception message
"""
from seedsigner.views.view import UnhandledExceptionView
logger.exception(e)

# The final exception output line is:
Expand Down
12 changes: 8 additions & 4 deletions src/seedsigner/gui/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from PIL import Image, ImageDraw, ImageFont, ImageFilter
from typing import List, Tuple

from seedsigner.models import Singleton
from seedsigner.models.settings import Settings
from seedsigner.models.settings_definition import SettingsConstants
from seedsigner.models.singleton import Singleton


# TODO: Remove all pixel hard coding
Expand Down Expand Up @@ -625,13 +625,15 @@ def __post_init__(self):
def render(self):
import time
from seedsigner.controller import Controller
from seedsigner.hardware.buttons import HardwareButtons

self.controller: Controller = Controller.get_instance()
self.current_screen = self.renderer.canvas.copy()
buttons = HardwareButtons.get_instance()

# Special case when screensaver is running
if self.controller.screensaver._is_running:
self.controller.buttons.override_ind = True
if self.controller.is_screensaver_running:
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),
Expand All @@ -648,11 +650,13 @@ def render(self):

t_end = time.time() + 3
while time.time() < t_end:
if self.controller.buttons.has_any_input():
if buttons.has_any_input():
break

self.renderer.show_image(self.current_screen)



@dataclass
class FormattedAddress(BaseComponent):
"""
Expand Down
6 changes: 2 additions & 4 deletions src/seedsigner/gui/renderer.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from PIL import Image, ImageDraw, ImageFont
from PIL import Image, ImageDraw
from threading import Lock

from seedsigner.gui.components import Fonts, GUIConstants
from seedsigner.hardware.ST7789 import ST7789
from seedsigner.models import ConfigurableSingleton
from seedsigner.models.singleton import ConfigurableSingleton



Expand All @@ -19,8 +19,6 @@ class Renderer(ConfigurableSingleton):

@classmethod
def configure_instance(cls):
from seedsigner.models.settings import Settings

# Instantiate the one and only Renderer instance
renderer = cls.__new__(cls)
cls._instance = renderer
Expand Down
6 changes: 4 additions & 2 deletions src/seedsigner/gui/screens/scan_screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from seedsigner.gui import renderer
from seedsigner.hardware.buttons import HardwareButtonsConstants
from seedsigner.hardware.camera import Camera
from seedsigner.models import DecodeQR, DecodeQRStatus
from seedsigner.models.decode_qr import DecodeQR, DecodeQRStatus
from seedsigner.models.threads import BaseThread

from .screen import BaseScreen, ButtonListScreen
Expand Down Expand Up @@ -42,7 +42,7 @@ class ScanScreen(BaseScreen):
this should probably be refactored into the Controller.
"""
decoder: DecodeQR = None
instructions_text: str = "< back | Scan a QR code"
instructions_text: str = None
resolution: tuple[int,int] = (480, 480)
framerate: int = 6 # TODO: alternate optimization for Pi Zero 2W?
render_rect: tuple[int,int,int,int] = None
Expand All @@ -53,6 +53,8 @@ def __post_init__(self):
# Initialize the base class
super().__post_init__()

self.instructions_text = "< back | " + self.instructions_text

self.camera = Camera.get_instance()
self.camera.start_video_stream_mode(resolution=self.resolution, framerate=self.framerate, format="rgb")

Expand Down
5 changes: 2 additions & 3 deletions src/seedsigner/gui/screens/screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from seedsigner.gui.renderer import Renderer

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

from ..components import (FontAwesomeIconConstants, GUIConstants, BaseComponent, Button, Icon, IconButton,
Expand Down Expand Up @@ -660,10 +659,10 @@ def swap_selected_button(new_selected_button: int):

@dataclass
class QRDisplayScreen(BaseScreen):
qr_encoder: EncodeQR = None
qr_encoder: 'EncodeQR' = None

class QRDisplayThread(BaseThread):
def __init__(self, qr_encoder: EncodeQR, qr_brightness: ThreadsafeCounter, renderer: Renderer,
def __init__(self, qr_encoder: 'EncodeQR', qr_brightness: ThreadsafeCounter, renderer: Renderer,
tips_start_time: ThreadsafeCounter):
super().__init__()
self.qr_encoder = qr_encoder
Expand Down
6 changes: 1 addition & 5 deletions src/seedsigner/gui/screens/seed_screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@
import time

from dataclasses import dataclass
from typing import List, Tuple
from typing import List

from PIL import Image, ImageDraw, ImageFilter
from seedsigner.gui.renderer import Renderer
from seedsigner.helpers.qr import QR
from seedsigner.models.qr_type import QRType
from seedsigner.models.threads import BaseThread, ThreadsafeCounter

from seedsigner.models.seed import Seed
from seedsigner.models.settings_definition import SettingsConstants, SettingsDefinition

from .screen import RET_CODE__BACK_BUTTON, BaseScreen, BaseTopNavScreen, ButtonListScreen, KeyboardScreen, WarningEdgesMixin
from ..components import (Button, FontAwesomeIconConstants, Fonts, FormattedAddress, IconButton,
IconTextLine, SeedSignerIconConstants, TextArea, GUIConstants,
Expand Down
2 changes: 1 addition & 1 deletion src/seedsigner/hardware/buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def wait_for(self, keys=[], check_release=True, release_keys=[]) -> int:

while True:
cur_time = int(time.time() * 1000)
if cur_time - self.last_input_time > controller.screensaver_activation_ms and not controller.screensaver.is_running:
if cur_time - self.last_input_time > controller.screensaver_activation_ms and not controller.is_screensaver_running:
# Start the screensaver. Will block execution until input detected.
controller.start_screensaver()

Expand Down
Loading

0 comments on commit d27e9e3

Please sign in to comment.