From: Andrew Date: Thu, 17 Jun 2021 14:08:18 +0000 (+0100) Subject: Added sound_stage X-Git-Url: https://git.liquid.me.uk/?a=commitdiff_plain;h=f47e2f569e8e0fae5dd82f32da847a0095c00b15;p=stuff.git Added sound_stage A deamon to deamonise and monitor multiple interdepedent programs and helper apps Does very little error checking, might break. --- diff --git a/sound_stage/sound_stage b/sound_stage/sound_stage new file mode 100755 index 0000000..8027cbd --- /dev/null +++ b/sound_stage/sound_stage @@ -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()