From 898a4c270d9cf69b25b639f1911a752465482255 Mon Sep 17 00:00:00 2001 From: Sasha Zykov Date: Mon, 29 Jun 2026 21:44:08 +0200 Subject: [PATCH] hw_wallet/qt: reuse device message dialog across button requests QtHandlerBase.message_dialog() used to tear down the current dialog (clear_dialog -> QDialog.accept) and build a brand-new WindowModalDialog on every device button request. A device emits one button request per output, so signing a tx with many outputs rebuilt the dialog once per output. On macOS a window-modal QDialog is shown as an animated "sheet", so the popup visibly slid closed and reopened for each output, and the GUI churn also competed with the single hardware-comms thread for the GIL, adding latency between device prompts. Reuse the open dialog and just update its label text when one is already showing (title and on_cancel are stable within a signing flow). This also smooths Ledger's repeated progress messages, which update the text on each call. --- electrum/hw_wallet/qt.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/electrum/hw_wallet/qt.py b/electrum/hw_wallet/qt.py index 5cef3e175441..cb562ac222ab 100644 --- a/electrum/hw_wallet/qt.py +++ b/electrum/hw_wallet/qt.py @@ -84,6 +84,8 @@ def __init__(self, win: Union['ElectrumWindow', 'QENewWalletWizard'], device: st self.win = win self.device = device self.dialog = None + self.dialog_label = None + self._dialog_on_cancel = None self.done = threading.Event() def top_level_window(self): @@ -174,12 +176,22 @@ def word_dialog(self, msg): MESSAGE_DIALOG_TITLE = None # type: Optional[str] def message_dialog(self, msg, on_cancel=None): + # If a dialog is already open, update its text instead of rebuilding it. + # A device emits one button request per output, and rebuilding the + # window-modal dialog each time is slow and visibly janky on macOS + # (the modal "sheet" animates closed/open between outputs). See #10718. + if self.dialog is not None and self._dialog_on_cancel == on_cancel: + self.dialog_label.setText(msg) + if not self.dialog.isVisible(): # e.g. was hidden by a user "cancel" + self.dialog.show() + return self.clear_dialog() title = self.MESSAGE_DIALOG_TITLE if title is None: title = _('Please check your {} device').format(self.device) self.dialog = dialog = WindowModalDialog(self.top_level_window(), title) - label = QLabel(msg) + self._dialog_on_cancel = on_cancel + self.dialog_label = label = QLabel(msg) label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) vbox = QVBoxLayout(dialog) vbox.addWidget(label) @@ -197,6 +209,8 @@ def clear_dialog(self): if self.dialog: self.dialog.accept() self.dialog = None + self.dialog_label = None + self._dialog_on_cancel = None def win_query_choice(self, msg: str, choices: Sequence[ChoiceItem]): try: