Attaching Argodejo to a running nsmd GUI, including --load-session, should work now. Maybe some corner cases, but this is a good basis for testing. FYI There is a known bug with our own nsm-data client that needs further work.
self._queue=list()#Incoming OSC messages are buffered here.
self._queue=list()#Incoming OSC messages are buffered here.
#Status variables that are set by our callbacks
#Status variables that are set by our callbacks
@ -318,16 +326,7 @@ class NsmServerControl(object):
}
}
self.dataStorage=None#Becomes DataStorage() every time a datastorage client does a broadcast announce.
self.dataStorage=None#Becomes DataStorage() every time a datastorage client does a broadcast announce.
self._addToNextSession=[]#A list of executables in PATH. Filled by new, waits for reply that session is created and then will send clientNew and clear the list.
self._addToNextSession=[]#A list of executables in PATH. Filled by new, waits for reply that session is created and then will send clientNew and clear the list.
#Hooks for api callbacks
self.sessionOpenReadyHook=sessionOpenReadyHook#nsmSessionName as parameter
self.sessionOpenLoadingHook=sessionOpenLoadingHook#nsmSessionName as parameter
self.sessionClosedHook=sessionClosedHook#no parameter. This is also "choose a session" mode
self.clientStatusHook=clientStatusHook#all client status is done via this single hook. GUIs need to check if they already know the client or not.
self.singleInstanceActivateWindowHook=singleInstanceActivateWindowHook#added to self.processSingleInstance() to listen for a message from another wannabe-instance
ifuseCallbacks:
ifuseCallbacks:
self.callbacks={
self.callbacks={
@ -387,7 +386,7 @@ class NsmServerControl(object):
#No further action required. GUI announce below this testing.
#No further action required. GUI announce below this testing.
pass
pass
else:
else:
self._startNsmdOurselves(sessionRoot)#Session root can be a commandline parameter we forward to the server if we start it ourselves.
self._startNsmdOurselves(sessionRoot,startupSession)#Session root can be a commandline parameter we forward to the server if we start it ourselves. startupSession is an autoloader. Both are usually None.
self.singleInstanceActivateWindowHook=singleInstanceActivateWindowHook#added to self.processSingleInstance() to listen for a message from another wannabe-instance
self._receiverActive=True
self._receiverActive=True
logger.info("nsmservercontrol init is complete. Ready for event loop")
#Now an external event loop can add self.process
#Now an external event loop can add self.process
#Internal Methods
#Internal Methods
@ -448,7 +460,7 @@ class NsmServerControl(object):
"""Set both the socket and the thread into waiting mode or not.
"""Set both the socket and the thread into waiting mode or not.
raiseValueError("We were expecting a clean _queue with only 'hi' as leftover, but instead there were unhandled messages. see print above. Better abort than a wrong program state")
raiseValueError("We were expecting a clean _queue with only 'hi' as leftover, but instead there were unhandled messages. see print above. Better abort than a wrong program state")
#all ok
#all ok
returnpathlib.Path(resultArguments[0])
returnpathlib.Path(resultArguments[0])
@ -604,7 +662,7 @@ class NsmServerControl(object):
defgui_announce(self):
defgui_announce(self):
"""This is just the announce without any answer. This is a last-resort method if another GUI
"""This is just the announce without any answer. This is a last-resort method if another GUI
logger.warning(f"{executableName} must be just an executable file in your $PATH. We expected: {pathlib.Path(executableName).name} . We will not ask nsmd to add it as client")
logger.warning(f"{executableName} must be just an executable file in your $PATH. We expected: {pathlib.Path(executableName).name} . We will not ask nsmd to add it as client")
returnFalse
returnFalse
@ -790,7 +856,11 @@ class NsmServerControl(object):
defclientRemove(self,clientId:str):
defclientRemove(self,clientId:str):
"""Client needs to be stopped already. We will do that and wait for an answer.
"""Client needs to be stopped already. We will do that and wait for an answer.
@ -962,7 +1037,7 @@ class NsmServerControl(object):
nsmSessionName,sessionPath=parameters
nsmSessionName,sessionPath=parameters
ifnotnsmSessionNameandnotsessionPath:#No session loaded. We are in session-choosing mode.
ifnotnsmSessionNameandnotsessionPath:#No session loaded. We are in session-choosing mode.
logger.info("Session closed or never started. Choose-A-Session mode.")
logger.info("Session closed or never started. Choose-A-Session mode.")
self.internalState["currentSession"]=None
self.internalState["currentSession"]=None#sessionCloseHooked triggers rebuilding of the session list, which will not work when there is a current session.
self.sessionClosedHook()
self.sessionClosedHook()
else:
else:
sessionPath=sessionPath.lstrip("/")
sessionPath=sessionPath.lstrip("/")
@ -972,24 +1047,32 @@ class NsmServerControl(object):
#We have a counterpart-message reaction that signals the attempt to load.
#We have a counterpart-message reaction that signals the attempt to load.
self.sessionOpenReadyHook(self.sessionAsDict(nsmSessionName))#notify the api->UI
self.sessionOpenReadyHook(self.sessionAsDict(nsmSessionName))#notify the api->UI
self.internalState["currentSession"]=None#sessionCloseHooked triggers rebuilding of the session list, which will not work when there is a current session.
self.sessionClosedHook()
self.sessionClosedHook()
else:
else:
raiseNotImplementedError(parameters)
raiseNotImplementedError(parameters)
def_initializeEmptyClient(self,clientId:str):
def_initializeEmptyClient(self,clientId:str):
"""NSM reuses signals. It is quite possible that this will be called multiple times,
"""NSM reuses signals. It is quite possible that this will be called multiple times,
# logger.warning(f"We received a clientNew for ID {clientId} but no session open was received."
# "This would happen in an old nsmd version. If you see the GUI with an open session and a client list you can ignore this warning")
ifclientIdinself.internalState["clients"]:
ifclientIdinself.internalState["clients"]:
return
return
logger.info(f"Creating new internal entry for client {clientId}")
logger.info(f"Creating new internal entry for client {clientId}")
client={
client={
"clientId":clientId,#for convenience, included internally as well
"clientId":clientId,#for convenience, included internally as well
"executable":None,#For dumb clients this is the same as reportedName.
"dumbClient":True,#Bool. Real nsm or just any old program? status "Ready" switches this.
"dumbClient":True,#Bool. Real nsm or just any old program? status "Ready" switches this.
"executable":None,#Every client announces to the GUI with the exectuable name. True nsm clients later overwrite with a pretty name which we save as "reportedName"
"reportedName":None,#str . The reported name is first the executable name, for status started. But for NSM clients it gets replaced with a reported name.
"reportedName":None,#str . The reported name is first the executable name, for status started. But for NSM clients it gets replaced with a reported name.
"label":None,#str
"label":None,#str
"lastStatus":None,#str
"lastStatus":None,#str
@ -1010,7 +1093,7 @@ class NsmServerControl(object):
@ -1186,7 +1269,7 @@ class NsmServerControl(object):
afterstartbytheGUI.
afterstartbytheGUI.
"""
"""
pass
pass
def_reactStatus_save(self,clientId:str):
def_reactStatus_save(self,clientId:str):
"""
"""
@ -1376,11 +1459,11 @@ class NsmServerControl(object):
logger.warning(f"Can't rename {nsmSessionName} to {newName}. {newName} already exists.")
logger.warning(f"Can't rename {nsmSessionName} to {newName}. {newName} already exists.")
returnFalse
returnFalse
else:
else:
logger.info(f"Renaming {nsmSessionName} to {newName}.")
logger.info(f"Renaming {nsmSessionName} to {newName}.")
tmp=pathlib.Path(oldPath.name+str(uuid4()))#Can't move itself into a subdir in itself. move to temp first. We don't use tempdir because that could be on another partition. we already know we can write here.
tmp=pathlib.Path(oldPath.name+str(uuid4()))#Can't move itself into a subdir in itself. move to temp first. We don't use tempdir because that could be on another partition. we already know we can write here.
@ -1421,7 +1504,6 @@ class NsmServerControl(object):
ifnotsessionFile.exists():
ifnotsessionFile.exists():
#This is a reason to let the program exit.
#This is a reason to let the program exit.
print(nsmSessionName)
logger.error("Got wrong session directory from nsmd. Race condition after delete? In any case a breaking error (please report). Quitting. Project was: "+repr(sessionFile))
logger.error("Got wrong session directory from nsmd. Race condition after delete? In any case a breaking error (please report). Quitting. Project was: "+repr(sessionFile))
sysexit()#return None switch to return None to let it crash and see the python traceback
sysexit()#return None switch to return None to let it crash and see the python traceback
@ -1430,7 +1512,7 @@ class NsmServerControl(object):
entry["parents"]=basePath.relative_to(self.sessionRoot).parts[:-1]#tuple of each dir between NSM root and nsmSessionName/session.nsm, exluding the actual project name. This is the tree
entry["parents"]=basePath.relative_to(self.sessionRoot).parts[:-1]#tuple of each dir between NSM root and nsmSessionName/session.nsm, exluding the actual project name. This is the tree
@ -1440,7 +1522,10 @@ class NsmServerControl(object):
defexportSessionsAsDicts(self)->list:
defexportSessionsAsDicts(self)->list:
"""Return a list of dicts of projects with additional information:
"""Return a list of dicts of projects with additional information:
"""
"""
logger.info("Exporting sessions to dict. Will call blocking list sessions next")
results=[]
results=[]
#assert not self.internalState["currentSession"], self.internalState["currentSession"] #Do not request session list while in active session
parser.add_argument("-u","--url",action='store',dest="url",help="Force URL for the session. If there is already a running session we will connect to it. Otherwise we will start one there. Default is local host with random port. Example: osc.udp://myhost.localdomain:14294/")
parser.add_argument("-u","--url",action='store',dest="url",help="Force URL for the session. If there is already a running session we will connect to it. Otherwise we will start one there. Default is local host with random port. Example: osc.udp://myhost.localdomain:14294/")
parser.add_argument("--nsm-url",action='store',dest="url",help="Same as --url.")
parser.add_argument("--nsm-url",action='store',dest="url",help="Same as --url.")
parser.add_argument("-s","--session",action='store',dest="session",help="Session to open on startup. Overrides --continue")
parser.add_argument("-l","--load-session",action='store',dest="session",help="Session to open on startup, must exist. Overrides --continue")
parser.add_argument("-c","--continue",action='store_true',dest="continueLastSession",help="Autostart last active session.")
parser.add_argument("-c","--continue",action='store_true',dest="continueLastSession",help="Autostart last active session.")
parser.add_argument("-i","--hide",action='store_true',dest="starthidden",help="Start GUI hidden in tray, only if tray available on system.")
parser.add_argument("-i","--hide",action='store_true',dest="starthidden",help="Start GUI hidden in tray, only if tray available on system.")
parser.add_argument("--session-root",action='store',dest="sessionRoot",help="Root directory of all sessions. Defaults to '$HOME/NSM Sessions'")
parser.add_argument("--session-root",action='store',dest="sessionRoot",help="Root directory of all sessions. Defaults to '$HOME/NSM Sessions'")
QtGui.QIcon.setThemeName("hicolor")#audio applications can be found here. We have no need for other icons.
QtGui.QIcon.setThemeName("hicolor")#audio applications can be found here. We have no need for other icons.
logger.info("Init MainWindow")
logger.info("Init MainWindow")
#QtGui.QIcon.setFallbackThemeName("hicolor") #only one, not a list. This is the fallback if the theme can't be found. Not if icons can't be found in a theme.
#QtGui.QIcon.setFallbackThemeName("hicolor") #only one, not a list. This is the fallback if the theme can't be found. Not if icons can't be found in a theme.
self.setText(self.parentController.clientsTreeWidgetColumns.index("lastStatus"),QtCore.QCoreApplication.translate("OpenSession","(command not found)"))
classClientTable(object):
classClientTable(object):
"""Controls the QTreeWidget that holds loaded clients"""
"""Controls the QTreeWidget that holds loaded clients"""
@ -129,8 +124,8 @@ class ClientTable(object):
self.sortByColumn=0#by name
self.sortByColumn=0#by name
self.sortAscending=0# Qt::SortOrder which is 0 for ascending and 1 for descending
self.sortAscending=0# Qt::SortOrder which is 0 for ascending and 1 for descending
p.drawPixmap(2,2,overlayPixmap)#top left corner of icon, with some padding for the shadow
p.drawPixmap(2,2,overlayPixmap)#top left corner of icon, with some padding for the shadow
p.end()
p.end()
ico=QtGui.QIcon(pixmap)
ico=QtGui.QIcon(pixmap)
self.setIcon(ico)
self.setIcon(ico)
else:
else:
ifself.argodejoExecinself.parentController.mainWindow.programIcons:#there was a strange bug once where this happened exactly one, and then everything was fine, including this icon. Some DB backwards compatibility.
ifself.argodejoExecinself.parentController.mainWindow.programIcons:#there was a strange bug once where this happened exactly one, and then everything was fine, including this icon. Some DB backwards compatibility.