parent
12b3dc23db
commit
5b3db7eca1
After Width: | Height: | Size: 182 B |
After Width: | Height: | Size: 224 B |
@ -0,0 +1,266 @@
|
||||
use crate::{
|
||||
time::{Duration, Instant},
|
||||
ui::{
|
||||
component::{Component, Empty, Event, EventCtx, Pad, TimerToken},
|
||||
display::{self, Color, Font},
|
||||
event::{TouchEvent, USBEvent},
|
||||
geometry::{Offset, Point, Rect},
|
||||
model_tt::constant,
|
||||
util::icon_text_center,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{theme, Loader, LoaderMsg, NotificationFrame};
|
||||
|
||||
const AREA: Rect = constant::screen();
|
||||
const TOP_CENTER: Point = AREA.top_center();
|
||||
const LABEL_Y: i16 = 216;
|
||||
const LOCKED_Y: i16 = 101;
|
||||
const TAP_Y: i16 = 134;
|
||||
const HOLD_Y: i16 = 35;
|
||||
const LOADER_OFFSET: Offset = Offset::y(-10);
|
||||
const LOADER_DELAY: Duration = Duration::from_millis(500);
|
||||
const LOADER_DURATION: Duration = Duration::from_millis(2000);
|
||||
|
||||
pub struct Homescreen<T> {
|
||||
label: T,
|
||||
notification: Option<(T, u8)>,
|
||||
hold_to_lock: bool,
|
||||
usb_connected: bool,
|
||||
loader: Loader,
|
||||
pad: Pad,
|
||||
delay: Option<TimerToken>,
|
||||
}
|
||||
|
||||
pub enum HomescreenMsg {
|
||||
Dismissed,
|
||||
}
|
||||
|
||||
impl<T> Homescreen<T>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
pub fn new(label: T, notification: Option<(T, u8)>, hold_to_lock: bool) -> Self {
|
||||
Self {
|
||||
label,
|
||||
notification,
|
||||
hold_to_lock,
|
||||
usb_connected: true,
|
||||
loader: Loader::new().with_durations(LOADER_DURATION, LOADER_DURATION / 3),
|
||||
pad: Pad::with_background(theme::BG),
|
||||
delay: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn level_to_style(level: u8) -> (Color, &'static [u8]) {
|
||||
match level {
|
||||
2 => (theme::VIOLET, theme::ICON_MAGIC),
|
||||
1 => (theme::YELLOW, theme::ICON_WARN),
|
||||
_ => (theme::RED, theme::ICON_WARN),
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_notification(&self) {
|
||||
if !self.usb_connected {
|
||||
let (color, icon) = Self::level_to_style(0);
|
||||
NotificationFrame::<Empty, T>::paint_notification(
|
||||
AREA,
|
||||
icon,
|
||||
"NO USB CONNECTION",
|
||||
color,
|
||||
);
|
||||
} else if let Some((notification, level)) = &self.notification {
|
||||
let (color, icon) = Self::level_to_style(*level);
|
||||
NotificationFrame::<Empty, T>::paint_notification(
|
||||
AREA,
|
||||
icon,
|
||||
notification.as_ref(),
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_loader(&mut self) {
|
||||
display::text_center(
|
||||
TOP_CENTER + Offset::y(HOLD_Y),
|
||||
"HOLD TO LOCK",
|
||||
Font::BOLD,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
);
|
||||
self.loader.paint()
|
||||
}
|
||||
|
||||
fn event_usb(&mut self, ctx: &mut EventCtx, event: Event) {
|
||||
if let Event::USB(USBEvent::Connected(is_connected)) = event {
|
||||
if self.usb_connected != is_connected {
|
||||
self.usb_connected = is_connected;
|
||||
ctx.request_paint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn event_hold(&mut self, ctx: &mut EventCtx, event: Event) -> bool {
|
||||
match event {
|
||||
Event::Touch(TouchEvent::TouchStart(_)) => {
|
||||
if self.loader.is_animating() {
|
||||
self.loader.start_growing(ctx, Instant::now());
|
||||
} else {
|
||||
self.delay = Some(ctx.request_timer(LOADER_DELAY));
|
||||
}
|
||||
}
|
||||
Event::Touch(TouchEvent::TouchEnd(_)) => {
|
||||
self.delay = None;
|
||||
let now = Instant::now();
|
||||
if self.loader.is_completely_grown(now) {
|
||||
return true;
|
||||
}
|
||||
if self.loader.is_animating() {
|
||||
self.loader.start_shrinking(ctx, now);
|
||||
}
|
||||
}
|
||||
Event::Timer(token) if Some(token) == self.delay => {
|
||||
self.delay = None;
|
||||
self.pad.clear();
|
||||
self.loader.start_growing(ctx, Instant::now());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.loader.event(ctx, event) {
|
||||
Some(LoaderMsg::GrownCompletely) => {
|
||||
// Wait for TouchEnd before returning.
|
||||
}
|
||||
Some(LoaderMsg::ShrunkCompletely) => {
|
||||
self.loader.reset();
|
||||
self.pad.clear();
|
||||
ctx.request_paint()
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for Homescreen<T>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
type Msg = HomescreenMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.pad.place(AREA);
|
||||
self.loader.place(AREA.translate(LOADER_OFFSET));
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
Self::event_usb(self, ctx, event);
|
||||
if self.hold_to_lock {
|
||||
Self::event_hold(self, ctx, event).then_some(HomescreenMsg::Dismissed)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
self.pad.paint();
|
||||
if self.loader.is_animating() || self.loader.is_completely_grown(Instant::now()) {
|
||||
self.paint_loader();
|
||||
} else {
|
||||
self.paint_notification();
|
||||
paint_label(self.label.as_ref(), false);
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||
self.loader.bounds(sink);
|
||||
sink(self.pad.area);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T: AsRef<str>> crate::trace::Trace for Homescreen<T> {
|
||||
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
|
||||
d.open("Homescreen");
|
||||
d.field("label", &self.label.as_ref());
|
||||
d.close();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Lockscreen<T> {
|
||||
label: T,
|
||||
bootscreen: bool,
|
||||
}
|
||||
|
||||
impl<T> Lockscreen<T> {
|
||||
pub fn new(label: T, bootscreen: bool) -> Self {
|
||||
Lockscreen { label, bootscreen }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for Lockscreen<T>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
type Msg = HomescreenMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, _ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if let Event::Touch(TouchEvent::TouchEnd(_)) = event {
|
||||
return Some(HomescreenMsg::Dismissed);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
let (locked, tap) = if self.bootscreen {
|
||||
("NOT CONNECTED", "Tap to connect")
|
||||
} else {
|
||||
("LOCKED", "Tap to unlock")
|
||||
};
|
||||
icon_text_center(
|
||||
TOP_CENTER + Offset::y(LOCKED_Y),
|
||||
theme::ICON_LOCK,
|
||||
2,
|
||||
locked,
|
||||
theme::TEXT_BOLD,
|
||||
Offset::zero(),
|
||||
);
|
||||
display::text_center(
|
||||
TOP_CENTER + Offset::y(TAP_Y),
|
||||
tap,
|
||||
Font::NORMAL,
|
||||
theme::OFF_WHITE,
|
||||
theme::BG,
|
||||
);
|
||||
paint_label(self.label.as_ref(), true);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for Lockscreen<T> {
|
||||
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
|
||||
d.open("Lockscreen");
|
||||
d.close();
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_label(label: &str, lockscreen: bool) {
|
||||
let label_color = if lockscreen {
|
||||
theme::GREY_MEDIUM
|
||||
} else {
|
||||
theme::FG
|
||||
};
|
||||
display::text_center(
|
||||
TOP_CENTER + Offset::y(LABEL_Y),
|
||||
label,
|
||||
Font::BOLD,
|
||||
label_color,
|
||||
theme::BG,
|
||||
);
|
||||
}
|
Binary file not shown.
Binary file not shown.
@ -1,47 +1,57 @@
|
||||
from typing import Any
|
||||
|
||||
import storage
|
||||
import storage.cache
|
||||
import storage.device
|
||||
from trezor import ui
|
||||
|
||||
|
||||
class HomescreenBase(ui.Layout):
|
||||
RENDER_INDICATOR: object | None = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.label = storage.device.get_label() or "My Trezor"
|
||||
self.repaint = storage.cache.homescreen_shown is not self.RENDER_INDICATOR
|
||||
|
||||
def get_image(self) -> bytes:
|
||||
from trezor import res
|
||||
|
||||
return storage.device.get_homescreen() or res.load(
|
||||
"apps/homescreen/res/bg.toif"
|
||||
)
|
||||
|
||||
async def __iter__(self) -> Any:
|
||||
# We need to catch the ui.Cancelled exception that kills us, because that means
|
||||
# that we will need to draw on screen again after restart.
|
||||
try:
|
||||
return await super().__iter__()
|
||||
except ui.Cancelled:
|
||||
storage.cache.homescreen_shown = None
|
||||
raise
|
||||
|
||||
def on_render(self) -> None:
|
||||
if not self.repaint:
|
||||
return
|
||||
self.do_render()
|
||||
self.set_repaint(False)
|
||||
|
||||
def do_render(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def set_repaint(self, value: bool) -> None:
|
||||
self.repaint = value
|
||||
storage.cache.homescreen_shown = None if value else self.RENDER_INDICATOR
|
||||
|
||||
def _before_render(self) -> None:
|
||||
if storage.cache.homescreen_shown is not self.RENDER_INDICATOR:
|
||||
super()._before_render()
|
||||
from trezor import config, wire
|
||||
from trezor.ui.layouts.homescreen import Busyscreen, Homescreen, Lockscreen
|
||||
|
||||
from apps.base import busy_expiry_ms, lock_device
|
||||
|
||||
|
||||
async def busyscreen() -> None:
|
||||
await Busyscreen(busy_expiry_ms())
|
||||
|
||||
|
||||
async def homescreen() -> None:
|
||||
if storage.device.is_initialized():
|
||||
label = storage.device.get_label()
|
||||
else:
|
||||
label = "Go to trezor.io/start"
|
||||
|
||||
notification = None
|
||||
notification_is_error = False
|
||||
if storage.device.is_initialized() and storage.device.no_backup():
|
||||
notification = "SEEDLESS"
|
||||
notification_is_error = True
|
||||
elif storage.device.is_initialized() and storage.device.unfinished_backup():
|
||||
notification = "BACKUP FAILED!"
|
||||
notification_is_error = True
|
||||
elif storage.device.is_initialized() and storage.device.needs_backup():
|
||||
notification = "NEEDS BACKUP!"
|
||||
elif storage.device.is_initialized() and not config.has_pin():
|
||||
notification = "PIN NOT SET!"
|
||||
elif storage.device.get_experimental_features():
|
||||
notification = "EXPERIMENTAL MODE!"
|
||||
|
||||
await Homescreen(
|
||||
label=label,
|
||||
notification=notification,
|
||||
notification_is_error=notification_is_error,
|
||||
hold_to_lock=config.has_pin(),
|
||||
)
|
||||
lock_device()
|
||||
|
||||
|
||||
async def lockscreen() -> None:
|
||||
from apps.common.request_pin import can_lock_device
|
||||
from apps.base import unlock_device
|
||||
|
||||
# Only show the lockscreen UI if the device can in fact be locked.
|
||||
if can_lock_device():
|
||||
await Lockscreen(label=storage.device.get_label())
|
||||
# Otherwise proceed directly to unlock() call. If the device is already unlocked,
|
||||
# it should be a no-op storage-wise, but it resets the internal configuration
|
||||
# to an unlocked state.
|
||||
try:
|
||||
await unlock_device()
|
||||
except wire.PinCancelled:
|
||||
pass
|
||||
|
@ -1,33 +0,0 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import storage.cache as storage_cache
|
||||
|
||||
from . import HomescreenBase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor import loop
|
||||
|
||||
|
||||
async def busyscreen() -> None:
|
||||
await Busyscreen()
|
||||
|
||||
|
||||
class Busyscreen(HomescreenBase):
|
||||
RENDER_INDICATOR = storage_cache.BUSYSCREEN_ON
|
||||
|
||||
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
|
||||
return self.handle_rendering(), self.handle_input(), self.handle_expiry()
|
||||
|
||||
def handle_expiry(self) -> loop.Task: # type: ignore [awaitable-is-generator]
|
||||
from apps.base import busy_expiry_ms, set_homescreen
|
||||
from trezor import ui, loop
|
||||
|
||||
yield loop.sleep(busy_expiry_ms())
|
||||
storage_cache.delete(storage_cache.APP_COMMON_BUSY_DEADLINE_MS)
|
||||
set_homescreen()
|
||||
raise ui.Result(None)
|
||||
|
||||
def do_render(self) -> None:
|
||||
from trezor.ui.layouts import show_coinjoin
|
||||
|
||||
show_coinjoin()
|
@ -1,142 +0,0 @@
|
||||
import utime
|
||||
from micropython import const
|
||||
from typing import TYPE_CHECKING, Tuple
|
||||
|
||||
import storage.cache
|
||||
from trezor import config, ui
|
||||
|
||||
from . import HomescreenBase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor import loop
|
||||
|
||||
|
||||
_LOADER_DELAY_MS = const(500)
|
||||
_LOADER_TOTAL_MS = const(2500)
|
||||
|
||||
|
||||
async def homescreen() -> None:
|
||||
from apps.base import lock_device
|
||||
|
||||
await Homescreen()
|
||||
lock_device()
|
||||
|
||||
|
||||
class Homescreen(HomescreenBase):
|
||||
RENDER_INDICATOR = storage.cache.HOMESCREEN_ON
|
||||
|
||||
def __init__(self) -> None:
|
||||
from trezor.ui.loader import Loader, LoaderNeutral
|
||||
import storage.device as storage_device
|
||||
|
||||
super().__init__()
|
||||
if not storage_device.is_initialized():
|
||||
self.label = "Go to trezor.io/start"
|
||||
|
||||
self.loader = Loader(
|
||||
LoaderNeutral,
|
||||
_LOADER_TOTAL_MS - _LOADER_DELAY_MS,
|
||||
-10,
|
||||
3,
|
||||
)
|
||||
self.touch_ms: int | None = None
|
||||
|
||||
def create_tasks(self) -> Tuple[loop.AwaitableTask, ...]:
|
||||
return super().create_tasks() + (self.usb_checker_task(),)
|
||||
|
||||
async def usb_checker_task(self) -> None:
|
||||
from trezor import loop, io
|
||||
|
||||
usbcheck = loop.wait(io.USB_CHECK)
|
||||
while True:
|
||||
await usbcheck
|
||||
self.set_repaint(True)
|
||||
|
||||
def do_render(self) -> None:
|
||||
from trezor import utils
|
||||
from trezor import ui # local_cache_global
|
||||
import storage.device as storage_device
|
||||
|
||||
header_error = ui.header_error # local_cache_attribute
|
||||
header_warning = ui.header_warning # local_cache_attribute
|
||||
display = ui.display # local_cache_attribute
|
||||
model = utils.MODEL # local_cache_attribute
|
||||
|
||||
# warning bar on top
|
||||
if storage_device.is_initialized() and storage_device.no_backup():
|
||||
header_error("SEEDLESS")
|
||||
elif storage_device.is_initialized() and storage_device.unfinished_backup():
|
||||
header_error("BACKUP FAILED!")
|
||||
elif storage_device.is_initialized() and storage_device.needs_backup():
|
||||
header_warning("NEEDS BACKUP!")
|
||||
elif storage_device.is_initialized() and not config.has_pin():
|
||||
header_warning("PIN NOT SET!")
|
||||
elif storage_device.get_experimental_features():
|
||||
header_warning("EXPERIMENTAL MODE!")
|
||||
else:
|
||||
display.bar(0, 0, ui.WIDTH, ui.get_header_height(), ui.BG)
|
||||
|
||||
# homescreen with shifted avatar and text on bottom
|
||||
# Differs for each model
|
||||
|
||||
if not utils.usb_data_connected():
|
||||
header_error("NO USB CONNECTION")
|
||||
|
||||
# TODO: support homescreen avatar change for R and 1
|
||||
if model in ("T",):
|
||||
display.avatar(48, 48 - 10, self.get_image(), ui.WHITE, ui.BLACK)
|
||||
elif model in ("R",):
|
||||
icon = "trezor/res/homescreen_model_r.toif" # 92x92 px
|
||||
display.icon(18, 18, ui.res.load(icon), ui.style.FG, ui.style.BG)
|
||||
elif model in ("1",):
|
||||
icon = "trezor/res/homescreen_model_1.toif" # 64x36 px
|
||||
display.icon(33, 14, ui.res.load(icon), ui.style.FG, ui.style.BG)
|
||||
|
||||
label_heights = {"1": 60, "R": 120, "T": 220}
|
||||
display.text_center(
|
||||
ui.WIDTH // 2,
|
||||
label_heights[model],
|
||||
self.label,
|
||||
ui.BOLD,
|
||||
ui.FG,
|
||||
ui.BG,
|
||||
)
|
||||
|
||||
ui.refresh()
|
||||
|
||||
def on_touch_start(self, _x: int, _y: int) -> None:
|
||||
if self.loader.start_ms is not None:
|
||||
self.loader.start()
|
||||
elif config.has_pin():
|
||||
self.touch_ms = utime.ticks_ms()
|
||||
|
||||
def on_touch_end(self, _x: int, _y: int) -> None:
|
||||
if self.loader.start_ms is not None:
|
||||
ui.display.clear()
|
||||
self.set_repaint(True)
|
||||
self.loader.stop()
|
||||
self.touch_ms = None
|
||||
|
||||
# raise here instead of self.loader.on_finish so as not to send TOUCH_END to the lockscreen
|
||||
if self.loader.elapsed_ms() >= self.loader.target_ms:
|
||||
raise ui.Result(None)
|
||||
|
||||
def dispatch(self, event: int, x: int, y: int) -> None:
|
||||
if (
|
||||
self.touch_ms is not None
|
||||
and self.touch_ms + _LOADER_DELAY_MS < utime.ticks_ms()
|
||||
):
|
||||
self.touch_ms = None
|
||||
|
||||
# _loader_start
|
||||
ui.display.clear()
|
||||
ui.display.text_center(
|
||||
ui.WIDTH // 2, 35, "Hold to lock", ui.BOLD, ui.FG, ui.BG
|
||||
)
|
||||
self.loader.start()
|
||||
# END _loader_start
|
||||
|
||||
if event is ui.RENDER and self.loader.start_ms is not None:
|
||||
self.loader.dispatch(event, x, y)
|
||||
else:
|
||||
super().dispatch(event, x, y)
|
@ -1,67 +0,0 @@
|
||||
import storage.cache
|
||||
from trezor import loop, ui
|
||||
|
||||
from . import HomescreenBase
|
||||
|
||||
|
||||
async def lockscreen() -> None:
|
||||
from trezor import wire
|
||||
|
||||
from apps.common.request_pin import can_lock_device
|
||||
from apps.base import unlock_device
|
||||
|
||||
# Only show the lockscreen UI if the device can in fact be locked.
|
||||
if can_lock_device():
|
||||
await Lockscreen()
|
||||
# Otherwise proceed directly to unlock() call. If the device is already unlocked,
|
||||
# it should be a no-op storage-wise, but it resets the internal configuration
|
||||
# to an unlocked state.
|
||||
try:
|
||||
await unlock_device()
|
||||
except wire.PinCancelled:
|
||||
pass
|
||||
|
||||
|
||||
class Lockscreen(HomescreenBase):
|
||||
BACKLIGHT_LEVEL = ui.BACKLIGHT_LOW
|
||||
RENDER_SLEEP = loop.SLEEP_FOREVER
|
||||
RENDER_INDICATOR = storage.cache.LOCKSCREEN_ON
|
||||
|
||||
def __init__(self, bootscreen: bool = False) -> None:
|
||||
if bootscreen:
|
||||
self.BACKLIGHT_LEVEL = ui.BACKLIGHT_NORMAL
|
||||
self.lock_label = "Not connected"
|
||||
self.tap_label = "Tap to connect"
|
||||
else:
|
||||
self.lock_label = "Locked"
|
||||
self.tap_label = "Tap to unlock"
|
||||
|
||||
super().__init__()
|
||||
|
||||
def do_render(self) -> None:
|
||||
from trezor import res
|
||||
from trezor import ui # local_cache_global
|
||||
|
||||
display = ui.display # local_cache_attribute
|
||||
title_grey = ui.TITLE_GREY # local_cache_attribute
|
||||
bg = ui.BG # local_cache_attribute
|
||||
|
||||
# homescreen with label text on top
|
||||
display.text_center(ui.WIDTH // 2, 35, self.label, ui.BOLD, title_grey, bg)
|
||||
display.avatar(48, 48, self.get_image(), ui.WHITE, ui.BLACK)
|
||||
|
||||
# lock bar
|
||||
display.bar_radius(40, 100, 160, 40, title_grey, bg, 4)
|
||||
display.bar_radius(42, 102, 156, 36, bg, title_grey, 4)
|
||||
display.text_center(
|
||||
ui.WIDTH // 2, 128, self.lock_label, ui.BOLD, title_grey, bg
|
||||
)
|
||||
|
||||
# "tap to unlock"
|
||||
display.text_center(
|
||||
ui.WIDTH // 2 + 10, 220, self.tap_label, ui.BOLD, title_grey, bg
|
||||
)
|
||||
display.icon(45, 202, res.load(ui.ICON_CLICK), title_grey, bg)
|
||||
|
||||
def on_touch_end(self, _x: int, _y: int) -> None:
|
||||
raise ui.Result(None)
|
@ -0,0 +1 @@
|
||||
from .tt_v2.homescreen import * # noqa: F401,F403
|
@ -0,0 +1,126 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import storage.cache as storage_cache
|
||||
from trezor import ui
|
||||
|
||||
import trezorui2
|
||||
|
||||
from . import _RustLayout
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from trezor import io
|
||||
from typing import Any, Tuple
|
||||
|
||||
|
||||
class HomescreenBase(_RustLayout):
|
||||
RENDER_INDICATOR: object | None = None
|
||||
|
||||
def __init__(self, layout: Any) -> None:
|
||||
super().__init__(layout=layout)
|
||||
self.is_connected = True
|
||||
|
||||
async def __iter__(self) -> Any:
|
||||
# We need to catch the ui.Cancelled exception that kills us, because that means
|
||||
# that we will need to draw on screen again after restart.
|
||||
try:
|
||||
return await super().__iter__()
|
||||
except ui.Cancelled:
|
||||
storage_cache.homescreen_shown = None
|
||||
raise
|
||||
|
||||
# In __debug__ mode, ignore {confirm,swipe,input}_signal.
|
||||
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]:
|
||||
return self.handle_timers(), self.handle_input_and_rendering()
|
||||
|
||||
|
||||
class Homescreen(HomescreenBase):
|
||||
RENDER_INDICATOR = storage_cache.HOMESCREEN_ON
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: str | None,
|
||||
notification: str | None,
|
||||
notification_is_error: bool,
|
||||
hold_to_lock: bool,
|
||||
) -> None:
|
||||
level = 1
|
||||
if notification is not None:
|
||||
notification = notification.rstrip("!")
|
||||
if "EXPERIMENTAL" in notification:
|
||||
level = 2
|
||||
elif notification_is_error:
|
||||
level = 0
|
||||
|
||||
super().__init__(
|
||||
layout=trezorui2.show_homescreen(
|
||||
label=label or "My Trezor",
|
||||
notification=notification,
|
||||
notification_level=level,
|
||||
hold=hold_to_lock,
|
||||
),
|
||||
)
|
||||
|
||||
async def usb_checker_task(self) -> None:
|
||||
from trezor import io, loop
|
||||
|
||||
usbcheck = loop.wait(io.USB_CHECK)
|
||||
while True:
|
||||
is_connected = await usbcheck
|
||||
if is_connected != self.is_connected:
|
||||
self.is_connected = is_connected
|
||||
self.layout.usb_event(is_connected)
|
||||
self.layout.paint()
|
||||
storage_cache.homescreen_shown = None
|
||||
|
||||
def create_tasks(self) -> Tuple[loop.AwaitableTask, ...]:
|
||||
return super().create_tasks() + (self.usb_checker_task(),)
|
||||
|
||||
|
||||
class Lockscreen(HomescreenBase):
|
||||
RENDER_INDICATOR = storage_cache.LOCKSCREEN_ON
|
||||
BACKLIGHT_LEVEL = ui.BACKLIGHT_LOW
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: str | None,
|
||||
bootscreen: bool = False,
|
||||
) -> None:
|
||||
self.bootscreen = bootscreen
|
||||
if bootscreen:
|
||||
self.BACKLIGHT_LEVEL = ui.BACKLIGHT_NORMAL
|
||||
|
||||
super().__init__(
|
||||
layout=trezorui2.show_lockscreen(
|
||||
label=label or "My Trezor",
|
||||
bootscreen=bootscreen,
|
||||
),
|
||||
)
|
||||
|
||||
async def __iter__(self) -> Any:
|
||||
result = await super().__iter__()
|
||||
if self.bootscreen:
|
||||
self.request_complete_repaint()
|
||||
return result
|
||||
|
||||
|
||||
class Busyscreen(HomescreenBase):
|
||||
RENDER_INDICATOR = storage_cache.BUSYSCREEN_ON
|
||||
|
||||
def __init__(self, delay_ms: int) -> None:
|
||||
super().__init__(
|
||||
layout=trezorui2.show_busyscreen(
|
||||
title="PLEASE WAIT",
|
||||
description="CoinJoin in progress.\n\nDo not disconnect your\nTrezor.",
|
||||
time_ms=delay_ms,
|
||||
)
|
||||
)
|
||||
|
||||
async def __iter__(self) -> Any:
|
||||
from apps.base import set_homescreen
|
||||
|
||||
# Handle timeout.
|
||||
result = await super().__iter__()
|
||||
assert result == trezorui2.CANCELLED
|
||||
storage_cache.delete(storage_cache.APP_COMMON_BUSY_DEADLINE_MS)
|
||||
set_homescreen()
|
||||
return result
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue