diff options
Diffstat (limited to 'examples/compact')
| -rw-r--r-- | examples/compact | 2756 | 
1 files changed, 2756 insertions, 0 deletions
| diff --git a/examples/compact b/examples/compact new file mode 100644 index 0000000..6ba9fe7 --- /dev/null +++ b/examples/compact @@ -0,0 +1,2756 @@ +# -*- 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 <http://www.gnu.org/licenses/>. +''' + +# 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 +# <https://github.com/maandree/pdeath> 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 + | 
