diff --git a/artiq/frontend/artiq_gui.py b/artiq/frontend/artiq_gui.py index 079eedb51..e83c0012a 100755 --- a/artiq/frontend/artiq_gui.py +++ b/artiq/frontend/artiq_gui.py @@ -112,6 +112,7 @@ def main(): smgr.register(d_datasets) appletmgr = applets.AppletManager(dock_area) + atexit_register_coroutine(appletmgr.stop) smgr.register(appletmgr) if os.name != "nt": diff --git a/artiq/gui/applets.py b/artiq/gui/applets.py index 4eb79fb53..a085ffa5f 100644 --- a/artiq/gui/applets.py +++ b/artiq/gui/applets.py @@ -1,4 +1,6 @@ import logging +import asyncio +import shlex from quamash import QtCore, QtGui, QtWidgets from pyqtgraph import dockarea @@ -8,23 +10,50 @@ logger = logging.getLogger(__name__) class AppletDock(dockarea.Dock): - def __init__(self, token, name): + def __init__(self, token, name, command): dockarea.Dock.__init__(self, "applet" + str(token), label="Applet: " + name, closable=True) self.setMinimumSize(QtCore.QSize(500, 400)) + self.token = token + self.applet_name = name + self.command = command + + async def start(self): + command = self.command.format(embed_token=self.token) + logger.debug("starting command %s for %s", command, self.applet_name) + try: + self.process = await asyncio.create_subprocess_exec( + *shlex.split(command)) + except FileNotFoundError: + logger.warning("Applet %s failed to start", self.applet_name) + else: + logger.warning("Applet %s exited", self.applet_name) def capture(self, win_id): + logger.debug("capturing window 0x%x for %s", win_id, self.applet_name) self.captured_window = QtGui.QWindow.fromWinId(win_id) - self.captured_widget = QtWidgets.QWidget.createWindowContainer(captured_window) - self.addWidget(captured_widget) + self.captured_widget = QtWidgets.QWidget.createWindowContainer( + self.captured_window) + self.addWidget(self.captured_widget) - def terminate(self): + async def terminate(self): if hasattr(self, "captured_window"): self.captured_window.close() self.captured_widget.deleteLater() del self.captured_window del self.captured_widget + if hasattr(self, "process"): + try: + await asyncio.wait_for(self.process.wait(), 2.0) + except: + logger.warning("Applet %s failed to exit, killing", + self.applet_name) + try: + self.process.kill() + except ProcessLookupError: + pass + await self.process.wait() class AppletsDock(dockarea.Dock): @@ -116,18 +145,24 @@ class AppletManager: logger.warning("Ignored incorrect embed token %d for winid 0x%x", token, win_id) return + self.applet_docks[token].capture(win_id) def create(self, name, command): token = next(iter(set(range(len(self.applet_docks) + 1)) - - self.applet_docks.keys())) - dock = AppletDock(token, name) + - self.applet_docks.keys())) + dock = AppletDock(token, name, command) self.applet_docks[token] = dock self.dock_area.floatDock(dock) + asyncio.ensure_future(dock.start()) return token def delete(self, token): del self.applet_docks[token] + async def stop(self): + for dock in self.applet_docks.values(): + await dock.terminate() + def save_state(self): return dict()