1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-30 01:01:00 +00:00

ui/passphrase: extract new passphrase keyboard

This commit is contained in:
Jan Pochyla 2018-02-05 20:26:13 +01:00
parent 7b89e4b14c
commit 1c9380a9d9
4 changed files with 215 additions and 122 deletions

View File

@ -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.FailureType import ActionCancelled
from trezor.messages.PassphraseRequest import PassphraseRequest from trezor.messages.PassphraseRequest import PassphraseRequest
from trezor.messages.wire_types import PassphraseAck, Cancel from trezor.messages.wire_types import PassphraseAck, Cancel
from trezor.ui.text import Text 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.entry_select import EntrySelector
from trezor.ui.keyboard import PassphraseKeyboard
ui.display.clear() ui.display.clear()
text = Text('Enter passphrase', ui.ICON_RESET, text = Text(
'Where to enter your', 'passphrase?') 'Enter passphrase', ui.ICON_RESET,
'Where to enter your', 'passphrase?')
entry = EntrySelector(text) entry = EntrySelector(text)
entry_type = await entry entry_type = await entry
if entry_type == 1: if entry_type == 1:
ui.display.clear() return await request_passphrase_on_host(ctx)
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
else: else:
def onchange(): return await request_passphrase_on_display(ctx)
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')
async def protect_by_passphrase(ctx): async def protect_by_passphrase(ctx):

View File

@ -12,12 +12,8 @@ class EntrySelector(Widget):
def __init__(self, content): def __init__(self, content):
self.content = content self.content = content
self.device = Button(ui.grid(8, n_y=4, n_x=4, cells_x=4), 'Device', self.device = Button(ui.grid(8, n_y=4, n_x=4, cells_x=4), 'Device')
normal_style=ui.BTN_KEY, self.host = Button(ui.grid(12, n_y=4, n_x=4, cells_x=4), 'Host')
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)
def render(self): def render(self):
self.device.render() self.device.render()

View File

@ -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.crypto import bip39
from trezor.ui import display from trezor.ui import display
from trezor.ui.button import Button, BTN_CLICKED, ICON from trezor.ui.button import BTN_CLICKED, ICON, Button
from .swipe import Swipe, SWIPE_LEFT, SWIPE_RIGHT, SWIPE_HORIZONTAL
KEYBOARD = { MNEMONIC_KEYS = ('abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu', 'vwx', 'yz')
'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': ['_', '.', '/', '!', '+', '-', '?', ',', ';', '$']
}
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): def key_buttons(keys):
bbox = const(240) return [Button(ui.grid(i + 3, n_y=4), k) for i, k in enumerate(keys)]
size = const(8)
padding = 12
page_count = len(KEYBOARD)
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: def compute_mask(text: str) -> int:
mask = 0 mask = 0
@ -46,14 +19,6 @@ def compute_mask(text: str) -> int:
mask |= 1 << shift mask |= 1 << shift
return mask 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): class Input(Button):
def __init__(self, area: tuple, content: str='', word: str=''): def __init__(self, area: tuple, content: str='', word: str=''):
@ -109,41 +74,6 @@ class Input(Button):
iy = ty - ICON iy = ty - ICON
display.icon(ix, iy, res.load(i), fg_color, bg_color) 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): class MnemonicKeyboard(ui.Widget):
def __init__(self, prompt: str=''): 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), self.back = Button(ui.grid(0, n_x=4, n_y=4),
res.load(ui.ICON_BACK), res.load(ui.ICON_BACK),
style=ui.BTN_CLEAR) style=ui.BTN_CLEAR)
self.keys = key_buttons() self.keys = key_buttons(MNEMONIC_KEYS)
self.pbutton = None # pending key button self.pbutton = None # pending key button
self.pindex = 0 # index of current pending char in pbutton self.pindex = 0 # index of current pending char in pbutton

170
src/trezor/ui/passphrase.py Normal file
View 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