mirror of
https://github.com/SeedSigner/seedsigner.git
synced 2026-10-05 15:08:25 +00:00
Work in progress after merge w/main; Lots of breaking changes in Settings
This commit is contained in:
+3
-3
@@ -2,9 +2,9 @@ embit==0.4.10
|
|||||||
numpy==1.21.1
|
numpy==1.21.1
|
||||||
picamera==1.13
|
picamera==1.13
|
||||||
Pillow==8.2.0
|
Pillow==8.2.0
|
||||||
pyzbar==0.1.8
|
-e git+https://github.com/kdmukai/pyzbar.git@c3c237821c6a20b17953efe59b90df0b514a1c03#egg=pyzbar
|
||||||
qrcode==6.1
|
qrcode==7.3.1
|
||||||
RPi.GPIO==0.7.0
|
RPi.GPIO==0.7.0
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
spidev==3.5
|
spidev==3.5
|
||||||
-e src/vendor/urtypes
|
urtypes @ git+https://github.com/jreesun/urtypes.git@e0d0db277ec2339650343eaf7b220fffb9233241
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from picamera import PiCamera
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
from seedsigner.models import Singleton
|
from seedsigner.models import Singleton
|
||||||
from seedsigner.helpers import PiVideoStream
|
from seedsigner.helpers import PiVideoStream
|
||||||
|
from seedsigner.models.settings import SettingsConstants
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ class Camera(Singleton):
|
|||||||
from seedsigner.models import Settings
|
from seedsigner.models import Settings
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = cls.__new__(cls)
|
cls._instance = cls.__new__(cls)
|
||||||
cls._instance._camera_rotation = Settings.get_instance().camera_rotation
|
cls._instance._camera_rotation = Settings.get_instance().get_value(SettingsConstants.SETTING__CAMERA_ROTATION)
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,7 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import re
|
|
||||||
from multiprocessing import Process, Queue
|
|
||||||
from subprocess import call
|
|
||||||
import os, sys
|
|
||||||
from embit import bip32, script, ec
|
|
||||||
from embit.networks import NETWORKS
|
|
||||||
from embit.descriptor import Descriptor
|
|
||||||
from binascii import hexlify
|
|
||||||
from threading import Thread
|
|
||||||
|
|
||||||
from embit.psbt import PSBT
|
from embit.psbt import PSBT
|
||||||
from seedsigner.gui.renderer import Renderer
|
from seedsigner.gui.renderer import Renderer
|
||||||
|
|
||||||
from .models import (EncodeQRDensity, Seed, SeedStorage, Settings,
|
from .models import Seed, SeedStorage, Settings, Singleton, PSBTParser
|
||||||
Singleton, DecodeQR, DecodeQRStatus, EncodeQR, PSBTParser)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -38,7 +24,7 @@ class Controller(Singleton):
|
|||||||
"""
|
"""
|
||||||
from .helpers import Buttons
|
from .helpers import Buttons
|
||||||
|
|
||||||
VERSION = "0.5.0"
|
VERSION = "0.5.0 prerelease 1"
|
||||||
|
|
||||||
# Declare class member vars with type hints to enable richer IDE support throughout
|
# Declare class member vars with type hints to enable richer IDE support throughout
|
||||||
# the code.
|
# the code.
|
||||||
@@ -67,30 +53,16 @@ class Controller(Singleton):
|
|||||||
# models
|
# models
|
||||||
# TODO: Rename "storage" to something more indicative of its temp, in-memory state
|
# TODO: Rename "storage" to something more indicative of its temp, in-memory state
|
||||||
controller.storage = SeedStorage()
|
controller.storage = SeedStorage()
|
||||||
|
|
||||||
controller.settings = Settings.get_instance()
|
controller.settings = Settings.get_instance()
|
||||||
|
|
||||||
# Store one working psbt in memory
|
# Store one working psbt in memory
|
||||||
controller.psbt = None
|
controller.psbt = None
|
||||||
controller.psbt_parser = None
|
controller.psbt_parser = None
|
||||||
|
|
||||||
# settings
|
|
||||||
# TODO: Remove these
|
|
||||||
controller.DEBUG = controller.settings.debug
|
|
||||||
controller.color = controller.settings.text_color
|
|
||||||
controller.current_bg_qr_color = controller.settings.qr_background_color
|
|
||||||
|
|
||||||
# Configure the Renderer
|
# Configure the Renderer
|
||||||
Renderer.configure_instance({"text_color": controller.color})
|
Renderer.configure_instance()
|
||||||
|
|
||||||
# TODO: Refactor so that we don't need the Renderer here
|
|
||||||
controller.renderer = Renderer.get_instance()
|
|
||||||
|
|
||||||
# Views
|
|
||||||
# controller.menu_view = MenuView()
|
|
||||||
# controller.seed_tools_view = SeedToolsView()
|
|
||||||
# controller.io_test_view = IOTestView()
|
|
||||||
# controller.signing_tools_view = SigningToolsView(controller.storage)
|
|
||||||
# controller.settings_tools_view = SettingsToolsView()
|
|
||||||
controller.screensaver = ScreensaverView(controller.buttons)
|
controller.screensaver = ScreensaverView(controller.buttons)
|
||||||
|
|
||||||
controller.back_stack = []
|
controller.back_stack = []
|
||||||
@@ -134,8 +106,8 @@ class Controller(Singleton):
|
|||||||
from .views import View, Destination, OpeningSplashView, MainMenuView, BackStackView
|
from .views import View, Destination, OpeningSplashView, MainMenuView, BackStackView
|
||||||
|
|
||||||
# Disabled to save some time during dev...
|
# Disabled to save some time during dev...
|
||||||
# opening_splash = OpeningSplashView()
|
opening_splash = OpeningSplashView()
|
||||||
# opening_splash.start()
|
opening_splash.start()
|
||||||
|
|
||||||
""" Class references can be stored as variables in python!
|
""" Class references can be stored as variables in python!
|
||||||
|
|
||||||
@@ -202,13 +174,14 @@ class Controller(Singleton):
|
|||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Clear the screen when exiting
|
# Clear the screen when exiting
|
||||||
self.renderer.display_blank_screen()
|
Renderer.get_instance().display_blank_screen()
|
||||||
|
|
||||||
|
|
||||||
def start_screensaver(self):
|
def start_screensaver(self):
|
||||||
self.screensaver.start()
|
self.screensaver.start()
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
### Menu
|
### Menu
|
||||||
### Menu View handles navigation within the menu
|
### Menu View handles navigation within the menu
|
||||||
### Sub Menu's like Seed Tools, Signing Tools, Settings are all in the Menu View
|
### Sub Menu's like Seed Tools, Signing Tools, Settings are all in the Menu View
|
||||||
@@ -965,7 +938,7 @@ class Controller(Singleton):
|
|||||||
|
|
||||||
def show_qr_density_tool(self):
|
def show_qr_density_tool(self):
|
||||||
r = self.settings_tools_view.display_qr_density_selection()
|
r = self.settings_tools_view.display_qr_density_selection()
|
||||||
if r in (EncodeQRDensity.LOW, EncodeQRDensity.MEDIUM, EncodeQRDensity.HIGH):
|
if r in (EncodeQR.DENSITY__LOW, EncodeQR.DENSITY__MEDIUM, EncodeQR.DENSITY__HIGH):
|
||||||
self.settings.qr_density = r
|
self.settings.qr_density = r
|
||||||
|
|
||||||
return Path.SETTINGS_SUB_MENU
|
return Path.SETTINGS_SUB_MENU
|
||||||
@@ -1084,3 +1057,4 @@ class Controller(Singleton):
|
|||||||
|
|
||||||
return Path.MAIN_MENU
|
return Path.MAIN_MENU
|
||||||
|
|
||||||
|
"""
|
||||||
@@ -18,25 +18,25 @@ class Renderer(ConfigurableSingleton):
|
|||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configure_instance(cls, config=None):
|
def configure_instance(cls, config={}):
|
||||||
|
from seedsigner.models.settings import Settings
|
||||||
super().configure_instance(config)
|
super().configure_instance(config)
|
||||||
|
|
||||||
# Instantiate the one and only Renderer instance
|
# Instantiate the one and only Renderer instance
|
||||||
renderer = cls.__new__(cls)
|
renderer = cls.__new__(cls)
|
||||||
cls._instance = renderer
|
cls._instance = renderer
|
||||||
|
|
||||||
renderer.color = config["text_color"]
|
# TODO: Use Settings values to wire up diff hardware params
|
||||||
|
settings = Settings.get_instance()
|
||||||
|
|
||||||
# Eventually we'll be able to plug in other display controllers
|
# Eventually we'll be able to plug in other display controllers
|
||||||
renderer.disp = ST7789()
|
renderer.disp = ST7789()
|
||||||
|
|
||||||
renderer.canvas_width = renderer.disp.width
|
renderer.canvas_width = renderer.disp.width
|
||||||
renderer.canvas_height = renderer.disp.height
|
renderer.canvas_height = renderer.disp.height
|
||||||
|
|
||||||
renderer.canvas = Image.new('RGB', (renderer.canvas_width, renderer.canvas_height))
|
renderer.canvas = Image.new('RGB', (renderer.canvas_width, renderer.canvas_height))
|
||||||
renderer.draw = ImageDraw.Draw(renderer.canvas)
|
renderer.draw = ImageDraw.Draw(renderer.canvas)
|
||||||
|
|
||||||
draw = ImageDraw.Draw(renderer.canvas)
|
|
||||||
|
|
||||||
|
|
||||||
def show_image(self, image=None, alpha_overlay=None):
|
def show_image(self, image=None, alpha_overlay=None):
|
||||||
if image == None:
|
if image == None:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import time
|
|||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from PIL import Image, ImageDraw, ImageColor
|
from PIL import Image, ImageDraw, ImageColor
|
||||||
from typing import List, Tuple
|
from typing import Any, List, Tuple
|
||||||
from seedsigner.gui.renderer import Renderer
|
from seedsigner.gui.renderer import Renderer
|
||||||
|
|
||||||
from seedsigner.helpers.threads import BaseThread
|
from seedsigner.helpers.threads import BaseThread
|
||||||
@@ -41,7 +41,7 @@ class BaseScreen(BaseComponent):
|
|||||||
self.paste_images: List[Tuple] = []
|
self.paste_images: List[Tuple] = []
|
||||||
|
|
||||||
|
|
||||||
def display(self):
|
def display(self) -> Any:
|
||||||
try:
|
try:
|
||||||
with self.renderer.lock:
|
with self.renderer.lock:
|
||||||
self._render()
|
self._render()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from seedsigner.models.settings import SettingsConstants
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SettingsEntryUpdateScreen(ButtonListScreen):
|
class SettingsEntryUpdateSelectionScreen(ButtonListScreen):
|
||||||
display_name: str = None
|
display_name: str = None
|
||||||
help_text: str = None
|
help_text: str = None
|
||||||
checked_buttons: List[int] = None
|
checked_buttons: List[int] = None
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from .singleton import Singleton, ConfigurableSingleton
|
|||||||
|
|
||||||
from .seed import *
|
from .seed import *
|
||||||
from .qr_type import *
|
from .qr_type import *
|
||||||
from .encode_qr_density import *
|
|
||||||
from .decode_qr import *
|
from .decode_qr import *
|
||||||
from .encode_qr import *
|
from .encode_qr import *
|
||||||
from .psbt_parser import *
|
from .psbt_parser import *
|
||||||
|
|||||||
@@ -698,6 +698,7 @@ class SeedQR:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Refactor this to work with the new SettingsDefinition
|
||||||
class SettingsQR:
|
class SettingsQR:
|
||||||
def __init__(self, wordlist=None):
|
def __init__(self, wordlist=None):
|
||||||
self.total_segments = 1
|
self.total_segments = 1
|
||||||
|
|||||||
@@ -7,18 +7,22 @@ from seedsigner.helpers.ur2.cbor_lite import CBOREncoder
|
|||||||
from seedsigner.helpers.ur2.ur import UR
|
from seedsigner.helpers.ur2.ur import UR
|
||||||
from seedsigner.helpers.bcur import (bc32encode, cbor_encode, bcur_encode)
|
from seedsigner.helpers.bcur import (bc32encode, cbor_encode, bcur_encode)
|
||||||
from seedsigner.helpers.qr import QR
|
from seedsigner.helpers.qr import QR
|
||||||
from seedsigner.models import Seed, QRType, EncodeQRDensity
|
from seedsigner.models import Seed, QRType
|
||||||
|
|
||||||
from urtypes.crypto import PSBT as UR_PSBT
|
from urtypes.crypto import PSBT as UR_PSBT
|
||||||
from urtypes.crypto import Account, HDKey, Output, Keypath, PathComponent, SCRIPT_EXPRESSION_TAG_MAP
|
from urtypes.crypto import Account, HDKey, Output, Keypath, PathComponent, SCRIPT_EXPRESSION_TAG_MAP
|
||||||
|
|
||||||
###
|
|
||||||
### EncodeQR Class
|
|
||||||
### Purpose: used to encode psbt for displaying as qr image
|
|
||||||
###
|
|
||||||
|
|
||||||
class EncodeQR:
|
class EncodeQR:
|
||||||
|
"""
|
||||||
|
Encode psbt for displaying as qr image
|
||||||
|
"""
|
||||||
|
DENSITY__LOW = "Low"
|
||||||
|
DENSITY__MEDIUM = "Medium"
|
||||||
|
DENSITY__HIGH = "High"
|
||||||
|
ALL_DENSITIES = [DENSITY__LOW, DENSITY__MEDIUM, DENSITY__HIGH]
|
||||||
|
|
||||||
WORDLIST = None
|
WORDLIST = None
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@@ -57,7 +61,7 @@ class EncodeQR:
|
|||||||
raise Exception('Encoder Type Required')
|
raise Exception('Encoder Type Required')
|
||||||
|
|
||||||
if self.qr_density == None:
|
if self.qr_density == None:
|
||||||
self.qr_density = EncodeQRDensity.MEDIUM
|
self.qr_density = EncodeQR.DENSITY__MEDIUM
|
||||||
|
|
||||||
self.encoder = None
|
self.encoder = None
|
||||||
if self.qr_type == QRType.PSBTSPECTER:
|
if self.qr_type == QRType.PSBTSPECTER:
|
||||||
@@ -108,11 +112,11 @@ class UREncodePSBTQR:
|
|||||||
|
|
||||||
qr_ur_bytes = UR("crypto-psbt", UR_PSBT(self.psbt.serialize()).to_cbor())
|
qr_ur_bytes = UR("crypto-psbt", UR_PSBT(self.psbt.serialize()).to_cbor())
|
||||||
|
|
||||||
if qr_density == EncodeQRDensity.LOW:
|
if qr_density == EncodeQR.DENSITY__LOW:
|
||||||
self.qr_max_fragement_size = 10
|
self.qr_max_fragement_size = 10
|
||||||
elif qr_density == EncodeQRDensity.MEDIUM:
|
elif qr_density == EncodeQR.DENSITY__MEDIUM:
|
||||||
self.qr_max_fragement_size = 30
|
self.qr_max_fragement_size = 30
|
||||||
elif qr_density == EncodeQRDensity.HIGH:
|
elif qr_density == EncodeQR.DENSITY__HIGH:
|
||||||
self.qr_max_fragement_size = 120
|
self.qr_max_fragement_size = 120
|
||||||
|
|
||||||
self.ur2_encode = UREncoder(qr_ur_bytes,self.qr_max_fragement_size,0)
|
self.ur2_encode = UREncoder(qr_ur_bytes,self.qr_max_fragement_size,0)
|
||||||
@@ -135,11 +139,11 @@ class SpecterEncodePSBTQR:
|
|||||||
self.part_num_sent = 0
|
self.part_num_sent = 0
|
||||||
self.sent_complete = False
|
self.sent_complete = False
|
||||||
|
|
||||||
if qr_density == EncodeQRDensity.LOW:
|
if qr_density == EncodeQR.DENSITY__LOW:
|
||||||
self.qr_max_fragement_size = 40
|
self.qr_max_fragement_size = 40
|
||||||
elif qr_density == EncodeQRDensity.MEDIUM:
|
elif qr_density == EncodeQR.DENSITY__MEDIUM:
|
||||||
self.qr_max_fragement_size = 65
|
self.qr_max_fragement_size = 65
|
||||||
elif qr_density == EncodeQRDensity.HIGH:
|
elif qr_density == EncodeQR.DENSITY__HIGH:
|
||||||
self.qr_max_fragement_size = 90
|
self.qr_max_fragement_size = 90
|
||||||
|
|
||||||
self.__createParts()
|
self.__createParts()
|
||||||
@@ -263,11 +267,11 @@ class SpecterXPubQR(XPubQR):
|
|||||||
|
|
||||||
def __init__(self, seed_phrase, passphrase, derivation, network, qr_density, wordlist):
|
def __init__(self, seed_phrase, passphrase, derivation, network, qr_density, wordlist):
|
||||||
self.qr_max_fragement_size = 65
|
self.qr_max_fragement_size = 65
|
||||||
if qr_density == EncodeQRDensity.LOW:
|
if qr_density == EncodeQR.DENSITY__LOW:
|
||||||
self.qr_max_fragement_size = 40
|
self.qr_max_fragement_size = 40
|
||||||
elif qr_density == EncodeQRDensity.MEDIUM:
|
elif qr_density == EncodeQR.DENSITY__MEDIUM:
|
||||||
self.qr_max_fragement_size = 65
|
self.qr_max_fragement_size = 65
|
||||||
elif qr_density == EncodeQRDensity.HIGH:
|
elif qr_density == EncodeQR.DENSITY__HIGH:
|
||||||
self.qr_max_fragement_size = 90
|
self.qr_max_fragement_size = 90
|
||||||
|
|
||||||
XPubQR.__init__(self, seed_phrase, passphrase, derivation, network, wordlist)
|
XPubQR.__init__(self, seed_phrase, passphrase, derivation, network, wordlist)
|
||||||
@@ -323,11 +327,11 @@ class URXPubQR(XPubQR):
|
|||||||
|
|
||||||
XPubQR.__init__(self, seed_phrase, passphrase, derivation, network, wordlist)
|
XPubQR.__init__(self, seed_phrase, passphrase, derivation, network, wordlist)
|
||||||
|
|
||||||
if qr_density == EncodeQRDensity.LOW:
|
if qr_density == EncodeQR.DENSITY__LOW:
|
||||||
self.qr_max_fragement_size = 10
|
self.qr_max_fragement_size = 10
|
||||||
elif qr_density == EncodeQRDensity.MEDIUM:
|
elif qr_density == EncodeQR.DENSITY__MEDIUM:
|
||||||
self.qr_max_fragement_size = 30
|
self.qr_max_fragement_size = 30
|
||||||
elif qr_density == EncodeQRDensity.HIGH:
|
elif qr_density == EncodeQR.DENSITY__HIGH:
|
||||||
self.qr_max_fragement_size = 120
|
self.qr_max_fragement_size = 120
|
||||||
|
|
||||||
def derivation_to_keypath(path: str) -> list:
|
def derivation_to_keypath(path: str) -> list:
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
from enum import IntEnum
|
|
||||||
|
|
||||||
###
|
|
||||||
### QRType Class IntEum
|
|
||||||
### Purpose: used with DecodeQR and EncodeQR to communicate qr encoding type
|
|
||||||
###
|
|
||||||
|
|
||||||
class EncodeQRDensity(IntEnum):
|
|
||||||
LOW = 1
|
|
||||||
MEDIUM = 2
|
|
||||||
HIGH = 3
|
|
||||||
@@ -5,6 +5,7 @@ from embit.psbt import PSBT
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import List
|
from typing import List
|
||||||
from seedsigner.models import Seed
|
from seedsigner.models import Seed
|
||||||
|
from seedsigner.models.seed import SeedConstants
|
||||||
|
|
||||||
class PSBTParser():
|
class PSBTParser():
|
||||||
|
|
||||||
@@ -178,7 +179,40 @@ class PSBTParser():
|
|||||||
|
|
||||||
return cnt
|
return cnt
|
||||||
|
|
||||||
# checks that all inputs are from the same wallet
|
|
||||||
|
@staticmethod
|
||||||
|
def calc_derivation(network, wallet_type, script_type):
|
||||||
|
# TODO: Move this to Seed?
|
||||||
|
if network == SeedConstants.MAINNET:
|
||||||
|
network_path = "0'"
|
||||||
|
elif network == SeedConstants.TESTNET:
|
||||||
|
network_path = "1'"
|
||||||
|
else:
|
||||||
|
raise Exception("Unexpected network")
|
||||||
|
|
||||||
|
if wallet_type == SeedConstants.SINGLE_SIG:
|
||||||
|
if script_type == SeedConstants.NATIVE_SEGWIT:
|
||||||
|
return f"m/84'/{network_path}/0'"
|
||||||
|
elif script_type == SeedConstants.NESTED_SEGWIT:
|
||||||
|
return f"m/49'/{network_path}/0'"
|
||||||
|
elif script_type == SeedConstants.TAPROOT:
|
||||||
|
return f"m/86'/{network_path}/0'"
|
||||||
|
else:
|
||||||
|
raise Exception("Unexpected script type")
|
||||||
|
|
||||||
|
elif wallet_type == SeedConstants.MULTISIG:
|
||||||
|
if script_type == SeedConstants.NATIVE_SEGWIT:
|
||||||
|
return f"m/48'/{network_path}/0'/2'"
|
||||||
|
elif script_type == SeedConstants.NESTED_SEGWIT:
|
||||||
|
return f"m/48'/{network_path}/0'/1'"
|
||||||
|
elif script_type == SeedConstants.TAPROOT:
|
||||||
|
raise Exception("Taproot multisig/musig not yet supported")
|
||||||
|
else:
|
||||||
|
raise Exception("Unexpected script type")
|
||||||
|
else:
|
||||||
|
raise Exception("Unexpected wallet type") # checks that all inputs are from the same wallet
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_policy(scope, scriptpubkey, xpubs):
|
def __get_policy(scope, scriptpubkey, xpubs):
|
||||||
"""Parse scope and get policy"""
|
"""Parse scope and get policy"""
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ from typing import List
|
|||||||
|
|
||||||
|
|
||||||
class SeedConstants:
|
class SeedConstants:
|
||||||
MAINNET = "main"
|
MAINNET = ("main", "Mainnet")
|
||||||
TESTNET = "test"
|
TESTNET = ("test", "Testnet")
|
||||||
REGTEST = "regtest"
|
REGTEST = ("regtest", "Regtest")
|
||||||
|
ALL_NETWORKS = [MAINNET, TESTNET, REGTEST]
|
||||||
|
|
||||||
SINGLE_SIG = "single_sig"
|
SINGLE_SIG = "single_sig"
|
||||||
MULTISIG = "multisig"
|
MULTISIG = "multisig"
|
||||||
|
|||||||
+133
-173
@@ -3,12 +3,12 @@ import json
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from embit import bip39
|
from embit import bip39
|
||||||
from typing import List
|
from typing import Any, List
|
||||||
|
|
||||||
from .seed import SeedConstants
|
from .seed import SeedConstants
|
||||||
from .singleton import Singleton
|
from .singleton import Singleton
|
||||||
from .qr_type import QRType
|
from .qr_type import QRType
|
||||||
from .encode_qr_density import EncodeQRDensity
|
from .encode_qr import EncodeQR
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -17,7 +17,6 @@ class SettingsConstants:
|
|||||||
COORDINATOR__BLUE_WALLET = "BlueWallet"
|
COORDINATOR__BLUE_WALLET = "BlueWallet"
|
||||||
COORDINATOR__SPARROW = "Sparrow"
|
COORDINATOR__SPARROW = "Sparrow"
|
||||||
COORDINATOR__SPECTER_DESKTOP = "Specter Desktop"
|
COORDINATOR__SPECTER_DESKTOP = "Specter Desktop"
|
||||||
|
|
||||||
ALL_COORDINATORS = [
|
ALL_COORDINATORS = [
|
||||||
COORDINATOR__BLUE_WALLET,
|
COORDINATOR__BLUE_WALLET,
|
||||||
COORDINATOR__SPARROW,
|
COORDINATOR__SPARROW,
|
||||||
@@ -28,7 +27,6 @@ class SettingsConstants:
|
|||||||
OPTION__DISABLED = "Disabled"
|
OPTION__DISABLED = "Disabled"
|
||||||
OPTION__PROMPT = "Prompt"
|
OPTION__PROMPT = "Prompt"
|
||||||
OPTION__REQUIRED = "Required"
|
OPTION__REQUIRED = "Required"
|
||||||
|
|
||||||
ALL_OPTIONS = [
|
ALL_OPTIONS = [
|
||||||
OPTION__ENABLED,
|
OPTION__ENABLED,
|
||||||
OPTION__DISABLED,
|
OPTION__DISABLED,
|
||||||
@@ -36,18 +34,28 @@ class SettingsConstants:
|
|||||||
OPTION__REQUIRED,
|
OPTION__REQUIRED,
|
||||||
]
|
]
|
||||||
|
|
||||||
LANGUAGE__ENGLISH = "English"
|
LANGUAGE__ENGLISH = ("en", "English")
|
||||||
|
|
||||||
ALL_LANGUAGES = [
|
ALL_LANGUAGES = [
|
||||||
LANGUAGE__ENGLISH,
|
LANGUAGE__ENGLISH,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ROTATION__0 = (0, "0°")
|
||||||
|
ROTATION__90 = (90, "90°")
|
||||||
|
ROTATION__180 = (180, "180°")
|
||||||
|
ROTATION__270 = (270, "270°")
|
||||||
|
ALL_ROTATIONS = [
|
||||||
|
ROTATION__0, ROTATION__90, ROTATION__180, ROTATION__270
|
||||||
|
]
|
||||||
|
|
||||||
# Individual settings entry attr_names
|
# Individual settings entry attr_names
|
||||||
SETTING__LANGUAGE = "language"
|
SETTING__LANGUAGE = "language"
|
||||||
SETTING__PERSISTENT_SETTINGS = "persistent_settings"
|
SETTING__PERSISTENT_SETTINGS = "persistent_settings"
|
||||||
SETTING__COORDINATORS = "coordinators"
|
SETTING__COORDINATORS = "coordinators"
|
||||||
|
|
||||||
|
SETTING__NETWORK = "network"
|
||||||
|
SETTING__QR_DENSITY = "qr_density"
|
||||||
|
SETTING__XPUB_EXPORT = "xpub_export"
|
||||||
|
SETTING__CAMERA_ROTATION = "camera_rotation"
|
||||||
SETTING__PRIVACY_WARNINGS = "privacy_warnings"
|
SETTING__PRIVACY_WARNINGS = "privacy_warnings"
|
||||||
SETTING__DIRE_WARNINGS = "dire_warnings"
|
SETTING__DIRE_WARNINGS = "dire_warnings"
|
||||||
|
|
||||||
@@ -93,22 +101,57 @@ class SettingsEntry:
|
|||||||
visibility: str = SettingsConstants.VISIBILITY__GENERAL
|
visibility: str = SettingsConstants.VISIBILITY__GENERAL
|
||||||
type: str = SettingsConstants.TYPE__ENABLED_DISABLED
|
type: str = SettingsConstants.TYPE__ENABLED_DISABLED
|
||||||
help_text: str = None
|
help_text: str = None
|
||||||
possible_values: List[str] = None
|
selection_options: List[str] = None
|
||||||
possible_values_abbreviated: List[str] = None
|
selection_options_abbreviated: List[str] = None
|
||||||
default_value: str = None
|
default_value: str = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if self.type == SettingsConstants.TYPE__ENABLED_DISABLED:
|
if self.type == SettingsConstants.TYPE__ENABLED_DISABLED:
|
||||||
self.possible_values = [SettingsConstants.OPTION__ENABLED,
|
self.selection_options = [SettingsConstants.OPTION__ENABLED,
|
||||||
SettingsConstants.OPTION__DISABLED]
|
SettingsConstants.OPTION__DISABLED]
|
||||||
|
|
||||||
elif self.type == SettingsConstants.TYPE__ENABLED_DISABLED_PROMPT:
|
elif self.type == SettingsConstants.TYPE__ENABLED_DISABLED_PROMPT:
|
||||||
self.possible_values = [SettingsConstants.OPTION__ENABLED,
|
self.selection_options = [SettingsConstants.OPTION__ENABLED,
|
||||||
SettingsConstants.OPTION__DISABLED,
|
SettingsConstants.OPTION__DISABLED,
|
||||||
SettingsConstants.OPTION__PROMPT]
|
SettingsConstants.OPTION__PROMPT]
|
||||||
|
|
||||||
elif self.type == SettingsConstants.TYPE__ENABLED_DISABLED_PROMPT_REQURIED:
|
elif self.type == SettingsConstants.TYPE__ENABLED_DISABLED_PROMPT_REQURIED:
|
||||||
self.possible_values = [SettingsConstants.ALL_OPTIONS]
|
self.selection_options = [SettingsConstants.ALL_OPTIONS]
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selection_options_display_names(self) -> List[str]:
|
||||||
|
if type(self.selection_options[0]) == tuple:
|
||||||
|
return [v[1] for v in self.selection_options]
|
||||||
|
else:
|
||||||
|
# Always return a copy so the original can't be altered
|
||||||
|
return list(self.selection_options)
|
||||||
|
|
||||||
|
|
||||||
|
def get_selection_option_value(self, i: int):
|
||||||
|
value = self.selection_options[i]
|
||||||
|
if type(value) == tuple:
|
||||||
|
value = value[0]
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def get_selection_option_display_name(self, i: int) -> str:
|
||||||
|
value = self.selection_options[i]
|
||||||
|
if type(value) == tuple:
|
||||||
|
value = value[1]
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def get_selection_option_display_name_by_value(self, value) -> str:
|
||||||
|
for option in self.selection_options:
|
||||||
|
if type(option) == tuple:
|
||||||
|
option_value = option[0]
|
||||||
|
display_name = option[1]
|
||||||
|
else:
|
||||||
|
option_value = option
|
||||||
|
display_name = option
|
||||||
|
if option_value == value:
|
||||||
|
return display_name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -131,32 +174,62 @@ class SettingsDefinition:
|
|||||||
attr_name=SettingsConstants.SETTING__LANGUAGE,
|
attr_name=SettingsConstants.SETTING__LANGUAGE,
|
||||||
display_name="Language",
|
display_name="Language",
|
||||||
type=SettingsConstants.TYPE__SELECT_1,
|
type=SettingsConstants.TYPE__SELECT_1,
|
||||||
possible_values=SettingsConstants.ALL_LANGUAGES,
|
selection_options=SettingsConstants.ALL_LANGUAGES,
|
||||||
default_value=SettingsConstants.LANGUAGE__ENGLISH),
|
default_value=SettingsConstants.LANGUAGE__ENGLISH),
|
||||||
|
|
||||||
SettingsEntry(category=SettingsConstants.CATEGORY__SYSTEM,
|
SettingsEntry(category=SettingsConstants.CATEGORY__SYSTEM,
|
||||||
attr_name=SettingsConstants.SETTING__PERSISTENT_SETTINGS,
|
attr_name=SettingsConstants.SETTING__PERSISTENT_SETTINGS,
|
||||||
display_name="Persistent Settings",
|
display_name="Persistent settings",
|
||||||
help_text="Store Settings on SD card",
|
help_text="Store Settings on SD card",
|
||||||
default_value=SettingsConstants.OPTION__DISABLED),
|
default_value=SettingsConstants.OPTION__DISABLED),
|
||||||
|
|
||||||
SettingsEntry(category=SettingsConstants.CATEGORY__WALLET,
|
SettingsEntry(category=SettingsConstants.CATEGORY__WALLET,
|
||||||
attr_name=SettingsConstants.SETTING__COORDINATORS,
|
attr_name=SettingsConstants.SETTING__COORDINATORS,
|
||||||
display_name="Coordinator Software",
|
display_name="Coordinator software",
|
||||||
type=SettingsConstants.TYPE__MULTISELECT,
|
type=SettingsConstants.TYPE__MULTISELECT,
|
||||||
possible_values=SettingsConstants.ALL_COORDINATORS,
|
selection_options=SettingsConstants.ALL_COORDINATORS,
|
||||||
default_value=SettingsConstants.ALL_COORDINATORS),
|
default_value=SettingsConstants.ALL_COORDINATORS),
|
||||||
|
|
||||||
# Advanced options
|
# Advanced options
|
||||||
|
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||||
|
attr_name=SettingsConstants.SETTING__NETWORK,
|
||||||
|
display_name="Bitcoin network",
|
||||||
|
type=SettingsConstants.TYPE__SELECT_1,
|
||||||
|
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||||
|
selection_options=SeedConstants.ALL_NETWORKS,
|
||||||
|
default_value=SeedConstants.MAINNET[0]),
|
||||||
|
|
||||||
|
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||||
|
attr_name=SettingsConstants.SETTING__QR_DENSITY,
|
||||||
|
display_name="QR code density",
|
||||||
|
type=SettingsConstants.TYPE__SELECT_1,
|
||||||
|
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||||
|
selection_options=EncodeQR.ALL_DENSITIES,
|
||||||
|
default_value=EncodeQR.DENSITY__MEDIUM),
|
||||||
|
|
||||||
|
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||||
|
attr_name=SettingsConstants.SETTING__XPUB_EXPORT,
|
||||||
|
display_name="Xpub export",
|
||||||
|
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||||
|
default_value=SettingsConstants.OPTION__ENABLED),
|
||||||
|
|
||||||
|
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||||
|
attr_name=SettingsConstants.SETTING__QR_DENSITY,
|
||||||
|
display_name="QR code density",
|
||||||
|
type=SettingsConstants.TYPE__SELECT_1,
|
||||||
|
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||||
|
selection_options=SettingsConstants.ALL_ROTATIONS,
|
||||||
|
default_value=SettingsConstants.ROTATION__0),
|
||||||
|
|
||||||
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||||
attr_name=SettingsConstants.SETTING__PRIVACY_WARNINGS,
|
attr_name=SettingsConstants.SETTING__PRIVACY_WARNINGS,
|
||||||
display_name="Show Privacy Warnings",
|
display_name="Show privacy warnings",
|
||||||
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||||
default_value=SettingsConstants.OPTION__ENABLED),
|
default_value=SettingsConstants.OPTION__ENABLED),
|
||||||
|
|
||||||
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||||
attr_name=SettingsConstants.SETTING__DIRE_WARNINGS,
|
attr_name=SettingsConstants.SETTING__DIRE_WARNINGS,
|
||||||
display_name="Show Dire Warnings",
|
display_name="Show dire warnings",
|
||||||
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||||
default_value=SettingsConstants.OPTION__ENABLED),
|
default_value=SettingsConstants.OPTION__ENABLED),
|
||||||
|
|
||||||
@@ -187,7 +260,7 @@ class SettingsDefinition:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_abbreviated_ini(cls, abbreviated_ini: str) -> dict:
|
def parse_abbreviated_ini(cls, abbreviated_ini: str) -> dict:
|
||||||
return False
|
raise Exception("Not implemented, maybe not needed")
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -205,12 +278,14 @@ class SettingsDefinition:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def to_json(cls):
|
def to_json(cls):
|
||||||
return json.dumps({})
|
raise Exception("Not implemented")
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def to_html(cls):
|
def to_html(cls):
|
||||||
return ""
|
raise Exception("Not implemented, maybe not needed")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(Singleton):
|
class Settings(Singleton):
|
||||||
SETTINGS_FILENAME = "settings.json"
|
SETTINGS_FILENAME = "settings.json"
|
||||||
@@ -240,7 +315,7 @@ class Settings(Singleton):
|
|||||||
# "wallet": {
|
# "wallet": {
|
||||||
# "network": SeedConstants.MAINNET,
|
# "network": SeedConstants.MAINNET,
|
||||||
# "coordinators": SettingsConstants.ALL_COORDINATORS,
|
# "coordinators": SettingsConstants.ALL_COORDINATORS,
|
||||||
# "qr_density": EncodeQRDensity.MEDIUM,
|
# "qr_density": EncodeQR.DENSITY__MEDIUM,
|
||||||
# "custom_derivation": "m/"
|
# "custom_derivation": "m/"
|
||||||
# },
|
# },
|
||||||
# "features": {
|
# "features": {
|
||||||
@@ -254,7 +329,7 @@ class Settings(Singleton):
|
|||||||
# }
|
# }
|
||||||
# }
|
# }
|
||||||
|
|
||||||
# # Read persistent settings, if it exists
|
# TODO: Read persistent settings, if it exists
|
||||||
# if os.path.exists(Settings.SETTINGS_FILENAME):
|
# if os.path.exists(Settings.SETTINGS_FILENAME):
|
||||||
# with open(Settings.SETTINGS_FILENAME) as settings_file:
|
# with open(Settings.SETTINGS_FILENAME) as settings_file:
|
||||||
# settings._data.update(json.load(settings_file))
|
# settings._data.update(json.load(settings_file))
|
||||||
@@ -287,43 +362,45 @@ class Settings(Singleton):
|
|||||||
self._data[attr_name] = value
|
self._data[attr_name] = value
|
||||||
# self.save()
|
# self.save()
|
||||||
|
|
||||||
# TODO: auto-intialize dict keys as getter properties?
|
|
||||||
def get_value(self, attr_name: str):
|
def get_value(self, attr_name: str):
|
||||||
if attr_name not in self._data:
|
if attr_name not in self._data:
|
||||||
raise Exception(f"Setting for {attr_name} not found")
|
raise Exception(f"Setting for {attr_name} not found")
|
||||||
return self._data[attr_name]
|
return self._data[attr_name]
|
||||||
|
|
||||||
|
|
||||||
### persistent settings handling
|
def get_value_display_name(self, attr_name: str) -> str:
|
||||||
|
if attr_name not in self._data:
|
||||||
|
raise Exception(f"Setting for {attr_name} not found")
|
||||||
|
settings_entry = SettingsDefinition.get_settings_entry(attr_name)
|
||||||
|
if settings_entry.type in [SettingsConstants.TYPE__FREE_ENTRY, SettingsConstants.TYPE__MULTISELECT]:
|
||||||
|
raise Exception(f"Unsupported SettingsEntry.type: {settings_entry.type}")
|
||||||
|
return settings_entry.get_selection_option_display_name_by_value(value=self._data[attr_name])
|
||||||
|
|
||||||
|
|
||||||
|
def get_multiselect_value_display_names(self, attr_name: str) -> List[str]:
|
||||||
|
if attr_name not in self._data:
|
||||||
|
raise Exception(f"Setting for {attr_name} not found")
|
||||||
|
settings_entry = SettingsDefinition.get_settings_entry(attr_name)
|
||||||
|
if settings_entry.type != SettingsConstants.TYPE__MULTISELECT:
|
||||||
|
raise Exception(f"Unsupported SettingsEntry.type: {settings_entry.type}")
|
||||||
|
|
||||||
|
display_names = []
|
||||||
|
for value in self._data[attr_name]:
|
||||||
|
display_names.append(settings_entry.get_selection_option_display_name_by_value(value))
|
||||||
|
return display_names
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def persistent_settings(self):
|
def persistent_settings(self) -> bool:
|
||||||
return self._data[SettingsConstants.SETTING__PERSISTENT_SETTINGS] == SettingsConstants.OPTION__ENABLED
|
return self._data[SettingsConstants.SETTING__PERSISTENT_SETTINGS] == SettingsConstants.OPTION__ENABLED
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def persistent_display(self):
|
def debug(self) -> bool:
|
||||||
if self.persistent:
|
return self._data[SettingsConstants.SETTING__DEBUG] == SettingsConstants.OPTION__ENABLED
|
||||||
return "Yes"
|
|
||||||
else:
|
|
||||||
return "No"
|
|
||||||
|
|
||||||
### system
|
|
||||||
|
|
||||||
@property
|
|
||||||
def debug(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def language(self):
|
|
||||||
return "en"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def wordlist(self):
|
|
||||||
# TODO: Support BIP-39 wordlists in other languages
|
|
||||||
return bip39.WORDLIST
|
|
||||||
|
|
||||||
### display
|
|
||||||
|
|
||||||
|
# TODO: Deeper support for GUI-wide color customization
|
||||||
@property
|
@property
|
||||||
def text_color(self):
|
def text_color(self):
|
||||||
from seedsigner.gui.components import GUIConstants
|
from seedsigner.gui.components import GUIConstants
|
||||||
@@ -332,55 +409,10 @@ class Settings(Singleton):
|
|||||||
@property
|
@property
|
||||||
def qr_background_color(self):
|
def qr_background_color(self):
|
||||||
return self._data["display"]["qr_background_color"]
|
return self._data["display"]["qr_background_color"]
|
||||||
|
|
||||||
@qr_background_color.setter
|
|
||||||
def qr_background_color(self, value):
|
|
||||||
self._data["display"]["qr_background_color"] = value
|
|
||||||
self.__writeConfig()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def camera_rotation(self):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
@camera_rotation.setter
|
@staticmethod
|
||||||
def camera_rotation(self, value: int):
|
def qr_psbt_type(coordinator):
|
||||||
if value in [0, 90, 180, 270]:
|
|
||||||
self._data["display"]["camera_rotation"] = value
|
|
||||||
self.save()
|
|
||||||
else:
|
|
||||||
raise Exception("Unexpected display.camera_rotation settings.json value")
|
|
||||||
|
|
||||||
### wallet
|
|
||||||
|
|
||||||
@property
|
|
||||||
def network(self):
|
|
||||||
return SeedConstants.MAINNET
|
|
||||||
|
|
||||||
@network.setter
|
|
||||||
def network(self, value):
|
|
||||||
if value in [SeedConstants.MAINNET, SeedConstants.TESTNET]:
|
|
||||||
self._data["wallet"]["network"] = value
|
|
||||||
self.save()
|
|
||||||
else:
|
|
||||||
raise Exception("Unexpected wallet.network settings.json value")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def coordinators(self):
|
|
||||||
return self._data[SettingsConstants.SETTING__COORDINATORS]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def qr_density(self):
|
|
||||||
return self._data["wallet"]["qr_density"]
|
|
||||||
|
|
||||||
@qr_density.setter
|
|
||||||
def qr_density(self, value):
|
|
||||||
if value in (EncodeQRDensity.LOW, EncodeQRDensity.MEDIUM, EncodeQRDensity.HIGH, int(EncodeQRDensity.LOW), int(EncodeQRDensity.MEDIUM), int(EncodeQRDensity.HIGH)):
|
|
||||||
self._data["wallet"]["qr_density"] = int(value)
|
|
||||||
self.save()
|
|
||||||
else:
|
|
||||||
raise Exception("Unexpected wallet.qr_density settings.json value")
|
|
||||||
|
|
||||||
def qr_psbt_type(self, coordinator):
|
|
||||||
if coordinator == SettingsConstants.COORDINATOR__SPECTER_DESKTOP:
|
if coordinator == SettingsConstants.COORDINATOR__SPECTER_DESKTOP:
|
||||||
return QRType.PSBTSPECTER
|
return QRType.PSBTSPECTER
|
||||||
else:
|
else:
|
||||||
@@ -388,28 +420,17 @@ class Settings(Singleton):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def qr_xpub_type(self):
|
def qr_xpub_type(self):
|
||||||
return Settings.getXPubType(self.coordinators)
|
return Settings.getXPubType(self._data[SettingsConstants.SETTING__COORDINATORS])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getXPubType(software):
|
def getXPubType(coordinators):
|
||||||
if software == SettingsConstants.COORDINATOR__SPECTER_DESKTOP:
|
if coordinators == [SettingsConstants.COORDINATOR__SPECTER_DESKTOP]:
|
||||||
return QRType.SPECTERXPUBQR
|
return QRType.SPECTERXPUBQR
|
||||||
elif software == "Blue Wallet":
|
elif coordinators == [SettingsConstants.COORDINATOR__BLUE_WALLET]:
|
||||||
return QRType.XPUBQR
|
return QRType.XPUBQR
|
||||||
else:
|
else:
|
||||||
return QRType.URXPUBQR
|
return QRType.URXPUBQR
|
||||||
|
|
||||||
@property
|
|
||||||
def qr_density_name(self):
|
|
||||||
if self.qr_density == EncodeQRDensity.LOW:
|
|
||||||
return "Low"
|
|
||||||
elif self.qr_density == EncodeQRDensity.MEDIUM:
|
|
||||||
return "Medium"
|
|
||||||
elif self.qr_density == EncodeQRDensity.HIGH:
|
|
||||||
return "High"
|
|
||||||
else:
|
|
||||||
return "Unknown"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def custom_derivation(self):
|
def custom_derivation(self):
|
||||||
return self._data["wallet"]["custom_derivation"]
|
return self._data["wallet"]["custom_derivation"]
|
||||||
@@ -419,64 +440,3 @@ class Settings(Singleton):
|
|||||||
# TODO: parse and validate custom derivation path
|
# TODO: parse and validate custom derivation path
|
||||||
self._data["wallet"]["custom_derivation"] = value
|
self._data["wallet"]["custom_derivation"] = value
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def calc_derivation(network, wallet_type, script_type):
|
|
||||||
# TODO: Move this to Seed?
|
|
||||||
if network == SeedConstants.MAINNET:
|
|
||||||
network_path = "0'"
|
|
||||||
elif network == SeedConstants.TESTNET:
|
|
||||||
network_path = "1'"
|
|
||||||
else:
|
|
||||||
raise Exception("Unexpected network")
|
|
||||||
|
|
||||||
if wallet_type == SeedConstants.SINGLE_SIG:
|
|
||||||
if script_type == SeedConstants.NATIVE_SEGWIT:
|
|
||||||
return f"m/84'/{network_path}/0'"
|
|
||||||
elif script_type == SeedConstants.NESTED_SEGWIT:
|
|
||||||
return f"m/49'/{network_path}/0'"
|
|
||||||
elif script_type == SeedConstants.TAPROOT:
|
|
||||||
return f"m/86'/{network_path}/0'"
|
|
||||||
else:
|
|
||||||
raise Exception("Unexpected script type")
|
|
||||||
|
|
||||||
elif wallet_type == SeedConstants.MULTISIG:
|
|
||||||
if script_type == SeedConstants.NATIVE_SEGWIT:
|
|
||||||
return f"m/48'/{network_path}/0'/2'"
|
|
||||||
elif script_type == SeedConstants.NESTED_SEGWIT:
|
|
||||||
return f"m/48'/{network_path}/0'/1'"
|
|
||||||
elif script_type == SeedConstants.TAPROOT:
|
|
||||||
raise Exception("Taproot multisig/musig not yet supported")
|
|
||||||
else:
|
|
||||||
raise Exception("Unexpected script type")
|
|
||||||
else:
|
|
||||||
raise Exception("Unexpected wallet type")
|
|
||||||
|
|
||||||
# Features
|
|
||||||
@property
|
|
||||||
def xpub_export(self):
|
|
||||||
return self._data["features"].get("xpub_export")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sig_types(self) -> List[str]:
|
|
||||||
return self._data["features"].get("sig_types")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def script_types(self) -> List[str]:
|
|
||||||
return self._data["features"].get("script_types")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def show_xpub_details(self) -> bool:
|
|
||||||
return self._data["features"].get("show_xpub_details") == SettingsConstants.OPTION__ENABLED
|
|
||||||
|
|
||||||
@property
|
|
||||||
def passphrase(self):
|
|
||||||
return "Enabled"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def show_privacy_warnings(self) -> bool:
|
|
||||||
return self._data["features"].get("show_privacy_warnings") == SettingsConstants.OPTION__ENABLED
|
|
||||||
|
|
||||||
@property
|
|
||||||
def show_dire_warnings(self) -> bool:
|
|
||||||
return self._data["features"].get("show_dire_warnings") == SettingsConstants.OPTION__ENABLED
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ class PSBTSelectSeedView(View):
|
|||||||
ENTER_WORDS = "Enter 12/24 words"
|
ENTER_WORDS = "Enter 12/24 words"
|
||||||
button_data = []
|
button_data = []
|
||||||
for seed in seeds:
|
for seed in seeds:
|
||||||
button_str = seed.get_fingerprint(self.settings.network)
|
button_str = seed.get_fingerprint(self.settings.get_value_display_name(SettingsConstants.SETTING__NETWORK))
|
||||||
if not PSBTParser.has_matching_input_fingerprint(psbt=self.controller.psbt, seed=seed, network=self.settings.network):
|
if not PSBTParser.has_matching_input_fingerprint(psbt=self.controller.psbt, seed=seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
|
||||||
# Doesn't look like this seed can sign the current PSBT
|
# Doesn't look like this seed can sign the current PSBT
|
||||||
button_str += " (?)"
|
button_str += " (?)"
|
||||||
|
|
||||||
@@ -40,12 +40,11 @@ class PSBTSelectSeedView(View):
|
|||||||
button_data.append(SCAN_SEED)
|
button_data.append(SCAN_SEED)
|
||||||
button_data.append(ENTER_WORDS)
|
button_data.append(ENTER_WORDS)
|
||||||
|
|
||||||
screen = ButtonListScreen(
|
selected_menu_num = ButtonListScreen(
|
||||||
title="Select Signer",
|
title="Select Signer",
|
||||||
is_button_text_centered=False,
|
is_button_text_centered=False,
|
||||||
button_data=button_data
|
button_data=button_data
|
||||||
)
|
).display()
|
||||||
selected_menu_num = screen.display()
|
|
||||||
|
|
||||||
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
||||||
return Destination(BackStackView)
|
return Destination(BackStackView)
|
||||||
@@ -79,7 +78,7 @@ class PSBTOverviewView(View):
|
|||||||
self.controller.psbt_parser = PSBTParser(
|
self.controller.psbt_parser = PSBTParser(
|
||||||
self.controller.psbt,
|
self.controller.psbt,
|
||||||
seed=self.controller.psbt_seed,
|
seed=self.controller.psbt_seed,
|
||||||
network=self.controller.settings.network
|
network=self.controller.settings.get_value(SettingsConstants.SETTING__NETWORK)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -112,8 +111,7 @@ class PSBTOverviewView(View):
|
|||||||
|
|
||||||
class PSBTNoChangeWarningView(View):
|
class PSBTNoChangeWarningView(View):
|
||||||
def run(self):
|
def run(self):
|
||||||
screen = psbt_screens.PSBTNoChangeWarningScreen()
|
selected_menu_num = psbt_screens.PSBTNoChangeWarningScreen().display()
|
||||||
selected_menu_num = screen.display()
|
|
||||||
|
|
||||||
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
||||||
return Destination(BackStackView)
|
return Destination(BackStackView)
|
||||||
@@ -140,15 +138,14 @@ class PSBTAmountDetailsView(View):
|
|||||||
# Should not be able to get here
|
# Should not be able to get here
|
||||||
return Destination(MainMenuView)
|
return Destination(MainMenuView)
|
||||||
|
|
||||||
screen = PSBTAmountDetailsScreen(
|
selected_menu_num = PSBTAmountDetailsScreen(
|
||||||
input_amount=psbt_parser.input_amount,
|
input_amount=psbt_parser.input_amount,
|
||||||
num_inputs=psbt_parser.num_inputs,
|
num_inputs=psbt_parser.num_inputs,
|
||||||
spend_amount=psbt_parser.spend_amount,
|
spend_amount=psbt_parser.spend_amount,
|
||||||
num_recipients=psbt_parser.num_destinations,
|
num_recipients=psbt_parser.num_destinations,
|
||||||
fee_amount=psbt_parser.fee_amount,
|
fee_amount=psbt_parser.fee_amount,
|
||||||
change_amount=psbt_parser.change_amount,
|
change_amount=psbt_parser.change_amount,
|
||||||
)
|
).display()
|
||||||
selected_menu_num = screen.display()
|
|
||||||
|
|
||||||
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
||||||
return Destination(BackStackView)
|
return Destination(BackStackView)
|
||||||
@@ -185,15 +182,13 @@ class PSBTAddressDetailsView(View):
|
|||||||
else:
|
else:
|
||||||
button_data.append("Next")
|
button_data.append("Next")
|
||||||
|
|
||||||
screen = psbt_screens.PSBTAddressDetailsScreen(
|
selected_menu_num = psbt_screens.PSBTAddressDetailsScreen(
|
||||||
title=title,
|
title=title,
|
||||||
button_data=button_data,
|
button_data=button_data,
|
||||||
address=psbt_parser.destination_addresses[self.address_num],
|
address=psbt_parser.destination_addresses[self.address_num],
|
||||||
amount=psbt_parser.destination_amounts[self.address_num],
|
amount=psbt_parser.destination_amounts[self.address_num],
|
||||||
is_change=self.is_change,
|
is_change=self.is_change,
|
||||||
)
|
).display()
|
||||||
|
|
||||||
selected_menu_num = screen.display()
|
|
||||||
|
|
||||||
if selected_menu_num == 0:
|
if selected_menu_num == 0:
|
||||||
if self.address_num < len(psbt_parser.destination_addresses) - 1:
|
if self.address_num < len(psbt_parser.destination_addresses) - 1:
|
||||||
@@ -233,10 +228,10 @@ class PSBTChangeDetailsView(View):
|
|||||||
|
|
||||||
# Single-sig verification is easy. We expect to find a single fingerprint
|
# Single-sig verification is easy. We expect to find a single fingerprint
|
||||||
# and derivation path.
|
# and derivation path.
|
||||||
print(f"seed fingerprint: {self.controller.psbt_seed.get_fingerprint(self.settings.network)}")
|
print(f"seed fingerprint: {self.controller.psbt_seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK))}")
|
||||||
print(change_data)
|
print(change_data)
|
||||||
|
|
||||||
if self.controller.psbt_seed.get_fingerprint(self.settings.network) != change_data.get("fingerprint")[0]:
|
if self.controller.psbt_seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK)) != change_data.get("fingerprint")[0]:
|
||||||
# TODO: Something is wrong with this psbt(?). Reroute to warning?
|
# TODO: Something is wrong with this psbt(?). Reroute to warning?
|
||||||
return Destination(MainMenuView)
|
return Destination(MainMenuView)
|
||||||
|
|
||||||
@@ -254,7 +249,7 @@ class PSBTChangeDetailsView(View):
|
|||||||
if psbt_parser.num_change_outputs > 1:
|
if psbt_parser.num_change_outputs > 1:
|
||||||
title += f" (#{self.change_address_num + 1})"
|
title += f" (#{self.change_address_num + 1})"
|
||||||
|
|
||||||
screen = psbt_screens.PSBTChangeDetailsScreen(
|
selected_menu_num = psbt_screens.PSBTChangeDetailsScreen(
|
||||||
title=title,
|
title=title,
|
||||||
button_data=["Next"],
|
button_data=["Next"],
|
||||||
address=change_data.get("address"),
|
address=change_data.get("address"),
|
||||||
@@ -263,9 +258,7 @@ class PSBTChangeDetailsView(View):
|
|||||||
derivation_path=derivation_path,
|
derivation_path=derivation_path,
|
||||||
is_own_change_addr=is_own_change_addr,
|
is_own_change_addr=is_own_change_addr,
|
||||||
own_addr_index=own_addr_index,
|
own_addr_index=own_addr_index,
|
||||||
)
|
).display()
|
||||||
|
|
||||||
selected_menu_num = screen.display()
|
|
||||||
|
|
||||||
if selected_menu_num == 0:
|
if selected_menu_num == 0:
|
||||||
if self.change_address_num < psbt_parser.num_change_outputs - 1:
|
if self.change_address_num < psbt_parser.num_change_outputs - 1:
|
||||||
@@ -290,10 +283,9 @@ class PSBTFinalizeView(View):
|
|||||||
# Should not be able to get here
|
# Should not be able to get here
|
||||||
return Destination(MainMenuView)
|
return Destination(MainMenuView)
|
||||||
|
|
||||||
screen = psbt_screens.PSBTFinalizeScreen(
|
selected_menu_num = psbt_screens.PSBTFinalizeScreen(
|
||||||
button_data=["Approve PSBT"]
|
button_data=["Approve PSBT"]
|
||||||
)
|
).display()
|
||||||
selected_menu_num = screen.display()
|
|
||||||
|
|
||||||
if selected_menu_num == 0:
|
if selected_menu_num == 0:
|
||||||
# Sign PSBT
|
# Sign PSBT
|
||||||
@@ -315,8 +307,8 @@ class PSBTFinalizeView(View):
|
|||||||
else:
|
else:
|
||||||
self.controller.psbt = trimmed_psbt
|
self.controller.psbt = trimmed_psbt
|
||||||
|
|
||||||
if len(self.settings.coordinators) == 1:
|
if len(self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)) == 1:
|
||||||
return Destination(PSBTSignedQRDisplayView, view_args={"coordinator": self.settings.coordinators[0]})
|
return Destination(PSBTSignedQRDisplayView, view_args={"coordinator": self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)[0]})
|
||||||
else:
|
else:
|
||||||
return Destination(PSBTSelectCoordinatorView)
|
return Destination(PSBTSelectCoordinatorView)
|
||||||
|
|
||||||
@@ -327,11 +319,10 @@ class PSBTFinalizeView(View):
|
|||||||
|
|
||||||
class PSBTSelectCoordinatorView(View):
|
class PSBTSelectCoordinatorView(View):
|
||||||
def run(self):
|
def run(self):
|
||||||
button_data = [c for c in self.settings.coordinators]
|
button_data = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS)
|
||||||
screen = psbt_screens.PSBTSelectCoordinatorScreen(
|
selected_menu_num = psbt_screens.PSBTSelectCoordinatorScreen(
|
||||||
button_data=button_data
|
button_data=button_data
|
||||||
)
|
).display()
|
||||||
selected_menu_num = screen.display()
|
|
||||||
|
|
||||||
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
||||||
return Destination(BackStackView)
|
return Destination(BackStackView)
|
||||||
@@ -347,13 +338,12 @@ class PSBTSignedQRDisplayView(View):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
qr_encoder = EncodeQR(
|
qr_encoder = EncodeQR(
|
||||||
psbt=self.controller.psbt,
|
psbt=self.controller.psbt,
|
||||||
qr_type=self.settings.qr_psbt_type(self.coordinator),
|
qr_type=self.settings.qr_psbt_type(self.coordinator),
|
||||||
qr_density=self.settings.qr_density,
|
qr_density=self.settings.get_value(SettingsConstants.SETTING__QR_DENSITY),
|
||||||
wordlist=self.settings.wordlist
|
wordlist=self.settings.wordlist
|
||||||
)
|
)
|
||||||
screen = QRDisplayScreen(qr_encoder=qr_encoder)
|
ret = QRDisplayScreen(qr_encoder=qr_encoder).display()
|
||||||
ret = screen.display()
|
|
||||||
|
|
||||||
# We're done with this PSBT. Remove all related data
|
# We're done with this PSBT. Remove all related data
|
||||||
self.controller.psbt = None
|
self.controller.psbt = None
|
||||||
@@ -371,14 +361,13 @@ class PSBTSigningErrorView(View):
|
|||||||
# Should not be able to get here
|
# Should not be able to get here
|
||||||
return Destination(MainMenuView)
|
return Destination(MainMenuView)
|
||||||
|
|
||||||
screen = WarningScreen(
|
selected_menu_num = WarningScreen(
|
||||||
title="Finalize PSBT",
|
title="Finalize PSBT",
|
||||||
warning_icon_name="warning",
|
warning_icon_name="warning",
|
||||||
warning_headline="Signing Failed",
|
warning_headline="Signing Failed",
|
||||||
warning_text="",
|
warning_text="",
|
||||||
button_data=["Select Diff Seed"],
|
button_data=["Select Diff Seed"],
|
||||||
)
|
).display()
|
||||||
selected_menu_num = screen.display()
|
|
||||||
|
|
||||||
if selected_menu_num == 0:
|
if selected_menu_num == 0:
|
||||||
return Destination(PSBTSelectSeedView, clear_history=True)
|
return Destination(PSBTSelectSeedView, clear_history=True)
|
||||||
|
|||||||
@@ -111,20 +111,22 @@ class SeedOptionsView(View):
|
|||||||
button_data = []
|
button_data = []
|
||||||
|
|
||||||
if self.controller.psbt:
|
if self.controller.psbt:
|
||||||
if not PSBTParser.has_matching_input_fingerprint(self.controller.psbt, self.seed, network=self.settings.network):
|
if not PSBTParser.has_matching_input_fingerprint(self.controller.psbt, self.seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
|
||||||
|
# This seed does not seem to be a signer for this PSBT
|
||||||
|
# TODO: How sure are we? Should disable this entirely if we're 100% sure?
|
||||||
SIGN_PSBT += " (?)"
|
SIGN_PSBT += " (?)"
|
||||||
button_data.append(SIGN_PSBT)
|
button_data.append(SIGN_PSBT)
|
||||||
|
|
||||||
button_data.append(VIEW_WORDS)
|
button_data.append(VIEW_WORDS)
|
||||||
|
|
||||||
if self.settings.xpub_export == SettingsConstants.OPTION__ENABLED:
|
if self.settings.get_value(SettingsConstants.SETTING__XPUB_EXPORT) == SettingsConstants.OPTION__ENABLED:
|
||||||
button_data.append(EXPORT_XPUB)
|
button_data.append(EXPORT_XPUB)
|
||||||
|
|
||||||
button_data.append(EXPORT_SEEDQR)
|
button_data.append(EXPORT_SEEDQR)
|
||||||
|
|
||||||
screen = seed_screens.SeedOptionsScreen(
|
screen = seed_screens.SeedOptionsScreen(
|
||||||
button_data=button_data,
|
button_data=button_data,
|
||||||
fingerprint=self.seed.get_fingerprint(self.settings.network),
|
fingerprint=self.seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK)),
|
||||||
has_passphrase=self.seed.passphrase is not None
|
has_passphrase=self.seed.passphrase is not None
|
||||||
)
|
)
|
||||||
selected_menu_num = screen.display()
|
selected_menu_num = screen.display()
|
||||||
@@ -139,7 +141,7 @@ class SeedOptionsView(View):
|
|||||||
elif button_data[selected_menu_num] == VIEW_WORDS:
|
elif button_data[selected_menu_num] == VIEW_WORDS:
|
||||||
return Destination(SeedWordsWarningView, view_args={"seed_num": self.seed_num})
|
return Destination(SeedWordsWarningView, view_args={"seed_num": self.seed_num})
|
||||||
|
|
||||||
elif button_data[selected_menu_num] == EXPORT_XPUB and self.settings.xpub_export == SettingsConstants.OPTION__ENABLED:
|
elif button_data[selected_menu_num] == EXPORT_XPUB and self.settings.get_value(SettingsConstants.SETTING__XPUB_EXPORT) == SettingsConstants.OPTION__ENABLED:
|
||||||
return Destination(SeedExportXpubSigTypeView, view_args={"seed_num": self.seed_num})
|
return Destination(SeedExportXpubSigTypeView, view_args={"seed_num": self.seed_num})
|
||||||
|
|
||||||
elif button_data[selected_menu_num] == EXPORT_SEEDQR:
|
elif button_data[selected_menu_num] == EXPORT_SEEDQR:
|
||||||
@@ -474,7 +476,7 @@ class SeedExportXpubQRDisplayView(View):
|
|||||||
derivation=derivation_path,
|
derivation=derivation_path,
|
||||||
network=self.settings.network,
|
network=self.settings.network,
|
||||||
qr_type=qr_type,
|
qr_type=qr_type,
|
||||||
qr_density=self.settings.qr_density,
|
qr_density=self.settings.get_value(SettingsConstants.SETTING__QR_DENSITY),
|
||||||
wordlist=self.seed.wordlist
|
wordlist=self.seed.wordlist
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
from . import View
|
from . import View
|
||||||
from seedsigner.helpers import B, QR
|
from seedsigner.helpers import B, QR
|
||||||
from seedsigner.gui.keyboard import Keyboard, TextEntryDisplay
|
from seedsigner.gui.keyboard import Keyboard, TextEntryDisplay
|
||||||
from seedsigner.models import EncodeQRDensity
|
from seedsigner.models import EncodeQR
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -58,11 +58,11 @@ class SettingsToolsView(View):
|
|||||||
def display_qr_density_selection(self) -> str:
|
def display_qr_density_selection(self) -> str:
|
||||||
r = self.renderer.display_generic_selection_menu(["... [ Return to Settings ]", "Low", "Medium", "High"], "Which QR Density?")
|
r = self.renderer.display_generic_selection_menu(["... [ Return to Settings ]", "Low", "Medium", "High"], "Which QR Density?")
|
||||||
if r == 2:
|
if r == 2:
|
||||||
return EncodeQRDensity.LOW
|
return EncodeQR.DENSITY__LOW
|
||||||
elif r == 3:
|
elif r == 3:
|
||||||
return EncodeQRDensity.MEDIUM
|
return EncodeQR.DENSITY__MEDIUM
|
||||||
elif r == 4:
|
elif r == 4:
|
||||||
return EncodeQRDensity.HIGH
|
return EncodeQR.DENSITY__HIGH
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -41,13 +41,12 @@ class SettingsMenuView(View):
|
|||||||
title = "Dev Options"
|
title = "Dev Options"
|
||||||
next = None
|
next = None
|
||||||
|
|
||||||
screen = ButtonListScreen(
|
selected_menu_num = ButtonListScreen(
|
||||||
title=title,
|
title=title,
|
||||||
is_button_text_centered=False,
|
is_button_text_centered=False,
|
||||||
button_data=button_data,
|
button_data=button_data,
|
||||||
selected_button=selected_button,
|
selected_button=selected_button,
|
||||||
)
|
).display()
|
||||||
selected_menu_num = screen.display()
|
|
||||||
|
|
||||||
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
||||||
if self.visibility == SettingsConstants.VISIBILITY__GENERAL:
|
if self.visibility == SettingsConstants.VISIBILITY__GENERAL:
|
||||||
@@ -61,76 +60,94 @@ class SettingsMenuView(View):
|
|||||||
return next
|
return next
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return Destination(SettingsEntryUpdateView, view_args={"attr_name": settings_entries[selected_menu_num].attr_name})
|
# TODO: Free-entry types (are there any?) will need their own SettingsEntryUpdateFreeEntryView(?).
|
||||||
|
return Destination(SettingsEntryUpdateSelectionView, view_args={"attr_name": settings_entries[selected_menu_num].attr_name})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SettingsEntryUpdateView(View):
|
class SettingsEntryUpdateSelectionView(View):
|
||||||
|
"""
|
||||||
|
Handles changes to all selection-type settings (Multiselect, SELECT_1,
|
||||||
|
Enabled/Disabled, etc).
|
||||||
|
"""
|
||||||
def __init__(self, attr_name: str):
|
def __init__(self, attr_name: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.settings_entry = SettingsDefinition.get_settings_entry(attr_name)
|
self.settings_entry = SettingsDefinition.get_settings_entry(attr_name)
|
||||||
self.selected_button = 0
|
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
button_data = self.settings_entry.possible_values
|
|
||||||
cur_value = self.settings.get_value(self.settings_entry.attr_name)
|
cur_value = self.settings.get_value(self.settings_entry.attr_name)
|
||||||
|
button_data = []
|
||||||
checked_buttons = []
|
checked_buttons = []
|
||||||
for i, value in enumerate(button_data):
|
selected_button = None
|
||||||
|
for i, value in enumerate(self.settings_entry.selection_options):
|
||||||
|
print(value)
|
||||||
|
if type(value) == tuple:
|
||||||
|
value, display_name = value
|
||||||
|
else:
|
||||||
|
display_name = value
|
||||||
|
button_data.append(display_name)
|
||||||
if (type(cur_value) == list and value in cur_value) or value == cur_value:
|
if (type(cur_value) == list and value in cur_value) or value == cur_value:
|
||||||
checked_buttons.append(i)
|
checked_buttons.append(i)
|
||||||
|
|
||||||
screen = settings_screens.SettingsEntryUpdateScreen(
|
if selected_button is None:
|
||||||
|
# Highlight the selection (for multiselect highlight the first
|
||||||
|
# selected option).
|
||||||
|
selected_button = i
|
||||||
|
print(f"setting selected_button: {i} - {display_name}")
|
||||||
|
|
||||||
|
ret_value = settings_screens.SettingsEntryUpdateSelectionScreen(
|
||||||
display_name=self.settings_entry.display_name,
|
display_name=self.settings_entry.display_name,
|
||||||
help_text=self.settings_entry.help_text,
|
help_text=self.settings_entry.help_text,
|
||||||
button_data=button_data,
|
button_data=button_data,
|
||||||
selected_button=self.selected_button,
|
selected_button=selected_button,
|
||||||
checked_buttons=checked_buttons,
|
checked_buttons=checked_buttons,
|
||||||
settings_entry_type=self.settings_entry.type,
|
settings_entry_type=self.settings_entry.type,
|
||||||
|
).display()
|
||||||
|
|
||||||
|
destination = None
|
||||||
|
settings_menu_view_destination = Destination(
|
||||||
|
SettingsMenuView,
|
||||||
|
view_args={
|
||||||
|
"visibility": self.settings_entry.visibility,
|
||||||
|
"selected_attr": self.settings_entry.attr_name
|
||||||
|
}
|
||||||
)
|
)
|
||||||
ret_value = screen.display()
|
|
||||||
|
|
||||||
if self.settings_entry.type == SettingsConstants.TYPE__MULTISELECT:
|
if ret_value == RET_CODE__BACK_BUTTON:
|
||||||
if ret_value == RET_CODE__BACK_BUTTON:
|
return settings_menu_view_destination
|
||||||
return Destination(
|
|
||||||
SettingsMenuView,
|
|
||||||
view_args={
|
|
||||||
"visibility": self.settings_entry.visibility,
|
|
||||||
"selected_attr": self.settings_entry.attr_name
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
value = self.settings_entry.get_selection_option_value(ret_value)
|
||||||
|
|
||||||
|
if self.settings_entry.type == SettingsConstants.TYPE__FREE_ENTRY:
|
||||||
|
updated_value = ret_value
|
||||||
|
destination = settings_menu_view_destination
|
||||||
|
|
||||||
|
elif self.settings_entry.type == SettingsConstants.TYPE__MULTISELECT:
|
||||||
|
updated_value = list(cur_value)
|
||||||
if ret_value not in checked_buttons:
|
if ret_value not in checked_buttons:
|
||||||
# This is a new selection to add
|
# This is a new selection to add
|
||||||
cur_value.append(self.settings_entry.possible_values[ret_value])
|
updated_value.append(value)
|
||||||
else:
|
else:
|
||||||
# This is a de-select to remove
|
# This is a de-select to remove
|
||||||
cur_value.remove(self.settings_entry.possible_values[ret_value])
|
updated_value.remove(value)
|
||||||
|
|
||||||
# Save the updated selections
|
|
||||||
self.settings.set_value(
|
|
||||||
attr_name=self.settings_entry.attr_name,
|
|
||||||
value=cur_value
|
|
||||||
)
|
|
||||||
# Multiselects stay in place; re-initialize where in the list we left off
|
|
||||||
# TODO: get Screen.scroll_y to pass in?
|
|
||||||
self.selected_button = ret_value
|
|
||||||
return self.run()
|
|
||||||
|
|
||||||
elif ret_value == RET_CODE__BACK_BUTTON:
|
|
||||||
return Destination(BackStackView)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.settings.set_value(
|
# All other types are single selects (e.g. Enabled/Disabled, SELECT_1)
|
||||||
attr_name=self.settings_entry.attr_name,
|
updated_value = value
|
||||||
value=self.settings_entry.possible_values[ret_value]
|
|
||||||
)
|
print(updated_value)
|
||||||
|
|
||||||
return Destination(
|
self.settings.set_value(
|
||||||
SettingsMenuView,
|
attr_name=self.settings_entry.attr_name,
|
||||||
view_args={
|
value=updated_value
|
||||||
"visibility": self.settings_entry.visibility,
|
)
|
||||||
"selected_attr": self.settings_entry.attr_name
|
|
||||||
}
|
if destination:
|
||||||
)
|
return destination
|
||||||
|
|
||||||
|
# All selects stay in place; re-initialize where in the list we left off
|
||||||
|
# TODO: get Screen.scroll_y to pass in?
|
||||||
|
self.selected_button = ret_value
|
||||||
|
return self.run()
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ class View:
|
|||||||
self.canvas_height = self.renderer.canvas_height
|
self.canvas_height = self.renderer.canvas_height
|
||||||
|
|
||||||
self.buttons = self.controller.buttons
|
self.buttons = self.controller.buttons
|
||||||
self.color = self.controller.color
|
|
||||||
|
|
||||||
|
|
||||||
def run(self, **kwargs):
|
def run(self, **kwargs):
|
||||||
@@ -64,28 +63,6 @@ class View:
|
|||||||
else:
|
else:
|
||||||
raise Exception("Must implement in the child class")
|
raise Exception("Must implement in the child class")
|
||||||
|
|
||||||
###
|
|
||||||
### Reusable components
|
|
||||||
###
|
|
||||||
# TODO: Obviated by seedsigner.gui.components.TopNav
|
|
||||||
def render_previous_button(self, highlight=False):
|
|
||||||
# Set up the "back" arrow in the upper left
|
|
||||||
arrow = "<"
|
|
||||||
word_font = Fonts.get_font("RobotoCondensed-Bold", 26)
|
|
||||||
top_padding = -3
|
|
||||||
bottom_padding = 3
|
|
||||||
side_padding = 3
|
|
||||||
tw, th = word_font.getsize(arrow)
|
|
||||||
self.previous_button_width = tw + 2 * side_padding
|
|
||||||
if highlight:
|
|
||||||
font_color = "black"
|
|
||||||
background_color = self.color
|
|
||||||
else:
|
|
||||||
font_color = self.color
|
|
||||||
background_color = "black"
|
|
||||||
self.renderer.draw.rectangle((0,0, self.previous_button_width, th + top_padding + bottom_padding), fill=background_color)
|
|
||||||
self.renderer.draw.text((side_padding, top_padding), arrow, fill=font_color, font=word_font)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ def test_specter_xpub_qr():
|
|||||||
|
|
||||||
mnemonic = "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash"
|
mnemonic = "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash"
|
||||||
|
|
||||||
e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.SPECTERXPUBQR, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQRDensity.LOW, wordlist=bip39.WORDLIST)
|
e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.SPECTERXPUBQR, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQR.DENSITY__LOW, wordlist=bip39.WORDLIST)
|
||||||
|
|
||||||
assert e.nextPart() == "p1of4 [c49122a5/48h/1h/0h/2h]Vpub5mXgECaX5yYDN"
|
assert e.nextPart() == "p1of4 [c49122a5/48h/1h/0h/2h]Vpub5mXgECaX5yYDN"
|
||||||
assert e.nextPart() == "p2of4 c5VnUG4jVNptyEg65qUjuofWchQeuMWWiq8rcPBo"
|
assert e.nextPart() == "p2of4 c5VnUG4jVNptyEg65qUjuofWchQeuMWWiq8rcPBo"
|
||||||
@@ -89,7 +89,7 @@ def test_ur_xpub_qr():
|
|||||||
|
|
||||||
mnemonic = "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash"
|
mnemonic = "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash"
|
||||||
|
|
||||||
e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.URXPUBQR, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQRDensity.MEDIUM, wordlist=bip39.WORDLIST)
|
e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.URXPUBQR, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQR.DENSITY__MEDIUM, wordlist=bip39.WORDLIST)
|
||||||
|
|
||||||
assert e.nextPart() == "UR:CRYPTO-ACCOUNT/1-4/LPADAACSJKCYYNRDRTYAHDCAOEADCYSSMECPONAOLYTAADMETAADDLOXAXHDCLAOKSRLNLKPUEGYATHPMNRTAAOLAH"
|
assert e.nextPart() == "UR:CRYPTO-ACCOUNT/1-4/LPADAACSJKCYYNRDRTYAHDCAOEADCYSSMECPONAOLYTAADMETAADDLOXAXHDCLAOKSRLNLKPUEGYATHPMNRTAAOLAH"
|
||||||
assert e.nextPart() == "UR:CRYPTO-ACCOUNT/2-4/LPAOAACSJKCYYNRDRTYAHDCASNKKGHZMLUZORPVDGUOTECSTTKTOLPCWPTNTLKZTTIZTBEAAHDCXVDTPMYQDLAWNGL"
|
assert e.nextPart() == "UR:CRYPTO-ACCOUNT/2-4/LPAOAACSJKCYYNRDRTYAHDCASNKKGHZMLUZORPVDGUOTECSTTKTOLPCWPTNTLKZTTIZTBEAAHDCXVDTPMYQDLAWNGL"
|
||||||
|
|||||||
Reference in New Issue
Block a user