Goodbye bacon!

This commit is contained in:
Geoffrey Frogeye 2018-10-06 10:27:36 +02:00
parent d010c48306
commit 4f02db850b
15 changed files with 423 additions and 204 deletions

View file

@ -64,8 +64,8 @@ bindsym $mod+F11 exec urxvtc -e 'pacmixer'
bindsym $mod+F12 exec urxvtc -e 'pacmixer'
#Brightness control
bindsym XF86MonBrightnessDown exec xbacklight -dec 20
bindsym XF86MonBrightnessUp exec xbacklight -inc 20
bindsym XF86MonBrightnessDown exec xbacklight -dec 20 -time 0
bindsym XF86MonBrightnessUp exec xbacklight -inc 20 -time 0
# Screenshots
bindsym Print exec scrot -ue 'mv $f ~/Screenshots/'
@ -226,13 +226,6 @@ bindsym $mod+ctrl+shift+Left move workspace to output left
bindsym $mod+Ctrl+Shift+Up move workspace to output above
bindsym $mod+Ctrl+Shift+Down move workspace to output below
# Open applications on specific workspaces
assign [class="Thunderbird"] $WS7
assign [class="Skype"] $WS7
assign [class="Pidgin"] $WS7
assign [class="Clementine"] $WS10
assign [title="TweetDeck"] $WS8
# Open specific applications in floating mode
for_window [title="pacmixer"] floating enable border pixel 2
for_window [class="Firefox"] layout tabbed
@ -353,6 +346,7 @@ set_from_resource $color15 i3wm.color15 #cfd0c2
# Inactivity settings
exec --no-startup-id xautolock -time 10 -locker 'xset dpms force standby' -killtime 1 -killer '$locker'
bindsym $mod+F1 exec --no-startup-id xset dpms force off
bindsym $mod+F4 exec --no-startup-id xautolock -disable
bindsym $mod+F5 exec --no-startup-id xautolock -enable
@ -366,7 +360,7 @@ exec --no-startup-id unclutter -root # Hide mouse cursor after some time
#exec --no-startup-id dunst # Notifications (handled by systemd)
exec --no-startup-id keynav # Keyboard cursor controller
#exec --no-startup-id mpd # Music Player Daemon (handled by systemd)
exec --no-startup-id ~/.config/i3/ashuffle # MPD Auto-refill
# exec --no-startup-id ~/.config/i3/ashuffle # MPD Auto-refill
exec --no-startup-id autorandr --change # Screen configuration and everything that depends on it
exec --no-startup-id ~/.config/i3/batteryNotify -d # Battery state notification
@ -381,3 +375,7 @@ client.urgent $color01 $color01 $color07 $foreground $color09
client.placeholder $ignore $color06 $color07 $ignore $color14
client.background $color15
# bar {
# i3bar_command ~/.config/lemonbar/bar.py
# }

View file

@ -25,6 +25,7 @@ if __name__ == "__main__":
Bar.addSectionAll(MpdProvider(theme=7), BarGroupType.LEFT)
# Bar.addSectionAll(I3WindowTitleProvider(), BarGroupType.LEFT)
# TODO Computer modes
SYSTEM_THEME = 2
DANGER_THEME = FOCUS_THEME
@ -40,6 +41,7 @@ if __name__ == "__main__":
# TODO Disk space provider
# TODO Screen (connected, autorandr configuration, bbswitch) provider
Bar.addSectionAll(PulseaudioProvider(theme=PERIPHERAL_THEME), BarGroupType.RIGHT)
Bar.addSectionAll(RfkillProvider(theme=PERIPHERAL_THEME), BarGroupType.RIGHT)
Bar.addSectionAll(NetworkProvider(theme=NETWORK_THEME), BarGroupType.RIGHT)
# Personal
@ -50,3 +52,5 @@ if __name__ == "__main__":
TIME_THEME = 6
Bar.addSectionAll(TimeProvider(theme=TIME_THEME), BarGroupType.RIGHT)
# Bar.run()

View file

@ -3,6 +3,9 @@
import enum
import threading
import time
import i3ipc
import os
import signal
import subprocess
import logging
import coloredlogs
@ -10,6 +13,7 @@ import coloredlogs
coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s')
log = logging.getLogger()
# 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)
@ -18,6 +22,7 @@ log = logging.getLogger()
# TODO Use bytes rather than strings
# TODO Use default colors of lemonbar sometimes
# TODO Adapt bar height with font height
# TODO OPTI Static text objects that update its parents if modified
class BarGroupType(enum.Enum):
@ -30,7 +35,7 @@ class BarGroupType(enum.Enum):
class BarStdoutThread(threading.Thread):
def run(self):
while True:
while Bar.running:
handle = Bar.process.stdout.readline().strip()
if not len(handle):
continue
@ -52,6 +57,7 @@ class Bar:
@staticmethod
def init():
Bar.running = True
Section.init()
cmd = ['lemonbar', '-b', '-a', '64']
@ -66,10 +72,35 @@ class Bar:
Bar(0)
# Bar(1)
@staticmethod
def stop():
Bar.running = False
Bar.process.kill()
# TODO This is not really the best way to do it I guess
os.killpg(os.getpid(), signal.SIGTERM)
@staticmethod
def run():
Bar.forever()
i3 = i3ipc.Connection()
def doStop(*args):
Bar.stop()
print(88)
try:
i3.on('ipc_shutdown', doStop)
i3.main()
except BaseException:
print(93)
Bar.stop()
# Class globals
everyone = set()
string = ""
process = None
running = False
nextHandle = 0
actionsF2H = dict()
@ -92,8 +123,11 @@ class Bar:
@staticmethod
def forever():
while True:
time.sleep(60)
try:
while True:
time.sleep(60)
except BaseException:
Bar.stop()
def __init__(self, screen):
assert isinstance(screen, int)
@ -136,15 +170,16 @@ class Bar:
@staticmethod
def updateAll():
Bar.string = ""
for bar in Bar.everyone:
bar.update()
Bar.string += bar.string
# Color for empty sections
Bar.string += BarGroup.color(*Section.EMPTY)
if Bar.running:
Bar.string = ""
for bar in Bar.everyone:
bar.update()
Bar.string += bar.string
# Color for empty sections
Bar.string += BarGroup.color(*Section.EMPTY)
Bar.process.stdin.write(bytes(Bar.string + '\n', 'utf-8'))
Bar.process.stdin.flush()
Bar.process.stdin.write(bytes(Bar.string + '\n', 'utf-8'))
Bar.process.stdin.flush()
class BarGroup:
@ -276,11 +311,14 @@ class SectionThread(threading.Thread):
class Section:
# TODO Update all of that to base16
COLORS = ['#002b36', '#dc322f', '#859900', '#b58900', '#268bd2', '#6c71c4',
'#2aa198', '#93a1a1', '#657b83', '#dc322f', '#859900', '#b58900',
'#268bd2', '#6c71c4', '#2aa198', '#fdf6e3']
FGCOLOR = '#93a1a1'
BGCOLOR = '#002b36'
# COLORS = ['#272822', '#383830', '#49483e', '#75715e', '#a59f85', '#f8f8f2',
# '#f5f4f1', '#f9f8f5', '#f92672', '#fd971f', '#f4bf75', '#a6e22e',
# '#a1efe4', '#66d9ef', '#ae81ff', '#cc6633']
COLORS = ['#181818', '#AB4642', '#A1B56C', '#F7CA88', '#7CAFC2', '#BA8BAF',
'#86C1B9', '#D8D8D8', '#585858', '#AB4642', '#A1B56C', '#F7CA88',
'#7CAFC2', '#BA8BAF', '#86C1B9', '#F8F8F8']
FGCOLOR = '#F8F8F2'
BGCOLOR = '#272822'
THEMES = list()
EMPTY = (FGCOLOR, BGCOLOR)
@ -357,6 +395,8 @@ class Section:
def updateText(self, text):
if isinstance(text, str):
text = Text(text)
elif isinstance(text, Text) and not len(text.elements):
text = None
self.dstText[0] = None if (text is None and not self.persistent) else ((' ' + self.icon + ' ') if self.icon else ' ')
self.dstText[1] = text
@ -373,6 +413,12 @@ class Section:
Section.sizeChanging.add(self)
Section.somethingChanged.set()
def setDecorators(self, **kwargs):
self.dstText.setDecorators(**kwargs)
self.curText = str(self.dstText)
self.informParentsTextChanged()
Section.somethingChanged.set()
def updateTheme(self, theme):
assert isinstance(theme, int)
assert theme < len(Section.THEMES)
@ -440,13 +486,14 @@ class Section:
class StatefulSection(Section):
# TODO FEAT Allow to temporary expand the section (e.g. when important change)
NUMBER_STATES = None
DEFAULT_STATE = 0
def __init__(self, *args, **kwargs):
Section.__init__(self, *args, **kwargs)
self.state = 0
self.state = self.DEFAULT_STATE
if hasattr(self, 'onChangeState'):
self.onChangeState(self.state)
self.dstText.setDecorators(clickLeft=self.incrementState,
self.setDecorators(clickLeft=self.incrementState,
clickRight=self.decrementState)
def incrementState(self):
@ -466,6 +513,9 @@ class StatefulSection(Section):
self.refreshData()
class ColorCountsSection(StatefulSection):
# TODO FEAT Blend colors when not expanded
# TODO FEAT Blend colors with importance of count
# TODO FEAT Allow icons instead of counts
NUMBER_STATES = 3
COLORABLE_ICON = '?'
@ -552,6 +602,8 @@ class Text:
nest('A' + number + ':' + handle.decode() + ':', 'A' + number)
for key, val in self.decorators.items():
if val is None:
continue
if key == 'fg':
reset = self.section.THEMES[self.section.theme][0]
nest('F' + getColor(val), 'F' + reset)

15
config/lemonbar/launch.sh Executable file
View file

@ -0,0 +1,15 @@
#!/usr/bin/env sh
# TODO Make this better
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
ex="$DIR/bar.py"
# Terminate already running bar instances
ps -af | grep "python3 $ex" | grep -v grep | awk '{print $2}' | while read p; do kill $p; done
killall -q lemonbar
$ex

View file

@ -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
ramp = "" if sink.mute else (" " + self.ramp(vol))
sinks.append(icon + ramp)
return " ".join(sinks)
t = Text(icon, fg=fg)
sinks.append(t)
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 = []

View file

@ -190,10 +190,16 @@ class ThreadedUpdaterThread(threading.Thread):
def __init__(self, updater, *args, **kwargs):
self.updater = updater
threading.Thread.__init__(self, *args, **kwargs)
self.looping = True
def run(self):
while True:
self.updater.loop()
try:
while self.looping:
self.updater.loop()
except BaseException as e:
log.error("Error with {}".format(self.updater))
log.error(e, exc_info=True)
self.updater.updateText("")
class ThreadedUpdater(Updater):

View file

@ -76,7 +76,8 @@ enable-ipc = true
inherit = bar/base
modules-center = mpd
modules-right = cpu memory temperature 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
modules-right = cpu memory temperature mail vpncheck eth wlan bbswitch xbacklight volume battery shortdate
tray-position = right
tray-padding = 2

View file

@ -1 +0,0 @@
/home/geoffrey/.local/share/systemd/user/mbsync.timer

View file

@ -1 +0,0 @@
/usr/lib/systemd/user/syncthing.service