aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2021-06-26 13:18:37 +0200
committerMattias Andrée <maandree@kth.se>2021-06-26 13:18:37 +0200
commit3e21f6d13c0a70db95fec8b5a71b758223ff4293 (patch)
tree6d6d2eddb243935007ce1e316c61470224f93df0
parentAdd inputrc for readline + m (diff)
downloaddotfiles-3e21f6d13c0a70db95fec8b5a71b758223ff4293.tar.gz
dotfiles-3e21f6d13c0a70db95fec8b5a71b758223ff4293.tar.bz2
dotfiles-3e21f6d13c0a70db95fec8b5a71b758223ff4293.tar.xz
Add xpybar
Signed-off-by: Mattias Andrée <maandree@kth.se>
-rw-r--r--xpybar/Makefile18
-rw-r--r--xpybar/config/Makefile8
-rw-r--r--xpybar/config/adjbacklight90
-rw-r--r--xpybar/config/adjbrilliance94
-rw-r--r--xpybar/config/bin/sun61
-rw-r--r--xpybar/config/common.py231
-rw-r--r--xpybar/config/myalsa.py260
-rw-r--r--xpybar/config/mybacklight.py76
-rw-r--r--xpybar/config/mybattery.py163
-rw-r--r--xpybar/config/mybrilliance.py70
-rw-r--r--xpybar/config/myclock.py106
-rw-r--r--xpybar/config/mycomputer.py160
-rw-r--r--xpybar/config/mycpu.py214
-rw-r--r--xpybar/config/myii.py113
-rw-r--r--xpybar/config/myio.py351
-rw-r--r--xpybar/config/myipaddress.py89
-rw-r--r--xpybar/config/myirc.py38
-rw-r--r--xpybar/config/myleapsec.py69
-rw-r--r--xpybar/config/mylid.py32
-rw-r--r--xpybar/config/mymemory.py117
-rw-r--r--xpybar/config/mymoc.py70
-rw-r--r--xpybar/config/mynetwork.py205
-rw-r--r--xpybar/config/mynews.py45
-rw-r--r--xpybar/config/myscroll.py12
-rw-r--r--xpybar/config/mystat.py91
-rw-r--r--xpybar/config/mysun.py43
-rw-r--r--xpybar/config/mytimer.py142
-rw-r--r--xpybar/config/mytop.py40
-rw-r--r--xpybar/config/myweather.py420
-rw-r--r--xpybar/config/myxmonad.py158
-rw-r--r--xpybar/config/xmonad-monitor.gpp222
31 files changed, 3808 insertions, 0 deletions
diff --git a/xpybar/Makefile b/xpybar/Makefile
new file mode 100644
index 0000000..6d0d753
--- /dev/null
+++ b/xpybar/Makefile
@@ -0,0 +1,18 @@
+.POSIX:
+include ../common.mk
+
+install:
+ $(CHECK_INSTALLED) general-preprocessor
+ make -C config
+ mkdir -p -- ~/.config
+ if test -L ~/.config/xpybar; then \
+ test "$$(readlink -- ~/.config/xpybar)" = ~/.dotfiles/xpybar/config; \
+ else \
+ test ! -e ~/.config/xpybar && \
+ ln -s -- ~/.dotfiles/xpybar/config ~/.config/xpybar; \
+ fi
+
+uninstall:
+ -unlink -- ~/.config/xpybar
+
+.PHONY: install uninstall
diff --git a/xpybar/config/Makefile b/xpybar/config/Makefile
new file mode 100644
index 0000000..dc444aa
--- /dev/null
+++ b/xpybar/config/Makefile
@@ -0,0 +1,8 @@
+.POSIX:
+
+all: xmonad-monitor
+
+xmonad-monitor: xmonad-monitor.gpp
+ gpp -s '%%' < $@.gpp > $@
+
+.PHONY: all
diff --git a/xpybar/config/adjbacklight b/xpybar/config/adjbacklight
new file mode 100644
index 0000000..a480262
--- /dev/null
+++ b/xpybar/config/adjbacklight
@@ -0,0 +1,90 @@
+# -*- python -*-
+import os
+import sys
+import time
+
+OUTPUT, HEIGHT, YPOS, TOP = 0, 2 * 12, 0, True
+
+LEFT_BUTTON = 1
+MIDDLE_BUTTON = 2
+RIGHT_BUTTON = 3
+SCROLL_UP = 4
+SCROLL_DOWN = 5
+SCROLL_LEFT = 6
+SCROLL_RIGHT = 7
+FORWARD_BUTTON = 8
+BACKWARD_BUTTON = 9
+
+r, w = os.pipe()
+pid = os.fork()
+if not pid:
+ os.dup2(r, 0)
+ os.close(w)
+ os.execlp('adjbacklight', 'adjbacklight')
+ sys.exit(1)
+os.close(r)
+
+def get_backlight():
+ r, w = os.pipe()
+ pid = os.fork()
+ if not pid:
+ os.dup2(w, 1)
+ os.execlp('adjbacklight', 'adjbacklight', '-g')
+ sys.exit(1)
+ os.close(w)
+ ret = b''
+ while True:
+ bs = os.read(r, 512)
+ if not bs:
+ break
+ ret += bs
+ os.close(r)
+ os.waitpid(pid, 0)
+ ret = float(ret.decode('utf-8', 'replace').rstrip('%\n')) / 100
+ return ret
+
+def set_backlight():
+ if 0 <= backlight <= 1:
+ os.write(w, b'%f%%\n' % (backlight * 100))
+ time.sleep(0.0001)
+
+backlight = get_backlight()
+
+def redraw():
+ x = int(bar.width * backlight + 0.5)
+ bar.change_colour(bar.foreground)
+ bar.window.fill_rectangle(bar.gc, 0, 0, x, HEIGHT)
+ bar.change_colour(bar.background)
+ bar.window.fill_rectangle(bar.gc, x, 0, bar.width - x, HEIGHT)
+
+import x as _x
+_get_event_mask = _x.get_event_mask
+def get_event_mask():
+ return _get_event_mask() | Xlib.X.ButtonMotionMask
+_x.get_event_mask = get_event_mask
+
+def unhandled_event(e):
+ global backlight
+ if isinstance(e, Xlib.protocol.event.ButtonPress):
+ button, x = e.detail, e.event_x
+ if button == LEFT_BUTTON:
+ backlight = min(max(0, x / bar.width), 1)
+ set_backlight()
+ bar.invalidate()
+ elif button == SCROLL_UP:
+ backlight = min(backlight + 0.05, 1)
+ set_backlight()
+ bar.invalidate()
+ elif button == SCROLL_DOWN:
+ backlight = max(backlight - 0.05, 0)
+ set_backlight()
+ bar.invalidate()
+ elif button in (MIDDLE_BUTTON, RIGHT_BUTTON, FORWARD_BUTTON, BACKWARD_BUTTON):
+ os.close(w)
+ os.waitpid(pid, 0)
+ raise KeyboardInterrupt()
+ elif isinstance(e, Xlib.protocol.event.MotionNotify):
+ if e.detail == 0:
+ backlight = min(max(0, e.event_x / bar.width), 1)
+ set_backlight()
+ bar.invalidate()
diff --git a/xpybar/config/adjbrilliance b/xpybar/config/adjbrilliance
new file mode 100644
index 0000000..0ffb09d
--- /dev/null
+++ b/xpybar/config/adjbrilliance
@@ -0,0 +1,94 @@
+# -*- python -*-
+import os
+import sys
+import time
+import signal
+
+OUTPUT, HEIGHT, YPOS, TOP = 0, 2 * 12, 0, True
+
+LEFT_BUTTON = 1
+MIDDLE_BUTTON = 2
+RIGHT_BUTTON = 3
+SCROLL_UP = 4
+SCROLL_DOWN = 5
+SCROLL_LEFT = 6
+SCROLL_RIGHT = 7
+FORWARD_BUTTON = 8
+BACKWARD_BUTTON = 9
+
+path = os.getenv('XDG_RUNTIME_DIR', '/tmp') + '/.xpybar.brilliance'
+pid = None
+pending = False
+
+def sigchld(*_):
+ global pid, pending
+ if pid is not None:
+ r, _ = os.waitpid(-1, 0)
+ if r == pid:
+ pid = None
+ if pending:
+ pending = False
+ set_brilliance()
+ else:
+ with open(path, 'wb') as file:
+ return file.write(('%f' % brilliance).encode('utf-8'))
+
+def get_brilliance():
+ try:
+ with open(path, 'rb') as file:
+ return float(file.read().decode('utf-8', 'strict'))
+ except:
+ return 1
+
+def set_brilliance():
+ global pid, pending
+ if pid is None:
+ pid = os.fork()
+ if not pid:
+ if brilliance == 1:
+ os.execlp('cg-brilliance', 'cg-brilliance', '-x')
+ else:
+ os.execlp('cg-brilliance', 'cg-brilliance', '--', '%f' % brilliance)
+ sys.exit(1)
+ else:
+ pending = True
+
+signal.signal(signal.SIGCHLD, sigchld)
+brilliance = get_brilliance()
+
+def redraw():
+ x = int(bar.width * brilliance + 0.5)
+ bar.change_colour(bar.foreground)
+ bar.window.fill_rectangle(bar.gc, 0, 0, x, HEIGHT)
+ bar.change_colour(bar.background)
+ bar.window.fill_rectangle(bar.gc, x, 0, bar.width - x, HEIGHT)
+
+import x as _x
+_get_event_mask = _x.get_event_mask
+def get_event_mask():
+ return _get_event_mask() | Xlib.X.ButtonMotionMask
+_x.get_event_mask = get_event_mask
+
+def unhandled_event(e):
+ global brilliance
+ if isinstance(e, Xlib.protocol.event.ButtonPress):
+ button, x = e.detail, e.event_x
+ if button == LEFT_BUTTON:
+ brilliance = min(max(0, x / bar.width), 1)
+ set_brilliance()
+ bar.invalidate()
+ elif button == SCROLL_UP:
+ brilliance = min(brilliance + 0.05, 1)
+ set_brilliance()
+ bar.invalidate()
+ elif button == SCROLL_DOWN:
+ brilliance = max(brilliance - 0.05, 0)
+ set_brilliance()
+ bar.invalidate()
+ elif button in (MIDDLE_BUTTON, RIGHT_BUTTON, FORWARD_BUTTON, BACKWARD_BUTTON):
+ raise KeyboardInterrupt()
+ elif isinstance(e, Xlib.protocol.event.MotionNotify):
+ if e.detail == 0:
+ brilliance = min(max(0, e.event_x / bar.width), 1)
+ set_brilliance()
+ bar.invalidate()
diff --git a/xpybar/config/bin/sun b/xpybar/config/bin/sun
new file mode 100644
index 0000000..072542c
--- /dev/null
+++ b/xpybar/config/bin/sun
@@ -0,0 +1,61 @@
+# -*- python -*-
+import os
+
+# Geographical coodinates.
+try:
+ with open(os.getenv("XDG_CONFIG_HOME") + "/geolocation", 'rb') as file:
+ latitude, longitude = file.read().split(b'\n')[0].decode('utf-8', 'replace').split(' ')
+ latitude, longitude = float(latitude), float(longitude)
+except:
+ with open("/etc/geolocation", 'rb') as file:
+ latitude, longitude = file.read().split(b'\n')[0].decode('utf-8', 'replace').split(' ')
+ latitude, longitude = float(latitude), float(longitude)
+
+# The colour temperature at day and at night.
+temperature_day, temperature_night = 5500, 3500
+
+
+# Get current time.
+now = epoch()
+now_m = now - 1
+now_p = now + 1
+now_d = now_p - now_m
+now = epoch_to_julian_centuries(now)
+now_m = epoch_to_julian_centuries(now_m)
+now_p = epoch_to_julian_centuries(now_p)
+
+# The visibility of the Sun.
+dayness = sun(latitude, longitude, now)
+dayness_m = sun(latitude, longitude, now_m)
+dayness_p = sun(latitude, longitude, now_p)
+dayness_d = (dayness_p - dayness_m) / now_d
+
+# The Sun's elevation.
+elevation = solar_elevation(latitude, longitude, now)
+elevation_m = solar_elevation(latitude, longitude, now_m)
+elevation_p = solar_elevation(latitude, longitude, now_p)
+elevation_d = (elevation_p - elevation_m) / now_d
+
+# Calculation of the colour temperature.
+temperature = temperature_day * dayness + temperature_night * (1 - dayness)
+temperature_m = temperature_day * dayness_m + temperature_night * (1 - dayness_m)
+temperature_p = temperature_day * dayness_p + temperature_night * (1 - dayness_p)
+temperature_d = (temperature_p - temperature_m) / now_d
+
+# Calculate the whitepoint with adjusted colour temperature.
+whitepoint = divide_by_maximum(cmf_10deg(temperature))
+whitepoint = [str(int(c * 255 + 0.5)) for c in whitepoint]
+whitepoint = ';'.join(whitepoint)
+
+# Convert derivative to per minutes from per seconds.
+temperature_d *= 60
+elevation_d *= 60
+dayness_d *= 60
+
+
+# Print information.
+dayness *= 100
+dayness_d *= 100
+output = '\033[38;2;%sm%i\033[0mK %.1fK/m %.1f° %.2f°/m %.0f%% %.1f%%/m'
+output %= (whitepoint, temperature, temperature_d, elevation, elevation_d, dayness, dayness_d)
+print(output)
diff --git a/xpybar/config/common.py b/xpybar/config/common.py
new file mode 100644
index 0000000..fca2b73
--- /dev/null
+++ b/xpybar/config/common.py
@@ -0,0 +1,231 @@
+# -*- python -*-
+
+import os
+import pwd
+import sys
+import time
+import threading
+import subprocess
+
+from util import *
+from __main__ import Bar
+
+import Xlib.X, Xlib.XK, Xlib.protocol.event
+
+class Globals:
+ def __init__(self):
+ self.globals = None
+
+ @property
+ def bar(self):
+ return self.globals['bar']
+ @property
+ def display(self):
+ return self.globals['display']
+
+ @property
+ def OUTPUT(self):
+ return self.globals['OUTPUT']
+ @property
+ def HEIGHT_PER_LINE(self):
+ return self.globals['HEIGHT_PER_LINE']
+ @property
+ def YPOS(self):
+ return self.globals['YPOS']
+ @property
+ def TOP(self):
+ return self.globals['TOP']
+ @property
+ def HEIGHT(self):
+ return self.globals['HEIGHT']
+
+ @OUTPUT.setter
+ def OUTPUT(self, value):
+ self.globals['OUTPUT'] = value
+ @HEIGHT_PER_LINE.setter
+ def HEIGHT_PER_LINE(self, value):
+ self.globals['HEIGHT_PER_LINE'] = value
+ @YPOS.setter
+ def YPOS(self, value):
+ self.globals['YPOS'] = value
+ @TOP.setter
+ def TOP(self, value):
+ self.globals['TOP'] = value
+ @HEIGHT.setter
+ def HEIGHT(self, value):
+ self.globals['HEIGHT'] = value
+
+G = Globals()
+
+LEFT_BUTTON = 1
+MIDDLE_BUTTON = 2
+RIGHT_BUTTON = 3
+SCROLL_UP = 4
+SCROLL_DOWN = 5
+SCROLL_LEFT = 6
+SCROLL_RIGHT = 7
+FORWARD_BUTTON = 8 # X1
+BACKWARD_BUTTON = 9 # X2
+
+limited = lambda v : min(max(int(v + 0.5), 0), 100)
+
+def t_(f):
+ try:
+ f()
+ except Exception as e:
+ time.sleep(1)
+
+t = lambda f : (lambda : t_(f))
+
+def pdeath(signal, *command):
+ try:
+ path = os.environ['PATH'].split(':')
+ for p in path:
+ p += '/pdeath'
+ if os.access(p, os.X_OK, effective_ids = True):
+ return (p, signal, *command)
+ except:
+ pass
+ return command
+
+HOME = pwd.getpwuid(os.getuid()).pw_dir
+
+SEPARATOR = ' │ '
+
+def invalidate():
+ try:
+ G.bar.invalidate()
+ except:
+ pass
+
+mqueue_map = {}
+
+class Entry:
+ def __init__(self, wrap = None, left = True, line = 0, prev = None):
+ self.left = left
+ self.line = line
+ self.prev = prev
+ self.width = 0
+ self.offset = 0
+ self.last_text = None
+ self.short_layout = None
+ self.wrapped = self.function if wrap is None else wrap(self.function)
+
+ def invalidate(self):
+ wrapper = self.wrapped
+ if isinstance(wrapper, Clocked):
+ wrapper = wrapper.sometimes
+ if isinstance(wrapper, Sometimes):
+ wrapper.counter = 0
+ invalidate()
+
+ def action(self, col, button, x, y):
+ pass
+
+ def click(self, row, lcol, rcol, button, x, y):
+ if row == self.line:
+ col = (lcol if self.left else rcol) - self.offset
+ if 0 <= col < self.width:
+ if self.left:
+ x -= self.offset * G.bar.font_width
+ else:
+ col = self.width - 1 - col
+ x = G.bar.width - x
+ x -= self.offset * G.bar.font_width
+ x = self.width * G.bar.font_width - x
+ y -= self.line * G.HEIGHT_PER_LINE
+ try:
+ self.action(col, button, x, y)
+ except Exception as err:
+ print(str(err), file = sys.stderr, flush = True)
+ return True
+ return False
+
+ def function(self):
+ return SEPARATOR
+
+ def __call__(self, *args, **kwargs):
+ try:
+ text = self.wrapped(*args, **kwargs)
+ if text is not self.last_text:
+ self.width = Bar.coloured_length(text)
+ self.last_text = text
+ return text
+ except Exception as err:
+ print(err, file = sys.stderr, flush = True)
+
+ def update_position(self):
+ if self.prev is not None:
+ self.prev.update_position()
+ self.offset = self.prev.offset + self.prev.width
+
+class Group:
+ def __init__(self, functions):
+ self.pattern = ''
+ self.posupdate = []
+ left = None
+ right = None
+ lineno = 0
+ atleft = True
+ newline = False
+ lastright = None
+ new_functions = []
+ separator = None
+ for entry in functions:
+ if entry is None:
+ newline = not atleft
+ lineno += 1 if newline else 0
+ atleft = not atleft
+ continue
+ entry.left = atleft
+ entry.line = lineno
+ if newline:
+ self.pattern += '\n'
+ newline = False
+ if left is not None:
+ self.posupdate.append(left)
+ if right is not None:
+ self.posupdate.append(right)
+ left = None
+ right = None
+ lastright = None
+ if entry.left:
+ if left is not None:
+ separator = Entry(left = True)
+ new_functions.append(separator)
+ self.pattern += '%s'
+ separator.prev = left
+ left = separator
+ entry.prev = left
+ left = entry
+ elif right is None:
+ if left is not None:
+ self.pattern += '\0 '
+ right = entry
+ lastright = entry
+ else:
+ separator = Entry(left = False)
+ new_functions.append(separator)
+ self.pattern += '%s'
+ lastright.prev = separator
+ separator.prev = entry
+ lastright = entry
+ new_functions.append(entry)
+ self.pattern += '%s'
+ if left is not None:
+ self.posupdate.append(left)
+ if right is not None:
+ self.posupdate.append(right)
+ height = (lineno + 1) * G.HEIGHT_PER_LINE
+ if G.HEIGHT < height:
+ G.HEIGHT = height
+ self.functions = new_functions
+
+def colour_interpol(low, high, weight):
+ rl, rh = (low >> 16) & 255, (high >> 16) & 255
+ gl, gh = (low >> 8) & 255, (high >> 8) & 255
+ bl, bh = (low >> 0) & 255, (high >> 0) & 255
+ r = int(rl * (1 - weight) + rh * weight)
+ g = int(gl * (1 - weight) + gh * weight)
+ b = int(bl * (1 - weight) + bh * weight)
+ return '38;2;%i;%i;%i' % (r, g, b)
diff --git a/xpybar/config/myalsa.py b/xpybar/config/myalsa.py
new file mode 100644
index 0000000..14b8a7b
--- /dev/null
+++ b/xpybar/config/myalsa.py
@@ -0,0 +1,260 @@
+# -*- python -*-
+from plugins.alsa import ALSA
+
+from common import *
+
+class MyALSA(Entry):
+ def __init__(self, *args, timeout = None, sleep = None, cards = -1, mixers = None, colours = {}, **kwargs): ## TODO support multiple cards and all mixers
+ self.colours = colours if colours is not None else {}
+ if timeout is not None:
+ self.timeout = timeout
+ else:
+ self.timeout = 5
+ if sleep is not None:
+ self.sleep = sleep
+ else:
+ self.sleep = 5
+ if mixers is None:
+ mixers = ('Master', 'PCM')
+ elif isinstance(mixers, str):
+ mixers = [mixers]
+ else:
+ mixers = mixers
+ if cards is None:
+ cards = [-1]
+ elif isinstance(cards, str) or isinstance(cards, int):
+ cards = [cards]
+ else:
+ cards = cards
+ self.alsa = []
+ for card in cards:
+ for mixer in mixers: ## TODO support by name (and 'default')
+ try:
+ if isinstance(mixer, tuple) or isinstance(mixer, list):
+ self.alsa.append([ALSA(card, m) for m in mixer])
+ else:
+ self.alsa.append(ALSA(card, mixer))
+ except:
+ pass
+ if True:
+ method = 3
+ try:
+ path = os.environ['PATH'].split(':')
+ for p in path:
+ p += '/bus'
+ if os.access(p, os.F_OK | os.X_OK, effective_ids = True):
+ method = 0
+ break
+ except:
+ pass
+ else:
+ try:
+ import posix_ipc
+ method = 1
+ except:
+ method = 3
+ try:
+ path = os.environ['PATH'].split(':')
+ for p in path:
+ p += '/cmdipc'
+ if os.access(p, os.F_OK | os.X_OK, effective_ids = True):
+ method = 2
+ break
+ except:
+ pass
+ self.sep_width = Bar.coloured_length(SEPARATOR)
+ self.get_volume()
+ self.broadcast_update = None
+ if method == 0:
+ self.create_broadcast_update_bus()
+ Entry.__init__(self, *args, **kwargs)
+ xasync((self.refresh_bus, self.refresh_posix_ipc, self.refresh_cmdipc, self.refresh_wait)[method], name = 'alsa')
+
+ def action(self, col, button, x, y):
+ mixer = 0
+ mixer_text = self.text.split(SEPARATOR)
+ while mixer < len(mixer_text): ## the limit is just a precaution
+ mixer_width = Bar.coloured_length(mixer_text[mixer])
+ if col >= mixer_width:
+ col -= mixer_width
+ if col < self.sep_width:
+ return
+ col -= self.sep_width
+ else:
+ break
+ mixer += 1
+ mixer_text = mixer_text[mixer]
+
+ channel = -1
+ mixer_text = mixer_text.split(': ')
+ mixer_label = ': '.join(mixer_text[:-1]) + ': '
+ mixer_text = mixer_text[-1].split(' ')
+ mixer_label = Bar.coloured_length(mixer_label)
+ if col >= mixer_label:
+ col -= mixer_label
+ while channel < len(mixer_text): ## the limit is just a precaution
+ channel += 1
+ channel_width = Bar.coloured_length(mixer_text[channel])
+ if col < channel_width:
+ break
+ col -= channel_width
+ if col < 1:
+ channel = -1
+ break
+ col -= 1
+
+ mixer = self.alsa[mixer]
+ if isinstance(mixer, list):
+ if button == LEFT_BUTTON:
+ self.switch_exclusive(mixer)
+ elif button == LEFT_BUTTON:
+ muted = mixer.get_mute()
+ muted = not (any(muted) if channel == -1 else muted[channel])
+ mixer.set_mute(muted, channel)
+ elif button == RIGHT_BUTTON:
+ volume = mixer.get_volume()
+ volume = limited(sum(volume) / len(volume))
+ mixer.set_volume(volume, -1)
+ elif button in (SCROLL_UP, SCROLL_DOWN):
+ volume = mixer.get_volume()
+ adj = -5 if button == SCROLL_DOWN else 5
+ if channel < 0:
+ for ch in range(len(volume)):
+ mixer.set_volume(limited(volume[ch] + adj), ch)
+ else:
+ mixer.set_volume(limited(volume[channel] + adj), channel)
+ else:
+ return
+ self.get_volume()
+ self.invalidate()
+ if self.broadcast_update is not None:
+ self.broadcast_update()
+
+ def switch_exclusive(self, mixers):
+ def f(status):
+ if all(status):
+ return True
+ elif any(status):
+ return None
+ else:
+ return False
+ muted = [f(m.get_mute()) for m in mixers]
+ count = sum((0 if m else 1) for m in muted)
+ if None in muted or count != 1:
+ index = 0
+ else:
+ [index] = [i for i, m in enumerate(muted) if not m]
+ index = (index + 1) % len(mixers)
+ for m in mixers:
+ m.set_mute(True)
+ mixers[index].set_mute(False)
+
+ def get_exclusive(self, mixers):
+ def f(status):
+ if all(status):
+ return True
+ elif any(status):
+ return None
+ else:
+ return False
+ muted = [f(m.get_mute()) for m in mixers]
+ count = sum((0 if m else 1) for m in muted)
+ if None in muted or count != 1:
+ index = 0
+ for m in mixers:
+ m.set_mute(True)
+ mixers[index].set_mute(False)
+ else:
+ [index] = [i for i, m in enumerate(muted) if not m]
+ name = mixers[index].mixername
+ if name in self.colours:
+ name = '\033[%sm%s\033[0m' % (self.colours[name], name)
+ return name
+
+ def get_volume(self):
+ text_v = lambda v : '--%' if v is None else ('%2i%%' % v)[:3]
+ read_m = lambda m : '%s: %s' % (m.mixername, ' '.join(text_v(v) for v in m.get_volume()))
+ text = SEPARATOR.join((self.get_exclusive(m) if isinstance(m, list) else read_m(m)) for m in self.alsa)
+ self.text = text
+
+ def refresh_bus(self):
+ if 'BUS_AUDIO' in os.environ:
+ bus = os.environ['BUS_AUDIO']
+ else:
+ bus = os.environ['XDG_RUNTIME_DIR'] + '/@bus/audio'
+ wait = pdeath('HUP', 'bus', 'wait', bus, 'true')
+ while True:
+ try:
+ spawn_read(*wait)
+ self.get_volume()
+ except:
+ time.sleep(self.sleep)
+
+ def create_broadcast_update_bus(self):
+ if 'BUS_AUDIO' in os.environ:
+ bus = os.environ['BUS_AUDIO']
+ else:
+ bus = os.environ['XDG_RUNTIME_DIR'] + '/@bus/audio'
+ self.bus_broadcast = pdeath('HUP', 'bus', 'broadcast', bus, '%i volume' % os.getpid())
+ self.broadcast_update = lambda : spawn_read(*(self.bus_broadcast))
+
+ def refresh_posix_ipc(self):
+ import posix_ipc
+ s = posix_ipc.Semaphore('/.xpybar.alsa.0', posix_ipc.O_CREAT, 0o600, 1)
+ c = posix_ipc.Semaphore('/.xpybar.alsa.1', posix_ipc.O_CREAT, 0o600, 0)
+ q = posix_ipc.Semaphore('/.xpybar.alsa.2', posix_ipc.O_CREAT, 0o600, 0)
+ try:
+ s.acquire(self.timeout)
+ while True:
+ failed = False
+ try:
+ c.release()
+ try:
+ s.release()
+ try:
+ q.acquire(timeout)
+ except posix_ipc.BusyError:
+ sys.exit(1)
+ pass
+ finally:
+ s.acquire(self.timeout)
+ finally:
+ c.acquire(self.timeout)
+ except:
+ sys.exit(1)
+ failed = True
+ try:
+ self.get_volume()
+ except:
+ sys.exit(1)
+ failed = True
+ if failed:
+ time.sleep(self.sleep)
+ finally:
+ s.release()
+
+ def refresh_cmdipc(self):
+ enter = pdeath('HUP', 'cmdipc', '-PCck', '/.xpybar.alsa.0/.xpybar.alsa.1/.xpybar.alsa.2', 'enter', '-b%i' % self.timeout)
+ wait = pdeath('HUP', 'cmdipc', '-PCk', '/.xpybar.alsa.0/.xpybar.alsa.1/.xpybar.alsa.2', 'wait', '-b%i' % self.timeout)
+ leave = pdeath('HUP', 'cmdipc', '-PCk', '/.xpybar.alsa.0/.xpybar.alsa.1/.xpybar.alsa.2', 'leave')
+ try:
+ spawn_read(*enter)
+ while True:
+ try:
+ spawn_read(*wait)
+ self.get_volume()
+ except:
+ time.sleep(self.sleep)
+ finally:
+ spawn_read(*leave)
+
+ def refresh_wait(self):
+ while True:
+ try:
+ time.sleep(self.sleep)
+ self.get_volume()
+ except:
+ pass
+
+ def function(self):
+ return self.text
diff --git a/xpybar/config/mybacklight.py b/xpybar/config/mybacklight.py
new file mode 100644
index 0000000..bcb3d53
--- /dev/null
+++ b/xpybar/config/mybacklight.py
@@ -0,0 +1,76 @@
+# -*- python -*-
+import os
+import sys
+from common import *
+
+class MyBacklight(Entry):
+ def __init__(self, *args, **kwargs):
+ self.show_label = True
+ self.backlight = self.get_backlight()
+ self.counter = 0
+ Entry.__init__(self, *args, **kwargs)
+
+ def get_backlight(self):
+ r, w = os.pipe()
+ pid = os.fork()
+ if not pid:
+ os.dup2(w, 1)
+ os.execlp('adjbacklight', 'adjbacklight', '-g')
+ sys.exit(1)
+ os.close(w)
+ ret = b''
+ while True:
+ bs = os.read(r, 512)
+ if not bs:
+ break
+ ret += bs
+ os.close(r)
+ os.waitpid(pid, 0)
+ ret = float(ret.decode('utf-8', 'replace').rstrip('%\n')) / 100
+ return ret
+
+ def set_backlight(self):
+ pid = os.fork()
+ if not pid:
+ os.execlp('adjbacklight', 'adjbacklight', '-s', '%f%%' % (self.backlight * 100))
+ sys.exit(1)
+ os.waitpid(pid, 0)
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ pid = os.fork()
+ if not pid:
+ pid = os.fork()
+ if not pid:
+ os.execlp('xpybar', 'xpybar', '-c', '%s/.config/xpybar/adjbacklight' % HOME)
+ sys.exit(1)
+ sys.exit(0)
+ os.waitpid(pid, 0)
+ elif button == MIDDLE_BUTTON:
+ self.show_label = not self.show_label
+ self.invalidate()
+ elif button == RIGHT_BUTTON:
+ self.backlight = self.get_backlight()
+ self.invalidate()
+ elif button == SCROLL_UP:
+ self.backlight = min(self.backlight + 0.05, 1)
+ self.set_backlight()
+ self.invalidate()
+ elif button == SCROLL_DOWN:
+ self.backlight = max(self.backlight - 0.05, 0)
+ self.set_backlight()
+ self.invalidate()
+
+ def function(self):
+ self.counter += 1
+ if self.counter == 6:
+ self.counter = 0
+ self.backlight = self.get_backlight()
+ val = int(self.backlight * 100 + 0.5)
+ if val >= 100:
+ val = str(val)
+ else:
+ val = '%2i%%' % val
+ if self.show_label:
+ val = 'Blight: ' + val
+ return val
diff --git a/xpybar/config/mybattery.py b/xpybar/config/mybattery.py
new file mode 100644
index 0000000..3a8e167
--- /dev/null
+++ b/xpybar/config/mybattery.py
@@ -0,0 +1,163 @@
+# -*- python -*-
+from plugins.powersupply import PowerSupply
+
+from common import *
+
+class MyBattery(Entry):
+ COMPACT = 0
+ NORMAL = 1
+ DETAILED = 2
+
+ def __init__(self, *args, show_all = False, show_battery = 0, details = 1, batteries = None, **kwargs):
+ self.show_all = show_all
+ self.show_battery = show_battery
+ self.supply_filter = batteries
+ self.details = details
+ self.supplies = None
+ self.batteries = None
+ self.reload()
+ Entry.__init__(self, *args, **kwargs)
+
+ def reload(self):
+ name = None
+ newsupplies = {}
+ newbatteries = []
+ if self.batteries is not None:
+ if isinstance(self.batteries, int):
+ name = self.batteries[self.show_battery]
+ else:
+ name = self.show_battery
+ for supply in PowerSupply.supplies():
+ if self.supply_filter is not None:
+ if supply not in self.supply_filter:
+ continue
+ supply = PowerSupply(supply)
+ if supply.type != 'Battery':
+ continue
+ newsupplies[supply.name] = supply
+ newbatteries.append(supply.name)
+ if self.supply_filter is not None:
+ newbatteries = [b for b in self.supply_filter if b in newsupplies]
+ if name is None:
+ index = 0
+ elif isinstance(name, int):
+ index = name % len(newbatteries) if len(newbatteries) > 0 else 0
+ elif name in self.batteries:
+ index = self.batteries.index(index)
+ else:
+ index = 0
+ self.supplies, self.batteries, self.show_battery = newsupplies, newbatteries, index
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ self.show_all = not self.show_all
+ self.invalidate()
+ elif button == MIDDLE_BUTTON:
+ self.details += 1
+ self.details %= 3
+ self.invalidate()
+ elif button == RIGHT_BUTTON:
+ self.reload()
+ self.invalidate()
+ elif button == SCROLL_UP:
+ if len(self.batteries) > 0:
+ self.show_battery += 1
+ self.show_battery %= len(self.batteries)
+ self.invalidate()
+ elif button == SCROLL_DOWN:
+ if len(self.batteries) > 0:
+ self.show_battery -= 1
+ if self.show_battery < 0:
+ self.show_battery += len(self.batteries)
+ self.invalidate()
+
+ def colourise(self, text):
+ try:
+ value = text.replace(',', '.')
+ value = float(value) if '.' in value else int(value)
+ colour = '39'
+ if value < 80: colour = '32'
+ if value < 50: colour = '33'
+ if value < 15: colour = '31'
+ if value < 5: colour = '7;31'
+ return '\033[%sm%s\033[0m' % (colour, text)
+ except:
+ return text
+
+ def battery(self, bat):
+ bat = self.supplies[bat]
+ name = bat.name[:1].upper() + bat.name[1:].lower()
+ if bat.is_online():
+ try:
+ charge = (bat.get_charge(), bat.get_charge_full(), bat.charge_full_design)
+ current = bat.get_current()
+ status = bat.get_status().lower()
+ left = None
+ if charge[0] == charge[1]:
+ capacity = '100'
+ else:
+ capacity = '%.1f' % (charge[0] / charge[1] * 100)
+ capacity = self.colourise(capacity)
+
+ if current > 0:
+ if status == 'charging':
+ left = (charge[1] - charge[0]) / current
+ elif status == 'discharging':
+ left = charge[0] / current
+ if left is not None:
+ hours = int(left)
+ minutes = int((left - hours) * 60 + 0.5)
+ if minutes >= 60:
+ minutes -= 60
+ hours += 1
+ left = '%ih%02im until %s' % (hours, minutes, status.replace('ing', 'ed'))
+
+ if self.details == MyBattery.COMPACT:
+ if status == 'full':
+ return 'full'
+ elif status == 'charging':
+ status = '+'
+ elif status == 'discharging':
+ status = '-'
+ else:
+ status = '?'
+ if left is None:
+ return '%s%%%s' % (capacity, status)
+ else:
+ return '%s%%%s %s' % (capacity, status, left)
+ elif self.details == MyBattery.NORMAL:
+ if left is None:
+ return '%s: %s%% %s' % (name, capacity, status)
+ else:
+ return '%s: %s%% %s %s' % (name, capacity, status, left)
+ else:
+ if charge[1] == charge[2]:
+ health = '100'
+ else:
+ health = '%.1f' % (charge[1] / charge[2] * 100)
+ health = self.colourise(health)
+ volt = bat.get_voltage() / bat.voltage_min_design
+ if volt < 1:
+ volt = '\033[31m%.1f\033[0m' % volt
+ elif volt < 1.05:
+ volt = '\033[33m%.1f\033[0m' % volt
+ else:
+ volt = '%.1f' % volt
+ if left is None:
+ return '%s: %s%% %s, %s%% healthy, %sx voltage' % (name, capacity, status, health, volt)
+ else:
+ return '%s: %s%% %s %s, %s%% healthy, %sx voltage' % (name, capacity, status, left, health, volt)
+ except Exception as e:
+ return (repr(e))
+ return ('%s: ???' % name) if self.details > 0 else '???'
+ else:
+ return ('%s: offline' % name) if self.details > 0 else 'offline'
+
+ def function(self):
+ if len(self.batteries) == 0:
+ return 'no batteries available'
+
+ if self.show_all:
+ return SEPARATOR.join(self.battery(bat) for bat in self.batteries)
+ else:
+ return self.battery(self.batteries[self.show_battery])
diff --git a/xpybar/config/mybrilliance.py b/xpybar/config/mybrilliance.py
new file mode 100644
index 0000000..f7647e7
--- /dev/null
+++ b/xpybar/config/mybrilliance.py
@@ -0,0 +1,70 @@
+# -*- python -*-
+import os
+import sys
+from common import *
+
+class MyBrilliance(Entry):
+ def __init__(self, *args, **kwargs):
+ self.show_label = True
+ self.brilliance = self.get_brilliance()
+ self.counter = 0
+ self.path = os.getenv('XDG_RUNTIME_DIR', '/tmp') + '/.xpybar.brilliance'
+ Entry.__init__(self, *args, **kwargs)
+
+ def get_brilliance(self):
+ try:
+ with open(self.path, 'rb') as file:
+ return float(file.read().decode('utf-8', 'strict'))
+ except:
+ return 1
+
+ def set_brilliance(self):
+ pid = os.fork()
+ if not pid:
+ if self.brilliance == 1:
+ os.execlp('cg-brilliance', 'cg-brilliance', '-x')
+ else:
+ os.execlp('cg-brilliance', 'cg-brilliance', '--', '%f' % self.brilliance)
+ sys.exit(1)
+ os.waitpid(pid, 0)
+ with open(self.path, 'wb') as file:
+ return file.write(('%f' % self.brilliance).encode('utf-8'))
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ pid = os.fork()
+ if not pid:
+ pid = os.fork()
+ if not pid:
+ os.execlp('xpybar', 'xpybar', '-c', '%s/.config/xpybar/adjbrilliance' % HOME)
+ sys.exit(1)
+ sys.exit(0)
+ os.waitpid(pid, 0)
+ elif button == MIDDLE_BUTTON:
+ self.show_label = not self.show_label
+ self.invalidate()
+ elif button == RIGHT_BUTTON:
+ self.brilliance = self.get_brilliance()
+ self.invalidate()
+ elif button == SCROLL_UP:
+ self.brilliance = min(self.brilliance + 0.05, 1)
+ self.set_brilliance()
+ self.invalidate()
+ elif button == SCROLL_DOWN:
+ self.brilliance = max(self.brilliance - 0.05, 0)
+ self.set_brilliance()
+ self.invalidate()
+
+ def function(self):
+ self.counter += 1
+ if self.counter == 6:
+ self.counter = 0
+ self.brilliance = self.get_brilliance()
+ val = int(self.brilliance * 100 + 0.5)
+ if val >= 100:
+ val = str(val)
+ else:
+ val = '%2i%%' % val
+ if self.show_label:
+ val = 'Brill: ' + val
+ return val
diff --git a/xpybar/config/myclock.py b/xpybar/config/myclock.py
new file mode 100644
index 0000000..f97c8ea
--- /dev/null
+++ b/xpybar/config/myclock.py
@@ -0,0 +1,106 @@
+# -*- python -*-
+from plugins.tzclock import TZClock
+
+from common import *
+
+class MyClock(Entry):
+ def __init__(self, *args, format = '%Y-%m-%d %T', long_format = '%Y-(%m)%b-%d %T, %a w%V, %Z', tzs = None, **kwargs):
+ self.default_format = format
+ self.long_format = long_format
+ self.clock = G.clock
+ self.set_format(self.clock, self.default_format)
+ self.tzs = [] if tzs is None else tzs
+ self.tzi = 0
+ self.twilight = '39'
+ mqueue_map['timefmt'] = self.mqueue
+ mqueue_map['tz'] = self.mqueue
+ Entry.__init__(self, *args, **kwargs)
+
+ def mqueue(self, args):
+ if args[0] == 'timefmt':
+ args = args[1:]
+ if len(args) == 0:
+ self.action(0, RIGHT_BUTTON, 0, 0)
+ else:
+ self.set_format(self.clock, ' '.join(args))
+ self.invalidate()
+ if args[0] == 'tz':
+ args = args[1:]
+ if len(args) == 0:
+ self.action(0, LEFT_BUTTON, 0, 0)
+ else:
+ old_clock = self.clock
+ tz = '/'.join(args)
+ try:
+ tzclock = TZClock(timezone = tz, format = self.clock.u_format)
+ except:
+ try:
+ tzclock = TZClock(timezone = tz.upper(), format = self.clock.u_format)
+ except:
+ print('%s: unknown typezone: %s' % (sys.argv[0], tz), file = sys.stderr, flush = True)
+ return
+ tzclock.u_format = tzclock.format
+ self.clock = tzclock
+ if old_clock is not G.clock:
+ del old_clock
+ self.invalidate()
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ if self.clock is G.clock:
+ G.clock.utc = not G.clock.utc
+ self.set_format(G.clock, self.clock.u_format)
+ else:
+ self.set_format(G.clock, self.clock.u_format)
+ self.clock = G.clock
+ self.invalidate()
+ elif button == RIGHT_BUTTON:
+ if self.clock.u_format == self.default_format:
+ self.set_format(self.clock, self.long_format)
+ else:
+ self.set_format(self.clock, self.default_format)
+ self.invalidate()
+ elif button in (SCROLL_UP, SCROLL_DOWN):
+ i = self.tzi + (+1 if button == SCROLL_UP else -1)
+ if not (0 <= i <= len(self.tzs)):
+ return
+ self.tzi = i
+ old_clock = self.clock
+ if i > 0:
+ tzclock = TZClock(timezone = self.tzs[i - 1], format = self.clock.u_format)
+ tzclock.u_format = tzclock.format
+ self.clock = tzclock
+ else:
+ self.clock = G.clock
+ if old_clock is not G.clock:
+ del old_clock
+ self.invalidate()
+
+ def set_format(self, clck, format):
+ clck.u_format = format
+ if not (clck is G.clock and clck.utc):
+ clck.format = format
+ else:
+ fmt, esc = '', False
+ for c in format:
+ if esc:
+ fmt += c
+ if c == '%' or 'a' <= c.lower() <= 'z':
+ esc = False
+ if c == 'Z':
+ while fmt[-1] != '%':
+ fmt = fmt[:-1]
+ fmt = fmt[:-1] + 'UTC'
+ elif c == '%':
+ esc = True
+ fmt += c
+ else:
+ fmt += c
+ clck.format = fmt
+
+ def function(self):
+ clck = self.clock
+ colour = self.twilight
+ if clck is not G.clock or clck.utc:
+ colour += ';7'
+ return '\033[%sm%s\033[00m' % (colour, clck.read())
diff --git a/xpybar/config/mycomputer.py b/xpybar/config/mycomputer.py
new file mode 100644
index 0000000..bbfd539
--- /dev/null
+++ b/xpybar/config/mycomputer.py
@@ -0,0 +1,160 @@
+# -*- python -*-
+from plugins.pacman import Pacman
+from plugins.uname import Uname
+from plugins.users import Users
+from plugins.xdisplay import XDisplay
+from plugins.xkb import XKB
+
+from common import *
+
+class MyComputer(Entry):
+ def __init__(self, *args, locks = None, **kwargs):
+ self.have_linux_libre, self.have_pacman = True, None
+ self.linux_installed, self.linux_latest = None, None
+ self.last_uname_read = ''
+ self.last_users_read = ''
+ self.xdisplay = None
+ self.you = pwd.getpwuid(os.getuid()).pw_name
+ self.display = 0
+ self.show_detailed = False
+ self.keys = []
+ if locks is None:
+ locks = ['num', 'cap']
+ self.num_lock = 'num' in locks or 'number' in locks or 'numerical' in locks or 'numpad' in locks
+ self.cap_lock = 'cap' in locks or 'caps' in locks
+ self.scr_lock = 'scr' in locks or 'scroll' in locks or 'scrl' in locks
+ if self.num_lock:
+ self.keys.append(Xlib.XK.XK_Num_Lock)
+ if self.cap_lock:
+ self.keys.append(Xlib.XK.XK_Caps_Lock)
+ if self.scr_lock:
+ self.keys.append(Xlib.XK.XK_Scroll_Lock)
+ Entry.__init__(self, *args, **kwargs)
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ if self.display == 3:
+ col -= 5
+ if col % 4 == 3:
+ return
+ col //= 4
+ key = self.keys[col]
+ G.display.xtest_fake_input(Xlib.X.KeyPress, G.display.keysym_to_keycode(key))
+ G.display.xtest_fake_input(Xlib.X.KeyRelease, G.display.keysym_to_keycode(key))
+ G.display.flush()
+ elif button == MIDDLE_BUTTON:
+ self.show_detailed = not self.show_detailed
+ self.invalidate()
+ elif button == RIGHT_BUTTON:
+ if self.display == 0:
+ xasync(lambda : subprocess.Popen(['mate-system-monitor']).wait())
+ else:
+ self.invalidate()
+ elif button == SCROLL_UP:
+ self.display = min(self.display + 1, 2) ## TODO the keyboard support is not too good
+ self.invalidate()
+ elif button == SCROLL_DOWN:
+ self.display = max(self.display - 1, 0)
+ self.invalidate()
+
+ def function_uname(self):
+ uname = Uname()
+ nodename = uname.nodename
+ kernel_release = uname.kernel_release
+ operating_system = uname.operating_system
+
+ lts = '-lts' if kernel_release.lower().endswith('-lts') else ''
+ if self.have_pacman is None:
+ self.have_pacman = True
+ try:
+ self.linux_installed = Pacman('linux-libre' + lts, True)
+ except:
+ self.have_linux_libre = False
+ try:
+ self.linux_installed = Pacman('linux' + lts, True)
+ except:
+ self.have_pacman = False
+ if self.have_pacman:
+ try:
+ self.linux_latest = Pacman(('linux-libre' if self.have_linux_libre else 'linux') + lts, False)
+ except:
+ self.have_pacman = None
+ elif self.have_pacman:
+ try:
+ self.linux_installed = Pacman(('linux-libre' if self.have_linux_libre else 'linux') + lts, True)
+ self.linux_latest = Pacman(('linux-libre' if self.have_linux_libre else 'linux') + lts, False)
+ except:
+ self.have_pacman = None
+
+ if (self.have_pacman is not None) and self.have_pacman:
+ linux_running = kernel_release.split('-')
+ linux_running, kernel_release = linux_running[:2], linux_running[2:]
+ linux_running = '-'.join(linux_running)
+ kernel_release = '-' + '-'.join(kernel_release)
+ self.linux_installed = self.linux_installed.version
+ self.linux_latest = self.linux_latest.version
+ if self.linux_installed == self.linux_latest:
+ if linux_running == self.linux_installed:
+ linux_running = '\033[32m%s\033[39m' % linux_running
+ else:
+ if linux_running == self.linux_installed:
+ linux_running = '\033[33m%s\033[39m' % linux_running
+ else:
+ linux_running = '\033[31m%s\033[39m' % linux_running
+ kernel_release = linux_running + kernel_release
+
+ rc = '%s %s %s'
+ rc %= (nodename, kernel_release, operating_system)
+ self.last_uname_read = rc
+ return rc
+
+ def function_users(self): ## TODO use inotify
+ users = Users('devfs').users
+ def colour_user(user):
+ if user == 'root': return '\033[31m%s\033[39m'
+ elif not user == self.you: return '\033[33m%s\033[39m'
+ else: return '%s'
+ users = ['%s{%i}' % (colour_user(u) % u, len(users[u])) for u in users.keys()]
+ users = 'Usr: %s' % (' '.join(users))
+ self.last_users_read = users
+ return users
+
+ def function_xdisplay(self):
+ if self.xdisplay is None:
+ x = XDisplay()
+ if x.connection is None:
+ self.xdisplay = 'No DISPLAY environment variable set'
+ self.xdisplay_detailed = self.xdisplay
+ else:
+ self.xdisplay = 'X: %s' % x.connection
+ self.xdisplay_detailed = 'Host: %s' % ('(localhost)' if x.host is None else x.host)
+ self.xdisplay_detailed += SEPARATOR
+ self.xdisplay_detailed += 'Display: %s' % (x.display)
+ if x.screen is not None:
+ self.xdisplay_detailed += SEPARATOR
+ self.xdisplay_detailed += 'Screen: %s' % (x.screen)
+ if x.vt is not None:
+ self.xdisplay += '%sVt: %i' % (SEPARATOR, x.vt)
+ self.xdisplay_detailed += '%sVt: %i' % (SEPARATOR, x.vt)
+ return self.xdisplay_detailed if self.show_detailed else self.xdisplay
+
+ def function_xkb(self): ## TODO add update listener
+ xkb = XKB().get_locks()
+ kbd = 'Kbd:'
+ if self.num_lock:
+ kbd += ' \033[3%imnum\033[0m' % (1 if (xkb & XKB.NUM) == 0 else 2)
+ if self.cap_lock:
+ kbd += ' \033[3%imcap\033[0m' % (1 if (xkb & XKB.CAPS) == 0 else 2)
+ if self.scr_lock:
+ kbd += ' \033[3%imscr\033[0m' % (1 if (xkb & XKB.SCROLL) == 0 else 2)
+ return kbd
+
+ def function(self):
+ if self.display == 0:
+ return self.function_uname()
+ elif self.display == 1:
+ return self.function_users()
+ elif self.display == 2:
+ return self.function_xdisplay()
+ elif self.display == 3:
+ return self.function_xkb()
diff --git a/xpybar/config/mycpu.py b/xpybar/config/mycpu.py
new file mode 100644
index 0000000..af2c78d
--- /dev/null
+++ b/xpybar/config/mycpu.py
@@ -0,0 +1,214 @@
+# -*- python -*-
+from plugins.cpu import CPU
+from plugins.cpuonline import CPUOnline
+from plugins.softirqs import SoftIRQs
+
+from common import *
+
+class MyCPU(Entry):
+ def __init__(self, *args, **kwargs):
+ self.last_sirq_split = SoftIRQs()
+ cpu = CPU()
+ self.last_time = time.monotonic()
+ self.last_cpu_stat = cpu.cpu
+ self.last_cpu_total = sum(cpu.cpu)
+ self.last_cpus_total = [sum(c) for c in cpu.cpus]
+ self.last_cpus_stat = [[c[s] for c in cpu.cpus] for s in range(len(cpu.cpu))]
+ self.last_intr = cpu.intr_total
+ self.last_ctxt = cpu.ctxt
+ self.last_fork = cpu.processes
+ self.last_sirq = cpu.softirq_total
+ self.none = self.colourise(None)
+ self.coloured = tuple(self.colourise(i) for i in range(101))
+ self.show_all = True
+ self.show_function = 0
+ self.functions = [ lambda *v : self.function_cpu(CPU.idle, '', *v)
+ , self.function_intr
+ , self.function_ctxt
+ , self.function_fork
+ , self.function_proc
+ , self.function_sirq
+ ]
+ for key in self.last_sirq_split.keys:
+ make = lambda key : (lambda *v : self.function_sirq_split(key, *v))
+ self.functions.append(make(key))
+ self.functions += [ lambda *v : self.function_cpu(CPU.user, '(user)', *v)
+ , lambda *v : self.function_cpu(CPU.nice, '(nice)', *v)
+ , lambda *v : self.function_cpu(CPU.system, '(system)', *v)
+ , lambda *v : self.function_cpu(CPU.iowait, '(iowait)', *v)
+ , lambda *v : self.function_cpu(CPU.irq, '(irq)', *v)
+ , lambda *v : self.function_cpu(CPU.softirq, '(softirq)', *v)
+ , lambda *v : self.function_cpu(CPU.steal, '(steal)', *v)
+ , lambda *v : self.function_cpu(CPU.guest, '(guest)', *v)
+ , lambda *v : self.function_cpu(CPU.guest_nice, '(guest nice)', *v)
+ , self.function_load
+ , self.function_task
+ , self.function_pid
+ , self.function_online
+ ]
+ Entry.__init__(self, *args, **kwargs)
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ self.show_all = not self.show_all
+ self.invalidate()
+ elif button == SCROLL_UP:
+ n = self.show_function + 1
+ if n < len(self.functions):
+ self.show_function = n
+ self.invalidate()
+ elif button == SCROLL_DOWN:
+ n = self.show_function - 1
+ if n >= 0:
+ self.show_function = n
+ self.invalidate()
+
+ def usage(self, now_stat, now_total, last_stat, last_total, idle):
+ total = now_total - last_total
+ stat = now_stat - last_stat
+ return None if total == 0 else (total - stat if idle else stat) * 100 / total
+
+ def colourise_(self, value):
+ if value is None:
+ return self.none
+ else:
+ return self.coloured[limited(value)]
+
+ def colourise(self, value):
+ if value is None:
+ return '--%'
+ elif value >= 100:
+ return '\033[31m100\033[0m'
+ colour = '39'
+ if value >= 5: colour = '32'
+ if value >= 50: colour = '33'
+ if value >= 90: colour = '31'
+ return '\033[%sm%2i\033[0m%%' % (colour, value)
+
+ def function_cpu(self, stat, name, cpu, display, tdiff):
+ now_cpu_stat = self.now_cpu_stat[stat]
+ now_cpus_stat = self.now_cpus_stat[stat]
+ last_cpus_stat = self.last_cpus_stat[stat]
+ last_cpu_stat = self.last_cpu_stat[stat]
+
+ if display:
+ cpu = (now_cpu_stat, self.now_cpu_total, last_cpu_stat, self.last_cpu_total)
+ cpu = self.colourise_(self.usage(*cpu, idle = stat == CPU.idle))
+ if self.show_all:
+ cpus = (now_cpus_stat, self.now_cpus_total, last_cpus_stat, self.last_cpus_total)
+ cpus = ' '.join(self.colourise_(self.usage(*c, idle = stat == CPU.idle)) for c in zip(*cpus))
+ cpu = 'Cpu%s: %s : %s' % (name, cpus, cpu)
+ else:
+ cpu = 'Cpu%s: %s' % (name, cpu)
+
+ return cpu
+
+ def function_intr(self, cpu, display, tdiff):
+ now_intr = cpu.intr_total
+ if display:
+ cpu = 'Intr: %.0fHz' % ((now_intr - self.last_intr) / tdiff)
+ self.last_intr = now_intr
+ return cpu
+
+ def function_ctxt(self, cpu, display, tdiff):
+ now_ctxt = cpu.ctxt
+ if display:
+ cpu = 'Ctxt: %.0fHz' % ((now_ctxt - self.last_ctxt) / tdiff)
+ self.last_ctxt = now_ctxt
+ return cpu
+
+ def function_fork(self, cpu, display, tdiff):
+ now_fork = cpu.processes
+ if display:
+ cpu = 'Fork: %.0fHz' % ((now_fork - self.last_fork) / tdiff)
+ self.last_fork = now_fork
+ return cpu
+
+ def function_sirq(self, cpu, display, tdiff):
+ now_sirq = cpu.softirq_total
+ if display:
+ cpu = 'Sirq: %.0fHz' % ((now_sirq - self.last_sirq) / tdiff)
+ self.last_sirq = now_sirq
+ return cpu
+
+ def function_proc(self, cpu, display, tdiff):
+ if display:
+ cpu = 'Proc: %irun %iio' % (cpu.procs_running, cpu.procs_blocked)
+ return cpu
+
+ def function_load(self, cpu, display, tdiff):
+ if display:
+ load = AverageLoad()
+ cpu = 'Load: %.2f %.2f %.2f' % (load.total_avg_5_min, load.total_avg_10_min, load.total_avg_15_min)
+ return cpu
+
+ def function_task(self, cpu, display, tdiff):
+ if display:
+ load = AverageLoad()
+ cpu = 'Task: %i/%i (%.0f%%)' % (load.active_tasks, load.total_tasks, load.active_tasks * 100 / load.total_tasks)
+ return cpu
+
+ def function_pid(self, cpu, display, tdiff):
+ if display:
+ load = AverageLoad()
+ cpu = 'Last PID: %i' % load.last_pid
+ return cpu
+
+ def function_online(self, cpu, display, tdiff):
+ if display:
+ try:
+ cpu, cpuonline, on, off = '', CPUOnline(), 0, 0
+ online, offline = cpuonline.online, cpuonline.offline
+ while on < len(online) and off < len(offline):
+ if online[on] < offline[off]:
+ cpu += ' \033[32m%s\033[0m' % online[on]
+ on += 1
+ else:
+ cpu += ' \033[31m%s\033[0m' % offline[off]
+ off += 1
+ cpu += ''.join(' \033[32m%s\033[0m' % c for c in online[on:])
+ cpu += ''.join(' \033[31m%s\033[0m' % c for c in offline[off:])
+ cpu = 'Online:%s' % cpu
+ except Exception as e:
+ cpu = str(e)
+ return cpu
+
+ def function_sirq_split(self, key, cpu, display, tdiff):
+ if display:
+ label = 'Sirq(%s)' % key.lower().replace('_', ' ').replace(' rx', '↓').replace(' tx', '↑')
+ now = self.now_sirq_split[key]
+ last = self.last_sirq_split[key]
+ hz = lambda n, l : '%0.fHz' % ((n - l) / tdiff)
+ n = len(now)
+ snow, slast = sum(now), sum(last)
+ anow, alast = snow / n, slast / n
+ each = ''
+ if self.show_all:
+ each = '%s : ' % ' '.join(hz(n, l) for n, l in zip(now, last))
+ else:
+ each = ''
+ cpu = '%s: %s%s(%s)' % (label, each, hz(snow, slast), hz(anow, alast))
+ return cpu
+
+ def function(self):
+ now = time.monotonic()
+ cpu = CPU()
+ self.now_sirq_split = SoftIRQs()
+ tdiff = now - self.last_time
+ self.last_time = now
+ display = self.show_function
+ self.now_cpu_stat = cpu.cpu
+ self.now_cpu_total = sum(cpu.cpu)
+ self.now_cpus_total = [sum(c) for c in cpu.cpus]
+ self.now_cpus_stat = [[c[s] for c in cpu.cpus] for s in range(len(cpu.cpu))]
+ for i in range(len(self.functions)):
+ if i == display:
+ ret = self.functions[i](cpu, True, tdiff)
+ else:
+ self.functions[i](cpu, False, tdiff)
+ self.last_cpus_stat = self.now_cpus_stat
+ self.last_cpu_stat = self.now_cpu_stat
+ self.last_cpus_total = self.now_cpus_total
+ self.last_cpu_total = self.now_cpu_total
+ self.last_sirq_split = self.now_sirq_split
+ return ret
diff --git a/xpybar/config/myii.py b/xpybar/config/myii.py
new file mode 100644
index 0000000..77bf813
--- /dev/null
+++ b/xpybar/config/myii.py
@@ -0,0 +1,113 @@
+# -*- python -*-
+from plugins.ii import II
+
+from common import *
+
+class MyII(Entry):
+ def __init__(self, *args, channel = None, prefix = None, display = None, backlog = None, watch = None, **kwargs):
+ self.semaphore = threading.Semaphore()
+ self.log = []
+ self.text = ''
+ self.ii = II(channel, prefix = prefix)
+ self.display = display
+ self.backlog = backlog
+ self.watch = (lambda s : False) if watch is None else watch
+ self.show_line = 0
+ Entry.__init__(self, *args, **kwargs)
+
+ def start(self):
+ xasync(self.wait, name = 'ii')
+
+ def wait(self):
+ self.refresh()
+ def refresh_():
+ time.sleep(0.1)
+ self.refresh()
+ while True:
+ xasync(refresh_, name = 'ii')
+ try:
+ self.ii.wait()
+ except:
+ time.sleep(5)
+ self.refresh()
+
+ def refresh(self):
+ on_last = False
+ highlighted = 0
+ self.semaphore.acquire()
+ try:
+ new = self.ii.read()
+ if self.backlog is not None:
+ new = new[-(self.backlog):]
+ self.backlog = None
+ for i in range(len(new)):
+ if self.watch(new[i]):
+ new[i] = (new[i], True)
+ highlighted += 1
+ else:
+ new[i] = (new[i], False)
+ on_last = self.show_line >= len(self.log) - 1
+ self.log.extend(new)
+ if on_last and len(new) > 0:
+ self.show_line = len(self.log) - 1
+ text = self.log[self.show_line][0]
+ if self.log[self.show_line][1]:
+ text = '\033[33m%s\033[39m' % text
+ self.text = text
+ finally:
+ self.semaphore.release()
+ if len(new) > 0:
+ if on_last:
+ self.invalidate()
+ if highlighted > 0 and self.display is not None:
+ self.display.adjust(highlighted)
+
+ def action(self, col, button, x, y):
+ if len(self.log) == 0:
+ return
+ update = False
+ self.semaphore.acquire()
+ try:
+ if button == LEFT_BUTTON:
+ if self.log[self.show_line][1]:
+ self.log[self.show_line] = (self.log[self.show_line][0], False)
+ self.text = self.log[self.show_line][0]
+ update = True
+ if self.display is not None:
+ self.display.adjust(-1)
+ elif self.watch(self.log[self.show_line][0]):
+ self.log[self.show_line] = (self.log[self.show_line][0], True)
+ self.text = '\033[33m%s\033[39m' % self.log[self.show_line][0]
+ update = True
+ if self.display is not None:
+ self.display.adjust(-1)
+ elif button == MIDDLE_BUTTON:
+ adj = 0
+ for i in range(self.show_line):
+ if self.log[i][1]:
+ adj -= 1
+ self.log = self.log[self.show_line:]
+ self.show_line = 0
+ if adj != 0:
+ self.display.adjust(adj)
+ elif button == RIGHT_BUTTON:
+ for i, (text, marked) in enumerate(self.log):
+ if marked:
+ self.show_line = i
+ self.text = '\033[33m%s\033[39m' % text if marked else text
+ update = True
+ break
+ elif button in (SCROLL_UP, SCROLL_DOWN):
+ line = self.show_line + (-1 if button == SCROLL_UP else +1)
+ if 0 <= line < len(self.log):
+ self.show_line = line
+ (text, marked) = self.log[line]
+ self.text = '\033[33m%s\033[39m' % text if marked else text
+ update = True
+ finally:
+ self.semaphore.release()
+ if update:
+ self.invalidate()
+
+ def function(self):
+ return self.text
diff --git a/xpybar/config/myio.py b/xpybar/config/myio.py
new file mode 100644
index 0000000..2475348
--- /dev/null
+++ b/xpybar/config/myio.py
@@ -0,0 +1,351 @@
+# -*- python -*-
+from plugins.dentrystate import DentryState
+from plugins.df import Discs
+from plugins.discstats import DiscStats
+from plugins.files import Files
+from plugins.inodestate import InodeState
+from plugins.locks import Locks
+from plugins.random import Random
+
+from common import *
+
+class MyIO(Entry):
+ def __init__(self, *args, fs_name_map = None, fs_ignore = None, fs_type_ignore = None, **kwargs):
+ self.fs_name_map = [] if fs_name_map is None else fs_name_map
+ self.fs_ignore = set([] if fs_ignore is None else fs_ignore)
+ self.fs_type_ignore = set(MyIO.nodev_fs() if fs_type_ignore is None else fs_type_ignore)
+ self.show_value = 0
+ self.show_disc_value = 0
+ self.show_overall = True
+ self.show_disc = 0
+ self.disc_map = None
+ Entry.__init__(self, *args, **kwargs)
+
+ @staticmethod
+ def nodev_fs():
+ ret = []
+ with open('/proc/filesystems', 'rb') as file:
+ data = file.read()
+ data = data.decode('utf-8', 'strict')[:-1].replace('\t', ' ').split('\n')
+ for line in data:
+ cols = line.split(' ')
+ if 'nodev' in cols[:-1]:
+ ret.append(cols[-1])
+ ret += ['fuse.gvfsd-fuse']
+ return ret
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ overall = self.show_overall
+ disc_map = self.disc_map
+ if overall and disc_map is not None:
+ x = 0
+ for text, disc in disc_map:
+ if col < x:
+ break
+ w = Bar.coloured_length(text)
+ if col < x + w:
+ if disc is not None:
+ self.show_disc = disc
+ break
+ x += w + 1
+ self.show_overall = not overall
+ elif button == MIDDLE_BUTTON and not self.show_overall:
+ self.show_disc_value = max(self.show_disc_value - 1, 0)
+ elif button == RIGHT_BUTTON and not self.show_overall:
+ self.show_disc_value = min(self.show_disc_value + 1, 4)
+ elif button == SCROLL_UP:
+ if self.show_overall:
+ self.show_value = min(self.show_value + 1, 13)
+ else:
+ show_disc = self.show_disc
+ if isinstance(show_disc, str):
+ try:
+ show_disc = [d.filesystem for d in self.get_discs()].index(show_disc)
+ except:
+ show_disc = -1
+ self.show_disc = show_disc + 1
+ elif button == SCROLL_DOWN:
+ if self.show_overall:
+ self.show_value = max(self.show_value - 1, 0)
+ else:
+ show_disc = self.show_disc
+ if isinstance(show_disc, str):
+ try:
+ show_disc = [d.filesystem for d in self.get_discs()].index(show_disc)
+ except:
+ show_disc = -1
+ self.show_disc = max(show_disc - 1, 0)
+ else:
+ return
+ self.invalidate()
+
+ def colour(self, percent):
+ colour = '39'
+ if percent < 25: colour = '32'
+ if percent > 50: colour = '33'
+ if percent > 90: colour = '31'
+ if percent > 95: colour = '39;41'
+ return colour
+
+ def unit(self, value):
+ units = ['', 'K', 'M', 'G', 'T', 'P', 'E']
+ unit = 0
+ while unit + 1 < len(units) and value >= 1024:
+ unit += 1
+ value /= 1024
+ return (value, units[unit])
+
+ def si(self, value, baseunit = 0):
+ unit = baseunit + 3
+ units = ['n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E']
+ while unit + 1 < len(units) and value >= 1000:
+ unit += 1
+ value /= 1000
+ return (value, units[unit])
+
+ def get_discs(self):
+ discs = Discs()
+ fs = [discs.filesystems[f] for f in sorted(discs.filesystems.keys()) if
+ f not in self.fs_ignore and
+ discs.filesystems[f].fstype not in self.fs_type_ignore and
+ f[0] == '/']
+ return fs
+
+ def function_overall(self):
+ df = []
+ if self.show_value == 0:
+ label = 'Df'
+ self.disc_map = [(label + ':', None)]
+ used = 0
+ available = 0
+ for disc in self.get_discs():
+ percent = disc.used * 100 / (disc.used + disc.available)
+ text = '\033[%sm%.0f\033[0m%%' % (self.colour(percent), percent)
+ df.append(text)
+ self.disc_map.append((text, disc.filesystem))
+ used += disc.used
+ available += disc.available
+ df.append(':')
+ percent = used * 100 / (used + available)
+ df.append('\033[%sm%.0f\033[0m%%' % (self.colour(percent), percent))
+
+ elif self.show_value == 1:
+ label = 'Df'
+ self.disc_map = [(label + ':', None)]
+ used = 0
+ available = 0
+ for disc in self.get_discs():
+ percent = disc.used * 100 / (disc.used + disc.available)
+ text = '\033[%sm%.1f\033[0m%s' % (self.colour(percent), *self.unit(disc.available))
+ df.append(text)
+ self.disc_map.append((text, disc.filesystem))
+ used += disc.used
+ available += disc.available
+ df.append(':')
+ percent = used * 100 / (used + available)
+ df.append('\033[%sm%.1f\033[0m%s' % (self.colour(percent), *self.unit(available)))
+
+ elif self.show_value == 2:
+ label = 'Df(inodes)'
+ self.disc_map = [(label + ':', None)]
+ used = 0
+ available = 0
+ for disc in self.get_discs():
+ percent = disc.iused * 100 / (disc.iused + disc.ifree)
+ text = '\033[%sm%.0f\033[0m%%' % (self.colour(percent), percent)
+ df.append(text)
+ self.disc_map.append((text, disc.filesystem))
+ used += disc.iused
+ available += disc.ifree
+ df.append(':')
+ percent = used * 100 / (used + available)
+ df.append('\033[%sm%.0f\033[0m%%' % (self.colour(percent), percent))
+
+ elif self.show_value == 3:
+ label = 'Dent'
+ dentry = DentryState()
+ percent = (dentry.nr_dentry - dentry.nr_unused) * 100 / dentry.nr_dentry
+ df.append('\033[%sm%.0f\033[0m%%' % (self.colour(percent), percent))
+ df.append('%is' % dentry.age_limit)
+ df.append('%ipages' % dentry.want_pages)
+
+ elif self.show_value == 4:
+ label = 'Inode'
+ inode = InodeState()
+ percent = (inode.nr_inodes - inode.nr_free_inodes) * 100 / inode.nr_inodes
+ df.append('\033[%sm%.0f\033[0m%%%s' % (self.colour(percent), percent,
+ '' if inode.preshrink == 0 else ' preshrink'))
+ elif self.show_value == 5:
+ label = 'File'
+ files = Files()
+ percent = (files.nr_files - files.nr_free_files) * 100 / files.file_max
+ df.append('\033[%sm%.0f\033[0m%%' % (self.colour(percent), percent))
+ df.append('%i/%i/%i' % (files.nr_files - files.nr_free_files, files.nr_files, files.file_max))
+
+ elif self.show_value == 6:
+ label = 'Lock'
+ locks = Locks().locks
+ ar, aw, mr, mw = 0, 0, 0, 0
+ for lock in locks:
+ if lock.mandatory:
+ if lock.shared:
+ mr += 1
+ else:
+ mw += 1
+ else:
+ if lock.shared:
+ ar += 1
+ else:
+ aw += 1
+ df.append('%iar' % ar)
+ df.append('%iaw' % aw)
+ df.append('%imr' % mr)
+ df.append('%imw' % mw)
+ df.append(':')
+ df.append('%ia' % (ar + aw))
+ df.append('%im' % (mr + mw))
+ df.append('%ir' % (ar + mr))
+ df.append('%iw' % (aw + mw))
+ df.append(':')
+ df.append('%i' % (ar + aw + mr + mw))
+
+ elif self.show_value == 7:
+ label = 'Rand'
+ random = Random()
+ df.append(str(random.poolsize))
+
+ elif self.show_value in (8, 11):
+ label = 'Disc(r %s)' % ('sum' if self.show_value == 8 else 'avg')
+ stats = DiscStats().devices.values()
+ r_complete, r_merge, r_sectors, r_time = 0, 0, 0, 0
+ for stat in stats:
+ r_complete += stat.r_complete
+ r_merge += stat.r_merge
+ r_sectors += stat.r_sectors
+ r_time += stat.r_time
+ n = 1 if self.show_value == 8 else len(stats)
+ r_complete /= n
+ r_merge /= n
+ r_sectors /= n
+ r_time /= n
+ df.append('%.1f%scompleted' % self.si(r_complete))
+ df.append('%.1f%smerge' % self.si(r_merge))
+ df.append('%.1f%ssectors' % self.si(r_sectors))
+ df.append('%.1f%ss' % self.si(r_time, -1))
+
+ elif self.show_value in (9, 12):
+ label = 'Disc(w %s)' % ('sum' if self.show_value == 9 else 'avg')
+ stats = DiscStats().devices.values()
+ w_complete, w_merge, w_sectors, w_time = 0, 0, 0, 0
+ for stat in stats:
+ w_complete += stat.w_complete
+ w_merge += stat.w_merge
+ w_sectors += stat.w_sectors
+ w_time += stat.w_time
+ n = 1 if self.show_value == 9 else len(stats)
+ w_complete /= n
+ w_merge /= n
+ w_sectors /= n
+ w_time /= n
+ df.append('%.1f%scomplete' % self.si(w_complete))
+ df.append('%.1f%smerge' % self.si(w_merge))
+ df.append('%.1f%ssectors' % self.si(w_sectors))
+ df.append('%.1f%ss' % self.si(w_time, -1))
+
+ elif self.show_value in (10, 13):
+ label = 'Disc(io %s)' % ('sum' if self.show_value == 10 else 'avg')
+ stats = DiscStats().devices.values()
+ io_current, io_time, io_weighted_time = 0, 0, 0
+ for stat in stats:
+ io_current += stat.io_current
+ io_time += stat.io_time
+ io_weighted_time += stat.io_weighted_time
+ n = 1 if self.show_value == 10 else len(stats)
+ io_current /= n
+ io_time /= n
+ io_weighted_time /= n
+ df.append('%.1f%s' % self.si(io_current))
+ df.append('%.1f%ss' % self.si(io_time, -1))
+ df.append('%.1f%ss(weighted)' % self.si(io_weighted_time, -1))
+
+ return '%s: %s' % (label, ' '.join(df))
+
+ def function_disc(self):
+ discs = self.get_discs()
+ disc = self.show_disc
+ if isinstance(disc, str):
+ for i, d in enumerate(discs):
+ if d.filesystem == disc:
+ disc = i
+ break
+ if isinstance(disc, str):
+ disc = 0
+ elif disc >= len(discs):
+ disc = len(discs) - 1
+ disc = discs[disc]
+ name = disc.filesystem
+ name = self.fs_name_map[name] if name in self.fs_name_map else name.split('/')[-1]
+ df = []
+
+ if self.show_disc_value > 1:
+ try:
+ devices = DiscStats().devices
+ device = None
+ fs = os.path.realpath(disc.filesystem)
+ stat = None
+ for dev in devices.keys():
+ if '/dev/' + dev == fs:
+ stat = devices[dev]
+ break
+ if stat is None:
+ self.show_disc_value = 0
+ except:
+ self.show_disc_value = 0
+
+ if self.show_disc_value == 0:
+ percent = disc.used * 100 / (disc.used + disc.available)
+ text = '\033[%sm%.0f\033[0m%%(%.1f%sB/%.f%sB)' % (self.colour(percent), percent,
+ *self.unit(disc.available),
+ *self.unit(disc.used + disc.available))
+ df.append(text)
+ percent = disc.iused * 100 / (disc.iused + disc.ifree)
+ text = '\033[%sm%.0f\033[0m%%(%.1f%s/%.1f%s)inodes' % (self.colour(percent), percent,
+ *self.si(disc.iused), *self.si(disc.inodes))
+ df.append(text)
+
+ elif self.show_disc_value == 1:
+ df.append(disc.fstype)
+ df.append(disc.mountpoint)
+
+ elif self.show_disc_value == 2:
+ name += '(r)'
+ df.append('%.1f%scompleted' % self.si(stat.r_complete))
+ df.append('%.1f%smerge' % self.si(stat.r_merge))
+ df.append('%.1f%ssectors' % self.si(stat.r_sectors))
+ df.append('%.1f%ss' % self.si(stat.r_time, -1))
+
+ elif self.show_disc_value == 3:
+ name += '(w)'
+ df.append('%.1f%scomplete' % self.si(stat.w_complete))
+ df.append('%.1f%smerge' % self.si(stat.w_merge))
+ df.append('%.1f%ssectors' % self.si(stat.w_sectors))
+ df.append('%.1f%ss' % self.si(stat.w_time, -1))
+
+ elif self.show_disc_value == 4:
+ name += '(io)'
+ df.append('%.1f%s' % self.si(stat.io_current))
+ df.append('%.1f%ss' % self.si(stat.io_time, -1))
+ df.append('%.1f%ss(weighted)' % self.si(stat.io_weighted_time, -1))
+
+ return '%s: %s' % (name, ' '.join(df))
+
+ def function(self):
+ try:
+ self.disc_map = None
+ if self.show_overall:
+ return self.function_overall()
+ else:
+ return self.function_disc()
+ except Exception as e:
+ return str(e)
diff --git a/xpybar/config/myipaddress.py b/xpybar/config/myipaddress.py
new file mode 100644
index 0000000..424dfac
--- /dev/null
+++ b/xpybar/config/myipaddress.py
@@ -0,0 +1,89 @@
+# -*- python -*-
+from plugins.ipaddress import IPAddress
+from plugins.clock import Clock
+
+from common import *
+
+# TODO add IPv6 address
+class MyIPAddress(Entry):
+ def __init__(self, *args, ignore = None, public = True, both = False, **kwargs):
+ self.ignore = ['lo'] if ignore is None else ignore
+ self.text = None
+ self.show_both = both
+ self.show_public = public
+ self.show_nic = None
+ Entry.__init__(self, *args, **kwargs)
+ def init():
+ self.refresh()
+ xasync(lambda : Clock(sync_to = 10 * Clock.MINUTES).continuous_sync(t(self.refresh)), name = 'ipaddress')
+ xasync(init, name = 'ipaddress')
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ self.show_public = not self.show_public
+ self.invalidate()
+ elif button == MIDDLE_BUTTON:
+ self.show_both = not self.show_both
+ self.invalidate()
+ elif button == RIGHT_BUTTON:
+ xasync(self.refresh, name = 'ipaddress')
+ elif button in (SCROLL_UP, SCROLL_DOWN):
+ nic = self.show_nic
+ if nic is None:
+ return
+ nics = list(sorted(self.text[0].keys()))
+ try:
+ nic = nics.index(nic)
+ except:
+ if len(nics) == 0:
+ self.show_nic = None
+ self.invalidate()
+ else:
+ self.show_nic = nics[0]
+ self.invalidate()
+ nic += +1 if button == SCROLL_UP else -1
+ if 0 <= nic < len(nics):
+ self.show_nic = nics[nic]
+ self.invalidate()
+
+ def refresh(self):
+ text_private, text_public, text_both = {}, {}, {}
+ ipa = IPAddress(*(self.ignore))
+ for nic in ipa.nics.keys():
+ (state, a4, a6, a) = ipa.nics[nic]
+ if state == IPAddress.DOWN:
+ label = '31'
+ elif state == IPAddress.UP:
+ label = '32'
+ elif state == IPAddress.UNKNOWN:
+ label = '33'
+ else:
+ label = '39'
+ label = '\033[%sm%s\033[0m' % (label, nic)
+ prv = pub = ''
+ if a4 is not None:
+ prv += ' ' + a4
+ if a6 is not None:
+ prv += ' ' + a6
+ if a is not None:
+ pub += ' ' + a
+ finalise = lambda addrs : '%s:%s' % (label, (addrs if addrs != '' else ' no address'))
+ text_private[nic] = finalise(prv)
+ text_public[nic] = finalise(pub)
+ text_both[nic] = finalise(pub + prv)
+ self.text = (text_private, text_public, text_both)
+ self.invalidate()
+
+ def function(self):
+ text = self.text
+ if text is None:
+ return '...'
+ text = text[2 if self.show_both else (1 if self.show_public else 0)]
+ nic = self.show_nic
+ if nic is None or nic not in text:
+ if len(text.keys()) == 0:
+ self.show_nic = None
+ return 'No NIC available'
+ nic = sorted(text.keys())[0]
+ self.show_nic = nic
+ return text[nic]
diff --git a/xpybar/config/myirc.py b/xpybar/config/myirc.py
new file mode 100644
index 0000000..3fa4b71
--- /dev/null
+++ b/xpybar/config/myirc.py
@@ -0,0 +1,38 @@
+# -*- python -*-
+from common import *
+
+class MyIRC(Entry):
+ def __init__(self, *args, ii = None, **kwargs):
+ self.semaphore = threading.Semaphore()
+ self.text = 'Irc: 0'
+ self.count = 0
+ self.ii = ii
+ Entry.__init__(self, *args, **kwargs)
+
+ def colourise(self, n):
+ if n >= 1: return '32'
+ if n >= 5: return '33'
+ if n >= 10: return '31'
+ return '39'
+
+ def adjust(self, n):
+ self.semaphore.acquire()
+ try:
+ self.count += n
+ if self.count < 0:
+ self.count = 0
+ self.text = 'Irc: \033[%sm%i\033[0m' % (self.colourise(self.count), self.count)
+ finally:
+ self.semaphore.release()
+ self.invalidate()
+
+ def action(self, col, button, x, y):
+ if self.count == 0:
+ return
+ if button == LEFT_BUTTON:
+ G.groupi = 1
+ G.group = G.groups[G.groupi]
+ self.ii.action(0, RIGHT_BUTTON, 0, 0)
+
+ def function(self):
+ return self.text
diff --git a/xpybar/config/myleapsec.py b/xpybar/config/myleapsec.py
new file mode 100644
index 0000000..e45c985
--- /dev/null
+++ b/xpybar/config/myleapsec.py
@@ -0,0 +1,69 @@
+# -*- python -*-
+from plugins.leapsec import LeapSeconds
+
+from common import *
+
+class MyLeapsec(Entry):
+ def __init__(self, *args, **kwargs):
+ self.announcement = None
+ Entry.__init__(self, *args, **kwargs)
+ def init():
+ self.refresh()
+ xasync(lambda : Clock(sync_to = Clock.MINUTES).continuous_sync(Sometimes(t(self.refresh), 12 * 60)), name = 'leapsec')
+ xasync(init, name = 'leapsec')
+
+ def action(self, col, button, x, y):
+ if button == RIGHT_BUTTON:
+ self.refresh()
+
+ def refresh(self):
+ leapanon = LeapSeconds()
+ found = -1
+ now = time.time()
+ for index in range(len(leapanon)):
+ if leapanon[index][3] >= now:
+ found = index
+ break
+ announcement = leapanon[found]
+ text = ('+%i' if announcement[4] > 0 else '%i') % announcement[4]
+ text += 's ' + ('\033[%sm%s\033[00m ago' if found < 0 else 'in \033[%sm%s\033[00m')
+ text += ' end of UTC %i-%02i-%0i' % announcement[:3]
+ if announcement[5] == LeapSeconds.SECONDARY:
+ text += ' (secondary)'
+ elif announcement[5] == LeapSeconds.OUT_OF_BAND:
+ text += ' (out of band)'
+ self.announcement = (text, announcement[3])
+ self.invalidate()
+
+ def dur(self, t):
+ s, t = t % 60, t // 60
+ m, t = t % 60, t // 60
+ h, d = t % 24, t // 24
+ if d > 0:
+ return '%id%ih%i\'%02i"' % (d, h, m, s)
+ elif h > 0:
+ return '%ih%i\'%02i"' % (h, m, s)
+ elif m > 0:
+ return '%i\'%02i"' % (m, s)
+ else:
+ return '%02is' % s
+
+ def colour(self, time_until):
+ c = '00'
+ if time_until >= 0:
+ if time_until < 10:
+ c = '41'
+ elif time_until < 60:
+ c = '31'
+ elif time_until < 60 * 60:
+ c = '33'
+ elif time_until < 24 * 60 * 60:
+ c = '32'
+ return c
+
+ def function(self):
+ if self.announcement is None:
+ return '...'
+ (text, when) = self.announcement
+ time_until = when - int(time.time())
+ return text % (self.colour(time_until), self.dur(abs(time_until)))
diff --git a/xpybar/config/mylid.py b/xpybar/config/mylid.py
new file mode 100644
index 0000000..268a860
--- /dev/null
+++ b/xpybar/config/mylid.py
@@ -0,0 +1,32 @@
+# -*- python -*-
+from plugins.lid import Lid
+
+from common import *
+
+class MyLid(Entry):
+ COMPACT = 0
+ LONG = 1
+ TITLED = 2
+ FULL = 3
+
+ def __init__(self, *args, details = 2, **kwargs):
+ self.details = details
+ self.map = [{True : 'o',
+ False : 'c'},
+ {True : 'open',
+ False : 'closed'}]
+ Entry.__init__(self, *args, **kwargs)
+
+ def action(self, col, button, x, y):
+ if button == MIDDLE_BUTTON:
+ self.details ^= MyLid.TITLED
+ self.invalidate()
+ elif button == RIGHT_BUTTON:
+ self.details ^= MyLid.LONG
+ self.invalidate()
+
+ def function(self):
+ prefix = '' if (self.details & MyLid.TITLED) == 0 else 'Lid: '
+ smap = self.map[self.details & MyLid.LONG]
+ state = Lid.is_open()
+ return prefix + ' '.join(smap[state[lid]] for lid in state)
diff --git a/xpybar/config/mymemory.py b/xpybar/config/mymemory.py
new file mode 100644
index 0000000..e0eb76b
--- /dev/null
+++ b/xpybar/config/mymemory.py
@@ -0,0 +1,117 @@
+# -*- python -*-
+from plugins.mem import Memory
+from plugins.swaps import Swaps
+
+from common import *
+
+class MyMemory(Entry):
+ def __init__(self, *args, **kwargs):
+ self.coloured = tuple(self.colourise(i) for i in range(101))
+ self.show_labels = True
+ self.show_value = -1
+ self.show_default = False
+ self.show_swaps = False
+ self.show_swap = 0
+ self.labels = list(sorted(Memory().keys))
+ swaps = Swaps()
+ self.prio = swaps.header_map['Priority']
+ self.used = swaps.header_map['Used']
+ self.size = swaps.header_map['Size']
+ self.swap = swaps.header_map['Filename']
+ Entry.__init__(self, *args, **kwargs)
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ if self.show_value >= 0:
+ self.show_default = not self.show_default
+ self.invalidate()
+ elif button == MIDDLE_BUTTON:
+ self.show_labels = not self.show_labels
+ self.invalidate()
+ elif button == RIGHT_BUTTON:
+ self.show_swaps = not self.show_swaps
+ self.invalidate()
+ elif button == SCROLL_UP:
+ if self.show_swaps:
+ self.show_swap += 1
+ self.invalidate()
+ else:
+ n = self.show_value + 1
+ if n < len(self.labels):
+ self.show_value = n
+ self.invalidate()
+ elif button == SCROLL_DOWN:
+ if self.show_swaps:
+ n = self.show_swap
+ if n > 0:
+ self.show_swap = n - 1
+ self.invalidate()
+ else:
+ self.show_value -= 1
+ if self.show_value < -1:
+ self.show_value = -1
+ self.invalidate()
+
+ def u(self, value):
+ units = 'KMGTPE'
+ unit = 0
+ while unit + 1 < len(units) and value >= 1024:
+ unit += 1
+ value /= 1024
+ return '%.1f%s' % (value, units[unit])
+
+ def colourise(self, value):
+ if value >= 100:
+ return '\033[31m100\033[0m'
+ colour = '39'
+ if value > 30: colour = '32'
+ if value > 50: colour = '33'
+ if value > 80: colour = '31'
+ return '\033[%sm%i\033[0m%%' % (colour, value)
+
+ def function(self):
+ if self.show_swaps:
+ swaps = Swaps()
+ while True:
+ if len(swaps.swaps) == 0:
+ return 'no swaps available'
+ try:
+ swap = swaps.swaps[self.show_swap]
+ used, size = int(swap[self.used]), int(swap[self.size])
+ swap, prio = swap[self.swap], int(swap[self.prio])
+ return '%s: %s (%sB/%sB, prio %i)' % (swap, self.colourise(used * 100 / size),
+ self.u(used), self.u(size), prio)
+ except:
+ self.show_swap -= 1
+ if self.show_swap < 0:
+ self.show_swaps = False
+ return self.function()
+ else:
+ memory = Memory()
+ if not self.show_default and self.show_value > 0:
+ label = self.labels[self.show_value]
+ try:
+ value = memory[label]
+ unit = 0
+ units = ['K', 'M', 'G', 'T', 'P', 'E']
+ while (unit + 1 < len(units)) and (value >= 1024):
+ value /= 1024
+ unit += 1
+ return '%s: %.1f%sB' % (label, value, units[unit])
+ except:
+ self.show_value = -1
+ if memory.mem_total == 0:
+ mem = '---'
+ shm = '---'
+ else:
+ mem = self.coloured[limited(memory.mem_used * 100 / memory.mem_total)]
+ shm = self.coloured[limited(memory.shmem * 100 / memory.mem_total)]
+ if memory.swap_total == 0:
+ swp = 'n/a'
+ else:
+ swp = self.coloured[limited(memory.swap_used * 100 / memory.swap_total)]
+ if self.show_labels:
+ mem = 'Mem: %s%sSwp: %s%sShm: %s' % (mem, SEPARATOR, swp, SEPARATOR, shm)
+ else:
+ mem = 'Mem: %s %s %s' % (mem, swp, shm)
+ return mem
diff --git a/xpybar/config/mymoc.py b/xpybar/config/mymoc.py
new file mode 100644
index 0000000..da16ece
--- /dev/null
+++ b/xpybar/config/mymoc.py
@@ -0,0 +1,70 @@
+# -*- python -*-
+from plugins.moc import MOC
+
+from common import *
+
+class MyMOC(Entry):
+ def __init__(self, *args, ignore = None, **kwargs):
+ self.show_state = False
+ self.display = 0
+ self.function_display = Sometimes(self.function_display_, 4)
+ Entry.__init__(self, *args, **kwargs)
+
+ def invalidate(self):
+ self.function_display.counter = 0
+ Entry.invalidate(self)
+
+ def action(self, col, button, x, y):
+ if button == RIGHT_BUTTON:
+ self.show_state = not self.show_state
+ self.invalidate()
+ elif self.show_state:
+ if button == LEFT_BUTTON:
+ self.invalidate()
+ elif button == SCROLL_UP:
+ n = self.display + 1
+ if n >= 6:
+ return
+ self.display = n
+ self.invalidate()
+ elif button == SCROLL_DOWN:
+ n = self.display - 1
+ if n < 0:
+ return
+ self.display = n
+ self.invalidate()
+ else:
+ moc = MOC()
+ if moc.state == MOC.NOT_RUNNING:
+ return
+ elif button == LEFT_BUTTON:
+ if col < 5:
+ return
+ col -= 5
+ if col % 3 == 2:
+ return
+ col //= 3
+ if col == 0: xasync(lambda : moc.play().wait())
+ elif col == 1: xasync(lambda : moc.toggle_pause().wait())
+ elif col == 2: xasync(lambda : moc.stop().wait())
+ elif col == 3: xasync(lambda : moc.previous().wait())
+ elif col == 4: xasync(lambda : moc.next().wait())
+ elif button == SCROLL_UP: xasync(lambda : moc.seek(+5).wait())
+ elif button == SCROLL_DOWN: xasync(lambda : moc.seek(-5).wait())
+
+ def function_display_(self):
+ moc = MOC()
+ display = self.display
+ if display == 0 or moc.state in (MOC.NOT_RUNNING, MOC.STOPPED):
+ return 'Moc: %s' % { MOC.NOT_RUNNING : 'not running'
+ , MOC.STOPPED : 'stopped'
+ , MOC.PAUSED : 'paused'
+ , MOC.PLAYING : 'playing'}[moc.state]
+ key = ['SongTitle', 'Album', 'Artist', 'Title', 'File'][display - 1]
+ label = key.lower() if display != 1 else 'song'
+ return 'Moc(%s): %s' % (label, moc[key])
+
+ def function(self):
+ if not self.show_state:
+ return 'Moc: |> || [] |< >|'
+ return self.function_display()
diff --git a/xpybar/config/mynetwork.py b/xpybar/config/mynetwork.py
new file mode 100644
index 0000000..79cd271
--- /dev/null
+++ b/xpybar/config/mynetwork.py
@@ -0,0 +1,205 @@
+# -*- python -*-
+from plugins.network import Network
+from plugins.ping import Ping
+
+from common import *
+
+class MyNetwork(Entry):
+ def __init__(self, *args, limits = None, ignore = None, pings = None, **kwargs):
+ self.limits = { 'rx_bytes' : None # Download speed in bytes (not bits)
+ , 'tx_bytes' : None # Upload speed in bytes (not bits)
+ , 'rx_total' : None # Download cap in bytes
+ , 'tx_total' : None # Upload cap in bytes
+ }
+ if limits is not None:
+ self.limits = limits
+ if pings is None:
+ pings = ['gateway']
+ elif isinstance(pings, str) or isinstance(pings, Ping):
+ pings = [pings]
+ pings = [(Ping(targets = Ping.get_nics(p)) if isinstance(p, str) else p).monitors for p in pings]
+ self.pings = {}
+ for ping in pings:
+ for nic in ping.keys():
+ if nic not in self.pings:
+ self.pings[nic] = ping[nic]
+ else:
+ self.pings[nic] += ping[nic]
+ self.ignore = ['lo'] if ignore is None else ignore
+ self.net_time = time.monotonic()
+ self.net_last = {}
+ self.show_all = True
+ self.show_name = True
+ self.show_value = 0
+ self.labels = ['bytes', 'total', 'packets', 'errs',
+ 'drop', 'fifo', 'frame', 'colls',
+ 'carrier', 'compressed', 'multicast']
+ self.in_bytes = False # in bits if showing total
+ Entry.__init__(self, *args, **kwargs)
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ self.show_all = not self.show_all
+ elif button == MIDDLE_BUTTON:
+ self.show_name = not self.show_name
+ elif button == RIGHT_BUTTON:
+ self.in_bytes = not self.in_bytes
+ elif button == SCROLL_UP:
+ n = self.show_value + 1
+ if n >= len(self.labels):
+ return
+ self.show_value = n
+ elif button == SCROLL_DOWN:
+ n = self.show_value - 1
+ if self.show_value < 0:
+ return
+ self.show_value = n
+ else:
+ return
+ self.invalidate()
+
+ def colourise(self, value, percent):
+ colour = '39'
+ if percent > 25: colour = '32'
+ if percent > 50: colour = '33'
+ if percent > 90: colour = '31'
+ return '\033[%sm%3.0f\033[0m' % (colour, value)
+
+ def KBps(self, network, device, direction, net_tdiff):
+ direction += '_bytes'
+ value = network[device][direction]
+ limit = None
+ if direction in self.limits:
+ limit = self.limits[direction]
+ if limit is None:
+ limit = 12500000 # 100 mbps
+ if device in self.net_last:
+ value -= self.net_last[device][direction]
+ else:
+ value = 0
+ percent = value * 100 / (limit * net_tdiff)
+ value /= 1024 * net_tdiff
+ return self.colourise(value, percent)
+
+ def kbps(self, network, device, direction, net_tdiff):
+ direction += '_bytes'
+ value = network[device][direction]
+ limit = None
+ if direction in self.limits:
+ limit = self.limits[direction]
+ if limit is None:
+ limit = 12500000 # 100 mbps
+ if device in self.net_last:
+ value -= self.net_last[device][direction]
+ else:
+ value = 0
+ percent = value * 100 / (limit * net_tdiff)
+ value /= 128 * net_tdiff
+ return self.colourise(value, percent)
+
+ def total(self, network, device, direction, bits):
+ direction += '_bytes'
+ value = network[device][direction]
+ limit = self.limits[direction] if direction in self.limits else None
+ if limit is not None:
+ percent = value * 100 / limit
+ colour = '32'
+ if percent >= 25: colour = '39'
+ if percent >= 50: colour = '33'
+ if percent >= 75: colour = '31'
+ if percent >= 95: colour = '7;33'
+ if percent >= 100: colour = '7;31'
+ else:
+ colour = '39'
+ if bits:
+ value *= 8
+ unit = 0
+ if bits:
+ units = ['', 'k', 'm', 'g', 't', 'p', 'e']
+ else:
+ units = ['', 'K', 'M', 'G', 'T', 'P', 'E']
+ while (unit + 1 < len(units)) and (value >= 1024):
+ value /= 1024
+ unit += 1
+ return '\033[%sm%.1f\033[0m%s' % (colour, value, units[unit])
+
+ def ping(self, monitor):
+ monitor.semaphore.acquire()
+ try:
+ latency = monitor.get_latency(True)[1]
+ droptime = monitor.dropped_time(True)
+ if droptime:
+ return ' \033[31m%4is\033[00m' % droptime
+ elif latency is None:
+ return ' \033[31mdrop?\033[00m'
+ colour = '31'
+ if latency < 5: colour = '32'
+ elif latency < 10: colour = '39'
+ elif latency < 20: colour = '33'
+ return ' \033[%sm%5.2f\033[00m' % (colour, latency)
+ finally:
+ monitor.semaphore.release()
+
+ def function(self):
+ net_now = time.monotonic()
+ net_tdiff, self.net_time = net_now - self.net_time, net_now
+ network = Network(*self.ignore).devices
+ label = self.labels[self.show_value]
+ show_total = label == 'total'
+ show_bytes = label == 'bytes'
+ xlabel = 'bytes' if show_total else label
+ suffix = ''
+ if show_total and self.in_bytes:
+ f = lambda dev, dir : self.total(network, dev, dir, True) + 'b'
+ elif show_total:
+ f = lambda dev, dir : self.total(network, dev, dir, False) + 'B'
+ elif show_bytes and self.in_bytes:
+ f = lambda dev, dir : self.KBps(network, dev, dir, net_tdiff) + 'KB/s'
+ elif show_bytes:
+ f = lambda dev, dir : self.kbps(network, dev, dir, net_tdiff) + 'kbps'
+ else:
+ f = lambda dev, dir : '%i' % network[dev][dir + '_' + label]
+ suffix = ' ' + label
+ def create(lbl, dev):
+ ret = ''
+ if lbl is not None:
+ ret += lbl + ': '
+ have_down = ('rx_' + xlabel) in network[dev]
+ have_up = ('tx_' + xlabel) in network[dev]
+ if have_down:
+ ret += f(dev, 'rx') + '↓'
+ if have_up:
+ ret += ' '
+ if have_up:
+ ret += f(dev, 'tx') + '↑'
+ ret += suffix
+ if show_bytes or show_total:
+ if dev == '*':
+ for dev in self.pings.keys():
+ for ping in self.pings[dev]:
+ ret += self.ping(ping)
+ elif dev in self.pings:
+ for ping in self.pings[dev]:
+ ret += self.ping(ping)
+ return ret
+ if self.show_all:
+ if self.show_name:
+ net = [create(dev, dev) for dev in network]
+ else:
+ net = [create(None, dev) for dev in network]
+ net = (SEPARATOR if self.show_name else ' ').join(net)
+ else:
+ devsum = {}
+ for dev in network.keys():
+ table = network[dev]
+ for key in table.keys():
+ if key not in devsum:
+ devsum[key] = 0
+ devsum[key] += table[key]
+ network['*'] = devsum
+ if self.show_name:
+ net = create('Net', '*')
+ else:
+ net = create(None, '*')
+ self.net_last = network
+ return net
diff --git a/xpybar/config/mynews.py b/xpybar/config/mynews.py
new file mode 100644
index 0000000..a65396e
--- /dev/null
+++ b/xpybar/config/mynews.py
@@ -0,0 +1,45 @@
+# -*- python -*-
+import os
+from common import *
+
+class MyNews(Entry):
+ def __init__(self, *args, status_path = None, **kwargs):
+ if status_path is not None:
+ self.status_path = status_path
+ else:
+ self.status_path = HOME + '/.var/lib/featherweight/status'
+ self.get_news()
+ Entry.__init__(self, *args, **kwargs)
+ xasync(self.refresh, name = 'news')
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ xasync(lambda : subprocess.Popen([G.TERMINAL, '-e', 'featherweight']).wait())
+ elif button == RIGHT_BUTTON:
+ xasync(lambda : subprocess.Popen(['featherweight', '--update', '--system']).wait())
+
+ def get_news(self):
+ try:
+ with open(self.status_path, 'rb') as file:
+ status = int(file.read().decode('utf-8', 'replace').replace('\n', ''))
+ except:
+ status = 0
+ colour = '31'
+ if status <= 0: colour = '39'
+ elif status <= 5: colour = '32'
+ elif status <= 10: colour = '33'
+ self.text = 'News: \033[%sm%i\033[0m' % (colour, status)
+
+ def refresh(self):
+ while True:
+ if os.path.exists(self.status_path):
+ try:
+ spawn_read(*pdeath('HUP', 'inotifywait', self.status_path, '-e', 'close_write'))
+ self.get_news()
+ except:
+ time.sleep(1)
+ else:
+ time.sleep(1)
+
+ def function(self):
+ return self.text
diff --git a/xpybar/config/myscroll.py b/xpybar/config/myscroll.py
new file mode 100644
index 0000000..c5ef807
--- /dev/null
+++ b/xpybar/config/myscroll.py
@@ -0,0 +1,12 @@
+# -*- python -*-
+from common import *
+
+class MyScroll(Entry):
+ def function(self):
+ return '↓↑'
+
+ def action(self, col, button, x, y):
+ if button in (LEFT_BUTTON, RIGHT_BUTTON, SCROLL_UP, SCROLL_DOWN):
+ G.groupi = (G.groupi + (+1 if button in (LEFT_BUTTON, SCROLL_DOWN) else -1)) % len(G.groups)
+ G.group = G.groups[G.groupi]
+ self.invalidate()
diff --git a/xpybar/config/mystat.py b/xpybar/config/mystat.py
new file mode 100644
index 0000000..d358d15
--- /dev/null
+++ b/xpybar/config/mystat.py
@@ -0,0 +1,91 @@
+# -*- python -*-
+from plugins.snmp import SNMP
+from plugins.snmp6 import SNMP6
+from plugins.uptime import Uptime
+from plugins.vmstat import VMStat
+
+from common import *
+
+class MyStat(Entry):
+ def __init__(self, *args, **kwargs):
+ self.keys_4 = SNMP().keys
+ self.keys_6 = SNMP6().keys
+ self.keys_vm = VMStat().keys
+ self.show_group = 0
+ self.show_value_4 = 0
+ self.show_value_6 = 0
+ self.show_value_vm = 0
+ Entry.__init__(self, *args, **kwargs)
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ n = self.show_group + 1
+ if n < 4:
+ self.show_group = n
+ self.invalidate()
+ elif button == MIDDLE_BUTTON:
+ self.invalidate()
+ elif button == RIGHT_BUTTON:
+ n = self.show_group
+ if n > 0:
+ self.show_group = n - 1
+ self.invalidate()
+ elif button == SCROLL_UP:
+ if self.show_group == 0:
+ n = self.show_value_4
+ if n + 1 < len(self.keys_4):
+ self.show_value_4 = n + 1
+ self.invalidate()
+ elif self.show_group == 1:
+ n = self.show_value_6
+ if n + 1 < len(self.keys_6):
+ self.show_value_6 = n + 1
+ self.invalidate()
+ elif self.show_group == 2:
+ n = self.show_value_vm
+ if n + 1 < len(self.keys_vm):
+ self.show_value_vm = n + 1
+ self.invalidate()
+ elif self.show_group == 3:
+ pass
+ elif button == SCROLL_DOWN:
+ if self.show_group == 0:
+ n = self.show_value_4
+ if n > 0:
+ self.show_value_4 = n - 1
+ self.invalidate()
+ elif self.show_group == 1:
+ n = self.show_value_6
+ if n > 0:
+ self.show_value_6 = n - 1
+ self.invalidate()
+ elif self.show_group == 2:
+ n = self.show_value_vm
+ if n > 0:
+ self.show_value_vm = n - 1
+ self.invalidate()
+ elif self.show_group == 3:
+ pass
+
+ def function(self):
+ try:
+ if self.show_group == 0:
+ label = self.keys_4[self.show_value_4]
+ value = SNMP()[label]
+ elif self.show_group == 1:
+ label = self.keys_6[self.show_value_6]
+ value = SNMP6()[label]
+ elif self.show_group == 2:
+ label = self.keys_vm[self.show_value_vm]
+ value = VMStat()[label]
+ label = ''.join(w[:1].upper() + w[1:] for w in label.split('_'))
+ elif self.show_group == 3:
+ uptime = Uptime()
+ avg = uptime.uptime_seconds - uptime.average_idle_seconds
+ wrk = (avg) * 100 / uptime.uptime_seconds
+ up = '%id%i:%02i' % uptime.uptime[:3]
+ avg = '%id%i:%02i' % Uptime.split_time(avg)[:3]
+ return 'Uptime: %.0f%% %s / %s' % (wrk, avg, up)
+ return '%s: %i' % (label, value)
+ except Exception as e:
+ return str(e)
diff --git a/xpybar/config/mysun.py b/xpybar/config/mysun.py
new file mode 100644
index 0000000..cfd1d5c
--- /dev/null
+++ b/xpybar/config/mysun.py
@@ -0,0 +1,43 @@
+# -*- python -*-
+from common import *
+
+class MySun(Entry):
+ def __init__(self, *args, clock = None, **kwargs):
+ self.text = '...'
+ self.clock = clock
+ Entry.__init__(self, *args, **kwargs)
+ xasync(self.wait, name = 'solar')
+
+ def wait(self):
+ palette = None
+ while palette is None:
+ try:
+ palette = G.bar.palette
+ except:
+ time.sleep(0.1)
+ twilight_12 = colour_interpol(palette[1], palette[3], 1 / 3)
+ twilight_6 = colour_interpol(palette[1], palette[3], 2 / 3)
+ command = pdeath('HUP', 'blueshift', '-c', HOME + '/.config/xpybar/bin/sun')
+ while True:
+ try:
+ text = 'Sun: %s' % spawn_read(*command)
+ if self.clock is not None:
+ elevation = float(text.split(' ')[3][:-1])
+ if elevation <= -18: self.clock.twilight = '31'
+ elif elevation <= -12: self.clock.twilight = twilight_12
+ elif elevation <= -6: self.clock.twilight = twilight_6
+ elif elevation <= 0: self.clock.twilight = '33'
+ elif elevation <= 6: self.clock.twilight = '39'
+ else: self.clock.twilight = '34'
+ self.clock.invalidate()
+ except:
+ text = '¿Cannot get solar information?'
+ if self.clock is not None:
+ self.clock.twilight = '39'
+ self.clock.invalidate()
+ self.text = text
+ self.invalidate()
+ time.sleep(20)
+
+ def function(self):
+ return self.text
diff --git a/xpybar/config/mytimer.py b/xpybar/config/mytimer.py
new file mode 100644
index 0000000..e886ed3
--- /dev/null
+++ b/xpybar/config/mytimer.py
@@ -0,0 +1,142 @@
+# -*- python -*-
+from common import *
+
+from datetime import datetime
+
+
+class MyTimer(Entry):
+ def __init__(self, *args, alarms = [], **kwargs):
+ self.start = None
+ self.length = None
+ self.notified = False
+ self.pause_text = None
+ self.triggered_alarm = None
+ self.alarms = []
+ for alarm in alarms:
+ alarm = alarm.split(': ')
+ text = ': '.join(alarm[1:])
+ if not text:
+ text = alarm
+ weekday = None
+ alarm = alarm[0].split(':')
+ if not alarm[0].isdigit():
+ weekday, alarm[0] = alarm[0].split(' ')
+ weekday = {'mon' : 0,
+ 'tue' : 1,
+ 'wed' : 2,
+ 'thu' : 3,
+ 'fri' : 4,
+ 'sat' : 5,
+ 'sun' : 6}[weekday.lower()[:3]]
+ time = (int(alarm[0]) * 60 + int(alarm[1])) * 60
+ if len(alarm) > 2:
+ time += int(alarm[2])
+ self.alarms.append([False, time, weekday, text])
+ mqueue_map['timer'] = self.mqueue
+ Entry.__init__(self, *args, **kwargs)
+
+ def mqueue(self, args):
+ if args[1] == 'stop':
+ self.stop()
+ elif args[1] in ('pause', 'resume'):
+ self.pause()
+ else:
+ duration = args[1].split(':')
+ duration.reverse()
+ seconds = 0
+ if len(duration) > 0: seconds += int(duration[0])
+ if len(duration) > 1: seconds += int(duration[1]) * 60
+ if len(duration) > 2: seconds += int(duration[2]) * 60 * 60
+ if len(duration) > 3: seconds += int(duration[3]) * 60 * 60 * 24
+ self.start = time.time()
+ self.length = seconds
+ self.notified = False
+ self.pause_text = None
+ self.invalidate()
+
+ def action(self, col, button, x, y):
+ if self.triggered_alarm is not None:
+ self.triggered_alarm = None
+ self.invalidate()
+ elif button == LEFT_BUTTON:
+ self.pause()
+ elif button == RIGHT_BUTTON:
+ self.stop()
+
+ def pause(self):
+ if self.pause_text is not None:
+ self.start = time.time()
+ self.pause_text = None
+ elif self.length is None:
+ pass
+ elif self.length - int(time.time() - self.start) < 0:
+ self.start, self.length, self.notified, self.pause_text = None, None, False, None
+ else:
+ self.pause_text = ''
+ self.length -= int(time.time() - self.start)
+ self.pause_text = self.dur(self.length)
+ self.invalidate()
+
+ def stop(self):
+ self.start = None
+ self.length = None
+ self.notified = False
+ self.pause_text = None
+ self.invalidate()
+
+ def dur(self, t):
+ s, t = t % 60, t // 60
+ m, t = t % 60, t // 60
+ h, d = t % 24, t // 24
+ if d > 0:
+ return '%id %i:%02i:%02i' % (d, h, m, s)
+ elif h > 0:
+ return '%i:%02i:%02i' % (h, m, s)
+ else:
+ return '%i:%02i' % (m, s)
+
+ def notify(self, text = 'You are done with your task'):
+ subprocess.Popen(['notify-send', '-u', 'critical', text]).wait()
+
+ def wall(self, text = 'You are done with your task'):
+ subprocess.Popen(['wall', text]).wait()
+
+ def function(self):
+ now = datetime.now()
+ weekday = now.weekday()
+ now = now.hour * 60 * 60 + now.minute * 60 + now.second
+
+ if self.triggered_alarm is not None:
+ countdown = int(self.alarms[self.triggered_alarm][1] - now) % 2
+ if countdown == 0:
+ return '\033[37;41m%s\033[00m' % self.alarms[self.triggered_alarm][3]
+ else:
+ return '\033[31m%s\033[00m' % self.alarms[self.triggered_alarm][3]
+
+ for i, alarm in enumerate(self.alarms):
+ if (alarm[2] is None or weekday == alarm[2]) and now >= alarm[1] and now < alarm[1] + 30 * 60:
+ if not alarm[0]:
+ alarm[0] = True
+ xasync(lambda : self.notify(alarm[3]), name = 'alarm notify')
+ xasync(lambda : self.wall(alarm[3]), name = 'alarm wall')
+ self.triggered_alarm = i
+ return self.function()
+ else:
+ alarm[0] = False
+
+ if self.pause_text is not None:
+ return '%s (paused)' % self.pause_text
+ if self.start is None:
+ return 'Timer inactive'
+ countdown = self.length - int(time.time() - self.start)
+ if countdown > 0:
+ return self.dur(countdown)
+ if not self.notified:
+ self.notified = True
+ xasync(self.notify, name = 'timer notify')
+ xasync(self.wall, name = 'timer wall')
+ countdown %= 2
+ if countdown == 0:
+ return '\033[37;41mYou are done\033[00m'
+ else:
+ return '\033[31mYou are done\033[00m'
diff --git a/xpybar/config/mytop.py b/xpybar/config/mytop.py
new file mode 100644
index 0000000..6dc1415
--- /dev/null
+++ b/xpybar/config/mytop.py
@@ -0,0 +1,40 @@
+# -*- python -*-
+from common import *
+
+class MyTop(Entry):
+ def __init__(self, *args, **kwargs):
+ self.show_pid = True
+ self.show_cpu = True
+ self.show_label = True
+ self.show_nic = None
+ self.top_cmd = pdeath('HUP', 'top', '-b', '-n', '1', '-o', '%CPU', '-w', '10000')
+ self.refresh()
+ Entry.__init__(self, *args, **kwargs)
+ xasync(lambda : watch(5, t(self.refresh)), name = 'top')
+
+ def action(self, col, button, x, y):
+ if button == LEFT_BUTTON:
+ self.show_pid = not self.show_pid
+ elif button == MIDDLE_BUTTON:
+ self.show_label = not self.show_label
+ elif button == RIGHT_BUTTON:
+ self.show_cpu = not self.show_cpu
+ else:
+ return
+ self.refresh()
+ self.invalidate()
+
+ def refresh(self):
+ top = spawn_read(*self.top_cmd).split('\n')
+ top = [line for line in top if not line.startswith('%')][6]
+ top = [col for col in top.replace('\t', ' ').split(' ') if not col == '']
+ text = 'Top: ' if self.show_label else ''
+ if self.show_pid:
+ text += top[0] + ' '
+ if self.show_cpu:
+ text += top[6] + ' '
+ text += ' '.join(top[10:])
+ self.text = text
+
+ def function(self):
+ return self.text
diff --git a/xpybar/config/myweather.py b/xpybar/config/myweather.py
new file mode 100644
index 0000000..bb90c80
--- /dev/null
+++ b/xpybar/config/myweather.py
@@ -0,0 +1,420 @@
+# -*- python -*-
+from plugins.weather import Weather
+
+from common import *
+
+class MyWeather(Entry):
+ def __init__(self, *args, stations = None, fahrenheit = False, wind = 'm/s', from_wind = False, visibility = 'km', **kwargs):
+ self.semaphore = threading.Semaphore()
+ self.stations = [MyWeather.get_metar_station()] if stations is None else [s.upper() for s in stations]
+ self.reports = [None] * len(self.stations)
+ self.up_to_date = [False] * len(self.stations)
+ self.in_deg_f = fahrenheit
+ self.wind_unit = wind
+ self.from_wind = from_wind
+ self.visibility_unit = visibility
+ self.show_temp = True
+ self.show_wind_speed = False
+ self.show_wind_gusts = False
+ self.show_wind_dir = False
+ self.show_wind_chill = False
+ self.show_humidity = False
+ self.show_dew = False
+ self.show_pressure = False
+ self.show_visibility = False
+ self.show_sky = False
+ self.show_station = False
+ self.station = 0
+ self.updated = False
+ self.segments = []
+ self.refresh = Sometimes(lambda : xasync(self.refresh_, name = 'weather'), 20 * 60 * 4)
+ self.refresh()
+ Entry.__init__(self, *args, **kwargs)
+
+ def action(self, col, button, x, y):
+ if button == MIDDLE_BUTTON:
+ self.show_temp = True
+ self.show_wind_speed = True
+ self.show_wind_gusts = True
+ self.show_wind_dir = True
+ self.show_wind_chill = True
+ self.show_humidity = True
+ self.show_dew = True
+ self.show_pressure = True
+ self.show_visibility = True
+ self.show_sky = True
+ self.show_station = True
+ self.invalidate()
+ elif button == RIGHT_BUTTON:
+ self.refresh.counter = 0
+ self.refresh()
+ elif button in (LEFT_BUTTON, SCROLL_UP, SCROLL_DOWN):
+ index = None
+ for i, seg in enumerate(self.segments):
+ if seg is None:
+ continue
+ (x, width) = seg
+ if 0 <= col - x < width:
+ index = i
+ break
+ if index is None:
+ return
+ if index == 0: # station
+ if button == LEFT_BUTTON:
+ self.show_station = False
+ elif button == SCROLL_UP:
+ station = self.station
+ if station + 1 < len(self.stations):
+ self.station = station + 1
+ else:
+ station = self.station
+ if station > 0:
+ self.station = station - 1
+ elif index == 1: # temperature
+ if button == LEFT_BUTTON:
+ self.show_temp = False
+ else:
+ self.in_deg_f = button == SCROLL_UP
+ elif index == 2: # wind speed/gusts
+ if button == LEFT_BUTTON:
+ if self.towards is None or col < self.towards:
+ self.show_wind_speed = False
+ else:
+ self.show_wind_gusts = False
+ else:
+ if self.wind_unit == 'm/s':
+ self.wind_unit = 'mph' if button == SCROLL_UP else 'm/s'
+ elif self.wind_unit == 'mph':
+ self.wind_unit = 'kn' if button == SCROLL_UP else 'm/s'
+ elif self.wind_unit in ('kn', 'kt', 'knot', 'knots'):
+ self.wind_unit = 'km/h' if button == SCROLL_UP else 'mph'
+ elif self.wind_unit in ('km/h', 'kmph'):
+ self.wind_unit = 'ft/s' if button == SCROLL_UP else 'kn'
+ elif self.wind_unit in ('ft/s', 'ftps'):
+ self.wind_unit = 'b' if button == SCROLL_UP else 'km/h'
+ else:
+ self.wind_unit = 'b' if button == SCROLL_UP else 'ft/s'
+ elif index == 3: # wind direction
+ if button == LEFT_BUTTON:
+ self.show_wind_dir = False
+ else:
+ self.from_wind = button == SCROLL_UP
+ elif index == 4: # wind chill
+ if button == LEFT_BUTTON:
+ self.show_wind_chill = False
+ else:
+ self.in_deg_f = button == SCROLL_UP
+ elif index == 5: # relative humidity
+ if button == LEFT_BUTTON:
+ self.show_humidity = False
+ else:
+ return
+ elif index == 6: # dew point
+ if button == LEFT_BUTTON:
+ self.show_dew = False
+ else:
+ self.in_deg_f = button == SCROLL_UP
+ elif index == 7: # pressure
+ if button == LEFT_BUTTON:
+ self.show_pressure = False
+ else:
+ return
+ elif index == 8: # visibility
+ if button == LEFT_BUTTON:
+ self.show_visibility = False
+ elif button == SCROLL_UP:
+ unit = self.visibility_unit
+ if unit in {'km' : 'm', 'm' : 'mi'}:
+ self.visibility_unit = {'km' : 'm', 'm' : 'mi'}[unit]
+ else:
+ unit = self.visibility_unit
+ if unit in {'mi' : 'm', 'mile' : 'm', 'm' : 'km'}:
+ self.visibility_unit = {'mi' : 'm', 'mile' : 'm', 'm' : 'km'}[unit]
+ elif index == 9: # sky conditions
+ if button == LEFT_BUTTON:
+ self.show_sky = False
+ else:
+ return
+ self.invalidate()
+
+ def refresh_(self):
+ if self.semaphore.acquire(blocking = False):
+ for index, station in enumerate(self.stations):
+ try:
+ self.reports[index] = Weather(station)
+ self.up_to_date[index] = True
+ except:
+ self.up_to_date[index] = False
+ self.semaphore.release()
+ self.invalidate()
+
+ def invalidate(self):
+ self.updated = False
+ Entry.invalidate(self)
+
+ def get_weather(self, index):
+ station = self.stations[index]
+ report = self.reports[index]
+ up_to_date = self.up_to_date[index]
+ station = station[0] + station[1:].lower()
+
+ if report is None:
+ if self.show_station:
+ return '%s: ?' % station
+ else:
+ return '?'
+
+ text = []
+
+ if self.show_temp and report.temp is not None:
+ value = report.temp
+ colour = '34'
+ if value < -10: colour = '39;44'
+ if value >= 18: colour = '39'
+ if value >= 25: colour = '33'
+ if value >= 30: colour = '31'
+ if self.in_deg_f:
+ value = value * 9 / 5 + 32
+ value = '\033[%sm%.0f\033[0m%s' % (colour, value, '°F' if self.in_deg_f else '°C')
+ text.append(value)
+ else:
+ text.append(None)
+
+ if self.show_wind_speed and report.wind_speed is not None:
+ knot = report.wind_speed
+ kmph = knot * 1.852 # km/h
+ mps = kmph / 3.6 # m/s
+ mph = knot * 1.151 # mph
+ ftps = mps * 3.281 # ft/s
+ beaufort = (mps / 0.8365) ** (2 / 3) # B
+ if self.wind_unit in ('kn', 'kt', 'knot', 'knots'):
+ value, unit = knot, 'kn'
+ elif self.wind_unit in ('km/h', 'kmph'):
+ value, unit = kmph, 'km/h'
+ elif self.wind_unit == 'mph':
+ value, unit = mph, 'mph'
+ elif self.wind_unit in ('ft/s', 'ftps'):
+ value, unit = ftps, 'ft/s'
+ elif self.wind_unit.lower() in ('beaufort', 'beaufort scale', 'bf', 'bt', 'b'):
+ value, unit = beaufort, 'B'
+ else:
+ value, unit = mps, 'm/s'
+ beaufort = int(beaufort + 0.5)
+ colour = '32'
+ if beaufort >= 1: colour = '39'
+ if beaufort >= 3: colour = '32'
+ if beaufort >= 7: colour = '33'
+ if beaufort >= 10: colour = '31'
+ if beaufort >= 12: colour = '39;41'
+ wind = '\033[%sm%.0f\033[0m' % (colour, value)
+ if self.show_wind_gusts and report.wind_gusts is not None:
+ knot = report.wind_gusts
+ kmph = knot * 1.852 # km/h
+ mps = kmph / 3.6 # m/s
+ mph = knot * 1.151 # mph
+ ftps = mps * 3.281 # ft/s
+ beaufort = (mps / 0.8365) ** (2 / 3) # B
+ if self.wind_unit in ('kn', 'kt', 'knot', 'knots'):
+ value = knot
+ elif self.wind_unit in ('km/h', 'kmph'):
+ value = kmph
+ elif self.wind_unit == 'mph':
+ value = mph
+ elif self.wind_unit in ('ft/s', 'ftps'):
+ value = ftps
+ elif self.wind_unit.lower() in ('beaufort', 'beaufort scale', 'bf', 'bt', 'b'):
+ value = beaufort
+ else:
+ value = mps
+ beaufort = int(beaufort + 0.5)
+ colour = '32'
+ if beaufort >= 1: colour = '39'
+ if beaufort >= 3: colour = '32'
+ if beaufort >= 7: colour = '33'
+ if beaufort >= 10: colour = '31'
+ if beaufort >= 12: colour = '39;41'
+ wind += '→\033[%sm%.0f\033[0m' % (colour, value)
+ wind += unit
+ text.append(wind)
+ else:
+ text.append(None)
+
+ def wind_dir(direction):
+ if not self.from_wind:
+ direction += 180
+ direction %= 360
+ if direction < 0:
+ direction += 360
+ if (direction < 90):
+ if direction == 0:
+ return 'N'
+ if direction <= 45:
+ return 'N%.0f°E' % direction
+ return 'E%.0f°N' % (90 - direction)
+ elif (90 <= direction < 180):
+ if direction == 90:
+ return 'E'
+ direction -= 90
+ if direction <= 45:
+ return 'E%.0f°S' % direction
+ return 'S%.0f°E' % (90 - direction)
+ elif (180 <= direction < 270):
+ if direction == 180:
+ return 'S'
+ direction -= 180
+ if direction <= 45:
+ return 'S%.0f°W' % direction
+ return 'W%.0f°S' % (90 - direction)
+ else:
+ if direction == 270:
+ return 'W'
+ direction -= 270
+ if direction <= 45:
+ return 'W%.0f°N' % direction
+ return 'N%.0f°W' % (90 - direction)
+ if not self.show_wind_dir:
+ wind = None
+ elif report.wind_var is not None:
+ wind = wind_dir(report.wind_var[0]) + '-' + wind_dir(report.wind_var[1])
+ elif report.wind_dir is not None:
+ wind = wind_dir(report.wind_dir)
+ else:
+ wind = None
+ if wind is not None:
+ if self.from_wind:
+ wind += '→'
+ else:
+ wind = '→' + wind
+ text.append(wind)
+ else:
+ text.append(None)
+
+ if self.show_wind_chill and report.wind_chill is not None:
+ value = report.wind_chill
+ # Coloured by frostbite time, however the wind is not taken
+ # into account so the colour is approximate
+ colour = '39'
+ if value <= -28: colour = '33'
+ if value <= -37: colour = '31'
+ if value <= -46: colour = '39;41'
+ if self.in_deg_f:
+ value = value * 9 / 5 + 32
+ value = 'w\033[%sm%.0f\033[0m%s' % (colour, value, '°F' if self.in_deg_f else '°C')
+ text.append(value)
+ else:
+ text.append(None)
+
+ if self.show_humidity and report.humidity is not None:
+ value = report.humidity
+ colour = '34'
+ if value >= 26: colour = '32'
+ if value >= 31: colour = '39'
+ if value >= 37: colour = '33'
+ if value >= 52: colour = '31'
+ if value >= 73: colour = '39;41'
+ value = '\033[%sm%.0f\033[0m%%' % (colour, value)
+ text.append(value)
+ else:
+ text.append(None)
+
+ if self.show_dew and report.dew is not None:
+ value = report.dew
+ colour = '34'
+ if value >= 10: colour = '32'
+ if value >= 13: colour = '39'
+ if value >= 16: colour = '33'
+ if value >= 21: colour = '31'
+ if value >= 26: colour = '39;41'
+ if self.in_deg_f:
+ value = value * 9 / 5 + 32
+ value = 'd\033[%sm%.0f\033[0m%s' % (colour, value, '°F' if self.in_deg_f else '°C')
+ text.append(value)
+ else:
+ text.append(None)
+
+ if self.show_pressure and report.pressure is not None:
+ text.append('%.0fhPa' % report.pressure)
+ else:
+ text.append(None)
+
+ if self.show_visibility and report.visibility is not None:
+ mile = report.visibility
+ km = mile * 1.609344
+ m = mile * 1609.344
+ if self.visibility_unit in ('mile', 'mi'):
+ value, unit = mile, 'mi'
+ elif self.visibility_unit == 'm':
+ value, unit = m, 'm'
+ else:
+ value, unit = km, 'km'
+ text.append('%.0f%s' % (value, unit))
+ else:
+ text.append(None)
+
+ if self.show_sky and 'Sky conditions' in report.fields:
+ value = report.fields['Sky conditions']
+ if not value == '':
+ text.append(value)
+ else:
+ text.append(None)
+ else:
+ text.append(None)
+
+ segments = []
+ x = 0
+ if self.show_station:
+ w = len(station)
+ segments.append((x, w))
+ x += w + 2
+ else:
+ segments.append(None)
+ x -= 1
+ self.towards = None
+ for i, segment in enumerate(text):
+ if segment is None:
+ segments.append(None)
+ else:
+ if i == 1 and '→' in segment:
+ self.towards = x + Bar.coloured_length(segment.split('→')[0])
+ w = Bar.coloured_length(segment)
+ x += 1
+ segments.append((x, w))
+ x += w
+ self.segments = segments
+ text = [s for s in text if s is not None]
+
+ if len(text) == 0:
+ return station
+
+ if not up_to_date:
+ if len(text) <= 1:
+ text = ' '.join(text) + '?'
+ else:
+ text = ' '.join(text) + ' ?'
+ else:
+ text = ' '.join(text)
+ if self.show_station:
+ text = '%s: %s' % (station, text)
+ return text
+
+ def function(self):
+ if not self.updated:
+ self.updated = True
+ self.text = self.get_weather(self.station)
+ rc = self.text
+ self.refresh()
+ return rc
+
+ @staticmethod
+ def get_metar_station():
+ try:
+ with open(HOME + '/.config/metar', 'rb') as file:
+ station = file.read()
+ except:
+ try:
+ with open('/etc/metar', 'rb') as file:
+ station = file.read()
+ except:
+ print('~/.config/metar is not set', file = sys.stderr, flush = True)
+ station = b'ESSA'
+ return station.decode('utf-8', 'strict').split('\n')[0].upper()
diff --git a/xpybar/config/myxmonad.py b/xpybar/config/myxmonad.py
new file mode 100644
index 0000000..0b57cef
--- /dev/null
+++ b/xpybar/config/myxmonad.py
@@ -0,0 +1,158 @@
+# -*- python -*-
+from common import *
+
+class MyXMonad(Entry):
+ def __init__(self, *args, mod = Xlib.XK.XK_Super_L, ppWsSep = '', ppSep = ' ', **kwargs):
+ self.text = ''
+ self.text_short = ''
+ self.ws_sep_len = len(ppWsSep)
+ self.sep_len = len(ppSep)
+ self.mod = mod
+ self.show_full = True
+ self.short_layout = None
+ Entry.__init__(self, *args, **kwargs)
+ xasync(lambda : forever(t(self.refresh)), name = 'xmonad')
+
+ def action(self, col, button, x, y):
+ if button == MIDDLE_BUTTON:
+ self.show_full = not self.show_full
+ self.invalidate()
+ return
+ text = self.text
+ full = self.show_full
+ UTF8_STRING = G.display.intern_atom('UTF8_STRING')
+ _XMONAD_LOG = G.display.intern_atom('_XMONAD_LOG')
+ _NET_DESKTOP_NAMES = G.display.intern_atom('_NET_DESKTOP_NAMES')
+ xmonad_log = G.display.screen().root.get_full_property(_XMONAD_LOG, UTF8_STRING).value.decode('utf-8', 'replace')
+ xmonad_log = xmonad_log.split(' : ')
+ net_desktop_names = G.display.screen().root.get_full_property(_NET_DESKTOP_NAMES, UTF8_STRING).value.decode('utf-8', 'replace')
+ net_desktop_names = net_desktop_names[:-1].split('\0')
+ ws = 0
+ ws_hit = False
+ while ws < len(net_desktop_names):
+ if ws > 0:
+ col -= self.ws_sep_len
+ if col < 0:
+ break
+ width = len(net_desktop_names[ws] if full else str(ws + 1)) + 2
+ if col < width:
+ ws_hit = True
+ break
+ ws += 1
+ col -= width
+ if ws < len(net_desktop_names):
+ if button == LEFT_BUTTON:
+ self.switch_workspace(ws + 1)
+ elif button in (SCROLL_UP, SCROLL_DOWN):
+ xmonad_log = xmonad_log[0].split(' ')
+ active = [net_desktop_names.index(w[1:-1]) for w in xmonad_log if w[:1] == '<']
+ ws = [net_desktop_names.index(w[1:-1]) for w in xmonad_log if w[:1] == '['][0]
+ ws += -1 if button == SCROLL_UP else +1
+ ws %= len(net_desktop_names)
+ while ws in active:
+ ws += -1 if button == SCROLL_UP else +1
+ ws %= len(net_desktop_names)
+ self.switch_workspace(ws + 1)
+ elif button == LEFT_BUTTON:
+ col -= self.sep_len
+ layout = xmonad_log[1]
+ if not self.show_full and self.short_layout is not None:
+ layout = self.short_layout
+ if col < len(layout) + 2:
+ G.display.xtest_fake_input(Xlib.X.KeyPress, G.display.keysym_to_keycode(self.mod))
+ G.display.xtest_fake_input(Xlib.X.KeyPress, G.display.keysym_to_keycode(Xlib.XK.XK_space))
+ G.display.xtest_fake_input(Xlib.X.KeyRelease, G.display.keysym_to_keycode(Xlib.XK.XK_space))
+ G.display.xtest_fake_input(Xlib.X.KeyRelease, G.display.keysym_to_keycode(self.mod))
+ G.display.flush()
+
+ def refresh(self):
+ try:
+ text = input()
+ except:
+ sys.exit(0)
+ buf, esc = '', None
+ for c in text:
+ if esc is not None:
+ esc += c
+ if esc == '^':
+ buf += '^'
+ esc = None
+ elif esc[-1] == ')':
+ if esc.startswith('bg(') or esc.startswith('fg('):
+ c = 4 if esc.startswith('bg(') else 3
+ esc = esc[3 : -1]
+ if esc == '':
+ buf += '\033[%i9m' % c
+ else:
+ r, g, b = esc[1 : 3], esc[3 : 5], esc[5 : 7]
+ r, g, b = int(r, 16), int(g, 16), int(b, 16)
+ r, g, b = str(r), str(g), str(b)
+ buf += '\033[%i8;2;%sm' % (c, ';'.join([r, g, b]))
+ esc = None
+ elif c == '^':
+ esc = ''
+ else:
+ buf += c
+ self.text = buf
+ short = ''
+ space, esc, layout, lbuf = False, False, 0, ''
+ ws = 1
+ for c in buf:
+ if esc:
+ short += c
+ esc = not (c == '~' or 'a' <= c.lower() <= 'z')
+ elif c == '\033':
+ esc = True
+ if layout == 2:
+ layout = 3
+ end = ''
+ while lbuf.startswith(' '):
+ short += ' '
+ lbuf = lbuf[1:]
+ while lbuf.endswith(' '):
+ end += ' '
+ lbuf = lbuf[:-1]
+ self.short_layout = self.shorten_layout(lbuf)
+ short += self.short_layout + end
+ short += c
+ elif ws <= 9:
+ if c in ' <>[]':
+ space = not space
+ if not space:
+ short += '%i' % ws
+ ws += 1
+ if ws == 10:
+ layout = 1
+ short += c
+ elif not space:
+ short += c
+ elif layout == 1:
+ if c == ' ':
+ short += c
+ else:
+ layout = 2
+ lbuf += c
+ elif layout == 2:
+ lbuf += c
+ else:
+ short += c
+ self.text_short = short
+ self.invalidate()
+
+ def shorten_layout(self, text):
+ text = text.split(' ')
+ incl = []
+ for word in text:
+ if word not in ('ImageButtonDeco', 'Maximize', 'Minimize'):
+ incl.append(word)
+ return ' '.join(incl).replace('Mirror ', '\\')
+
+ def switch_workspace(self, ws):
+ G.display.xtest_fake_input(Xlib.X.KeyPress, G.display.keysym_to_keycode(self.mod))
+ G.display.xtest_fake_input(Xlib.X.KeyPress, G.display.keysym_to_keycode(Xlib.XK.XK_0 + ws))
+ G.display.xtest_fake_input(Xlib.X.KeyRelease, G.display.keysym_to_keycode(Xlib.XK.XK_0 + ws))
+ G.display.xtest_fake_input(Xlib.X.KeyRelease, G.display.keysym_to_keycode(self.mod))
+ G.display.flush()
+
+ def function(self):
+ return self.text if self.show_full else self.text_short
diff --git a/xpybar/config/xmonad-monitor.gpp b/xpybar/config/xmonad-monitor.gpp
new file mode 100644
index 0000000..337e661
--- /dev/null
+++ b/xpybar/config/xmonad-monitor.gpp
@@ -0,0 +1,222 @@
+# -*- python -*-
+
+import os
+import sys
+
+
+from plugins.application import Application
+from plugins.clock import Clock
+from plugins.cpuinfo import CPUInfo
+from plugins.hdparm import HDParm
+from plugins.image import Image
+from plugins.kmsg import KMsg
+from plugins.loadavg import AverageLoad
+from plugins.lunar import Lunar
+from plugins.menu import Menu
+from plugins.ropty import ROPTY
+from plugins.solar import Solar
+
+sys.path.append(os.path.dirname(config_file) if '/' in config_file else '.')
+from common import *
+
+G.globals = globals()
+G.clock = Clock(sync_to = 0.5)
+%%>if test -x /usr/bin/terminator; then
+G.TERMINAL = 'terminator'
+%%>elif test -x /usr/bin/st; then
+G.TERMINAL = 'st'
+%%>else
+G.TERMINAL = 'xterm'
+%%>fi
+
+G.OUTPUT, G.HEIGHT_PER_LINE, G.YPOS, G.TOP = 0, 12, 0, True
+G.FONT = '-misc-fixed-medium-r-normal-*-10-*-*-*-c-*-iso10646-1'
+
+from myalsa import MyALSA
+from mybacklight import MyBacklight
+from mybattery import MyBattery
+from mybrilliance import MyBrilliance
+from myclock import MyClock
+from mycomputer import MyComputer
+from mycpu import MyCPU
+from myio import MyIO
+from myipaddress import MyIPAddress
+from myirc import MyIRC
+from mylid import MyLid
+from myleapsec import MyLeapsec
+from mymemory import MyMemory
+from mymoc import MyMOC
+from mynetwork import MyNetwork, Ping
+from mynews import MyNews
+from myscroll import MyScroll
+from mystat import MyStat
+from mysun import MySun
+from mytimer import MyTimer
+from mytop import MyTop
+from myweather import MyWeather
+from myxmonad import MyXMonad
+
+
+def mqueue_wait():
+ import posix_ipc
+ qkey = '/.xpybar.' + os.environ['DISPLAY'].split('.')[0]
+ q = posix_ipc.MessageQueue(qkey, posix_ipc.O_CREAT, 0o600, 8, 128)
+ while True:
+ try:
+ message = q.receive(None)[0].decode('utf-8', 'replace').split(' ')
+ if message[0] in mqueue_map:
+ try:
+ mqueue_map[message[0]](message)
+ except Exception as err:
+ print('%s: %s' % (sys.argv[0], str(err)), file = sys.stderr, flush = True)
+ else:
+ print('%s: unrecognised message: %s' % (sys.argv[0], ' '.join(message)), file = sys.stderr, flush = True)
+ except:
+ time.sleep(1)
+
+
+
+myxmonad = MyXMonad(None)
+myscroll = MyScroll(None)
+myclock = MyClock (lambda f : Clocked(f, 1), format = '%Y-(%m)%b-%d %T, %a w%V, %Z', long_format = '%Y-%m-%d %T')
+mixers = ['Master', 'PCM'] ## TODO
+#mixers.append(('Headphone', 'Speaker'))
+#myii = ...
+#myirc = ...
+#from plugins.ii import II
+#from myii import MyII
+#def irc_watch(s):
+# s = s.lower()
+# return any(map(lambda x : x in s, ['mattias andrée', 'maandree', 'mandree', 'maandre', 'mandre']))
+#ii = II('irc.oftc.net/#suckless', prefix = HOME + '/.irc')
+#myii = MyII(None, channel = 'irc.oftc.net/#suckless', prefix = HOME + '/.irc', watch = irc_watch)
+#myirc = MyIRC(None, ii = myii)
+#myii.display = myirc
+mylid = ...
+mybattery = ...
+%%>if test -d /proc/acpi/button/lid; then
+mylid = MyLid (None)
+%%>fi
+%%>if test ! $(ls /sys/class/power_supply/ | wc -l) = 0; then
+mybattery = MyBattery(None)
+%%>fi
+
+pingthese = []
+%%>if test -r ~/.dotfiles/.secrets/ping-"$(hostname | tr '[[:upper:]]' '[[:lower:]]')"; then
+%%> for address in $(cat ~/.dotfiles/.secrets/ping-"$(hostname | tr '[[:upper:]]' '[[:lower:]]')"); do
+pingthese.append(Ping(targets = Ping.get_nics('%%{address}'), interval = 30))
+%%> done
+%%>fi
+
+try:
+ with open(HOME + '/.config/metar', 'rb') as file:
+ metar_stations = file.read().decode('utf-8', 'strict').split('\n')
+except:
+ raise
+ try:
+ with open('/etc/metar', 'rb') as file:
+ metar_stations = file.read().decode('utf-8', 'strict').split('\n')
+ except:
+ metar_stations = []
+metar_stations = [x[0].upper() + x[1:].lower() for x in metar_stations if x != '']
+
+functions = [ [ myxmonad
+ , None
+ , MyTimer (None, alarms = [])
+ , MyALSA (None, mixers = mixers, colours = {'Speaker' : '31'})
+ , MyComputer (lambda f : Clocked(f, 20))
+ , myscroll
+ , None
+ , myclock
+ , MyCPU (lambda f : Clocked(f, 2))
+ , MyMemory (lambda f : Clocked(f, 2))
+ , None
+ , MyNetwork (lambda f : Clocked(f, 2), pings = pingthese)
+ #, myirc
+%%>if test -x /usr/bin/featherweight; then
+ , MyNews (None)
+%%>fi
+ , MyWeather (None, stations = metar_stations) if metar_stations else ...
+ #, MySun (None, clock = myclock)
+ ]
+ , [ myxmonad
+ , None
+ , MyIPAddress (lambda f : Clocked(f, 20), public = False)
+ , MyMOC (None)
+ , myscroll
+ , None
+ , myclock
+ , mylid
+ , mybattery
+ #, myii
+ , None
+ , MyStat (lambda f : Clocked(f, 10))
+ , MyBrilliance(None)
+ #, MyBacklight (None)
+ #, MyIO (lambda f : Clocked(f, 10), fs_ignore = [])
+ ]
+ ]
+
+functions = [[mon for mon in group if mon != ...] for group in functions]
+
+G.HEIGHT = 0
+G.groups = [Group(f) for f in functions]
+G.groupi = 0
+G.group = G.groups[G.groupi]
+G.semaphore = threading.Semaphore()
+
+def update_per_clock():
+ if G.semaphore.acquire(blocking = False):
+ try:
+ for g in G.groups:
+ for f in g.functions:
+ if isinstance(f.wrapped, Clocked):
+ f(True)
+ finally:
+ G.semaphore.release()
+ invalidate()
+
+start_ = start
+def start():
+ start_()
+ #if myii is not ...:
+ # myii.start()
+ bar.clear()
+ get_display().flush()
+ xasync(lambda : G.clock.continuous_sync(t(update_per_clock)), name = 'clock')
+ xasync(mqueue_wait, name = 'mqueue')
+
+def redraw():
+ if G.semaphore.acquire(blocking = False):
+ try:
+ gr = G.groups[G.groupi]
+ for g in G.groups:
+ if g is not gr:
+ for f in g.functions:
+ f()
+ values = gr.pattern % tuple(f() for f in gr.functions)
+ bar.partial_clear(0, bar.width, 10, 0, 2, values)
+ bar.draw_coloured_splitted_text(0, bar.width, 10, 0, 2, values)
+ finally:
+ G.semaphore.release()
+ return True
+ return False
+
+def unhandled_event(e):
+ if isinstance(e, Xlib.protocol.event.ButtonPress):
+ y = e.event_y
+ x = e.event_x
+ row = y // HEIGHT_PER_LINE
+ lcol = x // bar.font_width
+ rcol = (bar.width - x) // bar.font_width
+ button = e.detail
+ if button in (FORWARD_BUTTON, BACKWARD_BUTTON):
+ G.groupi = (G.groupi + (+1 if button == BACKWARD_BUTTON else -1)) % len(G.groups)
+ G.group = G.groups[G.groupi]
+ invalidate()
+ else:
+ for f in G.group.posupdate:
+ f.update_position()
+ for f in G.group.functions:
+ if f.click(row, lcol, rcol, button, x, y):
+ break