Added sound_stage
authorAndrew <andrew@liquid.me.uk>
Thu, 17 Jun 2021 14:08:18 +0000 (15:08 +0100)
committerAndrew <andrew@liquid.me.uk>
Thu, 17 Jun 2021 14:08:18 +0000 (15:08 +0100)
A deamon to deamonise and monitor multiple interdepedent programs and helper apps
Does very little error checking, might break.

sound_stage/sound_stage [new file with mode: 0755]

diff --git a/sound_stage/sound_stage b/sound_stage/sound_stage
new file mode 100755 (executable)
index 0000000..8027cbd
--- /dev/null
@@ -0,0 +1,425 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import psutil
+import signal
+from time import sleep
+from subprocess import Popen
+
+class gs:
+       #debug = 1 spawn and exit
+       #debug = 2 spawn waiting messages
+       #debug = 3 all messages
+       debug = 0
+
+class daemon:
+       def __init__(self):
+               self.basepath='/home/andrew/.soundstage/'
+               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
+
+       def start(self):
+               child=os.fork()
+               if child: return
+               self.writefile(self.pidfile,os.getpid())
+               signal.signal(signal.SIGINT, self.exit)
+               signal.signal(signal.SIGUSR1, self.exit)
+               signal.signal(signal.SIGTERM, self.exit)
+               while True:
+                       self.check_depends()
+                       self.spawn_daemon()
+                       self.monitor()
+                       self.cleanup()
+
+       def check_depends(self):
+               if gs.debug > 1 :print ('Checking depends for %s' %self.name)
+               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...':
+                                       break
+                               if gs.debug > 2: print ('%s not running yet...' %requirement)
+                               #sleep(.15)
+
+       def monitor_depends(self):
+               if gs.debug > 1 :print ('Montoring depends for %s' %self.name)
+               status=True
+               for requirement in self.depends:
+                       if self.readfile('%s%s.status'%(self.basepath,requirement)) != 'Running...':
+                               if gs.debug > 0 :print('we have a broken dependency %s'%requirement)
+                               status=False
+               return status
+
+       def spawn_daemon(self):
+               if gs.debug != 0 :print('Spawning %s Daemon...'%self.name)
+               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.run()
+                       #sleep(.5)
+               self.writefile(self.statusfile,'Running...')
+
+       def monitor(self):
+               if gs.debug != 0 :print('Running %s monitor...'%self.name)
+               while self.monitor_depends():
+                       status=self.child.poll()
+                       if gs.debug > 2 :print('%s status: %s' %(self.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)
+               self.writefile(self.statusfile,'Stopping...')
+               if self.child:
+                       self.child.terminate()
+                       if not self.child.poll():
+                               self.child.kill()
+                       self.child_log.close()
+               self.writefile(self.statusfile,'Stopped.')
+
+       def exit(self,sig,fan):
+               if gs.debug > 2 :print('%s caught exit signal...'%self.name)
+               #try:
+               self.cleanup()
+               os.remove(self.pidfile)
+               #except:
+               #       pass
+               sys.exit()
+
+       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']
+
+class qjackctl(daemon):
+       def __init__(self):
+               super().__init__()
+               self.arguments=['/usr/bin/qjackctl']
+               self.depends=['jackd','pulse']
+
+class pulse(daemon):
+       def __init__(self):
+               super().__init__()
+               self.arguments=['/usr/bin/pulseaudio','--fail']
+               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...')
+
+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.depends=['jackd']
+               self.postrun=['xdotool_1']
+
+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.depends=['jackd','jalv_1']
+               self.postrun=['xdotool_2']
+
+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.depends=['jackd','jalv_1','jalv_2']
+               self.postrun=['xdotool_3']
+
+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.depends=['jackd','jalv_1','jalv_2','jalv_3']
+               self.postrun=['xdotool_4','xdotool_5','xdotool_6']
+
+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.depends=['jackd']
+               
+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.depends=['jackd']
+               
+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.depends=['jackd']
+
+class kmix(daemon):
+       def __init__(self):
+               super().__init__()
+               self.arguments=['/usr/bin/kmix']
+               self.depends=['pulse']
+               self.postrun=['welcome']
+
+class conky(daemon):
+       def __init__(self):
+               super().__init__()
+               self.arguments=['/usr/bin/conky']
+               self.depends=['jalv_2']
+
+daemons={
+               '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  ,
+}
+class assist:
+       def __init__(self):
+               self.args=[]
+
+       def run(self):
+               return_code='none'
+               f=open('/dev/null','w')
+               while return_code != 0:
+                       child=Popen(self.args,stderr=f,stdout=f)
+                       child.wait()
+                       return_code=child.returncode
+               f.close()
+               return return_code
+
+class xdotool_1(assist):
+       def __init__(self):
+               self.args=['xdotool','search','--name','EQ4Q Stereo','set_window','--name','Front Speakers','windowmove','1920','0']
+
+class xdotool_2(assist):
+       def __init__(self):
+               self.args=['xdotool','search','--name','EQ4Q Stereo','set_window','--name','Aux Speakers','windowmove','2110','0']
+
+class xdotool_3(assist):
+       def __init__(self):
+               self.args=['xdotool','search','--name','EQ4Q Stereo','set_window','--name','Rear Speakers','windowmove','2300','0']
+
+class xdotool_4(assist):
+       def __init__(self):
+               self.args=['xdotool','search','--name','EQ10Q Stereo','set_window','--name','Headphones','windowmove','2324','0']
+
+class xdotool_5(assist):
+       def __init__(self):
+               pass
+
+       def run(self):
+               windows=['Headphones','Rear Speakers','Aux Speakers','Front Speakers']
+               for window in windows:
+                       Popen(['xdotool','search','--name',window,'windowactivate'])
+                       sleep(.125)
+                       #Popen(['xdotool','search','--name',window,'windowminimize'])
+
+class xdotool_6(assist):
+       def __init__(self):
+               sleep(.5)
+               self.args=['xdotool','set_desktop','0']
+
+class volumes_1(assist):
+       def __init__(self):
+               self.args=['/usr/bin/pactl','set-sink-volume','0','35%']
+
+class volumes_2(assist):
+       def __init__(self):
+               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']
+
+class welcome(assist):
+       def __init__(self):
+               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 tv_on(assist):# aka DoAsYouAreFuckingToldXorgServer
+       def __init__(self):
+               self.args=['/home/andrew/bin/TV-On']
+
+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,
+}
+
+def start():
+       for run_me in daemons:
+               current=daemons[run_me]()
+               current.start()
+
+def startsingle(run_me):
+       current=daemons[run_me]()
+       current.start()
+
+def stopsingle(stop_me):
+       try:
+               current=daemons[stop_me]()
+               pidfile=open(current.pidfile,'r')
+               pid=pidfile.read()
+               pidfile.close()
+               Popen(['kill','-SIGUSR1',pid])
+       except:
+               print('Error: Couldn\'t stop %s!' %stop_me)
+
+def stop():
+       for stop_me in daemons:
+               try:
+                       current=daemons[stop_me]()
+                       pidfile=open(current.pidfile,'r')
+                       pid=pidfile.read()
+                       pidfile.close()
+                       Popen(['kill','-SIGUSR1',pid])
+               except FileNotFoundError:
+                       pass
+       init()
+
+def status():
+       for how_am_i in daemons:
+               current=daemons[how_am_i]()
+               statusfile=open(current.statusfile,'r')
+               status=statusfile.read()
+               print('%s status: %s'%(current.__class__.__name__,status))
+
+def usage():
+       print('Usage: %s [start|stop|status|init]'%sys.argv[0])
+       sys.exit(1)
+       
+def advusage():
+       print('Advanced Usage: %s module [module name] [start|stop]'%sys.argv[0])
+       sys.exit(1)
+
+def init():
+       for init_me in daemons:
+               current=daemons[init_me]()
+               if not os.path.exists(current.basepath):
+                       os.mkdirs(current.basepath)
+               if os.path.exists(current.pidfile):
+                       file=open(current.pidfile,'r')
+                       pid=file.read()
+                       file.close()
+                       os.remove(current.pidfile)
+                       try:
+                               os.kill(int(pid),signal.SIGKILL)
+                       except:
+                               pass
+               current.writefile(current.statusfile,'Stopped.')
+
+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=='help':
+       usage()
+elif arg=='start':
+       start()
+elif arg=='module':
+       if action=='start':
+               startsingle(module)
+       elif action=='stop':
+               stopsingle(module)
+       elif action=='restart':
+               stopsingle(module)
+               sleep(5)
+               startsingle(module)
+       else:
+               advusage()
+elif arg=='stop':
+       stop()
+elif arg=='status':
+       status()
+elif arg=='restart':
+       stop()
+       sleep(5)
+       start()
+elif arg=='init':
+       init()
+else:
+       usage()