|
|
@ -18,6 +18,8 @@ import random |
|
|
|
coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s') |
|
|
|
log = logging.getLogger() |
|
|
|
|
|
|
|
# TODO Generator class (for I3WorkspacesProvider, NetworkProvider and later |
|
|
|
# PulseaudioProvider and MpdProvider) |
|
|
|
|
|
|
|
def humanSize(num): |
|
|
|
""" |
|
|
@ -43,6 +45,7 @@ class TimeProvider(StatefulSection, PeriodicUpdater): |
|
|
|
"%d/%m %H:%M:%S", |
|
|
|
"%a %d/%m/%y %H:%M:%S"] |
|
|
|
NUMBER_STATES = len(FORMATS) |
|
|
|
DEFAULT_STATE = 1 |
|
|
|
|
|
|
|
def fetcher(self): |
|
|
|
now = datetime.datetime.now() |
|
|
@ -114,13 +117,13 @@ class RamProvider(AlertingSection, PeriodicUpdater): |
|
|
|
|
|
|
|
def fetcher(self): |
|
|
|
mem = psutil.virtual_memory() |
|
|
|
freePerc = 1-mem.percent/100 |
|
|
|
freePerc = mem.percent/100 |
|
|
|
self.updateLevel(freePerc) |
|
|
|
|
|
|
|
if self.state < 1: |
|
|
|
return None |
|
|
|
|
|
|
|
text = Text(Section.ramp(freePerc)) |
|
|
|
text = Text(Section.ramp(1-freePerc)) |
|
|
|
if self.state >= 2: |
|
|
|
freeStr = humanSize(mem.available) |
|
|
|
text.append(freeStr) |
|
|
@ -162,7 +165,8 @@ class TemperatureProvider(AlertingSection, PeriodicUpdater): |
|
|
|
|
|
|
|
|
|
|
|
class BatteryProvider(AlertingSection, PeriodicUpdater): |
|
|
|
NUMBER_STATES = 2 |
|
|
|
# TODO Support ACPID for events |
|
|
|
NUMBER_STATES = 3 |
|
|
|
RAMP = "" |
|
|
|
|
|
|
|
def fetcher(self): |
|
|
@ -179,8 +183,15 @@ class BatteryProvider(AlertingSection, PeriodicUpdater): |
|
|
|
if self.state < 1: |
|
|
|
return |
|
|
|
|
|
|
|
return Text('{:.0f}%'.format(bat.percent)) |
|
|
|
# TODO Time remaining (if the estimation is somewhat correct) |
|
|
|
t = Text('{:.0f}%'.format(bat.percent)) |
|
|
|
|
|
|
|
if self.state < 2: |
|
|
|
return t |
|
|
|
|
|
|
|
h = int(bat.secsleft / 3600) |
|
|
|
m = int((bat.secsleft - h * 3600) / 60) |
|
|
|
t.append(" ({:d}:{:02d})".format(h, m)) |
|
|
|
return t |
|
|
|
|
|
|
|
def __init__(self, theme=None): |
|
|
|
AlertingSection.__init__(self, theme) |
|
|
@ -189,10 +200,13 @@ class BatteryProvider(AlertingSection, PeriodicUpdater): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PulseaudioProvider(Section, ThreadedUpdater): |
|
|
|
class PulseaudioProvider(StatefulSection, ThreadedUpdater): |
|
|
|
NUMBER_STATES = 3 |
|
|
|
DEFAULT_STATE = 1 |
|
|
|
|
|
|
|
def __init__(self, theme=None): |
|
|
|
ThreadedUpdater.__init__(self) |
|
|
|
Section.__init__(self, theme) |
|
|
|
StatefulSection.__init__(self, theme) |
|
|
|
self.pulseEvents = pulsectl.Pulse('event-handler') |
|
|
|
|
|
|
|
self.pulseEvents.event_mask_set(pulsectl.PulseEventMaskEnum.sink) |
|
|
@ -205,20 +219,32 @@ class PulseaudioProvider(Section, ThreadedUpdater): |
|
|
|
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 = "" |
|
|
|
icon = "" if sink.mute else "" |
|
|
|
else: |
|
|
|
icon = "?" |
|
|
|
vol = pulse.volume_get_all_chans(sink) |
|
|
|
fg = (sink.mute and '#333333') or (vol > 1 and '#FF0000') or None |
|
|
|
|
|
|
|
t = Text(icon, fg=fg) |
|
|
|
sinks.append(t) |
|
|
|
|
|
|
|
ramp = "" if sink.mute else (" " + self.ramp(vol)) |
|
|
|
sinks.append(icon + ramp) |
|
|
|
return " ".join(sinks) |
|
|
|
if self.state < 1: |
|
|
|
continue |
|
|
|
|
|
|
|
if self.state < 2: |
|
|
|
if not sink.mute: |
|
|
|
ramp = " " |
|
|
|
while vol >= 0: |
|
|
|
ramp += self.ramp(vol if vol < 1 else 1) |
|
|
|
vol -= 1 |
|
|
|
t.append(ramp) |
|
|
|
else: |
|
|
|
t.append(" {:2.0f}%".format(vol*100)) |
|
|
|
|
|
|
|
return Text(*sinks) |
|
|
|
|
|
|
|
def loop(self): |
|
|
|
self.pulseEvents.event_listen() |
|
|
@ -230,6 +256,7 @@ class PulseaudioProvider(Section, ThreadedUpdater): |
|
|
|
class NetworkProviderSection(StatefulSection, Updater): |
|
|
|
|
|
|
|
NUMBER_STATES = 5 |
|
|
|
DEFAULT_STATE = 1 |
|
|
|
|
|
|
|
def actType(self): |
|
|
|
self.ssid = None |
|
|
@ -260,6 +287,8 @@ class NetworkProviderSection(StatefulSection, Updater): |
|
|
|
return ipv4, ipv6 |
|
|
|
|
|
|
|
def fetcher(self): |
|
|
|
self.icon = None |
|
|
|
self.persistent = False |
|
|
|
if self.iface not in self.parent.stats or \ |
|
|
|
not self.parent.stats[self.iface].isup or \ |
|
|
|
self.iface.startswith('lo'): |
|
|
@ -364,6 +393,41 @@ class NetworkProvider(Section, PeriodicUpdater): |
|
|
|
self.fetchData() |
|
|
|
self.changeInterval(5) |
|
|
|
|
|
|
|
class RfkillProvider(Section, PeriodicUpdater): |
|
|
|
# TODO FEAT rfkill doesn't seem to indicate that the hardware switch is |
|
|
|
# toggled |
|
|
|
PATH = '/sys/class/rfkill' |
|
|
|
|
|
|
|
def fetcher(self): |
|
|
|
t = Text() |
|
|
|
for device in os.listdir(self.PATH): |
|
|
|
with open(os.path.join(self.PATH, device, 'soft'), 'rb') as f: |
|
|
|
softBlocked = f.read().strip() != b'0' |
|
|
|
with open(os.path.join(self.PATH, device, 'hard'), 'rb') as f: |
|
|
|
hardBlocked = f.read().strip() != b'0' |
|
|
|
|
|
|
|
if not hardBlocked and not softBlocked: |
|
|
|
continue |
|
|
|
|
|
|
|
with open(os.path.join(self.PATH, device, 'type'), 'rb') as f: |
|
|
|
typ = f.read().strip() |
|
|
|
|
|
|
|
fg = (hardBlocked and '#CCCCCC') or (softBlocked and '#FF0000') |
|
|
|
if typ == b'wlan': |
|
|
|
icon = '' |
|
|
|
elif typ == b'bluetooth': |
|
|
|
icon = '' |
|
|
|
else: |
|
|
|
icon = '?' |
|
|
|
|
|
|
|
t.append(Text(icon, fg=fg)) |
|
|
|
return t |
|
|
|
|
|
|
|
def __init__(self, theme=None): |
|
|
|
PeriodicUpdater.__init__(self) |
|
|
|
Section.__init__(self, theme) |
|
|
|
self.changeInterval(5) |
|
|
|
|
|
|
|
class SshAgentProvider(PeriodicUpdater): |
|
|
|
def fetcher(self): |
|
|
|
cmd = ["ssh-add", "-l"] |
|
|
@ -405,6 +469,7 @@ class GpgAgentProvider(PeriodicUpdater): |
|
|
|
self.changeInterval(5) |
|
|
|
|
|
|
|
class KeystoreProvider(Section, MergedUpdater): |
|
|
|
# TODO OPTI+FEAT Use ColorCountsSection and not MergedUpdater, this is useless |
|
|
|
ICON = '' |
|
|
|
|
|
|
|
def __init__(self, theme=None): |
|
|
@ -412,8 +477,6 @@ class KeystoreProvider(Section, MergedUpdater): |
|
|
|
Section.__init__(self, theme) |
|
|
|
|
|
|
|
class NotmuchUnreadProvider(ColorCountsSection, InotifyUpdater): |
|
|
|
# TODO OPTI Transform InotifyUpdater (watching notmuch folder should be |
|
|
|
# enough) |
|
|
|
COLORABLE_ICON = '' |
|
|
|
|
|
|
|
def subfetcher(self): |
|
|
@ -456,7 +519,6 @@ class TodoProvider(ColorCountsSection, InotifyUpdater): |
|
|
|
COLORABLE_ICON = '' |
|
|
|
|
|
|
|
def updateCalendarList(self): |
|
|
|
print(459) |
|
|
|
calendars = sorted(os.listdir(self.dir)) |
|
|
|
for calendar in calendars: |
|
|
|
# If the calendar wasn't in the list |
|
|
@ -473,7 +535,6 @@ class TodoProvider(ColorCountsSection, InotifyUpdater): |
|
|
|
with open(path, 'r') as f: |
|
|
|
self.colors[calendar] = f.read().strip() |
|
|
|
self.calendars = calendars |
|
|
|
print(475, self.calendars) |
|
|
|
|
|
|
|
def __init__(self, dir, theme=None): |
|
|
|
""" |
|
|
@ -539,6 +600,9 @@ class I3WorkspacesProviderSection(Section): |
|
|
|
else: |
|
|
|
return self.parent.themeNormal |
|
|
|
|
|
|
|
# TODO On mode change the state (shown / hidden) gets overriden so every |
|
|
|
# tab is shown |
|
|
|
|
|
|
|
def show(self): |
|
|
|
self.updateTheme(self.selectTheme()) |
|
|
|
self.updateText(self.fullName if self.focused else self.shortName) |
|
|
@ -553,10 +617,15 @@ class I3WorkspacesProviderSection(Section): |
|
|
|
self.fullName = self.parent.customNames[name] \ |
|
|
|
if name in self.parent.customNames else name |
|
|
|
|
|
|
|
def switchTo(self): |
|
|
|
self.parent.i3.command('workspace {}'.format(self.shortName)) |
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, name, parent): |
|
|
|
Section.__init__(self) |
|
|
|
self.parent = parent |
|
|
|
self.setName(name) |
|
|
|
self.setDecorators(clickLeft=self.switchTo) |
|
|
|
|
|
|
|
def empty(self): |
|
|
|
self.updateTheme(self.parent.themeNormal) |
|
|
@ -564,7 +633,7 @@ class I3WorkspacesProviderSection(Section): |
|
|
|
|
|
|
|
|
|
|
|
class I3WorkspacesProvider(Section, I3Updater): |
|
|
|
# TODO Multi-screen |
|
|
|
# TODO FEAT Multi-screen |
|
|
|
|
|
|
|
def initialPopulation(self, parent): |
|
|
|
""" |
|
|
@ -674,10 +743,13 @@ class MpdProvider(Section, ThreadedUpdater): |
|
|
|
self.start() |
|
|
|
|
|
|
|
def fetcher(self): |
|
|
|
cur = self.mpd.currentsong() |
|
|
|
stat = self.mpd.status() |
|
|
|
if not len(stat) or stat["state"] == "stop": |
|
|
|
return None |
|
|
|
|
|
|
|
cur = self.mpd.currentsong() |
|
|
|
if not len(cur): |
|
|
|
return '' |
|
|
|
return None |
|
|
|
|
|
|
|
infos = [] |
|
|
|
|
|
|
|