#!/usr/bin/env python3 """ Beautiful script """ import subprocess import time import datetime import os import multiprocessing import i3ipc import difflib # Constants FONT = "DejaVuSansMono Nerd Font Mono" # TODO Update to be in sync with base16 thm = [ "#002b36", "#dc322f", "#859900", "#b58900", "#268bd2", "#6c71c4", "#2aa198", "#93a1a1", "#657b83", "#dc322f", "#859900", "#b58900", "#268bd2", "#6c71c4", "#2aa198", "#fdf6e3", ] fg = "#93a1a1" bg = "#002b36" THEMES = { "CENTER": (fg, bg), "DEFAULT": (thm[0], thm[8]), "1": (thm[0], thm[9]), "2": (thm[0], thm[10]), "3": (thm[0], thm[11]), "4": (thm[0], thm[12]), "5": (thm[0], thm[13]), "6": (thm[0], thm[14]), "7": (thm[0], thm[15]), } # Utils def fitText(text, size): """ Add spaces or cut a string to be `size` characters long """ if size > 0: t = len(text) if t >= size: return text[:size] else: diff = size - t return text + " " * diff else: return "" def fgColor(theme): global THEMES return THEMES[theme][0] def bgColor(theme): global THEMES return THEMES[theme][1] class Section: def __init__(self, theme="DEFAULT"): self.text = "" self.size = 0 self.toSize = 0 self.theme = theme self.visible = False self.name = "" def update(self, text): if text == "": self.toSize = 0 else: if len(text) < len(self.text): self.text = text + self.text[len(text) :] else: self.text = text self.toSize = len(text) + 3 def updateSize(self): """ Set the size for the next frame of animation Return if another frame is needed """ if self.toSize > self.size: self.size += 1 elif self.toSize < self.size: self.size -= 1 self.visible = self.size return self.toSize == self.size def draw(self, left=True, nextTheme="DEFAULT"): s = "" if self.visible: if not left: if self.theme == nextTheme: s += "" else: s += "%{F" + bgColor(self.theme) + "}" s += "%{B" + bgColor(nextTheme) + "}" s += "" s += "%{F" + fgColor(self.theme) + "}" s += "%{B" + bgColor(self.theme) + "}" s += " " if self.size > 1 else "" s += fitText(self.text, self.size - 3) s += " " if self.size > 2 else "" if left: if self.theme == nextTheme: s += "" else: s += "%{F" + bgColor(self.theme) + "}" s += "%{B" + bgColor(nextTheme) + "}" s += "" return s # Section definition sTime = Section("3") hostname = os.environ["HOSTNAME"].split(".")[0] sHost = Section("2") sHost.update( os.environ["USER"] + "@" + hostname.split("-")[-1] if "-" in hostname else hostname ) # Groups definition gLeft = [] gRight = [sTime, sHost] # Bar handling bar = subprocess.Popen(["lemonbar", "-f", FONT, "-b"], stdin=subprocess.PIPE) def updateBar(): global timeLastUpdate, timeUpdate global gLeft, gRight global outputs text = "" for oi in range(len(outputs)): output = outputs[oi] gLeftFiltered = list( filter( lambda s: s.visible and (not s.output or s.output == output.name), gLeft ) ) tLeft = "" l = len(gLeftFiltered) for gi in range(l): g = gLeftFiltered[gi] # Next visible section for transition nextTheme = gLeftFiltered[gi + 1].theme if gi + 1 < l else "CENTER" tLeft = tLeft + g.draw(True, nextTheme) tRight = "" for gi in range(len(gRight)): g = gRight[gi] nextTheme = "CENTER" for gn in gRight[gi + 1 :]: if gn.visible: nextTheme = gn.theme break tRight = g.draw(False, nextTheme) + tRight text += ( "%{l}" + tLeft + "%{r}" + tRight + "%{B" + bgColor("CENTER") + "}" + "%{S" + str(oi + 1) + "}" ) bar.stdin.write(bytes(text + "\n", "utf-8")) bar.stdin.flush() # Values i3 = i3ipc.Connection() outputs = [] def on_output(): global outputs outputs = sorted( sorted( list(filter(lambda o: o.active, i3.get_outputs())), key=lambda o: o.rect.y ), key=lambda o: o.rect.x, ) on_output() def on_workspace_focus(): global i3 global gLeft workspaces = i3.get_workspaces() wNames = [w.name for w in workspaces] sNames = [s.name for s in gLeft] newGLeft = [] def actuate(section, workspace): if workspace: section.name = workspace.name section.output = workspace.output if workspace.visible: section.update(workspace.name) else: section.update(workspace.name.split(" ")[0]) if workspace.focused: section.theme = "4" elif workspace.urgent: section.theme = "1" else: section.theme = "6" else: section.update("") section.theme = "6" for tag, i, j, k, l in difflib.SequenceMatcher(None, sNames, wNames).get_opcodes(): if tag == "equal": # If the workspaces didn't changed for a in range(j - i): workspace = workspaces[k + a] section = gLeft[i + a] actuate(section, workspace) newGLeft.append(section) if tag in ("delete", "replace"): # If the workspaces were removed for section in gLeft[i:j]: if section.visible: actuate(section, None) newGLeft.append(section) else: del section if tag in ("insert", "replace"): # If the workspaces were removed for workspace in workspaces[k:l]: section = Section() actuate(section, workspace) newGLeft.append(section) gLeft = newGLeft updateBar() on_workspace_focus() def i3events(i3childPipe): global i3 # Proxy functions def on_workspace_focus(i3, e): global i3childPipe i3childPipe.send("on_workspace_focus") i3.on("workspace::focus", on_workspace_focus) def on_output(i3, e): global i3childPipe i3childPipe.send("on_output") i3.on("output", on_output) i3.main() i3parentPipe, i3childPipe = multiprocessing.Pipe() i3process = multiprocessing.Process(target=i3events, args=(i3childPipe,)) i3process.start() def updateValues(): # Time now = datetime.datetime.now() sTime.update(now.strftime("%x %X")) def updateAnimation(): for s in set(gLeft + gRight): s.updateSize() updateBar() lastUpdate = 0 while True: now = time.time() if i3parentPipe.poll(): msg = i3parentPipe.recv() if msg == "on_workspace_focus": on_workspace_focus() elif msg == "on_output": on_output() # TODO Restart lemonbar else: print(msg) updateAnimation() if now >= lastUpdate + 1: updateValues() lastUpdate = now time.sleep(0.05)