From 39add5548bb4c32721a0529f2cd08799c6419c4a Mon Sep 17 00:00:00 2001 From: Jakub Fojt Date: Wed, 22 Jan 2020 19:30:19 +0100 Subject: [PATCH] Create wrapper class that handles launching the lemonbar process and in and out threads --- .i3/lemonbar/i3_lemonbar_common.py | 2 - .i3/lemonbar/i3_lemonbar_launcher.py | 177 +++++++++++++-------------- .i3/lemonbar/i3_lemonbar_modules.py | 33 ++--- 3 files changed, 102 insertions(+), 110 deletions(-) diff --git a/.i3/lemonbar/i3_lemonbar_common.py b/.i3/lemonbar/i3_lemonbar_common.py index a6270fe..9f19185 100755 --- a/.i3/lemonbar/i3_lemonbar_common.py +++ b/.i3/lemonbar/i3_lemonbar_common.py @@ -11,8 +11,6 @@ kill_on_unfocus = [] logger = None health_logger = None -bar_in_shelf = None - # TODO bar mode can be moved to i3Module class bar_mode(Enum): power, normal, control = range(-1,2) # Don't cycle through power diff --git a/.i3/lemonbar/i3_lemonbar_launcher.py b/.i3/lemonbar/i3_lemonbar_launcher.py index 1b682c1..e35758e 100755 --- a/.i3/lemonbar/i3_lemonbar_launcher.py +++ b/.i3/lemonbar/i3_lemonbar_launcher.py @@ -11,9 +11,6 @@ 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: @@ -85,7 +82,6 @@ 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() os._exit(0) @@ -95,89 +91,98 @@ def keep_fifo_open(): 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)) +class LemonbarWrapper: - 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.bar_in_shelf.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 __init__(self, only_instance = True): + assert only_instance, 'Multiple instances not supported' # TODO + assert_only_instance() # Creates pid file and fifo file -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) + self.buffer_in = common.Shelf() + self.all_threads = [] - while True: - data = common.bar_in_shelf.get() # Blocking read - p_lemonbar.stdin.write(data + '\n') - p_lemonbar.stdin.flush() - #common.logger.debug('Read: "{0}"'.format(data)) - #common.logger.debug('Parsed "{0}"'.format(psd)) + # Start writing and reading threads + # Create readers before writers + self.p_lemonbar = subprocess.Popen(config.lemonbar_args, stdin=subprocess.PIPE + , stdout=subprocess.PIPE, text=True) + self.start_thread(target = self.exec_commands, desc='Execute commands thread') + self.start_thread(target = self.write_parsed, desc='Write parsed status thread') + self.start_thread(target = keep_fifo_open, desc='Keep fifo open thread') + self.start_thread(target = self.put_fifo_in_queue, desc='Put fifo entries in queue thread') + modules.start_all(self) -def exec_commands(): - global p_lemonbar + common.logger.debug('Threads started') - # 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 + # Helpers to start threads + def start_thread(self, target, desc): + # Wrapper around arbitrary target + def run_thread(target, desc): + target() + common.health_logger.info('"%s" reached end', desc) - common.logger.debug('Trying reading: "{0}"'.format(data.strip('\n'))) - try: - modules.do_action(data) + thread = Thread(target = run_thread, args=(target, desc)) - 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) + self.all_threads.append(thread) common.health_logger.info('"%s" starting', desc) - self.thread.start() + thread.start() - # Wrapper around the target - def run(target, desc): - target() - common.health_logger.info('"%s" reached end', desc) + def join(self): + for thread in self.all_threads: + thread.join() - def join_threads(): - for i3_th in i3_thread.all_threads: - i3_th.thread.join() + # Thread targets + def write_parsed(self): + # Write parse entries in queue to lemonbar + while True: + data = self.buffer_in.get() # Blocking read + self.p_lemonbar.stdin.write(data + '\n') + self.p_lemonbar.stdin.flush() + #common.logger.debug('Read: "{0}"'.format(data)) + #common.logger.debug('Parsed "{0}"'.format(psd)) + + def exec_commands(self): + while True: + data = self.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'))) + + def put_fifo_in_queue(self): + 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 + self.buffer_in.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 Exception as e: + common.logger.debug('Unknown exception in parse status thread, exiting') + common.health_logger.info('Unknown exception in parse status thread, exiting') + raise + common.logger.debug(str(e)) + clean_up() if __name__ == "__main__": parser = argparse.ArgumentParser() @@ -205,23 +210,11 @@ if __name__ == "__main__": common.health_logger = logging.getLogger('Health logger') common.health_logger.addHandler(handler) - # Make sure no other instance running and ensure clean up on exit - assert_only_instance() # Creates pid file and fifo file + # Ensure clean up on exit atexit.register(clean_up) signal.signal(signal.SIGTERM, handle_exit) signal.signal(signal.SIGINT, handle_exit) - common.bar_in_shelf = common.Shelf() - - # 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() - + # Start the lemonbar wrapper + LemonbarWrapper().join() common.logger.debug('Reached end') diff --git a/.i3/lemonbar/i3_lemonbar_modules.py b/.i3/lemonbar/i3_lemonbar_modules.py index 6e19c04..721e7c3 100644 --- a/.i3/lemonbar/i3_lemonbar_modules.py +++ b/.i3/lemonbar/i3_lemonbar_modules.py @@ -20,9 +20,10 @@ class LemonModule(threading.Thread): if isinstance(attr_instance, parser.LemonUnit): yield attr_instance - def __init__(self): + def __init__(self, lemonbar_wrapper): self.actions = {} super().__init__() + self.lemonbar_wrapper = lemonbar_wrapper self._start_module() for unit in self.units(): @@ -43,7 +44,7 @@ class LemonModule(threading.Thread): break parsed = self.parse(line) - common.bar_in_shelf.put(parsed) + self.lemonbar_wrapper.buffer_in.put(parsed) def stop(self): self._stop_module() @@ -87,8 +88,8 @@ def format_load(data, module, alert): class DateTimeModule(LemonModule): - def __init__(self): - super().__init__() + def __init__(self, lemonbar_wrapper): + super().__init__(lemonbar_wrapper) def _start_module(self): class DateTimeThread(threading.Thread): @@ -187,7 +188,7 @@ conky_end_body = """ ]]""" class ConkyFastModule(LemonModule): - def __init__(self): + def __init__(self, lemonbar_wrapper): self.conky_config = (conky_config(update_interval=1) + conky_begin_body + """\\ @@ -196,7 +197,7 @@ ${exec ~/.i3/lemonbar/get_vol.sh} """ (conky_net('wlp3s0') + conky_net('enp2s0'))) + conky_end_body) - super().__init__() + super().__init__(lemonbar_wrapper) def _start_module(self): self.p_handle = subprocess.Popen(['conky', '-c', '-'], @@ -260,7 +261,7 @@ ${exec ~/.i3/lemonbar/get_vol.sh} """ class ConkySlowModule(LemonModule): - def __init__(self): + def __init__(self, lemonbar_wrapper): self.conky_config = (conky_config() + conky_begin_body + """\\ @@ -275,7 +276,7 @@ ${exec brillo} """ if common.hostname() != 'kubaDesktop' else '') ${exec /home/kuba/.i3/scripts/lang.sh show} \\ ${exec ~/.i3/lemonbar/get_vol.sh}""" + conky_end_body) - super().__init__() + super().__init__(lemonbar_wrapper) def _start_module(self): self.p_handle = subprocess.Popen(['conky', '-c', '-'], @@ -369,8 +370,8 @@ class i3Module(LemonModule): # Handles outputs (displays), workspaces and active window # TODO trigger formatting - def __init__(self): - super().__init__() + def __init__(self, lemonbar_wrapper): + super().__init__(lemonbar_wrapper) self.displays = '' self.win_title = '' self.workspaces = '' @@ -555,8 +556,8 @@ class ScreenModule(LemonModule): # Start detached, in UTF-8 mode. Log to fifo start_flags = ['-d', '-m', '-U', '-L', '-Logfile', config.fifo_screen_log] - def __init__(self): - super().__init__() + def __init__(self, lemonbar_wrapper): + super().__init__(lemonbar_wrapper) def _start_module(self): self.identifier = 'lemonbar_{}_{}'.format(getpass.getuser(), os.getpid()) @@ -629,8 +630,8 @@ class ScreenModule(LemonModule): class PowerOptionsModule(LemonModule): - def __init__(self): - super().__init__() + def __init__(self, lemonbar_wrapper): + super().__init__(lemonbar_wrapper) def _start_module(self): # No external commands needed @@ -689,12 +690,12 @@ def do_action(keyword): for module in all_modules: module.do_action(keyword) -def start_all(): +def start_all(lemonbar_wrapper): global all_modules all_modules = [] for cls in get_active_modules(): - all_modules.append(cls()) + all_modules.append(cls(lemonbar_wrapper)) for module in all_modules: module.start()