class PageWithButtons(ui.Component): def __init__( self, content: ui.Component, paginated: "PaginatedWithButtons", index: int, count: int, ) -> None: super().__init__() self.content = content self.paginated = paginated self.index = index self.count = count # somewhere in the middle, we can go up or down left = res.load(ui.ICON_BACK) left_style = ButtonDefault right = res.load(ui.ICON_CLICK) right_style = ButtonDefault if self.index == 0: # first page, we can cancel or go down left = res.load(ui.ICON_CANCEL) left_style = ButtonCancel right = res.load(ui.ICON_CLICK) right_style = ButtonDefault elif self.index == count - 1: # last page, we can go up or confirm left = res.load(ui.ICON_BACK) left_style = ButtonDefault right = res.load(ui.ICON_CONFIRM) right_style = ButtonConfirm self.left = Button(ui.grid(8, n_x=2), left, left_style) self.left.on_click = self.on_left # type: ignore self.right = Button(ui.grid(9, n_x=2), right, right_style) self.right.on_click = self.on_right # type: ignore def dispatch(self, event: int, x: int, y: int) -> None: self.content.dispatch(event, x, y) self.left.dispatch(event, x, y) self.right.dispatch(event, x, y) def on_left(self) -> None: if self.index == 0: self.paginated.on_cancel() else: self.paginated.on_up() def on_right(self) -> None: if self.index == self.count - 1: self.paginated.on_confirm() else: self.paginated.on_down() if __debug__: def read_content(self) -> List[str]: return self.content.read_content()
class HoldToConfirm(ui.Layout): DEFAULT_CONFIRM = "Hold To Confirm" DEFAULT_CONFIRM_STYLE = ButtonConfirm DEFAULT_LOADER_STYLE = LoaderDefault def __init__( self, content: ui.Component, confirm: str = DEFAULT_CONFIRM, confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE, loader_style: LoaderStyleType = DEFAULT_LOADER_STYLE, ): self.content = content self.loader = Loader(loader_style) self.loader.on_start = self._on_loader_start # type: ignore self.button = Button(ui.grid(4, n_x=1), confirm, confirm_style) self.button.on_press_start = self._on_press_start # type: ignore self.button.on_press_end = self._on_press_end # type: ignore self.button.on_click = self._on_click # type: ignore def _on_press_start(self) -> None: self.loader.start() def _on_press_end(self) -> None: self.loader.stop() def _on_loader_start(self) -> None: # Loader has either started growing, or returned to the 0-position. # In the first case we need to clear the content leftovers, in the latter # we need to render the content again. ui.display.bar(0, 0, ui.WIDTH, ui.HEIGHT - 58, ui.BG) self.content.dispatch(ui.REPAINT, 0, 0) def _on_click(self) -> None: if self.loader.elapsed_ms() >= self.loader.target_ms: self.on_confirm() def dispatch(self, event: int, x: int, y: int) -> None: if self.loader.start_ms is not None: self.loader.dispatch(event, x, y) else: self.content.dispatch(event, x, y) self.button.dispatch(event, x, y) def on_confirm(self) -> None: raise ui.Result(CONFIRMED) if __debug__: def read_content(self) -> List[str]: return self.content.read_content() def create_tasks(self) -> Tuple[loop.Task, ...]: return super().create_tasks() + (confirm_signal(), )
class Confirm(ui.Layout): DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM) DEFAULT_CONFIRM_STYLE = ButtonConfirm DEFAULT_CANCEL = res.load(ui.ICON_CANCEL) DEFAULT_CANCEL_STYLE = ButtonCancel def __init__( self, content: ui.Component, confirm: Optional[ButtonContent] = DEFAULT_CONFIRM, confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE, cancel: Optional[ButtonContent] = DEFAULT_CANCEL, cancel_style: ButtonStyleType = DEFAULT_CANCEL_STYLE, major_confirm: bool = False, ) -> None: self.content = content if confirm is not None: if cancel is None: area = ui.grid(4, n_x=1) elif major_confirm: area = ui.grid(13, cells_x=2) else: area = ui.grid(9, n_x=2) self.confirm = Button( area, confirm, confirm_style ) # type: Optional[Button] self.confirm.on_click = self.on_confirm # type: ignore else: self.confirm = None if cancel is not None: if confirm is None: area = ui.grid(4, n_x=1) elif major_confirm: area = ui.grid(12, cells_x=1) else: area = ui.grid(8, n_x=2) self.cancel = Button(area, cancel, cancel_style) # type: Optional[Button] self.cancel.on_click = self.on_cancel # type: ignore else: self.cancel = None def dispatch(self, event: int, x: int, y: int) -> None: super().dispatch(event, x, y) self.content.dispatch(event, x, y) if self.confirm is not None: self.confirm.dispatch(event, x, y) if self.cancel is not None: self.cancel.dispatch(event, x, y) def on_confirm(self) -> None: raise ui.Result(CONFIRMED) def on_cancel(self) -> None: raise ui.Result(CANCELLED)
class Confirm(ui.Layout): DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM) DEFAULT_CONFIRM_STYLE = ButtonConfirm DEFAULT_CANCEL = res.load(ui.ICON_CANCEL) DEFAULT_CANCEL_STYLE = ButtonCancel def __init__( self, content, confirm=DEFAULT_CONFIRM, confirm_style=DEFAULT_CONFIRM_STYLE, cancel=DEFAULT_CANCEL, cancel_style=DEFAULT_CANCEL_STYLE, major_confirm=False, ): self.content = content if confirm is not None: if cancel is None: area = ui.grid(4, n_x=1) elif major_confirm: area = ui.grid(13, cells_x=2) else: area = ui.grid(9, n_x=2) self.confirm = Button(area, confirm, confirm_style) self.confirm.on_click = self.on_confirm else: self.confirm = None if cancel is not None: if confirm is None: area = ui.grid(4, n_x=1) elif major_confirm: area = ui.grid(12, cells_x=1) else: area = ui.grid(8, n_x=2) self.cancel = Button(area, cancel, cancel_style) self.cancel.on_click = self.on_cancel else: self.cancel = None def dispatch(self, event, x, y): self.content.dispatch(event, x, y) if self.confirm is not None: self.confirm.dispatch(event, x, y) if self.cancel is not None: self.cancel.dispatch(event, x, y) def on_confirm(self): raise ui.Result(CONFIRMED) def on_cancel(self): raise ui.Result(CANCELLED)
class InfoConfirm(ui.Layout): DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM) DEFAULT_STYLE = DefaultInfoConfirm def __init__( self, text: str, confirm: ButtonContent = DEFAULT_CONFIRM, style: InfoConfirmStyleType = DEFAULT_STYLE, ) -> None: super().__init__() self.text = text.split() self.style = style panel_area = ui.grid(0, n_x=1, n_y=1) self.panel_area = panel_area confirm_area = ui.grid(4, n_x=1) self.confirm = Button(confirm_area, confirm, style.button) self.confirm.on_click = self.on_confirm # type: ignore def dispatch(self, event: int, x: int, y: int) -> None: if event == ui.RENDER: self.on_render() self.confirm.dispatch(event, x, y) def on_render(self) -> None: if self.repaint: x, y, w, h = self.panel_area fg_color = self.style.fg_color bg_color = self.style.bg_color # render the background panel ui.display.bar_radius(x, y, w, h, bg_color, ui.BG, ui.RADIUS) # render the info text render_text( # type: ignore self.text, new_lines=False, max_lines=6, offset_y=y + TEXT_LINE_HEIGHT, offset_x=x + TEXT_MARGIN_LEFT - ui.VIEWX, offset_x_max=x + w - ui.VIEWX, fg=fg_color, bg=bg_color, ) self.repaint = False def on_confirm(self) -> None: raise ui.Result(CONFIRMED)
class PageWithButtons(ui.Control): def __init__(self, content, paginated, index, count): self.content = content self.paginated = paginated self.index = index self.count = count if self.index == 0: # first page, we can cancel or go down left = res.load(ui.ICON_CANCEL) left_style = ButtonCancel right = res.load(ui.ICON_CLICK) right_style = ButtonDefault elif self.index == count - 1: # last page, we can go up or confirm left = res.load(ui.ICON_BACK) left_style = ButtonDefault right = res.load(ui.ICON_CONFIRM) right_style = ButtonConfirm else: # somewhere in the middle, we can go up or down left = res.load(ui.ICON_BACK) left_style = ButtonDefault right = res.load(ui.ICON_CLICK) right_style = ButtonDefault self.left = Button(ui.grid(8, n_x=2), left, left_style) self.left.on_click = self.on_left self.right = Button(ui.grid(9, n_x=2), right, right_style) self.right.on_click = self.on_right def dispatch(self, event, x, y): self.content.dispatch(event, x, y) self.left.dispatch(event, x, y) self.right.dispatch(event, x, y) def on_left(self): if self.index == 0: self.paginated.on_cancel() else: self.paginated.on_down() def on_right(self): if self.index == self.count - 1: self.paginated.on_confirm() else: self.paginated.on_up()
class NumInput(ui.Component): def __init__(self, count: int = 5, max_count: int = 16, min_count: int = 1) -> None: super().__init__() self.count = count self.max_count = max_count self.min_count = min_count self.minus = Button(ui.grid(3), "-") self.minus.on_click = self.on_minus # type: ignore self.plus = Button(ui.grid(5), "+") self.plus.on_click = self.on_plus # type: ignore self.text = Label(ui.grid(4), "", LABEL_CENTER, ui.BOLD) self.edit(count) def dispatch(self, event: int, x: int, y: int) -> None: self.minus.dispatch(event, x, y) self.plus.dispatch(event, x, y) self.text.dispatch(event, x, y) def on_minus(self) -> None: self.edit(self.count - 1) def on_plus(self) -> None: self.edit(self.count + 1) def edit(self, count: int) -> None: count = max(count, self.min_count) count = min(count, self.max_count) if self.count != count: self.on_change(count) self.count = count self.text.content = str(count) self.text.repaint = True if self.count == self.min_count: self.minus.disable() else: self.minus.enable() if self.count == self.max_count: self.plus.disable() else: self.plus.enable() def on_change(self, count: int) -> None: pass
class HoldToConfirm(ui.Layout): DEFAULT_CONFIRM = "Hold To Confirm" DEFAULT_CONFIRM_STYLE = ButtonConfirm DEFAULT_LOADER_STYLE = LoaderDefault def __init__( self, content, confirm=DEFAULT_CONFIRM, confirm_style=DEFAULT_CONFIRM_STYLE, loader_style=DEFAULT_LOADER_STYLE, ): self.content = content self.loader = Loader(loader_style) self.loader.on_start = self._on_loader_start self.button = Button(ui.grid(4, n_x=1), confirm, confirm_style) self.button.on_press_start = self._on_press_start self.button.on_press_end = self._on_press_end self.button.on_click = self._on_click def _on_press_start(self): self.loader.start() def _on_press_end(self): self.loader.stop() def _on_loader_start(self): # Loader has either started growing, or returned to the 0-position. # In the first case we need to clear the content leftovers, in the latter # we need to render the content again. ui.display.bar(0, 0, ui.WIDTH, ui.HEIGHT - 60, ui.BG) self.content.dispatch(ui.REPAINT, 0, 0) def _on_click(self): if self.loader.elapsed_ms() >= self.loader.target_ms: self.on_confirm() def dispatch(self, event, x, y): if self.loader.start_ms is not None: self.loader.dispatch(event, x, y) else: self.content.dispatch(event, x, y) self.button.dispatch(event, x, y) def on_confirm(self): raise ui.Result(CONFIRMED)
class WordSelector(ui.Layout): def __init__(self, content: ui.Component) -> None: super().__init__() self.content = content self.w12 = Button(ui.grid(6, n_y=4), "12") self.w12.on_click = self.on_w12 # type: ignore self.w18 = Button(ui.grid(7, n_y=4), "18") self.w18.on_click = self.on_w18 # type: ignore self.w20 = Button(ui.grid(8, n_y=4), "20") self.w20.on_click = self.on_w20 # type: ignore self.w24 = Button(ui.grid(9, n_y=4), "24") self.w24.on_click = self.on_w24 # type: ignore self.w33 = Button(ui.grid(10, n_y=4), "33") self.w33.on_click = self.on_w33 # type: ignore def dispatch(self, event: int, x: int, y: int) -> None: self.content.dispatch(event, x, y) self.w12.dispatch(event, x, y) self.w18.dispatch(event, x, y) self.w20.dispatch(event, x, y) self.w24.dispatch(event, x, y) self.w33.dispatch(event, x, y) def on_w12(self) -> None: raise ui.Result(12) def on_w18(self) -> None: raise ui.Result(18) def on_w20(self) -> None: raise ui.Result(20) def on_w24(self) -> None: raise ui.Result(24) def on_w33(self) -> None: raise ui.Result(33) if __debug__: def create_tasks(self) -> Tuple[loop.Task, ...]: from apps.debug import input_signal return super().create_tasks() + (input_signal(), )
class PassphraseSource(ui.Layout): def __init__(self, content: ui.Control) -> None: self.content = content self.device = Button(ui.grid(8, n_y=4, n_x=4, cells_x=4), "Device") self.device.on_click = self.on_device # type: ignore self.host = Button(ui.grid(12, n_y=4, n_x=4, cells_x=4), "Host") self.host.on_click = self.on_host # type: ignore def dispatch(self, event: int, x: int, y: int) -> None: self.content.dispatch(event, x, y) self.device.dispatch(event, x, y) self.host.dispatch(event, x, y) def on_device(self) -> None: raise ui.Result(PassphraseSourceType.DEVICE) def on_host(self) -> None: raise ui.Result(PassphraseSourceType.HOST)
class PinDialog(ui.Layout): def __init__(self, prompt, subprompt, allow_cancel=True, maxlength=9): self.maxlength = maxlength self.input = PinInput(prompt, subprompt, "") icon_confirm = res.load(ui.ICON_CONFIRM) self.confirm_button = Button(ui.grid(14), icon_confirm, ButtonConfirm) self.confirm_button.on_click = self.on_confirm icon_back = res.load(ui.ICON_BACK) self.reset_button = Button(ui.grid(12), icon_back, ButtonClear) self.reset_button.on_click = self.on_reset if allow_cancel: icon_lock = res.load(ui.ICON_LOCK) self.cancel_button = Button(ui.grid(12), icon_lock, ButtonCancel) self.cancel_button.on_click = self.on_cancel else: self.cancel_button = Button(ui.grid(12), "") self.cancel_button.disable() self.pin_buttons = [ PinButton(i, d, self) for i, d in enumerate(generate_digits()) ] def dispatch(self, event, x, y): for btn in self.pin_buttons: btn.dispatch(event, x, y) self.input.dispatch(event, x, y) self.confirm_button.dispatch(event, x, y) if self.input.pin: self.reset_button.dispatch(event, x, y) else: self.cancel_button.dispatch(event, x, y) def assign(self, pin): if len(pin) > self.maxlength: return for btn in self.pin_buttons: if len(pin) < self.maxlength: btn.enable() else: btn.disable() if pin: self.reset_button.enable() self.cancel_button.disable() else: self.reset_button.disable() self.cancel_button.enable() self.input.pin = pin self.input.repaint = True def on_reset(self): self.assign("") def on_cancel(self): raise ui.Result(CANCELLED) def on_confirm(self): raise ui.Result(self.input.pin)
class WordSelector(ui.Layout): def __init__(self, content): self.content = content self.w12 = Button(ui.grid(6, n_y=4), "12") self.w12.on_click = self.on_w12 self.w18 = Button(ui.grid(7, n_y=4), "18") self.w18.on_click = self.on_w18 self.w20 = Button(ui.grid(8, n_y=4), "20") self.w20.on_click = self.on_w20 self.w24 = Button(ui.grid(9, n_y=4), "24") self.w24.on_click = self.on_w24 self.w33 = Button(ui.grid(10, n_y=4), "33") self.w33.on_click = self.on_w33 def dispatch(self, event, x, y): self.content.dispatch(event, x, y) self.w12.dispatch(event, x, y) self.w18.dispatch(event, x, y) self.w20.dispatch(event, x, y) self.w24.dispatch(event, x, y) self.w33.dispatch(event, x, y) def on_w12(self): raise ui.Result(12) def on_w18(self): raise ui.Result(18) def on_w20(self): raise ui.Result(20) def on_w24(self): raise ui.Result(24) def on_w33(self): raise ui.Result(33)
class InfoConfirm(ui.Layout): DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM) DEFAULT_CONFIRM_STYLE = ButtonConfirm DEFAULT_CANCEL = res.load(ui.ICON_CANCEL) DEFAULT_CANCEL_STYLE = ButtonCancel DEFAULT_INFO = res.load( ui.ICON_CLICK) # TODO: this should be (i) icon, not click DEFAULT_INFO_STYLE = ButtonDefault def __init__( self, content: ui.Component, confirm: ButtonContent = DEFAULT_CONFIRM, confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE, cancel: ButtonContent = DEFAULT_CANCEL, cancel_style: ButtonStyleType = DEFAULT_CANCEL_STYLE, info: ButtonContent = DEFAULT_INFO, info_style: ButtonStyleType = DEFAULT_INFO_STYLE, ) -> None: super().__init__() self.content = content self.confirm = Button(ui.grid(14), confirm, confirm_style) self.confirm.on_click = self.on_confirm # type: ignore self.info = Button(ui.grid(13), info, info_style) self.info.on_click = self.on_info # type: ignore self.cancel = Button(ui.grid(12), cancel, cancel_style) self.cancel.on_click = self.on_cancel # type: ignore def dispatch(self, event: int, x: int, y: int) -> None: self.content.dispatch(event, x, y) if self.confirm is not None: self.confirm.dispatch(event, x, y) if self.cancel is not None: self.cancel.dispatch(event, x, y) if self.info is not None: self.info.dispatch(event, x, y) def on_confirm(self) -> None: raise ui.Result(CONFIRMED) def on_cancel(self) -> None: raise ui.Result(CANCELLED) def on_info(self) -> None: raise ui.Result(INFO) if __debug__: def read_content(self) -> List[str]: return self.content.read_content() def create_tasks(self) -> Tuple[loop.Task, ...]: return super().create_tasks() + (confirm_signal(), )
class WordSelector(ui.Layout): def __init__(self, content: ui.Component) -> None: self.content = content self.w12 = Button(ui.grid(6, n_y=4), "12") self.w12.on_click = self.on_w12 # type: ignore self.w18 = Button(ui.grid(7, n_y=4), "18") self.w18.on_click = self.on_w18 # type: ignore self.w20 = Button(ui.grid(8, n_y=4), "20") self.w20.on_click = self.on_w20 # type: ignore self.w24 = Button(ui.grid(9, n_y=4), "24") self.w24.on_click = self.on_w24 # type: ignore self.w33 = Button(ui.grid(10, n_y=4), "33") self.w33.on_click = self.on_w33 # type: ignore def dispatch(self, event: int, x: int, y: int) -> None: self.content.dispatch(event, x, y) self.w12.dispatch(event, x, y) self.w18.dispatch(event, x, y) self.w20.dispatch(event, x, y) self.w24.dispatch(event, x, y) self.w33.dispatch(event, x, y) def on_w12(self) -> None: raise ui.Result(12) def on_w18(self) -> None: raise ui.Result(18) def on_w20(self) -> None: raise ui.Result(20) def on_w24(self) -> None: raise ui.Result(24) def on_w33(self) -> None: raise ui.Result(33)
class InfoConfirm(ui.Layout): DEFAULT_CONFIRM = res.load(ui.ICON_CONFIRM) DEFAULT_CONFIRM_STYLE = ButtonConfirm DEFAULT_CANCEL = res.load(ui.ICON_CANCEL) DEFAULT_CANCEL_STYLE = ButtonCancel DEFAULT_INFO = res.load( ui.ICON_CLICK) # TODO: this should be (i) icon, not click DEFAULT_INFO_STYLE = ButtonDefault def __init__( self, content: ui.Component, confirm: ButtonContent = DEFAULT_CONFIRM, confirm_style: ButtonStyleType = DEFAULT_CONFIRM_STYLE, cancel: ButtonContent = DEFAULT_CANCEL, cancel_style: ButtonStyleType = DEFAULT_CANCEL_STYLE, info: ButtonContent = DEFAULT_INFO, info_style: ButtonStyleType = DEFAULT_INFO_STYLE, ) -> None: self.content = content self.confirm = Button(ui.grid(14), confirm, confirm_style) self.confirm.on_click = self.on_confirm # type: ignore self.info = Button(ui.grid(13), info, info_style) self.info.on_click = self.on_info self.cancel = Button(ui.grid(12), cancel, cancel_style) self.cancel.on_click = self.on_cancel # type: ignore def dispatch(self, event: int, x: int, y: int) -> None: self.content.dispatch(event, x, y) if self.confirm is not None: self.confirm.dispatch(event, x, y) if self.cancel is not None: self.cancel.dispatch(event, x, y) if self.info is not None: self.info.dispatch(event, x, y) def on_confirm(self) -> None: raise ui.Result(CONFIRMED) def on_cancel(self) -> None: raise ui.Result(CANCELLED) def on_info(self) -> None: raise ui.Result(INFO)
class WordSelector(ui.Layout): def __init__(self, content): self.content = content self.w12 = Button(ui.grid(6, n_y=4, n_x=3, cells_y=2), "12") self.w12.on_click = self.on_w12 self.w18 = Button(ui.grid(7, n_y=4, n_x=3, cells_y=2), "18") self.w18.on_click = self.on_w18 self.w24 = Button(ui.grid(8, n_y=4, n_x=3, cells_y=2), "24") self.w24.on_click = self.on_w24 def dispatch(self, event, x, y): self.content.dispatch(event, x, y) self.w12.dispatch(event, x, y) self.w18.dispatch(event, x, y) self.w24.dispatch(event, x, y) def on_w12(self): raise ui.Result(12) def on_w18(self): raise ui.Result(18) def on_w24(self): raise ui.Result(24)
class PassphraseKeyboard(ui.Layout): def __init__(self, prompt, max_length, page=1): self.prompt = Prompt(prompt) self.max_length = max_length 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), ButtonClear) self.back.on_click = self.on_back_click self.back.disable() self.done = Button(ui.grid(14), res.load(ui.ICON_CONFIRM), ButtonConfirm) self.done.on_click = self.on_confirm self.keys = key_buttons(KEYBOARD_KEYS[self.page], self) self.pending_button = None self.pending_index = 0 def dispatch(self, event, x, y): if self.input.content: self.input.dispatch(event, x, y) else: self.prompt.dispatch(event, x, y) self.back.dispatch(event, x, y) self.done.dispatch(event, x, y) for btn in self.keys: btn.dispatch(event, x, y) if event == ui.RENDER: render_scrollbar(self.page) def on_back_click(self): # Backspace was clicked. If we have any content in the input, let's delete # the last character. Otherwise cancel. content = self.input.content if content: self.edit(content[:-1]) else: self.on_cancel() def on_key_click(self, button: KeyButton): # Key button was clicked. If this button is pending, let's cycle the # pending character in input. If not, let's just append the first # character. button_text = button.get_text_content() if self.pending_button is button: index = (self.pending_index + 1) % len(button_text) prefix = self.input.content[:-1] else: index = 0 prefix = self.input.content if len(button_text) > 1: self.edit(prefix + button_text[index], button, index) else: self.edit(prefix + button_text[index]) def on_timeout(self): # Timeout occurred, let's just reset the pending marker. self.edit(self.input.content) def edit(self, content: str, button: Button = None, index: int = 0): if len(content) > self.max_length: return self.pending_button = button self.pending_index = index # modify the input state pending = button is not None self.input.edit(content, pending) if content: self.back.enable() else: self.back.disable() self.prompt.repaint = True async def handle_input(self): touch = loop.wait(io.TOUCH) timeout = loop.sleep(1000 * 1000 * 1) spawn_touch = loop.spawn(touch) spawn_timeout = loop.spawn(touch, timeout) while True: if self.pending_button is not None: spawn = spawn_timeout else: spawn = spawn_touch result = await spawn if touch in spawn.finished: event, x, y = result self.dispatch(event, x, y) else: self.on_timeout() async def handle_paging(self): swipe = await Swipe(SWIPE_HORIZONTAL) if swipe == 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], self) self.back.repaint = True self.done.repaint = True self.input.repaint = True self.prompt.repaint = True def on_cancel(self): raise ui.Result(CANCELLED) def on_confirm(self): raise ui.Result(self.input.content) def create_tasks(self): return self.handle_input(), self.handle_rendering( ), self.handle_paging()
class Slip39Keyboard(ui.Layout): def __init__(self, prompt: str) -> None: super().__init__() self.prompt = Prompt(prompt) icon_back = res.load(ui.ICON_BACK) self.back = Button(ui.grid(0, n_x=3, n_y=4), icon_back, ButtonClear) self.back.on_click = self.on_back_click # type: ignore self.input = InputButton(ui.grid(1, n_x=3, n_y=4, cells_x=2), self) self.input.on_click = self.on_input_click # type: ignore self.keys = [ KeyButton(ui.grid(i + 3, n_y=4), k, self, i + 1) for i, k in enumerate( ("ab", "cd", "ef", "ghij", "klm", "nopq", "rs", "tuv", "wxyz") ) ] self.pending_button: Optional[Button] = None self.pending_index = 0 self.button_sequence = "" self.mask = slip39.KEYBOARD_FULL_MASK def dispatch(self, event: int, x: int, y: int) -> None: for btn in self.keys: btn.dispatch(event, x, y) if self.input.text: self.input.dispatch(event, x, y) self.back.dispatch(event, x, y) else: self.prompt.dispatch(event, x, y) def on_back_click(self) -> None: # Backspace was clicked, let's delete the last character of input. self.button_sequence = self.button_sequence[:-1] self.edit() def on_input_click(self) -> None: # Input button was clicked. If the content matches the suggested word, # let's confirm it, otherwise just auto-complete. result = self.input.word if self.is_input_final(): self.button_sequence = "" self.edit() self.on_confirm(result) def on_key_click(self, btn: KeyButton) -> None: # Key button was clicked. If this button is pending, let's cycle the # pending character in input. If not, let's just append the first # character. if self.pending_button is btn: index = (self.pending_index + 1) % len(btn.text) else: index = 0 self.button_sequence += str(btn.index) self.edit(btn, index) def on_timeout(self) -> None: # Timeout occurred. Let's redraw to draw asterisks. self.edit() def on_confirm(self, word: str) -> None: # Word was confirmed by the user. raise ui.Result(word) def edit(self, button: Button = None, index: int = 0) -> None: self.pending_button = button self.pending_index = index # find the completions word = "" self.mask = slip39.word_completion_mask(self.button_sequence) if self.is_input_final(): word = slip39.button_sequence_to_word(self.button_sequence) # modify the input state self.input.edit( self.button_sequence, word, self.pending_button, self.pending_index ) # enable or disable key buttons for btn in self.keys: if self.is_input_final(): btn.disable() elif btn is button or self.check_mask(btn.index): btn.enable() else: btn.disable() # invalidate the prompt if we display it next frame if not self.input.text: self.prompt.repaint = True def is_input_final(self) -> bool: # returns True if mask has exactly one bit set to 1 or is 0 return not (self.mask & (self.mask - 1)) def check_mask(self, index: int) -> bool: return bool((1 << (index - 1)) & self.mask) async def handle_input(self) -> None: touch = loop.wait(io.TOUCH) timeout = loop.sleep(1000) race_touch = loop.race(touch) race_timeout = loop.race(touch, timeout) while True: if self.pending_button is not None: race = race_timeout else: race = race_touch result = await race if touch in race.finished: event, x, y = result workflow.idle_timer.touch() self.dispatch(event, x, y) else: self.on_timeout() if __debug__: def create_tasks(self) -> Tuple[loop.Task, ...]: from apps.debug import input_signal return super().create_tasks() + (input_signal(),)
class Bip39Keyboard(ui.Layout): def __init__(self, prompt: str) -> None: self.prompt = Prompt(prompt) icon_back = res.load(ui.ICON_BACK) self.back = Button(ui.grid(0, n_x=3, n_y=4), icon_back, ButtonClear) self.back.on_click = self.on_back_click # type: ignore self.input = InputButton(ui.grid(1, n_x=3, n_y=4, cells_x=2), "", "") self.input.on_click = self.on_input_click # type: ignore self.keys = [ KeyButton(ui.grid(i + 3, n_y=4), k, self) for i, k in enumerate( ("abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz") ) ] self.pending_button = None # type: Optional[Button] self.pending_index = 0 def dispatch(self, event: int, x: int, y: int) -> None: for btn in self.keys: btn.dispatch(event, x, y) if self.input.text: self.input.dispatch(event, x, y) self.back.dispatch(event, x, y) else: self.prompt.dispatch(event, x, y) def on_back_click(self) -> None: # Backspace was clicked, let's delete the last character of input. self.edit(self.input.text[:-1]) def on_input_click(self) -> None: # Input button was clicked. If the content matches the suggested word, # let's confirm it, otherwise just auto-complete. text = self.input.text word = self.input.word if word and word == text: self.edit("") self.on_confirm(word) else: self.edit(word) def on_key_click(self, btn: Button) -> None: # Key button was clicked. If this button is pending, let's cycle the # pending character in input. If not, let's just append the first # character. if self.pending_button is btn: index = (self.pending_index + 1) % len(btn.text) text = self.input.text[:-1] + btn.text[index] else: index = 0 text = self.input.text + btn.text[0] self.edit(text, btn, index) def on_timeout(self) -> None: # Timeout occurred. If we can auto-complete current input, let's just # reset the pending marker. If not, input is invalid, let's backspace # the last character. if self.input.word: self.edit(self.input.text) else: self.edit(self.input.text[:-1]) def on_confirm(self, word: str) -> None: # Word was confirmed by the user. raise ui.Result(word) def edit(self, text: str, button: Button = None, index: int = 0) -> None: self.pending_button = button self.pending_index = index # find the completions pending = button is not None word = bip39.complete_word(text) or "" mask = bip39.word_completion_mask(text) # modify the input state self.input.edit(text, word, pending) # enable or disable key buttons for btn in self.keys: if btn is button or compute_mask(btn.text) & mask: btn.enable() else: btn.disable() # invalidate the prompt if we display it next frame if not self.input.text: self.prompt.repaint = True async def handle_input(self) -> None: touch = loop.wait(io.TOUCH) timeout = loop.sleep(1000 * 1000 * 1) race_touch = loop.race(touch) race_timeout = loop.race(touch, timeout) while True: if self.pending_button is not None: race = race_timeout else: race = race_touch result = await race if touch in race.finished: event, x, y = result self.dispatch(event, x, y) else: self.on_timeout()
class PassphraseKeyboard(ui.Layout): def __init__(self, prompt: str, max_length: int, page: int = 1) -> None: super().__init__() self.prompt = Prompt(prompt) self.max_length = max_length 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), ButtonClear) self.back.on_click = self.on_back_click # type: ignore self.back.disable() self.done = Button(ui.grid(14), res.load(ui.ICON_CONFIRM), ButtonConfirm) self.done.on_click = self.on_confirm # type: ignore self.keys = key_buttons(KEYBOARD_KEYS[self.page], self) self.pending_button: Optional[KeyButton] = None self.pending_index = 0 def dispatch(self, event: int, x: int, y: int) -> None: if self.input.text: self.input.dispatch(event, x, y) else: self.prompt.dispatch(event, x, y) self.back.dispatch(event, x, y) self.done.dispatch(event, x, y) for btn in self.keys: btn.dispatch(event, x, y) if event == ui.RENDER: render_scrollbar(self.page) def on_back_click(self) -> None: # Backspace was clicked. If we have any content in the input, let's delete # the last character. Otherwise cancel. text = self.input.text if text: self.edit(text[:-1]) else: self.on_cancel() def on_key_click(self, button: KeyButton) -> None: # Key button was clicked. If this button is pending, let's cycle the # pending character in input. If not, let's just append the first # character. button_text = button.get_text_content() if self.pending_button is button: index = (self.pending_index + 1) % len(button_text) prefix = self.input.text[:-1] else: index = 0 prefix = self.input.text if len(button_text) > 1: self.edit(prefix + button_text[index], button, index) else: self.edit(prefix + button_text[index]) def on_timeout(self) -> None: # Timeout occurred, let's just reset the pending marker. self.edit(self.input.text) def edit(self, text: str, button: KeyButton = None, index: int = 0) -> None: if len(text) > self.max_length: return self.pending_button = button self.pending_index = index # modify the input state pending = button is not None self.input.edit(text, pending) if text: self.back.enable() else: self.back.disable() self.prompt.repaint = True async def handle_input(self) -> None: touch = loop.wait(io.TOUCH) timeout = loop.sleep(1000) race_touch = loop.race(touch) race_timeout = loop.race(touch, timeout) while True: if self.pending_button is not None: race = race_timeout else: race = race_touch result = await race if touch in race.finished: event, x, y = result workflow.idle_timer.touch() self.dispatch(event, x, y) else: self.on_timeout() async def handle_paging(self) -> None: swipe = await Swipe(SWIPE_HORIZONTAL) if swipe == 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], self) self.back.repaint = True self.done.repaint = True self.input.repaint = True self.prompt.repaint = True def on_cancel(self) -> None: raise ui.Result(CANCELLED) def on_confirm(self) -> None: raise ui.Result(self.input.text) def create_tasks(self) -> Tuple[loop.Task, ...]: tasks: Tuple[loop.Task, ...] = ( self.handle_input(), self.handle_rendering(), self.handle_paging(), ) if __debug__: from apps.debug import input_signal return tasks + (input_signal(), ) else: return tasks
class PinDialog(ui.Layout): def __init__( self, prompt: str, subprompt: Optional[str], allow_cancel: bool = True, maxlength: int = 9, ) -> None: self.maxlength = maxlength self.input = PinInput(prompt, subprompt, "") icon_confirm = res.load(ui.ICON_CONFIRM) self.confirm_button = Button(ui.grid(14), icon_confirm, ButtonConfirm) self.confirm_button.on_click = self.on_confirm # type: ignore self.confirm_button.disable() icon_back = res.load(ui.ICON_BACK) self.reset_button = Button(ui.grid(12), icon_back, ButtonClear) self.reset_button.on_click = self.on_reset # type: ignore if allow_cancel: icon_lock = res.load( ui.ICON_CANCEL if config.is_unlocked() else ui.ICON_LOCK) self.cancel_button = Button(ui.grid(12), icon_lock, ButtonCancel) self.cancel_button.on_click = self.on_cancel # type: ignore else: self.cancel_button = Button(ui.grid(12), "") self.cancel_button.disable() self.pin_buttons = [ PinButton(i, d, self) for i, d in enumerate(generate_digits()) ] def dispatch(self, event: int, x: int, y: int) -> None: self.input.dispatch(event, x, y) if self.input.pin: self.reset_button.dispatch(event, x, y) else: self.cancel_button.dispatch(event, x, y) self.confirm_button.dispatch(event, x, y) for btn in self.pin_buttons: btn.dispatch(event, x, y) def assign(self, pin: str) -> None: if len(pin) > self.maxlength: return for btn in self.pin_buttons: if len(pin) < self.maxlength: btn.enable() else: btn.disable() if pin: self.confirm_button.enable() self.reset_button.enable() self.cancel_button.disable() else: self.confirm_button.disable() self.reset_button.disable() self.cancel_button.enable() self.input.pin = pin self.input.repaint = True def on_reset(self) -> None: self.assign("") def on_cancel(self) -> None: raise ui.Result(CANCELLED) def on_confirm(self) -> None: if self.input.pin: raise ui.Result(self.input.pin) if __debug__: def create_tasks(self) -> Tuple[loop.Task, ...]: from apps.debug import input_signal return super().create_tasks() + (input_signal(), )
class Slip39Keyboard(ui.Layout): def __init__(self, prompt): self.prompt = Prompt(prompt) icon_back = res.load(ui.ICON_BACK) self.back = Button(ui.grid(0, n_x=4, n_y=4), icon_back, ButtonClear) self.back.on_click = self.on_back_click self.input = InputButton(ui.grid(1, n_x=4, n_y=4, cells_x=3), "", "") self.input.on_click = self.on_input_click self.keys = [ KeyButton(ui.grid(i + 3, n_y=4), k, self, i + 1) for i, k in enumerate(("ab", "cd", "ef", "ghij", "klm", "nopq", "rs", "tuv", "wxyz")) ] self.pending_button = None self.pending_index = 0 self.button_sequence = "" def dispatch(self, event: int, x: int, y: int): for btn in self.keys: btn.dispatch(event, x, y) if self.input.content: self.input.dispatch(event, x, y) self.back.dispatch(event, x, y) else: self.prompt.dispatch(event, x, y) def on_back_click(self): # Backspace was clicked, let's delete the last character of input. self.button_sequence = self.button_sequence[:-1] self.edit() def on_input_click(self): # Input button was clicked. If the content matches the suggested word, # let's confirm it, otherwise just auto-complete. result = self.input.word if _is_final(self.input.content): self.button_sequence = "" self.edit() self.on_confirm(result) def on_key_click(self, btn: KeyButton): # Key button was clicked. If this button is pending, let's cycle the # pending character in input. If not, let's just append the first # character. if self.pending_button is btn: index = (self.pending_index + 1) % len(btn.content) else: index = 0 self.button_sequence += str(btn.index) self.edit(btn, index) def on_timeout(self): # Timeout occurred. Let's redraw to draw asterisks. self.edit() def on_confirm(self, word): # Word was confirmed by the user. raise ui.Result(word) def edit(self, button: KeyButton = None, index: int = 0): self.pending_button = button self.pending_index = index # find the completions mask = 0 word = "" if _is_final(self.button_sequence): word = slip39.button_sequence_to_word(self.button_sequence) else: mask = slip39.compute_mask(self.button_sequence) # modify the input state self.input.edit(self.button_sequence, word, self.pending_button, self.pending_index) # enable or disable key buttons for btn in self.keys: if (not _is_final(self.button_sequence) and btn is button) or check_mask(mask, btn.index): btn.enable() else: btn.disable() # invalidate the prompt if we display it next frame if not self.input.content: self.prompt.repaint = True async def handle_input(self): touch = loop.wait(io.TOUCH) timeout = loop.sleep(1000 * 1000 * 1) spawn_touch = loop.spawn(touch) spawn_timeout = loop.spawn(touch, timeout) while True: if self.pending_button is not None: spawn = spawn_timeout else: spawn = spawn_touch result = await spawn if touch in spawn.finished: event, x, y = result self.dispatch(event, x, y) else: self.on_timeout()