import fcntl, sys, os, time, logging import queue import signal, atexit import subprocess import contextlib from threading import Thread import argparse import i3_lemonbar_config as config import i3_lemonbar_common as common import i3_lemonbar_modules as modules import i3_lemonbar_parser as lemonparser p_conky_slow = None p_lemonbar = 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''') common.create_new_fifo(config.fifo_file_status) 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_term(p_conky_slow) modules.stop_all() sys.exit(0) def keep_fifo_open(): with open(config.fifo_file_status, 'w', buffering=1) as fifo_write: while True: fifo_write.write('HEARTBEAT\n') time.sleep(30) def put_fifo_in_queue(): 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)) while True: try: data = fifo_read.readline() # Blocking read if len(data) == 0: common.logger.debug("Writer closed") break data = data.rstrip() # Remove trailing newlines common.write_queue.put(lemonparser.parse_line(data)) 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 write_parsed(): # Write parse entries in queue to lemonbar global p_lemonbar p_lemonbar = subprocess.Popen(config.lemonbar_args, stdin=subprocess.PIPE , stdout=subprocess.PIPE, text=True) while True: data = common.write_queue.get() # Blocking read if data is None: common.logger.debug('Queue closed') common.health_logger.info('Queue closed') break p_lemonbar.stdin.write(data + '\n') p_lemonbar.stdin.flush() #common.logger.debug('Read: "{0}"'.format(data)) #common.logger.debug('Parsed "{0}"'.format(psd)) def exec_commands(): global p_lemonbar # Wait until up TODO better solution while True: if p_lemonbar is not None: break while True: data = p_lemonbar.stdout.readline() if not data: common.logger.debug('Lemonbar closed, exiting') common.health_logger.info('Lemonbar closed, exiting') clean_up() break common.logger.debug('Trying reading: "{0}"'.format(data.strip('\n'))) try: modules.do_action(data) except Exception as e: common.logger.debug('Exception occured executing command\n Line in: {}'.format(data)) common.logger.debug(str(e)) if len(data) == 0: common.logger.debug("Lemonbar output closed") break common.logger.debug('Read: "{0}"'.format(data.strip('\n'))) 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() 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) common.write_queue = queue.Queue() # Start writing and reading threads # Create readers before writers i3_thread(target = exec_commands, desc='Exec commands thread') i3_thread(target = write_parsed, desc='Write parsed status thread') i3_thread(target = keep_fifo_open, desc='') i3_thread(target = put_fifo_in_queue, desc='') modules.start_all() common.logger.debug('Threads started') i3_thread.join_threads() common.logger.debug('Reached end')