diff --git a/.i3/lemonbar/i3_lemonbar_config.py b/.i3/lemonbar/i3_lemonbar_config.py index 9c289a7..2ff85ca 100755 --- a/.i3/lemonbar/i3_lemonbar_config.py +++ b/.i3/lemonbar/i3_lemonbar_config.py @@ -51,10 +51,10 @@ net_alert = 5 #fi # Icon definitions -sep_left = "" # Powerline separator left alt.  -sep_right = " " # Powerline separator right -sep_l_left = "  " # Powerline light separator left -sep_l_right = "  " # Powerline light sepatator right +sep_left = "" # Powerline separator left alt.  +sep_right = "" # Powerline separator right +sep_l_left = "" # Powerline light separator left +sep_l_right = "" # Powerline light sepatator right # Icon glyphs from Terminusicons2 icon_clock = "" # Clock icon diff --git a/.i3/lemonbar/i3_lemonbar_modules.py b/.i3/lemonbar/i3_lemonbar_modules.py index a336b57..64385a5 100644 --- a/.i3/lemonbar/i3_lemonbar_modules.py +++ b/.i3/lemonbar/i3_lemonbar_modules.py @@ -12,14 +12,21 @@ class LemonModule(threading.Thread): def __init__(self): super().__init__() self._start_module() - # TODO if exists dummy data send it + + 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)) while True: line = self.status_handle.readline() if not line: common.logger.info('Reached end of module {}'.format(self.name)) - common.health_logger.info('Reached end of module {}'.format(self.name)) + common.health_logger.info('Module {} down'.format(self.name)) break parsed = self.parse(line) @@ -36,94 +43,164 @@ class LemonModule(threading.Thread): formatted_line = parser.format_line() # Construct entire line return formatted_line +def format_load(data, module, alert): + # Helper function to format network modules + # Changes colors scheme to inactive when interfaces are down, or to alert when + # alert level is reached + # Returns tuple (down, up) + if data[0] == 'down': # wlan + module.alt_scheme = parser.COLOR_SCHEME.INA + return ('x', 'x') + else: + (d_v, u_v) = (data[0],data[1]) + if max(float(d_v), float(u_v)) > float(alert): + module.alt_scheme = parser.COLOR_SCHEME.NET_ALERT + else: + # Reset to default + module.alt_scheme = None + return (d_v, u_v) + 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): self.p_handle = subprocess.Popen(['conky', '-c', config.path+'conky_fast'], stdout=subprocess.PIPE, text=True) self.status_handle = self.p_handle.stdout - common.logger.debug('Started conky fast module') + + 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' + , alt_scheme=parser.COLOR_SCHEME.SPECIAL) + + parser.g_parser.register_unit(self.wlan_load) + parser.g_parser.register_unit(self.eth_load) + parser.g_parser.register_unit(self.volume) + parser.g_parser.register_unit(self.date) + parser.g_parser.register_unit(self.time) def _stop_module(self): if self.p_handle is not None: self.p_handle.terminate() + parser.g_parser.remove_unit(self.wlan_load) + parser.g_parser.remove_unit(self.eth_load) + parser.g_parser.remove_unit(self.volume) + parser.g_parser.remove_unit(self.date) + parser.g_parser.remove_unit(self.time) + def _parse_data(self, data): + # wlan and eth + (wland_v, wlanu_v) = format_load(data[5:7], self.wlan_load, config.net_alert) + self.wlan_load.items = [(config.icon_wlan + config.icon_dl, wland_v) + ,(config.icon_ul, wlanu_v)] + + (ethd_v, ethu_v) = format_load(data[7:9], self.eth_load, config.net_alert) + self.eth_load.items = [(config.icon_eth + config.icon_dl, ethd_v) + ,(config.icon_ul, ethu_v)] + + # Volume mute = data[4] == 'MUTE' or data[4] == 'NONE' (vol,vols) = (-1,'×') if mute else (int(data[4]), data[4]+'%') icon_v = config.icon_vol_mute if vol == 0 else \ config.icon_vol_low if vol < 50 else config.icon_vol - parser.volume = parser.single_sect(icon=icon_v, click='pavu', text=vols - , alt=parser.COLOR_SCHEME.A2) + self.volume.items = [(icon_v, vols)] - tme = data[3] if common.show_secs else data[3][:-3] - parser.date = parser.single_sect(icon=config.icon_clock, click='date' - , text=' '.join(data[0:3]), alt=parser.COLOR_SCHEME.A1) - parser.time = parser.single_sect(click='toggle_secs', text=tme, alt=parser.COLOR_SCHEME.SPECIAL) - - parser.update_net(data[5:9]) + # Date and time + self.date.items = [(config.icon_clock, ' '.join(data[0:3]))] + self.time.items = [('', data[3] if common.show_secs else data[3][:-3])] 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): self.p_handle = subprocess.Popen(['conky', '-c', config.path+'conky_slow'], stdout=subprocess.PIPE, text=True) self.status_handle = self.p_handle.stdout - common.logger.debug('Started conky slow module') + + 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' + , external={'LANG': self.parse_language}) + + parser.g_parser.register_unit(self.sys_load) + parser.g_parser.register_unit(self.disk) + parser.g_parser.register_unit(self.brightness) + parser.g_parser.register_unit(self.battery) + parser.g_parser.register_unit(self.language) def _stop_module(self): + parser.g_parser.remove_unit(self.sys_load) + parser.g_parser.remove_unit(self.disk) + parser.g_parser.remove_unit(self.brightness) + parser.g_parser.remove_unit(self.battery) + parser.g_parser.remove_unit(self.language) + if self.p_handle is not None: self.p_handle.terminate() def _parse_data(self, data): - # System load + self.parse_sys_load (data[0:2]) # System load + self.parse_disk (data[2:4]) # Disk usage + self.parse_battery (data[4:5]) # Battery + self.parse_brightness (data[5:6]) # Screen brightness + self.parse_language (data[6:7]) # Language + + def parse_sys_load(self, data): if int(data[0]) > int(config.cpu_alert): - cpu_alt = parser.COLOR_SCHEME.CPU_ALERT + self.sys_load.alt_scheme = parser.COLOR_SCHEME.CPU_ALERT else: - cpu_alt = parser.COLOR_SCHEME.A2 + self.sys_load.alt_scheme = None - parser.sys_load = ''.join([ - parser.double_sect(text1 = (data[0] + '%'), text2 = data[1], icon1 = config.icon_cpu - , icon2 = config.icon_mem, alt=cpu_alt , click = 'load') - , parser.double_sect(text1 = (data[2] + '%'), text2 = (data[3] + '%') - , icon1 = config.icon_hd, icon2 = config.icon_home, alt=parser.COLOR_SCHEME.A1) - ]) # cpu mem disk_r disk_home + self.sys_load.items = [(config.icon_cpu, data[0] + '%') + ,(config.icon_ul, data[1])] - #sec_color = config.color_sec_b1 if (b == 1) else config.color_sec_b2 - - (batt_stat, batt) = (data[4][0], int(data[4][1:])) + def parse_disk(self, data): + self.disk.items = [(config.icon_hd , data[0] + '%') + ,(config.icon_home, data[1] + '%')] + def parse_battery(self, data): + (batt_stat, batt) = (data[0][0], data[0][1:]) + batt_i = int(batt) icon_batt = config.icon_charging if batt_stat == 'C' else \ config.icon_charged if batt_stat == 'F' else \ - config.icon_batt_0 if batt < 20 else \ - config.icon_batt_1 if batt < 40 else \ - config.icon_batt_2 if batt < 60 else \ - config.icon_batt_3 if batt < 80 else \ + config.icon_batt_0 if batt_i < 20 else \ + config.icon_batt_1 if batt_i < 40 else \ + config.icon_batt_2 if batt_i < 60 else \ + config.icon_batt_3 if batt_i < 80 else \ config.icon_batt_4 - battery = parser.single_sect(icon=icon_batt, click='dpms', text=(str(batt)+'%') - , alt=parser.COLOR_SCHEME.A2) - parser.update_bright([data[5]]) - parser.update_lang([data[6]]) + self.battery.items = [(icon_batt, batt+'%')] + def parse_brightness(self, data): + brtxt = str(int(float(data[0]))) + self.brightness.items = [(config.icon_bright, brtxt+'%')] + + def parse_language(self, data): + self.language.items = [(config.icon_lang, data[0])] def start_all(): global m_conky_fast, m_conky_slow - m_conky_fast = ConkyFastModule() m_conky_slow = ConkySlowModule() + m_conky_fast = ConkyFastModule() - m_conky_fast.start() m_conky_slow.start() + m_conky_fast.start() def stop_all(): global m_conky_fast, m_conky_slow - m_conky_fast.stop() m_conky_slow.stop() + m_conky_fast.stop() diff --git a/.i3/lemonbar/i3_lemonbar_parser.py b/.i3/lemonbar/i3_lemonbar_parser.py index 6569124..fefab19 100755 --- a/.i3/lemonbar/i3_lemonbar_parser.py +++ b/.i3/lemonbar/i3_lemonbar_parser.py @@ -6,13 +6,6 @@ import i3_lemonbar_common as common displays = '' workspaces = '' win_title = '' -sys_load = '' -net_load = '' -volume = '' -brightness = '' -battery = '' -date = '' -language = '' time = '' response = '' @@ -23,6 +16,79 @@ 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): + self.name = name + self.action = action + self.alt_scheme = alt_scheme # None means default + self.external = external + self.items = [] # List of tuples (icon, text) + +class LemonParser: + # Handle parsing of units + # Apply alternating colors + # Contains list of units + + def __init__(self): + self.units = [] + + def register_unit(self, unit): + self.units.append(unit) + + def remove_unit(self, unit): + self.remove(unit) + + def format(self): + formatted = [] + + # 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 + else: + # Apply alternate color scheme + color_scheme = unit.alt_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 + + 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)) + + return ' '.join(formatted) + +g_parser = LemonParser() + def block(fg = None, bg = None, font = None, click = None, append=''): # Remeber that clickable blocks need to be closed rtn = [] @@ -45,6 +111,7 @@ 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) + 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) @@ -146,48 +213,6 @@ def update_workspaces(data): workspaces = ''.join([prefix, ' '.join(wspces)]) -def update_lang(data): - global language - language = single_sect(icon=conf.icon_lang, click='lang', text=data[0] - , alt=COLOR_SCHEME.A2) - -def update_bright(data): - global brightness - brtxt = str(int(float(data[0]))) - brightness = single_sect(icon=conf.icon_bright, click='adj_br' - , text=(brtxt+'%'), alt=COLOR_SCHEME.A1) - -def update_net(data): - global net_load - - if data[0] == 'down': # wlan - (wland_v, wlanu_v) = ('x', 'x') - wlan_alt = COLOR_SCHEME.A2_INA - else: - (wland_v, wlanu_v) = (data[0],data[1]) - if max(float(wland_v), float(wlanu_v)) > float(conf.net_alert): - wlan_alt = COLOR_SCHEME.NET_ALERT - else: - wlan_alt = COLOR_SCHEME.A2 - - if data[2] == 'down': # eth - (ethd_v, ethu_v) = ('x', 'x') - eth_alt = COLOR_SCHEME.A1_INA - else: - (ethd_v, ethu_v) = (data[2],data[3]) - if max(float(ethd_v), float(ethu_v)) > float(conf.net_alert): - eth_alt = COLOR_SCHEME.NET_ALERT - else: - eth_alt = COLOR_SCHEME.A1 - - net_load = ''.join([ - double_sect(click = 'wlan', alt=wlan_alt, text1 = wland_v, text2 = wlanu_v, - icon1 = (conf.icon_wlan + conf.icon_dl), icon2 = conf.icon_ul) - , double_sect(click = 'eth', alt=eth_alt, text1 = ethd_v, text2 = ethu_v, - icon1 = (conf.icon_eth + conf.icon_dl), icon2 = conf.icon_ul) - ]) # wlan_d wlan_u eth_d eth_u - - def update_title(data): global win_title win_title = ' '.join([block(fg=conf.color_head, bg=conf.color_sec_b2) @@ -199,19 +224,19 @@ def update_title(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}', sys_load, net_load - , volume, brightness, battery, date, language, time, reset]) + 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}', sys_load, net_load - , volume, brightness, battery, date, language, time, reset_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]) + return ''.join(['%{l}', reset, displays, workspaces, controls + , '%{r}', response, time, reset]) else: - return ''.join(['%{l}', reset, displays, workspaces, 'Not normal', '%{r}', time, reset]) + return ''.join(['%{l}', reset, displays, workspaces, 'Not normal' + , '%{r}', time, reset]) parsers_dict = { 'WSP':update_workspaces - ,'LANG':update_lang - ,'BRIGHT':update_bright ,'WIN':update_title ,'DISP':update_displays ,'RESP':update_response @@ -225,6 +250,14 @@ def parse_line(line_in): WSPINA1___main INA2___web FOC5___terms INA6___stats ''' + for unit in g_parser.units: + if unit.external is not None: + for key,func in unit.external.items(): + l = len(key) + if line_in[:l] == key: + func(line_in[l:].split()) + break + try: for key,func in parsers_dict.items(): l = len(key)