diff --git a/jmbase/jmbase/__init__.py b/jmbase/jmbase/__init__.py index d0b0e61..55c27b3 100644 --- a/jmbase/jmbase/__init__.py +++ b/jmbase/jmbase/__init__.py @@ -12,7 +12,7 @@ from .support import (get_log, chunks, debug_silence, jmprint, from .proof_of_work import get_pow, verify_pow from .twisted_utils import (stop_reactor, is_hs_uri, get_tor_agent, get_nontor_agent, JMHiddenService, - JMHTTPResource) + JMHTTPResource, set_custom_stop_reactor) from .bytesprod import BytesProducer from .commands import * diff --git a/jmbase/jmbase/twisted_utils.py b/jmbase/jmbase/twisted_utils.py index 8a8f507..f7e2f28 100644 --- a/jmbase/jmbase/twisted_utils.py +++ b/jmbase/jmbase/twisted_utils.py @@ -9,6 +9,9 @@ import txtorcon from txtorcon.web import tor_agent from txtorcon import TorControlProtocol, TorConfig +_custom_stop_reactor_is_set = False +custom_stop_reactor = None + # This removes `CONF_CHANGED` requests # over the Tor control port, which aren't needed for our use case. def patch_add_event_listener(self, evt, callback): @@ -70,7 +73,19 @@ class WhitelistContextFactory(object): return CertificateOptions(verify=False) return self.default_policy.creatorForNetloc(hostname, port) +def set_custom_stop_reactor(fn): + global _custom_stop_reactor_is_set + global custom_stop_reactor + _custom_stop_reactor_is_set = True + custom_stop_reactor = fn + def stop_reactor(): + if not _custom_stop_reactor_is_set: + _stop_reactor() + else: + custom_stop_reactor() + +def _stop_reactor(): """ The value of the bool `reactor.running` does not reliably tell us whether the reactor is running (!). There are startup @@ -83,8 +98,6 @@ def stop_reactor(): except ReactorNotRunning: pass - - def is_hs_uri(s): x = wrapped_urlparse(s) if x.hostname.endswith(".onion"): diff --git a/scripts/joinmarket-qt.py b/scripts/joinmarket-qt.py index 48cc4b8..762b40d 100755 --- a/scripts/joinmarket-qt.py +++ b/scripts/joinmarket-qt.py @@ -55,7 +55,7 @@ donation_address_url = "https://bitcoinprivacy.me/joinmarket-donations" #Version of this Qt script specifically JM_GUI_VERSION = '27dev' -from jmbase import get_log, stop_reactor +from jmbase import get_log, stop_reactor, set_custom_stop_reactor from jmbase.support import EXIT_FAILURE, utxo_to_utxostr,\ hextobin, JM_CORE_VERSION import jmbitcoin as btc @@ -1623,9 +1623,20 @@ class JMMainWindow(QMainWindow): self.reactor = reactor self.initUI() + # a flag to indicate that shutdown should not + # depend on user input + self.unconditional_shutdown = False + def closeEvent(self, event): - quit_msg = "Are you sure you want to quit?" - reply = JMQtMessageBox(self, quit_msg, mbtype='question') + if self.unconditional_shutdown: + JMQtMessageBox(self, + "RPC connection is lost; shutting down.", + mbtype='crit', + title="Error") + reply = QMessageBox.Yes + else: + quit_msg = "Are you sure you want to quit?" + reply = JMQtMessageBox(self, quit_msg, mbtype='question') if reply == QMessageBox.Yes: event.accept() if self.reactor.threadpool is not None: @@ -2413,6 +2424,18 @@ tabWidget.currentChanged.connect(onTabChange) mainWindow.show() reactor.runReturn() +# Qt does not stop automatically when we stop the qt5reactor, and +# also we don't want to close without warning the user; +# patch our stop_reactor method to include the necessary cleanup: +def qt_shutdown(): + # checking ensures we only fire the close + # event once even if stop_reactor is called + # multiple times (which it often is): + if mainWindow.isVisible(): + mainWindow.unconditional_shutdown = True + mainWindow.close() +set_custom_stop_reactor(qt_shutdown) + # Upon launching the app, ask the user to choose a wallet to open mainWindow.openWallet()