diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index e5b1f7215..e83dda35e 100644 --- a/gui/qt/main_window.py +++ b/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 diff --git a/lib/plugins.py b/lib/plugins.py index a8077e563..90ba13d87 100644 --- a/lib/plugins.py +++ b/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 diff --git a/plugins/__init__.py b/plugins/__init__.py index 9944de42e..1f7662cb2 100644 --- a/plugins/__init__.py +++ b/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 . -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."), '
', - _("For more information, visit"), - " https://api.trustedcoin.com/#/electrum-help" - ]), - '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'], - } -] diff --git a/plugins/audio_modem/__init__.py b/plugins/audio_modem/__init__.py new file mode 100644 index 000000000..46c2d2091 --- /dev/null +++ b/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'] + diff --git a/plugins/audio_modem.py b/plugins/audio_modem/qt.py similarity index 99% rename from plugins/audio_modem.py rename to plugins/audio_modem/qt.py index 27904705d..e797f12c8 100644 --- a/plugins/audio_modem.py +++ b/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) diff --git a/plugins/cosigner_pool/__init__.py b/plugins/cosigner_pool/__init__.py new file mode 100644 index 000000000..48863f9b3 --- /dev/null +++ b/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'] diff --git a/plugins/cosigner_pool.py b/plugins/cosigner_pool/qt.py similarity index 99% rename from plugins/cosigner_pool.py rename to plugins/cosigner_pool/qt.py index b2e9a651f..5f00098be 100644 --- a/plugins/cosigner_pool.py +++ b/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) diff --git a/plugins/email_requests/__init__.py b/plugins/email_requests/__init__.py new file mode 100644 index 000000000..8c9e82472 --- /dev/null +++ b/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'] diff --git a/plugins/email_requests.py b/plugins/email_requests/qt.py similarity index 99% rename from plugins/email_requests.py rename to plugins/email_requests/qt.py index d9740937e..f76099e4b 100644 --- a/plugins/email_requests.py +++ b/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' diff --git a/plugins/exchange_rate/__init__.py b/plugins/exchange_rate/__init__.py new file mode 100644 index 000000000..7e41e72d3 --- /dev/null +++ b/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'] diff --git a/plugins/exchange_rate.py b/plugins/exchange_rate/exchange_rate.py similarity index 64% rename from plugins/exchange_rate.py rename to plugins/exchange_rate/exchange_rate.py index 559044b82..e43b0c7a4 100644 --- a/plugins/exchange_rate.py +++ b/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_() diff --git a/plugins/exchange_rate/kivy.py b/plugins/exchange_rate/kivy.py new file mode 100644 index 000000000..f31464ff6 --- /dev/null +++ b/plugins/exchange_rate/kivy.py @@ -0,0 +1,3 @@ +from exchange_rate import FxPlugin +class Plugin(FxPlugin): + pass diff --git a/plugins/exchange_rate/qt.py b/plugins/exchange_rate/qt.py new file mode 100644 index 000000000..917f523ff --- /dev/null +++ b/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_() diff --git a/plugins/greenaddress_instant/__init__.py b/plugins/greenaddress_instant/__init__.py new file mode 100644 index 000000000..d2b317854 --- /dev/null +++ b/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'] diff --git a/plugins/greenaddress_instant.py b/plugins/greenaddress_instant/qt.py similarity index 99% rename from plugins/greenaddress_instant.py rename to plugins/greenaddress_instant/qt.py index f68748847..0dddf23e9 100644 --- a/plugins/greenaddress_instant.py +++ b/plugins/greenaddress_instant/qt.py @@ -28,7 +28,7 @@ from electrum.i18n import _ -class QtPlugin(BasePlugin): +class Plugin(BasePlugin): button_label = _("Verify GA instant") diff --git a/plugins/keepkey/__init__.py b/plugins/keepkey/__init__.py new file mode 100644 index 000000000..a0d14d5e3 --- /dev/null +++ b/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'] diff --git a/plugins/keepkey.py b/plugins/keepkey/keepkey.py similarity index 83% rename from plugins/keepkey.py rename to plugins/keepkey/keepkey.py index 6376c1629..8100cb755 100644 --- a/plugins/keepkey.py +++ b/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): diff --git a/plugins/keepkey/qt.py b/plugins/keepkey/qt.py new file mode 100644 index 000000000..0d9ba2ff0 --- /dev/null +++ b/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_() + diff --git a/plugins/labels/__init__.py b/plugins/labels/__init__.py new file mode 100644 index 000000000..05b9ecdd2 --- /dev/null +++ b/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'] + diff --git a/plugins/labels/kivy.py b/plugins/labels/kivy.py new file mode 100644 index 000000000..d548bda7d --- /dev/null +++ b/plugins/labels/kivy.py @@ -0,0 +1,3 @@ +from labels import LabelsPlugin +class Plugin(LabelsPlugin): + pass diff --git a/plugins/labels.py b/plugins/labels/labels.py similarity index 64% rename from plugins/labels.py rename to plugins/labels/labels.py index d691592ba..1619b7381 100644 --- a/plugins/labels.py +++ b/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) - diff --git a/plugins/labels/qt.py b/plugins/labels/qt.py new file mode 100644 index 000000000..582468144 --- /dev/null +++ b/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) + diff --git a/plugins/ledger/__init__.py b/plugins/ledger/__init__.py new file mode 100644 index 000000000..072e4d074 --- /dev/null +++ b/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'] diff --git a/plugins/btchipwallet.py b/plugins/ledger/ledger.py similarity index 89% rename from plugins/btchipwallet.py rename to plugins/ledger/ledger.py index d7c77f5bd..a9b5644a1 100644 --- a/plugins/btchipwallet.py +++ b/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 diff --git a/plugins/ledger/qt.py b/plugins/ledger/qt.py new file mode 100644 index 000000000..f2cadc878 --- /dev/null +++ b/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 diff --git a/plugins/plot/__init__.py b/plugins/plot/__init__.py new file mode 100644 index 000000000..c621a86cc --- /dev/null +++ b/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'] diff --git a/plugins/plot.py b/plugins/plot/qt.py similarity index 99% rename from plugins/plot.py rename to plugins/plot/qt.py index 0ff8d98a8..6aae31f03 100644 --- a/plugins/plot.py +++ b/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: diff --git a/plugins/trezor/__init__.py b/plugins/trezor/__init__.py new file mode 100644 index 000000000..f9c00e66e --- /dev/null +++ b/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'] + diff --git a/plugins/trezor/qt.py b/plugins/trezor/qt.py new file mode 100644 index 000000000..00eef5300 --- /dev/null +++ b/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_() + + + + diff --git a/plugins/trezor.py b/plugins/trezor/trezor.py similarity index 69% rename from plugins/trezor.py rename to plugins/trezor/trezor.py index 3f75ccb7e..d7898e5b4 100644 --- a/plugins/trezor.py +++ b/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: diff --git a/plugins/trustedcoin/__init__.py b/plugins/trustedcoin/__init__.py new file mode 100644 index 000000000..19e36d960 --- /dev/null +++ b/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."), '
', + _("For more information, visit"), + " https://api.trustedcoin.com/#/electrum-help" +]) +requires_wallet_type = ['2fa'] +registers_wallet_type = ('twofactor', '2fa', _("Wallet with two-factor authentication")) +available_for = ['qt', 'cmdline'] diff --git a/plugins/trustedcoin/qt.py b/plugins/trustedcoin/qt.py new file mode 100644 index 000000000..c716f80e6 --- /dev/null +++ b/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.') + '
'\ + + _("For more information, visit") + " https://api.trustedcoin.com/#/electrum-help" + 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.') + '
' + 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('') + + diff --git a/plugins/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py similarity index 66% rename from plugins/trustedcoin.py rename to plugins/trustedcoin/trustedcoin.py index 3e33f1054..9451c1811 100644 --- a/plugins/trustedcoin.py +++ b/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.') + '
'\ - + _("For more information, visit") + " https://api.trustedcoin.com/#/electrum-help" - 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.') + '
' - 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('') - - diff --git a/plugins/virtualkeyboard/__init__.py b/plugins/virtualkeyboard/__init__.py new file mode 100644 index 000000000..c24f19b1a --- /dev/null +++ b/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'] diff --git a/plugins/virtualkeyboard.py b/plugins/virtualkeyboard/qt.py similarity index 98% rename from plugins/virtualkeyboard.py rename to plugins/virtualkeyboard/qt.py index cd75faf07..4ad2cc326 100644 --- a/plugins/virtualkeyboard.py +++ b/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 diff --git a/setup.py b/setup.py index 7e0021732..4e8204a9a 100644 --- a/setup.py +++ b/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',