diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index 97cc69cdf..973f70bca 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -95,9 +95,12 @@ class NodesListWidget(QTreeWidget): DISCONNECTED_SERVER = 2 TOPLEVEL = 3 - def __init__(self, parent): + followServer = pyqtSignal([object], arguments=['server']) + followChain = pyqtSignal([str], arguments=['chain_id']) + setServer = pyqtSignal([str], arguments=['server']) + + def __init__(self): QTreeWidget.__init__(self) - self.parent = parent # type: NetworkChoiceLayout self.setHeaderLabels([_('Server'), _('Height')]) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.create_menu) @@ -110,16 +113,19 @@ class NodesListWidget(QTreeWidget): menu = QMenu() if item_type == self.ItemType.CONNECTED_SERVER: server = item.data(0, self.SERVER_ADDR_ROLE) # type: ServerAddr - menu.addAction(_("Use as server"), lambda: self.parent.follow_server(server)) + def do_follow_server(): + self.followServer.emit(server) + menu.addAction(_("Use as server"), do_follow_server) elif item_type == self.ItemType.DISCONNECTED_SERVER: server = item.data(0, self.SERVER_ADDR_ROLE) # type: ServerAddr - def func(): - self.parent.server_e.setText(str(server)) - self.parent.set_server() - menu.addAction(_("Use as server"), func) + def do_set_server(): + self.setServer.emit(str(server)) + menu.addAction(_("Use as server"), do_set_server) elif item_type == self.ItemType.CHAIN: chain_id = item.data(0, self.CHAIN_ID_ROLE) - menu.addAction(_("Follow this branch"), lambda: self.parent.follow_branch(chain_id)) + def do_follow_chain(): + self.followChain.emit(chain_id) + menu.addAction(_("Follow this branch"), do_follow_chain) else: return menu.exec_(self.viewport().mapToGlobal(position)) @@ -136,9 +142,11 @@ class NodesListWidget(QTreeWidget): pt.setX(50) self.customContextMenuRequested.emit(pt) - def update(self, *, network: Network, servers: dict, use_tor: bool): + def update(self, *, network: Network, servers: dict): self.clear() + use_tor = network.tor_proxy + # connected servers connected_servers_item = QTreeWidgetItem([_("Connected nodes"), '']) connected_servers_item.setData(0, self.ITEMTYPE_ROLE, self.ItemType.TOPLEVEL) @@ -146,7 +154,8 @@ class NodesListWidget(QTreeWidget): n_chains = len(chains) for chain_id, interfaces in chains.items(): b = blockchain.blockchains.get(chain_id) - if b is None: continue + if b is None: + continue name = b.get_name() if n_chains > 1: x = QTreeWidgetItem([name + '@%d'%b.get_max_forkpoint(), '%d'%b.height()]) @@ -201,7 +210,9 @@ class NodesListWidget(QTreeWidget): class NetworkChoiceLayout(object): - + # TODO consolidate to ProxyWidget+ServerWidget + # TODO TorDetector is unnecessary, Network tests socks5 peer and detects Tor + # TODO apply on editingFinished is not ideal, separate Apply button and on Close? def __init__(self, network: Network, config: 'SimpleConfig', wizard=False): self.network = network self.config = config @@ -304,7 +315,14 @@ class NetworkChoiceLayout(object): self.split_label = QLabel('') grid.addWidget(self.split_label, 4, 0, 1, 3) - self.nodes_list_widget = NodesListWidget(self) + self.nodes_list_widget = NodesListWidget() + self.nodes_list_widget.followServer.connect(self.follow_server) + self.nodes_list_widget.followChain.connect(self.follow_branch) + + def do_set_server(server): + self.server_e.setText(server) + self.set_server() + self.nodes_list_widget.setServer.connect(do_set_server) grid.addWidget(self.nodes_list_widget, 6, 0, 1, 5) vbox = QVBoxLayout() @@ -361,8 +379,7 @@ class NetworkChoiceLayout(object): msg = '' self.split_label.setText(msg) self.nodes_list_widget.update(network=self.network, - servers=self.network.get_servers(), - use_tor=self.tor_cb.isChecked()) + servers=self.network.get_servers()) self.enable_set_server() def fill_in_proxy_settings(self): @@ -499,13 +516,11 @@ class ProxyWidget(QWidget): grid = QGridLayout(self) grid.setSpacing(8) - # proxy setting + # proxy setting. self.proxy_cb = QCheckBox(_('Use proxy')) - # self.proxy_cb.clicked.connect(self.check_disable_proxy) - # self.proxy_cb.clicked.connect(self.set_proxy) - self.proxy_mode = QComboBox() self.proxy_mode.addItems(['SOCKS4', 'SOCKS5']) + self.proxy_mode.setCurrentIndex(1) self.proxy_host = QLineEdit() self.proxy_host.setFixedWidth(fixed_width_hostname) self.proxy_port = QLineEdit() @@ -516,43 +531,37 @@ class ProxyWidget(QWidget): self.proxy_password.setPlaceholderText(_("Password")) self.proxy_password.setFixedWidth(fixed_width_port) - # self.proxy_mode.currentIndexChanged.connect(self.set_proxy) - # self.proxy_host.editingFinished.connect(self.set_proxy) - # self.proxy_port.editingFinished.connect(self.set_proxy) - # self.proxy_user.editingFinished.connect(self.set_proxy) - # self.proxy_password.editingFinished.connect(self.set_proxy) - - # self.proxy_mode.currentIndexChanged.connect(self.proxy_settings_changed) - # self.proxy_host.textEdited.connect(self.proxy_settings_changed) - # self.proxy_port.textEdited.connect(self.proxy_settings_changed) - # self.proxy_user.textEdited.connect(self.proxy_settings_changed) - # self.proxy_password.textEdited.connect(self.proxy_settings_changed) - - self.tor_cb = QCheckBox(_("Use Tor Proxy")) - self.tor_cb.setIcon(read_QIcon("tor_logo.png")) - self.tor_cb.hide() - # self.tor_cb.clicked.connect(self.use_tor_proxy) - - grid.addWidget(self.tor_cb, 1, 0, 1, 3) - grid.addWidget(self.proxy_cb, 2, 0, 1, 3) - grid.addWidget(HelpButton(_('Proxy settings apply to all connections: with Electrum servers, but also with third-party services.')), 2, 4) - grid.addWidget(self.proxy_mode, 4, 1) - grid.addWidget(self.proxy_host, 4, 2) - grid.addWidget(self.proxy_port, 4, 3) - grid.addWidget(self.proxy_user, 5, 2) - grid.addWidget(self.proxy_password, 5, 3) - grid.setRowStretch(7, 1) - - -class ServerWidget(QWidget): - def __init__(self, parent=None): + grid.addWidget(self.proxy_cb, 0, 0, 1, 3) + grid.addWidget(HelpButton(_('Proxy settings apply to all connections: with Electrum servers, but also with third-party services.')), 0, 4) + grid.addWidget(self.proxy_mode, 1, 1) + grid.addWidget(self.proxy_host, 1, 2) + grid.addWidget(self.proxy_port, 1, 3) + grid.addWidget(self.proxy_user, 2, 2) + grid.addWidget(self.proxy_password, 2, 3) + + def get_proxy_settings(self): + return { + 'enabled': self.proxy_cb.isChecked(), + 'mode': ['socks4', 'socks5'][self.proxy_mode.currentIndex()], + 'host': self.proxy_host.text(), + 'port': self.proxy_port.text(), + 'user': self.proxy_user.text(), + 'password': self.proxy_password.text() + } + + +class ServerWidget(QWidget, QtEventListener): + def __init__(self, network, parent=None): super().__init__(parent) + self.network = network + self.config = network.config fixed_width_hostname = 24 * char_width_in_lineedit() fixed_width_port = 6 * char_width_in_lineedit() + self.setLayout(QVBoxLayout()) + grid = QGridLayout(self) - # self.setLayout(grid) msg = ' '.join([ _("Electrum connects to several nodes in order to download block headers and find out the longest blockchain."), @@ -564,9 +573,7 @@ class ServerWidget(QWidget): grid.addWidget(HelpButton(msg), 0, 4) self.autoconnect_cb = QCheckBox(_('Select server automatically')) - # self.autoconnect_cb.setEnabled(self.config.cv.NETWORK_AUTO_CONNECT.is_modifiable()) - # self.autoconnect_cb.clicked.connect(self.set_server) - # self.autoconnect_cb.clicked.connect(self.update) + self.autoconnect_cb.setEnabled(self.config.cv.NETWORK_AUTO_CONNECT.is_modifiable()) msg = ' '.join([ _("If auto-connect is enabled, Electrum will always use a server that is on the longest blockchain."), _("If it is disabled, you have to choose a server you want to use. Electrum will warn you if your server is lagging.") @@ -576,7 +583,6 @@ class ServerWidget(QWidget): self.server_e = QLineEdit() self.server_e.setFixedWidth(fixed_width_hostname + fixed_width_port) - # self.server_e.editingFinished.connect(self.set_server) msg = _("Electrum sends your wallet addresses to a single server, in order to receive your transaction history.") grid.addWidget(QLabel(_('Server') + ':'), 2, 0) grid.addWidget(self.server_e, 2, 1, 1, 3) @@ -591,16 +597,46 @@ class ServerWidget(QWidget): self.split_label = QLabel('') grid.addWidget(self.split_label, 4, 0, 1, 3) - self.nodes_list_widget = NodesListWidget(self) - grid.addWidget(self.nodes_list_widget, 6, 0, 1, 5) + self.layout().addLayout(grid) + + self.nodes_list_widget = NodesListWidget() + self.nodes_list_widget.followServer.connect(self.follow_server) + self.nodes_list_widget.followChain.connect(self.follow_branch) + + def do_set_server(server): + self.server_e.setText(server) + self.set_server() + self.nodes_list_widget.setServer.connect(do_set_server) + + + self.layout().addWidget(self.nodes_list_widget) + self.nodes_list_widget.update(network=self.network, + servers=self.network.get_servers()) + + self.register_callbacks() + self.destroyed.connect(lambda: self.unregister_callbacks()) + + @qt_event_listener + def on_event_network_updated(self): + self.nodes_list_widget.update(network=self.network, servers=self.network.get_servers()) + + def follow_branch(self, chain_id): + self.network.run_from_another_thread(self.network.follow_chain_given_id(chain_id)) + self.update() - # vbox = QVBoxLayout() - # vbox.addWidget(tabs) - # self.layout_ = vbox - # # tor detector - # self.td = td = TorDetector() - # td.found_proxy.connect(self.suggest_proxy) - # td.start() + def follow_server(self, server: ServerAddr): + self.server_e.setText(str(server)) + self.network.run_from_another_thread(self.network.follow_chain_given_server(server)) + self.update() - # self.fill_in_proxy_settings() - # self.update() + def set_server(self): + net_params = self.network.get_parameters() + try: + server = ServerAddr.from_str_with_inference(str(self.server_e.text())) + if not server: + raise Exception("failed to parse server") + except Exception: + return + net_params = net_params._replace(server=server, + auto_connect=self.autoconnect_cb.isChecked()) + self.network.run_from_another_thread(self.network.set_parameters(net_params)) diff --git a/electrum/gui/qt/wizard/server_connect.py b/electrum/gui/qt/wizard/server_connect.py index e347bb122..eaf9df24a 100644 --- a/electrum/gui/qt/wizard/server_connect.py +++ b/electrum/gui/qt/wizard/server_connect.py @@ -46,16 +46,7 @@ class WCAutoConnect(WizardComponent): self._valid = True def apply(self): - r = self.choice_w.selected_index - self.wizard_data['autoconnect'] = (r == 0) - # if r == 1: - # nlayout = NetworkChoiceLayout(network, self.config, wizard=True) - # if self.exec_layout(nlayout.layout()): - # nlayout.accept() - # self.config.NETWORK_AUTO_CONNECT = network.auto_connect - # else: - # network.auto_connect = True - # self.config.NETWORK_AUTO_CONNECT = True + self.wizard_data['autoconnect'] = (self.choice_w.selected_item[0] == 'autoconnect') class WCProxyAsk(WizardComponent): @@ -70,29 +61,31 @@ class WCProxyAsk(WizardComponent): self._valid = True def apply(self): - r = self.choice_w.selected_index - self.wizard_data['want_proxy'] = (r == 0) + self.wizard_data['want_proxy'] = (self.choice_w.selected_item[0] == 'yes') class WCProxyConfig(WizardComponent): def __init__(self, parent, wizard): WizardComponent.__init__(self, parent, wizard, title=_("Proxy")) - pw = ProxyWidget(self) - self.layout().addWidget(pw) + self.pw = ProxyWidget(self) + self.pw.proxy_cb.setChecked(True) + self.pw.proxy_host.setText('localhost') + self.pw.proxy_port.setText('9050') + self.layout().addWidget(self.pw) self.layout().addStretch(1) + self._valid = True def apply(self): - # TODO - pass + self.wizard_data['proxy'] = self.pw.get_proxy_settings() class WCServerConfig(WizardComponent): def __init__(self, parent, wizard): WizardComponent.__init__(self, parent, wizard, title=_("Server")) - sw = ServerWidget(self) - self.layout().addWidget(sw) - self.layout().addStretch(1) + self.sw = ServerWidget(wizard._daemon.network, self) + self.layout().addWidget(self.sw) + self._valid = True def apply(self): - # TODO - pass + self.wizard_data['autoconnect'] = self.sw.autoconnect_cb.isChecked() + self.wizard_data['server'] = self.sw.server_e.text() diff --git a/electrum/wizard.py b/electrum/wizard.py index 2f3e4ebb8..c587a488b 100644 --- a/electrum/wizard.py +++ b/electrum/wizard.py @@ -4,6 +4,7 @@ import os from typing import List, NamedTuple, Any, Dict, Optional, Tuple from electrum.i18n import _ +from electrum.interface import ServerAddr from electrum.keystore import hardware_keystore from electrum.logging import get_logger from electrum.plugin import run_hook @@ -647,25 +648,57 @@ class ServerConnectWizard(AbstractWizard): _logger = get_logger(__name__) - def __init__(self, daemon): + def __init__(self, daemon: 'Daemon'): AbstractWizard.__init__(self) self.navmap = { 'autoconnect': { 'next': 'server_config', + 'accept': self.do_configure_autoconnect, 'last': lambda d: d['autoconnect'] }, 'proxy_ask': { 'next': lambda d: 'proxy_config' if d['want_proxy'] else 'autoconnect' }, 'proxy_config': { - 'next': 'autoconnect' + 'next': 'autoconnect', + 'accept': self.do_configure_proxy }, 'server_config': { + 'accept': self.do_configure_server, 'last': True } } self._daemon = daemon + def do_configure_proxy(self, wizard_data): + proxy_settings = wizard_data['proxy'] + if not self._daemon.network: + self._logger.debug('not configuring proxy, electrum config wants offline mode') + return + self._logger.debug(f'configuring proxy: {proxy_settings!r}') + net_params = self._daemon.network.get_parameters() + if not proxy_settings['enabled']: + proxy_settings = None + net_params = net_params._replace(proxy=proxy_settings) + self._daemon.network.run_from_another_thread(self._daemon.network.set_parameters(net_params)) + + def do_configure_server(self, wizard_data): + self._logger.debug(f'configuring server: {wizard_data!r}') + net_params = self._daemon.network.get_parameters() + try: + server = ServerAddr.from_str_with_inference(wizard_data['server']) + if not server: + raise Exception('failed to parse server %s' % wizard_data['server']) + except Exception: + return + net_params = net_params._replace(server=server, auto_connect=wizard_data['autoconnect']) + self._daemon.network.run_from_another_thread(self._daemon.network.set_parameters(net_params)) + + def do_configure_autoconnect(self, wizard_data): + self._logger.debug(f'configuring autoconnect: {wizard_data!r}') + if self._daemon.config.cv.NETWORK_AUTO_CONNECT.is_modifiable(): + self._daemon.config.NETWORK_AUTO_CONNECT = wizard_data['autoconnect'] + def start(self, initial_data=None): if initial_data is None: initial_data = {}