diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index e5fda06e6..1b2a6055f 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -303,7 +303,7 @@ class ElectrumGui(Logger): return window def _start_wizard_to_select_or_create_wallet(self, path) -> Optional[Abstract_Wallet]: - wizard = InstallWizard(self.config, self.app, self.plugins) + wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self) try: path, storage = wizard.select_storage(path, self.daemon.get_wallet) # storage is None if file does not exist @@ -342,7 +342,7 @@ class ElectrumGui(Logger): # Show network dialog if config does not exist if self.daemon.network: if self.config.get('auto_connect') is None: - wizard = InstallWizard(self.config, self.app, self.plugins) + wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self) wizard.init_network(self.daemon.network) wizard.terminate() diff --git a/electrum/gui/qt/installwizard.py b/electrum/gui/qt/installwizard.py index 45af0a21a..961f88916 100644 --- a/electrum/gui/qt/installwizard.py +++ b/electrum/gui/qt/installwizard.py @@ -31,6 +31,7 @@ from electrum.plugin import run_hook, Plugins if TYPE_CHECKING: from electrum.simple_config import SimpleConfig + from . import ElectrumGui MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys.") + '\n'\ @@ -121,12 +122,13 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): accept_signal = pyqtSignal() - def __init__(self, config: 'SimpleConfig', app: QApplication, plugins: 'Plugins'): + def __init__(self, config: 'SimpleConfig', app: QApplication, plugins: 'Plugins', *, gui_object: 'ElectrumGui'): QDialog.__init__(self, None) BaseWizard.__init__(self, config, plugins) self.setWindowTitle('Electrum - ' + _('Install Wizard')) self.app = app self.config = config + self.gui_thread = gui_object.gui_thread self.setMinimumSize(600, 400) self.accept_signal.connect(self.accept) self.title = QLabel() diff --git a/electrum/plugin.py b/electrum/plugin.py index d873a64e5..dbebf3a9f 100644 --- a/electrum/plugin.py +++ b/electrum/plugin.py @@ -556,6 +556,8 @@ class DeviceMgr(ThreadJob): keystore: 'Hardware_KeyStore', devices: List['Device'] = None) -> 'DeviceInfo': '''Ask the user to select a device to use if there is more than one, and return the DeviceInfo for the device.''' + # ideally this should not be called from the GUI thread... + # assert handler.get_gui_thread() != threading.current_thread(), 'must not be called from GUI thread' while True: infos = self.unpaired_device_infos(handler, plugin, devices) if infos: diff --git a/electrum/plugins/hw_wallet/plugin.py b/electrum/plugins/hw_wallet/plugin.py index 19e06a077..3080b9f0c 100644 --- a/electrum/plugins/hw_wallet/plugin.py +++ b/electrum/plugins/hw_wallet/plugin.py @@ -36,6 +36,7 @@ from electrum.storage import get_derivation_used_for_hw_device_encryption from electrum.keystore import Xpub, Hardware_KeyStore if TYPE_CHECKING: + import threading from electrum.wallet import Abstract_Wallet from electrum.base_wizard import BaseWizard @@ -210,6 +211,11 @@ class HardwareHandlerBase: if hasattr(self.win, 'wallet'): return self.win.wallet + def get_gui_thread(self) -> Optional['threading.Thread']: + if self.win is not None: + if hasattr(self.win, 'gui_thread'): + return self.win.gui_thread + def update_status(self, paired: bool) -> None: pass diff --git a/electrum/plugins/hw_wallet/qt.py b/electrum/plugins/hw_wallet/qt.py index 8df94ad7c..c8432f29e 100644 --- a/electrum/plugins/hw_wallet/qt.py +++ b/electrum/plugins/hw_wallet/qt.py @@ -67,6 +67,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger): def __init__(self, win: Union[ElectrumWindow, InstallWizard], device: str): QObject.__init__(self) Logger.__init__(self) + assert win.gui_thread == threading.current_thread(), 'must be called from GUI thread' self.clear_signal.connect(self.clear_dialog) self.error_signal.connect(self.error_dialog) self.message_signal.connect(self.message_dialog) @@ -254,6 +255,7 @@ class QtPluginBase(object): keystore: 'Hardware_KeyStore') -> Optional[str]: '''This dialog box should be usable even if the user has forgotten their PIN or it is in bootloader mode.''' + assert window.gui_thread != threading.current_thread(), 'must not be called from GUI thread' device_id = self.device_manager().xpub_id(keystore.xpub) if not device_id: try: @@ -264,7 +266,10 @@ class QtPluginBase(object): return device_id def show_settings_dialog(self, window: ElectrumWindow, keystore: 'Hardware_KeyStore') -> None: - device_id = self.choose_device(window, keystore) + # default implementation (if no dialog): just try to connect to device + def connect(): + device_id = self.choose_device(window, keystore) + keystore.thread.add(connect) def add_show_address_on_hw_device_button_for_receive_addr(self, wallet: 'Abstract_Wallet', keystore: 'Hardware_KeyStore', diff --git a/electrum/plugins/keepkey/qt.py b/electrum/plugins/keepkey/qt.py index fce633df9..72508ee73 100644 --- a/electrum/plugins/keepkey/qt.py +++ b/electrum/plugins/keepkey/qt.py @@ -208,9 +208,13 @@ class QtPlugin(QtPluginBase): menu.addAction(_("Show on {}").format(device_name), show_address) def show_settings_dialog(self, window, keystore): - device_id = self.choose_device(window, keystore) - if device_id: - SettingsDialog(window, self, keystore, device_id).exec_() + def connect(): + device_id = self.choose_device(window, keystore) + return device_id + def show_dialog(device_id): + if device_id: + SettingsDialog(window, self, keystore, device_id).exec_() + keystore.thread.add(connect, on_success=show_dialog) def request_trezor_init_settings(self, wizard, method, device): vbox = QVBoxLayout() diff --git a/electrum/plugins/safe_t/qt.py b/electrum/plugins/safe_t/qt.py index aa44495bb..d83663d53 100644 --- a/electrum/plugins/safe_t/qt.py +++ b/electrum/plugins/safe_t/qt.py @@ -84,9 +84,13 @@ class QtPlugin(QtPluginBase): menu.addAction(_("Show on {}").format(device_name), show_address) def show_settings_dialog(self, window, keystore): - device_id = self.choose_device(window, keystore) - if device_id: - SettingsDialog(window, self, keystore, device_id).exec_() + def connect(): + device_id = self.choose_device(window, keystore) + return device_id + def show_dialog(device_id): + if device_id: + SettingsDialog(window, self, keystore, device_id).exec_() + keystore.thread.add(connect, on_success=show_dialog) def request_safe_t_init_settings(self, wizard, method, device): vbox = QVBoxLayout() diff --git a/electrum/plugins/trezor/qt.py b/electrum/plugins/trezor/qt.py index 37a45ce6b..1bae9039e 100644 --- a/electrum/plugins/trezor/qt.py +++ b/electrum/plugins/trezor/qt.py @@ -182,9 +182,13 @@ class QtPlugin(QtPluginBase): menu.addAction(_("Show on {}").format(device_name), show_address) def show_settings_dialog(self, window, keystore): - device_id = self.choose_device(window, keystore) - if device_id: - SettingsDialog(window, self, keystore, device_id).exec_() + def connect(): + device_id = self.choose_device(window, keystore) + return device_id + def show_dialog(device_id): + if device_id: + SettingsDialog(window, self, keystore, device_id).exec_() + keystore.thread.add(connect, on_success=show_dialog) def request_trezor_init_settings(self, wizard, method, device_id): vbox = QVBoxLayout()