diff --git a/plugins/trezor/clientbase.py b/plugins/trezor/clientbase.py index e5fcfe43e..721544988 100644 --- a/plugins/trezor/clientbase.py +++ b/plugins/trezor/clientbase.py @@ -84,6 +84,15 @@ class GuiMixin(object): return self.proto.PassphraseStateAck() def callback_WordRequest(self, msg): + if (msg.type is not None + and msg.type in (self.types.WordRequestType_Matrix9, + self.types.WordRequestType_Matrix6)): + num = 9 if msg.type == self.types.WordRequestType_Matrix9 else 6 + char = self.handler.get_matrix(num) + if (char == 'x'): + return self.proto.Cancel() + return self.proto.WordAck(word=char) + self.step += 1 msg = _("Step {}/24. Enter seed word as explained on " "your {}:").format(self.step, self.device) diff --git a/plugins/trezor/qt.py b/plugins/trezor/qt.py index fd32b5d42..9185f1b2c 100644 --- a/plugins/trezor/qt.py +++ b/plugins/trezor/qt.py @@ -30,16 +30,86 @@ PASSPHRASE_NOT_PIN = _( "If you forget a passphrase you will be unable to access any " "bitcoins in the wallet behind it. A passphrase is not a PIN. " "Only change this if you are sure you understand it.") +MATRIX_RECOVERY = ( + "Enter the recovery words by pressing the buttons according to what " + "the device shows on its display. You can also use your NUMPAD.\n" + "Press BACKSPACE to go back a choice or word.\n") + + +class MatrixDialog(WindowModalDialog): + + def __init__(self, parent): + super(MatrixDialog, self).__init__(parent) + self.setWindowTitle(_("Trezor Matrix Recovery")) + self.num = 9 + self.loop = QEventLoop() + + vbox = QVBoxLayout(self) + vbox.addWidget(WWLabel(MATRIX_RECOVERY)) + + grid = QGridLayout() + grid.setSpacing(0) + self.char_buttons = []; + for y in range(3): + for x in range(3): + button = QPushButton('?') + button.clicked.connect(partial(self.process_key, ord('1') + y * 3 + x)) + grid.addWidget(button, 3 - y, x) + self.char_buttons.append(button) + vbox.addLayout(grid) + + hbox = QHBoxLayout() + self.backspace_button = QPushButton(_("<=")) + self.backspace_button.clicked.connect(partial(self.process_key, Qt.Key_Backspace)) + self.cancel_button = QPushButton(_("Cancel")) + self.cancel_button.clicked.connect(partial(self.process_key, Qt.Key_Escape)) + buttons = Buttons(self.backspace_button, self.cancel_button) + vbox.addSpacing(40) + vbox.addLayout(buttons) + self.refresh() + self.show() + + def refresh(self): + for y in range(3): + self.char_buttons[3 * y + 1].setEnabled(self.num == 9) + + def is_valid(self, key): + return key >= ord('1') and key <= ord('9') + + def process_key(self, key): + self.data = None + if key == Qt.Key_Backspace: + self.data = '\010' + elif key == Qt.Key_Escape: + self.data = 'x' + elif self.is_valid(key): + self.char_buttons[key - ord('1')].setFocus() + self.data = '%c' % key; + if self.data: + self.loop.exit(0) + + def keyPressEvent(self, event): + self.process_key(event.key()) + if not self.data: + QDialog.keyPressEvent(self, event) + + def get_matrix(self, num): + self.num = num + self.refresh() + self.loop.exec_() class QtHandler(QtHandlerBase): pin_signal = pyqtSignal(object) + matrix_signal = pyqtSignal(object) def __init__(self, win, pin_matrix_widget_class, device): super(QtHandler, self).__init__(win, device) self.pin_signal.connect(self.pin_dialog) self.pin_matrix_widget_class = pin_matrix_widget_class + self.matrix_signal.connect(self.matrix_recovery_dialog) + self.matrix_dialog = None def get_pin(self, msg): self.done.clear() @@ -47,6 +117,16 @@ class QtHandler(QtHandlerBase): self.done.wait() return self.response + def get_matrix(self, msg): + self.done.clear() + self.matrix_signal.emit(msg) + self.done.wait() + data = self.matrix_dialog.data + if data == 'x': + self.matrix_dialog.accept() + self.matrix_dialog = None + return data + def pin_dialog(self, msg): # Needed e.g. when resetting a device self.clear_dialog() @@ -61,6 +141,12 @@ class QtHandler(QtHandlerBase): self.response = str(matrix.get_value()) self.done.set() + def matrix_recovery_dialog(self, msg): + if not self.matrix_dialog: + self.matrix_dialog = MatrixDialog(self.top_level_window()) + self.matrix_dialog.get_matrix(msg) + self.done.set() + class QtPlugin(QtPluginBase): # Derived classes must provide the following class-static variables: