You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

219 lines
8.0 KiB

from functools import partial
import threading
from PyQt4.Qt import QGridLayout, QInputDialog, QPushButton
from PyQt4.Qt import QVBoxLayout, QLabel, SIGNAL
from electrum_gui.qt.main_window import StatusBarButton
from electrum_gui.qt.password_dialog import PasswordDialog
from electrum_gui.qt.util import *
from plugin import TrezorCompatiblePlugin
from electrum.i18n import _
from electrum.plugins import hook
from electrum.util import PrintError
from electrum.wallet import BIP44_Wallet
# By far the trickiest thing about this handler is the window stack;
# MacOSX is very fussy the modal dialogs are perfectly parented
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('clear_dialog'), self.clear_dialog)
win.connect(win, SIGNAL('error_dialog'), self.error_dialog)
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.window_stack = [win]
self.win = win
self.pin_matrix_widget_class = pin_matrix_widget_class
self.device = device
self.dialog = None
self.done = threading.Event()
def watching_only_changed(self):
self.win.emit(SIGNAL('watching_only_changed'))
def show_message(self, msg, cancel_callback=None):
self.win.emit(SIGNAL('message_dialog'), msg, cancel_callback)
def show_error(self, msg):
self.win.emit(SIGNAL('error_dialog'), msg)
def finished(self):
self.win.emit(SIGNAL('clear_dialog'))
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
dialog = WindowModalDialog(self.window_stack[-1], _("Enter PIN"))
matrix = self.pin_matrix_widget_class()
vbox = QVBoxLayout()
vbox.addWidget(QLabel(msg))
vbox.addWidget(matrix)
vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog)))
dialog.setLayout(vbox)
dialog.exec_()
self.response = str(matrix.get_value())
self.done.set()
def passphrase_dialog(self, msg):
d = PasswordDialog(self.window_stack[-1], None, msg,
PasswordDialog.PW_PASSPHRASE)
confirmed, p, passphrase = d.run()
if confirmed:
passphrase = BIP44_Wallet.normalize_passphrase(passphrase)
self.passphrase = passphrase
self.done.set()
def message_dialog(self, msg, cancel_callback):
# Called more than once during signing, to confirm output and fee
self.clear_dialog()
title = _('Please check your %s device') % self.device
self.dialog = dialog = WindowModalDialog(self.window_stack[-1], title)
self.window_stack.append(dialog)
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 error_dialog(self, msg):
self.win.show_error(msg, parent=self.window_stack[-1])
def clear_dialog(self):
if self.dialog:
self.dialog.accept()
self.window_stack.remove(self.dialog)
self.dialog = None
def exec_dialog(self, dialog):
self.window_stack.append(dialog)
try:
dialog.exec_()
finally:
assert dialog == self.window_stack.pop()
def qt_plugin_class(base_plugin_class):
class QtPlugin(base_plugin_class):
# 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
window.tzb = StatusBarButton(QIcon(self.icon_file), self.device,
partial(self.settings_dialog, window))
window.statusBar().addPermanentWidget(window.tzb)
wallet.handler = self.create_handler(window)
# Trigger a pairing
self.client(wallet)
def on_create_wallet(self, wallet, wizard):
assert type(wallet) == self.wallet_class
wallet.handler = self.create_handler(wizard)
self.select_device(wallet, wizard)
wallet.create_main_account(None)
@hook
def receive_menu(self, menu, addrs, wallet):
if type(wallet) == self.wallet_class and len(addrs) == 1:
menu.addAction(_("Show on %s") % self.device,
lambda: self.show_address(wallet, addrs[0]))
def settings_dialog(self, window):
handler = window.wallet.handler
client = self.client(window.wallet)
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])
client.change_label(new_label)
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):
client.set_pin(remove=remove)
update_pin_info()
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(window, _("%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)
handler.exec_dialog(dialog)
return QtPlugin