From 73e6b793136cb51f198e391118cf8fa743cdf62e Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Sat, 20 Jun 2026 21:32:55 +0800 Subject: [PATCH] qtvcp: arm terminate/interrupt handlers before screen construction The handlers were installed only just before the event loop, after construction. SIGINT's default handler raises KeyboardInterrupt, so an interrupt during construction became an unhandled exception (headless, it hung on the error dialog). Install them right after QApplication is created; before the loop runs a signal does a clean shutdown and exit, once running it quits the loop as before. --- src/emc/usr_intf/qtvcp/qtvcp.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/emc/usr_intf/qtvcp/qtvcp.py b/src/emc/usr_intf/qtvcp/qtvcp.py index 8acb3398b72..1f1d8901c38 100644 --- a/src/emc/usr_intf/qtvcp/qtvcp.py +++ b/src/emc/usr_intf/qtvcp/qtvcp.py @@ -114,6 +114,23 @@ def __init__(self): global APP APP = MyApplication(sys.argv) + # Install signal handlers before the slow screen construction. SIGINT's + # default handler raises KeyboardInterrupt, so an interrupt mid-build + # would otherwise be an unhandled exception. Before the loop runs + # APP.quit() is a no-op, so exit cleanly instead. + self._loop_running = False + def _handle_quit_signal(signum, frame): + if self._loop_running: + APP.quit() + else: + try: + self.shutdown() + except Exception: + pass + os._exit(0) + signal.signal(signal.SIGTERM, _handle_quit_signal) + signal.signal(signal.SIGINT, _handle_quit_signal) + # a specific path has been set to load from or... # no path set but -ini is present: default qtvcp screen...or # oops error @@ -447,17 +464,8 @@ def __init__(self): if (INITITLE !=''): window.setWindowTitle(INITITLE) - # catch control c and terminate signals - # Quit the Qt event loop on the signal so APP.exec() returns and the - # shutdown() below runs the normal cleanup once. Calling shutdown() - # directly from the handler would clean up but leave the loop running. - # Quitting the loop also bypasses the window-close confirmation dialog, - # which is correct for a terminate request and leaves that interactive - # feature untouched. Qt's C++ event loop does not return to Python often - # enough to run Python signal handlers, so a periodic no-op timer yields - # to the interpreter to let them fire. - signal.signal(signal.SIGTERM, lambda *a: APP.quit()) - signal.signal(signal.SIGINT, lambda *a: APP.quit()) + # Handlers were installed above. Qt's C++ event loop rarely returns to + # Python, so a periodic no-op timer yields to let them fire. self._signal_timer = QtCore.QTimer() self._signal_timer.timeout.connect(lambda: None) self._signal_timer.start(200) @@ -478,7 +486,9 @@ def __init__(self): # start loop global _app + self._loop_running = True _app = APP.exec() + self._loop_running = False self.shutdown() # finds the postgui file name and INI file path