electrum-bitcoinprivate/plugins/trezor/qt_generic.py

242 lines
8.7 KiB
Python
Raw Normal View History

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
2015-12-26 22:10:53 -08:00
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,
2015-12-26 22:10:53 -08:00
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
2015-12-26 22:10:53 -08:00
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):
2015-12-26 22:10:53 -08:00
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()