From ee01ab32079ed0e6b876b9f4be9a157b93f50db7 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 19 Jun 2021 17:23:50 +0100 Subject: [PATCH] V1.1 --- sound_stage/sound_stage | 365 ++++++++++++++++++++++++---------------- 1 file changed, 218 insertions(+), 147 deletions(-) diff --git a/sound_stage/sound_stage b/sound_stage/sound_stage index 8027cbd..8f76552 100755 --- a/sound_stage/sound_stage +++ b/sound_stage/sound_stage @@ -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 [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() -- 2.44.2