1
0

Merge branch 'master' of git://github.com/spesmilo/electrum

This commit is contained in:
ThomasV
2016-01-21 16:35:44 +01:00
6 changed files with 232 additions and 229 deletions

View File

@@ -2,10 +2,10 @@ from keepkeylib.client import proto, BaseClient, ProtocolMixin
from ..trezor.clientbase import TrezorClientBase
class KeepKeyClient(TrezorClientBase, ProtocolMixin, BaseClient):
def __init__(self, transport, handler, plugin, hid_id):
def __init__(self, transport, handler, plugin):
BaseClient.__init__(self, transport)
ProtocolMixin.__init__(self, transport)
TrezorClientBase.__init__(self, handler, plugin, hid_id, proto)
TrezorClientBase.__init__(self, handler, plugin, proto)
def recovery_device(self, *args):
ProtocolMixin.recovery_device(self, True, *args)

View File

@@ -2,10 +2,10 @@ from trezorlib.client import proto, BaseClient, ProtocolMixin
from clientbase import TrezorClientBase
class TrezorClient(TrezorClientBase, ProtocolMixin, BaseClient):
def __init__(self, transport, handler, plugin, hid_id):
def __init__(self, transport, handler, plugin):
BaseClient.__init__(self, transport)
ProtocolMixin.__init__(self, transport)
TrezorClientBase.__init__(self, handler, plugin, hid_id, proto)
TrezorClientBase.__init__(self, handler, plugin, proto)
TrezorClientBase.wrap_methods(TrezorClient)

View File

@@ -68,27 +68,22 @@ class GuiMixin(object):
class TrezorClientBase(GuiMixin, PrintError):
def __init__(self, handler, plugin, hid_id, proto):
def __init__(self, handler, plugin, proto):
assert hasattr(self, 'tx_api') # ProtocolMixin already constructed?
self.proto = proto
self.device = plugin.device
self.handler = handler
self.hid_id_ = hid_id
self.tx_api = plugin
self.types = plugin.types
self.msg_code_override = None
def __str__(self):
return "%s/%s" % (self.label(), self.hid_id())
return "%s/%s" % (self.label(), self.features.device_id)
def label(self):
'''The name given by the user to the device.'''
return self.features.label
def hid_id(self):
'''The HID ID of the device.'''
return self.hid_id_
def is_initialized(self):
'''True if initialized, False if wiped.'''
return self.features.initialized
@@ -163,7 +158,7 @@ class TrezorClientBase(GuiMixin, PrintError):
def close(self):
'''Called when Our wallet was closed or the device removed.'''
self.print_error("disconnected")
self.print_error("closing client")
self.clear_session()
# Release the device
self.transport.close()

View File

@@ -51,23 +51,26 @@ class TrezorCompatibleWallet(BIP44_Wallet):
self.session_timeout = seconds
self.storage.put('session_timeout', seconds)
def disconnected(self):
'''A device paired with the wallet was diconnected. Note this is
called in the context of the Plugins thread.'''
self.print_error("disconnected")
def unpaired(self):
'''A device paired with the wallet was diconnected. This can be
called in any thread context.'''
self.print_error("unpaired")
self.force_watching_only = True
self.handler.watching_only_changed()
def connected(self):
'''A device paired with the wallet was (re-)connected. Note this
is called in the context of the Plugins thread.'''
self.print_error("connected")
def paired(self):
'''A device paired with the wallet was (re-)connected. This can be
called in any thread context.'''
self.print_error("paired")
self.force_watching_only = False
self.handler.watching_only_changed()
def timeout(self):
'''Informs the wallet it timed out. Note this is called from
'''Called when the wallet session times out. Note this is called from
the Plugins thread.'''
client = self.get_client(force_pair=False)
if client:
client.clear_session()
self.print_error("timed out")
def get_action(self):
@@ -178,8 +181,7 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
self.wallet_class.plugin = self
self.prevent_timeout = time.time() + 3600 * 24 * 365
if self.libraries_available:
self.device_manager().register_devices(
self.DEVICE_IDS, self.create_client)
self.device_manager().register_devices(self.DEVICE_IDS)
def is_enabled(self):
return self.libraries_available
@@ -199,13 +201,11 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
if (isinstance(wallet, self.wallet_class)
and hasattr(wallet, 'last_operation')
and now > wallet.last_operation + wallet.session_timeout):
client = self.get_client(wallet, force_pair=False)
if client:
client.clear_session()
wallet.last_operation = self.prevent_timeout
wallet.timeout()
wallet.timeout()
wallet.last_operation = self.prevent_timeout
def create_client(self, path, handler, hid_id):
def create_client(self, device, handler):
path = device.path
pair = ((None, path) if self.HidTransport._detect_debuglink(path)
else (path, None))
try:
@@ -215,50 +215,48 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
self.print_error("cannot connect at", path, str(e))
return None
self.print_error("connected to device at", path)
return self.client_class(transport, handler, self, hid_id)
def get_client(self, wallet, force_pair=True, check_firmware=True):
assert self.main_thread != threading.current_thread()
'''check_firmware is ignored unless force_pair is True.'''
client = self.device_manager().get_client(wallet, force_pair)
client = self.client_class(transport, handler, self)
# Try a ping for device sanity
try:
client.ping('t')
except BaseException as e:
self.print_error("ping failed", str(e))
return None
if not client.atleast_version(*self.minimum_firmware):
msg = (_('Outdated %s firmware for device labelled %s. Please '
'download the updated firmware from %s') %
(self.device, client.label(), self.firmware_URL))
handler.show_error(msg)
return None
return client
def get_client(self, wallet, force_pair=True):
# All client interaction should not be in the main GUI thread
assert self.main_thread != threading.current_thread()
devmgr = self.device_manager()
client = devmgr.client_for_wallet(self, wallet, force_pair)
if client:
self.print_error("set last_operation")
wallet.last_operation = time.time()
try:
client.ping('t')
except BaseException as e:
self.print_error("ping failed", str(e))
# Remove it from the manager's cache
self.device_manager().close_client(client)
client = None
if force_pair:
assert wallet.handler
if not client:
msg = (_('Could not connect to your %s. Verify the '
'cable is connected and that no other app is '
'using it.\nContinuing in watching-only mode '
'until the device is re-connected.') % self.device)
wallet.handler.show_error(msg)
raise DeviceDisconnectedError(msg)
if (check_firmware and not
client.atleast_version(*self.minimum_firmware)):
msg = (_('Outdated %s firmware for device labelled %s. Please '
'download the updated firmware from %s') %
(self.device, client.label(), self.firmware_URL))
wallet.handler.show_error(msg)
raise OutdatedFirmwareError(msg)
elif force_pair:
msg = (_('Could not connect to your %s. Verify the '
'cable is connected and that no other app is '
'using it.\nContinuing in watching-only mode '
'until the device is re-connected.') % self.device)
raise DeviceDisconnectedError(msg)
return client
@hook
def close_wallet(self, wallet):
if isinstance(wallet, self.wallet_class):
self.device_manager().close_wallet(wallet)
self.device_manager().unpair_wallet(wallet)
def initialize_device(self, wallet):
# Prevent timeouts during initialization
@@ -310,28 +308,35 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
wallet.thread.add(initialize_device)
def unpaired_clients(self, handler):
def unpaired_devices(self, handler):
'''Returns all connected, unpaired devices as a list of clients and a
list of descriptions.'''
devmgr = self.device_manager()
clients = devmgr.unpaired_clients(handler, self.client_class)
devices = devmgr.unpaired_devices(handler)
states = [_("wiped"), _("initialized")]
def client_desc(client):
label = client.label() or _("An unnamed device")
infos = []
for device in devices:
client = self.device_manager().create_client(device, handler, self)
if not client:
continue
state = states[client.is_initialized()]
return ("%s: serial number %s (%s)"
% (label, client.hid_id(), state))
return clients, list(map(client_desc, clients))
label = client.label() or _("An unnamed device")
descr = "%s: device ID %s (%s)" % (label, device.id_, state)
infos.append((device, descr, client.is_initialized()))
return infos
def select_device(self, wallet):
'''Called when creating a new wallet. Select the device to use. If
the device is uninitialized, go through the intialization
process.'''
msg = _("Please select which %s device to use:") % self.device
clients, labels = self.unpaired_clients(wallet.handler)
client = clients[wallet.handler.query_choice(msg, labels)]
self.device_manager().pair_wallet(wallet, client)
if not client.is_initialized():
infos = self.unpaired_devices(wallet.handler)
labels = [info[1] for info in infos]
device, descr, init = infos[wallet.handler.query_choice(msg, labels)]
self.device_manager().pair_wallet(wallet, device.id_)
if not init:
self.initialize_device(wallet)
def on_restore_wallet(self, wallet, wizard):

View File

@@ -15,6 +15,18 @@ from electrum.util import PrintError
from electrum.wallet import Wallet, BIP44_Wallet
from electrum.wizard import UserCancelled
PASSPHRASE_HELP_SHORT =_(
"Passphrases allow you to access new wallets, each "
"hidden behind a particular case-sensitive passphrase.")
PASSPHRASE_HELP = PASSPHRASE_HELP_SHORT + " " + _(
"You need to create a separate Electrum wallet for each passphrase "
"you use as they each generate different addresses. Changing "
"your passphrase does not lose other wallets, each is still "
"accessible behind its own passphrase.")
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.")
# By far the trickiest thing about this handler is the window stack;
# MacOSX is very fussy the modal dialogs are perfectly parented
@@ -195,12 +207,16 @@ class QtHandler(PrintError):
else:
vbox.addLayout(hbox_pin)
cb_phrase = QCheckBox(_('Enable Passphrase protection'))
passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)
passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
passphrase_warning.setStyleSheet("color: red")
cb_phrase = QCheckBox(_('Enable passphrases'))
cb_phrase.setChecked(False)
vbox.addWidget(passphrase_msg)
vbox.addWidget(passphrase_warning)
vbox.addWidget(cb_phrase)
title = _("Initialization settings for your %s:") % device
wizard.set_main_layout(vbox, next_enabled=next_enabled, title=title)
wizard.set_main_layout(vbox, next_enabled=next_enabled)
if method in [TIM_NEW, TIM_RECOVER]:
item = bg.checkedId()
@@ -252,25 +268,26 @@ def qt_plugin_class(base_plugin_class):
menu.addAction(_("Show on %s") % self.device, show_address)
def settings_dialog(self, window):
hid_id = self.choose_device(window)
if hid_id:
SettingsDialog(window, self, hid_id).exec_()
device_id = self.choose_device(window)
if device_id:
SettingsDialog(window, self, device_id).exec_()
def choose_device(self, window):
'''This dialog box should be usable even if the user has
forgotten their PIN or it is in bootloader mode.'''
handler = window.wallet.handler
hid_id = self.device_manager().wallet_hid_id(window.wallet)
if not hid_id:
clients, labels = self.unpaired_clients(handler)
if clients:
device_id = self.device_manager().wallet_id(window.wallet)
if not device_id:
infos = self.unpaired_devices(handler)
if infos:
labels = [info[1] for info in infos]
msg = _("Select a %s device:") % self.device
choice = self.query_choice(window, msg, labels)
if choice is not None:
hid_id = clients[choice].hid_id()
device_id = infos[choice][0].id_
else:
handler.show_error(_("No devices found"))
return hid_id
return device_id
def query_choice(self, window, msg, choices):
dialog = WindowModalDialog(window)
@@ -292,28 +309,29 @@ class SettingsDialog(WindowModalDialog):
We want users to be able to wipe a device even if they've forgotten
their PIN.'''
def __init__(self, window, plugin, hid_id):
def __init__(self, window, plugin, device_id):
title = _("%s Settings") % plugin.device
super(SettingsDialog, self).__init__(window, title)
self.setMaximumWidth(540)
devmgr = plugin.device_manager()
handler = window.wallet.handler
thread = window.wallet.thread
# wallet can be None, needn't be window.wallet
wallet = devmgr.wallet_by_hid_id(hid_id)
wallet = devmgr.wallet_by_id(device_id)
hs_rows, hs_cols = (64, 128)
self.current_label=None
def invoke_client(method, *args, **kw_args):
def task():
client = plugin.get_client(wallet, False)
client = devmgr.client_by_id(device_id, handler)
if not client:
raise RuntimeError("Device not connected")
if method:
getattr(client, method)(*args, **kw_args)
update(client.features)
return client.features
wallet.thread.add(task)
thread.add(task, on_success=update)
def update(features):
self.current_label = features.label
@@ -364,7 +382,7 @@ class SettingsDialog(WindowModalDialog):
if not self.question(msg, title=title):
return
invoke_client('toggle_passphrase')
devmgr.unpair(hid_id)
devmgr.unpair_id(device_id)
def change_homescreen():
from PIL import Image # FIXME
@@ -402,7 +420,7 @@ class SettingsDialog(WindowModalDialog):
icon=QMessageBox.Critical):
return
invoke_client('wipe_device')
devmgr.unpair(hid_id)
devmgr.unpair_id(device_id)
def slider_moved():
mins = timeout_slider.sliderPosition()
@@ -544,19 +562,8 @@ class SettingsDialog(WindowModalDialog):
# Advanced tab - toggle passphrase protection
passphrase_button = QPushButton()
passphrase_button.clicked.connect(toggle_passphrase)
passphrase_msg = QLabel(
_("Passphrases allow you to access new wallets, each "
"hidden behind a particular case-sensitive passphrase. You "
"need to create a separate Electrum wallet for each passphrase "
"you use as they each generate different addresses. Changing "
"your passphrase does not lose other wallets, each is still "
"accessible behind its own passphrase."))
passphrase_msg.setWordWrap(True)
passphrase_warning = QLabel(
_("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."))
passphrase_warning.setWordWrap(True)
passphrase_msg = WWLabel(PASSPHRASE_MSG)
passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
passphrase_warning.setStyleSheet("color: red")
advanced_glayout.addWidget(passphrase_button, 3, 2)
advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5)