#!/usr/bin/env python3 import enum import logging import threading import time import subprocess # TODO Update strategies (periodic, inotify file) # TODO Section order (priority system maybe ?) # TODO Allow deletion of Bar, BarGroup and Section for screen changes # TODO Optimize to use write() calls instead of string concatenation (writing # BarGroup strings should be a good compromise) # TODO Use bytes rather than strings # TODO Use default colors of lemonbar sometimes class BarGroupType(enum.Enum): LEFT = 0 RIGHT = 1 # MID_LEFT = 2 # MID_RIGHT = 3 class Bar: """ One bar for each screen """ # Constants FONT = "DejaVu Sans Mono for Powerline" @staticmethod def init(): Section.init() cmd = ['lemonbar', '-f', Bar.FONT, '-b'] Bar.process = subprocess.Popen(cmd, stdin=subprocess.PIPE) # Debug # Bar(0) Bar(1) # Class globals everyone = set() string = "" process = None def __init__(self, screen): assert isinstance(screen, int) self.screen = "%{S" + str(screen) + "}" self.groups = dict() for groupType in BarGroupType: group = BarGroup(groupType, self) self.groups[groupType] = group self.childsChanged = False self.everyone.add(self) @staticmethod def addSectionAll(section, group, screens=None): """ .. note:: Add the section before updating it for the first time. """ assert isinstance(section, Section) assert isinstance(group, BarGroupType) # TODO screens selection for bar in Bar.everyone: bar.addSection(section, group=group) def addSection(self, section, group): assert isinstance(section, Section) assert isinstance(group, BarGroupType) self.groups[group].addSection(section) def update(self): if self.childsChanged: self.string = self.screen self.string += self.groups[BarGroupType.LEFT].string self.string += self.groups[BarGroupType.RIGHT].string self.childsChanged = False @staticmethod def updateAll(): Bar.string = "" for bar in Bar.everyone: bar.update() Bar.string += bar.string # Color for empty sections Bar.string += BarGroup.color(*Section.EMPTY) print(Bar.string) Bar.process.stdin.write(bytes(Bar.string + '\n', 'utf-8')) Bar.process.stdin.flush() class BarGroup: """ One for each group of each bar """ everyone = set() def __init__(self, groupType, parent): assert isinstance(groupType, BarGroupType) assert isinstance(parent, Bar) self.groupType = groupType self.parent = parent self.sections = list() self.string = '' self.parts = [] #: One of the sections that had their theme or visibility changed self.childsThemeChanged = False #: One of the sections that had their text (maybe their size) changed self.childsTextChanged = False BarGroup.everyone.add(self) def addSection(self, section): self.sections.append(section) section.parents.add(self) ALIGNS = {BarGroupType.LEFT: "%{l}", BarGroupType.RIGHT: "%{r}"} @staticmethod def fgColor(color): return "%{F" + color + "}" @staticmethod def bgColor(color): return "%{B" + color + "}" @staticmethod def color(fg, bg): return BarGroup.fgColor(fg) + BarGroup.bgColor(bg) def update(self): if self.childsThemeChanged: parts = [BarGroup.ALIGNS[self.groupType]] secs = [sec for sec in self.sections if sec.visible] lenS = len(secs) for s in range(lenS): sec = secs[s] theme = Section.THEMES[sec.theme] if self.groupType == BarGroupType.LEFT: oSec = secs[s + 1] if s < lenS - 1 else None else: oSec = secs[s - 1] if s > 1 else None oTheme = Section.THEMES[oSec.theme] \ if oSec is not None else Section.EMPTY if self.groupType == BarGroupType.LEFT: if s == 0: parts.append(BarGroup.bgColor(theme[1])) parts.append(BarGroup.fgColor(theme[0])) parts.append(sec) parts.append(BarGroup.color(theme[1], oTheme[1]) + "") else: parts.append(BarGroup.fgColor(theme[1]) + "") parts.append(BarGroup.color(*theme)) parts.append(sec) # TODO Concatenate successive strings self.parts = parts if self.childsTextChanged or self.childsThemeChanged: self.string = "" for part in self.parts: if isinstance(part, str): self.string += part elif isinstance(part, Section): self.string += part.curText self.parent.childsChanged = True self.childsThemeChanged = False self.childsTextChanged = False @staticmethod def updateAll(): for group in BarGroup.everyone: group.update() Bar.updateAll() class SectionThread(threading.Thread): def run(self): while Section.somethingChanged.wait(): Section.updateAll() while len(Section.sizeChanging) > 0: time.sleep(0.1) Section.updateAll() class Section: # TODO Update all of that to base16 COLORS = ['#002b36', '#dc322f', '#859900', '#b58900', '#268bd2', '#6c71c4', '#2aa198', '#93a1a1', '#657b83', '#dc322f', '#859900', '#b58900', '#268bd2', '#6c71c4', '#2aa198', '#fdf6e3'] FGCOLOR = '#93a1a1' BGCOLOR = '#002b36' THEMES = list() EMPTY = (FGCOLOR, BGCOLOR) @staticmethod def init(): for t in range(8, 16): Section.THEMES.append((Section.COLORS[0], Section.COLORS[t])) Section.updateThread = SectionThread(daemon=True) Section.updateThread.start() #: Sections that do not have their destination size sizeChanging = set() somethingChanged = threading.Event() updateThread = None def __init__(self, theme=0): #: Displayed section #: Note: A section can be empty and displayed! self.visible = False self.theme = theme #: Displayed text self.curText = '' #: Displayed text size self.curSize = 0 #: Destination text self.dstText = '' #: Destination size self.dstSize = 0 #: Groups that have this section self.parents = set() def __str__(self): return "<{}><{}>{:01d}{}{:02d}/{:02d}" \ .format(self.curText, self.dstText, self.theme, "+" if self.visible else "-", self.curSize, self.dstSize) def informParentsThemeChanged(self): for parent in self.parents: parent.childsThemeChanged = True def informParentsTextChanged(self): for parent in self.parents: parent.childsTextChanged = True def updateText(self, text): if len(text): self.dstText = ' {} '.format(text) self.dstSize = len(self.dstText) else: self.dstSize = 0 if self.curSize == self.dstSize: self.curText = self.dstText self.informParentsTextChanged() else: Section.sizeChanging.add(self) Section.somethingChanged.set() def updateTheme(self, theme): assert isinstance(theme, int) assert theme < len(Section.THEMES) self.theme = theme self.informParentsThemeChanged() Section.somethingChanged.set() def updateVisibility(self, visibility): assert isinstance(visibility, bool) self.visible = visibility self.informParentsThemeChanged() Section.somethingChanged.set() @staticmethod def fit(text, size): t = len(text) return text[:size] if t >= size else text + " " * (size - t) def update(self): # TODO Might profit of a better logic if not self.visible: self.updateVisibility(True) return if self.dstSize > self.curSize: self.curSize += 1 elif self.dstSize < self.curSize: self.curSize -= 1 else: # Visibility toggling must be done one step after curSize = 0 if self.dstSize == 0: self.updateVisibility(False) Section.sizeChanging.remove(self) return self.curText = Section.fit(self.dstText, self.curSize) self.informParentsTextChanged() @staticmethod def updateAll(): """ Process all sections for text size changes """ for sizeChanging in Section.sizeChanging.copy(): sizeChanging.update() BarGroup.updateAll() Section.somethingChanged.clear() if __name__ == '__main__': Bar.init() sec = Section(0) sech = Section(1) sec1 = Section(2) sec2 = Section(3) sec2h = Section(4) sec3 = Section(5) Bar.addSectionAll(sec, BarGroupType.LEFT) Bar.addSectionAll(sech, BarGroupType.LEFT) Bar.addSectionAll(sec1, BarGroupType.LEFT) Bar.addSectionAll(sec2, BarGroupType.RIGHT) Bar.addSectionAll(sec2h, BarGroupType.RIGHT) Bar.addSectionAll(sec3, BarGroupType.RIGHT) time.sleep(1) sec.updateText("A") time.sleep(1) sec.updateText("") time.sleep(1) sec.updateText("Hello") sec1.updateText("world!") sec2.updateText("Salut") sec2h.updateText("le") sec3.updateText("monde !") time.sleep(3) sech.updateText("the") sec2h.updateText("") time.sleep(2) sec.updateText("") sech.updateText("") sec1.updateText("") sec2.updateText("") sec2h.updateText("") sec3.updateText("") time.sleep(5)