V1.1
authorAndrew <andrew@liquid.me.uk>
Sat, 19 Jun 2021 16:23:50 +0000 (17:23 +0100)
committerAndrew <andrew@liquid.me.uk>
Sat, 19 Jun 2021 16:23:50 +0000 (17:23 +0100)
sound_stage/sound_stage

index 8027cbd407860e3830f563618288215ceaaa70cc..8f76552f1ccf45874b3f3e130a8e40b264d0dc71 100755 (executable)
@@ -1,32 +1,61 @@
 #!/usr/bin/env python
-
+#version 1.1, with some sanity checking and better 'initial' cleanup of existing instances
 import os
 import sys
 import psutil
 import signal
 from time import sleep
-from subprocess import Popen
+from subprocess import run, Popen, TimeoutExpired
+from getpass import getuser
 
 class gs:
        #debug = 1 spawn and exit
-       #debug = 2 spawn waiting messages
+       #debug = 2 spawn-waiting messages
        #debug = 3 all messages
+       #debug = 4 too many messages
        debug = 0
+       curdt = 0
+       rtprio={
+               'jackd'                 :41,
+               'alsa_out_1'    :41,
+               'alsa_in_1'             :41,
+               'jalv_1'                :31,
+               'jalv_2'                :31,
+               'jalv_3'                :31,
+               'jalv_4'                :31,
+               'jalv_5'                :31,
+               'alsa_out_2'    :21,
+               'alsa_out_3'    :21,
+               'alsa_out_4'    :21,
+               'pulseaudio'    :21,
+               'qjackctl'              :0,
+               'kmix'                  :0,
+               'conky'                 :0,
+               }
 
 class daemon:
        def __init__(self):
-               self.basepath='/home/andrew/.soundstage/'
+               self.basepath='/home/%s/.soundstage/'%getuser()
                self.pidfile='%s%s.pid'%(self.basepath,self.__class__.__name__)
                self.statusfile='%s%s.status'%(self.basepath,self.__class__.__name__)
                self.logfile='%s%s.log'%(self.basepath,self.__class__.__name__)
-               self.arguments=[]
-               self.depends=[]
-               self.postrun=[]
-               self.child=None
+               self.arguments=self.depends=self.postrun=[]
+               self.child=self.hardclean=None
 
        def start(self):
-               child=os.fork()
-               if child: return
+               try:
+                       oops=open(self.pidfile,'r')
+                       pid=oops.read()
+                       oops.close()
+                       child=psutil.Process(int(pid))
+                       child.terminate()
+                       try:
+                               child.wait(timeout=3)
+                       except psutil.Timeout:
+                               child.kill()
+               except:
+                       pass
+               if os.fork():return
                self.writefile(self.pidfile,os.getpid())
                signal.signal(signal.SIGINT, self.exit)
                signal.signal(signal.SIGUSR1, self.exit)
@@ -38,285 +67,310 @@ class daemon:
                        self.cleanup()
 
        def check_depends(self):
-               if gs.debug > 1 :print ('Checking depends for %s' %self.name)
+               if gs.debug > 1 :print ('Checking depends for %s:%s'%(self.__class__.__name__,self.depends))
+
                for requirement in self.depends:
-                       while self.depends:
-                               if gs.debug > 2 :print ('checking for %s%s' %(self.basepath,requirement))
-                               if self.readfile('%s%s.status'%(self.basepath,requirement)) == 'Running...':
+                       while True:
+                               if gs.debug > 2 :print ('Checking %s%s for %s ... ' %(self.basepath,requirement,self.__class__.__name__),end='',flush=True)
+                               depstatus=self.readfile('%s%s.status'%(self.basepath,requirement))
+                               if gs.debug > 2 :print (depstatus)
+                               if depstatus == 'Running...':
                                        break
-                               if gs.debug > 2: print ('%s not running yet...' %requirement)
-                               #sleep(.15)
+                               if gs.debug > 2: print ('%s not yet running for %s...'%(requirement,self.__class__.__name__))
+
 
        def monitor_depends(self):
-               if gs.debug > 1 :print ('Montoring depends for %s' %self.name)
                status=True
                for requirement in self.depends:
+                       if gs.debug > 1 :print ('Montoring dep %s for %s' %(requirement,self.__class__.__name__))
                        if self.readfile('%s%s.status'%(self.basepath,requirement)) != 'Running...':
-                               if gs.debug > 0 :print('we have a broken dependency %s'%requirement)
+                               if gs.debug > 0 :print('%s has a broken dependency %s'%(self.__class__.__name__,requirement))
                                status=False
                return status
 
        def spawn_daemon(self):
-               if gs.debug != 0 :print('Spawning %s Daemon...'%self.name)
+               if gs.debug != 0 :print('Spawning %s Daemon...'%self.__class__.__name__)
+               self.killcheck()
                self.child_log=open(self.logfile,'w')
                self.child=Popen(self.arguments, stderr=self.child_log, stdout=self.child_log)
-               #sleep(.5)
                for run_me in self.postrun:
                        if gs.debug > 1:print('Running assist: %s'%run_me)
-                       assistant=assists[run_me]()
+                       assistant=assists[run_me](gs.rtprio[self.__class__.__name__],self.child.pid)
                        assistant.run()
-                       #sleep(.5)
                self.writefile(self.statusfile,'Running...')
+               if gs.debug > 0:print('%s started with pid:%s'%(psutil.Process(self.child.pid).name(),self.child.pid))
 
        def monitor(self):
-               if gs.debug != 0 :print('Running %s monitor...'%self.name)
+               if gs.debug != 0 :print('Running %s monitor...'%self.__class__.__name__)
                while self.monitor_depends():
                        status=self.child.poll()
-                       if gs.debug > 2 :print('%s status: %s' %(self.name, status))
+                       if gs.debug > 3 :print('%s child status: %s' %(self.__class__.__name__, status))
                        if status==None and self.monitor_depends():
                                sleep(5)
                        else:
                                break
 
        def cleanup(self):
-               if gs.debug != 0 :print('Running %s cleanup...'%self.name)
+               if gs.debug != 0 :print('Running %s cleanup...'%self.__class__.__name__)
                self.writefile(self.statusfile,'Stopping...')
                if self.child:
                        self.child.terminate()
-                       if not self.child.poll():
+                       try:
+                               self.child.wait(timeout=10)
+                       except TimeoutExpired:
                                self.child.kill()
                        self.child_log.close()
+               self.killcheck()
                self.writefile(self.statusfile,'Stopped.')
 
-       def exit(self,sig,fan):
-               if gs.debug > 2 :print('%s caught exit signal...'%self.name)
-               #try:
+       def exit(self,sig,frame):
+               if gs.debug > 2 :print('%s caught exit signal...'%self.__class__.__name__)
                self.cleanup()
                os.remove(self.pidfile)
-               #except:
-               #       pass
-               sys.exit()
+               sys.exit(0)
+
+       def killcheck(self):
+               username=getuser()
+               if self.arguments[0]=='/usr/bin/chrt':
+                       args=self.arguments[2:]
+               else:
+                       args=self.arguments
+               for proc in psutil.process_iter():
+                       if username==proc.username() and(args==proc.cmdline() or(self.__class__.__name__==proc.name() and self.hardclean)):
+                               proc.terminate()
+                               try:
+                                       proc.wait(timeout=3)
+                               except psutil.TimeoutExpired:
+                                       proc.kill()
 
        def readfile(self,filename):
                data=''
-               #try:
                with open(filename, 'r') as file:
                        data=file.read()
-               #except:
-               #       pass
                return data
 
        def writefile(self,filename,data):
-               #try:
                with open(filename, 'w') as file:
                        result=file.write(str(data))
-               #except:
-               #       pass
                return result
 
 class jackd(daemon):
        def __init__(self):
                super().__init__()
-               self.arguments=['/usr/bin/chrt','89','/usr/bin/jackd','-dalsa','-dhw:Generic','-r48000','-p1024','-n2']
-               self.postrun=['volumes_3','tv_on']
+               self.hardclean=True
+               self.arguments=['/usr/bin/jackd','-R','-P41','-dalsa','-Chw:1,0','-Phw:1,0','-r48000','-p1024','-n2']
+#              self.arguments=['/usr/bin/chrt','55','/usr/bin/jackd','-ddummy','-C0','-P0','-r48000','-p1024']
+               self.postrun=['volumes_3','tv_on','chrt']
 
 class qjackctl(daemon):
        def __init__(self):
                super().__init__()
+               self.hardclean=True
                self.arguments=['/usr/bin/qjackctl']
-               self.depends=['jackd','pulse']
+               self.depends=['jackd']
 
-class pulse(daemon):
+class pulseaudio(daemon):
        def __init__(self):
                super().__init__()
-               self.arguments=['/usr/bin/pulseaudio','--fail']
+               self.hardclean=True
+               self.arguments=['/usr/bin/pulseaudio']
                self.depends=['jackd']
-               self.postrun=['volumes_1','chrt','volumes_2']
-               
-       def killall(self):
-               for proc in psutil.process_iter():
-                       if proc.name()=='pulseaudio':
-                               os.kill(int(proc.pid),signal.SIGKILL)
-                               
-       def spawn_daemon(self):
-               if gs.debug != 0 :print('Spawning %s Daemon...'%self.name)
-               self.killall()
-               self.child_log=open(self.logfile,'w')
-               self.child=Popen(self.arguments, stderr=self.child_log, stdout=self.child_log)
-               #sleep(.5)
-               for run_me in self.postrun:
-                       if gs.debug > 1:print('Running assist: %s'%run_me)
-                       if run_me=='chrt':
-                               assistant=assists[run_me](self.child.pid)
-                       else:
-                               assistant=assists[run_me]()
-                       assistant.run()
-                       #sleep(.5)
-               self.writefile(self.statusfile,'Running...')
+               self.postrun=['volumes_1','volumes_2'] #,'chrt']
 
 class jalv_1(daemon):
        def __init__(self):
                super().__init__()
-               self.arguments=['/usr/bin/chrt','84','/usr/bin/jalv.gtk','-l','/home/andrew/.eq_files/FrontSpeakers/','--jack-name','EQ Front']
+               self.arguments=['/usr/bin/jalv.gtk','-l','/home/%s/.eq_files/FrontSpeakers/'%getuser(),'--jack-name','EQ Front']
                self.depends=['jackd']
-               self.postrun=['xdotool_1']
+               self.postrun=['xdotool_1','chrt']
 
 class jalv_2(daemon):
        def __init__(self):
                super().__init__()
-               self.arguments=['/usr/bin/chrt','84','/usr/bin/jalv.gtk','-l','/home/andrew/.eq_files/AuxSpeakers/','--jack-name','EQ Centre']
+               self.arguments=['/usr/bin/jalv.gtk','-l','/home/%s/.eq_files/AuxSpeakers/'%getuser(),'--jack-name','EQ Centre']
                self.depends=['jackd','jalv_1']
-               self.postrun=['xdotool_2']
+               self.postrun=['xdotool_2','chrt']
 
 class jalv_3(daemon):
        def __init__(self):
                super().__init__()
-               self.arguments=['/usr/bin/chrt','84','/usr/bin/jalv.gtk','-l','/home/andrew/.eq_files/RearSpeakers/','--jack-name','EQ Rear']
+               self.arguments=['/usr/bin/jalv.gtk','-l','/home/%s/.eq_files/RearSpeakers/'%getuser(),'--jack-name','EQ Rear']
                self.depends=['jackd','jalv_1','jalv_2']
-               self.postrun=['xdotool_3']
+               self.postrun=['xdotool_3','chrt']
 
 class jalv_4(daemon):
        def __init__(self):
                super().__init__()
-               self.arguments=['/usr/bin/chrt','84','/usr/bin/jalv.gtk','-l','/home/andrew/.eq_files/Headphones/','--jack-name','EQ Headphones']
+               self.arguments=['/usr/bin/jalv.gtk','-l','/home/%s/.eq_files/Headphones/'%getuser(),'--jack-name','EQ Headphones']
                self.depends=['jackd','jalv_1','jalv_2','jalv_3']
-               self.postrun=['xdotool_4','xdotool_5','xdotool_6']
+               self.postrun=['xdotool_4','chrt']
+
+class jalv_5(daemon):
+       def __init__(self):
+               super().__init__()
+               self.arguments=['/usr/bin/jalv.gtk','-l','/home/%s/.eq_files/Microphone/'%getuser(),'--jack-name','EQ Capture']
+               self.depends=['jackd','jalv_1','jalv_2','jalv_3','jalv_4']
+               self.postrun=['xdotool_5','xdotool_6','xdotool_7','chrt']
 
 class alsa_out_1(daemon):
        def __init__(self):
                super().__init__()
-               self.arguments=['/usr/bin/chrt','84','/usr/bin/alsa_out','-j','HDMI_Audio_TV','-d','hw:0,11','-c','2']
+               self.arguments=['/usr/bin/alsa_out','-j','ALSA Speakers','-d','hw:1,0','-c','8']
                self.depends=['jackd']
-               
+               self.postrun=['chrt']
+
 class alsa_out_2(daemon):
        def __init__(self):
                super().__init__()
-               self.arguments=['/usr/bin/chrt','84','/usr/bin/alsa_out','-j','HDMI_Left_Monitor','-d','hw:0,7','-c','2']
+               self.arguments=['/usr/bin/alsa_out','-j','HDMI_Audio_TV','-d','hw:0,11','-c','2']
                self.depends=['jackd']
-               
+               self.postrun=['chrt']
+
 class alsa_out_3(daemon):
        def __init__(self):
                super().__init__()
-               self.arguments=['/usr/bin/chrt','84','/usr/bin/alsa_out','-j','HDMI_Right_Monitor','-d','hw:0,10','-c','2']
+               self.arguments=['/usr/bin/alsa_out','-j','HDMI_Left_Monitor','-d','hw:0,7','-c','2']
+               self.depends=['jackd']
+               self.postrun=['chrt']
+
+class alsa_out_4(daemon):
+       def __init__(self):
+               super().__init__()
+               self.arguments=['/usr/bin/alsa_out','-j','HDMI_Right_Monitor','-d','hw:0,10','-c','2']
+               self.depends=['jackd']
+               self.postrun=['chrt']
+
+class alsa_in_1(daemon):
+       def __init__(self):
+               super().__init__()
+               self.arguments=['/usr/bin/alsa_in','-j','ALSA Capture 1','-d','hw:1,0','-c','2']
                self.depends=['jackd']
+               self.postrun=['chrt']
 
 class kmix(daemon):
        def __init__(self):
                super().__init__()
+               self.hardclean=True
                self.arguments=['/usr/bin/kmix']
-               self.depends=['pulse']
+               self.depends=['pulseaudio']
                self.postrun=['welcome']
 
 class conky(daemon):
        def __init__(self):
                super().__init__()
+               self.hardclean=True
                self.arguments=['/usr/bin/conky']
                self.depends=['jalv_2']
 
 daemons={
-               'jackd'                                 :jackd ,
+               'jackd'                                 :jackd,
                'qjackctl'                              :qjackctl,
-               'pulse'                                 :pulse ,
-               'jalv_1'                                :jalv_1 ,
-               'jalv_2'                                :jalv_2 ,
-               'jalv_3'                                :jalv_3 ,
-               'jalv_4'                                :jalv_4 ,
-               'alsa_out_1'                    :alsa_out_1 ,
-               'alsa_out_2'                    :alsa_out_2 ,
-               'alsa_out_3'                    :alsa_out_3 ,
-               'kmix'                                  :kmix   ,
-               'conky'                                 :conky  ,
-}
+               'pulseaudio'                    :pulseaudio,
+               'jalv_1'                                :jalv_1,
+               'jalv_2'                                :jalv_2,
+               'jalv_3'                                :jalv_3,
+               'jalv_4'                                :jalv_4,
+               'jalv_5'                                :jalv_5,
+#              'alsa_out_1'                    :alsa_out_1,
+               'alsa_out_2'                    :alsa_out_2,
+               'alsa_out_3'                    :alsa_out_3,
+               'alsa_out_4'                    :alsa_out_4,
+#              'alsa_in_1'                             :alsa_in_1,
+               'kmix'                                  :kmix,
+               'conky'                                 :conky,
+               }
+
 class assist:
-       def __init__(self):
+       def __init__(self,*arg):
                self.args=[]
 
        def run(self):
                return_code='none'
                f=open('/dev/null','w')
-               while return_code != 0:
+               tries=10
+               while return_code != 0 and tries > 0 :
+                       if tries < 10:
+                               if gs.debug >2:print('%s didn\'t start, retrying'%' '.join(self.args))
+                               sleep(.375)
                        child=Popen(self.args,stderr=f,stdout=f)
                        child.wait()
                        return_code=child.returncode
+                       tries-=1
                f.close()
                return return_code
 
 class xdotool_1(assist):
-       def __init__(self):
+       def __init__(self,*arg):
                self.args=['xdotool','search','--name','EQ4Q Stereo','set_window','--name','Front Speakers','windowmove','1920','0']
 
 class xdotool_2(assist):
-       def __init__(self):
+       def __init__(self,*arg):
                self.args=['xdotool','search','--name','EQ4Q Stereo','set_window','--name','Aux Speakers','windowmove','2110','0']
 
 class xdotool_3(assist):
-       def __init__(self):
+       def __init__(self,*arg):
                self.args=['xdotool','search','--name','EQ4Q Stereo','set_window','--name','Rear Speakers','windowmove','2300','0']
 
 class xdotool_4(assist):
-       def __init__(self):
+       def __init__(self,*arg):
                self.args=['xdotool','search','--name','EQ10Q Stereo','set_window','--name','Headphones','windowmove','2324','0']
 
 class xdotool_5(assist):
-       def __init__(self):
-               pass
+       def __init__(self,*arg):
+               self.args=['xdotool','search','--name','EQ10Q Stereo','set_window','--name','Microphone','windowmove','2514','0']
 
+class xdotool_6(assist):
        def run(self):
-               windows=['Headphones','Rear Speakers','Aux Speakers','Front Speakers']
+               windows=['Microphone','Headphones','Rear Speakers','Aux Speakers','Front Speakers']
                for window in windows:
+                       sleep(.125)
                        Popen(['xdotool','search','--name',window,'windowactivate'])
                        sleep(.125)
                        #Popen(['xdotool','search','--name',window,'windowminimize'])
 
-class xdotool_6(assist):
-       def __init__(self):
+class xdotool_7(assist):
+       def __init__(self,*arg):
                sleep(.5)
-               self.args=['xdotool','set_desktop','0']
+               self.args=['xdotool','set_desktop',gs.curdt]
 
 class volumes_1(assist):
-       def __init__(self):
-               self.args=['/usr/bin/pactl','set-sink-volume','0','35%']
+       def __init__(self,*arg):
+               self.args=['/usr/bin/pactl','set-sink-volume','0','45%']
 
 class volumes_2(assist):
-       def __init__(self):
+       def __init__(self,*arg):
                self.args=['/usr/bin/pactl','set-sink-volume','2','100%']
 
 class volumes_3(assist):
-       def __init__(self):
-               self.args=['/usr/sbin/alsactl','restore','-f','/home/andrew/.asound.state']
+       def __init__(self,*arg):
+               self.args=['/usr/sbin/alsactl','restore','-f','/home/%s/.asound.state'%getuser()]
 
 class welcome(assist):
-       def __init__(self):
+       def __init__(self,*arg):
                self.args=['/usr/bin/ogg123','/usr/share/sounds/Oxygen-Sys-Log-In-Long.ogg']
-                       
-class DoAsYouAreFuckingToldPulseaudio(assist):
-       def __init__(self,pid):
-               self.args=['/usr/bin/chrt','--pid','75',str(pid)]
 
-       def run(self):
-               f=open('/dev/null','w')
-               child=Popen(self.args, stdout=f,stderr=f)
-               child.wait()
-               f.close()
-               return child.poll()
+class chrt(assist):
+       def __init__(self,*arg):
+               self.args=['/usr/bin/chrt','--pid',str(arg[0]),str(arg[1])]
 
-class tv_on(assist):# aka DoAsYouAreFuckingToldXorgServer
-       def __init__(self):
-               self.args=['/home/andrew/bin/TV-On']
+class tv_on(assist):
+       def __init__(self,*arg):
+               self.args=['/home/%s/bin/TV-On'%getuser()]
 
 assists={
-               'xdotool_1'                             :xdotool_1 ,
-               'xdotool_2'                             :xdotool_2 ,
-               'xdotool_3'                             :xdotool_3 ,
-               'xdotool_4'                             :xdotool_4 ,
-               'xdotool_5'                             :xdotool_5 ,
-               'xdotool_6'                             :xdotool_6 ,
-               'volumes_1'                             :volumes_1 ,
-               'volumes_2'                             :volumes_2 ,
-               'volumes_3'             :volumes_3 ,
-               'welcome'                               :welcome   ,
-               'chrt'                                  :DoAsYouAreFuckingToldPulseaudio,
-               'tv_on'                                 :tv_on,
+               'xdotool_1'             :xdotool_1,
+               'xdotool_2'             :xdotool_2,
+               'xdotool_3'             :xdotool_3,
+               'xdotool_4'             :xdotool_4,
+               'xdotool_5'             :xdotool_5,
+               'xdotool_6'             :xdotool_6,
+               'xdotool_7'             :xdotool_7,
+               'volumes_1'             :volumes_1,
+               'volumes_2'             :volumes_2,
+               'volumes_3'             :volumes_3,
+               'welcome'               :welcome,
+               'chrt'                  :chrt,
+               'tv_on'                 :tv_on,
 }
 
 def start():
@@ -338,6 +392,13 @@ def stopsingle(stop_me):
        except:
                print('Error: Couldn\'t stop %s!' %stop_me)
 
+def statussingle(how_am_i):
+               current=daemons[how_am_i]()
+               try:
+                       print('%s status: %s'%(how_am_i,current.readfile(current.statusfile)))
+               except:
+                       print('%s status: Unknown'%how_am_i)
+
 def stop():
        for stop_me in daemons:
                try:
@@ -345,7 +406,7 @@ def stop():
                        pidfile=open(current.pidfile,'r')
                        pid=pidfile.read()
                        pidfile.close()
-                       Popen(['kill','-SIGUSR1',pid])
+                       os.kill(int(pid),signal.SIGUSR1)
                except FileNotFoundError:
                        pass
        init()
@@ -358,11 +419,11 @@ def status():
                print('%s status: %s'%(current.__class__.__name__,status))
 
 def usage():
-       print('Usage: %s [start|stop|status|init]'%sys.argv[0])
+       print('Usage: %s <module name> [start|stop|status|init|restart]'%sys.argv[0])
        sys.exit(1)
-       
+
 def advusage():
-       print('Advanced Usage: %s module [module name] [start|stop]'%sys.argv[0])
+       print('Advanced Usage: %s [module name] [start|stop|status|restart]'%sys.argv[0])
        sys.exit(1)
 
 def init():
@@ -381,20 +442,26 @@ def init():
                                pass
                current.writefile(current.statusfile,'Stopped.')
 
+def killall():
+       stop()
+       for kill_me in daemons:
+               current=daemons[kill_me]()
+               current.hardclean=True
+               current.killcheck()
+
+gs.curdt=run(['xdotool','get_desktop'],capture_output=True,encoding='UTF-8').stdout[0]
 try:
        arg=sys.argv[1]
 except IndexError:
        usage()
 
-try:
-       module=sys.argv[2]
-except IndexError:
-       pass
-
-try:
-       action=sys.argv[3]
-except IndexError:
-       action='start'
+if arg in daemons:
+       module=arg
+       arg='module'
+       try:
+               action=sys.argv[2]
+       except IndexError:
+               advusage()
 
 if arg=='help':
        usage()
@@ -409,6 +476,8 @@ elif arg=='module':
                stopsingle(module)
                sleep(5)
                startsingle(module)
+       elif action=='status':
+               statussingle(module)
        else:
                advusage()
 elif arg=='stop':
@@ -421,5 +490,7 @@ elif arg=='restart':
        start()
 elif arg=='init':
        init()
+elif arg=='killall':
+       killall()
 else:
        usage()