Browse Source

finish separation between plugins and GUIs

283
ThomasV 9 years ago
parent
commit
ba2c737a21
  1. 2
      gui/qt/main_window.py
  2. 39
      lib/plugins.py
  3. 101
      plugins/__init__.py
  4. 7
      plugins/audio_modem/__init__.py
  5. 2
      plugins/audio_modem/qt.py
  6. 9
      plugins/cosigner_pool/__init__.py
  7. 2
      plugins/cosigner_pool/qt.py
  8. 5
      plugins/email_requests/__init__.py
  9. 2
      plugins/email_requests/qt.py
  10. 5
      plugins/exchange_rate/__init__.py
  11. 184
      plugins/exchange_rate/exchange_rate.py
  12. 3
      plugins/exchange_rate/kivy.py
  13. 184
      plugins/exchange_rate/qt.py
  14. 5
      plugins/greenaddress_instant/__init__.py
  15. 2
      plugins/greenaddress_instant/qt.py
  16. 8
      plugins/keepkey/__init__.py
  17. 96
      plugins/keepkey/keepkey.py
  18. 95
      plugins/keepkey/qt.py
  19. 9
      plugins/labels/__init__.py
  20. 3
      plugins/labels/kivy.py
  21. 77
      plugins/labels/labels.py
  22. 80
      plugins/labels/qt.py
  23. 8
      plugins/ledger/__init__.py
  24. 68
      plugins/ledger/ledger.py
  25. 66
      plugins/ledger/qt.py
  26. 6
      plugins/plot/__init__.py
  27. 2
      plugins/plot/qt.py
  28. 9
      plugins/trezor/__init__.py
  29. 200
      plugins/trezor/qt.py
  30. 195
      plugins/trezor/trezor.py
  31. 11
      plugins/trustedcoin/__init__.py
  32. 225
      plugins/trustedcoin/qt.py
  33. 228
      plugins/trustedcoin/trustedcoin.py
  34. 5
      plugins/virtualkeyboard/__init__.py
  35. 2
      plugins/virtualkeyboard/qt.py
  36. 18
      setup.py

2
gui/qt/main_window.py

@ -2867,7 +2867,7 @@ class ElectrumWindow(QMainWindow, PrintError):
run_hook('init_qt', self.gui_object)
for i, descr in enumerate(plugins.descriptions):
name = descr['name']
name = descr['__name__']
p = plugins.get(name)
if descr.get('registers_wallet_type'):
continue

39
lib/plugins.py

@ -33,26 +33,25 @@ class Plugins(PrintError):
if is_local:
find = imp.find_module('plugins')
plugins = imp.load_module('electrum_plugins', *find)
self.pathname = find[1]
else:
plugins = __import__('electrum_plugins')
self.pathname = None
self.pkgpath = os.path.dirname(plugins.__file__)
self.plugins = {}
self.network = None
self.gui_name = gui_name
self.descriptions = plugins.descriptions
for item in self.descriptions:
name = item['name']
if gui_name not in item.get('available_for', []):
self.descriptions = []
for loader, name, ispkg in pkgutil.iter_modules([self.pkgpath]):
m = loader.find_module(name).load_module(name)
d = m.__dict__
if gui_name not in d.get('available_for', []):
continue
x = item.get('registers_wallet_type')
self.descriptions.append(d)
x = d.get('registers_wallet_type')
if x:
self.register_wallet_type(config, name, x)
if config.get('use_' + name):
if not d.get('requires_wallet_type') and config.get('use_' + name):
self.load_plugin(config, name)
def get(self, name):
return self.plugins.get(name)
@ -60,22 +59,10 @@ class Plugins(PrintError):
return len(self.plugins)
def load_plugin(self, config, name):
full_name = 'electrum_plugins.' + name
full_name = 'electrum_plugins.' + name + '.' + self.gui_name
try:
if self.pathname: # local
path = os.path.join(self.pathname, name + '.py')
p = imp.load_source(full_name, path)
else:
p = __import__(full_name, fromlist=['electrum_plugins'])
if self.gui_name == 'qt':
klass = p.QtPlugin
elif self.gui_name == 'cmdline':
klass = p.CmdlinePlugin
else:
return
plugin = klass(self, config, name)
p = pkgutil.find_loader(full_name).load_module(full_name)
plugin = p.Plugin(self, config, name)
if self.network:
self.network.add_jobs(plugin.thread_jobs())
self.plugins[name] = plugin
@ -103,7 +90,7 @@ class Plugins(PrintError):
def is_available(self, name, w):
for d in self.descriptions:
if d.get('name') == name:
if d.get('__name__') == name:
break
else:
return False

101
plugins/__init__.py

@ -16,105 +16,4 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import electrum
from electrum.i18n import _
descriptions = [
{
'name': 'audio_modem',
'fullname': _('Audio MODEM'),
'description': _('Provides support for air-gapped transaction signing.'),
'requires': [('amodem', 'http://github.com/romanz/amodem/')],
'available_for': ['qt'],
},
{
'name': 'btchipwallet',
'fullname': _('Ledger Wallet'),
'description': _('Provides support for Ledger hardware wallet'),
'requires': [('btchip', 'github.com/ledgerhq/btchip-python')],
'requires_wallet_type': ['btchip'],
'registers_wallet_type': ('hardware', 'btchip', _("Ledger wallet")),
'available_for': ['qt', 'cmdline'],
},
{
'name': 'cosigner_pool',
'fullname': _('Cosigner Pool'),
'description': ' '.join([
_("This plugin facilitates the use of multi-signatures wallets."),
_("It sends and receives partially signed transactions from/to your cosigner wallet."),
_("Transactions are encrypted and stored on a remote server.")
]),
'requires_wallet_type': ['2of2', '2of3'],
'available_for': ['qt'],
},
{
'name': 'email_requests',
'fullname': 'Email',
'description': _("Send and receive payment request with an email account"),
'available_for': ['qt'],
},
{
'name': 'exchange_rate',
'fullname': _("Exchange rates"),
'description': _("Exchange rates and currency conversion tools."),
'available_for': ['qt','kivy'],
},
{
'name': 'greenaddress_instant',
'fullname': 'GreenAddress instant',
'description': _("Allows validating if your transactions have instant confirmations by GreenAddress"),
'available_for': ['qt'],
},
{
'name':'keepkey',
'fullname': 'KeepKey',
'description': _('Provides support for KeepKey hardware wallet'),
'requires': [('keepkeylib','github.com/keepkey/python-keepkey')],
'requires_wallet_type': ['keepkey'],
'registers_wallet_type': ('hardware', 'keepkey', _("KeepKey wallet")),
'available_for': ['qt', 'cmdline'],
},
{
'name': 'labels',
'fullname': _('LabelSync'),
'description': '\n'.join([
_("Synchronize your labels across multiple Electrum installs by using a remote database to save your data. Labels, transactions ids and addresses are encrypted before they are sent to the remote server."),
_("The label sync's server software is open-source as well and can be found on github.com/maran/electrum-sync-server")
]),
'available_for': ['qt','kivy']
},
{
'name': 'plot',
'fullname': 'Plot History',
'description': _("Ability to plot transaction history in graphical mode."),
'requires': [('matplotlib', 'matplotlib')],
'available_for': ['qt'],
},
{
'name':'trezor',
'fullname': 'Trezor Wallet',
'description': _('Provides support for Trezor hardware wallet'),
'requires': [('trezorlib','github.com/trezor/python-trezor')],
'requires_wallet_type': ['trezor'],
'registers_wallet_type': ('hardware', 'trezor', _("Trezor wallet")),
'available_for': ['qt', 'cmdline'],
},
{
'name': 'trustedcoin',
'fullname': _('Two Factor Authentication'),
'description': ''.join([
_("This plugin adds two-factor authentication to your wallet."), '<br/>',
_("For more information, visit"),
" <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>"
]),
'requires_wallet_type': ['2fa'],
'registers_wallet_type': ('twofactor', '2fa', _("Wallet with two-factor authentication")),
'available_for': ['qt', 'cmdline'],
},
{
'name': 'virtualkeyboard',
'fullname': 'Virtual Keyboard',
'description': '%s\n%s' % (_("Add an optional virtual keyboard to the password dialog."), _("Warning: do not use this if it makes you pick a weaker password.")),
'available_for': ['qt'],
}
]

7
plugins/audio_modem/__init__.py

@ -0,0 +1,7 @@
from electrum.i18n import _
fullname = _('Audio MODEM')
description = _('Provides support for air-gapped transaction signing.')
requires = [('amodem', 'http://github.com/romanz/amodem/')]
available_for = ['qt']

2
plugins/audio_modem.py → plugins/audio_modem/qt.py

@ -25,7 +25,7 @@ except ImportError:
print_error('Audio MODEM is not found.')
class QtPlugin(BasePlugin):
class Plugin(BasePlugin):
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)

9
plugins/cosigner_pool/__init__.py

@ -0,0 +1,9 @@
from electrum.i18n import _
fullname = _('Cosigner Pool')
description = ' '.join([
_("This plugin facilitates the use of multi-signatures wallets."),
_("It sends and receives partially signed transactions from/to your cosigner wallet."),
_("Transactions are encrypted and stored on a remote server.")
])
requires_wallet_type = ['2of2', '2of3']
available_for = ['qt']

2
plugins/cosigner_pool.py → plugins/cosigner_pool/qt.py

@ -79,7 +79,7 @@ class Listener(util.DaemonThread):
time.sleep(30)
class QtPlugin(BasePlugin):
class Plugin(BasePlugin):
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)

5
plugins/email_requests/__init__.py

@ -0,0 +1,5 @@
from electrum.i18n import _
fullname = _('Email')
description = _("Send and receive payment request with an email account")
available_for = ['qt']

2
plugins/email_requests.py → plugins/email_requests/qt.py

@ -101,7 +101,7 @@ class Processor(threading.Thread):
s.quit()
class QtPlugin(BasePlugin):
class Plugin(BasePlugin):
def fullname(self):
return 'Email'

5
plugins/exchange_rate/__init__.py

@ -0,0 +1,5 @@
from electrum.i18n import _
fullname = _("Exchange rates")
description = _("Exchange rates and currency conversion tools.")
available_for = ['qt','kivy']

184
plugins/exchange_rate.py → plugins/exchange_rate/exchange_rate.py

@ -7,7 +7,6 @@ import time
import traceback
import csv
from decimal import Decimal
from functools import partial
from electrum.bitcoin import COIN
from electrum.plugins import BasePlugin, hook
@ -235,7 +234,7 @@ class Winkdex(ExchangeBase):
for h in history])
class Plugin(BasePlugin, ThreadJob):
class FxPlugin(BasePlugin, ThreadJob):
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
@ -243,7 +242,6 @@ class Plugin(BasePlugin, ThreadJob):
self.history_used_spot = False
self.ccy_combo = None
self.hist_checkbox = None
self.app = None
is_exchange = lambda obj: (inspect.isclass(obj)
and issubclass(obj, ExchangeBase)
and obj != ExchangeBase)
@ -286,16 +284,18 @@ class Plugin(BasePlugin, ThreadJob):
if self.config_exchange() != name:
self.config.set_key('use_exchange', name, True)
on_quotes = lambda: self.app.emit(SIGNAL('new_fx_quotes'))
on_history = lambda: self.app.emit(SIGNAL('new_fx_history'))
self.exchange = class_(on_quotes, on_history)
self.exchange = class_(self.on_quotes, self.on_history)
# A new exchange means new fx quotes, initially empty. Force
# a quote refresh
self.timeout = 0
self.get_historical_rates()
#self.on_fx_quotes()
def on_quotes(self):
pass
def on_history(self):
pass
def exchange_rate(self):
'''Returns None, or the exchange rate as a Decimal'''
@ -363,175 +363,3 @@ class Plugin(BasePlugin, ThreadJob):
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from electrum_gui.qt.util import *
from electrum_gui.qt.amountedit import AmountEdit
class QtPlugin(Plugin):
def connect_fields(self, window, btc_e, fiat_e, fee_e):
def edit_changed(edit):
edit.setStyleSheet(BLACK_FG)
fiat_e.is_last_edited = (edit == fiat_e)
amount = edit.get_amount()
rate = self.exchange_rate()
if rate is None or amount is None:
if edit is fiat_e:
btc_e.setText("")
if fee_e:
fee_e.setText("")
else:
fiat_e.setText("")
else:
if edit is fiat_e:
btc_e.setAmount(int(amount / Decimal(rate) * COIN))
if fee_e: window.update_fee()
btc_e.setStyleSheet(BLUE_FG)
else:
fiat_e.setText(self.ccy_amount_str(
amount * Decimal(rate) / COIN, False))
fiat_e.setStyleSheet(BLUE_FG)
fiat_e.textEdited.connect(partial(edit_changed, fiat_e))
btc_e.textEdited.connect(partial(edit_changed, btc_e))
fiat_e.is_last_edited = False
@hook
def init_qt(self, gui):
self.app = gui.app
@hook
def do_clear(self, window):
window.fiat_send_e.setText('')
def close(self):
# Get rid of hooks before updating status bars.
BasePlugin.close(self)
self.app.emit(SIGNAL('close_fx_plugin'))
def restore_window(self, window):
window.update_status()
window.history_list.refresh_headers()
window.fiat_send_e.hide()
window.fiat_receive_e.hide()
def on_fx_history(self, window):
'''Called when historical fx quotes are updated'''
window.history_list.update()
def on_fx_quotes(self, window):
'''Called when fresh spot fx quotes come in'''
window.update_status()
self.populate_ccy_combo()
# Refresh edits with the new rate
edit = window.fiat_send_e if window.fiat_send_e.is_last_edited else window.amount_e
edit.textEdited.emit(edit.text())
edit = window.fiat_receive_e if window.fiat_receive_e.is_last_edited else window.receive_amount_e
edit.textEdited.emit(edit.text())
# History tab needs updating if it used spot
if self.history_used_spot:
self.on_fx_history(window)
def on_ccy_combo_change(self):
'''Called when the chosen currency changes'''
ccy = str(self.ccy_combo.currentText())
if ccy and ccy != self.ccy:
self.ccy = ccy
self.config.set_key('currency', ccy, True)
self.app.emit(SIGNAL('new_fx_quotes'))
self.get_historical_rates() # Because self.ccy changes
self.hist_checkbox_update()
def hist_checkbox_update(self):
if self.hist_checkbox:
self.hist_checkbox.setEnabled(self.ccy in self.exchange.history_ccys())
self.hist_checkbox.setChecked(self.config_history())
def populate_ccy_combo(self):
# There should be at most one instance of the settings dialog
combo = self.ccy_combo
# NOTE: bool(combo) is False if it is empty. Nuts.
if combo is not None:
combo.blockSignals(True)
combo.clear()
combo.addItems(sorted(self.exchange.quotes.keys()))
combo.blockSignals(False)
combo.setCurrentIndex(combo.findText(self.ccy))
@hook
def on_new_window(self, window):
# Additional send and receive edit boxes
send_e = AmountEdit(self.config_ccy)
window.send_grid.addWidget(send_e, 4, 2, Qt.AlignLeft)
window.amount_e.frozen.connect(
lambda: send_e.setFrozen(window.amount_e.isReadOnly()))
receive_e = AmountEdit(self.config_ccy)
window.receive_grid.addWidget(receive_e, 2, 2, Qt.AlignLeft)
window.fiat_send_e = send_e
window.fiat_receive_e = receive_e
self.connect_fields(window, window.amount_e, send_e, window.fee_e)
self.connect_fields(window, window.receive_amount_e, receive_e, None)
window.history_list.refresh_headers()
window.update_status()
window.connect(window.app, SIGNAL('new_fx_quotes'), lambda: self.on_fx_quotes(window))
window.connect(window.app, SIGNAL('new_fx_history'), lambda: self.on_fx_history(window))
window.connect(window.app, SIGNAL('close_fx_plugin'), lambda: self.restore_window(window))
window.connect(window.app, SIGNAL('refresh_headers'), window.history_list.refresh_headers)
def settings_widget(self, window):
return EnterButton(_('Settings'), self.settings_dialog)
def settings_dialog(self):
d = QDialog()
d.setWindowTitle("Settings")
layout = QGridLayout(d)
layout.addWidget(QLabel(_('Exchange rate API: ')), 0, 0)
layout.addWidget(QLabel(_('Currency: ')), 1, 0)
layout.addWidget(QLabel(_('History Rates: ')), 2, 0)
# Currency list
self.ccy_combo = QComboBox()
self.ccy_combo.currentIndexChanged.connect(self.on_ccy_combo_change)
self.populate_ccy_combo()
def on_change_ex(idx):
exchange = str(combo_ex.currentText())
if exchange != self.exchange.name():
self.set_exchange(exchange)
self.hist_checkbox_update()
def on_change_hist(checked):
if checked:
self.config.set_key('history_rates', 'checked')
self.get_historical_rates()
else:
self.config.set_key('history_rates', 'unchecked')
self.app.emit(SIGNAL('refresh_headers'))
def ok_clicked():
self.timeout = 0
self.ccy_combo = None
d.accept()
combo_ex = QComboBox()
combo_ex.addItems(sorted(self.exchanges.keys()))
combo_ex.setCurrentIndex(combo_ex.findText(self.config_exchange()))
combo_ex.currentIndexChanged.connect(on_change_ex)
self.hist_checkbox = QCheckBox()
self.hist_checkbox.stateChanged.connect(on_change_hist)
self.hist_checkbox_update()
ok_button = QPushButton(_("OK"))
ok_button.clicked.connect(lambda: ok_clicked())
layout.addWidget(self.ccy_combo,1,1)
layout.addWidget(combo_ex,0,1)
layout.addWidget(self.hist_checkbox,2,1)
layout.addWidget(ok_button,3,1)
return d.exec_()

3
plugins/exchange_rate/kivy.py

@ -0,0 +1,3 @@
from exchange_rate import FxPlugin
class Plugin(FxPlugin):
pass

184
plugins/exchange_rate/qt.py

@ -0,0 +1,184 @@
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from electrum_gui.qt.util import *
from electrum_gui.qt.amountedit import AmountEdit
from electrum.bitcoin import COIN
from electrum.i18n import _
from decimal import Decimal
from functools import partial
from electrum.plugins import hook
from exchange_rate import FxPlugin
class Plugin(FxPlugin):
def connect_fields(self, window, btc_e, fiat_e, fee_e):
def edit_changed(edit):
edit.setStyleSheet(BLACK_FG)
fiat_e.is_last_edited = (edit == fiat_e)
amount = edit.get_amount()
rate = self.exchange_rate()
if rate is None or amount is None:
if edit is fiat_e:
btc_e.setText("")
if fee_e:
fee_e.setText("")
else:
fiat_e.setText("")
else:
if edit is fiat_e:
btc_e.setAmount(int(amount / Decimal(rate) * COIN))
if fee_e: window.update_fee()
btc_e.setStyleSheet(BLUE_FG)
else:
fiat_e.setText(self.ccy_amount_str(
amount * Decimal(rate) / COIN, False))
fiat_e.setStyleSheet(BLUE_FG)
fiat_e.textEdited.connect(partial(edit_changed, fiat_e))
btc_e.textEdited.connect(partial(edit_changed, btc_e))
fiat_e.is_last_edited = False
@hook
def init_qt(self, gui):
self.app = gui.app
@hook
def do_clear(self, window):
window.fiat_send_e.setText('')
def close(self):
# Get rid of hooks before updating status bars.
FxPlugin.close(self)
self.app.emit(SIGNAL('close_fx_plugin'))
def restore_window(self, window):
window.update_status()
window.history_list.refresh_headers()
window.fiat_send_e.hide()
window.fiat_receive_e.hide()
def on_quotes(self):
self.app.emit(SIGNAL('new_fx_quotes'))
def on_history(self):
self.app.emit(SIGNAL('new_fx_history'))
def on_fx_history(self, window):
'''Called when historical fx quotes are updated'''
window.history_list.update()
def on_fx_quotes(self, window):
'''Called when fresh spot fx quotes come in'''
window.update_status()
self.populate_ccy_combo()
# Refresh edits with the new rate
edit = window.fiat_send_e if window.fiat_send_e.is_last_edited else window.amount_e
edit.textEdited.emit(edit.text())
edit = window.fiat_receive_e if window.fiat_receive_e.is_last_edited else window.receive_amount_e
edit.textEdited.emit(edit.text())
# History tab needs updating if it used spot
if self.history_used_spot:
self.on_fx_history(window)
def on_ccy_combo_change(self):
'''Called when the chosen currency changes'''
ccy = str(self.ccy_combo.currentText())
if ccy and ccy != self.ccy:
self.ccy = ccy
self.config.set_key('currency', ccy, True)
self.app.emit(SIGNAL('new_fx_quotes'))
self.get_historical_rates() # Because self.ccy changes
self.hist_checkbox_update()
def hist_checkbox_update(self):
if self.hist_checkbox:
self.hist_checkbox.setEnabled(self.ccy in self.exchange.history_ccys())
self.hist_checkbox.setChecked(self.config_history())
def populate_ccy_combo(self):
# There should be at most one instance of the settings dialog
combo = self.ccy_combo
# NOTE: bool(combo) is False if it is empty. Nuts.
if combo is not None:
combo.blockSignals(True)
combo.clear()
combo.addItems(sorted(self.exchange.quotes.keys()))
combo.blockSignals(False)
combo.setCurrentIndex(combo.findText(self.ccy))
@hook
def on_new_window(self, window):
# Additional send and receive edit boxes
send_e = AmountEdit(self.config_ccy)
window.send_grid.addWidget(send_e, 4, 2, Qt.AlignLeft)
window.amount_e.frozen.connect(
lambda: send_e.setFrozen(window.amount_e.isReadOnly()))
receive_e = AmountEdit(self.config_ccy)
window.receive_grid.addWidget(receive_e, 2, 2, Qt.AlignLeft)
window.fiat_send_e = send_e
window.fiat_receive_e = receive_e
self.connect_fields(window, window.amount_e, send_e, window.fee_e)
self.connect_fields(window, window.receive_amount_e, receive_e, None)
window.history_list.refresh_headers()
window.update_status()
window.connect(window.app, SIGNAL('new_fx_quotes'), lambda: self.on_fx_quotes(window))
window.connect(window.app, SIGNAL('new_fx_history'), lambda: self.on_fx_history(window))
window.connect(window.app, SIGNAL('close_fx_plugin'), lambda: self.restore_window(window))
window.connect(window.app, SIGNAL('refresh_headers'), window.history_list.refresh_headers)
def settings_widget(self, window):
return EnterButton(_('Settings'), self.settings_dialog)
def settings_dialog(self):
d = QDialog()
d.setWindowTitle("Settings")
layout = QGridLayout(d)
layout.addWidget(QLabel(_('Exchange rate API: ')), 0, 0)
layout.addWidget(QLabel(_('Currency: ')), 1, 0)
layout.addWidget(QLabel(_('History Rates: ')), 2, 0)
# Currency list
self.ccy_combo = QComboBox()
self.ccy_combo.currentIndexChanged.connect(self.on_ccy_combo_change)
self.populate_ccy_combo()
def on_change_ex(idx):
exchange = str(combo_ex.currentText())
if exchange != self.exchange.name():
self.set_exchange(exchange)
self.hist_checkbox_update()
def on_change_hist(checked):
if checked:
self.config.set_key('history_rates', 'checked')
self.get_historical_rates()
else:
self.config.set_key('history_rates', 'unchecked')
self.app.emit(SIGNAL('refresh_headers'))
def ok_clicked():
self.timeout = 0
self.ccy_combo = None
d.accept()
combo_ex = QComboBox()
combo_ex.addItems(sorted(self.exchanges.keys()))
combo_ex.setCurrentIndex(combo_ex.findText(self.config_exchange()))
combo_ex.currentIndexChanged.connect(on_change_ex)
self.hist_checkbox = QCheckBox()
self.hist_checkbox.stateChanged.connect(on_change_hist)
self.hist_checkbox_update()
ok_button = QPushButton(_("OK"))
ok_button.clicked.connect(lambda: ok_clicked())
layout.addWidget(self.ccy_combo,1,1)
layout.addWidget(combo_ex,0,1)
layout.addWidget(self.hist_checkbox,2,1)
layout.addWidget(ok_button,3,1)
return d.exec_()

5
plugins/greenaddress_instant/__init__.py

@ -0,0 +1,5 @@
from electrum.i18n import _
fullname = 'GreenAddress instant'
description = _("Allows validating if your transactions have instant confirmations by GreenAddress")
available_for = ['qt']

2
plugins/greenaddress_instant.py → plugins/greenaddress_instant/qt.py

@ -28,7 +28,7 @@ from electrum.i18n import _
class QtPlugin(BasePlugin):
class Plugin(BasePlugin):
button_label = _("Verify GA instant")

8
plugins/keepkey/__init__.py

@ -0,0 +1,8 @@
from electrum.i18n import _
fullname = 'KeepKey'
description = _('Provides support for KeepKey hardware wallet')
requires = [('keepkeylib','github.com/keepkey/python-keepkey')]
requires_wallet_type = ['keepkey']
registers_wallet_type = ('hardware', 'keepkey', _("KeepKey wallet"))
available_for = ['qt', 'cmdline']

96
plugins/keepkey.py → plugins/keepkey/keepkey.py

@ -207,7 +207,7 @@ class KeepKeyWallet(BIP32_HD_Wallet):
class Plugin(BasePlugin):
class KeepKeyPlugin(BasePlugin):
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
@ -419,100 +419,6 @@ class Plugin(BasePlugin):
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
import PyQt4.QtCore as QtCore
from electrum_gui.qt.util import *
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
from electrum_gui.qt.installwizard import InstallWizard
from keepkeylib.qt.pinmatrix import PinMatrixWidget
class QtPlugin(Plugin):
@hook
def load_wallet(self, wallet, window):
self.print_error("load_wallet")
self.wallet = wallet
self.wallet.plugin = self
self.keepkey_button = StatusBarButton(QIcon(":icons/keepkey.png"), _("KeepKey"), partial(self.settings_dialog, window))
if type(window) is ElectrumWindow:
window.statusBar().addPermanentWidget(self.keepkey_button)
if self.handler is None:
self.handler = KeepKeyQtHandler(window)
try:
self.get_client().ping('t')
except BaseException as e:
QMessageBox.information(window, _('Error'), _("KeepKey device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK'))
self.wallet.force_watching_only = True
return
if self.wallet.addresses() and not self.wallet.check_proper_device():
QMessageBox.information(window, _('Error'), _("This wallet does not match your KeepKey device"), _('OK'))
self.wallet.force_watching_only = True
@hook
def installwizard_load_wallet(self, wallet, window):
if type(wallet) != KeepKeyWallet:
return
self.load_wallet(wallet, window)
@hook
def installwizard_restore(self, wizard, storage):
if storage.get('wallet_type') != 'keepkey':
return
seed = wizard.enter_seed_dialog("Enter your KeepKey seed", None, func=lambda x:True)
if not seed:
return
wallet = KeepKeyWallet(storage)
self.wallet = wallet
handler = KeepKeyQtHandler(wizard)
passphrase = handler.get_passphrase(_("Please enter your KeepKey passphrase.") + '\n' + _("Press OK if you do not use one."))
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)
# disable keepkey plugin
self.set_enabled(False)
return wallet
@hook
def receive_menu(self, menu, addrs):
if not self.wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1:
menu.addAction(_("Show on TREZOR"), lambda: self.show_address(addrs[0]))
def settings_dialog(self, window):
try:
device_id = self.get_client().get_device_id()
except BaseException as e:
window.show_message(str(e))
return
get_label = lambda: self.get_client().features.label
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
d = QDialog()
layout = QGridLayout(d)
layout.addWidget(QLabel("KeepKey Options"),0,0)
layout.addWidget(QLabel("ID:"),1,0)
layout.addWidget(QLabel(" %s" % device_id),1,1)
def modify_label():
response = QInputDialog().getText(None, "Set New KeepKey Label", "New KeepKey Label: (upon submission confirm on KeepKey)")
if not response[1]:
return
new_label = str(response[0])
self.handler.show_message("Please confirm label change on KeepKey")
status = self.get_client().apply_settings(label=new_label)
self.handler.stop()
update_label()
current_label_label = QLabel()
update_label()
change_label_button = QPushButton("Modify")
change_label_button.clicked.connect(modify_label)
layout.addWidget(current_label_label,3,0)
layout.addWidget(change_label_button,3,1)
d.exec_()
class CmdlinePlugin(Plugin):

95
plugins/keepkey/qt.py

@ -0,0 +1,95 @@
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
import PyQt4.QtCore as QtCore
from electrum_gui.qt.util import *
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
from electrum_gui.qt.installwizard import InstallWizard
from keepkeylib.qt.pinmatrix import PinMatrixWidget
from keepkey import KeepKeyPlugin
class Plugin(KeepKeyPlugin):
@hook
def load_wallet(self, wallet, window):
self.print_error("load_wallet")
self.wallet = wallet
self.wallet.plugin = self
self.keepkey_button = StatusBarButton(QIcon(":icons/keepkey.png"), _("KeepKey"), partial(self.settings_dialog, window))
if type(window) is ElectrumWindow:
window.statusBar().addPermanentWidget(self.keepkey_button)
if self.handler is None:
self.handler = KeepKeyQtHandler(window)
try:
self.get_client().ping('t')
except BaseException as e:
QMessageBox.information(window, _('Error'), _("KeepKey device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK'))
self.wallet.force_watching_only = True
return
if self.wallet.addresses() and not self.wallet.check_proper_device():
QMessageBox.information(window, _('Error'), _("This wallet does not match your KeepKey device"), _('OK'))
self.wallet.force_watching_only = True
@hook
def installwizard_load_wallet(self, wallet, window):
if type(wallet) != KeepKeyWallet:
return
self.load_wallet(wallet, window)
@hook
def installwizard_restore(self, wizard, storage):
if storage.get('wallet_type') != 'keepkey':
return
seed = wizard.enter_seed_dialog("Enter your KeepKey seed", None, func=lambda x:True)
if not seed:
return
wallet = KeepKeyWallet(storage)
self.wallet = wallet
handler = KeepKeyQtHandler(wizard)
passphrase = handler.get_passphrase(_("Please enter your KeepKey passphrase.") + '\n' + _("Press OK if you do not use one."))
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)
# disable keepkey plugin
self.set_enabled(False)
return wallet
@hook
def receive_menu(self, menu, addrs):
if not self.wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1:
menu.addAction(_("Show on TREZOR"), lambda: self.show_address(addrs[0]))
def settings_dialog(self, window):
try:
device_id = self.get_client().get_device_id()
except BaseException as e:
window.show_message(str(e))
return
get_label = lambda: self.get_client().features.label
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
d = QDialog()
layout = QGridLayout(d)
layout.addWidget(QLabel("KeepKey Options"),0,0)
layout.addWidget(QLabel("ID:"),1,0)
layout.addWidget(QLabel(" %s" % device_id),1,1)
def modify_label():
response = QInputDialog().getText(None, "Set New KeepKey Label", "New KeepKey Label: (upon submission confirm on KeepKey)")
if not response[1]:
return
new_label = str(response[0])
self.handler.show_message("Please confirm label change on KeepKey")
status = self.get_client().apply_settings(label=new_label)
self.handler.stop()
update_label()
current_label_label = QLabel()
update_label()
change_label_button = QPushButton("Modify")
change_label_button.clicked.connect(modify_label)
layout.addWidget(current_label_label,3,0)
layout.addWidget(change_label_button,3,1)
d.exec_()

9
plugins/labels/__init__.py

@ -0,0 +1,9 @@
from electrum.i18n import _
fullname = _('LabelSync')
description = '\n'.join([
_("Synchronize your labels across multiple Electrum installs by using a remote database to save your data. Labels, transactions ids and addresses are encrypted before they are sent to the remote server."),
_("The label sync's server software is open-source as well and can be found on github.com/maran/electrum-sync-server")
])
available_for = ['qt', 'kivy']

3
plugins/labels/kivy.py

@ -0,0 +1,3 @@
from labels import LabelsPlugin
class Plugin(LabelsPlugin):
pass

77
plugins/labels.py → plugins/labels/labels.py

@ -1,11 +1,8 @@
import socket
import requests
import threading
import hashlib
import json
import sys
import traceback
from functools import partial
import aes
import base64
@ -15,7 +12,9 @@ from electrum.plugins import BasePlugin, hook
from electrum.i18n import _
class Plugin(BasePlugin):
class LabelsPlugin(BasePlugin):
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
@ -137,73 +136,3 @@ class Plugin(BasePlugin):
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
import PyQt4.QtGui as QtGui
from electrum_gui.qt import HelpButton, EnterButton
from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton, OkButton
class QtPlugin(Plugin):
def __init__(self, *args):
Plugin.__init__(self, *args)
self.obj = QObject()
def requires_settings(self):
return True
def settings_widget(self, window):
return EnterButton(_('Settings'),
partial(self.settings_dialog, window))
def settings_dialog(self, window):
d = QDialog(window)
vbox = QVBoxLayout(d)
layout = QGridLayout()
vbox.addLayout(layout)
layout.addWidget(QLabel("Label sync options: "), 2, 0)
self.upload = ThreadedButton("Force upload",
partial(self.push_thread, window.wallet),
self.done_processing)
layout.addWidget(self.upload, 2, 1)
self.download = ThreadedButton("Force download",
partial(self.pull_thread, window.wallet, True),
self.done_processing)
layout.addWidget(self.download, 2, 2)
self.accept = OkButton(d, _("Done"))
vbox.addLayout(Buttons(CancelButton(d), self.accept))
if d.exec_():
return True
else:
return False
def on_pulled(self, wallet):
self.obj.emit(SIGNAL('labels_changed'), wallet)
def done_processing(self):
QMessageBox.information(None, _("Labels synchronised"),
_("Your labels have been synchronised."))
@hook
def on_new_window(self, window):
window.connect(window.app, SIGNAL('labels_changed'), window.update_tabs)
wallet = window.wallet
nonce = self.get_nonce(wallet)
self.print_error("wallet", wallet.basename(), "nonce is", nonce)
mpk = ''.join(sorted(wallet.get_master_public_keys().values()))
if not mpk:
return
password = hashlib.sha1(mpk).digest().encode('hex')[:32]
iv = hashlib.sha256(password).digest()[:16]
wallet_id = hashlib.sha256(mpk).digest().encode('hex')
self.wallets[wallet] = (password, iv, wallet_id)
# If there is an auth token we can try to actually start syncing
t = threading.Thread(target=self.pull_thread, args=(wallet, False))
t.setDaemon(True)
t.start()
@hook
def on_close_window(self, window):
self.wallets.pop(window.wallet)

80
plugins/labels/qt.py

@ -0,0 +1,80 @@
import hashlib
import threading
from functools import partial
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
import PyQt4.QtGui as QtGui
from electrum.plugins import hook
from electrum.i18n import _
from electrum_gui.qt import HelpButton, EnterButton
from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton, OkButton
from labels import LabelsPlugin
class Plugin(LabelsPlugin):
def __init__(self, *args):
LabelsPlugin.__init__(self, *args)
self.obj = QObject()
def requires_settings(self):
return True
def settings_widget(self, window):
return EnterButton(_('Settings'),
partial(self.settings_dialog, window))
def settings_dialog(self, window):
d = QDialog(window)
vbox = QVBoxLayout(d)
layout = QGridLayout()
vbox.addLayout(layout)
layout.addWidget(QLabel("Label sync options: "), 2, 0)
self.upload = ThreadedButton("Force upload",
partial(self.push_thread, window.wallet),
self.done_processing)
layout.addWidget(self.upload, 2, 1)
self.download = ThreadedButton("Force download",
partial(self.pull_thread, window.wallet, True),
self.done_processing)
layout.addWidget(self.download, 2, 2)
self.accept = OkButton(d, _("Done"))
vbox.addLayout(Buttons(CancelButton(d), self.accept))
if d.exec_():
return True
else:
return False
def on_pulled(self, wallet):
self.obj.emit(SIGNAL('labels_changed'), wallet)
def done_processing(self):
QMessageBox.information(None, _("Labels synchronised"),
_("Your labels have been synchronised."))
@hook
def on_new_window(self, window):
window.connect(window.app, SIGNAL('labels_changed'), window.update_tabs)
wallet = window.wallet
nonce = self.get_nonce(wallet)
self.print_error("wallet", wallet.basename(), "nonce is", nonce)
mpk = ''.join(sorted(wallet.get_master_public_keys().values()))
if not mpk:
return
password = hashlib.sha1(mpk).digest().encode('hex')[:32]
iv = hashlib.sha256(password).digest()[:16]
wallet_id = hashlib.sha256(mpk).digest().encode('hex')
self.wallets[wallet] = (password, iv, wallet_id)
# If there is an auth token we can try to actually start syncing
t = threading.Thread(target=self.pull_thread, args=(wallet, False))
t.setDaemon(True)
t.start()
@hook
def on_close_window(self, window):
self.wallets.pop(window.wallet)

8
plugins/ledger/__init__.py

@ -0,0 +1,8 @@
from electrum.i18n import _
fullname = 'Ledger Wallet'
description = 'Provides support for Ledger hardware wallet'
requires = [('btchip', 'github.com/ledgerhq/btchip-python')]
requires_wallet_type = ['btchip']
registers_wallet_type = ('hardware', 'btchip', _("Ledger wallet"))
available_for = ['qt', 'cmdline']

68
plugins/btchipwallet.py → plugins/ledger/ledger.py

@ -425,7 +425,7 @@ class BTChipWallet(BIP32_HD_Wallet):
return True, response, response
class Plugin(BasePlugin):
class LedgerPlugin(BasePlugin):
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
@ -495,72 +495,6 @@ class Plugin(BasePlugin):
except Exception as e:
tx.error = str(e)
from PyQt4.Qt import QApplication, QMessageBox, QDialog, QInputDialog, QLineEdit, QVBoxLayout, QLabel, QThread, SIGNAL
import PyQt4.QtCore as QtCore
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
class QtPlugin(Plugin):
@hook
def load_wallet(self, wallet, window):
self.wallet = wallet
self.wallet.plugin = self
if self.handler is None:
self.handler = BTChipQTHandler(window)
if self.btchip_is_connected():
if not self.wallet.check_proper_device():
QMessageBox.information(window, _('Error'), _("This wallet does not match your Ledger device"), _('OK'))
self.wallet.force_watching_only = True
else:
QMessageBox.information(window, _('Error'), _("Ledger device not detected.\nContinuing in watching-only mode."), _('OK'))
self.wallet.force_watching_only = True
class BTChipQTHandler:
def __init__(self, win):
self.win = win
self.win.connect(win, SIGNAL('btchip_done'), self.dialog_stop)
self.win.connect(win, SIGNAL('btchip_message_dialog'), self.message_dialog)
self.win.connect(win, SIGNAL('btchip_auth_dialog'), self.auth_dialog)
self.done = threading.Event()
def stop(self):
self.win.emit(SIGNAL('btchip_done'))
def show_message(self, msg):
self.message = msg
self.win.emit(SIGNAL('btchip_message_dialog'))
def prompt_auth(self, msg):
self.done.clear()
self.message = msg
self.win.emit(SIGNAL('btchip_auth_dialog'))
self.done.wait()
return self.response
def auth_dialog(self):
response = QInputDialog.getText(None, "Ledger Wallet Authentication", self.message, QLineEdit.Password)
if not response[1]:
self.response = None
else:
self.response = str(response[0])
self.done.set()
def message_dialog(self):
self.d = QDialog()
self.d.setModal(1)
self.d.setWindowTitle('Ledger')
self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
l = QLabel(self.message)
vbox = QVBoxLayout(self.d)
vbox.addWidget(l)
self.d.show()
def dialog_stop(self):
if self.d is not None:
self.d.hide()
self.d = None
class CmdlinePlugin(Plugin):
@hook

66
plugins/ledger/qt.py

@ -0,0 +1,66 @@
from PyQt4.Qt import QApplication, QMessageBox, QDialog, QInputDialog, QLineEdit, QVBoxLayout, QLabel, QThread, SIGNAL
import PyQt4.QtCore as QtCore
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
class Plugin(LedgerPlugin):
@hook
def load_wallet(self, wallet, window):
self.wallet = wallet
self.wallet.plugin = self
if self.handler is None:
self.handler = BTChipQTHandler(window)
if self.btchip_is_connected():
if not self.wallet.check_proper_device():
QMessageBox.information(window, _('Error'), _("This wallet does not match your Ledger device"), _('OK'))
self.wallet.force_watching_only = True
else:
QMessageBox.information(window, _('Error'), _("Ledger device not detected.\nContinuing in watching-only mode."), _('OK'))
self.wallet.force_watching_only = True
class BTChipQTHandler:
def __init__(self, win):
self.win = win
self.win.connect(win, SIGNAL('btchip_done'), self.dialog_stop)
self.win.connect(win, SIGNAL('btchip_message_dialog'), self.message_dialog)
self.win.connect(win, SIGNAL('btchip_auth_dialog'), self.auth_dialog)
self.done = threading.Event()
def stop(self):
self.win.emit(SIGNAL('btchip_done'))
def show_message(self, msg):
self.message = msg
self.win.emit(SIGNAL('btchip_message_dialog'))
def prompt_auth(self, msg):
self.done.clear()
self.message = msg
self.win.emit(SIGNAL('btchip_auth_dialog'))
self.done.wait()
return self.response
def auth_dialog(self):
response = QInputDialog.getText(None, "Ledger Wallet Authentication", self.message, QLineEdit.Password)
if not response[1]:
self.response = None
else:
self.response = str(response[0])
self.done.set()
def message_dialog(self):
self.d = QDialog()
self.d.setModal(1)
self.d.setWindowTitle('Ledger')
self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
l = QLabel(self.message)
vbox = QVBoxLayout(self.d)
vbox.addWidget(l)
self.d.show()
def dialog_stop(self):
if self.d is not None:
self.d.hide()
self.d = None

6
plugins/plot/__init__.py

@ -0,0 +1,6 @@
from electrum.i18n import _
fullname = 'Plot History'
description = _("Ability to plot transaction history in graphical mode.")
requires = [('matplotlib', 'matplotlib')]
available_for = ['qt']

2
plugins/plot.py → plugins/plot/qt.py

@ -17,7 +17,7 @@ except:
flag_matlib=False
class QtPlugin(BasePlugin):
class Plugin(BasePlugin):
def is_available(self):
if flag_matlib:

9
plugins/trezor/__init__.py

@ -0,0 +1,9 @@
from electrum.i18n import _
fullname = 'Trezor Wallet'
description = _('Provides support for Trezor hardware wallet')
requires = [('trezorlib','github.com/trezor/python-trezor')]
requires_wallet_type = ['trezor']
registers_wallet_type = ('hardware', 'trezor', _("Trezor wallet"))
available_for = ['qt', 'cmdline']

200
plugins/trezor/qt.py

@ -0,0 +1,200 @@
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
import PyQt4.QtCore as QtCore
from electrum_gui.qt.util import *
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
from electrum_gui.qt.installwizard import InstallWizard
from trezorlib.qt.pinmatrix import PinMatrixWidget
from functools import partial
import unicodedata
from electrum.i18n import _
from electrum.plugins import hook, always_hook, run_hook
from trezor import TrezorPlugin
class TrezorQtHandler:
def __init__(self, win):
self.win = win
self.win.connect(win, SIGNAL('trezor_done'), self.dialog_stop)
self.win.connect(win, SIGNAL('message_dialog'), self.message_dialog)
self.win.connect(win, SIGNAL('pin_dialog'), self.pin_dialog)
self.win.connect(win, SIGNAL('passphrase_dialog'), self.passphrase_dialog)
self.done = threading.Event()
def stop(self):
self.win.emit(SIGNAL('trezor_done'))
def show_message(self, msg):
self.message = msg
self.win.emit(SIGNAL('message_dialog'))
def get_pin(self, msg):
self.done.clear()
self.message = msg
self.win.emit(SIGNAL('pin_dialog'))
self.done.wait()
return self.response
def get_passphrase(self, msg):
self.done.clear()
self.message = msg
self.win.emit(SIGNAL('passphrase_dialog'))
self.done.wait()
return self.passphrase
def pin_dialog(self):
d = QDialog(None)
d.setModal(1)
d.setWindowTitle(_("Enter PIN"))
d.setWindowFlags(d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
matrix = PinMatrixWidget()
vbox = QVBoxLayout()
vbox.addWidget(QLabel(self.message))
vbox.addWidget(matrix)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
d.setLayout(vbox)
if not d.exec_():
self.response = None
self.response = str(matrix.get_value())
self.done.set()
def passphrase_dialog(self):
if type(self.win) is ElectrumWindow:
passphrase = self.win.password_dialog(_("Please enter your Trezor passphrase"))
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
else:
assert type(self.win) is InstallWizard
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
d = QDialog()
d.setModal(1)
d.setLayout(make_password_dialog(d, None, self.message, False))
confirmed, p, passphrase = run_password_dialog(d, None, None)
if not confirmed:
QMessageBox.critical(None, _('Error'), _("Password request canceled"), _('OK'))
self.passphrase = None
else:
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
self.done.set()
def message_dialog(self):
self.d = QDialog()
self.d.setModal(1)
self.d.setWindowTitle('Please Check Trezor Device')
self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
l = QLabel(self.message)
vbox = QVBoxLayout(self.d)
vbox.addWidget(l)
self.d.show()
def dialog_stop(self):
self.d.hide()
class Plugin(TrezorPlugin):
@hook
def load_wallet(self, wallet, window):
self.print_error("load_wallet")
self.wallet = wallet
self.wallet.plugin = self
self.trezor_button = StatusBarButton(QIcon(":icons/trezor.png"), _("Trezor"), partial(self.settings_dialog, window))
if type(window) is ElectrumWindow:
window.statusBar().addPermanentWidget(self.trezor_button)
if self.handler is None:
self.handler = TrezorQtHandler(window)
try:
self.get_client().ping('t')
except BaseException as e:
QMessageBox.information(window, _('Error'), _("Trezor device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK'))
self.wallet.force_watching_only = True
return
if self.wallet.addresses() and not self.wallet.check_proper_device():
QMessageBox.information(window, _('Error'), _("This wallet does not match your Trezor device"), _('OK'))
self.wallet.force_watching_only = True
@hook
def installwizard_load_wallet(self, wallet, window):
if type(wallet) != TrezorWallet:
return
self.load_wallet(wallet, window)
@hook
def installwizard_restore(self, wizard, storage):
if storage.get('wallet_type') != 'trezor':
return
seed = wizard.enter_seed_dialog("Enter your Trezor seed", None, func=lambda x:True)
if not seed:
return
wallet = TrezorWallet(storage)
self.wallet = wallet
handler = TrezorQtHandler(wizard)
passphrase = handler.get_passphrase(_("Please enter your Trezor passphrase.") + '\n' + _("Press OK if you do not use one."))
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)
# disable trezor plugin
self.set_enabled(False)
return wallet
@hook
def receive_menu(self, menu, addrs):
if not self.wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1:
menu.addAction(_("Show on TREZOR"), lambda: self.show_address(addrs[0]))
def show_address(self, address):
if not self.wallet.check_proper_device():
give_error('Wrong device or password')
try:
address_path = self.wallet.address_id(address)
address_n = self.get_client().expand_path(address_path)
except Exception, e:
give_error(e)
try:
self.get_client().get_address('Bitcoin', address_n, True)
except Exception, e:
give_error(e)
finally:
self.handler.stop()
def settings_dialog(self, window):
try:
device_id = self.get_client().get_device_id()
except BaseException as e:
window.show_message(str(e))
return
get_label = lambda: self.get_client().features.label
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
d = QDialog()
layout = QGridLayout(d)
layout.addWidget(QLabel("Trezor Options"),0,0)
layout.addWidget(QLabel("ID:"),1,0)
layout.addWidget(QLabel(" %s" % device_id),1,1)
def modify_label():
response = QInputDialog().getText(None, "Set New Trezor Label", "New Trezor Label: (upon submission confirm on Trezor)")
if not response[1]:
return
new_label = str(response[0])
self.handler.show_message("Please confirm label change on Trezor")
status = self.get_client().apply_settings(label=new_label)
self.handler.stop()
update_label()
current_label_label = QLabel()
update_label()
change_label_button = QPushButton("Modify")
change_label_button.clicked.connect(modify_label)
layout.addWidget(current_label_label,3,0)
layout.addWidget(change_label_button,3,1)
d.exec_()

195
plugins/trezor.py → plugins/trezor/trezor.py

@ -5,7 +5,6 @@ from time import sleep
import unicodedata
import threading
import re
from functools import partial
import electrum
@ -31,6 +30,9 @@ except ImportError:
import trezorlib.ckd_public as ckd_public
def log(msg):
stderr.write("%s\n" % msg)
stderr.flush()
@ -200,7 +202,7 @@ class TrezorWallet(BIP32_HD_Wallet):
class Plugin(BasePlugin):
class TrezorPlugin(BasePlugin):
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
@ -399,118 +401,6 @@ class Plugin(BasePlugin):
return self.electrum_tx_to_txtype(tx)
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
import PyQt4.QtCore as QtCore
from electrum_gui.qt.util import *
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
from electrum_gui.qt.installwizard import InstallWizard
from trezorlib.qt.pinmatrix import PinMatrixWidget
class QtPlugin(Plugin):
@hook
def load_wallet(self, wallet, window):
self.print_error("load_wallet")
self.wallet = wallet
self.wallet.plugin = self
self.trezor_button = StatusBarButton(QIcon(":icons/trezor.png"), _("Trezor"), partial(self.settings_dialog, window))
if type(window) is ElectrumWindow:
window.statusBar().addPermanentWidget(self.trezor_button)
if self.handler is None:
self.handler = TrezorQtHandler(window)
try:
self.get_client().ping('t')
except BaseException as e:
QMessageBox.information(window, _('Error'), _("Trezor device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK'))
self.wallet.force_watching_only = True
return
if self.wallet.addresses() and not self.wallet.check_proper_device():
QMessageBox.information(window, _('Error'), _("This wallet does not match your Trezor device"), _('OK'))
self.wallet.force_watching_only = True
@hook
def installwizard_load_wallet(self, wallet, window):
if type(wallet) != TrezorWallet:
return
self.load_wallet(wallet, window)
@hook
def installwizard_restore(self, wizard, storage):
if storage.get('wallet_type') != 'trezor':
return
seed = wizard.enter_seed_dialog("Enter your Trezor seed", None, func=lambda x:True)
if not seed:
return
wallet = TrezorWallet(storage)
self.wallet = wallet
handler = TrezorQtHandler(wizard)
passphrase = handler.get_passphrase(_("Please enter your Trezor passphrase.") + '\n' + _("Press OK if you do not use one."))
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)
# disable trezor plugin
self.set_enabled(False)
return wallet
@hook
def receive_menu(self, menu, addrs):
if not self.wallet.is_watching_only() and self.atleast_version(1, 3) and len(addrs) == 1:
menu.addAction(_("Show on TREZOR"), lambda: self.show_address(addrs[0]))
def show_address(self, address):
if not self.wallet.check_proper_device():
give_error('Wrong device or password')
try:
address_path = self.wallet.address_id(address)
address_n = self.get_client().expand_path(address_path)
except Exception, e:
give_error(e)
try:
self.get_client().get_address('Bitcoin', address_n, True)
except Exception, e:
give_error(e)
finally:
self.handler.stop()
def settings_dialog(self, window):
try:
device_id = self.get_client().get_device_id()
except BaseException as e:
window.show_message(str(e))
return
get_label = lambda: self.get_client().features.label
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
d = QDialog()
layout = QGridLayout(d)
layout.addWidget(QLabel("Trezor Options"),0,0)
layout.addWidget(QLabel("ID:"),1,0)
layout.addWidget(QLabel(" %s" % device_id),1,1)
def modify_label():
response = QInputDialog().getText(None, "Set New Trezor Label", "New Trezor Label: (upon submission confirm on Trezor)")
if not response[1]:
return
new_label = str(response[0])
self.handler.show_message("Please confirm label change on Trezor")
status = self.get_client().apply_settings(label=new_label)
self.handler.stop()
update_label()
current_label_label = QLabel()
update_label()
change_label_button = QPushButton("Modify")
change_label_button.clicked.connect(modify_label)
layout.addWidget(current_label_label,3,0)
layout.addWidget(change_label_button,3,1)
d.exec_()
@ -582,83 +472,6 @@ class TrezorCmdLineHandler:
print_msg(msg)
class TrezorQtHandler:
def __init__(self, win):
self.win = win
self.win.connect(win, SIGNAL('trezor_done'), self.dialog_stop)
self.win.connect(win, SIGNAL('message_dialog'), self.message_dialog)
self.win.connect(win, SIGNAL('pin_dialog'), self.pin_dialog)
self.win.connect(win, SIGNAL('passphrase_dialog'), self.passphrase_dialog)
self.done = threading.Event()
def stop(self):
self.win.emit(SIGNAL('trezor_done'))
def show_message(self, msg):
self.message = msg
self.win.emit(SIGNAL('message_dialog'))
def get_pin(self, msg):
self.done.clear()
self.message = msg
self.win.emit(SIGNAL('pin_dialog'))
self.done.wait()
return self.response
def get_passphrase(self, msg):
self.done.clear()
self.message = msg
self.win.emit(SIGNAL('passphrase_dialog'))
self.done.wait()
return self.passphrase
def pin_dialog(self):
d = QDialog(None)
d.setModal(1)
d.setWindowTitle(_("Enter PIN"))
d.setWindowFlags(d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
matrix = PinMatrixWidget()
vbox = QVBoxLayout()
vbox.addWidget(QLabel(self.message))
vbox.addWidget(matrix)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
d.setLayout(vbox)
if not d.exec_():
self.response = None
self.response = str(matrix.get_value())
self.done.set()
def passphrase_dialog(self):
if type(self.win) is ElectrumWindow:
passphrase = self.win.password_dialog(_("Please enter your Trezor passphrase"))
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
else:
assert type(self.win) is InstallWizard
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
d = QDialog()
d.setModal(1)
d.setLayout(make_password_dialog(d, None, self.message, False))
confirmed, p, passphrase = run_password_dialog(d, None, None)
if not confirmed:
QMessageBox.critical(None, _('Error'), _("Password request canceled"), _('OK'))
self.passphrase = None
else:
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
self.done.set()
def message_dialog(self):
self.d = QDialog()
self.d.setModal(1)
self.d.setWindowTitle('Please Check Trezor Device')
self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
l = QLabel(self.message)
vbox = QVBoxLayout(self.d)
vbox.addWidget(l)
self.d.show()
def dialog_stop(self):
self.d.hide()
if TREZOR:

11
plugins/trustedcoin/__init__.py

@ -0,0 +1,11 @@
from electrum.i18n import _
fullname = _('Two Factor Authentication')
description = ''.join([
_("This plugin adds two-factor authentication to your wallet."), '<br/>',
_("For more information, visit"),
" <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>"
])
requires_wallet_type = ['2fa']
registers_wallet_type = ('twofactor', '2fa', _("Wallet with two-factor authentication"))
available_for = ['qt', 'cmdline']

225
plugins/trustedcoin/qt.py

@ -0,0 +1,225 @@
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from electrum_gui.qt.util import *
from electrum_gui.qt.qrcodewidget import QRCodeWidget
from electrum_gui.qt.amountedit import AmountEdit
from electrum_gui.qt.main_window import StatusBarButton
class Plugin(TrustedCoinPlugin):
def auth_dialog(self, window):
d = QDialog(window)
d.setModal(1)
vbox = QVBoxLayout(d)
pw = AmountEdit(None, is_int = True)
msg = _('Please enter your Google Authenticator code')
vbox.addWidget(QLabel(msg))
grid = QGridLayout()
grid.setSpacing(8)
grid.addWidget(QLabel(_('Code')), 1, 0)
grid.addWidget(pw, 1, 1)
vbox.addLayout(grid)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
if not d.exec_():
return
return pw.get_amount()
@hook
def sign_tx(self, window, tx):
self.print_error("twofactor:sign_tx")
wallet = window.wallet
if type(wallet) is Wallet_2fa and not wallet.can_sign_without_server():
auth_code = None
if need_server(wallet, tx):
auth_code = self.auth_dialog(window)
else:
self.print_error("twofactor: xpub3 not needed")
window.wallet.auth_code = auth_code
@hook
def abort_send(self, window):
wallet = window.wallet
if type(wallet) is Wallet_2fa and not wallet.can_sign_without_server():
if wallet.billing_info is None:
# request billing info before forming the transaction
task = partial(self.request_billing_info, wallet)
waiting_dialog = WaitingDialog(window, 'please wait...', task)
waiting_dialog.start()
waiting_dialog.wait()
if wallet.billing_info is None:
window.show_message('Could not contact server')
return True
return False
def settings_dialog(self, window):
task = partial(self.request_billing_info, window.wallet)
self.waiting_dialog = WaitingDialog(window, 'please wait...', task, partial(self.show_settings_dialog, window))
self.waiting_dialog.start()
def show_settings_dialog(self, window, success):
if not success:
window.show_message(_('Server not reachable.'))
return
wallet = window.wallet
d = QDialog(window)
d.setWindowTitle("TrustedCoin Information")
d.setMinimumSize(500, 200)
vbox = QVBoxLayout(d)
hbox = QHBoxLayout()
logo = QLabel()
logo.setPixmap(QPixmap(":icons/trustedcoin.png"))
msg = _('This wallet is protected by TrustedCoin\'s two-factor authentication.') + '<br/>'\
+ _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>"
label = QLabel(msg)
label.setOpenExternalLinks(1)
hbox.addStretch(10)
hbox.addWidget(logo)
hbox.addStretch(10)
hbox.addWidget(label)
hbox.addStretch(10)
vbox.addLayout(hbox)
vbox.addStretch(10)
msg = _('TrustedCoin charges a fee per co-signed transaction. You may pay on each transaction (an extra output will be added to your transaction), or you may purchase prepaid transaction using this dialog.') + '<br/>'
label = QLabel(msg)
label.setWordWrap(1)
vbox.addWidget(label)
vbox.addStretch(10)
grid = QGridLayout()
vbox.addLayout(grid)
price_per_tx = wallet.price_per_tx
v = price_per_tx.get(1)
grid.addWidget(QLabel(_("Price per transaction (not prepaid):")), 0, 0)
grid.addWidget(QLabel(window.format_amount(v) + ' ' + window.base_unit()), 0, 1)
i = 1
if 10 not in price_per_tx:
price_per_tx[10] = 10 * price_per_tx.get(1)
for k, v in sorted(price_per_tx.items()):
if k == 1:
continue
grid.addWidget(QLabel("Price for %d prepaid transactions:"%k), i, 0)
grid.addWidget(QLabel("%d x "%k + window.format_amount(v/k) + ' ' + window.base_unit()), i, 1)
b = QPushButton(_("Buy"))
b.clicked.connect(lambda b, k=k, v=v: self.on_buy(window, k, v, d))
grid.addWidget(b, i, 2)
i += 1
n = wallet.billing_info.get('tx_remaining', 0)
grid.addWidget(QLabel(_("Your wallet has %d prepaid transactions.")%n), i, 0)
# tranfer button
#def on_transfer():
# server.transfer_credit(self.user_id, recipient, otp, signature_callback)
# pass
#b = QPushButton(_("Transfer"))
#b.clicked.connect(on_transfer)
#grid.addWidget(b, 1, 2)
#grid.addWidget(QLabel(_("Next Billing Address:")), i, 0)
#grid.addWidget(QLabel(self.billing_info['billing_address']), i, 1)
vbox.addLayout(Buttons(CloseButton(d)))
d.exec_()
def on_buy(self, window, k, v, d):
d.close()
if window.pluginsdialog:
window.pluginsdialog.close()
wallet = window.wallet
uri = "bitcoin:" + wallet.billing_info['billing_address'] + "?message=TrustedCoin %d Prepaid Transactions&amount="%k + str(Decimal(v)/100000000)
wallet.is_billing = True
window.pay_to_URI(uri)
window.payto_e.setFrozen(True)
window.message_e.setFrozen(True)
window.amount_e.setFrozen(True)
def accept_terms_of_use(self, window):
vbox = QVBoxLayout()
window.set_layout(vbox)
vbox.addWidget(QLabel(_("Terms of Service")))
tos_e = QTextEdit()
tos_e.setReadOnly(True)
vbox.addWidget(tos_e)
vbox.addWidget(QLabel(_("Please enter your e-mail address")))
email_e = QLineEdit()
vbox.addWidget(email_e)
vbox.addStretch()
accept_button = OkButton(window, _('Accept'))
accept_button.setEnabled(False)
vbox.addLayout(Buttons(CancelButton(window), accept_button))
def request_TOS():
tos = server.get_terms_of_service()
self.TOS = tos
window.emit(SIGNAL('twofactor:TOS'))
def on_result():
tos_e.setText(self.TOS)
window.connect(window, SIGNAL('twofactor:TOS'), on_result)
t = Thread(target=request_TOS)
t.setDaemon(True)
t.start()
regexp = r"[^@]+@[^@]+\.[^@]+"
email_e.textChanged.connect(lambda: accept_button.setEnabled(re.match(regexp,email_e.text()) is not None))
email_e.setFocus(True)
if not window.exec_():
return
email = str(email_e.text())
return email
def setup_google_auth(self, window, _id, otp_secret):
vbox = QVBoxLayout()
window.set_layout(vbox)
if otp_secret is not None:
uri = "otpauth://totp/%s?secret=%s"%('trustedcoin.com', otp_secret)
vbox.addWidget(QLabel("Please scan this QR code in Google Authenticator."))
qrw = QRCodeWidget(uri)
vbox.addWidget(qrw, 1)
msg = _('Then, enter your Google Authenticator code:')
else:
label = QLabel("This wallet is already registered, but it was never authenticated. To finalize your registration, please enter your Google Authenticator Code. If you do not have this code, delete the wallet file and start a new registration")
label.setWordWrap(1)
vbox.addWidget(label)
msg = _('Google Authenticator code:')
hbox = QHBoxLayout()
hbox.addWidget(QLabel(msg))
pw = AmountEdit(None, is_int = True)
pw.setFocus(True)
hbox.addWidget(pw)
hbox.addStretch(1)
vbox.addLayout(hbox)
b = OkButton(window, _('Next'))
b.setEnabled(False)
vbox.addLayout(Buttons(CancelButton(window), b))
pw.textChanged.connect(lambda: b.setEnabled(len(pw.text())==6))
while True:
if not window.exec_():
return False
otp = pw.get_amount()
try:
server.auth(_id, otp)
return True
except:
QMessageBox.information(window, _('Message'), _('Incorrect password'), _('OK'))
pw.setText('')

228
plugins/trustedcoin.py → plugins/trustedcoin/trustedcoin.py

@ -283,7 +283,8 @@ def need_server(wallet, tx):
return True
return False
class Plugin(BasePlugin):
class TrustedCoinPlugin(BasePlugin):
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
@ -454,228 +455,3 @@ class Plugin(BasePlugin):
return True
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from electrum_gui.qt.util import *
from electrum_gui.qt.qrcodewidget import QRCodeWidget
from electrum_gui.qt.amountedit import AmountEdit
from electrum_gui.qt.main_window import StatusBarButton
class QtPlugin(Plugin):
def auth_dialog(self, window):
d = QDialog(window)
d.setModal(1)
vbox = QVBoxLayout(d)
pw = AmountEdit(None, is_int = True)
msg = _('Please enter your Google Authenticator code')
vbox.addWidget(QLabel(msg))
grid = QGridLayout()
grid.setSpacing(8)
grid.addWidget(QLabel(_('Code')), 1, 0)
grid.addWidget(pw, 1, 1)
vbox.addLayout(grid)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
if not d.exec_():
return
return pw.get_amount()
@hook
def sign_tx(self, window, tx):
self.print_error("twofactor:sign_tx")
wallet = window.wallet
if type(wallet) is Wallet_2fa and not wallet.can_sign_without_server():
auth_code = None
if need_server(wallet, tx):
auth_code = self.auth_dialog(window)
else:
self.print_error("twofactor: xpub3 not needed")
window.wallet.auth_code = auth_code
@hook
def abort_send(self, window):
wallet = window.wallet
if type(wallet) is Wallet_2fa and not wallet.can_sign_without_server():
if wallet.billing_info is None:
# request billing info before forming the transaction
task = partial(self.request_billing_info, wallet)
waiting_dialog = WaitingDialog(window, 'please wait...', task)
waiting_dialog.start()
waiting_dialog.wait()
if wallet.billing_info is None:
window.show_message('Could not contact server')
return True
return False
def settings_dialog(self, window):
task = partial(self.request_billing_info, window.wallet)
self.waiting_dialog = WaitingDialog(window, 'please wait...', task, partial(self.show_settings_dialog, window))
self.waiting_dialog.start()
def show_settings_dialog(self, window, success):
if not success:
window.show_message(_('Server not reachable.'))
return
wallet = window.wallet
d = QDialog(window)
d.setWindowTitle("TrustedCoin Information")
d.setMinimumSize(500, 200)
vbox = QVBoxLayout(d)
hbox = QHBoxLayout()
logo = QLabel()
logo.setPixmap(QPixmap(":icons/trustedcoin.png"))
msg = _('This wallet is protected by TrustedCoin\'s two-factor authentication.') + '<br/>'\
+ _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>"
label = QLabel(msg)
label.setOpenExternalLinks(1)
hbox.addStretch(10)
hbox.addWidget(logo)
hbox.addStretch(10)
hbox.addWidget(label)
hbox.addStretch(10)
vbox.addLayout(hbox)
vbox.addStretch(10)
msg = _('TrustedCoin charges a fee per co-signed transaction. You may pay on each transaction (an extra output will be added to your transaction), or you may purchase prepaid transaction using this dialog.') + '<br/>'
label = QLabel(msg)
label.setWordWrap(1)
vbox.addWidget(label)
vbox.addStretch(10)
grid = QGridLayout()
vbox.addLayout(grid)
price_per_tx = wallet.price_per_tx
v = price_per_tx.get(1)
grid.addWidget(QLabel(_("Price per transaction (not prepaid):")), 0, 0)
grid.addWidget(QLabel(window.format_amount(v) + ' ' + window.base_unit()), 0, 1)
i = 1
if 10 not in price_per_tx:
price_per_tx[10] = 10 * price_per_tx.get(1)
for k, v in sorted(price_per_tx.items()):
if k == 1:
continue
grid.addWidget(QLabel("Price for %d prepaid transactions:"%k), i, 0)
grid.addWidget(QLabel("%d x "%k + window.format_amount(v/k) + ' ' + window.base_unit()), i, 1)
b = QPushButton(_("Buy"))
b.clicked.connect(lambda b, k=k, v=v: self.on_buy(window, k, v, d))
grid.addWidget(b, i, 2)
i += 1
n = wallet.billing_info.get('tx_remaining', 0)
grid.addWidget(QLabel(_("Your wallet has %d prepaid transactions.")%n), i, 0)
# tranfer button
#def on_transfer():
# server.transfer_credit(self.user_id, recipient, otp, signature_callback)
# pass
#b = QPushButton(_("Transfer"))
#b.clicked.connect(on_transfer)
#grid.addWidget(b, 1, 2)
#grid.addWidget(QLabel(_("Next Billing Address:")), i, 0)
#grid.addWidget(QLabel(self.billing_info['billing_address']), i, 1)
vbox.addLayout(Buttons(CloseButton(d)))
d.exec_()
def on_buy(self, window, k, v, d):
d.close()
if window.pluginsdialog:
window.pluginsdialog.close()
wallet = window.wallet
uri = "bitcoin:" + wallet.billing_info['billing_address'] + "?message=TrustedCoin %d Prepaid Transactions&amount="%k + str(Decimal(v)/100000000)
wallet.is_billing = True
window.pay_to_URI(uri)
window.payto_e.setFrozen(True)
window.message_e.setFrozen(True)
window.amount_e.setFrozen(True)
def accept_terms_of_use(self, window):
vbox = QVBoxLayout()
window.set_layout(vbox)
vbox.addWidget(QLabel(_("Terms of Service")))
tos_e = QTextEdit()
tos_e.setReadOnly(True)
vbox.addWidget(tos_e)
vbox.addWidget(QLabel(_("Please enter your e-mail address")))
email_e = QLineEdit()
vbox.addWidget(email_e)
vbox.addStretch()
accept_button = OkButton(window, _('Accept'))
accept_button.setEnabled(False)
vbox.addLayout(Buttons(CancelButton(window), accept_button))
def request_TOS():
tos = server.get_terms_of_service()
self.TOS = tos
window.emit(SIGNAL('twofactor:TOS'))
def on_result():
tos_e.setText(self.TOS)
window.connect(window, SIGNAL('twofactor:TOS'), on_result)
t = Thread(target=request_TOS)
t.setDaemon(True)
t.start()
regexp = r"[^@]+@[^@]+\.[^@]+"
email_e.textChanged.connect(lambda: accept_button.setEnabled(re.match(regexp,email_e.text()) is not None))
email_e.setFocus(True)
if not window.exec_():
return
email = str(email_e.text())
return email
def setup_google_auth(self, window, _id, otp_secret):
vbox = QVBoxLayout()
window.set_layout(vbox)
if otp_secret is not None:
uri = "otpauth://totp/%s?secret=%s"%('trustedcoin.com', otp_secret)
vbox.addWidget(QLabel("Please scan this QR code in Google Authenticator."))
qrw = QRCodeWidget(uri)
vbox.addWidget(qrw, 1)
msg = _('Then, enter your Google Authenticator code:')
else:
label = QLabel("This wallet is already registered, but it was never authenticated. To finalize your registration, please enter your Google Authenticator Code. If you do not have this code, delete the wallet file and start a new registration")
label.setWordWrap(1)
vbox.addWidget(label)
msg = _('Google Authenticator code:')
hbox = QHBoxLayout()
hbox.addWidget(QLabel(msg))
pw = AmountEdit(None, is_int = True)
pw.setFocus(True)
hbox.addWidget(pw)
hbox.addStretch(1)
vbox.addLayout(hbox)
b = OkButton(window, _('Next'))
b.setEnabled(False)
vbox.addLayout(Buttons(CancelButton(window), b))
pw.textChanged.connect(lambda: b.setEnabled(len(pw.text())==6))
while True:
if not window.exec_():
return False
otp = pw.get_amount()
try:
server.auth(_id, otp)
return True
except:
QMessageBox.information(window, _('Message'), _('Incorrect password'), _('OK'))
pw.setText('')

5
plugins/virtualkeyboard/__init__.py

@ -0,0 +1,5 @@
from electrum.i18n import _
fullname = 'Virtual Keyboard'
description = '%s\n%s' % (_("Add an optional virtual keyboard to the password dialog."), _("Warning: do not use this if it makes you pick a weaker password."))
available_for = ['qt']

2
plugins/virtualkeyboard.py → plugins/virtualkeyboard/qt.py

@ -3,7 +3,7 @@ from electrum.plugins import BasePlugin, hook
from electrum.i18n import _
import random
class QtPlugin(BasePlugin):
class Plugin(BasePlugin):
vkb = None
vkb_index = 0

18
setup.py

@ -34,12 +34,28 @@ setup(
'protobuf',
'dnspython',
],
packages=[
'electrum',
'electrum_gui',
'electrum_gui.qt',
'electrum_plugins.audio_modem',
'electrum_plugins.cosigner_pool',
'electrum_plugins.email_requests',
'electrum_plugins.exchange_rate',
'electrum_plugins.greenaddress_instant',
'electrum_plugins.keepkey',
'electrum_plugins.labels',
'electrum_plugins.ledger',
'electrum_plugins.plot',
'electrum_plugins.trezor',
'electrum_plugins.trustedcoin',
'electrum_plugins.virtualkeyboard',
],
package_dir={
'electrum': 'lib',
'electrum_gui': 'gui',
'electrum_plugins': 'plugins',
},
packages=['electrum','electrum_gui','electrum_gui.qt','electrum_plugins'],
package_data={
'electrum': [
'www/index.html',

Loading…
Cancel
Save