from functools import partial from unicodedata import normalize import threading from PyQt4.Qt import QGridLayout, QInputDialog, QPushButton from PyQt4.Qt import QVBoxLayout, QLabel, SIGNAL from trezor import TrezorPlugin from electrum_gui.qt.main_window import ElectrumWindow, StatusBarButton from electrum_gui.qt.password_dialog import PasswordDialog from electrum_gui.qt.util import * from electrum.i18n import _ from electrum.plugins import hook from electrum.util import PrintError class QtHandler(PrintError): '''An interface between the GUI (here, QT) and the device handling logic for handling I/O. This is a generic implementation of the Trezor protocol; derived classes can customize it.''' def __init__(self, win, pin_matrix_widget_class, device): win.connect(win, SIGNAL('message_done'), self.dialog_stop) win.connect(win, SIGNAL('message_dialog'), self.message_dialog) win.connect(win, SIGNAL('pin_dialog'), self.pin_dialog) win.connect(win, SIGNAL('passphrase_dialog'), self.passphrase_dialog) self.win = win self.windows = [win] self.pin_matrix_widget_class = pin_matrix_widget_class self.device = device self.done = threading.Event() self.dialog = None def stop(self): self.win.emit(SIGNAL('message_done')) def show_message(self, msg, cancel_callback=None): self.win.emit(SIGNAL('message_dialog'), msg, cancel_callback) def get_pin(self, msg): self.done.clear() self.win.emit(SIGNAL('pin_dialog'), msg) self.done.wait() return self.response def get_passphrase(self, msg): self.done.clear() self.win.emit(SIGNAL('passphrase_dialog'), msg) self.done.wait() return self.passphrase def pin_dialog(self, msg): # Needed e.g. when renaming label and haven't entered PIN self.dialog_stop() d = WindowModalDialog(self.windows[-1], _("Enter PIN")) matrix = self.pin_matrix_widget_class() vbox = QVBoxLayout() vbox.addWidget(QLabel(msg)) vbox.addWidget(matrix) vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) d.setLayout(vbox) if not d.exec_(): self.response = None # FIXME: this is lost? self.response = str(matrix.get_value()) self.done.set() def passphrase_dialog(self, msg): self.dialog_stop() d = PasswordDialog(self.windows[-1], None, None, msg, False) confirmed, p, phrase = d.run() if confirmed: phrase = normalize('NFKD', unicode(phrase or '')) self.passphrase = phrase self.done.set() def message_dialog(self, msg, cancel_callback): # Called more than once during signing, to confirm output and fee self.dialog_stop() title = _('Please check your %s device') % self.device dialog = self.dialog = WindowModalDialog(self.windows[-1], title) l = QLabel(msg) vbox = QVBoxLayout(dialog) if cancel_callback: vbox.addLayout(Buttons(CancelButton(dialog))) dialog.connect(dialog, SIGNAL('rejected()'), cancel_callback) vbox.addWidget(l) dialog.show() def dialog_stop(self): if self.dialog: self.dialog.hide() self.dialog = None def pop_window(self): self.windows.pop() def push_window(self, window): self.windows.append(window) class QtPlugin(TrezorPlugin): # Derived classes must provide the following class-static variables: # icon_file # pin_matrix_widget_class def create_handler(self, window): return QtHandler(window, self.pin_matrix_widget_class, self.device) @hook def load_wallet(self, wallet, window): if type(wallet) != self.wallet_class: return self.print_error("load_wallet") wallet.plugin = self self.button = StatusBarButton(QIcon(self.icon_file), self.device, partial(self.settings_dialog, window)) if type(window) is ElectrumWindow: window.statusBar().addPermanentWidget(self.button) if self.handler is None: self.handler = self.create_handler(window) msg = wallet.sanity_check() if msg: window.show_error(msg) @hook def installwizard_load_wallet(self, wallet, window): self.load_wallet(wallet, window) @hook def installwizard_restore(self, wizard, storage): if storage.get('wallet_type') != self.wallet_class.wallet_type: return seed = wizard.enter_seed_dialog(_("Enter your %s seed") % self.device, None, func=lambda x: True) if not seed: return # Restored wallets are not hardware wallets wallet_class = self.wallet_class.restore_wallet_class storage.put('wallet_type', wallet_class.wallet_type) wallet = wallet_class(storage) handler = self.create_handler(wizard) msg = "\n".join([_("Please enter your %s passphrase.") % self.device, _("Press OK if you do not use one.")]) passphrase = handler.get_passphrase(msg) if passphrase is None: return password = wizard.password_dialog() wallet.add_seed(seed, password) wallet.add_cosigner_seed(seed, 'x/', password, passphrase) wallet.create_main_account(password) return wallet @hook def receive_menu(self, menu, addrs, wallet): if type(wallet) != self.wallet_class: return if (not wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1): menu.addAction(_("Show on %s") % self.device, lambda: self.show_address(wallet, addrs[0])) def settings_dialog(self, window): def rename(): title = _("Set Device Label") msg = _("Enter new label:") response = QInputDialog().getText(dialog, title, msg) if not response[1]: return new_label = str(response[0]) try: client.change_label(new_label) finally: self.handler.stop() device_label.setText(new_label) def update_pin_info(): features = client.features pin_label.setText(noyes[features.pin_protection]) pin_button.setText(_("Change") if features.pin_protection else _("Set")) clear_pin_button.setVisible(features.pin_protection) def set_pin(remove): try: client.set_pin(remove=remove) finally: self.handler.stop() update_pin_info() client = self.get_client() features = client.features noyes = [_("No"), _("Yes")] bl_hash = features.bootloader_hash.encode('hex').upper() bl_hash = "%s...%s" % (bl_hash[:10], bl_hash[-10:]) info_tab = QWidget() layout = QGridLayout(info_tab) device_label = QLabel(features.label) rename_button = QPushButton(_("Rename")) rename_button.clicked.connect(rename) pin_label = QLabel() pin_button = QPushButton() pin_button.clicked.connect(partial(set_pin, False)) clear_pin_button = QPushButton(_("Clear")) clear_pin_button.clicked.connect(partial(set_pin, True)) update_pin_info() version = "%d.%d.%d" % (features.major_version, features.minor_version, features.patch_version) rows = [ (_("Bootloader Hash"), bl_hash), (_("Device ID"), features.device_id), (_("Device Label"), device_label, rename_button), (_("Firmware Version"), version), (_("Language"), features.language), (_("Has Passphrase"), noyes[features.passphrase_protection]), (_("Has PIN"), pin_label, pin_button, clear_pin_button) ] for row_num, items in enumerate(rows): for col_num, item in enumerate(items): widget = item if isinstance(item, QWidget) else QLabel(item) layout.addWidget(widget, row_num, col_num) dialog = WindowModalDialog(None, _("%s Settings") % self.device) vbox = QVBoxLayout() tabs = QTabWidget() tabs.addTab(info_tab, _("Information")) tabs.addTab(QWidget(), _("Advanced")) vbox.addWidget(tabs) vbox.addStretch(1) vbox.addLayout(Buttons(CloseButton(dialog))) dialog.setLayout(vbox) self.handler.push_window(dialog) try: dialog.exec_() finally: self.handler.pop_window()