328 lines
7.6 KiB
Python
Executable file
328 lines
7.6 KiB
Python
Executable file
#!/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)
|