qt: keepkey device init
Note: untested, don't have device
This commit is contained in:
@@ -219,7 +219,7 @@ class KeepKeyPlugin(HW_PluginBase):
|
||||
]
|
||||
def f(method):
|
||||
import threading
|
||||
settings = self.request_trezor_init_settings(wizard, method, self.device)
|
||||
settings = self.request_keepkey_init_settings(wizard, method, self.device)
|
||||
t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
@@ -510,7 +510,15 @@ class KeepKeyPlugin(HW_PluginBase):
|
||||
'accept': wizard.maybe_master_pubkey,
|
||||
'last': lambda d: wizard.is_single_password() and wizard.last_cosigner(d)
|
||||
},
|
||||
'keepkey_not_initialized': {},
|
||||
'keepkey_not_initialized': {
|
||||
'next': 'keepkey_choose_new_recover',
|
||||
},
|
||||
'keepkey_choose_new_recover': {
|
||||
'next': 'keepkey_do_init',
|
||||
},
|
||||
'keepkey_do_init': {
|
||||
'next': 'keepkey_start',
|
||||
},
|
||||
'keepkey_unlock': {
|
||||
'last': True
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import threading
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -6,18 +7,19 @@ from PyQt5.QtGui import QRegExpValidator
|
||||
from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton,
|
||||
QHBoxLayout, QButtonGroup, QGroupBox, QDialog,
|
||||
QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget,
|
||||
QMessageBox, QFileDialog, QSlider, QTabWidget)
|
||||
QMessageBox, QSlider, QTabWidget)
|
||||
|
||||
from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton,
|
||||
OkButton, CloseButton)
|
||||
OkButton, CloseButton, ChoiceWidget)
|
||||
from electrum.i18n import _
|
||||
from electrum.plugin import hook
|
||||
|
||||
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
|
||||
from ..hw_wallet.plugin import only_hook_if_libraries_available
|
||||
from .keepkey import KeepKeyPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC
|
||||
from .keepkey import KeepKeyPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY
|
||||
|
||||
from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUninitialized, WCHWUnlock, WCHWXPub
|
||||
from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock, WCHWXPub
|
||||
from electrum.gui.qt.wizard.wizard import WizardComponent
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from electrum.gui.qt.wizard.wallet import QENewWalletWizard
|
||||
@@ -47,6 +49,7 @@ CHARACTER_RECOVERY = (
|
||||
"Press ENTER or the Seed Entered button once the last word in your "
|
||||
"seed is auto-completed.")
|
||||
|
||||
|
||||
class CharacterButton(QPushButton):
|
||||
def __init__(self, text=None):
|
||||
QPushButton.__init__(self, text)
|
||||
@@ -138,7 +141,6 @@ class CharacterDialog(WindowModalDialog):
|
||||
|
||||
|
||||
class QtHandler(QtHandlerBase):
|
||||
|
||||
char_signal = pyqtSignal(object)
|
||||
pin_signal = pyqtSignal(object, object)
|
||||
close_char_dialog_signal = pyqtSignal()
|
||||
@@ -192,7 +194,6 @@ class QtHandler(QtHandlerBase):
|
||||
self.done.set()
|
||||
|
||||
|
||||
|
||||
class QtPlugin(QtPluginBase):
|
||||
# Derived classes must provide the following class-static variables:
|
||||
# icon_file
|
||||
@@ -219,54 +220,68 @@ class QtPlugin(QtPluginBase):
|
||||
SettingsDialog(window, self, keystore, device_id).exec_()
|
||||
keystore.thread.add(connect, on_success=show_dialog)
|
||||
|
||||
def request_trezor_init_settings(self, wizard, method, device):
|
||||
vbox = QVBoxLayout()
|
||||
next_enabled = True
|
||||
def request_keepkey_init_settings(self, wizard, method, device):
|
||||
keepkey_init_layout = KeepkeyInitLayout(method, device)
|
||||
keepkey_init_layout.validChanged.connect(wizard.next_button.setEnabled)
|
||||
next_enabled = method != TIM_PRIVKEY
|
||||
wizard.exec_layout(keepkey_init_layout, next_enabled=next_enabled)
|
||||
|
||||
return keepkey_init_layout.get_settings()
|
||||
|
||||
|
||||
def clean_text(widget):
|
||||
text = widget.toPlainText().strip()
|
||||
return ' '.join(text.split())
|
||||
|
||||
|
||||
class KeepkeyInitLayout(QVBoxLayout):
|
||||
validChanged = pyqtSignal([bool], arguments=['valid'])
|
||||
|
||||
def __init__(self, method, device):
|
||||
self.method = method
|
||||
|
||||
label = QLabel(_("Enter a label to name your device:"))
|
||||
name = QLineEdit()
|
||||
self.label_e = QLineEdit()
|
||||
hl = QHBoxLayout()
|
||||
hl.addWidget(label)
|
||||
hl.addWidget(name)
|
||||
hl.addWidget(self.label_e)
|
||||
hl.addStretch(1)
|
||||
vbox.addLayout(hl)
|
||||
self.addLayout(hl)
|
||||
|
||||
def clean_text(widget):
|
||||
text = widget.toPlainText().strip()
|
||||
return ' '.join(text.split())
|
||||
|
||||
if method in [TIM_NEW, TIM_RECOVER]:
|
||||
if self.method in [TIM_NEW, TIM_RECOVER]:
|
||||
gb = QGroupBox()
|
||||
hbox1 = QHBoxLayout()
|
||||
gb.setLayout(hbox1)
|
||||
# KeepKey recovery doesn't need a word count
|
||||
if method == TIM_NEW:
|
||||
vbox.addWidget(gb)
|
||||
if self.method == TIM_NEW:
|
||||
self.addWidget(gb)
|
||||
gb.setTitle(_("Select your seed length:"))
|
||||
bg = QButtonGroup()
|
||||
self.bg = QButtonGroup()
|
||||
for i, count in enumerate([12, 18, 24]):
|
||||
rb = QRadioButton(gb)
|
||||
rb.setText(_("{} words").format(count))
|
||||
bg.addButton(rb)
|
||||
bg.setId(rb, i)
|
||||
self.bg.addButton(rb)
|
||||
self.bg.setId(rb, i)
|
||||
hbox1.addWidget(rb)
|
||||
rb.setChecked(True)
|
||||
cb_pin = QCheckBox(_('Enable PIN protection'))
|
||||
cb_pin.setChecked(True)
|
||||
else:
|
||||
text = QTextEdit()
|
||||
text.setMaximumHeight(60)
|
||||
self.text_e = QTextEdit()
|
||||
self.text_e.setMaximumHeight(60)
|
||||
if method == TIM_MNEMONIC:
|
||||
msg = _("Enter your BIP39 mnemonic:")
|
||||
# TODO: validation?
|
||||
else:
|
||||
msg = _("Enter the master private key beginning with xprv:")
|
||||
|
||||
def set_enabled():
|
||||
from electrum.bip32 import is_xprv
|
||||
wizard.next_button.setEnabled(is_xprv(clean_text(text)))
|
||||
text.textChanged.connect(set_enabled)
|
||||
next_enabled = False
|
||||
self.validChanged.emit(is_xprv(clean_text(self.text_e)))
|
||||
self.text_e.textChanged.connect(set_enabled)
|
||||
|
||||
vbox.addWidget(QLabel(msg))
|
||||
vbox.addWidget(text)
|
||||
self.addWidget(QLabel(msg))
|
||||
self.addWidget(self.text_e)
|
||||
pin = QLineEdit()
|
||||
pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
|
||||
pin.setMaximumWidth(100)
|
||||
@@ -276,30 +291,29 @@ class QtPlugin(QtPluginBase):
|
||||
hbox_pin.addStretch(1)
|
||||
|
||||
if method in [TIM_NEW, TIM_RECOVER]:
|
||||
vbox.addWidget(WWLabel(RECOMMEND_PIN))
|
||||
vbox.addWidget(cb_pin)
|
||||
self.addWidget(WWLabel(RECOMMEND_PIN))
|
||||
self.addWidget(cb_pin)
|
||||
else:
|
||||
vbox.addLayout(hbox_pin)
|
||||
self.addLayout(hbox_pin)
|
||||
|
||||
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)
|
||||
self.cb_phrase = QCheckBox(_('Enable passphrases'))
|
||||
self.cb_phrase.setChecked(False)
|
||||
self.addWidget(passphrase_msg)
|
||||
self.addWidget(passphrase_warning)
|
||||
self.addWidget(self.cb_phrase)
|
||||
|
||||
wizard.exec_layout(vbox, next_enabled=next_enabled)
|
||||
|
||||
if method in [TIM_NEW, TIM_RECOVER]:
|
||||
item = bg.checkedId()
|
||||
pin = cb_pin.isChecked()
|
||||
def get_settings(self):
|
||||
if self.method in [TIM_NEW, TIM_RECOVER]:
|
||||
item = self.bg.checkedId()
|
||||
pin = self.cb_pin.isChecked()
|
||||
else:
|
||||
item = ' '.join(str(clean_text(text)).split())
|
||||
pin = str(pin.text())
|
||||
pin = str(self.pin.text())
|
||||
|
||||
return (item, name.text(), pin, cb_phrase.isChecked())
|
||||
return item, self.label_e.text(), pin, self.cb_phrase.isChecked()
|
||||
|
||||
|
||||
class Plugin(KeepKeyPlugin, QtPlugin):
|
||||
@@ -324,7 +338,9 @@ class Plugin(KeepKeyPlugin, QtPlugin):
|
||||
views = {
|
||||
'keepkey_start': {'gui': WCScriptAndDerivation},
|
||||
'keepkey_xpub': {'gui': WCHWXPub},
|
||||
'keepkey_not_initialized': {'gui': WCHWUninitialized},
|
||||
'safet_not_initialized': {'gui': WCKeepkeyInitMethod},
|
||||
'safet_choose_new_recover': {'gui': WCKeepkeyInitParams},
|
||||
'safet_do_init': {'gui': WCKeepkeyInit},
|
||||
'keepkey_unlock': {'gui': WCHWUnlock}
|
||||
}
|
||||
wizard.navmap_merge(views)
|
||||
@@ -590,3 +606,90 @@ class SettingsDialog(WindowModalDialog):
|
||||
|
||||
# Update information
|
||||
invoke_client(None)
|
||||
|
||||
|
||||
class WCKeepkeyInitMethod(WizardComponent):
|
||||
def __init__(self, parent, wizard):
|
||||
WizardComponent.__init__(self, parent, wizard, title=_('HW Setup'))
|
||||
|
||||
def on_ready(self):
|
||||
_name, _info = self.wizard_data['hardware_device']
|
||||
msg = _("Choose how you want to initialize your {}.\n\n"
|
||||
"The first two methods are secure as no secret information "
|
||||
"is entered into your computer.\n\n"
|
||||
"For the last two methods you input secrets on your keyboard "
|
||||
"and upload them to your {}, and so you should "
|
||||
"only do those on a computer you know to be trustworthy "
|
||||
"and free of malware."
|
||||
).format(_info.model_name, _info.model_name)
|
||||
choices = [
|
||||
# Must be short as QT doesn't word-wrap radio button text
|
||||
(TIM_NEW, _("Let the device generate a completely new seed randomly")),
|
||||
(TIM_RECOVER, _("Recover from a seed you have previously written down")),
|
||||
(TIM_MNEMONIC, _("Upload a BIP39 mnemonic to generate the seed")),
|
||||
(TIM_PRIVKEY, _("Upload a master private key"))
|
||||
]
|
||||
self.choice_w = ChoiceWidget(message=msg, choices=choices)
|
||||
self.layout().addWidget(self.choice_w)
|
||||
self.layout().addStretch(1)
|
||||
|
||||
self._valid = True
|
||||
|
||||
def apply(self):
|
||||
self.wizard_data['keepkey_init'] = self.choice_w.selected_item[0]
|
||||
|
||||
|
||||
class WCKeepkeyInitParams(WizardComponent):
|
||||
def __init__(self, parent, wizard):
|
||||
WizardComponent.__init__(self, parent, wizard, title=_('Set-up keepkey'))
|
||||
self.plugins = wizard.plugins
|
||||
self._busy = True
|
||||
|
||||
def on_ready(self):
|
||||
_name, _info = self.wizard_data['hardware_device']
|
||||
self.settings_layout = KeepkeyInitLayout(self.plugins.device_manager, self.wizard_data['keepkey_init'], _info.device.id_)
|
||||
self.layout().addLayout(self.settings_layout)
|
||||
self.layout().addStretch(1)
|
||||
|
||||
self.valid = self.wizard_data['keepkey_init'] != TIM_PRIVKEY
|
||||
self.busy = False
|
||||
|
||||
def apply(self):
|
||||
self.wizard_data['keepkey_settings'] = self.settings_layout.get_settings()
|
||||
|
||||
|
||||
class WCKeepkeyInit(WizardComponent, Logger):
|
||||
def __init__(self, parent, wizard):
|
||||
WizardComponent.__init__(self, parent, wizard, title=_('Set-up Keepkey'))
|
||||
Logger.__init__(self)
|
||||
self.plugins = wizard.plugins
|
||||
self.plugin = self.plugins.get_plugin('keepkey')
|
||||
|
||||
self.layout().addWidget(WWLabel('Done'))
|
||||
|
||||
self._busy = True
|
||||
|
||||
def on_ready(self):
|
||||
settings = self.wizard_data['keepkey_settings']
|
||||
method = self.wizard_data['keepkey_init']
|
||||
_name, _info = self.wizard_data['hardware_device']
|
||||
device_id = _info.device.id_
|
||||
client = self.plugins.device_manager.client_by_id(device_id, scan_now=False)
|
||||
client.handler = self.plugin.create_handler(self.wizard)
|
||||
|
||||
def initialize_device_task(settings, method, device_id, wizard, handler):
|
||||
self.plugin._initialize_device(settings, method, device_id, wizard, handler)
|
||||
self.init_done()
|
||||
|
||||
t = threading.Thread(
|
||||
target=initialize_device_task,
|
||||
args=(settings, method, device_id, None, client.handler),
|
||||
daemon=True)
|
||||
t.start()
|
||||
|
||||
def init_done(self):
|
||||
self.logger.info('Done initialize device')
|
||||
self.busy = False
|
||||
|
||||
def apply(self):
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user