1
0

make plugins dialog available in tray

This makes it possible to install a third-party plugin from
the wizard, before creating a wallet, e.g. for a hardware wallet.
This commit is contained in:
ThomasV
2025-04-11 08:53:10 +02:00
parent e084789577
commit a500d5194d
4 changed files with 43 additions and 38 deletions

View File

@@ -38,8 +38,8 @@ except Exception as e:
"Error: Could not import PyQt6. On Linux systems, "
"you may try 'sudo apt-get install python3-pyqt6'") from e
from PyQt6.QtGui import QGuiApplication
from PyQt6.QtWidgets import QApplication, QSystemTrayIcon, QWidget, QMenu, QMessageBox, QDialog
from PyQt6.QtGui import QGuiApplication, QCursor
from PyQt6.QtWidgets import QApplication, QSystemTrayIcon, QWidget, QMenu, QMessageBox, QDialog, QToolTip
from PyQt6.QtCore import QObject, pyqtSignal, QTimer, Qt
import PyQt6.QtCore as QtCore
@@ -208,6 +208,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
m = self.tray.contextMenu()
m.clear()
network = self.daemon.network
m.addAction(_("Plugins"), self.show_plugins_dialog)
if network:
m.addAction(_("Network"), self.show_network_dialog)
if network and network.lngossip:
@@ -293,6 +294,11 @@ class ElectrumGui(BaseElectrumGui, Logger):
self.lightning_dialog = LightningDialog(self)
self.lightning_dialog.bring_to_top()
def show_plugins_dialog(self):
from .plugins_dialog import PluginsDialog
d = PluginsDialog(self)
d.exec()
def show_network_dialog(self, proxy_tab=False):
if self.network_dialog:
self.network_dialog.show(proxy_tab=proxy_tab)
@@ -546,3 +552,9 @@ class ElectrumGui(BaseElectrumGui, Logger):
if hasattr(PyQt6, "__path__"):
ret["pyqt.path"] = ", ".join(PyQt6.__path__ or [])
return ret
def do_copy(self, text: str, *, title: str = None) -> None:
self.app.clipboard().setText(text)
message = _("Text copied to Clipboard") if title is None else _("{} copied to Clipboard").format(title)
# tooltip cannot be displayed immediately when called from a menu; wait 200ms
self.timer.singleShot(200, lambda: QToolTip.showText(QCursor.pos(), message, None))

View File

@@ -759,7 +759,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
tools_menu.addAction(_("Electrum preferences"), self.settings_dialog)
tools_menu.addAction(_("&Network"), self.gui_object.show_network_dialog).setEnabled(bool(self.network))
tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
tools_menu.addAction(_("&Plugins"), self.gui_object.show_plugins_dialog)
tools_menu.addSeparator()
tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
@@ -1131,9 +1131,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
return ReceiveTab(self)
def do_copy(self, text: str, *, title: str = None) -> None:
self.app.clipboard().setText(text)
message = _("Text copied to Clipboard") if title is None else _("{} copied to Clipboard").format(title)
self.show_tooltip_after_delay(message)
self.gui_object.do_copy(text, title=title)
def show_tooltip_after_delay(self, message):
# tooltip cannot be displayed immediately when called from a menu; wait 200ms
@@ -2209,12 +2207,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
layout.addLayout(hbox, 4, 1)
d.exec()
def password_dialog(self, msg=None, parent=None):
from .password_dialog import PasswordDialog
parent = parent or self
d = PasswordDialog(parent, msg)
return d.run()
def tx_from_text(self, data: Union[str, bytes]) -> Union[None, 'PartialTransaction', 'Transaction']:
from electrum.transaction import tx_from_any
try:
@@ -2654,11 +2646,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.gui_object.timer.timeout.disconnect(self.timer_actions)
self.gui_object.close_window(self)
def plugins_dialog(self):
from .plugins_dialog import PluginsDialog
d = PluginsDialog(self)
d.exec()
def cpfp_dialog(self, parent_tx: Transaction) -> None:
new_tx = self.wallet.cpfp(parent_tx, 0)
total_size = parent_tx.estimated_size() + new_tx.estimated_size()

View File

@@ -6,17 +6,17 @@ from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton, QWidg
from electrum.i18n import _
from electrum.plugin import run_hook
from .util import WindowModalDialog, Buttons, CloseButton, WWLabel, insert_spaces
from .util import WindowModalDialog, Buttons, CloseButton, WWLabel, insert_spaces, MessageBoxMixin
if TYPE_CHECKING:
from .main_window import ElectrumWindow
from . import ElectrumGui
from electrum_cc import ECPrivkey
class PluginDialog(WindowModalDialog):
def __init__(self, name, metadata, status_button: Optional['PluginStatusButton'], window: 'ElectrumWindow'):
def __init__(self, name, metadata, status_button: Optional['PluginStatusButton'], window: 'PluginsDialog'):
display_name = metadata.get('fullname', '')
author = metadata.get('author', '')
description = metadata.get('description', '')
@@ -80,12 +80,12 @@ class PluginDialog(WindowModalDialog):
self.status_button.update()
self.close()
# note: all enabled plugins will receive this hook:
run_hook('init_qt', self.window.window.gui_object)
run_hook('init_qt', self.window.gui_object)
class PluginStatusButton(QPushButton):
def __init__(self, window, name):
def __init__(self, window: 'PluginsDialog', name: str):
QPushButton.__init__(self, '')
self.window = window
self.plugins = window.plugins
@@ -103,7 +103,7 @@ class PluginStatusButton(QPushButton):
p = self.plugins.get(self.name)
plugin_is_loaded = p is not None
enabled = (
not plugin_is_loaded and self.plugins.is_available(self.name, self.window.wallet)
not plugin_is_loaded
or plugin_is_loaded and p.can_user_disable()
)
self.setEnabled(enabled)
@@ -113,14 +113,13 @@ class PluginStatusButton(QPushButton):
self.setText(text)
class PluginsDialog(WindowModalDialog):
class PluginsDialog(WindowModalDialog, MessageBoxMixin):
def __init__(self, window: 'ElectrumWindow'):
WindowModalDialog.__init__(self, window, _('Electrum Plugins'))
self.window = window
self.wallet = self.window.wallet
self.config = window.config
self.plugins = self.window.gui_object.plugins
def __init__(self, gui_object: 'ElectrumGui'):
WindowModalDialog.__init__(self, None, _('Electrum Plugins'))
self.gui_object = gui_object
self.config = gui_object.config
self.plugins = gui_object.plugins
vbox = QVBoxLayout(self)
scroll = QScrollArea()
scroll.setEnabled(True)
@@ -144,7 +143,7 @@ class PluginsDialog(WindowModalDialog):
self.init_plugins_password()
return
# ask for url and password, same window
pw = self.window.password_dialog(
pw = self.password_dialog(
msg=' '.join([
_('<b>Warning</b>: Third-party plugins are not endorsed by Electrum!'),
'<br/><br/>',
@@ -160,7 +159,7 @@ class PluginsDialog(WindowModalDialog):
privkey = self.plugins.derive_privkey(pw, salt)
if pubkey != privkey.get_public_key_bytes():
keyfile_path, keyfile_help = self.plugins.get_keyfile_path()
self.window.show_error(
self.show_error(
''.join([
_('Incorrect password.'), '\n\n',
_('Your plugin authorization password is required to install plugins.'), ' ',
@@ -186,8 +185,8 @@ class PluginsDialog(WindowModalDialog):
_('Your plugins key is:'), '\n\n', key_hex, '\n\n',
_('Please save this key in'), '\n\n' + keyfile_path, '\n\n', keyfile_help
])
self.window.do_copy(key_hex, title=_('Plugins key'))
self.window.show_message(msg)
self.gui_object.do_copy(key_hex, title=_('Plugins key'))
self.show_message(msg)
def download_plugin_dialog(self):
import os
@@ -206,12 +205,12 @@ class PluginsDialog(WindowModalDialog):
except UserCancelled:
return
except Exception as e:
self.window.show_error(f"{e}")
self.show_error(f"{e}")
return
try:
success = self.confirm_add_plugin(path)
except Exception as e:
self.window.show_error(f"{e}")
self.show_error(f"{e}")
success = False
if not success:
os.unlink(path)
@@ -232,7 +231,7 @@ class PluginsDialog(WindowModalDialog):
try:
success = self.confirm_add_plugin(path)
except Exception as e:
self.window.show_error(f"{e}")
self.show_error(f"{e}")
success = False
if not success:
os.unlink(path)
@@ -249,7 +248,7 @@ class PluginsDialog(WindowModalDialog):
return False
self.plugins.external_plugin_metadata[name] = manifest
self.plugins.authorize_plugin(name, path, privkey)
self.window.show_message(_('Plugin installed successfully.'))
self.show_message(_('Plugin installed successfully.'))
self.show_list()
return True

View File

@@ -320,6 +320,13 @@ class MessageBoxMixin(object):
return None
return choice_widget.selected_key
def password_dialog(self, msg=None, parent=None):
from .password_dialog import PasswordDialog
parent = parent or self
d = PasswordDialog(parent, msg)
return d.run()
def custom_message_box(*, icon, parent, title, text, buttons=QMessageBox.StandardButton.Ok,
defaultButton=QMessageBox.StandardButton.NoButton, rich_text=False,