dashboard: support for code applets

This commit is contained in:
Sebastien Bourdeauducq 2016-09-06 22:38:34 +08:00
parent deb51eaaa1
commit 56e3c80623
2 changed files with 87 additions and 34 deletions

View File

@ -53,10 +53,14 @@ class AppletsCCBDock(applets.AppletsDock):
if not self.listen_action.isChecked(): if not self.listen_action.isChecked():
return return
parent, applet = self.locate_applet(name, group, True) parent, applet = self.locate_applet(name, group, True)
if applet is None: if is_code:
applet = self.new(name=name, command=command_or_code, parent=parent) spec = {"ty": "code", "code": command_or_code}
else: else:
applet.setText(2, command_or_code) spec = {"ty": "command", "command": command_or_code}
if applet is None:
applet = self.new(name=name, spec=spec, parent=parent)
else:
self.set_spec(applet, spec)
applet.setCheckState(0, QtCore.Qt.Checked) applet.setCheckState(0, QtCore.Qt.Checked)
def ccb_disable_applet(self, name, group=None): def ccb_disable_applet(self, name, group=None):

View File

@ -91,7 +91,7 @@ class AppletIPCServer(AsyncioParentComm):
class _AppletDock(QDockWidgetCloseDetect): class _AppletDock(QDockWidgetCloseDetect):
def __init__(self, datasets_sub, uid, name, command): def __init__(self, datasets_sub, uid, name, spec):
QDockWidgetCloseDetect.__init__(self, "Applet: " + name) QDockWidgetCloseDetect.__init__(self, "Applet: " + name)
self.setObjectName("applet" + str(uid)) self.setObjectName("applet" + str(uid))
@ -101,7 +101,7 @@ class _AppletDock(QDockWidgetCloseDetect):
self.datasets_sub = datasets_sub self.datasets_sub = datasets_sub
self.applet_name = name self.applet_name = name
self.command = command self.spec = spec
self.starting_stopping = False self.starting_stopping = False
@ -112,30 +112,27 @@ class _AppletDock(QDockWidgetCloseDetect):
def _get_log_source(self): def _get_log_source(self):
return "applet({})".format(self.applet_name) return "applet({})".format(self.applet_name)
async def start(self): async def start_process(self, args, stdin):
if self.starting_stopping: if self.starting_stopping:
return return
self.starting_stopping = True self.starting_stopping = True
try: try:
self.ipc = AppletIPCServer(self.datasets_sub) self.ipc = AppletIPCServer(self.datasets_sub)
command_tpl = string.Template(self.command)
python = sys.executable.replace("\\", "\\\\")
command = command_tpl.safe_substitute(
python=python,
artiq_applet=python + " -m artiq.applets."
)
logger.debug("starting command %s for %s", command, self.applet_name)
env = os.environ.copy() env = os.environ.copy()
env["PYTHONUNBUFFERED"] = "1" env["PYTHONUNBUFFERED"] = "1"
env["ARTIQ_APPLET_EMBED"] = self.ipc.get_address() env["ARTIQ_APPLET_EMBED"] = self.ipc.get_address()
try: try:
await self.ipc.create_subprocess( await self.ipc.create_subprocess(
*shlex.split(command), *args,
stdin=None if stdin is None else subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
env=env, start_new_session=True) env=env, start_new_session=True)
except: except:
logger.warning("Applet %s failed to start", self.applet_name, logger.warning("Applet %s failed to start", self.applet_name,
exc_info=True) exc_info=True)
if stdin is not None:
self.ipc.process.stdin.write(stdin.encode())
self.ipc.process.stdin.write_eof()
asyncio.ensure_future( asyncio.ensure_future(
LogParser(self._get_log_source).stream_task( LogParser(self._get_log_source).stream_task(
self.ipc.process.stdout)) self.ipc.process.stdout))
@ -146,6 +143,21 @@ class _AppletDock(QDockWidgetCloseDetect):
finally: finally:
self.starting_stopping = False self.starting_stopping = False
async def start(self):
if self.spec["ty"] == "command":
command_tpl = string.Template(self.spec["command"])
python = sys.executable.replace("\\", "\\\\")
command = command_tpl.safe_substitute(
python=python,
artiq_applet=python + " -m artiq.applets."
)
logger.debug("starting command %s for %s", command, self.applet_name)
await self.start_process(shlex.split(command), None)
elif self.spec["ty"] == "code":
await self.start_process([sys.executable], self.spec["code"])
else:
raise ValueError
def embed(self, win_id): def embed(self, win_id):
logger.debug("capturing window 0x%x for %s", win_id, self.applet_name) logger.debug("capturing window 0x%x for %s", win_id, self.applet_name)
self.embed_window = QtGui.QWindow.fromWinId(win_id) self.embed_window = QtGui.QWindow.fromWinId(win_id)
@ -334,9 +346,10 @@ class AppletsDock(QtWidgets.QDockWidget):
self.table.addAction(new_action) self.table.addAction(new_action)
templates_menu = QtWidgets.QMenu() templates_menu = QtWidgets.QMenu()
for name, template in _templates: for name, template in _templates:
spec = {"ty": "command", "command": template}
action = QtWidgets.QAction(name, self.table) action = QtWidgets.QAction(name, self.table)
action.triggered.connect(partial( action.triggered.connect(partial(
self.new_with_parent, self.new, command=template)) self.new_with_parent, self.new, spec=spec))
templates_menu.addAction(action) templates_menu.addAction(action)
restart_action = QtWidgets.QAction("New applet from template", self.table) restart_action = QtWidgets.QAction("New applet from template", self.table)
restart_action.setMenu(templates_menu) restart_action.setMenu(templates_menu)
@ -357,8 +370,38 @@ class AppletsDock(QtWidgets.QDockWidget):
self.table.itemChanged.connect(self.item_changed) self.table.itemChanged.connect(self.item_changed)
def create(self, uid, name, command): def get_spec(self, item):
dock = _AppletDock(self.datasets_sub, uid, name, command) if item.applet_spec_ty == "command":
return {"ty": "command", "command": item.text(2)}
elif item.applet_spec_ty == "code":
return {"ty": "code", "code": item.applet_code}
else:
raise ValueError
def set_spec(self, item, spec):
self.table.itemChanged.disconnect()
try:
item.applet_spec_ty = spec["ty"]
if spec["ty"] == "command":
item.setText(2, spec["command"])
item.setIcon(2, QtGui.QIcon())
if hasattr(item, "applet_code"):
del item.applet_code
elif spec["ty"] == "code":
item.setText(2, "(code)")
item.setIcon(2, QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileIcon))
item.applet_code = spec["code"]
else:
raise ValueError
dock = item.applet_dock
if dock is not None:
dock.spec = spec
finally:
self.table.itemChanged.connect(self.item_changed)
def create(self, uid, name, spec):
dock = _AppletDock(self.datasets_sub, uid, name, spec)
self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)
dock.setFloating(True) dock.setFloating(True)
asyncio.ensure_future(dock.start()) asyncio.ensure_future(dock.start())
@ -369,16 +412,15 @@ class AppletsDock(QtWidgets.QDockWidget):
if item.ty == "applet": if item.ty == "applet":
if column == 0: if column == 0:
if item.checkState(0) == QtCore.Qt.Checked: if item.checkState(0) == QtCore.Qt.Checked:
command = item.text(2) name = item.text(1)
if command: spec = self.get_spec(item)
name = item.text(1) dock = self.create(item.applet_uid, name, spec)
dock = self.create(item.applet_uid, name, command) item.applet_dock = dock
item.applet_dock = dock if item.applet_geometry is not None:
if item.applet_geometry is not None: dock.restoreGeometry(item.applet_geometry)
dock.restoreGeometry(item.applet_geometry) # geometry is now handled by main window state
# geometry is now handled by main window state item.applet_geometry = None
item.applet_geometry = None self.dock_to_item[dock] = item
self.dock_to_item[dock] = item
else: else:
dock = item.applet_dock dock = item.applet_dock
if dock is not None: if dock is not None:
@ -391,7 +433,8 @@ class AppletsDock(QtWidgets.QDockWidget):
if column == 1: if column == 1:
dock.rename(new_value) dock.rename(new_value)
else: else:
dock.command = new_value self.set_spec(
item, {"ty": "command", "command": new_value})
elif item.ty == "group": elif item.ty == "group":
# To Qt's credit, it already does everything for us here. # To Qt's credit, it already does everything for us here.
pass pass
@ -422,15 +465,17 @@ class AppletsDock(QtWidgets.QDockWidget):
name = "untitled " + str(i) name = "untitled " + str(i)
return name return name
def new(self, uid=None, name=None, command="", parent=None): def new(self, uid=None, name=None, spec=None, parent=None):
if uid is None: if uid is None:
uid = next(i for i in count() if i not in self.applet_uids) uid = next(i for i in count() if i not in self.applet_uids)
if spec is None:
spec = {"ty": "command", "command": ""}
assert uid not in self.applet_uids, uid assert uid not in self.applet_uids, uid
self.applet_uids.add(uid) self.applet_uids.add(uid)
if name is None: if name is None:
name = self.get_untitled() name = self.get_untitled()
item = QtWidgets.QTreeWidgetItem(["", name, command]) item = QtWidgets.QTreeWidgetItem(["", name, ""])
item.ty = "applet" item.ty = "applet"
item.setFlags(QtCore.Qt.ItemIsSelectable | item.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsUserCheckable |
@ -444,6 +489,7 @@ class AppletsDock(QtWidgets.QDockWidget):
item.applet_geometry = None item.applet_geometry = None
item.setIcon(0, QtWidgets.QApplication.style().standardIcon( item.setIcon(0, QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_ComputerIcon)) QtWidgets.QStyle.SP_ComputerIcon))
self.set_spec(item, spec)
if parent is None: if parent is None:
self.table.addTopLevelItem(item) self.table.addTopLevelItem(item)
else: else:
@ -543,11 +589,11 @@ class AppletsDock(QtWidgets.QDockWidget):
uid = cwi.applet_uid uid = cwi.applet_uid
enabled = cwi.checkState(0) == QtCore.Qt.Checked enabled = cwi.checkState(0) == QtCore.Qt.Checked
name = cwi.text(1) name = cwi.text(1)
command = cwi.text(2) spec = self.get_spec(cwi)
geometry = cwi.applet_geometry geometry = cwi.applet_geometry
if geometry is not None: if geometry is not None:
geometry = bytes(geometry) geometry = bytes(geometry)
state.append(("applet", uid, enabled, name, command, geometry)) state.append(("applet", uid, enabled, name, spec, geometry))
elif cwi.ty == "group": elif cwi.ty == "group":
name = cwi.text(1) name = cwi.text(1)
expanded = cwi.isExpanded() expanded = cwi.isExpanded()
@ -563,8 +609,11 @@ class AppletsDock(QtWidgets.QDockWidget):
def restore_state_item(self, state, parent): def restore_state_item(self, state, parent):
for wis in state: for wis in state:
if wis[0] == "applet": if wis[0] == "applet":
_, uid, enabled, name, command, geometry = wis _, uid, enabled, name, spec, geometry = wis
item = self.new(uid, name, command, parent=parent) if spec["ty"] not in {"command", "code"}:
raise ValueError("Invalid applet spec type: "
+ str(spec["ty"]))
item = self.new(uid, name, spec, parent=parent)
if geometry is not None: if geometry is not None:
geometry = QtCore.QByteArray(geometry) geometry = QtCore.QByteArray(geometry)
item.applet_geometry = geometry item.applet_geometry = geometry