mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-29 00:31:02 +00:00
ui/passphrase: extract new passphrase keyboard
This commit is contained in:
parent
7b89e4b14c
commit
1c9380a9d9
@ -1,57 +1,54 @@
|
||||
from trezor import ui, wire, res
|
||||
from trezor import res, ui, wire
|
||||
|
||||
|
||||
async def request_passphrase(ctx):
|
||||
async def request_passphrase_on_display(ctx):
|
||||
from trezor.messages.FailureType import ActionCancelled
|
||||
from trezor.ui.confirm import ConfirmDialog, CONFIRMED
|
||||
from trezor.ui.passphrase import PassphraseKeyboard
|
||||
|
||||
ui.display.clear()
|
||||
keyboard = PassphraseKeyboard('Enter passphrase')
|
||||
|
||||
while True:
|
||||
result = await keyboard
|
||||
if result:
|
||||
print(result)
|
||||
else:
|
||||
raise wire.FailureError(ActionCancelled, 'Passphrase cancelled')
|
||||
|
||||
|
||||
async def request_passphrase_on_host(ctx):
|
||||
from trezor.messages.FailureType import ActionCancelled
|
||||
from trezor.messages.PassphraseRequest import PassphraseRequest
|
||||
from trezor.messages.wire_types import PassphraseAck, Cancel
|
||||
from trezor.ui.text import Text
|
||||
from trezor.ui.confirm import ConfirmDialog, CONFIRMED
|
||||
|
||||
text = Text(
|
||||
'Passphrase entry', ui.ICON_RESET,
|
||||
'Please, type passphrase', 'on connected host.')
|
||||
ui.display.clear()
|
||||
text.render()
|
||||
ack = await ctx.call(PassphraseRequest(), PassphraseAck, Cancel)
|
||||
if ack.MESSAGE_WIRE_TYPE == Cancel:
|
||||
raise wire.FailureError(ActionCancelled, 'Passphrase cancelled')
|
||||
return ack.passphrase
|
||||
|
||||
|
||||
async def request_passphrase(ctx):
|
||||
from trezor.ui.text import Text
|
||||
from trezor.ui.entry_select import EntrySelector
|
||||
from trezor.ui.keyboard import PassphraseKeyboard
|
||||
|
||||
ui.display.clear()
|
||||
text = Text('Enter passphrase', ui.ICON_RESET,
|
||||
'Where to enter your', 'passphrase?')
|
||||
text = Text(
|
||||
'Enter passphrase', ui.ICON_RESET,
|
||||
'Where to enter your', 'passphrase?')
|
||||
entry = EntrySelector(text)
|
||||
entry_type = await entry
|
||||
|
||||
if entry_type == 1:
|
||||
ui.display.clear()
|
||||
text = Text('Passphrase entry', ui.ICON_RESET,
|
||||
'Please, type passphrase', 'on connected host.')
|
||||
text.render()
|
||||
ack = await ctx.call(PassphraseRequest(), PassphraseAck, Cancel)
|
||||
if ack.MESSAGE_WIRE_TYPE == Cancel:
|
||||
raise wire.FailureError(ActionCancelled, 'Passphrase cancelled')
|
||||
return ack.passphrase
|
||||
return await request_passphrase_on_host(ctx)
|
||||
else:
|
||||
def onchange():
|
||||
c = dialog.cancel
|
||||
if keyboard.passphrase:
|
||||
c.content = res.load(ui.ICON_CLEAR)
|
||||
else:
|
||||
c.content = res.load(ui.ICON_LOCK)
|
||||
c.taint()
|
||||
c.render()
|
||||
|
||||
ui.display.clear()
|
||||
keyboard = PassphraseKeyboard('Enter passphrase')
|
||||
keyboard.onchange = onchange
|
||||
dialog = ConfirmDialog(keyboard)
|
||||
dialog.cancel.area = ui.grid(12)
|
||||
dialog.confirm.area = ui.grid(14)
|
||||
keyboard.onchange()
|
||||
|
||||
while True:
|
||||
result = await dialog
|
||||
if result == CONFIRMED:
|
||||
return keyboard.passphrase
|
||||
elif result != CONFIRMED and keyboard.passphrase:
|
||||
keyboard.change('')
|
||||
continue
|
||||
else:
|
||||
raise wire.FailureError(ActionCancelled, 'Passphrase cancelled')
|
||||
return await request_passphrase_on_display(ctx)
|
||||
|
||||
|
||||
async def protect_by_passphrase(ctx):
|
||||
|
@ -12,12 +12,8 @@ class EntrySelector(Widget):
|
||||
|
||||
def __init__(self, content):
|
||||
self.content = content
|
||||
self.device = Button(ui.grid(8, n_y=4, n_x=4, cells_x=4), 'Device',
|
||||
normal_style=ui.BTN_KEY,
|
||||
active_style=ui.BTN_KEY_ACTIVE)
|
||||
self.host = Button(ui.grid(12, n_y=4, n_x=4, cells_x=4), 'Host',
|
||||
normal_style=ui.BTN_KEY,
|
||||
active_style=ui.BTN_KEY_ACTIVE)
|
||||
self.device = Button(ui.grid(8, n_y=4, n_x=4, cells_x=4), 'Device')
|
||||
self.host = Button(ui.grid(12, n_y=4, n_x=4, cells_x=4), 'Host')
|
||||
|
||||
def render(self):
|
||||
self.device.render()
|
||||
|
@ -1,41 +1,14 @@
|
||||
from trezor import ui, res, loop, io
|
||||
from trezor import io, loop, res, ui
|
||||
from trezor.crypto import bip39
|
||||
from trezor.ui import display
|
||||
from trezor.ui.button import Button, BTN_CLICKED, ICON
|
||||
from .swipe import Swipe, SWIPE_LEFT, SWIPE_RIGHT, SWIPE_HORIZONTAL
|
||||
from trezor.ui.button import BTN_CLICKED, ICON, Button
|
||||
|
||||
KEYBOARD = {
|
||||
'0': ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
|
||||
'1': ['_', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz', '*#'],
|
||||
'2': ['_', 'ABC', 'DEF', 'GHI', 'JKL', 'MNO', 'PQRS', 'TUV', 'WXYZ', '*#'],
|
||||
'3': ['_', '.', '/', '!', '+', '-', '?', ',', ';', '$']
|
||||
}
|
||||
MNEMONIC_KEYS = ('abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz')
|
||||
|
||||
def key_buttons():
|
||||
keys = ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz']
|
||||
return [
|
||||
Button(ui.grid(i + 3, n_y=4), k, style=ui.BTN_KEY)
|
||||
for i, k in enumerate(keys)
|
||||
]
|
||||
|
||||
def render_scrollbar(page):
|
||||
bbox = const(240)
|
||||
size = const(8)
|
||||
padding = 12
|
||||
page_count = len(KEYBOARD)
|
||||
def key_buttons(keys):
|
||||
return [Button(ui.grid(i + 3, n_y=4), k) for i, k in enumerate(keys)]
|
||||
|
||||
if page_count * padding > bbox:
|
||||
padding = bbox // page_count
|
||||
|
||||
x = (bbox // 2) - (page_count // 2) * padding
|
||||
y = 44
|
||||
|
||||
for i in range(0, page_count):
|
||||
if i != page:
|
||||
ui.display.bar_radius(x + i * padding, y, size,
|
||||
size, ui.DARK_GREY, ui.BG, size // 2)
|
||||
ui.display.bar_radius(x + page * padding, y, size,
|
||||
size, ui.FG, ui.BG, size // 2)
|
||||
|
||||
def compute_mask(text: str) -> int:
|
||||
mask = 0
|
||||
@ -46,14 +19,6 @@ def compute_mask(text: str) -> int:
|
||||
mask |= 1 << shift
|
||||
return mask
|
||||
|
||||
def digit_area(i):
|
||||
if i == 9: # 0-position
|
||||
i = 10 # display it in the middle
|
||||
return ui.grid(i + 3) # skip the first line
|
||||
|
||||
def generate_keyboard(index):
|
||||
digits = list(range(0, 10)) # 0-9
|
||||
return digits
|
||||
|
||||
class Input(Button):
|
||||
def __init__(self, area: tuple, content: str='', word: str=''):
|
||||
@ -109,41 +74,6 @@ class Input(Button):
|
||||
iy = ty - ICON
|
||||
display.icon(ix, iy, res.load(i), fg_color, bg_color)
|
||||
|
||||
class PassphraseKeyboard(ui.Widget):
|
||||
def __init__(self, label):
|
||||
self.label = label
|
||||
self.passphrase = ''
|
||||
self.index = 1
|
||||
self.keyboard_type = 1
|
||||
self.keys = KEYBOARD[str(self.keyboard_type)]
|
||||
|
||||
self.key_buttons = [Button(digit_area(i), d)
|
||||
for i, d in enumerate(self.keys)]
|
||||
self.onchange = None
|
||||
|
||||
def render(self):
|
||||
# clear canvas under input line
|
||||
display.bar(0, 0, 240, 45, ui.BG)
|
||||
|
||||
# input line with a header
|
||||
header = self.passphrase if self.passphrase else self.label
|
||||
display.text_center(120, 32, header, ui.BOLD, ui.GREY, ui.BG)
|
||||
render_scrollbar(self.keyboard_type)
|
||||
# pin matrix buttons
|
||||
for btn in self.key_buttons:
|
||||
btn.render()
|
||||
|
||||
def touch(self, event, pos):
|
||||
for btn in self.key_buttons:
|
||||
if btn.touch(event, pos) == BTN_CLICKED:
|
||||
self.change(self.passphrase + btn.content)
|
||||
break
|
||||
|
||||
def change(self, passphrase):
|
||||
self.passphrase = passphrase
|
||||
if self.onchange:
|
||||
self.onchange()
|
||||
|
||||
|
||||
class MnemonicKeyboard(ui.Widget):
|
||||
def __init__(self, prompt: str=''):
|
||||
@ -152,7 +82,7 @@ class MnemonicKeyboard(ui.Widget):
|
||||
self.back = Button(ui.grid(0, n_x=4, n_y=4),
|
||||
res.load(ui.ICON_BACK),
|
||||
style=ui.BTN_CLEAR)
|
||||
self.keys = key_buttons()
|
||||
self.keys = key_buttons(MNEMONIC_KEYS)
|
||||
self.pbutton = None # pending key button
|
||||
self.pindex = 0 # index of current pending char in pbutton
|
||||
|
||||
|
170
src/trezor/ui/passphrase.py
Normal file
170
src/trezor/ui/passphrase.py
Normal file
@ -0,0 +1,170 @@
|
||||
from micropython import const
|
||||
from trezor import io, loop, ui, res
|
||||
from trezor.ui import display
|
||||
from trezor.ui.button import BTN_CLICKED, ICON, Button
|
||||
from trezor.ui.swipe import SWIPE_HORIZONTAL, SWIPE_LEFT, SWIPE_RIGHT, Swipe
|
||||
|
||||
|
||||
KEYBOARD_KEYS = (
|
||||
('1', '2', '3', '4', '5', '6', '7', '8', '9', '0'),
|
||||
('_', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz', '*#'),
|
||||
('_', 'ABC', 'DEF', 'GHI', 'JKL', 'MNO', 'PQRS', 'TUV', 'WXYZ', '*#'),
|
||||
('_', '.', '/', '!', '+', '-', '?', ',', ';', '$'))
|
||||
|
||||
|
||||
def digit_area(i):
|
||||
if i == 9: # 0-position
|
||||
i = 10 # display it in the middle
|
||||
return ui.grid(i + 3) # skip the first line
|
||||
|
||||
|
||||
def key_buttons(keys):
|
||||
return [Button(digit_area(i), str(k)) for i, k in enumerate(keys)]
|
||||
|
||||
|
||||
def render_scrollbar(page):
|
||||
bbox = const(240)
|
||||
size = const(8)
|
||||
padding = 12
|
||||
page_count = len(KEYBOARD_KEYS)
|
||||
|
||||
if page_count * padding > bbox:
|
||||
padding = bbox // page_count
|
||||
|
||||
x = (bbox // 2) - (page_count // 2) * padding
|
||||
y = 44
|
||||
|
||||
for i in range(0, page_count):
|
||||
if i != page:
|
||||
ui.display.bar_radius(
|
||||
x + i * padding, y, size, size, ui.DARK_GREY, ui.BG, size // 2)
|
||||
ui.display.bar_radius(
|
||||
x + page * padding, y, size, size, ui.FG, ui.BG, size // 2)
|
||||
|
||||
|
||||
class Input(Button):
|
||||
def __init__(self, area: tuple, content: str=''):
|
||||
super().__init__(area, content)
|
||||
self.pending = False
|
||||
self.disable()
|
||||
|
||||
def edit(self, content: str, pending: bool):
|
||||
self.content = content
|
||||
self.pending = pending
|
||||
self.taint()
|
||||
|
||||
def render_content(self, s, ax, ay, aw, ah):
|
||||
text_style = s['text-style']
|
||||
fg_color = s['fg-color']
|
||||
bg_color = s['bg-color']
|
||||
|
||||
p = self.pending # should we draw the pending marker?
|
||||
t = self.content # input content
|
||||
|
||||
tx = ax + 24 # x-offset of the content
|
||||
ty = ay + ah // 2 + 8 # y-offset of the content
|
||||
|
||||
# input content
|
||||
display.text(tx, ty, t, text_style, fg_color, bg_color)
|
||||
|
||||
if p: # pending marker
|
||||
width = display.text_width(t, text_style)
|
||||
pw = display.text_width(t[-1:], text_style)
|
||||
px = tx + width - pw
|
||||
display.bar(px, ty + 2, pw + 1, 3, fg_color)
|
||||
|
||||
|
||||
class PassphraseKeyboard(ui.Widget):
|
||||
def __init__(self, prompt, page=1):
|
||||
self.prompt = prompt
|
||||
self.page = page
|
||||
self.input = Input(ui.grid(0, n_x=1, n_y=6), '')
|
||||
self.back = Button(ui.grid(12),
|
||||
res.load(ui.ICON_BACK),
|
||||
style=ui.BTN_CLEAR)
|
||||
self.keys = key_buttons(KEYBOARD_KEYS[self.page])
|
||||
self.pbutton = None # pending key button
|
||||
self.pindex = 0 # index of current pending char in pbutton
|
||||
self.onchange = None
|
||||
|
||||
def render(self):
|
||||
if self.input.content:
|
||||
# content and backspace
|
||||
self.input.render()
|
||||
self.back.render()
|
||||
else:
|
||||
# prompt
|
||||
display.bar(0, 0, 240, 48, ui.BG)
|
||||
display.text_center(ui.SCREEN // 2, 32, self.prompt, ui.BOLD, ui.GREY, ui.BG)
|
||||
|
||||
# key buttons
|
||||
for btn in self.keys:
|
||||
btn.render()
|
||||
|
||||
render_scrollbar(self.page)
|
||||
|
||||
def touch(self, event, pos):
|
||||
content = self.input.content
|
||||
|
||||
if self.back.touch(event, pos) == BTN_CLICKED:
|
||||
# backspace, delete the last character of input
|
||||
self.edit(content[:-1])
|
||||
return
|
||||
|
||||
for btn in self.keys:
|
||||
if btn.touch(event, pos) == BTN_CLICKED:
|
||||
# key press, add new char to input or cycle the pending button
|
||||
if self.pbutton is btn:
|
||||
index = (self.pindex + 1) % len(btn.content)
|
||||
content = content[:-1] + btn.content[index]
|
||||
else:
|
||||
index = 0
|
||||
content += btn.content[0]
|
||||
self.edit(content, btn, index)
|
||||
return
|
||||
|
||||
def edit(self, content, button=None, index=0):
|
||||
self.pbutton = button
|
||||
self.pindex = index
|
||||
self.input.edit(content, button is not None)
|
||||
if self.onchange:
|
||||
self.onchange()
|
||||
|
||||
async def __iter__(self):
|
||||
while True:
|
||||
swipe = Swipe(directions=SWIPE_HORIZONTAL)
|
||||
wait = loop.wait(swipe, self.show_page())
|
||||
result = await wait
|
||||
if swipe in wait.finished:
|
||||
if result == SWIPE_LEFT:
|
||||
self.page = (self.page + 1) % len(KEYBOARD_KEYS)
|
||||
else:
|
||||
self.page = (self.page - 1) % len(KEYBOARD_KEYS)
|
||||
self.keys = key_buttons(KEYBOARD_KEYS[self.page])
|
||||
else:
|
||||
return result
|
||||
|
||||
@ui.layout
|
||||
async def show_page(self):
|
||||
timeout = loop.sleep(1000 * 1000 * 1)
|
||||
touch = loop.select(io.TOUCH)
|
||||
wait_timeout = loop.wait(touch, timeout)
|
||||
wait_touch = loop.wait(touch)
|
||||
content = None
|
||||
|
||||
self.back.taint()
|
||||
self.input.taint()
|
||||
|
||||
while content is None:
|
||||
self.render()
|
||||
if self.pbutton is not None:
|
||||
wait = wait_timeout
|
||||
else:
|
||||
wait = wait_touch
|
||||
result = await wait
|
||||
if touch in wait.finished:
|
||||
event, *pos = result
|
||||
content = self.touch(event, pos)
|
||||
else:
|
||||
self.edit(self.input.content)
|
||||
return content
|
Loading…
Reference in New Issue
Block a user