Bar bar bar
This commit is contained in:
parent
1c7efc4a76
commit
7987cdcaae
|
@ -1,161 +1,22 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
"""
|
from providers import *
|
||||||
Debugging script
|
|
||||||
"""
|
|
||||||
|
|
||||||
import i3ipc
|
if __name__ == "__main__":
|
||||||
import os
|
Bar.init()
|
||||||
import psutil
|
Updater.init()
|
||||||
# import alsaaudio
|
|
||||||
from time import time
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
i3 = i3ipc.Connection()
|
Bar.addSectionAll(I3Provider(), BarGroupType.LEFT)
|
||||||
lemonbar = subprocess.Popen(['lemonbar', '-b'], stdin=subprocess.PIPE)
|
|
||||||
|
|
||||||
# Utils
|
# TODO CPU provider
|
||||||
def upChart(p):
|
# TODO RAM provider
|
||||||
block = ' ▁▂▃▄▅▆▇█'
|
# TODO Temperature provider
|
||||||
return block[round(p * (len(block)-1))]
|
# TODO Disk space provider
|
||||||
|
# TODO Screen (connected, autorandr configuration, bbswitch) provider
|
||||||
def humanSizeOf(num, suffix='B'): # TODO Credit
|
# TODO Unlocked keys provider
|
||||||
for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
|
# TODO Mail provider
|
||||||
if abs(num) < 1024.0:
|
Bar.addSectionAll(TodoProvider(dir='~/.vdirsyncer/currentCalendars/'), BarGroupType.RIGHT)
|
||||||
return "%3.0f%2s%s" % (num, unit, suffix)
|
Bar.addSectionAll(NetworkProvider(), BarGroupType.RIGHT)
|
||||||
num /= 1024.0
|
Bar.addSectionAll(PulseaudioProvider(), BarGroupType.RIGHT)
|
||||||
return "%.0f%2s%s" % (num, 'Yi', suffix)
|
Bar.addSectionAll(BatteryProvider(), BarGroupType.RIGHT)
|
||||||
|
Bar.addSectionAll(TimeProvider(), BarGroupType.RIGHT)
|
||||||
# Values
|
|
||||||
mode = ''
|
|
||||||
container = i3.get_tree().find_focused()
|
|
||||||
workspaces = i3.get_workspaces()
|
|
||||||
outputs = i3.get_outputs()
|
|
||||||
|
|
||||||
username = os.environ['USER']
|
|
||||||
hostname = os.environ['HOSTNAME']
|
|
||||||
if '-' in hostname:
|
|
||||||
hostname = hostname.split('-')[-1]
|
|
||||||
|
|
||||||
oldNetIO = dict()
|
|
||||||
oldTime = time()
|
|
||||||
|
|
||||||
def update():
|
|
||||||
activeOutputs = sorted(sorted(list(filter(lambda o: o.active, outputs)), key=lambda o: o.rect.y), key=lambda o: o.rect.x)
|
|
||||||
z = ''
|
|
||||||
for aOutput in range(len(activeOutputs)):
|
|
||||||
output = activeOutputs[aOutput]
|
|
||||||
# Mode || Workspaces
|
|
||||||
t = []
|
|
||||||
if (mode != ''):
|
|
||||||
t.append(mode)
|
|
||||||
else:
|
|
||||||
t.append(' '.join([(w.name.upper() if w.focused else w.name) for w in workspaces if w.output == output.name]))
|
|
||||||
|
|
||||||
# Windows Title
|
|
||||||
#if container:
|
|
||||||
# t.append(container.name)
|
|
||||||
|
|
||||||
# CPU
|
|
||||||
t.append('C' + ''.join([upChart(p/100) for p in psutil.cpu_percent(percpu=True)]))
|
|
||||||
|
|
||||||
# Memory
|
|
||||||
t.append('M' + str(round(psutil.virtual_memory().percent)) + '% ' +
|
|
||||||
'S' + str(round(psutil.swap_memory().percent)) + '%')
|
|
||||||
|
|
||||||
# Disks
|
|
||||||
d = []
|
|
||||||
for disk in psutil.disk_partitions():
|
|
||||||
e = ''
|
|
||||||
if disk.device.startswith('/dev/sd'):
|
|
||||||
e += 'S' + disk.device[-2:].upper()
|
|
||||||
elif disk.device.startswith('/dev/mmcblk'):
|
|
||||||
e += 'M' + disk.device[-3] + disk.device[-1]
|
|
||||||
else:
|
|
||||||
e += '?'
|
|
||||||
e += ' '
|
|
||||||
e += str(round(psutil.disk_usage(disk.mountpoint).percent)) + '%'
|
|
||||||
d.append(e)
|
|
||||||
t.append(' '.join(d))
|
|
||||||
|
|
||||||
# Network
|
|
||||||
netStats = psutil.net_if_stats()
|
|
||||||
netIO = psutil.net_io_counters(pernic=True)
|
|
||||||
net = []
|
|
||||||
for iface in filter(lambda i: i != 'lo' and netStats[i].isup, netStats.keys()):
|
|
||||||
s = ''
|
|
||||||
if iface.startswith('eth'):
|
|
||||||
s += 'E'
|
|
||||||
elif iface.startswith('wlan'):
|
|
||||||
s += 'W'
|
|
||||||
else:
|
|
||||||
s += '?'
|
|
||||||
|
|
||||||
s += ' '
|
|
||||||
now = time()
|
|
||||||
global oldNetIO, oldTime
|
|
||||||
|
|
||||||
sent = ((oldNetIO[iface].bytes_sent if iface in oldNetIO else 0) - (netIO[iface].bytes_sent if iface in netIO else 0)) / (oldTime - now)
|
|
||||||
recv = ((oldNetIO[iface].bytes_recv if iface in oldNetIO else 0) - (netIO[iface].bytes_recv if iface in netIO else 0)) / (oldTime - now)
|
|
||||||
s += '↓' + humanSizeOf(abs(recv), 'B/s') + ' ↑' + humanSizeOf(abs(sent), 'B/s')
|
|
||||||
|
|
||||||
oldNetIO = netIO
|
|
||||||
oldTime = now
|
|
||||||
|
|
||||||
net.append(s)
|
|
||||||
t.append(' '.join(net))
|
|
||||||
|
|
||||||
# Battery
|
|
||||||
if os.path.isdir('/sys/class/power_supply/BAT0'):
|
|
||||||
with open('/sys/class/power_supply/BAT0/charge_now') as f:
|
|
||||||
charge_now = int(f.read())
|
|
||||||
with open('/sys/class/power_supply/BAT0/charge_full_design') as f:
|
|
||||||
charge_full = int(f.read())
|
|
||||||
t.append('B' + str(round(100*charge_now/charge_full)) + '%')
|
|
||||||
|
|
||||||
# Volume
|
|
||||||
# t.append('V ' + str(alsaaudio.Mixer('Master').getvolume()[0]) + '%')
|
|
||||||
|
|
||||||
t.append(username + '@' + hostname)
|
|
||||||
|
|
||||||
# print(' - '.join(t))
|
|
||||||
# t = [output.name]
|
|
||||||
|
|
||||||
z += ' - '.join(t) + '%{S' + str(aOutput + 1) + '}'
|
|
||||||
#lemonbar.stdin.write(bytes(' - '.join(t), 'utf-8'))
|
|
||||||
#lemonbar.stdin.write(bytes('%{S' + str(aOutput + 1) + '}', 'utf-8'))
|
|
||||||
|
|
||||||
lemonbar.stdin.write(bytes(z+'\n', 'utf-8'))
|
|
||||||
lemonbar.stdin.flush()
|
|
||||||
|
|
||||||
# Event listeners
|
|
||||||
def on_mode(i3, e):
|
|
||||||
global mode
|
|
||||||
if (e.change == 'default'):
|
|
||||||
mode = ''
|
|
||||||
else :
|
|
||||||
mode = e.change
|
|
||||||
update()
|
|
||||||
|
|
||||||
i3.on("mode", on_mode)
|
|
||||||
|
|
||||||
#def on_window_focus(i3, e):
|
|
||||||
# global container
|
|
||||||
# container = e.container
|
|
||||||
# update()
|
|
||||||
#
|
|
||||||
#i3.on("window::focus", on_window_focus)
|
|
||||||
|
|
||||||
def on_workspace_focus(i3, e):
|
|
||||||
global workspaces
|
|
||||||
workspaces = i3.get_workspaces()
|
|
||||||
update()
|
|
||||||
|
|
||||||
i3.on("workspace::focus", on_workspace_focus)
|
|
||||||
|
|
||||||
# Starting
|
|
||||||
|
|
||||||
update()
|
|
||||||
|
|
||||||
|
|
||||||
i3.main()
|
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import enum
|
import enum
|
||||||
import logging
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import subprocess
|
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 Allow deletion of Bar, BarGroup and Section for screen changes
|
||||||
|
# IDEA Use i3 ipc events rather than relying on xrandr or Xlib (less portable
|
||||||
|
# but easier)
|
||||||
# TODO Optimize to use write() calls instead of string concatenation (writing
|
# TODO Optimize to use write() calls instead of string concatenation (writing
|
||||||
# BarGroup strings should be a good compromise)
|
# BarGroup strings should be a good compromise)
|
||||||
# TODO Use bytes rather than strings
|
# TODO Use bytes rather than strings
|
||||||
# TODO Use default colors of lemonbar sometimes
|
# TODO Use default colors of lemonbar sometimes
|
||||||
|
# TODO Mouse actions
|
||||||
|
|
||||||
|
|
||||||
class BarGroupType(enum.Enum):
|
class BarGroupType(enum.Enum):
|
||||||
LEFT = 0
|
LEFT = 0
|
||||||
RIGHT = 1
|
RIGHT = 1
|
||||||
|
# TODO Middle
|
||||||
# MID_LEFT = 2
|
# MID_LEFT = 2
|
||||||
# MID_RIGHT = 3
|
# MID_RIGHT = 3
|
||||||
|
|
||||||
|
@ -28,24 +29,31 @@ class Bar:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
FONT = "DejaVu Sans Mono for Powerline"
|
FONTS = ["DejaVu Sans Mono for Powerline", "Font Awesome"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init():
|
def init():
|
||||||
Section.init()
|
Section.init()
|
||||||
|
|
||||||
cmd = ['lemonbar', '-f', Bar.FONT, '-b']
|
cmd = ['lemonbar', '-b']
|
||||||
|
for font in Bar.FONTS:
|
||||||
|
cmd += ["-f", font]
|
||||||
Bar.process = subprocess.Popen(cmd, stdin=subprocess.PIPE)
|
Bar.process = subprocess.Popen(cmd, stdin=subprocess.PIPE)
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
# Bar(0)
|
Bar(0)
|
||||||
Bar(1)
|
# Bar(1)
|
||||||
|
|
||||||
# Class globals
|
# Class globals
|
||||||
everyone = set()
|
everyone = set()
|
||||||
string = ""
|
string = ""
|
||||||
process = None
|
process = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def forever():
|
||||||
|
while True:
|
||||||
|
time.sleep(60)
|
||||||
|
|
||||||
def __init__(self, screen):
|
def __init__(self, screen):
|
||||||
assert isinstance(screen, int)
|
assert isinstance(screen, int)
|
||||||
self.screen = "%{S" + str(screen) + "}"
|
self.screen = "%{S" + str(screen) + "}"
|
||||||
|
@ -94,7 +102,7 @@ class Bar:
|
||||||
# Color for empty sections
|
# Color for empty sections
|
||||||
Bar.string += BarGroup.color(*Section.EMPTY)
|
Bar.string += BarGroup.color(*Section.EMPTY)
|
||||||
|
|
||||||
print(Bar.string)
|
# print(Bar.string)
|
||||||
Bar.process.stdin.write(bytes(Bar.string + '\n', 'utf-8'))
|
Bar.process.stdin.write(bytes(Bar.string + '\n', 'utf-8'))
|
||||||
Bar.process.stdin.flush()
|
Bar.process.stdin.flush()
|
||||||
|
|
||||||
|
@ -127,7 +135,12 @@ class BarGroup:
|
||||||
|
|
||||||
def addSection(self, section):
|
def addSection(self, section):
|
||||||
self.sections.append(section)
|
self.sections.append(section)
|
||||||
section.parents.add(self)
|
section.addParent(self)
|
||||||
|
|
||||||
|
def addSectionAfter(self, sectionRef, section):
|
||||||
|
index = self.sections.index(sectionRef)
|
||||||
|
self.sections.insert(index + 1, section)
|
||||||
|
section.addParent(self)
|
||||||
|
|
||||||
ALIGNS = {BarGroupType.LEFT: "%{l}", BarGroupType.RIGHT: "%{r}"}
|
ALIGNS = {BarGroupType.LEFT: "%{l}", BarGroupType.RIGHT: "%{r}"}
|
||||||
|
|
||||||
|
@ -155,7 +168,7 @@ class BarGroup:
|
||||||
if self.groupType == BarGroupType.LEFT:
|
if self.groupType == BarGroupType.LEFT:
|
||||||
oSec = secs[s + 1] if s < lenS - 1 else None
|
oSec = secs[s + 1] if s < lenS - 1 else None
|
||||||
else:
|
else:
|
||||||
oSec = secs[s - 1] if s > 1 else None
|
oSec = secs[s - 1] if s > 0 else None
|
||||||
oTheme = Section.THEMES[oSec.theme] \
|
oTheme = Section.THEMES[oSec.theme] \
|
||||||
if oSec is not None else Section.EMPTY
|
if oSec is not None else Section.EMPTY
|
||||||
|
|
||||||
|
@ -164,10 +177,16 @@ class BarGroup:
|
||||||
parts.append(BarGroup.bgColor(theme[1]))
|
parts.append(BarGroup.bgColor(theme[1]))
|
||||||
parts.append(BarGroup.fgColor(theme[0]))
|
parts.append(BarGroup.fgColor(theme[0]))
|
||||||
parts.append(sec)
|
parts.append(sec)
|
||||||
parts.append(BarGroup.color(theme[1], oTheme[1]) + "")
|
if theme == oTheme:
|
||||||
|
parts.append("")
|
||||||
|
else:
|
||||||
|
parts.append(BarGroup.color(theme[1], oTheme[1]) + "")
|
||||||
else:
|
else:
|
||||||
parts.append(BarGroup.fgColor(theme[1]) + "")
|
if theme is oTheme:
|
||||||
parts.append(BarGroup.color(*theme))
|
parts.append("")
|
||||||
|
else:
|
||||||
|
parts.append(BarGroup.fgColor(theme[1]) + "")
|
||||||
|
parts.append(BarGroup.color(*theme))
|
||||||
parts.append(sec)
|
parts.append(sec)
|
||||||
|
|
||||||
|
|
||||||
|
@ -189,21 +208,29 @@ class BarGroup:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def updateAll():
|
def updateAll():
|
||||||
|
|
||||||
for group in BarGroup.everyone:
|
for group in BarGroup.everyone:
|
||||||
|
|
||||||
group.update()
|
group.update()
|
||||||
|
|
||||||
Bar.updateAll()
|
Bar.updateAll()
|
||||||
|
|
||||||
|
|
||||||
class SectionThread(threading.Thread):
|
class SectionThread(threading.Thread):
|
||||||
|
ANIMATION_START = 0.025
|
||||||
|
ANIMATION_STOP = 0.001
|
||||||
|
ANIMATION_EVOLUTION = 0.9
|
||||||
def run(self):
|
def run(self):
|
||||||
while Section.somethingChanged.wait():
|
while Section.somethingChanged.wait():
|
||||||
Section.updateAll()
|
Section.updateAll()
|
||||||
|
animTime = self.ANIMATION_START
|
||||||
|
frameTime = time.perf_counter()
|
||||||
while len(Section.sizeChanging) > 0:
|
while len(Section.sizeChanging) > 0:
|
||||||
time.sleep(0.1)
|
frameTime += animTime
|
||||||
|
curTime = time.perf_counter()
|
||||||
|
sleepTime = frameTime - curTime
|
||||||
|
time.sleep(sleepTime if sleepTime > 0 else 0)
|
||||||
Section.updateAll()
|
Section.updateAll()
|
||||||
|
animTime *= self.ANIMATION_EVOLUTION
|
||||||
|
if animTime < self.ANIMATION_STOP:
|
||||||
|
animTime = self.ANIMATION_STOP
|
||||||
|
|
||||||
|
|
||||||
class Section:
|
class Section:
|
||||||
|
@ -218,19 +245,18 @@ class Section:
|
||||||
THEMES = list()
|
THEMES = list()
|
||||||
EMPTY = (FGCOLOR, BGCOLOR)
|
EMPTY = (FGCOLOR, BGCOLOR)
|
||||||
|
|
||||||
|
#: Sections that do not have their destination size
|
||||||
|
sizeChanging = set()
|
||||||
|
updateThread = SectionThread(daemon=True)
|
||||||
|
somethingChanged = threading.Event()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init():
|
def init():
|
||||||
for t in range(8, 16):
|
for t in range(8, 16):
|
||||||
Section.THEMES.append((Section.COLORS[0], Section.COLORS[t]))
|
Section.THEMES.append((Section.COLORS[0], Section.COLORS[t]))
|
||||||
|
|
||||||
Section.updateThread = SectionThread(daemon=True)
|
|
||||||
Section.updateThread.start()
|
Section.updateThread.start()
|
||||||
|
|
||||||
#: Sections that do not have their destination size
|
|
||||||
sizeChanging = set()
|
|
||||||
somethingChanged = threading.Event()
|
|
||||||
updateThread = None
|
|
||||||
|
|
||||||
def __init__(self, theme=0):
|
def __init__(self, theme=0):
|
||||||
#: Displayed section
|
#: Displayed section
|
||||||
#: Note: A section can be empty and displayed!
|
#: Note: A section can be empty and displayed!
|
||||||
|
@ -252,10 +278,21 @@ class Section:
|
||||||
self.parents = set()
|
self.parents = set()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<{}><{}>{:01d}{}{:02d}/{:02d}" \
|
try:
|
||||||
.format(self.curText, self.dstText,
|
return "<{}><{}>{:01d}{}{:02d}/{:02d}" \
|
||||||
self.theme, "+" if self.visible else "-",
|
.format(self.curText, self.dstText,
|
||||||
self.curSize, self.dstSize)
|
self.theme, "+" if self.visible else "-",
|
||||||
|
self.curSize, self.dstSize)
|
||||||
|
except:
|
||||||
|
return super().__str__()
|
||||||
|
|
||||||
|
def addParent(self, parent):
|
||||||
|
self.parents.add(parent)
|
||||||
|
|
||||||
|
def appendAfter(self, section):
|
||||||
|
assert len(self.parents)
|
||||||
|
for parent in self.parents:
|
||||||
|
parent.addSectionAfter(self, section)
|
||||||
|
|
||||||
def informParentsThemeChanged(self):
|
def informParentsThemeChanged(self):
|
||||||
for parent in self.parents:
|
for parent in self.parents:
|
||||||
|
@ -267,8 +304,15 @@ class Section:
|
||||||
|
|
||||||
def updateText(self, text):
|
def updateText(self, text):
|
||||||
if len(text):
|
if len(text):
|
||||||
self.dstText = ' {} '.format(text)
|
if isinstance(text, str):
|
||||||
self.dstSize = len(self.dstText)
|
text = [text]
|
||||||
|
text = [' '] + text + [' ']
|
||||||
|
|
||||||
|
raw = [(t if isinstance(t, str) else t['text']) for t in text]
|
||||||
|
self.dstSize = sum([len(t) for t in raw])
|
||||||
|
# TODO FEAT Handle colors
|
||||||
|
# TODO OPTI Not like that
|
||||||
|
self.dstText = ''.join(raw)
|
||||||
else:
|
else:
|
||||||
self.dstSize = 0
|
self.dstSize = 0
|
||||||
|
|
||||||
|
@ -331,6 +375,9 @@ class Section:
|
||||||
|
|
||||||
Section.somethingChanged.clear()
|
Section.somethingChanged.clear()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ramp(p, ramp=" ▁▂▃▄▅▆▇█"):
|
||||||
|
return ramp[round(p * (len(ramp)-1))]
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
Bar.init()
|
Bar.init()
|
29
config/lemonbar/net.py
Executable file
29
config/lemonbar/net.py
Executable file
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import psutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
netStats = psutil.net_if_stats()
|
||||||
|
netAddrs = psutil.net_if_addrs()
|
||||||
|
netIO = psutil.net_io_counters(pernic=True)
|
||||||
|
|
||||||
|
for iface in netStats.keys():
|
||||||
|
if not netStats[iface].isup or iface.startswith('lo'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
ssid = ''
|
||||||
|
if iface.startswith('eth') or iface.startswith('enp'):
|
||||||
|
icon = 'E'
|
||||||
|
elif iface.startswith('wlan') or iface.startswith('wlp'):
|
||||||
|
icon = 'W'
|
||||||
|
cmd = ["iwgetid", iface, "--raw"]
|
||||||
|
p = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||||
|
ssid = p.stdout.strip().decode()
|
||||||
|
# TODO Real icons
|
||||||
|
# TODO USB tethering
|
||||||
|
# TODO tan / tun
|
||||||
|
else:
|
||||||
|
icon = '?'
|
||||||
|
|
||||||
|
print(icon, iface, ssid)
|
||||||
|
print(netIO[iface])
|
161
config/lemonbar/oldbar.py
Executable file
161
config/lemonbar/oldbar.py
Executable file
|
@ -0,0 +1,161 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Debugging script
|
||||||
|
"""
|
||||||
|
|
||||||
|
import i3ipc
|
||||||
|
import os
|
||||||
|
import psutil
|
||||||
|
# import alsaaudio
|
||||||
|
from time import time
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
i3 = i3ipc.Connection()
|
||||||
|
lemonbar = subprocess.Popen(['lemonbar', '-b'], stdin=subprocess.PIPE)
|
||||||
|
|
||||||
|
# Utils
|
||||||
|
def upChart(p):
|
||||||
|
block = ' ▁▂▃▄▅▆▇█'
|
||||||
|
return block[round(p * (len(block)-1))]
|
||||||
|
|
||||||
|
def humanSizeOf(num, suffix='B'): # TODO Credit
|
||||||
|
for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
|
||||||
|
if abs(num) < 1024.0:
|
||||||
|
return "%3.0f%2s%s" % (num, unit, suffix)
|
||||||
|
num /= 1024.0
|
||||||
|
return "%.0f%2s%s" % (num, 'Yi', suffix)
|
||||||
|
|
||||||
|
# Values
|
||||||
|
mode = ''
|
||||||
|
container = i3.get_tree().find_focused()
|
||||||
|
workspaces = i3.get_workspaces()
|
||||||
|
outputs = i3.get_outputs()
|
||||||
|
|
||||||
|
username = os.environ['USER']
|
||||||
|
hostname = os.environ['HOSTNAME']
|
||||||
|
if '-' in hostname:
|
||||||
|
hostname = hostname.split('-')[-1]
|
||||||
|
|
||||||
|
oldNetIO = dict()
|
||||||
|
oldTime = time()
|
||||||
|
|
||||||
|
def update():
|
||||||
|
activeOutputs = sorted(sorted(list(filter(lambda o: o.active, outputs)), key=lambda o: o.rect.y), key=lambda o: o.rect.x)
|
||||||
|
z = ''
|
||||||
|
for aOutput in range(len(activeOutputs)):
|
||||||
|
output = activeOutputs[aOutput]
|
||||||
|
# Mode || Workspaces
|
||||||
|
t = []
|
||||||
|
if (mode != ''):
|
||||||
|
t.append(mode)
|
||||||
|
else:
|
||||||
|
t.append(' '.join([(w.name.upper() if w.focused else w.name) for w in workspaces if w.output == output.name]))
|
||||||
|
|
||||||
|
# Windows Title
|
||||||
|
#if container:
|
||||||
|
# t.append(container.name)
|
||||||
|
|
||||||
|
# CPU
|
||||||
|
t.append('C' + ''.join([upChart(p/100) for p in psutil.cpu_percent(percpu=True)]))
|
||||||
|
|
||||||
|
# Memory
|
||||||
|
t.append('M' + str(round(psutil.virtual_memory().percent)) + '% ' +
|
||||||
|
'S' + str(round(psutil.swap_memory().percent)) + '%')
|
||||||
|
|
||||||
|
# Disks
|
||||||
|
d = []
|
||||||
|
for disk in psutil.disk_partitions():
|
||||||
|
e = ''
|
||||||
|
if disk.device.startswith('/dev/sd'):
|
||||||
|
e += 'S' + disk.device[-2:].upper()
|
||||||
|
elif disk.device.startswith('/dev/mmcblk'):
|
||||||
|
e += 'M' + disk.device[-3] + disk.device[-1]
|
||||||
|
else:
|
||||||
|
e += '?'
|
||||||
|
e += ' '
|
||||||
|
e += str(round(psutil.disk_usage(disk.mountpoint).percent)) + '%'
|
||||||
|
d.append(e)
|
||||||
|
t.append(' '.join(d))
|
||||||
|
|
||||||
|
# Network
|
||||||
|
netStats = psutil.net_if_stats()
|
||||||
|
netIO = psutil.net_io_counters(pernic=True)
|
||||||
|
net = []
|
||||||
|
for iface in filter(lambda i: i != 'lo' and netStats[i].isup, netStats.keys()):
|
||||||
|
s = ''
|
||||||
|
if iface.startswith('eth'):
|
||||||
|
s += 'E'
|
||||||
|
elif iface.startswith('wlan'):
|
||||||
|
s += 'W'
|
||||||
|
else:
|
||||||
|
s += '?'
|
||||||
|
|
||||||
|
s += ' '
|
||||||
|
now = time()
|
||||||
|
global oldNetIO, oldTime
|
||||||
|
|
||||||
|
sent = ((oldNetIO[iface].bytes_sent if iface in oldNetIO else 0) - (netIO[iface].bytes_sent if iface in netIO else 0)) / (oldTime - now)
|
||||||
|
recv = ((oldNetIO[iface].bytes_recv if iface in oldNetIO else 0) - (netIO[iface].bytes_recv if iface in netIO else 0)) / (oldTime - now)
|
||||||
|
s += '↓' + humanSizeOf(abs(recv), 'B/s') + ' ↑' + humanSizeOf(abs(sent), 'B/s')
|
||||||
|
|
||||||
|
oldNetIO = netIO
|
||||||
|
oldTime = now
|
||||||
|
|
||||||
|
net.append(s)
|
||||||
|
t.append(' '.join(net))
|
||||||
|
|
||||||
|
# Battery
|
||||||
|
if os.path.isdir('/sys/class/power_supply/BAT0'):
|
||||||
|
with open('/sys/class/power_supply/BAT0/charge_now') as f:
|
||||||
|
charge_now = int(f.read())
|
||||||
|
with open('/sys/class/power_supply/BAT0/charge_full_design') as f:
|
||||||
|
charge_full = int(f.read())
|
||||||
|
t.append('B' + str(round(100*charge_now/charge_full)) + '%')
|
||||||
|
|
||||||
|
# Volume
|
||||||
|
# t.append('V ' + str(alsaaudio.Mixer('Master').getvolume()[0]) + '%')
|
||||||
|
|
||||||
|
t.append(username + '@' + hostname)
|
||||||
|
|
||||||
|
# print(' - '.join(t))
|
||||||
|
# t = [output.name]
|
||||||
|
|
||||||
|
z += ' - '.join(t) + '%{S' + str(aOutput + 1) + '}'
|
||||||
|
#lemonbar.stdin.write(bytes(' - '.join(t), 'utf-8'))
|
||||||
|
#lemonbar.stdin.write(bytes('%{S' + str(aOutput + 1) + '}', 'utf-8'))
|
||||||
|
|
||||||
|
lemonbar.stdin.write(bytes(z+'\n', 'utf-8'))
|
||||||
|
lemonbar.stdin.flush()
|
||||||
|
|
||||||
|
# Event listeners
|
||||||
|
def on_mode(i3, e):
|
||||||
|
global mode
|
||||||
|
if (e.change == 'default'):
|
||||||
|
mode = ''
|
||||||
|
else :
|
||||||
|
mode = e.change
|
||||||
|
update()
|
||||||
|
|
||||||
|
i3.on("mode", on_mode)
|
||||||
|
|
||||||
|
#def on_window_focus(i3, e):
|
||||||
|
# global container
|
||||||
|
# container = e.container
|
||||||
|
# update()
|
||||||
|
#
|
||||||
|
#i3.on("window::focus", on_window_focus)
|
||||||
|
|
||||||
|
def on_workspace_focus(i3, e):
|
||||||
|
global workspaces
|
||||||
|
workspaces = i3.get_workspaces()
|
||||||
|
update()
|
||||||
|
|
||||||
|
i3.on("workspace::focus", on_workspace_focus)
|
||||||
|
|
||||||
|
# Starting
|
||||||
|
|
||||||
|
update()
|
||||||
|
|
||||||
|
|
||||||
|
i3.main()
|
397
config/lemonbar/providers.py
Executable file
397
config/lemonbar/providers.py
Executable file
|
@ -0,0 +1,397 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from updaters import *
|
||||||
|
from display import *
|
||||||
|
import pulsectl
|
||||||
|
import psutil
|
||||||
|
import subprocess
|
||||||
|
import socket
|
||||||
|
import ipaddress
|
||||||
|
import logging
|
||||||
|
import coloredlogs
|
||||||
|
import json
|
||||||
|
import i3ipc
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s')
|
||||||
|
log = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
def humanSize(num):
|
||||||
|
"""
|
||||||
|
Returns a string of width 3+3
|
||||||
|
"""
|
||||||
|
for unit in ('B ','KiB','MiB','GiB','TiB','PiB','EiB','ZiB'):
|
||||||
|
if abs(num) < 1000:
|
||||||
|
if num >= 10:
|
||||||
|
return "{:3d}{}".format(int(num), unit)
|
||||||
|
else:
|
||||||
|
return "{:.1f}{}".format(num, unit)
|
||||||
|
num /= 1024.0
|
||||||
|
return "{:d}YiB".format(num)
|
||||||
|
|
||||||
|
|
||||||
|
class TimeProvider(Section, PeriodicUpdater):
|
||||||
|
def fetcher(self):
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
return now.strftime('%d/%m/%y %H:%M:%S')
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
Section.__init__(self)
|
||||||
|
self.changeInterval(1)
|
||||||
|
|
||||||
|
|
||||||
|
class BatteryProvider(Section, PeriodicUpdater):
|
||||||
|
|
||||||
|
RAMP = ""
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
with open(self.batdir + 'status') as f:
|
||||||
|
status = f.read().strip()
|
||||||
|
if status == "Full":
|
||||||
|
return ""
|
||||||
|
elif status == "Discharging":
|
||||||
|
icon = ""
|
||||||
|
elif status == "Charging":
|
||||||
|
icon = ""
|
||||||
|
else:
|
||||||
|
log.warn("Unknwon battery status: {}".format(status))
|
||||||
|
icon = "?"
|
||||||
|
with open(self.batdir + 'capacity') as f:
|
||||||
|
capacity = int(f.read())
|
||||||
|
icon += self.ramp(capacity/100, self.RAMP)
|
||||||
|
return '{} {}%'.format(icon, capacity)
|
||||||
|
|
||||||
|
def __init__(self, battery='BAT0'):
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
Section.__init__(self)
|
||||||
|
|
||||||
|
self.batdir = '/sys/class/power_supply/{}/'.format(battery)
|
||||||
|
assert os.path.isdir(self.batdir)
|
||||||
|
|
||||||
|
self.changeInterval(5)
|
||||||
|
|
||||||
|
|
||||||
|
class PulseaudioProvider(Section, ThreadedUpdater):
|
||||||
|
def __init__(self):
|
||||||
|
ThreadedUpdater.__init__(self)
|
||||||
|
Section.__init__(self)
|
||||||
|
self.pulseEvents = pulsectl.Pulse('event-handler')
|
||||||
|
|
||||||
|
self.pulseEvents.event_mask_set(pulsectl.PulseEventMaskEnum.sink)
|
||||||
|
self.pulseEvents.event_callback_set(self.handleEvent)
|
||||||
|
self.start()
|
||||||
|
self.refreshData()
|
||||||
|
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
sinks = []
|
||||||
|
with pulsectl.Pulse('list-sinks') as pulse:
|
||||||
|
for sink in pulse.sink_list():
|
||||||
|
vol = pulse.volume_get_all_chans(sink)
|
||||||
|
if vol > 1:
|
||||||
|
vol = 1
|
||||||
|
|
||||||
|
if sink.port_active.name == "analog-output-headphones":
|
||||||
|
icon = ""
|
||||||
|
elif sink.port_active.name == "analog-output-speaker":
|
||||||
|
icon = ""
|
||||||
|
else:
|
||||||
|
icon = "?"
|
||||||
|
|
||||||
|
ramp = "" if sink.mute else (" " + self.ramp(vol))
|
||||||
|
sinks.append(icon + ramp)
|
||||||
|
return " ".join(sinks)
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
self.pulseEvents.event_listen()
|
||||||
|
|
||||||
|
def handleEvent(self, ev):
|
||||||
|
self.refreshData()
|
||||||
|
|
||||||
|
class NetworkProviderSection(Section, Updater):
|
||||||
|
THEME = 5
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
text = ''
|
||||||
|
|
||||||
|
if self.iface not in self.parent.stats or \
|
||||||
|
not self.parent.stats[self.iface].isup or \
|
||||||
|
self.iface.startswith('lo'):
|
||||||
|
return text
|
||||||
|
|
||||||
|
# Get addresses
|
||||||
|
ipv4 = None
|
||||||
|
ipv6 = None
|
||||||
|
for address in self.parent.addrs[self.iface]:
|
||||||
|
if address.family == socket.AF_INET:
|
||||||
|
ipv4 = address
|
||||||
|
elif address.family == socket.AF_INET6:
|
||||||
|
ipv6 = address
|
||||||
|
if ipv4 is None and ipv6 is None:
|
||||||
|
return text
|
||||||
|
|
||||||
|
# Set icon
|
||||||
|
if self.iface.startswith('eth') or self.iface.startswith('enp'):
|
||||||
|
if 'u' in self.iface:
|
||||||
|
text = ''
|
||||||
|
else:
|
||||||
|
text = ''
|
||||||
|
elif self.iface.startswith('wlan') or self.iface.startswith('wlp'):
|
||||||
|
text = ''
|
||||||
|
cmd = ["iwgetid", self.iface, "--raw"]
|
||||||
|
p = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
text += ' ' + p.stdout.strip().decode()
|
||||||
|
elif self.iface.startswith('tun') or self.iface.startswith('tap'):
|
||||||
|
text = ''
|
||||||
|
else:
|
||||||
|
text = '?'
|
||||||
|
|
||||||
|
if self.showAddress:
|
||||||
|
if ipv4:
|
||||||
|
netStrFull = '{}/{}'.format(ipv4.address, ipv4.netmask)
|
||||||
|
addr = ipaddress.IPv4Network(netStrFull, strict=False)
|
||||||
|
text += ' {}/{}'.format(ipv4.address, addr.prefixlen)
|
||||||
|
# TODO IPV6
|
||||||
|
# if ipv6:
|
||||||
|
# text += ' ' + ipv6.address
|
||||||
|
|
||||||
|
if self.showSpeed:
|
||||||
|
recvDiff = self.parent.IO[self.iface].bytes_recv \
|
||||||
|
- self.parent.prevIO[self.iface].bytes_recv
|
||||||
|
sentDiff = self.parent.IO[self.iface].bytes_sent \
|
||||||
|
- self.parent.prevIO[self.iface].bytes_sent
|
||||||
|
recvDiff /= self.parent.dt
|
||||||
|
sentDiff /= self.parent.dt
|
||||||
|
text += ' ↓{}↑{}'.format(humanSize(recvDiff), humanSize(sentDiff))
|
||||||
|
|
||||||
|
if self.showTransfer:
|
||||||
|
text += ' ⇓{}⇑{}'.format(
|
||||||
|
humanSize(self.parent.IO[self.iface].bytes_recv),
|
||||||
|
humanSize(self.parent.IO[self.iface].bytes_sent))
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def cycleState(self):
|
||||||
|
newState = (self.state + 1) % 4
|
||||||
|
self.changeState(newState)
|
||||||
|
|
||||||
|
def changeState(self, state):
|
||||||
|
assert isinstance(state, int)
|
||||||
|
assert state < 4
|
||||||
|
self.state = state
|
||||||
|
self.showAddress = state >= 1
|
||||||
|
self.showSpeed = state >= 2
|
||||||
|
self.showTransfer = state >= 3
|
||||||
|
|
||||||
|
def __init__(self, iface, parent):
|
||||||
|
Section.__init__(self, theme=self.THEME)
|
||||||
|
self.iface = iface
|
||||||
|
self.parent = parent
|
||||||
|
self.changeState(1)
|
||||||
|
self.refreshData()
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkProvider(Section, PeriodicUpdater):
|
||||||
|
def fetchData(self):
|
||||||
|
self.prev = self.last
|
||||||
|
self.prevIO = self.IO
|
||||||
|
|
||||||
|
self.stats = psutil.net_if_stats()
|
||||||
|
self.addrs = psutil.net_if_addrs()
|
||||||
|
self.IO = psutil.net_io_counters(pernic=True)
|
||||||
|
self.ifaces = self.stats.keys()
|
||||||
|
|
||||||
|
self.last = time.perf_counter()
|
||||||
|
self.dt = self.last - self.prev
|
||||||
|
|
||||||
|
def refreshData(self):
|
||||||
|
self.fetchData()
|
||||||
|
|
||||||
|
lastSection = self
|
||||||
|
for iface in sorted(list(self.ifaces)):
|
||||||
|
if iface not in self.sections.keys():
|
||||||
|
section = NetworkProviderSection(iface, self)
|
||||||
|
lastSection.appendAfter(section)
|
||||||
|
self.sections[iface] = section
|
||||||
|
else:
|
||||||
|
section = self.sections[iface]
|
||||||
|
section.refreshData()
|
||||||
|
lastSection = section
|
||||||
|
|
||||||
|
def addParent(self, parent):
|
||||||
|
self.parents.add(parent)
|
||||||
|
self.refreshData()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
PeriodicUpdater.__init__(self)
|
||||||
|
Section.__init__(self)
|
||||||
|
|
||||||
|
self.sections = dict()
|
||||||
|
self.last = 0
|
||||||
|
self.IO = dict()
|
||||||
|
self.fetchData()
|
||||||
|
|
||||||
|
|
||||||
|
class TodoProvider(Section, InotifyUpdater):
|
||||||
|
# TODO OPT/UX Maybe we could get more data from the todoman python module
|
||||||
|
# TODO OPT Specific callback for specific directory
|
||||||
|
|
||||||
|
def updateCalendarList(self):
|
||||||
|
calendars = sorted(os.listdir(self.dir))
|
||||||
|
for calendar in calendars:
|
||||||
|
# If the calendar wasn't in the list
|
||||||
|
if calendar not in self.calendars:
|
||||||
|
self.addPath(os.path.join(self.dir, calendar), refresh=False)
|
||||||
|
self.calendars = calendars
|
||||||
|
|
||||||
|
def __init__(self, dir):
|
||||||
|
"""
|
||||||
|
:parm str dir: [main]path value in todoman.conf
|
||||||
|
"""
|
||||||
|
InotifyUpdater.__init__(self)
|
||||||
|
Section.__init__(self)
|
||||||
|
self.dir = os.path.realpath(os.path.expanduser(dir))
|
||||||
|
assert os.path.isdir(self.dir)
|
||||||
|
|
||||||
|
self.calendars = []
|
||||||
|
self.addPath(self.dir)
|
||||||
|
|
||||||
|
def getName(self, calendar):
|
||||||
|
path = os.path.join(self.dir, calendar, 'displayname')
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
name = f.read().strip()
|
||||||
|
return name
|
||||||
|
|
||||||
|
def getColor(self, calendar):
|
||||||
|
path = os.path.join(self.dir, calendar, 'color')
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
color = f.read().strip()
|
||||||
|
return color
|
||||||
|
|
||||||
|
def countUndone(self, calendar):
|
||||||
|
cmd = ["todo", "--porcelain", "list", self.getName(calendar)]
|
||||||
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||||
|
data = json.loads(proc.stdout)
|
||||||
|
return len(data)
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
text = []
|
||||||
|
self.updateCalendarList()
|
||||||
|
for calendar in self.calendars:
|
||||||
|
c = self.countUndone(calendar)
|
||||||
|
if c > 0:
|
||||||
|
color = self.getColor(calendar)
|
||||||
|
text += [' ']
|
||||||
|
text += [{"text": str(c), "fgColor": color}]
|
||||||
|
if len(text):
|
||||||
|
return [''] + text
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
class I3Provider(Section, ThreadedUpdater):
|
||||||
|
# TODO Multi-screen
|
||||||
|
|
||||||
|
THEME_NORMAL = 0
|
||||||
|
THEME_FOCUSED = 2
|
||||||
|
THEME_URGENT = 1
|
||||||
|
THEME_MODE = 1
|
||||||
|
|
||||||
|
def setName(self, section, origName):
|
||||||
|
# TODO Custom names
|
||||||
|
if origName:
|
||||||
|
section.fullName = origName
|
||||||
|
else:
|
||||||
|
section.fullName = ''
|
||||||
|
section.updateText(section.fullName)
|
||||||
|
|
||||||
|
def initialPopulation(self, parent):
|
||||||
|
"""
|
||||||
|
Called on init
|
||||||
|
Can't reuse addWorkspace since i3.get_workspaces() gives dict and not
|
||||||
|
ConObjects
|
||||||
|
"""
|
||||||
|
workspaces = self.i3.get_workspaces()
|
||||||
|
lastSection = self.modeSection
|
||||||
|
for workspace in workspaces:
|
||||||
|
# if parent.display != workspace["display"]:
|
||||||
|
# continue
|
||||||
|
theme = self.THEME_FOCUSED if workspace["focused"] \
|
||||||
|
else (self.THEME_URGENT if workspace["urgent"]
|
||||||
|
else self.THEME_NORMAL)
|
||||||
|
section = Section(theme=theme)
|
||||||
|
parent.addSectionAfter(lastSection, section)
|
||||||
|
self.setName(section, workspace["name"])
|
||||||
|
self.sections[workspace["num"]] = section
|
||||||
|
lastSection = section
|
||||||
|
|
||||||
|
def on_workspace(self, i3, e):
|
||||||
|
print(304, e.change)
|
||||||
|
|
||||||
|
def on_workspace_init(self, i3, e):
|
||||||
|
workspace = e.current
|
||||||
|
i = workspace.num
|
||||||
|
if i in self.sections:
|
||||||
|
section = self.sections[i]
|
||||||
|
else:
|
||||||
|
while i not in self.sections.keys() and i > 0:
|
||||||
|
i -= 1
|
||||||
|
prevSection = self.sections[i] if i != 0 else self.modeSection
|
||||||
|
section = Section()
|
||||||
|
self.sections[workspace.num] = section
|
||||||
|
prevSection.appendAfter(section)
|
||||||
|
self.setName(section, workspace.name)
|
||||||
|
|
||||||
|
def on_workspace_empty(self, i3, e):
|
||||||
|
workspace = e.current
|
||||||
|
section = self.sections[workspace.num]
|
||||||
|
self.setName(section, None)
|
||||||
|
|
||||||
|
def on_workspace_focus(self, i3, e):
|
||||||
|
self.sections[e.current.num].updateTheme(self.THEME_FOCUSED)
|
||||||
|
self.sections[e.old.num].updateTheme(self.THEME_NORMAL)
|
||||||
|
|
||||||
|
def on_workspace_urgent(self, i3, e):
|
||||||
|
self.sections[e.current.num].updateTheme(self.THEME_URGENT)
|
||||||
|
|
||||||
|
def on_workspace_rename(self, i3, e):
|
||||||
|
self.sections[e.current.num].updateText(e.name)
|
||||||
|
|
||||||
|
def on_mode(self, i3, e):
|
||||||
|
if e.change == 'default':
|
||||||
|
self.modeSection.updateText('')
|
||||||
|
for section in self.sections.values():
|
||||||
|
section.updateText(section.fullName)
|
||||||
|
else:
|
||||||
|
self.modeSection.updateText(e.change)
|
||||||
|
for section in self.sections.values():
|
||||||
|
section.updateText('')
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
ThreadedUpdater.__init__(self)
|
||||||
|
Section.__init__(self)
|
||||||
|
|
||||||
|
self.i3 = i3ipc.Connection()
|
||||||
|
self.sections = dict()
|
||||||
|
self.i3.on("workspace::init", self.on_workspace_init)
|
||||||
|
self.i3.on("workspace::focus", self.on_workspace_focus)
|
||||||
|
self.i3.on("workspace::empty", self.on_workspace_empty)
|
||||||
|
self.i3.on("workspace::urgent", self.on_workspace_urgent)
|
||||||
|
self.i3.on("workspace::rename", self.on_workspace_rename)
|
||||||
|
# TODO Un-handled/tested: reload, rename, restored, move
|
||||||
|
|
||||||
|
self.i3.on("mode", self.on_mode)
|
||||||
|
|
||||||
|
self.modeSection = Section(theme=self.THEME_MODE)
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def addParent(self, parent):
|
||||||
|
self.parents.add(parent)
|
||||||
|
parent.addSection(self.modeSection)
|
||||||
|
self.initialPopulation(parent)
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
self.i3.main()
|
194
config/lemonbar/updaters.py
Executable file
194
config/lemonbar/updaters.py
Executable file
|
@ -0,0 +1,194 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import math
|
||||||
|
import functools
|
||||||
|
import threading
|
||||||
|
import pyinotify
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
# TODO Multiple providers for the same section
|
||||||
|
|
||||||
|
|
||||||
|
class Updater:
|
||||||
|
@staticmethod
|
||||||
|
def init():
|
||||||
|
PeriodicUpdater.init()
|
||||||
|
InotifyUpdater.init()
|
||||||
|
|
||||||
|
def updateText(self, text):
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
def fetcher(self):
|
||||||
|
return "{} refreshed".format(self)
|
||||||
|
|
||||||
|
def refreshData(self):
|
||||||
|
data = self.fetcher()
|
||||||
|
self.updateText(data)
|
||||||
|
|
||||||
|
|
||||||
|
class PeriodicUpdaterThread(threading.Thread):
|
||||||
|
def run(self):
|
||||||
|
counter = 0
|
||||||
|
while True:
|
||||||
|
if PeriodicUpdater.intervalsChanged \
|
||||||
|
.wait(timeout=PeriodicUpdater.intervalStep):
|
||||||
|
# ↑ sleeps here
|
||||||
|
PeriodicUpdater.intervalsChanged.clear()
|
||||||
|
counter = 0
|
||||||
|
for providerList in PeriodicUpdater.intervals.copy().values():
|
||||||
|
for provider in providerList.copy():
|
||||||
|
provider.refreshData()
|
||||||
|
else:
|
||||||
|
counter += PeriodicUpdater.intervalStep
|
||||||
|
counter = counter % PeriodicUpdater.intervalLoop
|
||||||
|
for interval in PeriodicUpdater.intervals.keys():
|
||||||
|
if counter % interval == 0:
|
||||||
|
for provider in PeriodicUpdater.intervals[interval]:
|
||||||
|
provider.refreshData()
|
||||||
|
|
||||||
|
|
||||||
|
class PeriodicUpdater(Updater):
|
||||||
|
"""
|
||||||
|
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
|
||||||
|
"""
|
||||||
|
|
||||||
|
intervals = dict()
|
||||||
|
intervalStep = None
|
||||||
|
intervalLoop = None
|
||||||
|
updateThread = PeriodicUpdaterThread(daemon=True)
|
||||||
|
intervalsChanged = threading.Event()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def gcds(*args):
|
||||||
|
return functools.reduce(math.gcd, args)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def lcm(a, b):
|
||||||
|
"""Return lowest common multiple."""
|
||||||
|
return a * b // math.gcd(a, b)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def lcms(*args):
|
||||||
|
"""Return lowest common multiple."""
|
||||||
|
return functools.reduce(PeriodicUpdater.lcm, args)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def updateIntervals():
|
||||||
|
intervalsList = list(PeriodicUpdater.intervals.keys())
|
||||||
|
PeriodicUpdater.intervalStep = PeriodicUpdater.gcds(*intervalsList)
|
||||||
|
PeriodicUpdater.intervalLoop = PeriodicUpdater.lcms(*intervalsList)
|
||||||
|
PeriodicUpdater.intervalsChanged.set()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init():
|
||||||
|
PeriodicUpdater.updateThread.start()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Updater.__init__(self)
|
||||||
|
self.interval = None
|
||||||
|
|
||||||
|
def changeInterval(self, interval):
|
||||||
|
assert isinstance(interval, int)
|
||||||
|
|
||||||
|
if self.interval is not None:
|
||||||
|
PeriodicUpdater.intervals[self.interval].remove(self)
|
||||||
|
|
||||||
|
self.interval = interval
|
||||||
|
|
||||||
|
if interval not in PeriodicUpdater.intervals:
|
||||||
|
PeriodicUpdater.intervals[interval] = set()
|
||||||
|
PeriodicUpdater.intervals[interval].add(self)
|
||||||
|
|
||||||
|
PeriodicUpdater.updateIntervals()
|
||||||
|
|
||||||
|
|
||||||
|
class InotifyUpdaterEventHandler(pyinotify.ProcessEvent):
|
||||||
|
|
||||||
|
def process_default(self, event):
|
||||||
|
# DEBUG
|
||||||
|
# from pprint import pprint
|
||||||
|
# pprint(event.__dict__)
|
||||||
|
# return
|
||||||
|
|
||||||
|
assert event.path in InotifyUpdater.paths
|
||||||
|
|
||||||
|
if 0 in InotifyUpdater.paths[event.path]:
|
||||||
|
for provider in InotifyUpdater.paths[event.path][0]:
|
||||||
|
provider.refreshData()
|
||||||
|
|
||||||
|
if event.name in InotifyUpdater.paths[event.path]:
|
||||||
|
for provider in InotifyUpdater.paths[event.path][event.name]:
|
||||||
|
provider.refreshData()
|
||||||
|
|
||||||
|
|
||||||
|
class InotifyUpdater(Updater):
|
||||||
|
"""
|
||||||
|
Needs to call :func:`PeriodicUpdater.changeInterval` in `__init__`
|
||||||
|
"""
|
||||||
|
|
||||||
|
wm = pyinotify.WatchManager()
|
||||||
|
paths = dict()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init():
|
||||||
|
notifier = pyinotify.ThreadedNotifier(InotifyUpdater.wm,
|
||||||
|
InotifyUpdaterEventHandler())
|
||||||
|
notifier.start()
|
||||||
|
|
||||||
|
# TODO Mask for folders
|
||||||
|
MASK = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_DELETE
|
||||||
|
|
||||||
|
def addPath(self, path, refresh=True):
|
||||||
|
path = os.path.realpath(os.path.expanduser(path))
|
||||||
|
|
||||||
|
# Detect if file or folder
|
||||||
|
if os.path.isdir(path):
|
||||||
|
self.dirpath = path
|
||||||
|
# 0: Directory watcher
|
||||||
|
self.filename = 0
|
||||||
|
elif os.path.isfile(path):
|
||||||
|
self.dirpath = os.path.dirname(path)
|
||||||
|
self.filename = os.path.basename(path)
|
||||||
|
else:
|
||||||
|
raise FileNotFoundError("No such file or directory: '{}'"
|
||||||
|
.format(path))
|
||||||
|
|
||||||
|
# Register watch action
|
||||||
|
if self.dirpath not in InotifyUpdater.paths:
|
||||||
|
InotifyUpdater.paths[self.dirpath] = dict()
|
||||||
|
if self.filename not in InotifyUpdater.paths[self.dirpath]:
|
||||||
|
InotifyUpdater.paths[self.dirpath][self.filename] = set()
|
||||||
|
InotifyUpdater.paths[self.dirpath][self.filename].add(self)
|
||||||
|
|
||||||
|
# Add watch
|
||||||
|
InotifyUpdater.wm.add_watch(self.dirpath, InotifyUpdater.MASK)
|
||||||
|
|
||||||
|
if refresh:
|
||||||
|
self.refreshData()
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadedUpdaterThread(threading.Thread):
|
||||||
|
def __init__(self, updater, *args, **kwargs):
|
||||||
|
self.updater = updater
|
||||||
|
threading.Thread.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
self.updater.loop()
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadedUpdater(Updater):
|
||||||
|
"""
|
||||||
|
Must implement loop(), and call start()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.thread = ThreadedUpdaterThread(self, daemon=True)
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
self.refreshData()
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.thread.start()
|
10
config/lemonbar/x.py
Executable file
10
config/lemonbar/x.py
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import Xlib.display
|
||||||
|
|
||||||
|
dis = Xlib.display.Display()
|
||||||
|
|
||||||
|
nb = dis.screen_count()
|
||||||
|
|
||||||
|
for s in range(nb):
|
||||||
|
print(s)
|
|
@ -76,7 +76,7 @@ enable-ipc = true
|
||||||
inherit = bar/base
|
inherit = bar/base
|
||||||
|
|
||||||
modules-center = mpd
|
modules-center = mpd
|
||||||
modules-right = mail todo vpncheck eth wlan bbswitch xbacklight volume battery shortdate
|
modules-right = cpu memory temperature mail todo vpncheck eth wlan bbswitch xbacklight volume battery shortdate
|
||||||
|
|
||||||
tray-position = right
|
tray-position = right
|
||||||
tray-padding = 2
|
tray-padding = 2
|
||||||
|
|
|
@ -2,4 +2,11 @@
|
||||||
|
|
||||||
# Removes CRLF (^M or \r) from a file
|
# Removes CRLF (^M or \r) from a file
|
||||||
|
|
||||||
sed -e "s/^M//" $1 -i
|
#sed -e "s/^M//" "$1" -i
|
||||||
|
|
||||||
|
tmpfile=$(mktemp)
|
||||||
|
|
||||||
|
cp "$1" "$tmpfile"
|
||||||
|
tr -d '\r' < "$tmpfile" > "$1"
|
||||||
|
rm "$tmpfile"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue