#!/usr/bin/env python3 """ Beautiful script """ import subprocess import time import datetime import os import multiprocessing import i3ipc import difflib # Constants FONT = "DejaVu Sans Mono for Powerline" # 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)