# -*- python -*-
'''
xpybar – xmobar replacement written in python
Copyright © 2014, 2015, 2016 Mattias Andrée (maandree@member.fsf.org)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
'''
# This is a template configuration of a panel that can display almost
# all information and control, but suited for a height constrained screen
# It is recommended to have python-posix_ipc and
# installed
import os
import sys
import pwd
import time
import threading
import subprocess
from util import *
from plugins.alsa import ALSA
from plugins.clock import Clock
from plugins.cpu import CPU
from plugins.cpuinfo import CPUInfo
from plugins.cpuonline import CPUOnline
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.ipaddress import IPAddress
from plugins.leapsec import LeapSeconds
from plugins.loadavg import AverageLoad
from plugins.locks import Locks
from plugins.mem import Memory
from plugins.moc import MOC
from plugins.network import Network
from plugins.pacman import Pacman
from plugins.ping import Ping
from plugins.random import Random
from plugins.snmp import SNMP
from plugins.snmp6 import SNMP6
from plugins.softirqs import SoftIRQs
from plugins.swaps import Swaps
from plugins.tzclock import TZClock
from plugins.uname import Uname
from plugins.uptime import Uptime
from plugins.users import Users
from plugins.vmstat import VMStat
from plugins.weather import Weather
from plugins.xdisplay import XDisplay
from plugins.xkb import XKB
#from plugins.application import Application
#from plugins.hdparm import HDParm
#from plugins.image import Image
#from plugins.kmsg import KMsg
#from plugins.lunar import Lunar
#from plugins.menu import Menu
#from plugins.ropty import ROPTY
#from plugins.solar import Solar
import Xlib.X, Xlib.XK, Xlib.protocol.event
OUTPUT, HEIGHT_PER_LINE, YPOS, TOP = 0, 12, 0, True
clock = Clock(sync_to = 0.5)
TERMINAL = 'terminator'
LEFT_BUTTON = 1
MIDDLE_BUTTON = 2
RIGHT_BUTTON = 3
SCROLL_UP = 4
SCROLL_DOWN = 5
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:
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.F_OK | 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:
bar.invalidate()
except:
pass
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 * bar.font_width
else:
col = self.width - 1 - col
x = bar.width - x
x -= self.offset * bar.font_width
x = self.width * bar.font_width - x
y -= self.line * 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)
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):
global HEIGHT
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) * HEIGHT_PER_LINE
if HEIGHT < height:
HEIGHT = height
self.functions = new_functions
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 = False
self.short_layout = None
Entry.__init__(self, *args, **kwargs)
async(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 = display.intern_atom('UTF8_STRING')
_XMONAD_LOG = display.intern_atom('_XMONAD_LOG')
_NET_DESKTOP_NAMES = display.intern_atom('_NET_DESKTOP_NAMES')
xmonad_log = display.screen().root.get_full_property(_XMONAD_LOG, UTF8_STRING).value
xmonad_log = xmonad_log.split(' : ')
net_desktop_names = display.screen().root.get_full_property(_NET_DESKTOP_NAMES, UTF8_STRING).value
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:
display.xtest_fake_input(Xlib.X.KeyPress, display.keysym_to_keycode(self.mod))
display.xtest_fake_input(Xlib.X.KeyPress, display.keysym_to_keycode(Xlib.XK.XK_space))
display.xtest_fake_input(Xlib.X.KeyRelease, display.keysym_to_keycode(Xlib.XK.XK_space))
display.xtest_fake_input(Xlib.X.KeyRelease, display.keysym_to_keycode(self.mod))
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):
display.xtest_fake_input(Xlib.X.KeyPress, display.keysym_to_keycode(self.mod))
display.xtest_fake_input(Xlib.X.KeyPress, display.keysym_to_keycode(Xlib.XK.XK_0 + ws))
display.xtest_fake_input(Xlib.X.KeyRelease, display.keysym_to_keycode(Xlib.XK.XK_0 + ws))
display.xtest_fake_input(Xlib.X.KeyRelease, display.keysym_to_keycode(self.mod))
display.flush()
def function(self):
return self.text if self.show_full else self.text_short
class MyScroll(Entry):
def function(self):
return '↓↑'
def action(self, col, button, x, y):
global groups, groupi, group
if button in (LEFT_BUTTON, RIGHT_BUTTON, SCROLL_UP, SCROLL_DOWN):
groupi = (groupi + (+1 if button in (LEFT_BUTTON, SCROLL_DOWN) else -1)) % len(groups)
group = groups[groupi]
self.invalidate()
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 = clock
self.set_format(self.clock, self.default_format)
self.tzs = [] if tzs is None else tzs
self.tzi = 0
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 clock:
del old_clock
self.invalidate()
def action(self, col, button, x, y):
if button == LEFT_BUTTON:
if self.clock is clock:
clock.utc = not clock.utc
self.set_format(clock, self.clock.u_format)
else:
self.set_format(clock, self.clock.u_format)
self.clock = 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 = clock
if old_clock is not clock:
del old_clock
self.invalidate()
def set_format(self, clck, format):
clck.u_format = format
if not (clck is 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 = '0'
if clck is not clock or clck.utc:
colour += ';7'
return '\033[%sm%s\033[00m' % (colour, clck.read())
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]
display.xtest_fake_input(Xlib.X.KeyPress, display.keysym_to_keycode(key))
display.xtest_fake_input(Xlib.X.KeyRelease, display.keysym_to_keycode(key))
display.flush()
elif button == MIDDLE_BUTTON:
self.show_detailed = not self.show_detailed
self.invalidate()
elif button == RIGHT_BUTTON:
if self.display == 0:
async(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): ## 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()
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 : async(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.plusminus is None or col < self.plusminus:
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 == 0:
return 'E'
direction -= 90
if direction <= 45:
return 'E%.0f°S' % direction
return 'S%.0f°E' % (90 - direction)
elif (180 <= direction < 270):
if direction == 0:
return 'S'
direction -= 180
if direction <= 45:
return 'S%.0f°W' % direction
return 'W%.0f°S' % (90 - direction)
else:
if direction == 0:
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.plusminus = None
for i, segment in enumerate(text):
if segment is None:
segments.append(None)
else:
if i == 1 and '±' in segment:
self.plusminus = 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()
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)
async(self.refresh, name = 'news')
def action(self, col, button, x, y):
if button == LEFT_BUTTON:
async(lambda : subprocess.Popen([TERMINAL, '-e', 'featherweight']).wait())
elif button == RIGHT_BUTTON:
async(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 = '0'
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:
try:
spawn_read(*pdeath('HUP', 'inotifywait', self.status_path, '-e', 'close_write'))
self.get_news()
except:
time.sleep(1)
def function(self):
return self.text
class MyALSA(Entry):
def __init__(self, *args, timeout = None, sleep = None, mixers = None, **kwargs):
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 not None:
mixers = mixers
else:
mixers = ('Master', 'PCM')
self.alsa = []
for mixer in mixers:
try:
self.alsa.append(ALSA(mixername = mixer))
except:
pass
try:
import posix_ipc
method = 0
except:
method = 2
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 = 1
break
except:
pass
self.sep_width = Bar.coloured_length(SEPARATOR)
self.get_volume()
Entry.__init__(self, *args, **kwargs)
async((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 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()
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()))
self.text = SEPARATOR.join(read_m(m) for m in self.alsa)
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)
while True:
try:
s.release() ; c.release()
try:
q.acquire(timeout)
except posix_ipc.BusyError:
pass
c.acquire(None)
s.acquire(None)
self.get_volume()
except:
time.sleep(self.sleep)
def refresh_cmdipc(self):
command = pdeath('HUP', 'cmdipc', '-PCck', '/.xpybar.alsa.0/.xpybar.alsa.1/.xpybar.alsa.2', 'wait', '-b%i' % self.timeout)
while True:
try:
spawn_read(*command)
self.get_volume()
except:
time.sleep(self.sleep)
def refresh_wait(self):
while True:
try:
time.sleep(self.sleep)
self.get_volume()
except:
pass
def function(self):
return self.text
class MyBattery(Entry):
def __init__(self, *args, **kwargs):
self.show_all = False
self.show_battery = 0
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:
self.show_battery += 1
self.invalidate()
elif button == SCROLL_DOWN:
self.show_battery -= 1
if self.show_battery < 0:
self.show_battery = 0
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 colourise(self, text):
buf = []
state = None
for c in reversed(text):
if c == '\t':
c = ' '
if state is not None:
if c == ' ':
if state == '':
buf.append(c)
continue
buf.append(self.colourise_(state))
buf.append(c)
state = None
else:
state = c + state
elif c == '%':
buf.append(c)
state = ''
else:
buf.append(c)
if state is not None and state != '':
buf.append(self.colourise_(state))
return ''.join(reversed(buf))
def function(self):
try:
data = spawn_read('acpi').split('\n')
except:
return 'acpi missing'
if self.show_battery >= len(data):
self.show_battery = len(data) - 1
if len(data) == 1:
return self.colourise(': '.join(data[0].split(': ')[1:]))
if self.show_all:
data = [self.colourise(': '.join(datum.split(': ')[1:])) for datum in data]
data = SEPARATOR.join('Bat%i: %s' % (i, datum) for i, datum in enumerate(data))
return data
data = data[self.show_battery]
data = ': '.join(data.split(': ')[1:])
data = 'Bat%i: %s' % (self.show_battery, self.colourise(data))
return data
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 = False
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
class MyMemory(Entry):
def __init__(self, *args, **kwargs):
self.coloured = tuple(self.colourise(i) for i in range(101))
self.show_labels = False
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
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)
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 = False
self.show_name = False
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
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)
class MyTop(Entry):
def __init__(self, *args, **kwargs):
self.show_pid = False
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)
async(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
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: async(lambda : moc.play().wait())
elif col == 1: async(lambda : moc.toggle_pause().wait())
elif col == 2: async(lambda : moc.stop().wait())
elif col == 3: async(lambda : moc.previous().wait())
elif col == 4: async(lambda : moc.next().wait())
elif button == SCROLL_UP: async(lambda : moc.seek(+5).wait())
elif button == SCROLL_DOWN: async(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()
class MyIPAddress(Entry):
def __init__(self, *args, ignore = None, **kwargs):
self.ignore = ['lo'] if ignore is None else ignore
self.text = None
self.show_public = True
self.show_nic = None
Entry.__init__(self, *args, **kwargs)
def init():
self.refresh()
async(lambda : Clock(sync_to = 10 * Clock.MINUTES).continuous_sync(t(self.refresh)), name = 'ipaddress')
async(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 == RIGHT_BUTTON:
async(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 = {}, {}
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)
pub = label + ':'
prv = pub
if a4 is not None:
prv += ' ' + a4
if a6 is not None:
prv += ' ' + a6
elif a4 is None:
prv += ' no address'
pub += ' ' + a if a is not None else ' no address'
text_private[nic] = prv
text_public[nic] = pub
self.text = (text_private, text_public)
self.invalidate()
def function(self):
text = self.text
if text is None:
return '...'
text = text[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]
class MyLeapsec(Entry):
def __init__(self, *args, **kwargs):
self.announcement = None
Entry.__init__(self, *args, **kwargs)
def init():
self.refresh()
async(lambda : Clock(sync_to = Clock.MINUTES).continuous_sync(Sometimes(t(self.refresh), 12 * 60)), name = 'leapsec')
async(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)))
class MyTimer(Entry):
def __init__(self, *args, **kwargs):
self.start = None
self.length = None
self.notified = False
self.pause_text = None
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 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 - 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):
subprocess.Popen(['notify-send', '-u', 'critical', 'You are done with your task']).wait()
def wall(self):
subprocess.Popen(['wall', 'You are done with your task']).wait()
def function(self):
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
async(self.notify, name = 'timer notify')
async(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'
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)
mqueue_map = {}
myxmonad = MyXMonad(None)
myscroll = MyScroll(None)
functions = [ [ myscroll
, myxmonad
, None
, MyTimer (None)
, MyNetwork (lambda f : Clocked(f, 2))
, MyMemory (lambda f : Clocked(f, 2))
, MyCPU (lambda f : Clocked(f, 2))
, MyBattery (lambda f : Clocked(f, 10))
, MyALSA (None)
, MyClock (lambda f : Clocked(f, 1))
]
, [ myscroll
, myxmonad
, None
, MyTop (None)
, MyIO (lambda f : Clocked(f, 10))
, MyStat (lambda f : Clocked(f, 10))
, MyNews (None)
, MyWeather (None)
, MyComputer (lambda f : Clocked(f, 20))
]
, [ myscroll
, myxmonad
, None
, MyIPAddress(lambda f : Clocked(f, 20))
, MyLeapsec (None)
, MyMOC (None)
]
]
HEIGHT = 0
groups = [Group(f) for f in functions]
groupi = 0
group = groups[groupi]
semaphore = threading.Semaphore()
def update_per_clock():
if semaphore.acquire(blocking = False):
try:
for g in groups:
for f in g.functions:
if isinstance(f.wrapped, Clocked):
f(True)
finally:
semaphore.release()
invalidate()
start_ = start
def start():
start_()
bar.clear()
get_display().flush()
async(lambda : clock.continuous_sync(t(update_per_clock)), name = 'clock')
async(mqueue_wait, name = 'mqueue')
def redraw():
if semaphore.acquire(blocking = False):
try:
gr = groups[groupi]
for g in 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:
semaphore.release()
return True
return False
def unhandled_event(e):
global groups, groupi, group
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):
groupi = (groupi + (+1 if button == BACKWARD_BUTTON else -1)) % len(groups)
group = groups[groupi]
invalidate()
else:
for f in group.posupdate:
f.update_position()
for f in group.functions:
if f.click(row, lcol, rcol, button, x, y):
break