feat(core): hold homescreen to lock

pull/1504/head
Martin Milata 3 years ago
parent db2db8e6f3
commit 192d0dcf87

@ -79,6 +79,10 @@ if __debug__:
content = await layout_change_chan.take()
await ctx.write(DebugLinkLayout(lines=content))
async def touch_hold(x: int, y: int, duration_ms: int) -> None:
await loop.sleep(duration_ms)
loop.synthetic_events.append((io.TOUCH, (io.TOUCH_END, x, y)))
async def dispatch_DebugLinkWatchLayout(
ctx: wire.Context, msg: DebugLinkWatchLayout
) -> Success:
@ -94,11 +98,14 @@ if __debug__:
if debuglink_decision_chan.putters:
log.warning(__name__, "DebugLinkDecision queue is not empty")
if msg.x is not None:
if msg.x is not None and msg.y is not None:
evt_down = io.TOUCH_START, msg.x, msg.y
evt_up = io.TOUCH_END, msg.x, msg.y
loop.synthetic_events.append((io.TOUCH, evt_down))
loop.synthetic_events.append((io.TOUCH, evt_up))
if msg.hold_ms is not None:
loop.schedule(touch_hold(msg.x, msg.y, msg.hold_ms))
else:
loop.synthetic_events.append((io.TOUCH, evt_up))
else:
debuglink_decision_chan.publish(msg)

@ -1,26 +1,11 @@
import storage.device
from trezor import io, loop, res, ui
from trezor import res, ui
class HomescreenBase(ui.Layout):
RENDER_SLEEP = loop.SLEEP_FOREVER
def __init__(self) -> None:
super().__init__()
self.label = storage.device.get_label() or "My Trezor"
self.image = storage.device.get_homescreen() or res.load(
"apps/homescreen/res/bg.toif"
)
def on_tap(self) -> None:
"""Called when the user taps the screen."""
pass
def dispatch(self, event: int, x: int, y: int) -> None:
if event is ui.REPAINT:
self.repaint = True
elif event is ui.RENDER and self.repaint:
self.repaint = False
self.on_render()
elif event is io.TOUCH_END:
self.on_tap()

@ -1,12 +1,25 @@
import utime
from micropython import const
import storage
import storage.device
from trezor import config, ui
from trezor.ui.loader import Loader, LoaderNeutral
from apps.base import lock_device
from . import HomescreenBase
if False:
from typing import Optional
_LOADER_DELAY_MS = const(500)
_LOADER_TOTAL_MS = const(2500)
async def homescreen() -> None:
await Homescreen()
lock_device()
class Homescreen(HomescreenBase):
@ -15,7 +28,17 @@ class Homescreen(HomescreenBase):
if not storage.device.is_initialized():
self.label = "Go to trezor.io/start"
self.loader = Loader(
style=LoaderNeutral,
target_ms=_LOADER_TOTAL_MS - _LOADER_DELAY_MS,
offset_y=-10,
)
self.touch_ms: Optional[int] = None
def on_render(self) -> None:
if not self.repaint:
return
# warning bar on top
if storage.device.is_initialized() and storage.device.no_backup():
ui.header_error("SEEDLESS")
@ -33,3 +56,39 @@ class Homescreen(HomescreenBase):
# homescreen with shifted avatar and text on bottom
ui.display.avatar(48, 48 - 10, self.image, ui.WHITE, ui.BLACK)
ui.display.text_center(ui.WIDTH // 2, 220, self.label, ui.BOLD, ui.FG, ui.BG)
self.repaint = False
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:
self.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 _loader_start(self) -> None:
ui.display.clear()
ui.display.text_center(ui.WIDTH // 2, 35, "Hold to lock", ui.BOLD, ui.FG, ui.BG)
self.loader.start()
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
self._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,4 +1,4 @@
from trezor import res, ui, wire
from trezor import loop, res, ui, wire
from . import HomescreenBase
@ -21,6 +21,7 @@ async def lockscreen() -> None:
class Lockscreen(HomescreenBase):
BACKLIGHT_LEVEL = ui.BACKLIGHT_LOW
RENDER_SLEEP = loop.SLEEP_FOREVER
def __init__(self, bootscreen: bool = False) -> None:
if bootscreen:
@ -34,6 +35,9 @@ class Lockscreen(HomescreenBase):
super().__init__()
def on_render(self) -> None:
if not self.repaint:
return
# homescreen with label text on top
ui.display.text_center(
ui.WIDTH // 2, 35, self.label, ui.BOLD, ui.TITLE_GREY, ui.BG
@ -53,5 +57,7 @@ class Lockscreen(HomescreenBase):
)
ui.display.icon(45, 202, res.load(ui.ICON_CLICK), ui.TITLE_GREY, ui.BG)
def on_tap(self) -> None:
self.repaint = False
def on_touch_end(self, _x: int, _y: int) -> None:
raise ui.Result(None)

@ -18,8 +18,8 @@ class LoaderDefault:
class active(normal):
bg_color = ui.BG
fg_color = ui.GREEN
icon = ui.ICON_CHECK
icon_fg_color = ui.WHITE
icon: Optional[str] = ui.ICON_CHECK
icon_fg_color: Optional[int] = ui.WHITE
class LoaderDanger(LoaderDefault):
@ -30,21 +30,36 @@ class LoaderDanger(LoaderDefault):
fg_color = ui.RED
class LoaderNeutral(LoaderDefault):
class normal(LoaderDefault.normal):
fg_color = ui.FG
class active(LoaderDefault.active):
fg_color = ui.FG
if False:
LoaderStyleType = Type[LoaderDefault]
_TARGET_MS = const(1000)
_OFFSET_Y = const(-24)
class Loader(ui.Component):
def __init__(self, style: LoaderStyleType = LoaderDefault) -> None:
def __init__(
self,
style: LoaderStyleType = LoaderDefault,
target_ms: int = _TARGET_MS,
offset_y: int = _OFFSET_Y,
) -> None:
super().__init__()
self.normal_style = style.normal
self.active_style = style.active
self.target_ms = _TARGET_MS
self.target_ms = target_ms
self.start_ms: Optional[int] = None
self.stop_ms: Optional[int] = None
self.offset_y = offset_y
def start(self) -> None:
self.start_ms = utime.ticks_ms()
@ -75,13 +90,18 @@ class Loader(ui.Component):
else:
s = self.active_style
_Y = const(-24)
progress = r * 1000 // target
if s.icon is None:
display.loader(r, False, _Y, s.fg_color, s.bg_color)
display.loader(progress, False, self.offset_y, s.fg_color, s.bg_color)
else:
display.loader(
r, False, _Y, s.fg_color, s.bg_color, res.load(s.icon), s.icon_fg_color
progress,
False,
self.offset_y,
s.fg_color,
s.bg_color,
res.load(s.icon),
s.icon_fg_color,
)
if (r == 0) and (self.stop_ms is not None):
self.start_ms = None

@ -122,7 +122,16 @@ class DebugLink:
state = self._call(messages.DebugLinkGetState(wait_word_pos=True))
return state.reset_word_pos
def input(self, word=None, button=None, swipe=None, x=None, y=None, wait=False):
def input(
self,
word=None,
button=None,
swipe=None,
x=None,
y=None,
wait=False,
hold_ms=None,
):
if not self.allow_interactions:
return
@ -131,7 +140,7 @@ class DebugLink:
raise ValueError("Invalid input - must use one of word, button, swipe")
decision = messages.DebugLinkDecision(
yes_no=button, swipe=swipe, input=word, x=x, y=y, wait=wait
yes_no=button, swipe=swipe, input=word, x=x, y=y, wait=wait, hold_ms=hold_ms
)
ret = self._call(decision, nowait=not wait)
if ret is not None:

@ -0,0 +1,62 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2021 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import time
import pytest
from .. import buttons, common
PIN4 = "1234"
@pytest.mark.setup_client(pin=PIN4)
def test_hold_to_lock(device_handler):
debug = device_handler.debuglink()
def hold(duration, wait=True):
debug.input(x=13, y=37, hold_ms=duration, wait=wait)
time.sleep(duration / 1000 + 0.5)
assert device_handler.features().unlocked is False
# unlock with message
device_handler.run(common.get_test_address)
layout = debug.wait_layout()
assert layout.text == "PinDialog"
debug.input("1234", wait=True)
assert device_handler.result()
assert device_handler.features().unlocked is True
# short touch
hold(1000, wait=False)
assert device_handler.features().unlocked is True
# lock
hold(3500)
assert device_handler.features().unlocked is False
# unlock by touching
layout = debug.click(buttons.INFO, wait=True)
assert layout.text == "PinDialog"
debug.input("1234", wait=True)
assert device_handler.features().unlocked is True
# lock
hold(3500)
assert device_handler.features().unlocked is False
Loading…
Cancel
Save