From 0868cf101e6729a1c29336d9204e32a630af096f Mon Sep 17 00:00:00 2001 From: kuben Date: Tue, 27 Aug 2019 22:25:04 +0200 Subject: [PATCH] lemonbar: Move all lemonbar parsing to units. Remove i3 workspaces dependency on lemonbar. Implement sorting and alignment of modules --- .i3/lemonbar/i3_lemonbar_common.py | 55 ----- .i3/lemonbar/i3_lemonbar_launcher.py | 17 +- .i3/lemonbar/i3_lemonbar_modules.py | 289 ++++++++++++++++++++--- .i3/lemonbar/i3_lemonbar_parser.py | 329 +++++++++++---------------- .i3/lemonbar/i3_workspaces.py | 51 ++--- 5 files changed, 419 insertions(+), 322 deletions(-) diff --git a/.i3/lemonbar/i3_lemonbar_common.py b/.i3/lemonbar/i3_lemonbar_common.py index e146fe3..d394b0c 100755 --- a/.i3/lemonbar/i3_lemonbar_common.py +++ b/.i3/lemonbar/i3_lemonbar_common.py @@ -106,61 +106,6 @@ def strip_ansi_unicode(s): strip_unicode = (strip_ansi.encode('ascii', 'ignore')).decode('utf-8') return strip_unicode -# Callbacks for workspaces -def add_callbacks(i3ws): #TODO this should be done by module - i3ws.change_callbacks.append(set_bg) - i3ws.focus_callbacks.append(set_keymap) - i3ws.focus_callbacks.append(kill_floating_windows) - -def img_path(num): - dir = '/home/kuba/Obrazy/Wallpapers/' - return dir + { - 1: '1_main', - 2: '2_web', - 3: '3_music', - 4: '4_work', - 5: '5_terms', - 6: '6_stats', - 7: '7', - 8: '8', - 9: '9', - }.get(int(num), 'default') - -def set_bg(i3ws): - cmd_args = ['sh', '/home/kuba/scripts/set_bg.sh'] - for output in i3ws.outputs: - if output.active: - bg = img_path(output.current_workspace.partition(' ')[0]) - cmd_args.append(bg) - subprocess.call(cmd_args) - -def kill_floating_windows(i3ws): - global kill_on_unfocus, logger - role = i3ws.focused_window.window_role - wclass = i3ws.focused_window.window_class - - if role != 'FLOAT_TERM' and wclass != 'FLOAT_PAVU' and wclass != 'YADWINBR': - # Is there a window that the bar has opened? - for pid in kill_on_unfocus: - try: - os.kill(pid, signal.SIGTERM) - except ProcessLookupError: - logger.debug('Tried killing process {} but it doesn\'t exist'.format(pid)) - kill_on_unfocus = [] - -def set_keymap(i3ws): - global cur_class, keymaps, logger - wclass = i3ws.focused_window.window_class - cur_class = wclass - - if wclass in keymaps: - new_km = keymaps[wclass] - logger.debug('Setting {} as keymap for {}'.format(new_km, wclass)) - else: - new_km = def_keymap - logger.debug('Setting default keymap {} for {}'.format(def_keymap, wclass)) - subprocess.call(['/home/kuba/.i3/scripts/lang.sh', 'qset', new_km]) - def create_new_fifo(fifo_file): """ Create new fifo file, removing old one if it exists diff --git a/.i3/lemonbar/i3_lemonbar_launcher.py b/.i3/lemonbar/i3_lemonbar_launcher.py index eb185c5..2c8d605 100755 --- a/.i3/lemonbar/i3_lemonbar_launcher.py +++ b/.i3/lemonbar/i3_lemonbar_launcher.py @@ -10,11 +10,9 @@ import i3_lemonbar_config as config import i3_lemonbar_common as common import i3_lemonbar_modules as modules import i3_lemonbar_parser as lemonparser -import i3_workspaces as wspaces p_conky_slow = None p_lemonbar = None -i3_ws_obj = None def assert_only_instance(): """ @@ -90,19 +88,16 @@ def clean_up(): nice_delete(config.fifo_file_status) nice_term(p_conky_slow) modules.stop_all() - if i3_ws_obj is not None: - i3_ws_obj.quit() sys.exit(0) -def write_sys_status(): - global i3_ws_obj - - i3_ws_obj = wspaces.i3ws(logger=common.logger, fifo_file=config.fifo_file_status) - i3_ws_obj.work() - def queue_parse_job(job): common.parsing_queue.put(job) +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 @@ -263,8 +258,8 @@ if __name__ == "__main__": # 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 = keep_fifo_open, desc='') i3_thread(target = put_fifo_in_queue, desc='') - i3_thread(target = write_sys_status, desc='Write sys status thread') i3_thread(target = user_screen, desc='Screen thread for user commands') modules.start_all() diff --git a/.i3/lemonbar/i3_lemonbar_modules.py b/.i3/lemonbar/i3_lemonbar_modules.py index 64385a5..e9a9774 100644 --- a/.i3/lemonbar/i3_lemonbar_modules.py +++ b/.i3/lemonbar/i3_lemonbar_modules.py @@ -4,6 +4,7 @@ import subprocess import i3_lemonbar_config as config import i3_lemonbar_common as common import i3_lemonbar_parser as parser +import i3_workspaces as wspaces class LemonModule(threading.Thread): # Module generates some status message, parses it, and handles on click actions @@ -13,20 +14,17 @@ class LemonModule(threading.Thread): super().__init__() self._start_module() - if self.dummy is not None: - # Begin by parsing dummy data to create all lemonbar elements - parsed = self.parse(self.dummy) - common.parsing_queue.put([parsed]) - - def run(self): - common.logger.info('Started module {}'.format(self.name)) - common.health_logger.info('Module {} up'.format(self.name)) + if self.status_handle is None: + return + + common.logger.info('Started module {}'.format(self.__class__.__name__)) + common.health_logger.info('Module {} up'.format(self.__class__.__name__)) while True: line = self.status_handle.readline() if not line: - common.logger.info('Reached end of module {}'.format(self.name)) - common.health_logger.info('Module {} down'.format(self.name)) + common.logger.info('Reached end of module {}'.format(self.__class__.__name__)) + common.health_logger.info('Module {} down'.format(self.__class__.__name__)) break parsed = self.parse(line) @@ -64,7 +62,6 @@ class ConkyFastModule(LemonModule): def __init__(self): self.prefix = 'CNK_FAST' - self.dummy = 'CNK_FAST Fri 23 Aug 00:00:00 MUTE down down down down' super().__init__() def _start_module(self): @@ -72,12 +69,13 @@ class ConkyFastModule(LemonModule): stdout=subprocess.PIPE, text=True) self.status_handle = self.p_handle.stdout - self.wlan_load = parser.LemonUnit('wlan_load', action='wlan') - self.eth_load = parser.LemonUnit('eth_load', action='eth') - self.volume = parser.LemonUnit('volume', action='pavu') - self.date = parser.LemonUnit('date', action='date') - self.time = parser.LemonUnit('time', action='toggle_secs' + self.wlan_load = parser.IconTextUnit('wlan_load', action='wlan', order=13) + self.eth_load = parser.IconTextUnit('eth_load', action='eth', order=14) + self.volume = parser.IconTextUnit('volume', action='pavu', order=20) + self.date = parser.IconTextUnit('date', action='date', order=40) + self.time = parser.IconTextUnit('time', action='toggle_secs', order=41 , alt_scheme=parser.COLOR_SCHEME.SPECIAL) + self.time.modes = [mode for mode in common.bar_mode] parser.g_parser.register_unit(self.wlan_load) parser.g_parser.register_unit(self.eth_load) @@ -120,7 +118,6 @@ class ConkySlowModule(LemonModule): def __init__(self): self.prefix = 'CNK_SLOW' - self.dummy = 'CNK_SLOW 11 2.24G 91 74 F100 100.00 pl' super().__init__() def _start_module(self): @@ -128,12 +125,12 @@ class ConkySlowModule(LemonModule): stdout=subprocess.PIPE, text=True) self.status_handle = self.p_handle.stdout - self.sys_load = parser.LemonUnit('sys_load', action='load') - self.disk = parser.LemonUnit('disk') - self.brightness = parser.LemonUnit('brightness', action='adj_br' - , external={'BRIGHT': self.parse_brightness, 'BAJS': self.parse_brightness}) - self.battery = parser.LemonUnit('battery', action='dpms') - self.language = parser.LemonUnit('language', action='lang' + self.sys_load = parser.IconTextUnit('sys_load', action='load', order=10) + self.disk = parser.IconTextUnit('disk', order=11) + self.brightness = parser.IconTextUnit('brightness', action='adj_br', order=21 + , external={'BRIGHT': self.parse_brightness}) + self.battery = parser.IconTextUnit('battery', action='dpms', order=22) + self.language = parser.IconTextUnit('language', action='lang', order=32 , external={'LANG': self.parse_language}) parser.g_parser.register_unit(self.sys_load) @@ -190,17 +187,257 @@ class ConkySlowModule(LemonModule): def parse_language(self, data): self.language.items = [(config.icon_lang, data[0])] -def start_all(): - global m_conky_fast, m_conky_slow +class i3Module(LemonModule): + # Handles outputs (displays), workspaces and active window + # TODO trigger formatting + def __init__(self): + super().__init__() + self.displays = '' + self.win_title = '' + self.workspaces = '' + + def _start_module(self): + self.i3_ws_obj = wspaces.i3ws(logger=common.logger) + + self.displays = parser.CustomUnit('displays', format_function = self.format_displays, order=-30) + self.workspaces = parser.CustomUnit('workspaces', format_function = self.format_workspaces, order=-20) + self.title = parser.CustomUnit('title', format_function = self.format_title, order=-10) + self.displays.modes.append(common.bar_mode.control) + self.workspaces.modes.append(common.bar_mode.control) + + parser.g_parser.register_unit(self.displays) + parser.g_parser.register_unit(self.workspaces) + parser.g_parser.register_unit(self.title) + + # Add callbacks for parsing + self.i3_ws_obj.change_callbacks.append(self.parse_displays) + self.i3_ws_obj.change_callbacks.append(self.parse_workspaces) + self.i3_ws_obj.focus_callbacks.append(self.parse_title) + + # Add callbacks for special actions + self.i3_ws_obj.change_callbacks.append(i3Module.set_bg) + self.i3_ws_obj.focus_callbacks.append(i3Module.set_keymap) + self.i3_ws_obj.focus_callbacks.append(i3Module.kill_floating_windows) + + def _stop_module(self): + parser.g_parser.remove_unit(self.displays) + parser.g_parser.remove_unit(self.workspaces) + parser.g_parser.remove_unit(self.title) + + if self.i3_ws_obj is not None: + self.i3_ws_obj.quit() + + # Overload run as i3_ws_obj.work() is a blocking command + # Parsing is instead done through callbacks + def run(self): + common.logger.info('Started module {}'.format(self.__class__.__name__)) + common.health_logger.info('Module {} up'.format(self.__class__.__name__)) + + self.i3_ws_obj.work() # This is a blocking command + + common.logger.info('Reached end of module {}'.format(self.__class__.__name__)) + common.health_logger.info('Module {} down'.format(self.__class__.__name__)) + + def parse_displays(self, i3ws): + parsed_list = [parser.block(click='displays', font='2')] + for output in i3ws.outputs: + output_name = output.name + if output.active: + if output_name == 'eDP1': + col_head = config.color_head + elif output_name == 'DP1': + col_head = config.color_vga + elif output_name == 'HDMI2': + col_head = config.color_hdmi + else: + col_head = '#00000000' # Undefined + parsed_list.append(parser.block(fg=config.color_back, bg=col_head)) + parsed_list.append(config.icon_wsp) + parsed_list.append(parser.block(click='')) + + self.displays = ' '.join(parsed_list) + + def parse_workspaces(self, i3ws): + prefix = parser.block(font='1', fg=config.color_back, bg=config.color_head) + prefix_foc = ''.join([parser.block(fg = config.color_head, bg=config.color_wsp) + , ' ', config.sep_right, ' ' + , parser.block(fg=config.color_back, bg=config.color_wsp, font='1')]) + prefix_ina = parser.block(fg=config.color_back, bg=config.color_head, font='1', append=' ') + wspces = [] + + for workspace in i3ws.workspaces: + # Find out which output the workspace is on + output = None # TODO actually use this information + for output_ in i3ws.outputs: + if output_['name'] == workspace['output']: + output = output_ + break + if not output: + continue + status = i3ws.state.get_state(workspace, output) # FOC or INA + name = workspace['name'] # e.g. 5 terms + current = ''.join([parser.block(click=('i3-msg workspace' + name)) + , name, parser.block(click='')]) + if status == "FOC": + wspces.append(''.join([prefix_foc, current])) + else: + wspces.append(''.join([prefix_ina, current])) + + self.workspaces = ''.join([prefix, ' '.join(wspces)]) + + def parse_title(self, i3ws): + if i3ws.focused_window is None: + return + self.win_title = ' '.join([parser.block(fg=config.color_head, bg=config.color_sec_b2) + , config.sep_right, parser.block(fg=config.color_head, bg=config.color_sec_b2, click='mode cycle') + , config.icon_prog + , parser.block(fg=config.color_sec_b2, bg='-') + , i3ws.focused_window.name]) + + def format_displays(self): + return self.displays + + def format_workspaces(self): + return self.workspaces + + def format_title(self): + return self.win_title + + def img_path(num): + dir = '/home/kuba/Obrazy/Wallpapers/' + return dir + { + 1: '1_main', + 2: '2_web', + 3: '3_music', + 4: '4_work', + 5: '5_terms', + 6: '6_stats', + 7: '7', + 8: '8', + 9: '9', + }.get(int(num), 'default') + + def set_bg(i3ws): + cmd_args = ['sh', '/home/kuba/scripts/set_bg.sh'] + for output in i3ws.outputs: + if output.active: + bg = i3Module.img_path(output.current_workspace.partition(' ')[0]) + cmd_args.append(bg) + subprocess.call(cmd_args) + + def kill_floating_windows(i3ws): + if i3ws.focused_window is None: + return + + role = i3ws.focused_window.window_role + wclass = i3ws.focused_window.window_class + + if role != 'FLOAT_TERM' and wclass != 'FLOAT_PAVU' and wclass != 'YADWINBR': + # Is there a window that the bar has opened? + for pid in common.kill_on_unfocus: + try: + os.kill(pid, signal.SIGTERM) + except ProcessLookupError: + common.logger.debug('Tried killing process {} but it doesn\'t exist'.format(pid)) + common.kill_on_unfocus = [] + + def set_keymap(i3ws): + if i3ws.focused_window is None: + return + wclass = i3ws.focused_window.window_class + common.cur_class = wclass + + if wclass in common.keymaps: + new_km = common.keymaps[wclass] + common.logger.debug('Setting {} as keymap for {}'.format(new_km, wclass)) + else: + new_km = common.def_keymap + common.logger.debug('Setting default keymap {} for {}'.format(new_km, wclass)) + subprocess.call(['/home/kuba/.i3/scripts/lang.sh', 'qset', new_km]) + +class ScreenModule(LemonModule): + + def __init__(self): + self.prefix = 'RESP' + super().__init__() + + def _start_module(self): + # No external commands needed + self.status_handle = None + + self.controls = parser.ButtonsUnit('controls', order=-10) + self.response = parser.IconTextUnit('response', order=10) + + self.controls.items = [('', config.icon_prog, 'mode cycle') + ,('', '', None) + ,('', 'on', 'bluetooth power on') + ,('', 'off', 'bluetooth power off') + ,('PXC 550', '', None) + ,('', 'conn.', 'bluetooth connect pxc550') + ,('', 'disc.', 'bluetooth disconnect pxc550') + ] + + self.response.modes = [common.bar_mode.control] + self.controls.modes = [common.bar_mode.control] + + parser.g_parser.register_unit(self.response) + parser.g_parser.register_unit(self.controls) + + def _stop_module(self): + parser.g_parser.remove_unit(self.response) + parser.g_parser.remove_unit(self.controls) + + def _parse_data(self, data): + self.response.items = [('', ' '.join(data))] + +class PowerOptionsModule(LemonModule): + + def __init__(self): + self.prefix = '' + super().__init__() + + def _start_module(self): + # No external commands needed + self.status_handle = None + + self.power_opts = parser.CustomUnit('power', format_function=self.format_power_opts, order=-1) + + self.power_opts.modes = [common.bar_mode.power] + + parser.g_parser.register_unit(self.power_opts) + + def _stop_module(self): + parser.g_parser.remove_unit(self.power_opts) + + def _parse_data(self, data): + pass + + def format_power_opts(self): + return ''.join([parser.block(fg=config.color_fore, bg=config.color_poweropts) + , ' Abort (Esc) | System (l) lock, (e) logout, (s) suspend, (h) hibernate' + , ', (r) reboot, (Shift+s) shutdown']) + +def start_all(): + global m_conky_fast, m_conky_slow, m_i3ws + + m_i3ws = i3Module() m_conky_slow = ConkySlowModule() m_conky_fast = ConkyFastModule() + m_screen = ScreenModule() + m_power = PowerOptionsModule() + m_i3ws.start() m_conky_slow.start() m_conky_fast.start() + m_screen.start() + m_power.start() def stop_all(): - global m_conky_fast, m_conky_slow + global m_conky_fast, m_conky_slow, m_i3ws + m_i3ws.stop() m_conky_slow.stop() m_conky_fast.stop() + m_screen.stop() + m_power.stop() diff --git a/.i3/lemonbar/i3_lemonbar_parser.py b/.i3/lemonbar/i3_lemonbar_parser.py index fefab19..972d338 100755 --- a/.i3/lemonbar/i3_lemonbar_parser.py +++ b/.i3/lemonbar/i3_lemonbar_parser.py @@ -1,28 +1,107 @@ import sys +import bisect # Sorting of list from enum import Enum -import i3_lemonbar_config as conf +import i3_lemonbar_config as config import i3_lemonbar_common as common -displays = '' -workspaces = '' -win_title = '' -time = '' -response = '' +sr = config.sep_right +slr = config.sep_l_right +sl = config.sep_left +sll = config.sep_l_left -blank = ' ' - -sr = conf.sep_right -slr = conf.sep_l_right -sl = conf.sep_left -sll = conf.sep_l_left - -class LemonUnit: - def __init__(self, name, action = None, alt_scheme=None, external=None): +class IconTextUnit: + def __init__(self, name, order, action = None, alt_scheme=None, external=None): self.name = name self.action = action + self.order = order self.alt_scheme = alt_scheme # None means default self.external = external self.items = [] # List of tuples (icon, text) + self.modes = [common.bar_mode.normal] + + def format(self): + close = block(click='') if self.action is not None else '' + + b_color = self.alt_scheme.back_color + i_color = self.alt_scheme.icon_color + t_color = self.alt_scheme.text_color + + # Start with a major separator. Keep previous background color, set foreground color + # of separator to background color of this unit + blocks = [] + blocks.append(block(fg=b_color, append=sl)) + blocks.append(' ') # TODO perhaps more nice solution than manual spacing + if len(self.items) >= 1: + (icon, text) = self.items[0] + blocks.append(block(click=self.action, fg=i_color, bg=b_color, font='2' + , append=' ' + icon)) + blocks.append(' ') + blocks.append(block(fg=t_color, font='1', append=text)) + if len(self.items) >= 2: + for item in self.items[1:]: + (icon, text) = item + # Append minor separator + blocks.append(' ') + blocks.append(sll) + + blocks.append(block(fg=i_color, bg=b_color, font='2', append=' ' + icon)) + blocks.append(' ') + blocks.append(block(fg=t_color, font='1', append=text)) + + if len(self.items) >= 1: + blocks.append(close) + return ''.join(blocks) + + def __lt__(self, other): + return self.order < other.order + +class ButtonsUnit: + def __init__(self, name, order, alt_scheme=None, external=None): + self.name = name + self.order = order + self.alt_scheme = alt_scheme # None means default + self.external = external + self.items = [] # List of tuples (icon, text, action) + self.modes = [common.bar_mode.normal] + + def format(self): + b_color = self.alt_scheme.back_color + i_color = self.alt_scheme.icon_color + t_color = self.alt_scheme.text_color + + # Start with a major separator. Keep previous background color, set foreground color + # of separator to background color of this unit + blocks = [] + blocks.append(block(fg=b_color, append=sl)) + blocks.append(' ') + for item in self.items: + (icon, text, action) = item + close = block(click='') if action is not None else '' + + blocks.append(block(click=action, fg=i_color, bg=b_color, font='2' + , append=' ' + icon)) + blocks.append(' ') + blocks.append(block(fg=t_color, font='1', append=text)) + blocks.append(close) + + return ''.join(blocks) + + def __lt__(self, other): + return self.order < other.order + +class CustomUnit: + def __init__(self, name, format_function, order, action = None, alt_scheme=None, external=None): + self.name = name + self.action = action + self.order = order + self.alt_scheme = alt_scheme # None means default + self.external = external + self.format = format_function + self.items = [] # List of tuples (icon, text) + self.modes = [common.bar_mode.normal] + + def __lt__(self, other): + return self.order < other.order class LemonParser: # Handle parsing of units @@ -33,58 +112,42 @@ class LemonParser: self.units = [] def register_unit(self, unit): - self.units.append(unit) + # Keep the list sorted + bisect.insort_left(self.units, unit) def remove_unit(self, unit): - self.remove(unit) + self.units.remove(unit) def format(self): - formatted = [] + formatted = ['%{l}'] # Start left-justified + previously_left_justified = True + # Iterate over all units. Apply alternating color schemes + alt = 1 + for unit in self.units: + # Show only units appropriate for the current mode + if common.mode not in unit.modes: + continue + + # Negative order means left justified, positive means right justified + if previously_left_justified and unit.order > 0: + # Switch to right justified + formatted.append(block(fg='-', bg='-')) # Reset so the color doesn't fill + formatted.append('%{r}') + previously_left_justified = False - # Iterate over all units. Keep count to apply alternating color schemes - for count, unit in enumerate(self.units): if unit.alt_scheme is None: # Apply default color scheme, which is alternating - color_scheme = COLOR_SCHEME.A1 if count % 2 else COLOR_SCHEME.A2 + unit.alt_scheme = COLOR_SCHEME.A1 if alt == 1 else COLOR_SCHEME.A2 else: - # Apply alternate color scheme - color_scheme = unit.alt_scheme + # Keep alternate color scheme # INA is a special case - if color_scheme == COLOR_SCHEME.INA: - color_scheme = COLOR_SCHEME.A1_INA if count % 2 else COLOR_SCHEME.A2_INA + if unit.alt_scheme == COLOR_SCHEME.INA: + unit.alt_scheme = COLOR_SCHEME.A1_INA if alt == 1 else COLOR_SCHEME.A2_INA - close = block(click='') if unit.action is not None else '' - - b_color = color_scheme.back_color - i_color = color_scheme.icon_color - t_color = color_scheme.text_color - - # Start with a major separator. Keep previous background color, set foreground color - # of separator to background color of this unit - blocks = [] - blocks.append(block(fg=b_color, append=sl)) - blocks.append(' ') # TODO perhaps more nice soluting than manual spacing - if len(unit.items) >= 1: - (icon, text) = unit.items[0] - blocks.append(block(click=unit.action, fg=i_color, bg=b_color, font='2' - , append=' ' + icon)) - blocks.append(' ') - blocks.append(block(fg=t_color, font='1', append=text)) - if len(unit.items) >= 2: - for item in unit.items[1:]: - (icon, text) = item - # Append minor separator - blocks.append(' ') - blocks.append(sll) - - blocks.append(block(fg=i_color, bg=b_color, font='2', append=' ' + icon)) - blocks.append(' ') - blocks.append(block(fg=t_color, font='1', append=text)) - - if len(unit.items) >= 1: - blocks.append(close) - formatted.append(''.join(blocks)) + formatted.append(unit.format()) + alt = alt + 1 if alt + 1 <= 2 else 1 + formatted.append(block(fg='-', bg='-')) # Not sure if needed return ' '.join(formatted) g_parser = LemonParser() @@ -105,150 +168,25 @@ def block(fg = None, bg = None, font = None, click = None, append=''): rtn.append('T'+font) return ''.join(['%{', ' '.join(rtn), '}', append]) -reset = block(fg='-', bg='-') -reset_power = block(fg='-', bg=conf.color_poweropts) - class COLOR_SCHEME(Enum): - A1 = (conf.color_sec_b1, conf.color_fore, conf.color_icon) - A2 = (conf.color_sec_b2, conf.color_fore, conf.color_icon) + A1 = (config.color_sec_b1, config.color_fore, config.color_icon) + A2 = (config.color_sec_b2, config.color_fore, config.color_icon) INA = (None, None, None) # This one is special, dynamically changed to A1_INA or A2_INA - A1_INA = (conf.color_sec_b1, conf.color_disable, conf.color_disable) - A2_INA = (conf.color_sec_b2, conf.color_disable, conf.color_disable) - CPU_ALERT= (conf.color_cpu, conf.color_back, conf.color_back) - NET_ALERT= (conf.color_net, conf.color_back, conf.color_back) - SPECIAL = (conf.color_head, conf.color_back, conf.color_back) + A1_INA = (config.color_sec_b1, config.color_disable, config.color_disable) + A2_INA = (config.color_sec_b2, config.color_disable, config.color_disable) + CPU_ALERT= (config.color_cpu, config.color_back, config.color_back) + NET_ALERT= (config.color_net, config.color_back, config.color_back) + SPECIAL = (config.color_head, config.color_back, config.color_back) def __init__(self, back_color, text_color, icon_color): self.back_color = back_color self.text_color = text_color self.icon_color = icon_color -def single_sect(icon='', text='', click=None, alt=COLOR_SCHEME.A1): - # If there is an action we need to close the block - close = block(click='') if click is not None else '' - if icon != '': - icon = ' ' + icon - return ' '.join([block(fg=alt.back_color, append=sl) - , block(click=click, fg=alt.icon_color, bg=alt.back_color - , font='2', append=icon) - , block(fg=alt.text_color, font='1', append=text) - , close]) - -def double_sect(text1 = '', text2 = '', icon1 = '', icon2 = '', click = None - , alt=COLOR_SCHEME.A1): - # If text_color None then icon_color will apply - close = block(click='') if click is not None else '' - return ''.join([block(fg=alt.back_color, append=sl), ' ' - , block(click=click, fg=alt.icon_color, bg=alt.back_color, font='2') - , ' ', icon1, block(fg=alt.text_color, font='1'), ' ', text1 - , block(fg=alt.icon_color), sll, block(font='2') - , icon2, block(fg=alt.text_color, font='1'), ' ', text2, ' ', close]) - -def control_sect(sec_color=None, head='', buttons=[], actions=[]): - rtn = head - for button, action in zip(buttons, actions): - # If there is an action we need to close the block - close = block(click='') if action is not None else '' - rtn += ' '.join([block(click=action), button, close]) - return rtn - -# Constants -power_opts = ''.join([block(fg=conf.color_fore, bg=conf.color_poweropts) - , ' Abort (Esc) | System (l) lock, (e) logout, (s) suspend, (h) hibernate' - , ', (r) reboot, (Shift+s) shutdown']) -controls = ' '.join([block(fg=conf.color_head, bg=conf.color_sec_b2) - , conf.sep_right, block(fg=conf.color_head, bg=conf.color_sec_b2, click='mode cycle') - , conf.icon_prog - , block(fg=conf.color_sec_b2, bg='-', click='') - , control_sect(head='', buttons=['on', 'off'] - , actions=['bluetooth power on', 'bluetooth power off']) - , control_sect(head='PXC 550', buttons=['conn.', 'disc.'] - , actions=['bluetooth connect pxc550', 'bluetooth disconnect pxc550']) - ]) -def update_response(data): - global response - - resp = ' '.join(data) - #common.logger.debug('Got response {}'.format(resp)) - response= single_sect(text=resp) - -def update_displays(data): - global displays - - dsp_array = data[0].split(':') - parsed_list = [block(click='displays', font='2')] - for dsp in dsp_array[1:]: - if dsp == 'eDP1': - col_head = conf.color_head - elif dsp == 'DP1': - col_head = conf.color_vga - elif dsp == 'HDMI2': - col_head = conf.color_hdmi - else: - col_head = '#00000000' # Undefined - parsed_list.append(block(fg=conf.color_back, bg=col_head)) - parsed_list.append(conf.icon_wsp) - parsed_list.append(block(click='')) - - displays = ' '.join(parsed_list) - -def update_workspaces(data): - global workspaces - prefix = block(font='1', fg=conf.color_back, bg=conf.color_head) - prefix_foc = ''.join([block(fg = conf.color_head, bg=conf.color_wsp) - , conf.sep_right - , block(fg=conf.color_back, bg=conf.color_wsp, font='1')]) - prefix_ina = block(fg=conf.color_back, bg=conf.color_head, font='1') - wspces = [] - for entry in data: # entry for example FOC5___terms - status = entry[0:3] # FOC or INA - num = entry[3] - name = entry[7:] - full_name = ' '.join(['', num, name]) - current = ''.join([block(click=('i3-msg workspace' + full_name)) - , full_name, block(click='')]) - if status == "FOC": - wspces.append(''.join([prefix_foc, current])) - else: - wspces.append(''.join([prefix_ina, current])) - - workspaces = ''.join([prefix, ' '.join(wspces)]) - -def update_title(data): - global win_title - win_title = ' '.join([block(fg=conf.color_head, bg=conf.color_sec_b2) - , conf.sep_right, block(fg=conf.color_head, bg=conf.color_sec_b2, click='mode cycle') - , conf.icon_prog - , block(fg=conf.color_sec_b2, bg='-') - , ' '.join(data)]) - def format_line(): - # Need to end with a reset block, otherwise the color fills the %{r} spacer - if common.mode == common.bar_mode.normal: - return ''.join(['%{l}', reset, displays, workspaces, win_title - , '%{r}', g_parser.format(), reset]) - elif common.mode == common.bar_mode.power: - return ''.join(['%{l}', reset, power_opts - , '%{r}', g_parser.format(), reset_power]) - elif common.mode == common.bar_mode.control: - return ''.join(['%{l}', reset, displays, workspaces, controls - , '%{r}', response, time, reset]) - else: - return ''.join(['%{l}', reset, displays, workspaces, 'Not normal' - , '%{r}', time, reset]) - -parsers_dict = { 'WSP':update_workspaces - ,'WIN':update_title - ,'DISP':update_displays - ,'RESP':update_response - } + return g_parser.format() def parse_line(line_in): - ''' Lines are - - CNK_FAST Fri 21 Sep 19:45:18 VolXXX wlan_d wlan_u eth_d eth_u - CNK_SLOW cpu mem disk_root disk_home batt bri lang disp - WSPINA1___main INA2___web FOC5___terms INA6___stats - ''' + # Parse lines that external programs have written to fifo for unit in g_parser.units: if unit.external is not None: @@ -258,17 +196,6 @@ def parse_line(line_in): func(line_in[l:].split()) break - try: - for key,func in parsers_dict.items(): - l = len(key) - if line_in[:l] == key: - func(line_in[l:].split()) - break - - except: - print('Exception occured\n Line in: {}\n Data: {}'.format(line_in, line_in[l:].split())) - raise - formatted_line = format_line() return formatted_line diff --git a/.i3/lemonbar/i3_workspaces.py b/.i3/lemonbar/i3_workspaces.py index 88194bc..04306c4 100755 --- a/.i3/lemonbar/i3_workspaces.py +++ b/.i3/lemonbar/i3_workspaces.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# # Print i3 workspaces on every change. # # Format: @@ -20,7 +19,7 @@ import time from subprocess import call import i3ipc -import i3_lemonbar_common as common +print_stdout = False class State(object): # workspace states @@ -47,24 +46,20 @@ class State(object): class i3ws(object): ws_format = '%s%s ' end_format = 'WSP%s' - state = State() - outputs = [] - focused_window = None - running = True - fifo = None - # Callback functions have argument i3ws - change_callbacks = [] - focus_callbacks = [] - - - def __init__(self, logger, state=None, fifo_file=None): + def __init__(self, logger, state=None): + self.state = State() + self.outputs = [] + self.workspaces = [] + self.focused_window = None + self.running = True if state: self.state = state - if fifo_file: - self.fifo = open(fifo_file, 'w') self.logger = logger - common.add_callbacks(self) + + # Callback functions have argument i3ws + self.change_callbacks = [] + self.focus_callbacks = [] def work(self): # While loop to restart connection as long as there is an i3 ipc socket @@ -79,8 +74,11 @@ class i3ws(object): self.conn = i3ipc.Connection() # Run call backs once - #self.change(self.conn, None) - #self.win_focused(self.conn, None) + for cb in self.change_callbacks: + cb(self) + + for cb in self.focus_callbacks: + cb(self) self.conn.on('workspace::focus' , self.change) self.conn.on('workspace::init' , self.change) @@ -109,8 +107,8 @@ class i3ws(object): fmt_outputs.append(output.name) self.display(':'.join(fmt_outputs)) - workspaces = i3.get_workspaces() - text = self.format(workspaces, self.outputs) + self.workspaces = i3.get_workspaces() + text = self.format(self.workspaces, self.outputs) self.display(text) # Callbacks @@ -129,6 +127,7 @@ class i3ws(object): def format(self, workspaces, outputs): # Formats the text according to the workspace data given. + # Only important when running in free standing mode out = '' for workspace in workspaces: output = None @@ -145,10 +144,8 @@ class i3ws(object): return self.end_format % out def display(self, text): - if self.fifo is not None: - self.fifo.write(text + '\n') - self.fifo.flush() - else: + global print_stdout + if print_stdout: # Displays the text in stout print(text) sys.stdout.flush() @@ -157,11 +154,6 @@ class i3ws(object): self.logger.debug('Quitting i3 workspace script') self.running = False self.conn.main_quit() - if self.fifo is not None: - try: - self.fifo.close() - except BrokenPipeError: - pass def handle_exit(signal, frame): global ws @@ -171,6 +163,7 @@ def handle_exit(signal, frame): if __name__ == '__main__': # Run as stand-alone + print_stdout = True # Setup logger to stdout with debug log level formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')