Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/artiq
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: deb51eaaa175
Choose a base ref
...
head repository: m-labs/artiq
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 5d293d14c641
Choose a head ref
  • 4 commits
  • 3 files changed
  • 1 contributor

Commits on Sep 6, 2016

  1. Copy the full SHA
    56e3c80 View commit details
  2. Copy the full SHA
    996dfca View commit details
  3. Copy the full SHA
    f6b5d6d View commit details
  4. Copy the full SHA
    5d293d1 View commit details
Showing with 106 additions and 50 deletions.
  1. +6 −2 artiq/dashboard/applets_ccb.py
  2. +1 −1 artiq/dashboard/experiments.py
  3. +99 −47 artiq/gui/applets.py
8 changes: 6 additions & 2 deletions artiq/dashboard/applets_ccb.py
Original file line number Diff line number Diff line change
@@ -53,10 +53,14 @@ def ccb_create_applet(self, name, command_or_code, group=None, is_code=False):
if not self.listen_action.isChecked():
return
parent, applet = self.locate_applet(name, group, True)
if is_code:
spec = {"ty": "code", "code": command_or_code}
else:
spec = {"ty": "command", "command": command_or_code}
if applet is None:
applet = self.new(name=name, command=command_or_code, parent=parent)
applet = self.new(name=name, spec=spec, parent=parent)
else:
applet.setText(2, command_or_code)
self.set_spec(applet, spec)
applet.setCheckState(0, QtCore.Qt.Checked)

def ccb_disable_applet(self, name, group=None):
2 changes: 1 addition & 1 deletion artiq/dashboard/experiments.py
Original file line number Diff line number Diff line change
@@ -192,7 +192,7 @@ def _disable_other_scans(self, current_name):
for name, widgets in self._arg_to_widgets.items():
if (name != current_name
and isinstance(widgets["entry"], ScanEntry)):
entry.disable()
widgets["entry"].disable()

def save_state(self):
expanded = []
146 changes: 99 additions & 47 deletions artiq/gui/applets.py
Original file line number Diff line number Diff line change
@@ -81,17 +81,18 @@ async def serve(self, embed_cb, fix_initial_size_cb):
finally:
self.datasets_sub.notify_cbs.remove(self._on_mod)

def start(self, embed_cb, fix_initial_size_cb):
def start_server(self, embed_cb, fix_initial_size_cb):
self.server_task = asyncio.ensure_future(
self.serve(embed_cb, fix_initial_size_cb))

async def stop(self):
self.server_task.cancel()
await asyncio.wait([self.server_task])
async def stop_server(self):
if hasattr(self, "server_task"):
self.server_task.cancel()
await asyncio.wait([self.server_task])


class _AppletDock(QDockWidgetCloseDetect):
def __init__(self, datasets_sub, uid, name, command):
def __init__(self, datasets_sub, uid, name, spec):
QDockWidgetCloseDetect.__init__(self, "Applet: " + name)
self.setObjectName("applet" + str(uid))

@@ -101,7 +102,7 @@ def __init__(self, datasets_sub, uid, name, command):

self.datasets_sub = datasets_sub
self.applet_name = name
self.command = command
self.spec = spec

self.starting_stopping = False

@@ -112,40 +113,53 @@ def rename(self, name):
def _get_log_source(self):
return "applet({})".format(self.applet_name)

async def start(self):
async def start_process(self, args, stdin):
if self.starting_stopping:
return
self.starting_stopping = True
try:
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["PYTHONUNBUFFERED"] = "1"
env["ARTIQ_APPLET_EMBED"] = self.ipc.get_address()
try:
await self.ipc.create_subprocess(
*shlex.split(command),
*args,
stdin=None if stdin is None else subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
env=env, start_new_session=True)
except:
logger.warning("Applet %s failed to start", self.applet_name,
exc_info=True)
return
if stdin is not None:
self.ipc.process.stdin.write(stdin.encode())
self.ipc.process.stdin.write_eof()
asyncio.ensure_future(
LogParser(self._get_log_source).stream_task(
self.ipc.process.stdout))
asyncio.ensure_future(
LogParser(self._get_log_source).stream_task(
self.ipc.process.stderr))
self.ipc.start(self.embed, self.fix_initial_size)
self.ipc.start_server(self.embed, self.fix_initial_size)
finally:
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):
logger.debug("capturing window 0x%x for %s", win_id, self.applet_name)
self.embed_window = QtGui.QWindow.fromWinId(win_id)
@@ -164,18 +178,19 @@ async def terminate(self, delete_self=True):
self.starting_stopping = True

if hasattr(self, "ipc"):
await self.ipc.stop()
self.ipc.write_pyon({"action": "terminate"})
try:
await asyncio.wait_for(self.ipc.process.wait(), 2.0)
except:
logger.warning("Applet %s failed to exit, killing",
self.applet_name)
await self.ipc.stop_server()
if hasattr(self.ipc, "process"):
self.ipc.write_pyon({"action": "terminate"})
try:
self.ipc.process.kill()
except ProcessLookupError:
pass
await self.ipc.process.wait()
await asyncio.wait_for(self.ipc.process.wait(), 2.0)
except:
logger.warning("Applet %s failed to exit, killing",
self.applet_name)
try:
self.ipc.process.kill()
except ProcessLookupError:
pass
await self.ipc.process.wait()
del self.ipc

if hasattr(self, "embed_widget"):
@@ -334,9 +349,10 @@ def __init__(self, main_window, datasets_sub):
self.table.addAction(new_action)
templates_menu = QtWidgets.QMenu()
for name, template in _templates:
spec = {"ty": "command", "command": template}
action = QtWidgets.QAction(name, self.table)
action.triggered.connect(partial(
self.new_with_parent, self.new, command=template))
self.new_with_parent, self.new, spec=spec))
templates_menu.addAction(action)
restart_action = QtWidgets.QAction("New applet from template", self.table)
restart_action.setMenu(templates_menu)
@@ -357,8 +373,38 @@ def __init__(self, main_window, datasets_sub):

self.table.itemChanged.connect(self.item_changed)

def create(self, uid, name, command):
dock = _AppletDock(self.datasets_sub, uid, name, command)
def get_spec(self, item):
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)
dock.setFloating(True)
asyncio.ensure_future(dock.start())
@@ -369,16 +415,15 @@ def item_changed(self, item, column):
if item.ty == "applet":
if column == 0:
if item.checkState(0) == QtCore.Qt.Checked:
command = item.text(2)
if command:
name = item.text(1)
dock = self.create(item.applet_uid, name, command)
item.applet_dock = dock
if item.applet_geometry is not None:
dock.restoreGeometry(item.applet_geometry)
# geometry is now handled by main window state
item.applet_geometry = None
self.dock_to_item[dock] = item
name = item.text(1)
spec = self.get_spec(item)
dock = self.create(item.applet_uid, name, spec)
item.applet_dock = dock
if item.applet_geometry is not None:
dock.restoreGeometry(item.applet_geometry)
# geometry is now handled by main window state
item.applet_geometry = None
self.dock_to_item[dock] = item
else:
dock = item.applet_dock
if dock is not None:
@@ -391,7 +436,8 @@ def item_changed(self, item, column):
if column == 1:
dock.rename(new_value)
else:
dock.command = new_value
self.set_spec(
item, {"ty": "command", "command": new_value})
elif item.ty == "group":
# To Qt's credit, it already does everything for us here.
pass
@@ -422,15 +468,17 @@ def walk(wi):
name = "untitled " + str(i)
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:
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
self.applet_uids.add(uid)

if name is None:
name = self.get_untitled()
item = QtWidgets.QTreeWidgetItem(["", name, command])
item = QtWidgets.QTreeWidgetItem(["", name, ""])
item.ty = "applet"
item.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsUserCheckable |
@@ -444,6 +492,7 @@ def new(self, uid=None, name=None, command="", parent=None):
item.applet_geometry = None
item.setIcon(0, QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_ComputerIcon))
self.set_spec(item, spec)
if parent is None:
self.table.addTopLevelItem(item)
else:
@@ -543,11 +592,11 @@ def save_state_item(self, wi):
uid = cwi.applet_uid
enabled = cwi.checkState(0) == QtCore.Qt.Checked
name = cwi.text(1)
command = cwi.text(2)
spec = self.get_spec(cwi)
geometry = cwi.applet_geometry
if geometry is not None:
geometry = bytes(geometry)
state.append(("applet", uid, enabled, name, command, geometry))
state.append(("applet", uid, enabled, name, spec, geometry))
elif cwi.ty == "group":
name = cwi.text(1)
expanded = cwi.isExpanded()
@@ -563,8 +612,11 @@ def save_state(self):
def restore_state_item(self, state, parent):
for wis in state:
if wis[0] == "applet":
_, uid, enabled, name, command, geometry = wis
item = self.new(uid, name, command, parent=parent)
_, uid, enabled, name, spec, geometry = wis
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:
geometry = QtCore.QByteArray(geometry)
item.applet_geometry = geometry