WIP: mypy
This commit is contained in:
parent
fe672fbd09
commit
ea39efb10e
13
Makefile
13
Makefile
|
@ -47,6 +47,19 @@ test: ## run unit tests
|
|||
testpy: ## run selected unit tests from python-trezor
|
||||
cd tests ; ./run_tests_python_trezor.sh
|
||||
|
||||
mypy: ## run mypy on appplication sources
|
||||
mypy --strict \
|
||||
src/trezor/config.py \
|
||||
src/trezor/io.py \
|
||||
src/trezor/log.py \
|
||||
src/trezor/loop.py \
|
||||
src/trezor/main.py \
|
||||
src/trezor/msg.py \
|
||||
src/trezor/utils.py \
|
||||
src/trezor/workflow.py \
|
||||
src/trezor/crypto/*.py \
|
||||
src/trezor/ui/*.py
|
||||
|
||||
pylint: ## run pylint on application sources
|
||||
pylint -E $(shell find src -name *.py)
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ typedef struct _mp_obj_Config_t {
|
|||
mp_obj_base_t base;
|
||||
} mp_obj_Config_t;
|
||||
|
||||
/// def __init__(self):
|
||||
/// def __init__(self) -> None:
|
||||
/// '''
|
||||
/// Initializes the storage.
|
||||
/// '''
|
||||
|
|
|
@ -150,6 +150,14 @@ STATIC mp_obj_t mod_trezorcrypto_AES___del__(mp_obj_t self) {
|
|||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_AES___del___obj, mod_trezorcrypto_AES___del__);
|
||||
|
||||
/// ECB: int
|
||||
/// CBC: int
|
||||
/// CFB: int
|
||||
/// OFB: int
|
||||
/// CTR: int
|
||||
/// Encrypt: int
|
||||
/// Decrypt: int
|
||||
|
||||
STATIC const mp_rom_map_elem_t mod_trezorcrypto_AES_locals_dict_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&mod_trezorcrypto_AES_update_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mod_trezorcrypto_AES___del___obj) },
|
||||
|
|
|
@ -242,7 +242,7 @@ typedef struct _mp_obj_Bip32_t {
|
|||
mp_obj_base_t base;
|
||||
} mp_obj_Bip32_t;
|
||||
|
||||
/// def __init__(self):
|
||||
/// def __init__(self) -> None:
|
||||
/// '''
|
||||
/// '''
|
||||
STATIC mp_obj_t mod_trezorcrypto_Bip32_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
|
||||
|
|
|
@ -16,7 +16,7 @@ typedef struct _mp_obj_Bip39_t {
|
|||
mp_obj_base_t base;
|
||||
} mp_obj_Bip39_t;
|
||||
|
||||
/// def __init__(self):
|
||||
/// def __init__(self) -> None:
|
||||
/// '''
|
||||
/// '''
|
||||
STATIC mp_obj_t mod_trezorcrypto_Bip39_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
|
||||
|
|
|
@ -374,6 +374,10 @@ STATIC mp_obj_t mod_trezorui_Display_save(mp_obj_t self, mp_obj_t filename) {
|
|||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_save_obj, mod_trezorui_Display_save);
|
||||
|
||||
/// FONT_MONO: int # Normal-weight monotype font.
|
||||
/// FONT_NORMAL: int # Normal-weight proportional font.
|
||||
/// FONT_BOLD: int # Bold proportional font.
|
||||
|
||||
STATIC const mp_rom_map_elem_t mod_trezorui_Display_locals_dict_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&mod_trezorui_Display_clear_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&mod_trezorui_Display_refresh_obj) },
|
||||
|
|
|
@ -6,7 +6,7 @@ class Config:
|
|||
Persistent key-value storage, with 16-bit keys and bytes values.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
'''
|
||||
Initializes the storage.
|
||||
'''
|
||||
|
|
|
@ -15,6 +15,13 @@ class AES:
|
|||
'''
|
||||
Update AES context with data.
|
||||
'''
|
||||
ECB: int
|
||||
CBC: int
|
||||
CFB: int
|
||||
OFB: int
|
||||
CTR: int
|
||||
Encrypt: int
|
||||
Decrypt: int
|
||||
|
||||
# extmod/modtrezorcrypto/modtrezorcrypto-bip32.h
|
||||
class HDNode:
|
||||
|
@ -87,7 +94,7 @@ class Bip32:
|
|||
'''
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
'''
|
||||
'''
|
||||
|
||||
|
@ -106,7 +113,7 @@ class Bip39:
|
|||
'''
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
'''
|
||||
'''
|
||||
|
||||
|
|
|
@ -109,3 +109,6 @@ class Display:
|
|||
'''
|
||||
Saves current display contents to file filename.
|
||||
'''
|
||||
FONT_MONO: int # Normal-weight monotype font.
|
||||
FONT_NORMAL: int # Normal-weight proportional font.
|
||||
FONT_BOLD: int # Bold proportional font.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
def const(c):
|
||||
return c
|
||||
def const(c: int) -> int: ...
|
||||
def mem_current() -> int: ...
|
||||
def mem_total() -> int: ...
|
||||
def mem_peak() -> int: ...
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from typing import *
|
||||
|
||||
class utimeq:
|
||||
class utimeq(Sized):
|
||||
def __init__(self, max_queue_size: int) -> None: ...
|
||||
def push(self, time: int, callback: Any, value: Any) -> None: ...
|
||||
def pop(self, entry: List[Any]) -> None: ...
|
||||
def peektime(self) -> int: ...
|
||||
def __len__(self) -> int: ...
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
[mypy]
|
||||
mypy_path = mocks:mocks/generated:src/lib
|
||||
warn_no_return = False
|
||||
|
||||
[flake8]
|
||||
ignore =
|
||||
# E221: multiple spaces before operator
|
||||
|
@ -9,4 +13,6 @@ ignore =
|
|||
# E402: module level import not at top of file
|
||||
E402,
|
||||
# E501: line too long
|
||||
E501
|
||||
E501,
|
||||
# ...
|
||||
F405
|
|
@ -1,69 +0,0 @@
|
|||
__names_get = [
|
||||
'AbstractSet',
|
||||
'AsyncIterable',
|
||||
'AsyncIterator',
|
||||
'Awaitable',
|
||||
'ByteString',
|
||||
'Callable',
|
||||
'Container',
|
||||
'DefaultDict',
|
||||
'Dict',
|
||||
'Generator',
|
||||
'Generic',
|
||||
'ItemsView',
|
||||
'Iterable',
|
||||
'Iterator',
|
||||
'KeysView',
|
||||
'List',
|
||||
'Mapping',
|
||||
'MappingView',
|
||||
'MutableMapping',
|
||||
'MutableSequence',
|
||||
'MutableSet',
|
||||
'Optional',
|
||||
'Reversible',
|
||||
'Sequence',
|
||||
'Set',
|
||||
'Tuple',
|
||||
'Type',
|
||||
'Union',
|
||||
'ValuesView',
|
||||
]
|
||||
|
||||
__names_obj = [
|
||||
'Any',
|
||||
'AnyStr',
|
||||
'Hashable',
|
||||
'Sized',
|
||||
'SupportsAbs',
|
||||
'SupportsFloat',
|
||||
'SupportsInt',
|
||||
'SupportsRound',
|
||||
'Text',
|
||||
]
|
||||
|
||||
|
||||
class __dummy:
|
||||
|
||||
def __getitem__(self, *args):
|
||||
return object
|
||||
|
||||
|
||||
__t = __dummy()
|
||||
|
||||
for __n in __names_get:
|
||||
globals()[__n] = __t
|
||||
|
||||
for __n in __names_obj:
|
||||
globals()[__n] = object
|
||||
|
||||
|
||||
def TypeVar(*args):
|
||||
return object
|
||||
|
||||
|
||||
def NewType(*args):
|
||||
return lambda x: x
|
||||
|
||||
|
||||
TYPE_CHECKING = False
|
|
@ -7,9 +7,9 @@ def get(app: int, key: int) -> bytes:
|
|||
return _config.get(app, key)
|
||||
|
||||
|
||||
def set(app: int, key: int, value: bytes):
|
||||
def set(app: int, key: int, value: bytes) -> None:
|
||||
return _config.set(app, key, value)
|
||||
|
||||
|
||||
def wipe():
|
||||
def wipe() -> None:
|
||||
return _config.wipe()
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
# This module adds shiny packaging and support for python3.
|
||||
#
|
||||
|
||||
from typing import Callable
|
||||
|
||||
|
||||
# 58 character alphabet used
|
||||
_alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||
|
||||
|
@ -64,14 +67,14 @@ def _dsha256_32(data: bytes) -> bytes:
|
|||
return sha256(sha256(data).digest()).digest()[:4]
|
||||
|
||||
|
||||
def encode_check(data: bytes, digestfunc=_dsha256_32) -> str:
|
||||
def encode_check(data: bytes, digestfunc: Callable[[bytes], bytes] = _dsha256_32) -> str:
|
||||
'''
|
||||
Convert bytes to base58 encoded string, append checksum.
|
||||
'''
|
||||
return encode(data + digestfunc(data))
|
||||
|
||||
|
||||
def decode_check(string: str, digestfunc=_dsha256_32) -> bytes:
|
||||
def decode_check(string: str, digestfunc: Callable[[bytes], bytes] = _dsha256_32) -> bytes:
|
||||
'''
|
||||
Convert base58 encoded string to bytes and verify checksum.
|
||||
'''
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
from typing import Any, Optional
|
||||
|
||||
|
||||
class Hmac:
|
||||
|
||||
def __init__(self, key, msg, digestmod):
|
||||
self.__digestmod = digestmod
|
||||
self.__inner = digestmod()
|
||||
self.digest_size = self.__inner.digest_size
|
||||
self.block_size = self.__inner.block_size
|
||||
def __init__(self, key: bytes, msg: Optional[bytes], digestmod: Any) -> None:
|
||||
self._digestmod = digestmod
|
||||
self._inner = digestmod()
|
||||
self.digest_size = self._inner.digest_size
|
||||
self.block_size = self._inner.block_size
|
||||
if len(key) > self.block_size:
|
||||
key = digestmod(key).digest()
|
||||
self.__key = key + bytes(self.block_size - len(key))
|
||||
self.__inner.update(bytes((x ^ 0x36) for x in self.__key))
|
||||
self._key = digestmod(key).digest()
|
||||
else:
|
||||
self._key = key + bytes(self.block_size - len(key))
|
||||
self._inner.update(bytes((x ^ 0x36) for x in self._key))
|
||||
if msg is not None:
|
||||
self.update(msg)
|
||||
|
||||
|
@ -17,20 +20,13 @@ class Hmac:
|
|||
'''
|
||||
Update the context with data.
|
||||
'''
|
||||
self.__inner.update(msg)
|
||||
self._inner.update(msg)
|
||||
|
||||
def digest(self) -> bytes:
|
||||
'''
|
||||
Returns the digest of processed data.
|
||||
'''
|
||||
outer = self.__digestmod()
|
||||
outer.update(bytes((x ^ 0x5C) for x in self.__key))
|
||||
outer.update(self.__inner.digest())
|
||||
return outer.digest()
|
||||
|
||||
|
||||
def new(key, msg, digestmod) -> Hmac:
|
||||
'''
|
||||
Creates a HMAC context object.
|
||||
'''
|
||||
return Hmac(key, msg, digestmod)
|
||||
outer = self._digestmod()
|
||||
outer.update(bytes((x ^ 0x5C) for x in self._key))
|
||||
outer.update(self._inner.digest())
|
||||
return outer.digest() # type: ignore # FIXME: digestmod type
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Any
|
||||
|
||||
|
||||
def int_to_bytes(x: int) -> bytes:
|
||||
if x == 0:
|
||||
|
@ -20,7 +22,7 @@ def encode_length(l: int, is_list: bool) -> bytes:
|
|||
raise ValueError('Input too long')
|
||||
|
||||
|
||||
def encode(data) -> bytes:
|
||||
def encode(data: Any) -> bytes:
|
||||
if isinstance(data, int):
|
||||
return encode(int_to_bytes(data))
|
||||
if isinstance(data, bytes):
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
from micropython import const
|
||||
from typing import Any
|
||||
|
||||
import sys
|
||||
import utime
|
||||
|
||||
NOTSET = const(0)
|
||||
DEBUG = const(10)
|
||||
INFO = const(20)
|
||||
WARNING = const(30)
|
||||
ERROR = const(40)
|
||||
CRITICAL = const(50)
|
||||
NOTSET = const(0) # type: int
|
||||
DEBUG = const(10) # type: int
|
||||
INFO = const(20) # type: int
|
||||
WARNING = const(30) # type: int
|
||||
ERROR = const(40) # type: int
|
||||
CRITICAL = const(50) # type: int
|
||||
|
||||
_leveldict = {
|
||||
DEBUG: ('DEBUG', '32'),
|
||||
|
@ -17,11 +19,11 @@ _leveldict = {
|
|||
CRITICAL: ('CRITICAL', '1;31'),
|
||||
}
|
||||
|
||||
level = NOTSET
|
||||
color = True
|
||||
level = NOTSET # type: int
|
||||
color = True # type: bool
|
||||
|
||||
|
||||
def _log(name, mlevel, msg, *args):
|
||||
def _log(name: str, mlevel: int, msg: str, *args: Any) -> None:
|
||||
if __debug__ and mlevel >= level:
|
||||
if color:
|
||||
fmt = '%d \x1b[35m%s\x1b[0m %s \x1b[' + \
|
||||
|
@ -31,26 +33,26 @@ def _log(name, mlevel, msg, *args):
|
|||
print(fmt % ((utime.ticks_us(), name, _leveldict[mlevel][0]) + args))
|
||||
|
||||
|
||||
def debug(name, msg, *args):
|
||||
def debug(name: str, msg: str, *args: Any) -> None:
|
||||
_log(name, DEBUG, msg, *args)
|
||||
|
||||
|
||||
def info(name, msg, *args):
|
||||
def info(name: str, msg: str, *args: Any) -> None:
|
||||
_log(name, INFO, msg, *args)
|
||||
|
||||
|
||||
def warning(name, msg, *args):
|
||||
def warning(name: str, msg: str, *args: Any) -> None:
|
||||
_log(name, WARNING, msg, *args)
|
||||
|
||||
|
||||
def error(name, msg, *args):
|
||||
def error(name: str, msg: str, *args: Any) -> None:
|
||||
_log(name, ERROR, msg, *args)
|
||||
|
||||
|
||||
def exception(name, exc):
|
||||
def exception(name: str, exc: BaseException) -> None:
|
||||
_log(name, ERROR, 'exception:')
|
||||
sys.print_exception(exc)
|
||||
|
||||
|
||||
def critical(name, msg, *args):
|
||||
def critical(name: str, msg: str, *args: Any) -> None:
|
||||
_log(name, CRITICAL, msg, *args)
|
||||
|
|
|
@ -8,6 +8,8 @@ See `schedule_task`, `run_forever`, and syscalls `Sleep`, `Select`, `Signal`
|
|||
and `Wait`.
|
||||
'''
|
||||
|
||||
from typing import *
|
||||
|
||||
import utime
|
||||
import utimeq
|
||||
from micropython import const
|
||||
|
@ -19,12 +21,13 @@ TOUCH_START = const(1) # event
|
|||
TOUCH_MOVE = const(2) # event
|
||||
TOUCH_END = const(4) # event
|
||||
|
||||
after_step_hook = None # function, called after each task step
|
||||
# function, called after each task step
|
||||
after_step_hook = None # type: Optional[Callable[[], Any]]
|
||||
|
||||
_MAX_SELECT_DELAY = const(1000000) # usec delay if queue is empty
|
||||
_MAX_QUEUE_SIZE = const(64) # maximum number of scheduled tasks
|
||||
|
||||
_paused_tasks = {} # {message interface: [task]}
|
||||
_paused_tasks = {} # type: Dict[int, List[Coroutine]]
|
||||
_scheduled_tasks = utimeq.utimeq(_MAX_QUEUE_SIZE)
|
||||
|
||||
if __debug__:
|
||||
|
@ -35,7 +38,7 @@ if __debug__:
|
|||
log_delay_rb = array.array('i', [0] * log_delay_rb_len)
|
||||
|
||||
|
||||
def schedule_task(task, value=None, deadline=None):
|
||||
def schedule_task(task: Coroutine, value: Any = None, deadline: int = None) -> None:
|
||||
'''
|
||||
Schedule task to be executed with `value` on given `deadline` (in
|
||||
microseconds). Does not start the event loop itself, see `run_forever`.
|
||||
|
@ -45,7 +48,7 @@ def schedule_task(task, value=None, deadline=None):
|
|||
_scheduled_tasks.push(deadline, task, value)
|
||||
|
||||
|
||||
def unschedule_task(task):
|
||||
def unschedule_task(task: Coroutine) -> None:
|
||||
'''
|
||||
Remove task from the time queue. Cancels previous `schedule_task`.
|
||||
'''
|
||||
|
@ -59,20 +62,20 @@ def unschedule_task(task):
|
|||
_scheduled_tasks = queue_copy
|
||||
|
||||
|
||||
def _pause_task(task, iface):
|
||||
def _pause_task(task: Coroutine, iface: int) -> None:
|
||||
tasks = _paused_tasks.get(iface, None)
|
||||
if tasks is None:
|
||||
tasks = _paused_tasks[iface] = []
|
||||
tasks.append(task)
|
||||
|
||||
|
||||
def _unpause_task(task):
|
||||
def _unpause_task(task: Coroutine) -> None:
|
||||
for iface in _paused_tasks:
|
||||
if task in _paused_tasks[iface]:
|
||||
_paused_tasks[iface].remove(task)
|
||||
|
||||
|
||||
def run_forever():
|
||||
def run_forever() -> None:
|
||||
'''
|
||||
Loop forever, stepping through scheduled tasks and awaiting I/O events
|
||||
inbetween. Use `schedule_task` first to add a coroutine to the task queue.
|
||||
|
@ -111,10 +114,10 @@ def run_forever():
|
|||
_step_task(task_entry[1], task_entry[2])
|
||||
|
||||
|
||||
def _step_task(task, value):
|
||||
def _step_task(task: Coroutine, value: Any) -> None:
|
||||
try:
|
||||
if isinstance(value, Exception):
|
||||
result = task.throw(value)
|
||||
result = task.throw(value) # type: ignore
|
||||
else:
|
||||
result = task.send(value)
|
||||
except StopIteration as e:
|
||||
|
@ -128,20 +131,25 @@ def _step_task(task, value):
|
|||
schedule_task(task)
|
||||
else:
|
||||
log.error(__name__, '%s is unknown syscall', result)
|
||||
if after_step_hook:
|
||||
if after_step_hook is not None:
|
||||
after_step_hook()
|
||||
|
||||
|
||||
class Syscall:
|
||||
class Syscall(Awaitable):
|
||||
'''
|
||||
When tasks want to perform any I/O, or do any sort of communication with the
|
||||
scheduler, they do so through instances of a class derived from `Syscall`.
|
||||
'''
|
||||
|
||||
def __iter__(self):
|
||||
# support `yield from` or `await` on syscalls
|
||||
def handle(self, task: Coroutine) -> None:
|
||||
pass
|
||||
|
||||
def __iter__(self) -> Generator:
|
||||
'''Support `yield from` or `await` on syscalls.'''
|
||||
return (yield self)
|
||||
|
||||
__await__ = __iter__
|
||||
|
||||
|
||||
class Sleep(Syscall):
|
||||
'''
|
||||
|
@ -154,10 +162,10 @@ class Sleep(Syscall):
|
|||
print('missed by %d us', utime.ticks_diff(utime.ticks_us(), planned))
|
||||
'''
|
||||
|
||||
def __init__(self, delay_us):
|
||||
def __init__(self, delay_us: int) -> None:
|
||||
self.delay_us = delay_us
|
||||
|
||||
def handle(self, task):
|
||||
def handle(self, task: Coroutine) -> None:
|
||||
deadline = utime.ticks_add(utime.ticks_us(), self.delay_us)
|
||||
schedule_task(task, deadline, deadline)
|
||||
|
||||
|
@ -173,10 +181,10 @@ class Select(Syscall):
|
|||
event, x, y = await loop.Select(loop.TOUCH) # await touch event
|
||||
'''
|
||||
|
||||
def __init__(self, msg_iface):
|
||||
def __init__(self, msg_iface: int) -> None:
|
||||
self.msg_iface = msg_iface
|
||||
|
||||
def handle(self, task):
|
||||
def handle(self, task: Coroutine) -> None:
|
||||
_pause_task(task, self.msg_iface)
|
||||
|
||||
|
||||
|
@ -198,19 +206,19 @@ class Signal(Syscall):
|
|||
# prints in the next iteration of the event loop
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.value = _NO_VALUE
|
||||
self.task = None
|
||||
self.task = None # type: Optional[Coroutine]
|
||||
|
||||
def handle(self, task):
|
||||
def handle(self, task: Coroutine) -> None:
|
||||
self.task = task
|
||||
self._deliver()
|
||||
|
||||
def send(self, value):
|
||||
def send(self, value: Any) -> None:
|
||||
self.value = value
|
||||
self._deliver()
|
||||
|
||||
def _deliver(self):
|
||||
def _deliver(self) -> None:
|
||||
if self.task is not None and self.value is not _NO_VALUE:
|
||||
schedule_task(self.task, self.value)
|
||||
self.task = None
|
||||
|
@ -241,29 +249,31 @@ class Wait(Syscall):
|
|||
`Wait.__iter__` for explanation. Always use `await`.
|
||||
'''
|
||||
|
||||
def __init__(self, children, wait_for=1, exit_others=True):
|
||||
def __init__(self, children: Iterable[Coroutine], wait_for: int = 1, exit_others: bool = True) -> None:
|
||||
self.children = children
|
||||
self.wait_for = wait_for
|
||||
self.exit_others = exit_others
|
||||
self.scheduled = None
|
||||
self.finished = None
|
||||
self.callback = None
|
||||
self.scheduled = [] # type: List[Coroutine]
|
||||
self.finished = [] # type: List[Coroutine]
|
||||
self.task = None # type: Optional[Coroutine]
|
||||
|
||||
def handle(self, task):
|
||||
self.callback = task
|
||||
self.finished = []
|
||||
self.scheduled = [self._wait(c) for c in self.children]
|
||||
for ct in self.scheduled:
|
||||
schedule_task(ct)
|
||||
def handle(self, task: Coroutine) -> None:
|
||||
self.task = task
|
||||
self.finished.clear()
|
||||
self.scheduled.clear()
|
||||
for child in self.children:
|
||||
child_task = self._wait(child)
|
||||
self.scheduled.append(child_task) # type: ignore # FIXME: https://github.com/python/typing/issues/441
|
||||
schedule_task(child_task) # type: ignore # FIXME: https://github.com/python/typing/issues/441
|
||||
|
||||
def exit(self):
|
||||
def exit(self) -> None:
|
||||
for task in self.scheduled:
|
||||
if task not in self.finished:
|
||||
_unpause_task(task)
|
||||
unschedule_task(task)
|
||||
task.close()
|
||||
|
||||
async def _wait(self, child):
|
||||
async def _wait(self, child: Coroutine) -> None:
|
||||
try:
|
||||
result = await child
|
||||
except Exception as e:
|
||||
|
@ -271,14 +281,15 @@ class Wait(Syscall):
|
|||
else:
|
||||
self._finish(child, result)
|
||||
|
||||
def _finish(self, child, result):
|
||||
def _finish(self, child: Coroutine, result: Any) -> None:
|
||||
self.finished.append(child)
|
||||
if self.wait_for == len(self.finished) or isinstance(result, Exception):
|
||||
if self.exit_others:
|
||||
self.exit()
|
||||
schedule_task(self.callback, result)
|
||||
if self.task is not None:
|
||||
schedule_task(self.task, result)
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> Generator:
|
||||
try:
|
||||
return (yield self)
|
||||
except:
|
||||
|
|
|
@ -1,42 +1,15 @@
|
|||
import sys
|
||||
|
||||
sys.path.append('lib')
|
||||
|
||||
import gc
|
||||
|
||||
from typing import *
|
||||
from trezor import loop
|
||||
from trezor import workflow
|
||||
from trezor import log
|
||||
|
||||
log.level = log.DEBUG
|
||||
# log.level = log.INFO
|
||||
|
||||
|
||||
def perf_info_debug():
|
||||
while True:
|
||||
queue_len = len(loop._scheduled_tasks)
|
||||
|
||||
delay_avg = sum(loop.log_delay_rb) / loop.log_delay_rb_len
|
||||
delay_last = loop.log_delay_rb[loop.log_delay_pos]
|
||||
|
||||
mem_alloc = gc.mem_alloc()
|
||||
gc.collect()
|
||||
log.debug(__name__, "mem_alloc: %s/%s, delay_avg: %d, delay_last: %d, queue_len: %d",
|
||||
mem_alloc, gc.mem_alloc(), delay_avg, delay_last, queue_len)
|
||||
|
||||
yield loop.Sleep(1000000)
|
||||
|
||||
|
||||
def perf_info():
|
||||
while True:
|
||||
gc.collect()
|
||||
log.info(__name__, "mem_alloc: %d", gc.mem_alloc())
|
||||
yield loop.Sleep(1000000)
|
||||
|
||||
|
||||
def run(default_workflow):
|
||||
# if __debug__:
|
||||
# loop.schedule_task(perf_info_debug())
|
||||
# else:
|
||||
# loop.schedule_task(perf_info())
|
||||
def run(default_workflow: Callable[[], Coroutine]) -> None:
|
||||
workflow.start_default(default_workflow)
|
||||
loop.run_forever()
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
from typing import List, Union
|
||||
from trezormsg import Msg, USB, HID, VCP
|
||||
|
||||
_msg = Msg()
|
||||
|
||||
|
||||
def init_usb(usb, ifaces):
|
||||
def init_usb(usb: USB, ifaces: List[Union[HID, VCP]]) -> None:
|
||||
return _msg.init_usb(usb, ifaces)
|
||||
|
||||
|
||||
def select(timeout_us):
|
||||
def select(timeout_us: int) -> tuple:
|
||||
return _msg.select(timeout_us)
|
||||
|
||||
|
||||
def send(iface, msg):
|
||||
return _msg.send(iface, msg)
|
||||
def send(iface: int, message: bytes) -> int:
|
||||
return _msg.send(iface, message)
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
from typing import *
|
||||
|
||||
try:
|
||||
from .resources import resdata
|
||||
except ImportError:
|
||||
resdata = None
|
||||
resdata = {}
|
||||
|
||||
|
||||
def load(name):
|
||||
def load(name: str) -> bytes:
|
||||
'''
|
||||
Loads resource of a given name as bytes.
|
||||
'''
|
||||
if resdata and name in resdata:
|
||||
if name in resdata:
|
||||
return resdata[name]
|
||||
with open(name, 'rb') as f:
|
||||
with open(name, 'rb') as f: # type: IO[bytes]
|
||||
return f.read()
|
||||
|
||||
|
||||
def gettext(message):
|
||||
def gettext(message: str) -> str:
|
||||
'''
|
||||
Returns localized string. This function is aliased to _.
|
||||
'''
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Any, Callable, Generator, Optional, Tuple
|
||||
|
||||
from micropython import const
|
||||
|
||||
import sys
|
||||
|
@ -17,65 +19,62 @@ def rgbcolor(r: int, g: int, b: int) -> int:
|
|||
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)
|
||||
|
||||
|
||||
LIGHT_RED = rgbcolor(0xFF, 0x00, 0x00)
|
||||
# RED E4572E
|
||||
RED = rgbcolor(0xE4, 0x57, 0x2E)
|
||||
# ACTIVE DARK RED A64022
|
||||
ACTIVE_RED = rgbcolor(0xA6, 0x40, 0x22)
|
||||
PINK = rgbcolor(0xE9, 0x1E, 0x63)
|
||||
PURPLE = rgbcolor(0x9C, 0x27, 0xB0)
|
||||
# colors
|
||||
RED = rgbcolor(0xE4, 0x57, 0x2E) # RED E4572E
|
||||
ACTIVE_RED = rgbcolor(0xA6, 0x40, 0x22) # ACTIVE DARK RED A64022
|
||||
LIGHT_RED = rgbcolor(0xFF, 0x00, 0x00)
|
||||
PINK = rgbcolor(0xE9, 0x1E, 0x63)
|
||||
PURPLE = rgbcolor(0x9C, 0x27, 0xB0)
|
||||
DEEP_PURPLE = rgbcolor(0x67, 0x3A, 0xB7)
|
||||
INDIGO = rgbcolor(0x3F, 0x51, 0xB5)
|
||||
BLUE = rgbcolor(0x21, 0x96, 0xF3)
|
||||
LIGHT_BLUE = rgbcolor(0x03, 0xA9, 0xF4)
|
||||
CYAN = rgbcolor(0x00, 0xBC, 0xD4)
|
||||
TEAL = rgbcolor(0x00, 0x96, 0x88)
|
||||
|
||||
# GREEN 4CC148
|
||||
GREEN = rgbcolor(0x4C, 0xC1, 0x48)
|
||||
# ACTIVE DARK GREEN 1A8C14
|
||||
ACTIVE_GREEN = rgbcolor(0x1A, 0x8C, 0x14)
|
||||
|
||||
INDIGO = rgbcolor(0x3F, 0x51, 0xB5)
|
||||
BLUE = rgbcolor(0x21, 0x96, 0xF3)
|
||||
LIGHT_BLUE = rgbcolor(0x03, 0xA9, 0xF4)
|
||||
CYAN = rgbcolor(0x00, 0xBC, 0xD4)
|
||||
TEAL = rgbcolor(0x00, 0x96, 0x88)
|
||||
GREEN = rgbcolor(0x4C, 0xC1, 0x48) # GREEN 4CC148
|
||||
ACTIVE_GREEN = rgbcolor(0x1A, 0x8C, 0x14) # ACTIVE DARK GREEN 1A8C14
|
||||
LIGHT_GREEN = rgbcolor(0x87, 0xCE, 0x26)
|
||||
LIME = rgbcolor(0xCD, 0xDC, 0x39)
|
||||
YELLOW = rgbcolor(0xFF, 0xEB, 0x3B)
|
||||
AMBER = rgbcolor(0xFF, 0xC1, 0x07)
|
||||
ORANGE = rgbcolor(0xFF, 0x98, 0x00)
|
||||
LIME = rgbcolor(0xCD, 0xDC, 0x39)
|
||||
YELLOW = rgbcolor(0xFF, 0xEB, 0x3B)
|
||||
AMBER = rgbcolor(0xFF, 0xC1, 0x07)
|
||||
ORANGE = rgbcolor(0xFF, 0x98, 0x00)
|
||||
DEEP_ORANGE = rgbcolor(0xFF, 0x57, 0x22)
|
||||
BROWN = rgbcolor(0x79, 0x55, 0x48)
|
||||
LIGHT_GREY = rgbcolor(0xDA, 0xDD, 0xD8)
|
||||
GREY = rgbcolor(0x9E, 0x9E, 0x9E)
|
||||
DARK_GREY = rgbcolor(0x3E, 0x3E, 0x3E)
|
||||
BLUE_GRAY = rgbcolor(0x60, 0x7D, 0x8B)
|
||||
BLACK = rgbcolor(0x00, 0x00, 0x00)
|
||||
WHITE = rgbcolor(0xFA, 0xFA, 0xFA)
|
||||
BROWN = rgbcolor(0x79, 0x55, 0x48)
|
||||
LIGHT_GREY = rgbcolor(0xDA, 0xDD, 0xD8)
|
||||
GREY = rgbcolor(0x9E, 0x9E, 0x9E)
|
||||
DARK_GREY = rgbcolor(0x3E, 0x3E, 0x3E)
|
||||
BLUE_GRAY = rgbcolor(0x60, 0x7D, 0x8B)
|
||||
BLACK = rgbcolor(0x00, 0x00, 0x00)
|
||||
WHITE = rgbcolor(0xFA, 0xFA, 0xFA)
|
||||
BLACKISH = rgbcolor(0x20, 0x20, 0x20)
|
||||
|
||||
BLACKISH = rgbcolor(0x20, 0x20, 0x20)
|
||||
MONO = Display.FONT_MONO
|
||||
# fonts
|
||||
MONO = Display.FONT_MONO
|
||||
NORMAL = Display.FONT_NORMAL
|
||||
BOLD = Display.FONT_BOLD
|
||||
BOLD = Display.FONT_BOLD
|
||||
|
||||
# radius for buttons and other elements
|
||||
BTN_RADIUS = const(2)
|
||||
|
||||
# display width and height
|
||||
SCREEN = const(240)
|
||||
|
||||
# backlight vlaues
|
||||
BACKLIGHT_NORMAL = const(60)
|
||||
BACKLIGHT_DIM = const(5)
|
||||
BACKLIGHT_NONE = const(2)
|
||||
BACKLIGHT_MAX = const(255)
|
||||
|
||||
# display width and height
|
||||
SCREEN = const(240)
|
||||
|
||||
# icons
|
||||
ICON_RESET = 'trezor/res/header_icons/reset.toig'
|
||||
ICON_WIPE = 'trezor/res/header_icons/wipe.toig'
|
||||
ICON_RESET = 'trezor/res/header_icons/reset.toig'
|
||||
ICON_WIPE = 'trezor/res/header_icons/wipe.toig'
|
||||
ICON_RECOVERY = 'trezor/res/header_icons/recovery.toig'
|
||||
|
||||
|
||||
def in_area(pos: tuple, area: tuple) -> bool:
|
||||
def in_area(pos: Tuple[int, int], area: Tuple[int, int, int, int]) -> bool:
|
||||
x, y = pos
|
||||
ax, ay, aw, ah = area
|
||||
return ax <= x <= ax + aw and ay <= y <= ay + ah
|
||||
return (ax <= x <= ax + aw) and (ay <= y <= ay + ah)
|
||||
|
||||
|
||||
def lerpi(a: int, b: int, t: float) -> int:
|
||||
|
@ -88,66 +87,75 @@ def blend(ca: int, cb: int, t: float) -> int:
|
|||
lerpi((ca << 3) & 0xF8, (cb << 3) & 0xF8, t))
|
||||
|
||||
|
||||
async def alert(count=3):
|
||||
def alert(count: int = 3) -> Generator:
|
||||
sleep_short = loop.sleep(20000)
|
||||
sleep_long = loop.sleep(80000)
|
||||
current = display.backlight()
|
||||
for i in range(count * 2):
|
||||
if i % 2 == 0:
|
||||
display.backlight(BACKLIGHT_MAX)
|
||||
yield loop.Sleep(20000)
|
||||
yield sleep_short
|
||||
else:
|
||||
display.backlight(BACKLIGHT_NORMAL)
|
||||
yield loop.Sleep(80000)
|
||||
yield sleep_long
|
||||
display.backlight(current)
|
||||
|
||||
|
||||
async def backlight_slide(val, speed=20000):
|
||||
def backlight_slide(target: int, delay: int = 20000) -> Generator:
|
||||
sleep = loop.sleep(delay)
|
||||
current = display.backlight()
|
||||
for i in range(current, val, -1 if current > val else 1):
|
||||
for i in range(current, target, -1 if current > target else 1):
|
||||
display.backlight(i)
|
||||
await loop.Sleep(speed)
|
||||
yield sleep
|
||||
|
||||
|
||||
def animate_pulse(func, ca, cb, speed=200000, delay=30000):
|
||||
def animate_pulse(func: Callable[[int], Any],
|
||||
color_a: int,
|
||||
color_b: int,
|
||||
speed: int = 200000,
|
||||
delay: int = 30000) -> Generator:
|
||||
sleep = loop.sleep(delay)
|
||||
while True:
|
||||
# normalize sin from interval -1:1 to 0:1
|
||||
y = 0.5 + 0.5 * math.sin(utime.ticks_us() / speed)
|
||||
c = blend(ca, cb, y)
|
||||
c = blend(color_a, color_b, y)
|
||||
func(c)
|
||||
yield loop.Sleep(delay)
|
||||
yield sleep
|
||||
|
||||
|
||||
def header(title, icon=ICON_RESET, fg=BLACK, bg=BLACK):
|
||||
def header(title: str, icon: str = ICON_RESET, fg: int = BLACK, bg: int = BLACK) -> None:
|
||||
display.bar(0, 0, 240, 32, bg)
|
||||
if icon is not None:
|
||||
display.icon(8, 4, res.load(icon), fg, bg)
|
||||
display.text(8 + 24 + 2, 24, title, BOLD, fg, bg)
|
||||
|
||||
|
||||
def rotate_coords(pos: tuple) -> tuple:
|
||||
def rotate_coords(pos: Tuple[int, int]) -> Tuple[int, int]:
|
||||
r = display.orientation()
|
||||
if r == 0:
|
||||
return pos
|
||||
x, y = pos
|
||||
if r == 90:
|
||||
return (y, 240 - x)
|
||||
if r == 180:
|
||||
elif r == 180:
|
||||
return (240 - x, 240 - y)
|
||||
if r == 270:
|
||||
else: # r == 270:
|
||||
return (240 - y, x)
|
||||
|
||||
|
||||
class Widget:
|
||||
|
||||
def render(self):
|
||||
def render(self) -> None:
|
||||
pass
|
||||
|
||||
def touch(self, event, pos):
|
||||
def touch(self, event: int, pos: Tuple[int, int]) -> Optional[int]:
|
||||
pass
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> Generator:
|
||||
touch = loop.select(loop.TOUCH)
|
||||
while True:
|
||||
self.render()
|
||||
event, *pos = yield loop.Select(loop.TOUCH)
|
||||
event, *pos = yield touch
|
||||
result = self.touch(event, pos)
|
||||
if result is not None:
|
||||
return result
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from typing import *
|
||||
from micropython import const
|
||||
from trezor import ui, loop
|
||||
from . import display, in_area, rotate_coords, Widget
|
||||
|
@ -80,11 +81,13 @@ BTN_DISABLED = const(8)
|
|||
|
||||
class Button(Widget):
|
||||
|
||||
def __init__(self, area, content,
|
||||
normal_style=None,
|
||||
active_style=None,
|
||||
disabled_style=None,
|
||||
absolute=False):
|
||||
def __init__(self,
|
||||
area: Tuple[int, int, int, int],
|
||||
content: str,
|
||||
normal_style: Dict = None,
|
||||
active_style: Dict = None,
|
||||
disabled_style: Dict = None,
|
||||
absolute: bool = False) -> None:
|
||||
self.area = area
|
||||
self.content = content
|
||||
self.normal_style = normal_style or DEFAULT_BUTTON
|
||||
|
@ -93,17 +96,17 @@ class Button(Widget):
|
|||
self.absolute = absolute
|
||||
self.state = BTN_DIRTY
|
||||
|
||||
def enable(self):
|
||||
def enable(self) -> None:
|
||||
self.state &= ~BTN_DISABLED
|
||||
self.state |= BTN_DIRTY
|
||||
|
||||
def disable(self):
|
||||
def disable(self) -> None:
|
||||
self.state |= BTN_DISABLED | BTN_DIRTY
|
||||
|
||||
def taint(self):
|
||||
def taint(self) -> None:
|
||||
self.state |= BTN_DIRTY
|
||||
|
||||
def render(self):
|
||||
def render(self) -> None:
|
||||
if not self.state & BTN_DIRTY:
|
||||
return
|
||||
state = self.state & ~BTN_DIRTY
|
||||
|
@ -138,7 +141,7 @@ class Button(Widget):
|
|||
|
||||
self.state = state
|
||||
|
||||
def touch(self, event, pos):
|
||||
def touch(self, event: int, pos: Tuple[int, int]) -> Optional[int]:
|
||||
if self.state & BTN_DISABLED:
|
||||
return
|
||||
if not self.absolute:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from typing import *
|
||||
from micropython import const
|
||||
from trezor import loop
|
||||
from trezor.ui import Widget
|
||||
|
@ -12,7 +13,10 @@ CANCELLED = const(2)
|
|||
|
||||
class ConfirmDialog(Widget):
|
||||
|
||||
def __init__(self, content=None, confirm='Confirm', cancel='Cancel'):
|
||||
def __init__(self,
|
||||
content: Widget = None,
|
||||
confirm: str = 'Confirm',
|
||||
cancel: str = 'Cancel') -> None:
|
||||
self.content = content
|
||||
if cancel is not None:
|
||||
self.confirm = Button((121, 240 - 48, 119, 48), confirm,
|
||||
|
@ -27,12 +31,12 @@ class ConfirmDialog(Widget):
|
|||
normal_style=CONFIRM_BUTTON,
|
||||
active_style=CONFIRM_BUTTON_ACTIVE)
|
||||
|
||||
def render(self):
|
||||
def render(self) -> None:
|
||||
self.confirm.render()
|
||||
if self.cancel is not None:
|
||||
self.cancel.render()
|
||||
|
||||
def touch(self, event, pos):
|
||||
def touch(self, event: int, pos: Tuple[int, int]) -> Optional[int]:
|
||||
if self.confirm.touch(event, pos) == BTN_CLICKED:
|
||||
return CONFIRMED
|
||||
|
||||
|
@ -40,27 +44,31 @@ class ConfirmDialog(Widget):
|
|||
if self.cancel.touch(event, pos) == BTN_CLICKED:
|
||||
return CANCELLED
|
||||
|
||||
async def __iter__(self):
|
||||
async def __iter__(self): # type: ignore
|
||||
return await loop.Wait((super().__iter__(), self.content))
|
||||
|
||||
|
||||
class HoldToConfirmDialog(Widget):
|
||||
|
||||
def __init__(self, content=None, hold='Hold to confirm', *args, **kwargs):
|
||||
def __init__(self,
|
||||
content: Widget = None,
|
||||
hold: str = 'Hold to confirm',
|
||||
*args: Any,
|
||||
**kwargs: Any) -> None:
|
||||
self.button = Button((0, 240 - 48, 240, 48), hold,
|
||||
normal_style=CONFIRM_BUTTON,
|
||||
active_style=CONFIRM_BUTTON_ACTIVE)
|
||||
self.content = content
|
||||
self.loader = Loader(*args, **kwargs)
|
||||
|
||||
def render(self):
|
||||
def render(self) -> None:
|
||||
if self.loader.is_active():
|
||||
self.loader.render()
|
||||
elif self.content is not None:
|
||||
self.content.render()
|
||||
self.button.render()
|
||||
|
||||
def touch(self, event, pos):
|
||||
def touch(self, event: int, pos: Tuple[int, int]) -> Optional[int]:
|
||||
button = self.button
|
||||
was_started = button.state & BTN_STARTED
|
||||
button.touch(event, pos)
|
||||
|
@ -75,18 +83,19 @@ class HoldToConfirmDialog(Widget):
|
|||
if self.content is not None:
|
||||
return self.content.touch(event, pos)
|
||||
|
||||
async def __iter__(self):
|
||||
return await loop.Wait((self._render_loop(), self._event_loop()))
|
||||
async def __iter__(self): # type: ignore
|
||||
return await loop.wait((self._render_loop(), self._event_loop()))
|
||||
|
||||
def _render_loop(self):
|
||||
RENDER_DELAY = const(1000000 // 60)
|
||||
def _render_loop(self) -> Generator:
|
||||
sleep = loop.sleep(1000000 // 60)
|
||||
while True:
|
||||
self.render()
|
||||
yield loop.Sleep(RENDER_DELAY)
|
||||
yield sleep
|
||||
|
||||
def _event_loop(self):
|
||||
def _event_loop(self) -> Generator:
|
||||
touch = loop.select(loop.TOUCH)
|
||||
while True:
|
||||
event, *pos = yield loop.Select(loop.TOUCH)
|
||||
event, *pos = yield touch
|
||||
result = self.touch(event, pos)
|
||||
if result is not None:
|
||||
return result
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from typing import Generator, Iterable, Optional, Tuple
|
||||
from trezor import ui, res, loop
|
||||
from trezor.crypto import bip39
|
||||
from trezor.ui import display
|
||||
|
@ -17,7 +18,14 @@ KEY_BUTTON_ACTIVE = {
|
|||
}
|
||||
|
||||
|
||||
def cell_area(i, n_x=3, n_y=3, start_x=0, start_y=40, end_x=240, end_y=240 - 48, spacing=0):
|
||||
def cell_area(i: int,
|
||||
n_x: int = 3,
|
||||
n_y: int = 3,
|
||||
start_x: int = 0,
|
||||
start_y: int = 40,
|
||||
end_x: int = ui.SCREEN,
|
||||
end_y: int = ui.SCREEN - 48,
|
||||
spacing: int = 0) -> Tuple[int, int, int, int]:
|
||||
w = (end_x - start_x) // n_x
|
||||
h = (end_y - start_y) // n_y
|
||||
x = (i % n_x) * w
|
||||
|
@ -25,7 +33,7 @@ def cell_area(i, n_x=3, n_y=3, start_x=0, start_y=40, end_x=240, end_y=240 - 48,
|
|||
return (x + start_x, y + start_y, w - spacing, h - spacing)
|
||||
|
||||
|
||||
def key_buttons():
|
||||
def key_buttons() -> Iterable[Button]:
|
||||
keys = ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz']
|
||||
# keys = [' ', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']
|
||||
return [Button(cell_area(i), k,
|
||||
|
@ -33,7 +41,7 @@ def key_buttons():
|
|||
active_style=KEY_BUTTON_ACTIVE) for i, k in enumerate(keys)]
|
||||
|
||||
|
||||
def compute_mask(text):
|
||||
def compute_mask(text: str) -> int:
|
||||
mask = 0
|
||||
for c in text:
|
||||
shift = ord(c) - 97 # ord('a') == 97
|
||||
|
@ -45,11 +53,11 @@ def compute_mask(text):
|
|||
|
||||
class KeyboardMultiTap(ui.Widget):
|
||||
|
||||
def __init__(self, content=''):
|
||||
def __init__(self, content: str = '') -> None:
|
||||
self.content = content
|
||||
self.sugg_mask = 0xffffffff
|
||||
self.sugg_word = None
|
||||
self.pending_button = None
|
||||
self.sugg_word = None # type: Optional[str]
|
||||
self.pending_button = None # type: Optional[Button]
|
||||
self.pending_index = 0
|
||||
|
||||
self.key_buttons = key_buttons()
|
||||
|
@ -59,7 +67,7 @@ class KeyboardMultiTap(ui.Widget):
|
|||
normal_style=CLEAR_BUTTON,
|
||||
active_style=CLEAR_BUTTON_ACTIVE)
|
||||
|
||||
def render(self):
|
||||
def render(self) -> None:
|
||||
|
||||
# clear canvas under input line
|
||||
display.bar(0, 0, 205, 40, ui.BLACK)
|
||||
|
@ -90,7 +98,7 @@ class KeyboardMultiTap(ui.Widget):
|
|||
for btn in self.key_buttons:
|
||||
btn.render()
|
||||
|
||||
def touch(self, event, pos):
|
||||
def touch(self, event: int, pos: Tuple[int, int]) -> None:
|
||||
if self.bs_button.touch(event, pos) == BTN_CLICKED:
|
||||
self.content = self.content[:-1]
|
||||
self.pending_button = None
|
||||
|
@ -120,7 +128,7 @@ class KeyboardMultiTap(ui.Widget):
|
|||
self.pending_index = 0
|
||||
return
|
||||
|
||||
def _update_suggestion(self):
|
||||
def _update_suggestion(self) -> None:
|
||||
if self.content:
|
||||
self.sugg_word = bip39.find_word(self.content)
|
||||
self.sugg_mask = bip39.complete_word(self.content)
|
||||
|
@ -128,14 +136,14 @@ class KeyboardMultiTap(ui.Widget):
|
|||
self.sugg_word = None
|
||||
self.sugg_mask = 0xffffffff
|
||||
|
||||
def _update_buttons(self):
|
||||
def _update_buttons(self) -> None:
|
||||
for btn in self.key_buttons:
|
||||
if compute_mask(btn.content) & self.sugg_mask:
|
||||
btn.enable()
|
||||
else:
|
||||
btn.disable()
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> Generator:
|
||||
timeout = loop.Sleep(1000 * 1000 * 1)
|
||||
touch = loop.Select(loop.TOUCH)
|
||||
wait = loop.Wait((touch, timeout))
|
||||
|
@ -152,7 +160,7 @@ class KeyboardMultiTap(ui.Widget):
|
|||
self._update_buttons()
|
||||
|
||||
|
||||
def zoom_buttons(keys, upper=False):
|
||||
def zoom_buttons(keys: str, upper: bool = False) -> Iterable[Button]:
|
||||
n_x = len(keys)
|
||||
if upper:
|
||||
keys = keys + keys.upper()
|
||||
|
@ -164,18 +172,18 @@ def zoom_buttons(keys, upper=False):
|
|||
|
||||
class KeyboardZooming(ui.Widget):
|
||||
|
||||
def __init__(self, content='', uppercase=True):
|
||||
def __init__(self, content: str = '', uppercase: bool = True) -> None:
|
||||
self.content = content
|
||||
self.uppercase = uppercase
|
||||
|
||||
self.zoom_buttons = None
|
||||
self.zoom_buttons = () # type: Iterable[Button]
|
||||
self.key_buttons = key_buttons()
|
||||
self.bs_button = Button((240 - 35, 5, 30, 30),
|
||||
res.load('trezor/res/pin_close.toig'),
|
||||
normal_style=CLEAR_BUTTON,
|
||||
active_style=CLEAR_BUTTON_ACTIVE)
|
||||
|
||||
def render(self):
|
||||
def render(self) -> None:
|
||||
self.render_input()
|
||||
if self.zoom_buttons:
|
||||
for btn in self.zoom_buttons:
|
||||
|
@ -184,7 +192,7 @@ class KeyboardZooming(ui.Widget):
|
|||
for btn in self.key_buttons:
|
||||
btn.render()
|
||||
|
||||
def render_input(self):
|
||||
def render_input(self) -> None:
|
||||
if self.content:
|
||||
display.bar(0, 0, 200, 40, ui.BLACK)
|
||||
else:
|
||||
|
@ -193,27 +201,26 @@ class KeyboardZooming(ui.Widget):
|
|||
if self.content:
|
||||
self.bs_button.render()
|
||||
|
||||
def touch(self, event, pos):
|
||||
def touch(self, event: int, pos: Tuple[int, int]) -> None:
|
||||
if self.bs_button.touch(event, pos) == BTN_CLICKED:
|
||||
self.content = self.content[:-1]
|
||||
self.bs_button.taint()
|
||||
return
|
||||
if self.zoom_buttons:
|
||||
return self.touch_zoom(event, pos)
|
||||
elif self.zoom_buttons:
|
||||
self.touch_zoom(event, pos)
|
||||
else:
|
||||
return self.touch_keyboard(event, pos)
|
||||
self.touch_keyboard(event, pos)
|
||||
|
||||
def touch_zoom(self, event, pos):
|
||||
def touch_zoom(self, event: int, pos: Tuple[int, int]) -> None:
|
||||
for btn in self.zoom_buttons:
|
||||
if btn.touch(event, pos) == BTN_CLICKED:
|
||||
self.content += btn.content
|
||||
self.zoom_buttons = None
|
||||
self.zoom_buttons = ()
|
||||
for btn in self.key_buttons:
|
||||
btn.taint()
|
||||
self.bs_button.taint()
|
||||
break
|
||||
|
||||
def touch_keyboard(self, event, pos):
|
||||
def touch_keyboard(self, event: int, pos: Tuple[int, int]) -> None:
|
||||
for btn in self.key_buttons:
|
||||
if btn.touch(event, pos) == BTN_CLICKED:
|
||||
self.zoom_buttons = zoom_buttons(btn.content, self.uppercase)
|
||||
|
@ -222,7 +229,7 @@ class KeyboardZooming(ui.Widget):
|
|||
self.bs_button.taint()
|
||||
break
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> Generator:
|
||||
timeout = loop.Sleep(1000 * 1000 * 1)
|
||||
touch = loop.Select(loop.TOUCH)
|
||||
wait = loop.Wait((touch, timeout))
|
||||
|
@ -233,7 +240,7 @@ class KeyboardZooming(ui.Widget):
|
|||
event, *pos = result
|
||||
self.touch(event, pos)
|
||||
elif self.zoom_buttons:
|
||||
self.zoom_buttons = None
|
||||
self.zoom_buttons = ()
|
||||
for btn in self.key_buttons:
|
||||
btn.taint()
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from typing import Dict, Optional
|
||||
import utime
|
||||
from micropython import const
|
||||
from trezor import ui
|
||||
|
@ -21,36 +22,38 @@ _LOADER_MSEC = const(1000)
|
|||
|
||||
class Loader(ui.Widget):
|
||||
|
||||
def __init__(self, normal_style=None, active_style=None):
|
||||
self.start_ticks_ms = None
|
||||
def __init__(self,
|
||||
normal_style: Dict = None,
|
||||
active_style: Dict = None) -> None:
|
||||
self.start_ticks_ms = 0
|
||||
self.normal_style = normal_style or DEFAULT_LOADER
|
||||
self.active_style = active_style or DEFAULT_LOADER_ACTIVE
|
||||
|
||||
def start(self):
|
||||
def start(self) -> None:
|
||||
self.start_ticks_ms = utime.ticks_ms()
|
||||
ui.display.bar(0, 32, 240, 240 - 80, ui.BLACK)
|
||||
|
||||
def stop(self):
|
||||
def stop(self) -> bool:
|
||||
ui.display.bar(0, 32, 240, 240 - 80, ui.BLACK)
|
||||
ticks_diff = utime.ticks_ms() - self.start_ticks_ms
|
||||
self.start_ticks_ms = None
|
||||
self.start_ticks_ms = 0
|
||||
return ticks_diff >= _LOADER_MSEC
|
||||
|
||||
def is_active(self):
|
||||
return self.start_ticks_ms is not None
|
||||
def is_active(self) -> bool:
|
||||
return self.start_ticks_ms == 0
|
||||
|
||||
def render(self):
|
||||
def render(self) -> None:
|
||||
progress = min(utime.ticks_ms() - self.start_ticks_ms, _LOADER_MSEC)
|
||||
if progress == _LOADER_MSEC:
|
||||
style = self.active_style
|
||||
else:
|
||||
style = self.normal_style
|
||||
if style['icon'] is None:
|
||||
ui.display.loader(
|
||||
ui.display.loader( # type: ignore # suppress the dict lookup failure
|
||||
progress, -8, style['fg-color'], style['bg-color'])
|
||||
elif style['icon-fg-color'] is None:
|
||||
ui.display.loader(
|
||||
ui.display.loader( # type: ignore # suppress the dict lookup failure
|
||||
progress, -8, style['fg-color'], style['bg-color'], style['icon'])
|
||||
else:
|
||||
ui.display.loader(
|
||||
ui.display.loader( # type: ignore # suppress the dict lookup failure
|
||||
progress, -8, style['fg-color'], style['bg-color'], style['icon'], style['icon-fg-color'])
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from typing import List, Tuple
|
||||
from micropython import const
|
||||
from trezor import ui, res
|
||||
from trezor.crypto import random
|
||||
|
@ -5,7 +6,7 @@ from trezor.ui import display
|
|||
from trezor.ui.button import Button, BTN_CLICKED, CLEAR_BUTTON, CLEAR_BUTTON_ACTIVE
|
||||
|
||||
|
||||
def digit_area(i):
|
||||
def digit_area(i: int) -> Tuple[int, int, int, int]:
|
||||
width = const(80)
|
||||
height = const(48)
|
||||
x = (i % 3) * width
|
||||
|
@ -15,7 +16,7 @@ def digit_area(i):
|
|||
return (x, y + 48, width - 1, height - 1)
|
||||
|
||||
|
||||
def generate_digits():
|
||||
def generate_digits() -> List[int]:
|
||||
digits = list(range(1, 10)) # 1-9
|
||||
random.shuffle(digits)
|
||||
return digits
|
||||
|
@ -23,7 +24,7 @@ def generate_digits():
|
|||
|
||||
class PinMatrix(ui.Widget):
|
||||
|
||||
def __init__(self, label, pin=''):
|
||||
def __init__(self, label: str, pin: str = '') -> None:
|
||||
self.label = label
|
||||
self.pin = pin
|
||||
self.digits = generate_digits()
|
||||
|
@ -39,7 +40,7 @@ class PinMatrix(ui.Widget):
|
|||
normal_style=CLEAR_BUTTON,
|
||||
active_style=CLEAR_BUTTON_ACTIVE)
|
||||
|
||||
def render(self):
|
||||
def render(self) -> None:
|
||||
|
||||
header = '*' * len(self.pin) if self.pin else self.label
|
||||
|
||||
|
@ -67,7 +68,7 @@ class PinMatrix(ui.Widget):
|
|||
# display.bar(0, 95, 240, 2, ui.blend(ui.BLACK, ui.WHITE, 0.25))
|
||||
# display.bar(0, 142, 240, 2, ui.blend(ui.BLACK, ui.WHITE, 0.25))
|
||||
|
||||
def touch(self, event, pos):
|
||||
def touch(self, event: int, pos: Tuple[int, int]) -> None:
|
||||
if self.clear_button.touch(event, pos) == BTN_CLICKED:
|
||||
self.pin = ''
|
||||
for btn in self.pin_buttons:
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
from typing import *
|
||||
from trezor import ui
|
||||
|
||||
|
||||
class Qr(ui.Widget):
|
||||
|
||||
def __init__(self, data, pos, scale):
|
||||
def __init__(self,
|
||||
data: bytes,
|
||||
pos: Tuple[int, int],
|
||||
scale: int) -> None:
|
||||
self.data = data
|
||||
self.pos = pos
|
||||
self.scale = scale
|
||||
|
||||
def render(self):
|
||||
def render(self) -> None:
|
||||
ui.display.qrcode(self.pos[0], self.pos[1], self.data, self.scale)
|
||||
|
|
|
@ -3,7 +3,7 @@ from trezor import loop, ui
|
|||
from .swipe import Swipe, SWIPE_UP, SWIPE_DOWN
|
||||
|
||||
|
||||
async def change_page(page, page_count):
|
||||
async def change_page(page: int, page_count: int) -> int:
|
||||
while True:
|
||||
s = await Swipe()
|
||||
if s == SWIPE_UP and page < page_count - 1:
|
||||
|
@ -12,7 +12,7 @@ async def change_page(page, page_count):
|
|||
return page - 1 # scroll up
|
||||
|
||||
|
||||
async def paginate(render_page, page_count, page=0, *args):
|
||||
async def paginate(render_page, page_count: int, page: int = 0, *args):
|
||||
while True:
|
||||
changer = change_page(page, page_count)
|
||||
renderer = render_page(page, page_count, *args)
|
||||
|
@ -24,17 +24,17 @@ async def paginate(render_page, page_count, page=0, *args):
|
|||
return result
|
||||
|
||||
|
||||
async def animate_swipe():
|
||||
async def animate_swipe() -> None:
|
||||
await ui.animate_pulse(render_swipe_icon, ui.GREY, ui.DARK_GREY, speed=300000, delay=200000)
|
||||
|
||||
|
||||
def render_swipe_icon(fg):
|
||||
def render_swipe_icon(fg: int):
|
||||
ui.display.bar_radius(102, 214, 36, 4, fg, ui.BLACK, 2)
|
||||
ui.display.bar_radius(106, 222, 28, 4, fg, ui.BLACK, 2)
|
||||
ui.display.bar_radius(110, 230, 20, 4, fg, ui.BLACK, 2)
|
||||
|
||||
|
||||
def render_scrollbar(page, page_count):
|
||||
def render_scrollbar(page: int, page_count: int) -> None:
|
||||
screen_height = const(220)
|
||||
size = const(10)
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from typing import *
|
||||
import utime
|
||||
from micropython import const
|
||||
from trezor import loop, ui
|
||||
|
@ -15,15 +16,17 @@ SWIPE_RIGHT = const(270)
|
|||
|
||||
class Swipe(ui.Widget):
|
||||
|
||||
def __init__(self, area=None, absolute=False):
|
||||
def __init__(self,
|
||||
area: Tuple[int, int, int, int]=None,
|
||||
absolute: bool = False) -> None:
|
||||
self.area = area or (0, 0, ui.SCREEN, ui.SCREEN)
|
||||
self.absolute = absolute
|
||||
self.start_pos = None
|
||||
self.start_time = 0
|
||||
self.light_origin = None
|
||||
self.start_pos = None # type: Optional[Tuple[int, int]]
|
||||
self.start_time = 0 # type: float
|
||||
self.light_origin = ui.BACKLIGHT_NORMAL
|
||||
self.light_target = ui.BACKLIGHT_NONE
|
||||
|
||||
def touch(self, event, pos):
|
||||
def touch(self, event: int, pos: Tuple[int, int]) -> Optional[int]:
|
||||
|
||||
if not self.absolute:
|
||||
pos = rotate_coords(pos)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from typing import Union
|
||||
from micropython import const
|
||||
from trezor import ui
|
||||
|
||||
|
@ -8,12 +9,15 @@ TEXT_MARGIN_LEFT = const(10)
|
|||
|
||||
class Text(ui.Widget):
|
||||
|
||||
def __init__(self, header_text, header_icon, *content):
|
||||
def __init__(self,
|
||||
header_text: str,
|
||||
header_icon: str,
|
||||
*content: Union[str, int]) -> None:
|
||||
self.header_text = header_text
|
||||
self.header_icon = header_icon
|
||||
self.content = content
|
||||
|
||||
def render(self):
|
||||
def render(self) -> None:
|
||||
offset_x = TEXT_MARGIN_LEFT
|
||||
offset_y = TEXT_LINE_HEIGHT + TEXT_HEADER_HEIGHT
|
||||
style = ui.NORMAL
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
from typing import Callable, Generator, Iterator, List, Union
|
||||
import sys
|
||||
import gc
|
||||
|
||||
from trezorutils import halt, memcpy
|
||||
|
||||
|
||||
def _gf():
|
||||
def _gf() -> Generator:
|
||||
yield
|
||||
|
||||
|
||||
type_gen = type(_gf())
|
||||
|
||||
|
||||
def _unimport_func(func):
|
||||
def inner(*args, **kwargs):
|
||||
def _unimport_func(func): # type: ignore
|
||||
def inner(*args, **kwargs): # type: ignore
|
||||
mods = set(sys.modules)
|
||||
try:
|
||||
ret = func(*args, **kwargs)
|
||||
|
@ -25,8 +26,8 @@ def _unimport_func(func):
|
|||
return inner
|
||||
|
||||
|
||||
def _unimport_gen(genfunc):
|
||||
async def inner(*args, **kwargs):
|
||||
def _unimport_gen(genfunc): # type: ignore
|
||||
async def inner(*args, **kwargs): # type: ignore
|
||||
mods = set(sys.modules)
|
||||
try:
|
||||
ret = await genfunc(*args, **kwargs)
|
||||
|
@ -39,18 +40,18 @@ def _unimport_gen(genfunc):
|
|||
return inner
|
||||
|
||||
|
||||
def unimport(func):
|
||||
def unimport(func): # type: ignore
|
||||
if isinstance(func, type_gen):
|
||||
return _unimport_gen(func)
|
||||
return _unimport_gen(func) # type: ignore
|
||||
else:
|
||||
return _unimport_func(func)
|
||||
return _unimport_func(func) # type: ignore
|
||||
|
||||
|
||||
def chunks(items, size):
|
||||
def chunks(items: List, size: int) -> Iterator[List]:
|
||||
for i in range(0, len(items), size):
|
||||
yield items[i:i + size]
|
||||
|
||||
|
||||
def ensure(cond):
|
||||
def ensure(cond: bool) -> None:
|
||||
if not cond:
|
||||
raise AssertionError()
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from typing import Callable, Coroutine, List, Optional
|
||||
from trezor import log, loop, ui
|
||||
|
||||
_started = []
|
||||
_default = None
|
||||
_default_genfunc = None
|
||||
_started = [] # type: List[Coroutine]
|
||||
_default = None # type: Optional[Coroutine]
|
||||
_default_genfunc = None # type: Optional[Callable[[], Coroutine]]
|
||||
|
||||
|
||||
def start_default(genfunc):
|
||||
def start_default(genfunc: Callable[[], Coroutine]) -> None:
|
||||
global _default
|
||||
global _default_genfunc
|
||||
_default_genfunc = genfunc
|
||||
|
@ -15,23 +16,23 @@ def start_default(genfunc):
|
|||
ui.display.backlight(ui.BACKLIGHT_NORMAL)
|
||||
|
||||
|
||||
def close_default():
|
||||
def close_default() -> None:
|
||||
global _default
|
||||
log.info(__name__, 'close default %s', _default)
|
||||
_default.close()
|
||||
_default = None
|
||||
|
||||
|
||||
def start(workflow):
|
||||
if _default is not None:
|
||||
close_default()
|
||||
log.info(__name__, 'close default %s', _default)
|
||||
_default.close()
|
||||
_default = None
|
||||
|
||||
|
||||
def start(workflow: Coroutine) -> None:
|
||||
close_default()
|
||||
_started.append(workflow)
|
||||
log.info(__name__, 'start %s', workflow)
|
||||
loop.schedule_task(_watch(workflow))
|
||||
loop.schedule_task(_wrap(workflow)) # type: ignore # FIXME: https://github.com/python/typing/issues/441
|
||||
|
||||
|
||||
async def _wrap(workflow: Coroutine):
|
||||
ui.display.backlight(ui.BACKLIGHT_NORMAL)
|
||||
|
||||
|
||||
async def _watch(workflow):
|
||||
try:
|
||||
return await workflow
|
||||
finally:
|
||||
|
|
|
@ -25,14 +25,12 @@ def split_to_parts(line, mod_desc=None):
|
|||
current_method = line[4:].split('(')[0]
|
||||
|
||||
yield (current_package, "\n")
|
||||
|
||||
if current_class is None:
|
||||
yield (current_package, '# ' + mod_desc + "\n")
|
||||
else:
|
||||
current_indent = 4
|
||||
|
||||
line = current_indent * ' ' + line
|
||||
|
||||
yield (current_package, line)
|
||||
|
||||
|
||||
|
@ -107,6 +105,7 @@ def clear_directory(top_dir):
|
|||
|
||||
os.rmdir(root)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
clear_directory('../mocks/generated')
|
||||
build_directory('../micropython/extmod', '../mocks/generated')
|
||||
|
|
Loading…
Reference in New Issue