Bar bar bar

This commit is contained in:
Geoffrey Frogeye 2018-09-05 09:07:37 +02:00
parent 1c7efc4a76
commit 7987cdcaae
9 changed files with 893 additions and 187 deletions

View File

@ -1,161 +1,22 @@
#!/usr/bin/env python3
"""
Debugging script
"""
from providers import *
import i3ipc
import os
import psutil
# import alsaaudio
from time import time
import subprocess
if __name__ == "__main__":
Bar.init()
Updater.init()
i3 = i3ipc.Connection()
lemonbar = subprocess.Popen(['lemonbar', '-b'], stdin=subprocess.PIPE)
Bar.addSectionAll(I3Provider(), BarGroupType.LEFT)
# 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()
# TODO CPU provider
# TODO RAM provider
# TODO Temperature provider
# TODO Disk space provider
# TODO Screen (connected, autorandr configuration, bbswitch) provider
# TODO Unlocked keys provider
# TODO Mail provider
Bar.addSectionAll(TodoProvider(dir='~/.vdirsyncer/currentCalendars/'), BarGroupType.RIGHT)
Bar.addSectionAll(NetworkProvider(), BarGroupType.RIGHT)
Bar.addSectionAll(PulseaudioProvider(), BarGroupType.RIGHT)
Bar.addSectionAll(BatteryProvider(), BarGroupType.RIGHT)
Bar.addSectionAll(TimeProvider(), BarGroupType.RIGHT)

View File

@ -1,23 +1,24 @@
#!/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
# 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
# BarGroup strings should be a good compromise)
# TODO Use bytes rather than strings
# TODO Use default colors of lemonbar sometimes
# TODO Mouse actions
class BarGroupType(enum.Enum):
LEFT = 0
RIGHT = 1
# TODO Middle
# MID_LEFT = 2
# MID_RIGHT = 3
@ -28,24 +29,31 @@ class Bar:
"""
# Constants
FONT = "DejaVu Sans Mono for Powerline"
FONTS = ["DejaVu Sans Mono for Powerline", "Font Awesome"]
@staticmethod
def 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)
# Debug
# Bar(0)
Bar(1)
Bar(0)
# Bar(1)
# Class globals
everyone = set()
string = ""
process = None
@staticmethod
def forever():
while True:
time.sleep(60)
def __init__(self, screen):
assert isinstance(screen, int)
self.screen = "%{S" + str(screen) + "}"
@ -94,7 +102,7 @@ class Bar:
# Color for empty sections
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.flush()
@ -127,7 +135,12 @@ class BarGroup:
def addSection(self, 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}"}
@ -155,7 +168,7 @@ class BarGroup:
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
oSec = secs[s - 1] if s > 0 else None
oTheme = Section.THEMES[oSec.theme] \
if oSec is not None else Section.EMPTY
@ -164,10 +177,16 @@ class BarGroup:
parts.append(BarGroup.bgColor(theme[1]))
parts.append(BarGroup.fgColor(theme[0]))
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:
parts.append(BarGroup.fgColor(theme[1]) + "")
parts.append(BarGroup.color(*theme))
if theme is oTheme:
parts.append("")
else:
parts.append(BarGroup.fgColor(theme[1]) + "")
parts.append(BarGroup.color(*theme))
parts.append(sec)
@ -189,21 +208,29 @@ class BarGroup:
@staticmethod
def updateAll():
for group in BarGroup.everyone:
group.update()
Bar.updateAll()
class SectionThread(threading.Thread):
ANIMATION_START = 0.025
ANIMATION_STOP = 0.001
ANIMATION_EVOLUTION = 0.9
def run(self):
while Section.somethingChanged.wait():
Section.updateAll()
animTime = self.ANIMATION_START
frameTime = time.perf_counter()
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()
animTime *= self.ANIMATION_EVOLUTION
if animTime < self.ANIMATION_STOP:
animTime = self.ANIMATION_STOP
class Section:
@ -218,19 +245,18 @@ class Section:
THEMES = list()
EMPTY = (FGCOLOR, BGCOLOR)
#: Sections that do not have their destination size
sizeChanging = set()
updateThread = SectionThread(daemon=True)
somethingChanged = threading.Event()
@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!
@ -252,10 +278,21 @@ class 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)
try:
return "<{}><{}>{:01d}{}{:02d}/{:02d}" \
.format(self.curText, self.dstText,
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):
for parent in self.parents:
@ -267,8 +304,15 @@ class Section:
def updateText(self, text):
if len(text):
self.dstText = ' {} '.format(text)
self.dstSize = len(self.dstText)
if isinstance(text, str):
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:
self.dstSize = 0
@ -331,6 +375,9 @@ class Section:
Section.somethingChanged.clear()
@staticmethod
def ramp(p, ramp=" ▁▂▃▄▅▆▇█"):
return ramp[round(p * (len(ramp)-1))]
if __name__ == '__main__':
Bar.init()

29
config/lemonbar/net.py Executable file
View 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
View 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
View 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
View 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
View 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)

View File

@ -76,7 +76,7 @@ enable-ipc = true
inherit = bar/base
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-padding = 2

View File

@ -2,4 +2,11 @@
# 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"