forked from M-Labs/artiq
dashboard: support for code applets
This commit is contained in:
parent
deb51eaaa1
commit
56e3c80623
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue