280 lines
10 KiB
Python
Executable File
280 lines
10 KiB
Python
Executable File
import fcntl, sys, os, time, logging
|
|
import signal, atexit
|
|
import subprocess
|
|
import contextlib
|
|
from threading import Thread
|
|
import argparse
|
|
import re # regexp
|
|
|
|
import i3_lemonbar_config as config
|
|
import i3_lemonbar_common as common
|
|
import i3_lemonbar_parser as lemonparser
|
|
import i3_workspaces as wspaces
|
|
|
|
p_conky_slow = None
|
|
p_conky_fast = None
|
|
p_lemonbar = None
|
|
i3_ws_obj = None
|
|
|
|
def assert_only_instance():
|
|
"""
|
|
If PID file exists:
|
|
Look for process with given PID
|
|
If found:
|
|
Exit program
|
|
If not found:
|
|
Delete PID file and continue
|
|
|
|
Look for fifo file
|
|
If exists:
|
|
Delete it
|
|
"""
|
|
|
|
try:
|
|
pid = None
|
|
with open(config.pid_file, 'r') as fp:
|
|
pid = int(fp.read())
|
|
except IOError:
|
|
common.logger.debug('Could not open PID file. Assuming non existent')
|
|
except ValueError:
|
|
common.logger.debug('''PID file contents broken''')
|
|
try:
|
|
os.remove(config.pid_file)
|
|
common.logger.debug('''Deleted old PID file, continuing as usual''')
|
|
except OSError:
|
|
common.logger.debug('''Failed deleting old PID file.''')
|
|
os._exit(1)
|
|
|
|
|
|
if pid is not None:
|
|
try:
|
|
common.logger.debug('''Found old PID file. Looking for owner''')
|
|
os.kill(pid, 0)
|
|
common.logger.debug('''Owner exists''')
|
|
common.logger.debug('''Failed, another instance of the launcher is running
|
|
(PID {})'''.format(pid))
|
|
os._exit(1)
|
|
except ProcessLookupError:
|
|
common.logger.debug('''Owner does not exist''')
|
|
try:
|
|
os.remove(config.pid_file)
|
|
common.logger.debug('''Deleted old PID file, continuing as usual''')
|
|
except OSError:
|
|
common.logger.debug('''Failed deleting old PID file.''')
|
|
os._exit(1)
|
|
|
|
with open(config.pid_file, 'w+') as fp:
|
|
fp.write('{:d}'.format(os.getpid()))
|
|
common.logger.debug('''Created and wrote to PID file''')
|
|
|
|
create_new_fifo(config.fifo_file_status)
|
|
create_new_fifo(config.fifo_file_executor)
|
|
|
|
def create_new_fifo(fifo_file):
|
|
"""
|
|
Create new fifo file, removing old one if it exists
|
|
"""
|
|
try:
|
|
os.remove(fifo_file)
|
|
common.logger.debug('''Removed old fifo file''')
|
|
except OSError:
|
|
common.logger.debug('''No old fifo file, good''')
|
|
|
|
try:
|
|
os.mkfifo(fifo_file)
|
|
except OSError:
|
|
common.logger.error('''Failed, couldn't create fifo file''')
|
|
os.remove(config.pid_file) # Clean up own PID file
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
def handle_exit(signum, frame):
|
|
common.logger.info('Signal handler called with signal {}'.format(signum))
|
|
common.logger.info('Calling os._exit(0)')
|
|
os._exit(0)
|
|
|
|
# Terminates process p nicely
|
|
def nice_term(p):
|
|
if p is not None:
|
|
p.terminate()
|
|
|
|
def nice_delete(f):
|
|
with contextlib.suppress(FileNotFoundError):
|
|
os.remove(f)
|
|
|
|
def clean_up():
|
|
common.logger.debug('Cleaning up')
|
|
nice_delete(config.pid_file)
|
|
nice_delete(config.fifo_file_status)
|
|
nice_delete(config.fifo_file_executor)
|
|
nice_term(p_conky_slow)
|
|
nice_term(p_conky_fast)
|
|
common.stuff_screen('kill')
|
|
if i3_ws_obj is not None:
|
|
i3_ws_obj.quit()
|
|
sys.exit(0)
|
|
|
|
def write_sys_status():
|
|
global p_conky_slow, p_conky_fast, i3_ws_obj
|
|
with open(config.fifo_file_status, 'w') as fifo:
|
|
p_conky_slow = subprocess.Popen(['conky', '-c', config.path+'conky_slow'], # Use communicate
|
|
stdout=fifo, stderr=fifo)
|
|
common.logger.debug('Started conky slow')
|
|
p_conky_fast = subprocess.Popen(['conky', '-c', config.path+'conky_fast'],
|
|
stdout=fifo, stderr=fifo)
|
|
common.logger.debug('Started conky fast')
|
|
i3_ws_obj = wspaces.i3ws(fifo_file=config.fifo_file_status)
|
|
i3_ws_obj.work()
|
|
|
|
def parse_status():
|
|
global p_lemonbar
|
|
with open(config.fifo_file_executor, 'w') as fifo_write:
|
|
p_lemonbar = subprocess.Popen(config.lemonbar_args
|
|
,stdin=subprocess.PIPE, stdout=fifo_write, text=True)
|
|
with open(config.fifo_file_status, 'r', buffering=1) as fifo_read:
|
|
# Let parser read from fifo
|
|
common.logger.debug("FIFO {} opened for reading".format(config.fifo_file_status))
|
|
lemonparser.parse_line('WSPINA1___main INA2___web FOC5___terms INA6___stats ') # TODO modular
|
|
while True:
|
|
try:
|
|
data = fifo_read.readline() # Blocking read
|
|
if len(data) == 0:
|
|
common.logger.debug("Writer closed")
|
|
break
|
|
psd = lemonparser.parse_line(data)
|
|
p_lemonbar.stdin.write(lemonparser.parse_line(data) + '\n')
|
|
p_lemonbar.stdin.flush()
|
|
#logger.debug('Read: "{0}"'.format(data))
|
|
#logger.debug('Parsed "{0}"'.format(psd))
|
|
except BrokenPipeError:
|
|
common.logger.debug('Broken pipe in parse status thread, exiting')
|
|
common.health_logger.info('Broken pipe in parse status thread, exiting')
|
|
clean_up()
|
|
except:
|
|
common.logger.debug('Unknown exception in parse status thread, exiting')
|
|
common.health_logger.info('Unknown exception in parse status thread, exiting')
|
|
clean_up()
|
|
|
|
def exec_commands():
|
|
with open(config.fifo_file_executor, 'r', buffering=1) as fifo_read:
|
|
common.logger.debug("FIFO {} opened for reading".format(config.fifo_file_executor))
|
|
while True:
|
|
try:
|
|
data = fifo_read.readline()
|
|
common.logger.debug('Trying reading: "{0}"'.format(data.strip('\n')))
|
|
try:
|
|
for key,func in common.commands_dict.items():
|
|
l = len(key)
|
|
if data[:l] == key:
|
|
func(data)
|
|
break
|
|
|
|
except:
|
|
common.logger.debug('Exception occured executing command\n Line in: {}\n Data: {}'.format(line_in, line_in[l:].split()))
|
|
if len(data) == 0:
|
|
common.logger.debug("Lemonbar output closed")
|
|
break
|
|
common.logger.debug('Read: "{0}"'.format(data.strip('\n')))
|
|
except BrokenPipeError:
|
|
common.logger.debug('Broken pipe in exec commands thread, exiting')
|
|
common.health_logger.info('Broken pipe in exec commands thread, exiting')
|
|
clean_up()
|
|
except:
|
|
common.health_logger.info('Unknown exception in exec commands thread, exiting')
|
|
raise
|
|
clean_up()
|
|
def strip_ansi_unicode(s):
|
|
# ANSI escape sequences are for colors in terminal and similar
|
|
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]')
|
|
strip_ansi = ansi_escape.sub('', s)
|
|
strip_unicode = (strip_ansi.encode('ascii', 'ignore')).decode('utf-8')
|
|
return strip_unicode
|
|
|
|
def user_screen():
|
|
# Create screen session, in UTF-8 mode, logging to fifo
|
|
create_new_fifo(config.fifo_screen_log)
|
|
subprocess.Popen(common.screen_args) # TODO lemonbar_user
|
|
with open(config.fifo_screen_log, 'r', buffering=1) as screen_read:
|
|
while True:
|
|
try:
|
|
line = screen_read.readline()
|
|
if not line:
|
|
break
|
|
common.logger.debug('Screen read line {}'.format(line.strip('\n')))
|
|
# with open(config.fifo_file_status, 'w') as fifo:
|
|
# clean_line = strip_ansi_unicode(line)
|
|
# if (not clean_line.startswith('[bluetooth]')
|
|
# and not clean_line.startswith('[CHG]')
|
|
# and not clean_line.isspace()):
|
|
# fifo.write('RESP [bluetoothctl] ' + clean_line)
|
|
except:
|
|
raise
|
|
|
|
nice_delete(config.fifo_screen_log)
|
|
|
|
class i3_thread:
|
|
""" Helper class to start and stop threads"""
|
|
all_threads = [] # Static, contains all started threads
|
|
|
|
def __init__(self, target, desc):
|
|
self.thread = Thread(target = self.__class__.run, args=(target,desc))
|
|
self.desc = desc
|
|
|
|
self.all_threads.append(self)
|
|
common.health_logger.info('"%s" starting', desc)
|
|
self.thread.start()
|
|
|
|
# Wrapper around the target
|
|
def run(target, desc):
|
|
target()
|
|
common.health_logger.info('"%s" reached end', desc)
|
|
|
|
def join_threads():
|
|
for i3_th in i3_thread.all_threads:
|
|
i3_th.thread.join()
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description='Blah blah blah.')
|
|
parser.add_argument('--debug', action='store_true')
|
|
args = parser.parse_args()
|
|
if(args.debug):
|
|
debuglvl = logging.DEBUG
|
|
else:
|
|
debuglvl = logging.INFO
|
|
|
|
# Setup logger to stdout
|
|
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
handler.setFormatter(formatter)
|
|
|
|
common.logger = logging.getLogger('Normal logger')
|
|
common.logger.setLevel(debuglvl)
|
|
common.logger.addHandler(handler)
|
|
|
|
# Setup health logger to file in tmp
|
|
formatter = logging.Formatter('%(asctime)s %(message)s')
|
|
handler = logging.FileHandler(config.health_file)
|
|
handler.setFormatter(formatter)
|
|
|
|
common.health_logger = logging.getLogger('Health logger')
|
|
common.health_logger.addHandler(handler)
|
|
|
|
# common.logger.basicConfig(stream=sys.stdout, level=debuglvl)
|
|
assert_only_instance() # Creates pid file and fifo file
|
|
atexit.register(clean_up)
|
|
signal.signal(signal.SIGTERM, handle_exit)
|
|
signal.signal(signal.SIGINT, handle_exit)
|
|
|
|
# Start writing and reading threads
|
|
# Create readers before writers
|
|
i3_thread(target = exec_commands, desc='Exec commands thread')
|
|
i3_thread(target = parse_status, desc='Parse status thread')
|
|
i3_thread(target = write_sys_status, desc='Write sys status thread')
|
|
i3_thread(target = user_screen, desc='Screen thread for user commands')
|
|
|
|
common.logger.debug('Threads started')
|
|
i3_thread.join_threads()
|
|
|
|
common.logger.debug('Reached end')
|