[core] Rework core implementation
Experimental re-implementation of core functionality with the aim: - Depend only on the Python Standard Library for core - If modules are missing elsewhere, *never* throw - Unit test *everything* - Cleaner and more minimal implementation - Better integration points for existing implementations (charts, braille, etc.) - Full backwards-compatibility with existing module system (except where modules can be vastly simplified)
This commit is contained in:
parent
72f88b3409
commit
e931bb93c6
214 changed files with 45 additions and 13040 deletions
|
@ -1,7 +0,0 @@
|
||||||
#start with a Python 3.x container
|
|
||||||
FROM python:3
|
|
||||||
|
|
||||||
#grab repository from github
|
|
||||||
RUN git clone --recursive https://github.com/tobi-wan-kenobi/bumblebee-status.git /var/bumblebee-status
|
|
||||||
#run the statusline with no modules or themes specified
|
|
||||||
CMD python3 /var/bumblebee-status/bumblebee-status
|
|
18
Makefile
18
Makefile
|
@ -1,18 +0,0 @@
|
||||||
DEBIAN_ROOT = build/debian/bumblebee-status/
|
|
||||||
|
|
||||||
deb:
|
|
||||||
mkdir -p $(DEBIAN_ROOT)/DEBIAN
|
|
||||||
mkdir -p $(DEBIAN_ROOT)/usr/share/bumblebee-status/modules
|
|
||||||
mkdir -p $(DEBIAN_ROOT)/usr/share/bumblebee-status/themes
|
|
||||||
cp build/debian/control $(DEBIAN_ROOT)/DEBIAN/
|
|
||||||
cp bumblebee-status $(DEBIAN_ROOT)/usr/share/bumblebee-status/
|
|
||||||
cp bumblebee/modules/*.py $(DEBIAN_ROOT)/usr/share/bumblebee-status/modules/
|
|
||||||
cp -r themes/* $(DEBIAN_ROOT)/usr/share/bumblebee-status/themes
|
|
||||||
cp LICENSE $(DEBIAN_ROOT)/usr/share/bumblebee-status/
|
|
||||||
dpkg-deb --build $(DEBIAN_ROOT)
|
|
||||||
cp build/debian/bumblebee-status.deb .
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf $(DEBIAN_ROOT)
|
|
||||||
rm -f build/debian/*.deb
|
|
||||||
rm -f *.deb
|
|
|
@ -1,30 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
if [ ! -f ~/.config/i3/config.template ]; then
|
|
||||||
cp ~/.config/i3/config ~/.config/i3/config.template
|
|
||||||
else
|
|
||||||
cp ~/.config/i3/config.template ~/.config/i3/config
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f ~/.config/i3/config.template.private ]; then
|
|
||||||
cat ~/.config/i3/config.template.private >> ~/.config/i3/config
|
|
||||||
fi
|
|
||||||
|
|
||||||
screens=$(xrandr -q|grep ' connected'| grep -P '\d+x\d+' |cut -d' ' -f1)
|
|
||||||
|
|
||||||
echo "screens: $screens"
|
|
||||||
|
|
||||||
while read -r line; do
|
|
||||||
screen=$(echo $line | cut -d' ' -f1)
|
|
||||||
others=$(echo $screens|tr ' ' '\n'|grep -v $screen|tr '\n' '-'|sed 's/.$//')
|
|
||||||
|
|
||||||
if [ -f ~/.config/i3/config.$screen-$others ]; then
|
|
||||||
cat ~/.config/i3/config.$screen-$others >> ~/.config/i3/config
|
|
||||||
else
|
|
||||||
if [ -f ~/.config/i3/config.$screen ]; then
|
|
||||||
cat ~/.config/i3/config.$screen >> ~/.config/i3/config
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done <<< "$screens"
|
|
||||||
|
|
||||||
i3-msg restart
|
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/bash
|
|
||||||
|
|
||||||
if ! type -P fakeroot >/dev/null; then
|
|
||||||
error 'Cannot find the fakeroot binary.'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z $CHECKUPDATES_DB ]]; then
|
|
||||||
CHECKUPDATES_DB="${TMPDIR:-/tmp}/checkup-db-${USER}/"
|
|
||||||
fi
|
|
||||||
|
|
||||||
trap 'rm -f $CHECKUPDATES_DB/db.lck' INT TERM EXIT
|
|
||||||
|
|
||||||
DBPath="${DBPath:-/var/lib/pacman/}"
|
|
||||||
eval $(awk -F' *= *' '$1 ~ /DBPath/ { print $1 "=" $2 }' /etc/pacman.conf)
|
|
||||||
|
|
||||||
mkdir -p "$CHECKUPDATES_DB"
|
|
||||||
ln -s "${DBPath}/local" "$CHECKUPDATES_DB" &> /dev/null
|
|
||||||
fakeroot -- pacman -Sy --dbpath "$CHECKUPDATES_DB" --logfile /dev/null &> /dev/null
|
|
||||||
fakeroot pacman -Su -p --dbpath "$CHECKUPDATES_DB"
|
|
||||||
|
|
||||||
exit 0
|
|
|
@ -1,15 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
echo $(dirname $(readlink -f "$0"))
|
|
||||||
|
|
||||||
i3bar_update=$(dirname $(readlink -f "$0"))/load-i3-bars.sh
|
|
||||||
|
|
||||||
xrandr "$@"
|
|
||||||
|
|
||||||
if [ -f $i3bar_update ]; then
|
|
||||||
sleep 1
|
|
||||||
if [ -f ~/.config/i3/images/background.png ]; then
|
|
||||||
feh --bg-fill ~/.config/i3/images/background.png
|
|
||||||
fi
|
|
||||||
$i3bar_update
|
|
||||||
fi
|
|
|
@ -1,5 +0,0 @@
|
||||||
Package: bumblebee-status
|
|
||||||
Version: 0
|
|
||||||
Maintainer: tobi-wan-kenobi
|
|
||||||
Architecture: all
|
|
||||||
Description: bumblebee-status is a modular, theme-able status line generator for the i3 window manager.
|
|
|
@ -1,5 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
ln -s /usr/share/bumblebee-status/bumblebee-status /usr/local/bin/bumblebee-status
|
|
||||||
|
|
||||||
|
|
|
@ -1,80 +1,21 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import core.output
|
||||||
import signal
|
|
||||||
import bumblebee.theme
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.config
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.modules.error
|
|
||||||
|
|
||||||
try:
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('UTF8')
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
def sig_USR1_handler(signum,stack):
|
output = core.output.i3()
|
||||||
engine.write_output()
|
modules = []
|
||||||
|
# modules = core.module.modules()
|
||||||
config = bumblebee.config.Config(sys.argv[1:])
|
sys.stdout.write(output.start())
|
||||||
|
|
||||||
if config.debug():
|
|
||||||
if config.logfile() in ["stdout", "stderr"]:
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.DEBUG,
|
|
||||||
format="[%(asctime)s] %(levelname)-8s %(message)s",
|
|
||||||
stream=sys.stdout if config.logfile() == "stdout" else sys.stderr
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.DEBUG,
|
|
||||||
format="[%(asctime)s] %(levelname)-8s %(message)s",
|
|
||||||
filename=config.logfile()
|
|
||||||
)
|
|
||||||
|
|
||||||
theme = bumblebee.theme.Theme(config.theme(), config.iconset())
|
|
||||||
output = bumblebee.output.I3BarOutput(theme=theme, config=config)
|
|
||||||
inp = bumblebee.input.I3BarInput()
|
|
||||||
engine = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
engine = bumblebee.engine.Engine(
|
|
||||||
config=config,
|
|
||||||
output=output,
|
|
||||||
inp=inp,
|
|
||||||
theme=theme,
|
|
||||||
)
|
|
||||||
signal.signal(10,sig_USR1_handler)
|
|
||||||
engine.run()
|
|
||||||
except KeyboardInterrupt as error:
|
|
||||||
inp.stop()
|
|
||||||
sys.exit(0)
|
|
||||||
except BaseException as e:
|
|
||||||
if not engine: raise
|
|
||||||
module = engine.current_module()
|
|
||||||
logging.exception(e)
|
|
||||||
if output.started():
|
|
||||||
output.flush()
|
|
||||||
output.end()
|
|
||||||
else:
|
|
||||||
output.start()
|
|
||||||
import time
|
|
||||||
while True:
|
while True:
|
||||||
output.begin()
|
sys.stdout.write(output.begin_status_line())
|
||||||
error = bumblebee.modules.error.Module(engine, config)
|
for module in modules:
|
||||||
error.set("exception occurred: {} in {}".format(e, module))
|
# module.update()
|
||||||
widget = error.widgets()[0]
|
sys.stdout.write(output.draw(module))
|
||||||
widget.link_module(error)
|
sys.stdout.write(output.end_status_line())
|
||||||
output.draw(widget, error)
|
sys.stdout.write(output.stop())
|
||||||
output.flush()
|
|
||||||
output.end()
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,132 +0,0 @@
|
||||||
"""Configuration handling
|
|
||||||
|
|
||||||
This module provides configuration information (loaded modules,
|
|
||||||
module parameters, etc.) to all other components
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import argparse
|
|
||||||
import textwrap
|
|
||||||
import importlib
|
|
||||||
import bumblebee.store
|
|
||||||
|
|
||||||
MODULE_HELP = "Specify a space-separated list of modules to load. The order of the list determines their order in the i3bar (from left to right). Use <module>:<alias> to provide an alias in case you want to load the same module multiple times, but specify different parameters."
|
|
||||||
THEME_HELP = "Specify the theme to use for drawing modules"
|
|
||||||
PARAMETER_HELP = "Provide configuration parameters in the form of <module>.<key>=<value>"
|
|
||||||
LIST_HELP = "Display a list of either available themes or available modules along with their parameters."
|
|
||||||
DEBUG_HELP = "Enable debug log, This will create '~/bumblebee-status-debug.log' by default, can be changed with the '-f' option"
|
|
||||||
|
|
||||||
class print_usage(argparse.Action):
|
|
||||||
def __init__(self, option_strings, dest, nargs=None, **kwargs):
|
|
||||||
argparse.Action.__init__(self, option_strings, dest, nargs, **kwargs)
|
|
||||||
self._indent = " "*2
|
|
||||||
|
|
||||||
def __call__(self, parser, namespace, value, option_string=None):
|
|
||||||
if value == "modules":
|
|
||||||
self._args = namespace
|
|
||||||
self.print_modules()
|
|
||||||
elif value == "themes":
|
|
||||||
self.print_themes()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def print_themes(self):
|
|
||||||
print(", ".join(bumblebee.theme.themes()))
|
|
||||||
|
|
||||||
def print_modules(self):
|
|
||||||
if self._args.list_format == "markdown":
|
|
||||||
print("# Table of modules")
|
|
||||||
print("|Name |Description |")
|
|
||||||
print("|-----|------------|")
|
|
||||||
|
|
||||||
for m in bumblebee.engine.all_modules():
|
|
||||||
try:
|
|
||||||
mod = importlib.import_module("bumblebee.modules.{}".format(m["name"]))
|
|
||||||
if self._args.list_format == "markdown":
|
|
||||||
print("|{} |{} |".format(m["name"], mod.__doc__.replace("\n", "<br>")))
|
|
||||||
else:
|
|
||||||
print(textwrap.fill("{}:".format(m["name"]), 80,
|
|
||||||
initial_indent=self._indent*2, subsequent_indent=self._indent*2))
|
|
||||||
for line in mod.__doc__.split("\n"):
|
|
||||||
print(textwrap.fill(line, 80,
|
|
||||||
initial_indent=self._indent*3, subsequent_indent=self._indent*6))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_parser():
|
|
||||||
"""Create the argument parser"""
|
|
||||||
parser = argparse.ArgumentParser(description="display system data in the i3bar")
|
|
||||||
parser.add_argument("-m", "--modules", nargs="+", action='append', default=[],
|
|
||||||
help=MODULE_HELP)
|
|
||||||
parser.add_argument("-t", "--theme", default="default", help=THEME_HELP)
|
|
||||||
parser.add_argument("--markup", default="none", help="Specify the markup type of the output (e.g. 'pango')")
|
|
||||||
parser.add_argument("-p", "--parameters", nargs="+", action='append', default=[],
|
|
||||||
help=PARAMETER_HELP)
|
|
||||||
parser.add_argument("-l", "--list", choices=["modules", "themes"], action=print_usage,
|
|
||||||
help=LIST_HELP)
|
|
||||||
parser.add_argument("--list-format", choices=["plain", "markdown"], help="output format of -l, *must* be specified before -l")
|
|
||||||
parser.add_argument("-d", "--debug", action="store_true",
|
|
||||||
help=DEBUG_HELP)
|
|
||||||
parser.add_argument("-r", "--right-to-left", action="store_true",
|
|
||||||
help="Draw widgets from right to left, rather than left to right (which is the default)")
|
|
||||||
parser.add_argument("-f", "--logfile", default="~/bumblebee-status-debug.log",
|
|
||||||
help="Location of the debug log file")
|
|
||||||
parser.add_argument("-i", "--iconset", default="auto",
|
|
||||||
help="Specify the name of an iconset to use (overrides theme default)")
|
|
||||||
parser.add_argument("-a", "--autohide", nargs="+", default=[],
|
|
||||||
help="Specify a list of modules to hide when not in warning/error state")
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
class Config(bumblebee.store.Store):
|
|
||||||
"""Top-level configuration class
|
|
||||||
|
|
||||||
Parses commandline arguments and provides non-module
|
|
||||||
specific configuration information.
|
|
||||||
"""
|
|
||||||
def __init__(self, args=None):
|
|
||||||
super(Config, self).__init__()
|
|
||||||
parser = create_parser()
|
|
||||||
self._args = parser.parse_args(args if args else [])
|
|
||||||
|
|
||||||
if not self._args.debug:
|
|
||||||
logging.getLogger().disabled = True
|
|
||||||
|
|
||||||
parameters = [item for sub in self._args.parameters for item in sub]
|
|
||||||
for param in parameters:
|
|
||||||
key, value = param.split("=", 1)
|
|
||||||
self.set(key, value)
|
|
||||||
|
|
||||||
def modules(self):
|
|
||||||
modules = [item for sub in self._args.modules for item in sub]
|
|
||||||
"""Return a list of all activated modules"""
|
|
||||||
return [{
|
|
||||||
"module": x.split(":")[0],
|
|
||||||
"name": x if not ":" in x else x.split(":")[1],
|
|
||||||
} for x in modules]
|
|
||||||
|
|
||||||
def theme(self):
|
|
||||||
"""Return the name of the selected theme"""
|
|
||||||
return self._args.theme
|
|
||||||
|
|
||||||
def iconset(self):
|
|
||||||
"""Return the name of a user-specified icon-set"""
|
|
||||||
return self._args.iconset
|
|
||||||
|
|
||||||
def debug(self):
|
|
||||||
return self._args.debug
|
|
||||||
|
|
||||||
def reverse(self):
|
|
||||||
return self._args.right_to_left
|
|
||||||
|
|
||||||
def logfile(self):
|
|
||||||
return os.path.expanduser(self._args.logfile)
|
|
||||||
|
|
||||||
def autohide(self):
|
|
||||||
return self._args.autohide
|
|
||||||
|
|
||||||
def markup(self):
|
|
||||||
return self._args.markup
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,306 +0,0 @@
|
||||||
"""Core application engine"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
import pkgutil
|
|
||||||
import logging
|
|
||||||
import importlib
|
|
||||||
import bumblebee.error
|
|
||||||
import bumblebee.modules
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from ConfigParser import RawConfigParser
|
|
||||||
except ImportError:
|
|
||||||
from configparser import RawConfigParser
|
|
||||||
|
|
||||||
def all_modules():
|
|
||||||
"""Return a list of available modules"""
|
|
||||||
result = []
|
|
||||||
path = os.path.dirname(bumblebee.modules.__file__)
|
|
||||||
for mod in [name for _, name, _ in pkgutil.iter_modules([path])]:
|
|
||||||
result.append({
|
|
||||||
"name": mod
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
|
|
||||||
class Module(object):
|
|
||||||
"""Module instance base class
|
|
||||||
|
|
||||||
Objects of this type represent the modules that
|
|
||||||
the user configures. Concrete module implementations
|
|
||||||
(e.g. CPU utilization, disk usage, etc.) derive from
|
|
||||||
this base class.
|
|
||||||
"""
|
|
||||||
def __init__(self, engine, config={}, widgets=[]):
|
|
||||||
self.name = config.get("name", self.__module__.split(".")[-1])
|
|
||||||
self._config = config
|
|
||||||
self.id = self.name
|
|
||||||
self.error = None
|
|
||||||
self._next = int(time.time())
|
|
||||||
self._default_interval = 0
|
|
||||||
self._interval_factor = 1
|
|
||||||
self._engine = engine
|
|
||||||
|
|
||||||
self._configFile = None
|
|
||||||
for cfg in [os.path.expanduser("~/.bumblebee-status.conf"), os.path.expanduser("~/.config/bumblebee-status.conf")]:
|
|
||||||
if os.path.exists(cfg):
|
|
||||||
self._configFile = RawConfigParser()
|
|
||||||
self._configFile.read(cfg)
|
|
||||||
log.debug("reading configuration file {}".format(cfg))
|
|
||||||
break
|
|
||||||
|
|
||||||
if self._configFile is not None and self._configFile.has_section("module-parameters"):
|
|
||||||
log.debug(self._configFile.items("module-parameters"))
|
|
||||||
self._widgets = []
|
|
||||||
if widgets:
|
|
||||||
self._widgets = widgets if isinstance(widgets, list) else [widgets]
|
|
||||||
|
|
||||||
def theme(self):
|
|
||||||
return self._engine.theme()
|
|
||||||
|
|
||||||
def widgets(self, widgets=None):
|
|
||||||
"""Return the widgets to draw for this module"""
|
|
||||||
if widgets:
|
|
||||||
self._widgets = widgets if isinstance(widgets, list) else [widgets]
|
|
||||||
return self._widgets
|
|
||||||
|
|
||||||
def hidden(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def widget(self, name):
|
|
||||||
for widget in self._widgets:
|
|
||||||
if widget.name == name:
|
|
||||||
return widget
|
|
||||||
|
|
||||||
def errorWidget(self):
|
|
||||||
msg = self.error
|
|
||||||
if len(msg) > 10:
|
|
||||||
msg = "{}...".format(msg[0:7])
|
|
||||||
return bumblebee.output.Widget(full_text="error: {}".format(msg))
|
|
||||||
|
|
||||||
def widget_by_id(self, uid):
|
|
||||||
for widget in self._widgets:
|
|
||||||
if widget.id == uid:
|
|
||||||
return widget
|
|
||||||
return None
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
"""By default, update() is a NOP"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def update_wrapper(self, widgets):
|
|
||||||
if self._next > int(time.time()):
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
self.error = None
|
|
||||||
self.update(self._widgets)
|
|
||||||
except Exception as e:
|
|
||||||
log.error("error updating '{}': {}".format(self.name, str(e)))
|
|
||||||
self.error = str(e)
|
|
||||||
self._next += int(self.parameter("interval", self._default_interval))*self._interval_factor
|
|
||||||
|
|
||||||
def interval_factor(self, factor):
|
|
||||||
self._interval_factor = factor
|
|
||||||
|
|
||||||
def interval(self, intvl):
|
|
||||||
self._default_interval = intvl
|
|
||||||
self._next = int(time.time())
|
|
||||||
|
|
||||||
def update_all(self):
|
|
||||||
self.update_wrapper(self._widgets)
|
|
||||||
|
|
||||||
def has_parameter(self, name):
|
|
||||||
v = self.parameter(name)
|
|
||||||
return v is not None
|
|
||||||
|
|
||||||
def parameter(self, name, default=None):
|
|
||||||
"""Return the config parameter 'name' for this module"""
|
|
||||||
module_name = "{}.{}".format(self.__module__.split(".")[-1], name)
|
|
||||||
name = "{}.{}".format(self.name, name)
|
|
||||||
value = self._config["config"].get(module_name, default)
|
|
||||||
value = self._config["config"].get(name, value)
|
|
||||||
if value == default:
|
|
||||||
try:
|
|
||||||
value = self._configFile.get("module-parameters", name)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return value
|
|
||||||
|
|
||||||
def threshold_state(self, value, warn, crit):
|
|
||||||
if value > float(self.parameter("critical", crit)):
|
|
||||||
return "critical"
|
|
||||||
if value > float(self.parameter("warning", warn)):
|
|
||||||
return "warning"
|
|
||||||
return None
|
|
||||||
|
|
||||||
class Engine(object):
|
|
||||||
"""Engine for driving the application
|
|
||||||
|
|
||||||
This class connects input/output, instantiates all
|
|
||||||
required modules and drives the "event loop"
|
|
||||||
"""
|
|
||||||
def __init__(self, config, output=None, inp=None, theme=None):
|
|
||||||
self._output = output
|
|
||||||
self._config = config
|
|
||||||
self._running = True
|
|
||||||
self._modules = []
|
|
||||||
self.input = inp
|
|
||||||
self._aliases = self._aliases()
|
|
||||||
self.load_modules(config.modules())
|
|
||||||
self._current_module = None
|
|
||||||
self._theme = theme
|
|
||||||
|
|
||||||
if bumblebee.util.asbool(config.get("engine.workspacewheel", "true")):
|
|
||||||
if bumblebee.util.asbool(config.get("engine.workspacewrap", "true")):
|
|
||||||
self.input.register_callback(None, bumblebee.input.WHEEL_UP,
|
|
||||||
"i3-msg workspace prev_on_output")
|
|
||||||
self.input.register_callback(None, bumblebee.input.WHEEL_DOWN,
|
|
||||||
"i3-msg workspace next_on_output")
|
|
||||||
else:
|
|
||||||
self.input.register_callback(None, bumblebee.input.WHEEL_UP,
|
|
||||||
cmd=self._prev_workspace)
|
|
||||||
self.input.register_callback(None, bumblebee.input.WHEEL_DOWN,
|
|
||||||
cmd=self._next_workspace)
|
|
||||||
if bumblebee.util.asbool(config.get("engine.collapsible", "true")):
|
|
||||||
self.input.register_callback(None, bumblebee.input.MIDDLE_MOUSE,
|
|
||||||
cmd=self._toggle_minimize)
|
|
||||||
|
|
||||||
self.input.start()
|
|
||||||
|
|
||||||
def theme(self):
|
|
||||||
return self._theme
|
|
||||||
|
|
||||||
def _toggle_minimize(self, event):
|
|
||||||
for module in self._modules:
|
|
||||||
widget = module.widget_by_id(event["instance"])
|
|
||||||
if widget:
|
|
||||||
log.debug("module {} found - toggle minimize".format(module.id))
|
|
||||||
widget.toggle_minimize()
|
|
||||||
|
|
||||||
def _prev_workspace(self, event):
|
|
||||||
self._change_workspace(-1)
|
|
||||||
|
|
||||||
def _next_workspace(self, event):
|
|
||||||
self._change_workspace(1)
|
|
||||||
|
|
||||||
def _change_workspace(self, amount):
|
|
||||||
try:
|
|
||||||
active_output = None
|
|
||||||
active_index = -1
|
|
||||||
output_workspaces = {}
|
|
||||||
data = json.loads(bumblebee.util.execute("i3-msg -t get_workspaces"))
|
|
||||||
for workspace in data:
|
|
||||||
output_workspaces.setdefault(workspace["output"], []).append(workspace)
|
|
||||||
if workspace["focused"]:
|
|
||||||
active_output = workspace["output"]
|
|
||||||
active_index = len(output_workspaces[workspace["output"]]) - 1
|
|
||||||
if (active_index + amount) < 0:
|
|
||||||
return
|
|
||||||
if (active_index + amount) >= len(output_workspaces[active_output]):
|
|
||||||
return
|
|
||||||
|
|
||||||
while amount != 0:
|
|
||||||
if amount > 0:
|
|
||||||
bumblebee.util.execute("i3-msg workspace next_on_output")
|
|
||||||
amount = amount - 1
|
|
||||||
if amount < 0:
|
|
||||||
bumblebee.util.execute("i3-msg workspace prev_on_output")
|
|
||||||
amount = amount + 1
|
|
||||||
except Exception as e:
|
|
||||||
log.error("failed to change workspace: {}".format(e))
|
|
||||||
|
|
||||||
def modules(self):
|
|
||||||
return self._modules
|
|
||||||
|
|
||||||
def load_modules(self, modules):
|
|
||||||
"""Load specified modules and return them as list"""
|
|
||||||
for module in modules:
|
|
||||||
mod = self._load_module(module["module"], module["name"])
|
|
||||||
self._modules.append(mod)
|
|
||||||
self._register_module_callbacks(mod)
|
|
||||||
return self._modules
|
|
||||||
|
|
||||||
def _register_module_callbacks(self, module):
|
|
||||||
buttons = [
|
|
||||||
{"name": "left-click", "id": bumblebee.input.LEFT_MOUSE},
|
|
||||||
{"name": "middle-click", "id": bumblebee.input.MIDDLE_MOUSE},
|
|
||||||
{"name": "right-click", "id": bumblebee.input.RIGHT_MOUSE},
|
|
||||||
{"name": "wheel-up", "id": bumblebee.input.WHEEL_UP},
|
|
||||||
{"name": "wheel-down", "id": bumblebee.input.WHEEL_DOWN},
|
|
||||||
]
|
|
||||||
for button in buttons:
|
|
||||||
if module.parameter(button["name"], None):
|
|
||||||
self.input.register_callback(obj=module,
|
|
||||||
button=button["id"], cmd=module.parameter(button["name"]))
|
|
||||||
|
|
||||||
def _aliases(self):
|
|
||||||
return {
|
|
||||||
'date': 'datetime',
|
|
||||||
'time': 'datetime',
|
|
||||||
'datetz': 'datetimetz',
|
|
||||||
'timetz': 'datetimetz',
|
|
||||||
'pasink': 'pulseaudio',
|
|
||||||
'pasource': 'pulseaudio',
|
|
||||||
'test-alias': 'test',
|
|
||||||
}
|
|
||||||
|
|
||||||
def _load_module(self, module_name, config_name=None):
|
|
||||||
"""Load specified module and return it as object"""
|
|
||||||
if module_name in self._aliases:
|
|
||||||
config_name is config_name if config_name else module_name
|
|
||||||
module_name = self._aliases[module_name]
|
|
||||||
if config_name is None:
|
|
||||||
config_name = module_name
|
|
||||||
err = None
|
|
||||||
try:
|
|
||||||
module = importlib.import_module("bumblebee.modules.{}".format(module_name))
|
|
||||||
except ImportError as error:
|
|
||||||
err = error
|
|
||||||
log.fatal("failed to import {}: {}".format(module_name, str(error)))
|
|
||||||
if err:
|
|
||||||
raise bumblebee.error.ModuleLoadError("unable to load module {}: {}".format(module_name, str(err)))
|
|
||||||
return getattr(module, "Module")(self, {
|
|
||||||
"name": config_name,
|
|
||||||
"config": self._config
|
|
||||||
})
|
|
||||||
|
|
||||||
def running(self):
|
|
||||||
"""Check whether the event loop is running"""
|
|
||||||
return self._running
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""Stop the event loop"""
|
|
||||||
self._running = False
|
|
||||||
|
|
||||||
def current_module(self):
|
|
||||||
return self._current_module.__module__
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""Start the event loop"""
|
|
||||||
self._output.start()
|
|
||||||
while self.running():
|
|
||||||
self.write_output()
|
|
||||||
if self.running():
|
|
||||||
self.input.wait(float(self._config.get("interval", 1)))
|
|
||||||
|
|
||||||
self._output.stop()
|
|
||||||
self.input.stop()
|
|
||||||
|
|
||||||
def write_output(self):
|
|
||||||
self._output.begin()
|
|
||||||
for module in self._modules:
|
|
||||||
self._current_module = module
|
|
||||||
module.update_wrapper(module.widgets())
|
|
||||||
if module.error is None:
|
|
||||||
for widget in module.widgets():
|
|
||||||
widget.link_module(module)
|
|
||||||
self._output.draw(widget=widget, module=module, engine=self)
|
|
||||||
else:
|
|
||||||
self._output.draw(widget=module.errorWidget(), module=module, engine=self)
|
|
||||||
self._output.flush()
|
|
||||||
self._output.end()
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,15 +0,0 @@
|
||||||
"""Collection of all exceptions raised by this tool"""
|
|
||||||
|
|
||||||
class BaseError(Exception):
|
|
||||||
"""Base class for all exceptions generated by this tool"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ModuleLoadError(BaseError):
|
|
||||||
"""Raised whenever loading a module fails"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ThemeLoadError(BaseError):
|
|
||||||
"""Raised whenever loading a theme fails"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,149 +0,0 @@
|
||||||
"""Input classes"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import uuid
|
|
||||||
import time
|
|
||||||
import select
|
|
||||||
import logging
|
|
||||||
import threading
|
|
||||||
import bumblebee.util
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
LEFT_MOUSE = 1
|
|
||||||
MIDDLE_MOUSE = 2
|
|
||||||
RIGHT_MOUSE = 3
|
|
||||||
WHEEL_UP = 4
|
|
||||||
WHEEL_DOWN = 5
|
|
||||||
|
|
||||||
def is_terminated():
|
|
||||||
for thread in threading.enumerate():
|
|
||||||
if thread.name == "MainThread" and not thread.is_alive():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def read_input(inp):
|
|
||||||
"""Read i3bar input and execute callbacks"""
|
|
||||||
poll = select.poll()
|
|
||||||
poll.register(sys.stdin.fileno(), select.POLLIN)
|
|
||||||
log.debug("starting click event processing")
|
|
||||||
while inp.running:
|
|
||||||
if is_terminated():
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
events = poll.poll(1000)
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
for fileno, event in events:
|
|
||||||
line = "["
|
|
||||||
while line.startswith("["):
|
|
||||||
line = sys.stdin.readline().strip(",").strip()
|
|
||||||
log.debug("new event: {}".format(line))
|
|
||||||
inp.has_event = True
|
|
||||||
try:
|
|
||||||
event = json.loads(line)
|
|
||||||
if "instance" in event:
|
|
||||||
inp.callback(event)
|
|
||||||
inp.redraw()
|
|
||||||
else:
|
|
||||||
log.debug("field 'instance' missing in input, not processing the event")
|
|
||||||
except ValueError as e:
|
|
||||||
log.debug("failed to parse event: {}".format(e))
|
|
||||||
log.debug("exiting click event processing")
|
|
||||||
poll.unregister(sys.stdin.fileno())
|
|
||||||
inp.has_event = True
|
|
||||||
inp.clean_exit = True
|
|
||||||
|
|
||||||
class I3BarInput(object):
|
|
||||||
"""Process incoming events from the i3bar"""
|
|
||||||
def __init__(self):
|
|
||||||
self.running = True
|
|
||||||
self._callbacks = {}
|
|
||||||
self.clean_exit = False
|
|
||||||
self.global_id = str(uuid.uuid4())
|
|
||||||
self.need_event = False
|
|
||||||
self.has_event = False
|
|
||||||
self._condition = threading.Condition()
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""Start asynchronous input processing"""
|
|
||||||
self.has_event = False
|
|
||||||
self.running = True
|
|
||||||
self._condition.acquire()
|
|
||||||
self._thread = threading.Thread(target=read_input, args=(self,))
|
|
||||||
self._thread.start()
|
|
||||||
|
|
||||||
def redraw(self):
|
|
||||||
self._condition.acquire()
|
|
||||||
self._condition.notify()
|
|
||||||
self._condition.release()
|
|
||||||
|
|
||||||
def alive(self):
|
|
||||||
"""Check whether the input processing is still active"""
|
|
||||||
return self._thread.is_alive()
|
|
||||||
|
|
||||||
def wait(self, timeout):
|
|
||||||
self._condition.wait(timeout)
|
|
||||||
|
|
||||||
def _wait(self):
|
|
||||||
while not self.has_event:
|
|
||||||
time.sleep(0.1)
|
|
||||||
self.has_event = False
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""Stop asynchronous input processing"""
|
|
||||||
self._condition.release()
|
|
||||||
if self.need_event:
|
|
||||||
self._wait()
|
|
||||||
self.running = False
|
|
||||||
self._thread.join()
|
|
||||||
return self.clean_exit
|
|
||||||
|
|
||||||
def _uuidstr(self, name, button):
|
|
||||||
return "{}::{}".format(name, button)
|
|
||||||
|
|
||||||
def _uid(self, obj, button):
|
|
||||||
uid = self.global_id
|
|
||||||
if obj:
|
|
||||||
uid = obj.id
|
|
||||||
return self._uuidstr(uid, button)
|
|
||||||
|
|
||||||
def deregister_callbacks(self, obj):
|
|
||||||
to_delete = []
|
|
||||||
uid = obj.id if obj else self.global_id
|
|
||||||
for key in self._callbacks:
|
|
||||||
if uid in key:
|
|
||||||
to_delete.append(key)
|
|
||||||
for key in to_delete:
|
|
||||||
del self._callbacks[key]
|
|
||||||
|
|
||||||
def register_callback(self, obj, button, cmd):
|
|
||||||
"""Register a callback function or system call"""
|
|
||||||
uid = self._uid(obj, button)
|
|
||||||
if uid not in self._callbacks:
|
|
||||||
self._callbacks[uid] = {}
|
|
||||||
self._callbacks[uid] = cmd
|
|
||||||
|
|
||||||
def callback(self, event):
|
|
||||||
"""Execute callback action for an incoming event"""
|
|
||||||
button = event["button"]
|
|
||||||
|
|
||||||
cmd = self._callbacks.get(self._uuidstr(self.global_id, button), None)
|
|
||||||
cmd = self._callbacks.get(self._uuidstr(event["name"], button), cmd)
|
|
||||||
cmd = self._callbacks.get(self._uuidstr(event["instance"], button), cmd)
|
|
||||||
|
|
||||||
if cmd is None:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
if callable(cmd):
|
|
||||||
cmd(event)
|
|
||||||
else:
|
|
||||||
bumblebee.util.execute(cmd, False)
|
|
||||||
except Exception:
|
|
||||||
# fall back to global default
|
|
||||||
if not "__fallback" in event:
|
|
||||||
return self.callback({"name": None, "instance": None, "__fallback": True, "button": event["button"]})
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,42 +0,0 @@
|
||||||
"""get volume level
|
|
||||||
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.volume)
|
|
||||||
)
|
|
||||||
self._level = "0"
|
|
||||||
self._muted = True
|
|
||||||
device = self.parameter("device", "Master,0")
|
|
||||||
self._cmdString = "amixer get {}".format(device)
|
|
||||||
|
|
||||||
def volume(self, widget):
|
|
||||||
m = re.search(r'([\d]+)\%', self._level)
|
|
||||||
self._muted = True
|
|
||||||
if m:
|
|
||||||
if m.group(1) != "0" and "[on]" in self._level:
|
|
||||||
self._muted = False
|
|
||||||
return "{}%".format(m.group(1))
|
|
||||||
else:
|
|
||||||
return "0%"
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
level = ""
|
|
||||||
try:
|
|
||||||
level = bumblebee.util.execute(self._cmdString)
|
|
||||||
except Exception as e:
|
|
||||||
level = ""
|
|
||||||
|
|
||||||
self._level = level
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if self._muted:
|
|
||||||
return ["warning", "muted"]
|
|
||||||
return ["unmuted"]
|
|
|
@ -1,81 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays APT package update information (<to upgrade>/<to remove >)
|
|
||||||
Requires the following debian packages:
|
|
||||||
* python-parse
|
|
||||||
* aptitude
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import threading
|
|
||||||
from parse import *
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
APT_CHECK_PATH = ("aptitude full-upgrade --simulate --assume-yes")
|
|
||||||
PATTERN = "{} packages upgraded, {} newly installed, {} to remove and {} not upgraded."
|
|
||||||
|
|
||||||
def parse_result(to_parse):
|
|
||||||
# We want to line with the iforamtion about package upgrade
|
|
||||||
line_to_parse = to_parse.split("\n")[-4]
|
|
||||||
|
|
||||||
result = parse(PATTERN, line_to_parse)
|
|
||||||
|
|
||||||
return int(result[0]), int(result[2])
|
|
||||||
|
|
||||||
def get_apt_check_info(widget):
|
|
||||||
try:
|
|
||||||
res = bumblebee.util.execute(APT_CHECK_PATH)
|
|
||||||
widget.set("error", None)
|
|
||||||
except (RuntimeError, FileNotFoundError) as e:
|
|
||||||
widget.set("error", "unable to query APT: {}".format(e))
|
|
||||||
return
|
|
||||||
|
|
||||||
to_upgrade = 0
|
|
||||||
to_remove = 0
|
|
||||||
try:
|
|
||||||
to_upgrade, to_remove = parse_result(res)
|
|
||||||
except e:
|
|
||||||
widget.set("error", "parse error: {}".format(e))
|
|
||||||
return
|
|
||||||
|
|
||||||
widget.set("to_upgrade", to_upgrade)
|
|
||||||
widget.set("to_remove", to_remove)
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widget = bumblebee.output.Widget(full_text=self.updates)
|
|
||||||
super(Module, self).__init__(engine, config, widget)
|
|
||||||
self.interval_factor(60)
|
|
||||||
self.interval(30)
|
|
||||||
|
|
||||||
def updates(self, widget):
|
|
||||||
result = []
|
|
||||||
if widget.get("error"):
|
|
||||||
return widget.get("error")
|
|
||||||
for t in ["to_upgrade", "to_remove"]:
|
|
||||||
result.append(str(widget.get(t, 0)))
|
|
||||||
return "/".join(result)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
thread = threading.Thread(target=get_apt_check_info, args=(widgets[0],))
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
cnt = 0
|
|
||||||
ret = "good"
|
|
||||||
for t in ["to_upgrade", "to_remove"]:
|
|
||||||
cnt += widget.get(t, 0)
|
|
||||||
if cnt > 50:
|
|
||||||
ret = "critical"
|
|
||||||
elif cnt > 0:
|
|
||||||
ret = "warning"
|
|
||||||
if widget.get("error"):
|
|
||||||
ret = "critical"
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,47 +0,0 @@
|
||||||
"""Check updates to Arch Linux.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widget = bumblebee.output.Widget(full_text=self.utilization)
|
|
||||||
super(Module, self).__init__(engine, config, widget)
|
|
||||||
self.packages = self.check_updates()
|
|
||||||
|
|
||||||
def check_updates(self):
|
|
||||||
p = subprocess.Popen(
|
|
||||||
"checkupdates", stdout=subprocess.PIPE, shell=True)
|
|
||||||
|
|
||||||
p_status = p.wait()
|
|
||||||
|
|
||||||
if p_status == 0:
|
|
||||||
(output, err) = p.communicate()
|
|
||||||
|
|
||||||
output = output.decode('utf-8')
|
|
||||||
packages = output.split('\n')
|
|
||||||
packages.pop()
|
|
||||||
|
|
||||||
return len(packages)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _format(self):
|
|
||||||
return self.parameter("format", "Update Arch: {}")
|
|
||||||
|
|
||||||
def utilization(self, widget):
|
|
||||||
return self._format.format(self.packages)
|
|
||||||
|
|
||||||
def hidden(self):
|
|
||||||
return self.check_updates() == 0
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self.packages = self.check_updates()
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
return self.threshold_state(self.packages, 1, 100)
|
|
|
@ -1,270 +0,0 @@
|
||||||
# UPowerManger Class Copyright (C) 2017 Oscar Svensson (wogscpar) under MIT licence from upower-python
|
|
||||||
|
|
||||||
"""Displays battery status, remaining percentage and charging information.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* battery-upower.warning : Warning threshold in % of remaining charge (defaults to 20)
|
|
||||||
* battery-upower.critical : Critical threshold in % of remaining charge (defaults to 10)
|
|
||||||
* battery-upower.showremaining : If set to true (default) shows the remaining time until the batteries are completely discharged
|
|
||||||
"""
|
|
||||||
|
|
||||||
import dbus
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.util
|
|
||||||
|
|
||||||
class UPowerManager():
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.UPOWER_NAME = "org.freedesktop.UPower"
|
|
||||||
self.UPOWER_PATH = "/org/freedesktop/UPower"
|
|
||||||
|
|
||||||
self.DBUS_PROPERTIES = "org.freedesktop.DBus.Properties"
|
|
||||||
self.bus = dbus.SystemBus()
|
|
||||||
|
|
||||||
def detect_devices(self):
|
|
||||||
upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH)
|
|
||||||
upower_interface = dbus.Interface(upower_proxy, self.UPOWER_NAME)
|
|
||||||
|
|
||||||
devices = upower_interface.EnumerateDevices()
|
|
||||||
return devices
|
|
||||||
|
|
||||||
def get_display_device(self):
|
|
||||||
upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH)
|
|
||||||
upower_interface = dbus.Interface(upower_proxy, self.UPOWER_NAME)
|
|
||||||
|
|
||||||
dispdev = upower_interface.GetDisplayDevice()
|
|
||||||
return dispdev
|
|
||||||
|
|
||||||
def get_critical_action(self):
|
|
||||||
upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH)
|
|
||||||
upower_interface = dbus.Interface(upower_proxy, self.UPOWER_NAME)
|
|
||||||
|
|
||||||
critical_action = upower_interface.GetCriticalAction()
|
|
||||||
return critical_action
|
|
||||||
|
|
||||||
def get_device_percentage(self, battery):
|
|
||||||
battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery)
|
|
||||||
battery_proxy_interface = dbus.Interface(battery_proxy, self.DBUS_PROPERTIES)
|
|
||||||
|
|
||||||
return battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Percentage")
|
|
||||||
|
|
||||||
def get_full_device_information(self, battery):
|
|
||||||
battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery)
|
|
||||||
battery_proxy_interface = dbus.Interface(battery_proxy, self.DBUS_PROPERTIES)
|
|
||||||
|
|
||||||
hasHistory = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "HasHistory")
|
|
||||||
hasStatistics = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "HasStatistics")
|
|
||||||
isPresent = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "IsPresent")
|
|
||||||
isRechargable = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "IsRechargeable")
|
|
||||||
online = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Online")
|
|
||||||
powersupply = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "PowerSupply")
|
|
||||||
capacity = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Capacity")
|
|
||||||
energy = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Energy")
|
|
||||||
energyempty = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "EnergyEmpty")
|
|
||||||
energyfull = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "EnergyFull")
|
|
||||||
energyfulldesign = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "EnergyFullDesign")
|
|
||||||
energyrate = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "EnergyRate")
|
|
||||||
luminosity = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Luminosity")
|
|
||||||
percentage = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Percentage")
|
|
||||||
temperature = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Temperature")
|
|
||||||
voltage = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Voltage")
|
|
||||||
timetoempty = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "TimeToEmpty")
|
|
||||||
timetofull = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "TimeToFull")
|
|
||||||
iconname = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "IconName")
|
|
||||||
model = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Model")
|
|
||||||
nativepath = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "NativePath")
|
|
||||||
serial = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Serial")
|
|
||||||
vendor = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Vendor")
|
|
||||||
state = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "State")
|
|
||||||
technology = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Technology")
|
|
||||||
battype = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Type")
|
|
||||||
warninglevel = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "WarningLevel")
|
|
||||||
updatetime = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "UpdateTime")
|
|
||||||
|
|
||||||
information_table = {
|
|
||||||
'HasHistory': hasHistory,
|
|
||||||
'HasStatistics': hasStatistics,
|
|
||||||
'IsPresent': isPresent,
|
|
||||||
'IsRechargeable': isRechargable,
|
|
||||||
'Online': online,
|
|
||||||
'PowerSupply': powersupply,
|
|
||||||
'Capacity': capacity,
|
|
||||||
'Energy': energy,
|
|
||||||
'EnergyEmpty': energyempty,
|
|
||||||
'EnergyFull': energyfull,
|
|
||||||
'EnergyFullDesign': energyfulldesign,
|
|
||||||
'EnergyRate': energyrate,
|
|
||||||
'Luminosity': luminosity,
|
|
||||||
'Percentage': percentage,
|
|
||||||
'Temperature': temperature,
|
|
||||||
'Voltage': voltage,
|
|
||||||
'TimeToEmpty': timetoempty,
|
|
||||||
'TimeToFull': timetofull,
|
|
||||||
'IconName': iconname,
|
|
||||||
'Model': model,
|
|
||||||
'NativePath': nativepath,
|
|
||||||
'Serial': serial,
|
|
||||||
'Vendor': vendor,
|
|
||||||
'State': state,
|
|
||||||
'Technology': technology,
|
|
||||||
'Type': battype,
|
|
||||||
'WarningLevel': warninglevel,
|
|
||||||
'UpdateTime': updatetime
|
|
||||||
}
|
|
||||||
|
|
||||||
return information_table
|
|
||||||
|
|
||||||
def is_lid_present(self):
|
|
||||||
upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH)
|
|
||||||
upower_interface = dbus.Interface(upower_proxy, self.DBUS_PROPERTIES)
|
|
||||||
|
|
||||||
is_lid_present = bool(upower_interface.Get(self.UPOWER_NAME, 'LidIsPresent'))
|
|
||||||
return is_lid_present
|
|
||||||
|
|
||||||
def is_lid_closed(self):
|
|
||||||
upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH)
|
|
||||||
upower_interface = dbus.Interface(upower_proxy, self.DBUS_PROPERTIES)
|
|
||||||
|
|
||||||
is_lid_closed = bool(upower_interface.Get(self.UPOWER_NAME, 'LidIsClosed'))
|
|
||||||
return is_lid_closed
|
|
||||||
|
|
||||||
def on_battery(self):
|
|
||||||
upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH)
|
|
||||||
upower_interface = dbus.Interface(upower_proxy, self.DBUS_PROPERTIES)
|
|
||||||
|
|
||||||
on_battery = bool(upower_interface.Get(self.UPOWER_NAME, 'OnBattery'))
|
|
||||||
return on_battery
|
|
||||||
|
|
||||||
def has_wakeup_capabilities(self):
|
|
||||||
upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH + "/Wakeups")
|
|
||||||
upower_interface = dbus.Interface(upower_proxy, self.DBUS_PROPERTIES)
|
|
||||||
|
|
||||||
has_wakeup_capabilities = bool(upower_interface.Get(self.UPOWER_NAME + '.Wakeups', 'HasCapability'))
|
|
||||||
return has_wakeup_capabilities
|
|
||||||
|
|
||||||
def get_wakeups_data(self):
|
|
||||||
upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH + "/Wakeups")
|
|
||||||
upower_interface = dbus.Interface(upower_proxy, self.UPOWER_NAME + '.Wakeups')
|
|
||||||
|
|
||||||
data = upower_interface.GetData()
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_wakeups_total(self):
|
|
||||||
upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH + "/Wakeups")
|
|
||||||
upower_interface = dbus.Interface(upower_proxy, self.UPOWER_NAME + '.Wakeups')
|
|
||||||
|
|
||||||
data = upower_interface.GetTotal()
|
|
||||||
return data
|
|
||||||
|
|
||||||
def is_loading(self, battery):
|
|
||||||
battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery)
|
|
||||||
battery_proxy_interface = dbus.Interface(battery_proxy, self.DBUS_PROPERTIES)
|
|
||||||
|
|
||||||
state = int(battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "State"))
|
|
||||||
|
|
||||||
if (state == 1):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_state(self, battery):
|
|
||||||
battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery)
|
|
||||||
battery_proxy_interface = dbus.Interface(battery_proxy, self.DBUS_PROPERTIES)
|
|
||||||
|
|
||||||
state = int(battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "State"))
|
|
||||||
|
|
||||||
if (state == 0):
|
|
||||||
return "Unknown"
|
|
||||||
elif (state == 1):
|
|
||||||
return "Loading"
|
|
||||||
elif (state == 2):
|
|
||||||
return "Discharging"
|
|
||||||
elif (state == 3):
|
|
||||||
return "Empty"
|
|
||||||
elif (state == 4):
|
|
||||||
return "Fully charged"
|
|
||||||
elif (state == 5):
|
|
||||||
return "Pending charge"
|
|
||||||
elif (state == 6):
|
|
||||||
return "Pending discharge"
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config, bumblebee.output.Widget(full_text=self.capacity))
|
|
||||||
try:
|
|
||||||
self.power = UPowerManager()
|
|
||||||
self.device = self.power.get_display_device()
|
|
||||||
except Exception as e:
|
|
||||||
logging.exception("unable to get battery display device: {}".format(str(e)))
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="gnome-power-statistics")
|
|
||||||
|
|
||||||
self._showremaining = bumblebee.util.asbool(
|
|
||||||
self.parameter("showremaining", True))
|
|
||||||
|
|
||||||
def capacity(self, widget):
|
|
||||||
widget.set("capacity", -1)
|
|
||||||
widget.set("ac", False)
|
|
||||||
output = "n/a"
|
|
||||||
try:
|
|
||||||
capacity = int(self.power.get_device_percentage(self.device))
|
|
||||||
capacity = capacity if capacity < 100 else 100
|
|
||||||
widget.set("capacity", capacity)
|
|
||||||
output = "{}%".format(capacity)
|
|
||||||
widget.set("theme.minwidth", "100%")
|
|
||||||
except Exception as e:
|
|
||||||
logging.exception("unable to get battery capacity: {}".format(str(e)))
|
|
||||||
|
|
||||||
if self._showremaining:
|
|
||||||
try:
|
|
||||||
p = self.power # an alias to make each line of code shorter
|
|
||||||
proxy = p.bus.get_object(p.UPOWER_NAME, self.device)
|
|
||||||
interface = dbus.Interface(proxy, p.DBUS_PROPERTIES)
|
|
||||||
state = int(interface.Get(p.UPOWER_NAME+".Device", "State"))
|
|
||||||
# state: 1 => charging, 2 => discharging, other => don't care
|
|
||||||
remain = int(interface.Get(
|
|
||||||
p.UPOWER_NAME+".Device", ["TimeToFull", "TimeToEmpty"][state-1]))
|
|
||||||
remain = bumblebee.util.durationfmt(remain, shorten=True, suffix=True)
|
|
||||||
output = "{} {}".format(output, remain)
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
logging.exception("unable to get battery remaining time: {}".format(str(e)))
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
state = []
|
|
||||||
capacity = widget.get("capacity")
|
|
||||||
if capacity < 0:
|
|
||||||
return ["critical", "unknown"]
|
|
||||||
|
|
||||||
if capacity < int(self.parameter("critical", 10)):
|
|
||||||
state.append("critical")
|
|
||||||
elif capacity < int(self.parameter("warning", 20)):
|
|
||||||
state.append("warning")
|
|
||||||
|
|
||||||
if widget.get("ac"):
|
|
||||||
state.append("AC")
|
|
||||||
else:
|
|
||||||
charge = "Unknown"
|
|
||||||
try:
|
|
||||||
charge = self.power.get_state(self.device)
|
|
||||||
except Exception as e:
|
|
||||||
logging.exception("unable to get charge value: {}".format(str(e)))
|
|
||||||
if charge == "Discharging":
|
|
||||||
state.append("discharging-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i - capacity))))
|
|
||||||
elif charge == "Unknown":
|
|
||||||
state.append("unknown-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i - capacity))))
|
|
||||||
else:
|
|
||||||
if capacity > 95:
|
|
||||||
state.append("charged")
|
|
||||||
else:
|
|
||||||
state.append("charging")
|
|
||||||
|
|
||||||
return state
|
|
|
@ -1,140 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays battery status, remaining percentage and charging information.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* battery.device : Comma-separated list of battery devices to read information from (defaults to auto for auto-detection)
|
|
||||||
* battery.warning : Warning threshold in % of remaining charge (defaults to 20)
|
|
||||||
* battery.critical : Critical threshold in % of remaining charge (defaults to 10)
|
|
||||||
* battery.showdevice : If set to "true", add the device name to the widget (defaults to False)
|
|
||||||
* battery.decorate : If set to "false", hides additional icons (charging, etc.) (defaults to True)
|
|
||||||
* battery.showpowerconsumption: If set to "true", show current power consumption (defaults to False)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import glob
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.util
|
|
||||||
|
|
||||||
try:
|
|
||||||
import power
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widgets = []
|
|
||||||
super(Module, self).__init__(engine, config, widgets)
|
|
||||||
self._batteries = self.parameter("device", "auto").split(",")
|
|
||||||
if self._batteries[0] == "auto":
|
|
||||||
self._batteries = glob.glob("/sys/class/power_supply/BAT*")
|
|
||||||
else:
|
|
||||||
self._batteries = ["/sys/class/power_supply/{}".format(b) for b in self._batteries]
|
|
||||||
if len(self._batteries) == 0:
|
|
||||||
self._batteries = ["/sys/class/power_supply/BAT0"]
|
|
||||||
self.update(widgets)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="gnome-power-statistics")
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
new_widgets = []
|
|
||||||
for path in self._batteries:
|
|
||||||
widget = self.widget(path)
|
|
||||||
if not widget:
|
|
||||||
widget = bumblebee.output.Widget(full_text=self.capacity, name=path)
|
|
||||||
new_widgets.append(widget)
|
|
||||||
self.capacity(widget)
|
|
||||||
while len(widgets) > 0: del widgets[0]
|
|
||||||
for widget in new_widgets:
|
|
||||||
if bumblebee.util.asbool(self.parameter("decorate", True)) == False:
|
|
||||||
widget.set("theme.exclude", "suffix")
|
|
||||||
widgets.append(widget)
|
|
||||||
self._widgets = widgets
|
|
||||||
|
|
||||||
def remaining(self):
|
|
||||||
estimate = 0.0
|
|
||||||
try:
|
|
||||||
estimate = power.PowerManagement().get_time_remaining_estimate()
|
|
||||||
# do not show remaining if on AC
|
|
||||||
if estimate == power.common.TIME_REMAINING_UNLIMITED:
|
|
||||||
return None
|
|
||||||
if estimate == power.common.TIME_REMAINING_UNKNOWN:
|
|
||||||
return ""
|
|
||||||
except Exception:
|
|
||||||
return ""
|
|
||||||
return bumblebee.util.durationfmt(estimate*60, shorten=True, suffix=True) # estimate is in minutes
|
|
||||||
|
|
||||||
def capacity(self, widget):
|
|
||||||
widget.set("capacity", -1)
|
|
||||||
widget.set("ac", False)
|
|
||||||
if not os.path.exists(widget.name):
|
|
||||||
widget.set("capacity", 100)
|
|
||||||
widget.set("ac", True)
|
|
||||||
return "ac"
|
|
||||||
capacity = 100
|
|
||||||
try:
|
|
||||||
with open("{}/capacity".format(widget.name)) as f:
|
|
||||||
capacity = int(f.read())
|
|
||||||
except IOError:
|
|
||||||
return "n/a"
|
|
||||||
|
|
||||||
capacity = capacity if capacity < 100 else 100
|
|
||||||
widget.set("capacity", capacity)
|
|
||||||
|
|
||||||
# Read power conumption
|
|
||||||
if bumblebee.util.asbool(self.parameter("showpowerconsumption", False)):
|
|
||||||
r=open(widget.name + '/power_now', "r")
|
|
||||||
output = "{}% ({})".format(capacity,str(int(r.read())/1000000) + "W")
|
|
||||||
else:
|
|
||||||
output = "{}%".format(capacity)
|
|
||||||
|
|
||||||
widget.set("theme.minwidth", "100%")
|
|
||||||
if bumblebee.util.asbool(self.parameter("showremaining", True))\
|
|
||||||
and self.getCharge(widget) == "Discharging":
|
|
||||||
output = "{} {}".format(output, self.remaining())
|
|
||||||
|
|
||||||
if bumblebee.util.asbool(self.parameter("showdevice", False)):
|
|
||||||
output = "{} ({})".format(output, os.path.basename(widget.name))
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
state = []
|
|
||||||
capacity = widget.get("capacity")
|
|
||||||
|
|
||||||
if capacity < 0:
|
|
||||||
return ["critical", "unknown"]
|
|
||||||
|
|
||||||
if capacity < int(self.parameter("critical", 10)):
|
|
||||||
state.append("critical")
|
|
||||||
elif capacity < int(self.parameter("warning", 20)):
|
|
||||||
state.append("warning")
|
|
||||||
|
|
||||||
if widget.get("ac"):
|
|
||||||
state.append("AC")
|
|
||||||
else:
|
|
||||||
charge = self.getCharge(widget)
|
|
||||||
if charge == "Discharging":
|
|
||||||
state.append("discharging-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i-capacity))))
|
|
||||||
elif charge == "Unknown":
|
|
||||||
state.append("unknown-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i-capacity))))
|
|
||||||
else:
|
|
||||||
if capacity > 95:
|
|
||||||
state.append("charged")
|
|
||||||
else:
|
|
||||||
state.append("charging")
|
|
||||||
|
|
||||||
return state
|
|
||||||
|
|
||||||
def getCharge(self, widget):
|
|
||||||
charge = ""
|
|
||||||
try:
|
|
||||||
with open("{}/status".format(widget.name)) as f:
|
|
||||||
charge = f.read().strip()
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
return charge
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,130 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays battery status, remaining percentage and charging information.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* battery.device : Comma-separated list of battery devices to read information from (defaults to auto for auto-detection)
|
|
||||||
* battery.warning : Warning threshold in % of remaining charge (defaults to 20)
|
|
||||||
* battery.critical : Critical threshold in % of remaining charge (defaults to 10)
|
|
||||||
* batter.showremaining : If set to true (default) shows the remaining time until the batteries are completely discharged
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import glob
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.util
|
|
||||||
|
|
||||||
try:
|
|
||||||
import power
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
self._batteries = []
|
|
||||||
try:
|
|
||||||
for battery in os.listdir('/sys/class/power_supply/'):
|
|
||||||
if not any(i in battery for i in ['AC', 'hidpp']):
|
|
||||||
self._batteries.append("/sys/class/power_supply/" + battery)
|
|
||||||
except Exception as e:
|
|
||||||
logging.exception("unable to detect batteries: {}".format(str(e)))
|
|
||||||
|
|
||||||
super(Module, self).__init__(engine, config, bumblebee.output.Widget(full_text=self.capacity))
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="gnome-power-statistics")
|
|
||||||
|
|
||||||
def remaining(self):
|
|
||||||
estimate = 0.0
|
|
||||||
power_now = 0.0
|
|
||||||
try:
|
|
||||||
estimate = power.PowerManagement().get_time_remaining_estimate()
|
|
||||||
# do not show remaining if on AC
|
|
||||||
if estimate == power.common.TIME_REMAINING_UNLIMITED:
|
|
||||||
return None
|
|
||||||
elif estimate == power.common.TIME_REMAINING_UNKNOWN:
|
|
||||||
return ""
|
|
||||||
except Exception:
|
|
||||||
return ""
|
|
||||||
return bumblebee.util.durationfmt(estimate*60, shorten=True, suffix=True) # estimate is in minutes
|
|
||||||
|
|
||||||
def capacity(self, widget):
|
|
||||||
widget.set("capacity", -1)
|
|
||||||
widget.set("ac", False)
|
|
||||||
capacity = 100
|
|
||||||
self.energy_now = 0
|
|
||||||
self.energy_full = 0
|
|
||||||
errors = 0
|
|
||||||
for path in self._batteries:
|
|
||||||
try:
|
|
||||||
with open("{}/energy_full".format(path)) as f:
|
|
||||||
self.energy_full += int(f.read())
|
|
||||||
with open("{}/energy_now".format(path)) as o:
|
|
||||||
self.energy_now += int(o.read())
|
|
||||||
except IOError:
|
|
||||||
return "n/a"
|
|
||||||
except Exception:
|
|
||||||
errors += 1
|
|
||||||
|
|
||||||
if errors == len(self._batteries):
|
|
||||||
return "n/a"
|
|
||||||
|
|
||||||
capacity = int( float(self.energy_now) / float(self.energy_full) * 100.0)
|
|
||||||
capacity = capacity if capacity < 100 else 100
|
|
||||||
widget.set("capacity", capacity)
|
|
||||||
output = "{}%".format(capacity)
|
|
||||||
widget.set("theme.minwidth", "100%")
|
|
||||||
|
|
||||||
if bumblebee.util.asbool(self.parameter("showremaining", True))\
|
|
||||||
and self.getCharge(widget) == "Discharging":
|
|
||||||
output = "{} {}".format(output, self.remaining())
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
state = []
|
|
||||||
capacity = widget.get("capacity")
|
|
||||||
|
|
||||||
if capacity < 0:
|
|
||||||
return ["critical", "unknown"]
|
|
||||||
|
|
||||||
if capacity < int(self.parameter("critical", 10)):
|
|
||||||
state.append("critical")
|
|
||||||
elif capacity < int(self.parameter("warning", 20)):
|
|
||||||
state.append("warning")
|
|
||||||
|
|
||||||
if widget.get("ac"):
|
|
||||||
state.append("AC")
|
|
||||||
else:
|
|
||||||
charge = self.getCharge(widget)
|
|
||||||
if charge == "Discharging":
|
|
||||||
state.append("discharging-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i-capacity))))
|
|
||||||
elif charge == "Unknown":
|
|
||||||
state.append("unknown-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i-capacity))))
|
|
||||||
else:
|
|
||||||
if capacity > 95:
|
|
||||||
state.append("charged")
|
|
||||||
else:
|
|
||||||
state.append("charging")
|
|
||||||
|
|
||||||
return state
|
|
||||||
|
|
||||||
def getCharge(self, widget):
|
|
||||||
charge = ""
|
|
||||||
charge_list = []
|
|
||||||
for x in range(len(self._batteries)):
|
|
||||||
try:
|
|
||||||
with open("{}/status".format(self._batteries[x])) as f:
|
|
||||||
charge_list.append(f.read().strip())
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
for x in range(len(charge_list)):
|
|
||||||
if charge_list[x] == "Discharging":
|
|
||||||
charge = charge_list[x]
|
|
||||||
break
|
|
||||||
return charge
|
|
|
@ -1,131 +0,0 @@
|
||||||
"""Displays bluetooth status (Bluez). Left mouse click launches manager app,
|
|
||||||
right click toggles bluetooth. Needs dbus-send to toggle bluetooth state.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* bluetooth.device : the device to read state from (default is hci0)
|
|
||||||
* bluetooth.manager : application to launch on click (blueman-manager)
|
|
||||||
* bluetooth.dbus_destination : dbus destination (defaults to org.blueman.Mechanism)
|
|
||||||
* bluetooth.dbus_destination_path : dbus destination path (defaults to /)
|
|
||||||
* bluetooth.right_click_popup : use popup menu when right-clicked (defaults to True)
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.popup
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
"""Bluetooth module."""
|
|
||||||
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
"""Initialize."""
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(
|
|
||||||
full_text=self.status))
|
|
||||||
|
|
||||||
device = self.parameter("device", "hci0")
|
|
||||||
self.manager = self.parameter("manager", "blueman-manager")
|
|
||||||
self._path = "/sys/class/bluetooth/{}".format(device)
|
|
||||||
self._status = "Off"
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self.manager)
|
|
||||||
|
|
||||||
# determine whether to use pop-up menu or simply toggle the device on/off
|
|
||||||
right_click_popup = bumblebee.util.asbool(
|
|
||||||
self.parameter("right_click_popup", True))
|
|
||||||
|
|
||||||
if right_click_popup:
|
|
||||||
engine.input.register_callback(self,
|
|
||||||
button=bumblebee.input.RIGHT_MOUSE,
|
|
||||||
cmd=self.popup)
|
|
||||||
else:
|
|
||||||
engine.input.register_callback(self,
|
|
||||||
button=bumblebee.input.RIGHT_MOUSE,
|
|
||||||
cmd=self._toggle)
|
|
||||||
|
|
||||||
def status(self, widget):
|
|
||||||
"""Get status."""
|
|
||||||
return self._status
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
"""Update current state."""
|
|
||||||
if not os.path.exists(self._path):
|
|
||||||
self._status = "?"
|
|
||||||
return
|
|
||||||
|
|
||||||
# search for whichever rfkill directory available
|
|
||||||
try:
|
|
||||||
dirnames = next(os.walk(self._path))[1]
|
|
||||||
for dirname in dirnames:
|
|
||||||
m = re.match(r"rfkill[0-9]+", dirname)
|
|
||||||
if m is not None:
|
|
||||||
with open(os.path.join(self._path,
|
|
||||||
dirname,
|
|
||||||
'state'), 'r') as f:
|
|
||||||
state = int(f.read())
|
|
||||||
if state == 1:
|
|
||||||
self._status = "On"
|
|
||||||
else:
|
|
||||||
self._status = "Off"
|
|
||||||
return
|
|
||||||
|
|
||||||
except IOError:
|
|
||||||
self._status = "?"
|
|
||||||
|
|
||||||
def manager(self, widget):
|
|
||||||
"""Launch manager."""
|
|
||||||
bumblebee.util.execute(self.manager)
|
|
||||||
|
|
||||||
def popup(self, widget):
|
|
||||||
"""Show a popup menu."""
|
|
||||||
menu = bumblebee.popup.PopupMenu()
|
|
||||||
if self._status == "On":
|
|
||||||
menu.add_menuitem('Disable Bluetooth')
|
|
||||||
elif self._status == "Off":
|
|
||||||
menu.add_menuitem('Enable Bluetooth')
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
# show menu and get return code
|
|
||||||
ret = menu.show(widget)
|
|
||||||
if ret == 0:
|
|
||||||
# first (and only) item selected.
|
|
||||||
self._toggle()
|
|
||||||
|
|
||||||
def _toggle(self, widget=None):
|
|
||||||
"""Toggle bluetooth state."""
|
|
||||||
if self._status == "On":
|
|
||||||
state = "false"
|
|
||||||
else:
|
|
||||||
state = "true"
|
|
||||||
|
|
||||||
dst = self.parameter("dbus_destination", "org.blueman.Mechanism")
|
|
||||||
dst_path = self.parameter("dbus_destination_path", "/")
|
|
||||||
|
|
||||||
cmd = "dbus-send --system --print-reply --dest={}"\
|
|
||||||
" {} org.blueman.Mechanism.SetRfkillState"\
|
|
||||||
" boolean:{}".format(dst, dst_path, state)
|
|
||||||
|
|
||||||
logging.debug('bt: toggling bluetooth')
|
|
||||||
bumblebee.util.execute(cmd)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
"""Get current state."""
|
|
||||||
state = []
|
|
||||||
|
|
||||||
if self._status == "?":
|
|
||||||
state = ["unknown"]
|
|
||||||
elif self._status == "On":
|
|
||||||
state = ["ON"]
|
|
||||||
else:
|
|
||||||
state = ["OFF"]
|
|
||||||
|
|
||||||
return state
|
|
|
@ -1,63 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the brightness of a display
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* brightness.step: The amount of increase/decrease on scroll in % (defaults to 2)
|
|
||||||
* brightness.device_path: The device path (defaults to /sys/class/backlight/intel_backlight), can contain wildcards (in this case, the first matching path will be used)
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
import glob
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.brightness))
|
|
||||||
self._brightness = 0
|
|
||||||
|
|
||||||
self._device_path = self.find_device(self.parameter("device_path", "/sys/class/backlight/intel_backlight"))
|
|
||||||
step = self.parameter("step", 2)
|
|
||||||
|
|
||||||
if bumblebee.util.which("light"):
|
|
||||||
self.register_cmd(engine, "light -A {}%".format(step),
|
|
||||||
"light -U {}%".format(step))
|
|
||||||
elif bumblebee.util.which("brightnessctl"):
|
|
||||||
self.register_cmd(engine, "brightnessctl s {}%+".format(step),
|
|
||||||
"brightnessctl s {}%-".format(step))
|
|
||||||
else:
|
|
||||||
self.register_cmd(engine, "xbacklight +{}%".format(step),
|
|
||||||
"xbacklight -{}%".format(step))
|
|
||||||
|
|
||||||
def find_device(self, device_path):
|
|
||||||
res = glob.glob(device_path)
|
|
||||||
if len(res) == 0:
|
|
||||||
return device_path
|
|
||||||
return res[0]
|
|
||||||
|
|
||||||
def register_cmd(self, engine, upCmd, downCmd):
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.WHEEL_UP, cmd=upCmd)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.WHEEL_DOWN, cmd=downCmd)
|
|
||||||
|
|
||||||
def brightness(self, widget):
|
|
||||||
if isinstance(self._brightness, float):
|
|
||||||
return "{:3.0f}%".format(self._brightness).strip()
|
|
||||||
else:
|
|
||||||
return "n/a"
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
try:
|
|
||||||
with open("{}/brightness".format(self._device_path)) as f:
|
|
||||||
backlight = int(f.readline())
|
|
||||||
with open("{}/max_brightness".format(self._device_path)) as f:
|
|
||||||
max_brightness = int(f.readline())
|
|
||||||
self._brightness = float(backlight * 100 / max_brightness)
|
|
||||||
except:
|
|
||||||
return "Error"
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,102 +0,0 @@
|
||||||
#pylint: disable=C0111,R0903,W0212
|
|
||||||
|
|
||||||
"""Enable/disable automatic screen locking.
|
|
||||||
|
|
||||||
Requires the following executables:
|
|
||||||
* xdg-screensaver
|
|
||||||
* xdotool
|
|
||||||
* xprop (as dependency for xdotool)
|
|
||||||
* notify-send
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import psutil
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text="")
|
|
||||||
)
|
|
||||||
self._active = False
|
|
||||||
self._xid = None
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self._toggle
|
|
||||||
)
|
|
||||||
|
|
||||||
def _check_requirements(self):
|
|
||||||
requirements = ['xdotool', 'xprop', 'xdg-screensaver']
|
|
||||||
missing = []
|
|
||||||
for tool in requirements:
|
|
||||||
if not bumblebee.util.which(tool):
|
|
||||||
missing.append(tool)
|
|
||||||
return missing
|
|
||||||
|
|
||||||
def _get_i3bar_xid(self):
|
|
||||||
xid = bumblebee.util.execute("xdotool search --class \"i3bar\"").partition('\n')[0].strip()
|
|
||||||
if xid.isdigit():
|
|
||||||
return xid
|
|
||||||
logging.warning("Module caffeine: xdotool couldn't get X window ID of \"i3bar\".")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _notify(self):
|
|
||||||
if not bumblebee.util.which('notify-send'):
|
|
||||||
return
|
|
||||||
|
|
||||||
if self._active:
|
|
||||||
bumblebee.util.execute("notify-send \"Consuming caffeine\"")
|
|
||||||
else:
|
|
||||||
bumblebee.util.execute("notify-send \"Out of coffee\"")
|
|
||||||
|
|
||||||
def _suspend_screensaver(self):
|
|
||||||
self._xid = self._get_i3bar_xid()
|
|
||||||
if self._xid is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
pid = os.fork()
|
|
||||||
if pid == 0:
|
|
||||||
os.setsid()
|
|
||||||
bumblebee.util.execute("xdg-screensaver suspend {}".format(self._xid))
|
|
||||||
os._exit(0)
|
|
||||||
else:
|
|
||||||
os.waitpid(pid, 0)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _resume_screensaver(self):
|
|
||||||
success = True
|
|
||||||
xprop_path = bumblebee.util.which('xprop')
|
|
||||||
pids = [ p.pid for p in psutil.process_iter() if p.cmdline() == [xprop_path, '-id', str(self._xid), '-spy'] ]
|
|
||||||
for pid in pids:
|
|
||||||
try:
|
|
||||||
os.kill(pid, 9)
|
|
||||||
except OSError:
|
|
||||||
success = False
|
|
||||||
return success
|
|
||||||
|
|
||||||
def state(self, _):
|
|
||||||
if self._active:
|
|
||||||
return "activated"
|
|
||||||
return "deactivated"
|
|
||||||
|
|
||||||
def _toggle(self, _):
|
|
||||||
missing = self._check_requirements()
|
|
||||||
if missing:
|
|
||||||
logging.warning('Could not run caffeine - missing %s!', ", ".join(missing))
|
|
||||||
return
|
|
||||||
|
|
||||||
self._active = not self._active
|
|
||||||
if self._active:
|
|
||||||
success = self._suspend_screensaver()
|
|
||||||
else:
|
|
||||||
success = self._resume_screensaver()
|
|
||||||
|
|
||||||
if success:
|
|
||||||
self._notify()
|
|
||||||
else:
|
|
||||||
self._active = not self._active
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,120 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays information about the current song in cmus.
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* cmus-remote
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* cmus.format: Format string for the song information. Tag values can be put in curly brackets (i.e. {artist})
|
|
||||||
Additional tags:
|
|
||||||
* {file} - full song file name
|
|
||||||
* {file1} - song file name without path prefix
|
|
||||||
if {file} = '/foo/bar.baz', then {file1} = 'bar.baz'
|
|
||||||
* {file2} - song file name without path prefix and extension suffix
|
|
||||||
if {file} = '/foo/bar.baz', then {file2} = 'bar'
|
|
||||||
* cmus.layout: Space-separated list of widgets to add. Possible widgets are the buttons/toggles cmus.prev, cmus.next, cmus.shuffle and cmus.repeat, and the main display with play/pause function cmus.main.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
import os
|
|
||||||
import string
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
from bumblebee.output import scrollable
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config, [])
|
|
||||||
|
|
||||||
self._layout = self.parameter("layout", "cmus.prev cmus.main cmus.next cmus.shuffle cmus.repeat")
|
|
||||||
self._fmt = self.parameter("format", "{artist} - {title} {position}/{duration}")
|
|
||||||
self._status = None
|
|
||||||
self._shuffle = False
|
|
||||||
self._repeat = False
|
|
||||||
self._tags = defaultdict(lambda: '')
|
|
||||||
|
|
||||||
# Create widgets
|
|
||||||
widget_list = []
|
|
||||||
widget_map = {}
|
|
||||||
for widget_name in self._layout.split():
|
|
||||||
widget = bumblebee.output.Widget(name=widget_name)
|
|
||||||
widget_list.append(widget)
|
|
||||||
|
|
||||||
if widget_name == "cmus.prev":
|
|
||||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "cmus-remote -r"}
|
|
||||||
elif widget_name == "cmus.main":
|
|
||||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "cmus-remote -u"}
|
|
||||||
widget.full_text(self.description)
|
|
||||||
elif widget_name == "cmus.next":
|
|
||||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "cmus-remote -n"}
|
|
||||||
elif widget_name == "cmus.shuffle":
|
|
||||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "cmus-remote -S"}
|
|
||||||
elif widget_name == "cmus.repeat":
|
|
||||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "cmus-remote -R"}
|
|
||||||
else:
|
|
||||||
raise KeyError("The cmus module does not support a {widget_name!r} widget".format(widget_name=widget_name))
|
|
||||||
self.widgets(widget_list)
|
|
||||||
|
|
||||||
# Register input callbacks
|
|
||||||
for widget, callback_options in widget_map.items():
|
|
||||||
engine.input.register_callback(widget, **callback_options)
|
|
||||||
|
|
||||||
def hidden(self):
|
|
||||||
return self._status is None
|
|
||||||
|
|
||||||
@scrollable
|
|
||||||
def description(self, widget):
|
|
||||||
return string.Formatter().vformat(self._fmt, (), self._tags)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._load_song()
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
returns = {
|
|
||||||
"cmus.shuffle": "shuffle-on" if self._shuffle else "shuffle-off",
|
|
||||||
"cmus.repeat": "repeat-on" if self._repeat else "repeat-off",
|
|
||||||
"cmus.prev": "prev",
|
|
||||||
"cmus.next": "next",
|
|
||||||
}
|
|
||||||
return returns.get(widget.name, self._status)
|
|
||||||
|
|
||||||
def _eval_line(self, line):
|
|
||||||
if line.startswith("file "):
|
|
||||||
full_file = line[5:]
|
|
||||||
file1 = os.path.basename(full_file)
|
|
||||||
file2 = os.path.splitext(file1)[0]
|
|
||||||
self._tags.update({"file": full_file})
|
|
||||||
self._tags.update({"file1": file1})
|
|
||||||
self._tags.update({"file2": file2})
|
|
||||||
return
|
|
||||||
name, key, value = (line.split(" ", 2) + [None, None])[:3]
|
|
||||||
|
|
||||||
if name == "status":
|
|
||||||
self._status = key
|
|
||||||
if name == "tag":
|
|
||||||
self._tags.update({key: value})
|
|
||||||
if name in ["duration", "position"]:
|
|
||||||
self._tags.update({name: bumblebee.util.durationfmt(int(key))})
|
|
||||||
if name == "set" and key == "repeat":
|
|
||||||
self._repeat = value == "true"
|
|
||||||
if name == "set" and key == "shuffle":
|
|
||||||
self._shuffle = value == "true"
|
|
||||||
|
|
||||||
def _load_song(self):
|
|
||||||
info = ""
|
|
||||||
try:
|
|
||||||
info = bumblebee.util.execute("cmus-remote -Q")
|
|
||||||
except RuntimeError:
|
|
||||||
self._status = None
|
|
||||||
|
|
||||||
self._tags = defaultdict(lambda: '')
|
|
||||||
for line in info.split("\n"):
|
|
||||||
self._eval_line(line)
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,42 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays CPU utilization across all CPUs.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* cpu.warning : Warning threshold in % of CPU usage (defaults to 70%)
|
|
||||||
* cpu.critical: Critical threshold in % of CPU usage (defaults to 80%)
|
|
||||||
* cpu.format : Format string (defaults to "{:.01f}%")
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
import psutil
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widget = bumblebee.output.Widget(full_text=self.utilization)
|
|
||||||
super(Module, self).__init__(engine, config, widget)
|
|
||||||
widget.set("theme.minwidth", self._format.format(10.0-10e-20))
|
|
||||||
self._utilization = psutil.cpu_percent(percpu=False)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="gnome-system-monitor")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _format(self):
|
|
||||||
return self.parameter("format", "{:.01f}%")
|
|
||||||
|
|
||||||
def utilization(self, _):
|
|
||||||
return self._format.format(self._utilization)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._utilization = psutil.cpu_percent(percpu=False)
|
|
||||||
|
|
||||||
def state(self, _):
|
|
||||||
return self.threshold_state(self._utilization, 70, 80)
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,157 +0,0 @@
|
||||||
"""Multiwidget CPU module
|
|
||||||
|
|
||||||
Can display any combination of:
|
|
||||||
|
|
||||||
* max CPU frequency
|
|
||||||
* total CPU load in percents (integer value)
|
|
||||||
* per-core CPU load as graph - either mono or colored
|
|
||||||
* CPU temperature (in Celsius degrees)
|
|
||||||
* CPU fan speed
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
|
|
||||||
* the psutil Python module for the first three items from the list above
|
|
||||||
* sensors executable for the rest
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* cpu2.layout: Space-separated list of widgets to add.
|
|
||||||
Possible widgets are:
|
|
||||||
* cpu2.maxfreq
|
|
||||||
* cpu2.cpuload
|
|
||||||
* cpu2.coresload
|
|
||||||
* cpu2.temp
|
|
||||||
* cpu2.fanspeed
|
|
||||||
* cpu2.colored: 1 for colored per core load graph, 0 for mono (default)
|
|
||||||
if this is set to 1, use --markup=pango
|
|
||||||
* cpu2.temp_pattern: pattern to look for in the output of 'sensors -u';
|
|
||||||
required if cpu2.temp widged is used
|
|
||||||
* cpu2.fan_pattern: pattern to look for in the output of 'sensors -u';
|
|
||||||
required if cpu2.fanspeed widged is used
|
|
||||||
|
|
||||||
Note: if you are getting "n/a" for CPU temperature / fan speed, then you're
|
|
||||||
lacking the aforementioned pattern settings or they have wrong values.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
import psutil
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.output
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config, [])
|
|
||||||
self._layout = self.parameter("layout", "cpu2.maxfreq cpu2.cpuload cpu2.coresload cpu2.temp cpu2.fanspeed")
|
|
||||||
self._widget_names = self._layout.split()
|
|
||||||
widget_list = []
|
|
||||||
for widget_name in self._widget_names:
|
|
||||||
if widget_name == "cpu2.maxfreq":
|
|
||||||
widget = bumblebee.output.Widget(
|
|
||||||
name=widget_name, full_text=self.maxfreq)
|
|
||||||
widget.set("type", "freq")
|
|
||||||
elif widget_name == "cpu2.cpuload":
|
|
||||||
widget = bumblebee.output.Widget(
|
|
||||||
name=widget_name, full_text=self.cpuload)
|
|
||||||
widget.set("type", "load")
|
|
||||||
elif widget_name == "cpu2.coresload":
|
|
||||||
widget = bumblebee.output.Widget(
|
|
||||||
name=widget_name, full_text=self.coresload)
|
|
||||||
widget.set("type", "loads")
|
|
||||||
elif widget_name == "cpu2.temp":
|
|
||||||
widget = bumblebee.output.Widget(
|
|
||||||
name=widget_name, full_text=self.temp)
|
|
||||||
widget.set("type", "temp")
|
|
||||||
elif widget_name == "cpu2.fanspeed":
|
|
||||||
widget = bumblebee.output.Widget(
|
|
||||||
name=widget_name, full_text=self.fanspeed)
|
|
||||||
widget.set("type", "fan")
|
|
||||||
widget_list.append(widget)
|
|
||||||
self.widgets(widget_list)
|
|
||||||
self._colored = bumblebee.util.asbool(self.parameter("colored", 0))
|
|
||||||
self._temp_pattern = self.parameter("temp_pattern")
|
|
||||||
if self._temp_pattern is None:
|
|
||||||
self._temp = "n/a"
|
|
||||||
self._fan_pattern = self.parameter("fan_pattern")
|
|
||||||
if self._fan_pattern is None:
|
|
||||||
self._fan = "n/a"
|
|
||||||
# maxfreq is loaded only once at startup
|
|
||||||
if "cpu2.maxfreq" in self._widget_names:
|
|
||||||
self._maxfreq = psutil.cpu_freq().max / 1000
|
|
||||||
self.update(widget_list)
|
|
||||||
|
|
||||||
def maxfreq(self, _):
|
|
||||||
return "{:.2f}GHz".format(self._maxfreq)
|
|
||||||
|
|
||||||
def cpuload(self, _):
|
|
||||||
return "{}%".format(self._cpuload)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def add_color(bar):
|
|
||||||
"""add color as pango markup to a bar"""
|
|
||||||
if bar in ["▁", "▂"]:
|
|
||||||
color = "green"
|
|
||||||
elif bar in ["▃", "▄"]:
|
|
||||||
color = "yellow"
|
|
||||||
elif bar in ["▅", "▆"]:
|
|
||||||
color = "orange"
|
|
||||||
elif bar in ["▇", "█"]:
|
|
||||||
color = "red"
|
|
||||||
colored_bar = "<span foreground='{}'>{}</span>".format(color, bar)
|
|
||||||
return colored_bar
|
|
||||||
|
|
||||||
def coresload(self, _):
|
|
||||||
mono_bars = [bumblebee.output.hbar(x) for x in self._coresload]
|
|
||||||
if not self._colored:
|
|
||||||
return "".join(mono_bars)
|
|
||||||
colored_bars = [self.add_color(x) for x in mono_bars]
|
|
||||||
return "".join(colored_bars)
|
|
||||||
|
|
||||||
def temp(self, _):
|
|
||||||
if self._temp == "n/a" or self._temp == 0:
|
|
||||||
return "n/a"
|
|
||||||
return "{}°C".format(self._temp)
|
|
||||||
|
|
||||||
def fanspeed(self, _):
|
|
||||||
if self._fanspeed == "n/a":
|
|
||||||
return "n/a"
|
|
||||||
return "{}RPM".format(self._fanspeed)
|
|
||||||
|
|
||||||
def _parse_sensors_output(self):
|
|
||||||
output = bumblebee.util.execute("sensors -u")
|
|
||||||
lines = output.split("\n")
|
|
||||||
temp = "n/a"
|
|
||||||
fan = "n/a"
|
|
||||||
temp_line = None
|
|
||||||
fan_line = None
|
|
||||||
for line in lines:
|
|
||||||
if self._temp_pattern is not None and self._temp_pattern in line:
|
|
||||||
temp_line = line
|
|
||||||
if self._fan_pattern is not None and self._fan_pattern in line:
|
|
||||||
fan_line = line
|
|
||||||
if temp_line is not None and fan_line is not None:
|
|
||||||
break
|
|
||||||
if temp_line is not None:
|
|
||||||
temp = round(float(temp_line.split(":")[1].strip()))
|
|
||||||
if fan_line is not None:
|
|
||||||
fan = int(fan_line.split(":")[1].strip()[:-4])
|
|
||||||
return temp, fan
|
|
||||||
|
|
||||||
def update(self, _):
|
|
||||||
if "cpu2.maxfreq" in self._widget_names:
|
|
||||||
self._maxfreq = psutil.cpu_freq().max / 1000
|
|
||||||
if "cpu2.cpuload" in self._widget_names:
|
|
||||||
self._cpuload = round(psutil.cpu_percent(percpu=False))
|
|
||||||
if "cpu2.coresload" in self._widget_names:
|
|
||||||
self._coresload = psutil.cpu_percent(percpu=True)
|
|
||||||
if "cpu2.temp" in self._widget_names or "cpu2.fanspeed" in self._widget_names:
|
|
||||||
self._temp, self._fanspeed = self._parse_sensors_output()
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
"""for having per-widget icons"""
|
|
||||||
return [widget.get("type", "")]
|
|
|
@ -1,139 +0,0 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays currency exchange rates. Currently, displays currency between GBP and USD/EUR only.
|
|
||||||
|
|
||||||
Requires the following python packages:
|
|
||||||
* requests
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* currency.interval: Interval in minutes between updates, default is 1.
|
|
||||||
* currency.source: Source currency (ex. "GBP", "EUR"). Defaults to "auto", which infers the local one from IP address.
|
|
||||||
* currency.destination: Comma-separated list of destination currencies (defaults to "USD,EUR")
|
|
||||||
* currency.sourceformat: String format for source formatting; Defaults to "{}: {}" and has two variables,
|
|
||||||
the base symbol and the rate list
|
|
||||||
* currency.destinationdelimiter: Delimiter used for separating individual rates (defaults to "|")
|
|
||||||
|
|
||||||
Note: source and destination names right now must correspond to the names used by the API of https://markets.ft.com
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
try:
|
|
||||||
import requests
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
from babel.numbers import format_currency
|
|
||||||
except ImportError:
|
|
||||||
format_currency = None
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
SYMBOL = {
|
|
||||||
"GBP": u"£", "EUR": u"€", "USD": u"$", "JPY": u"¥", "KRW": u"₩"
|
|
||||||
}
|
|
||||||
DEFAULT_DEST = "USD,EUR,auto"
|
|
||||||
DEFAULT_SRC = "GBP"
|
|
||||||
|
|
||||||
API_URL = "https://markets.ft.com/data/currencies/ajax/conversion?baseCurrency={}&comparison={}"
|
|
||||||
LOCATION_URL = "https://ipvigilante.com/"
|
|
||||||
|
|
||||||
|
|
||||||
def get_local_country():
|
|
||||||
r = requests.get(LOCATION_URL)
|
|
||||||
location = r.json()
|
|
||||||
return location['data']['country_name']
|
|
||||||
|
|
||||||
|
|
||||||
def load_country_to_currency():
|
|
||||||
fname = os.path.join(
|
|
||||||
os.path.dirname(os.path.abspath(__file__)),
|
|
||||||
'data', 'country-by-currency-code.json')
|
|
||||||
with open(fname, 'r') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
country2curr = {}
|
|
||||||
for dt in data:
|
|
||||||
country2curr[dt['country']] = dt['currency_code']
|
|
||||||
|
|
||||||
return country2curr
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.price)
|
|
||||||
)
|
|
||||||
self._data = []
|
|
||||||
self.interval_factor(60)
|
|
||||||
self.interval(1)
|
|
||||||
self._nextcheck = 0
|
|
||||||
|
|
||||||
src = self.parameter("source", DEFAULT_SRC)
|
|
||||||
if src == "auto":
|
|
||||||
self._base = self.find_local_currency()
|
|
||||||
else:
|
|
||||||
self._base = src
|
|
||||||
|
|
||||||
self._symbols = []
|
|
||||||
for d in self.parameter("destination", DEFAULT_DEST).split(","):
|
|
||||||
if d == 'auto':
|
|
||||||
new = self.find_local_currency()
|
|
||||||
else:
|
|
||||||
new = d
|
|
||||||
if new != self._base:
|
|
||||||
self._symbols.append(new)
|
|
||||||
|
|
||||||
def price(self, widget):
|
|
||||||
if len(self._data) == 0:
|
|
||||||
return "?"
|
|
||||||
|
|
||||||
rates = []
|
|
||||||
for sym, rate in self._data:
|
|
||||||
rate_float = float(rate.replace(',',''))
|
|
||||||
if format_currency:
|
|
||||||
rates.append(format_currency(rate_float, sym))
|
|
||||||
else:
|
|
||||||
rate = self.fmt_rate(rate)
|
|
||||||
rates.append(u"{}{}".format(rate, SYMBOL[sym] if sym in SYMBOL else sym))
|
|
||||||
|
|
||||||
basefmt = u"{}".format(self.parameter("sourceformat", "{}={}"))
|
|
||||||
ratefmt = u"{}".format(self.parameter("destinationdelimiter", "="))
|
|
||||||
|
|
||||||
if format_currency:
|
|
||||||
base_val = format_currency(1, self._base)
|
|
||||||
else:
|
|
||||||
base_val = '1{}'.format(SYMBOL[self._base] if self._base in SYMBOL else self._base)
|
|
||||||
|
|
||||||
return basefmt.format(base_val, ratefmt.join(rates))
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._data = []
|
|
||||||
for symbol in self._symbols:
|
|
||||||
url = API_URL.format(self._base, symbol)
|
|
||||||
try:
|
|
||||||
response = requests.get(url).json()
|
|
||||||
self._data.append((symbol, response['data']['exchangeRate']))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def find_local_currency(self):
|
|
||||||
'''Use geolocation lookup to find local currency'''
|
|
||||||
try:
|
|
||||||
country = get_local_country()
|
|
||||||
currency_map = load_country_to_currency()
|
|
||||||
return currency_map.get(country, DEFAULT_SRC)
|
|
||||||
except:
|
|
||||||
return DEFAULT_SRC
|
|
||||||
|
|
||||||
def fmt_rate(self, rate):
|
|
||||||
float_rate = float(rate.replace(',', ''))
|
|
||||||
if not 0.01 < float_rate < 100:
|
|
||||||
ret = rate
|
|
||||||
else:
|
|
||||||
ret = "%.3g" % float_rate
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,974 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"country": "Afghanistan",
|
|
||||||
"currency_code": "AFN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Albania",
|
|
||||||
"currency_code": "ALL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Algeria",
|
|
||||||
"currency_code": "DZD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "American Samoa",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Andorra",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Angola",
|
|
||||||
"currency_code": "AOA"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Anguilla",
|
|
||||||
"currency_code": "XCD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Antarctica",
|
|
||||||
"currency_code": "XCD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Antigua and Barbuda",
|
|
||||||
"currency_code": "XCD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Argentina",
|
|
||||||
"currency_code": "ARS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Armenia",
|
|
||||||
"currency_code": "AMD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Aruba",
|
|
||||||
"currency_code": "AWG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Australia",
|
|
||||||
"currency_code": "AUD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Austria",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Azerbaijan",
|
|
||||||
"currency_code": "AZN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Bahamas",
|
|
||||||
"currency_code": "BSD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Bahrain",
|
|
||||||
"currency_code": "BHD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Bangladesh",
|
|
||||||
"currency_code": "BDT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Barbados",
|
|
||||||
"currency_code": "BBD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Belarus",
|
|
||||||
"currency_code": "BYR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Belgium",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Belize",
|
|
||||||
"currency_code": "BZD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Benin",
|
|
||||||
"currency_code": "XOF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Bermuda",
|
|
||||||
"currency_code": "BMD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Bhutan",
|
|
||||||
"currency_code": "BTN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Bolivia",
|
|
||||||
"currency_code": "BOB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Bosnia and Herzegovina",
|
|
||||||
"currency_code": "BAM"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Botswana",
|
|
||||||
"currency_code": "BWP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Bouvet Island",
|
|
||||||
"currency_code": "NOK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Brazil",
|
|
||||||
"currency_code": "BRL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "British Indian Ocean Territory",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Brunei",
|
|
||||||
"currency_code": "BND"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Bulgaria",
|
|
||||||
"currency_code": "BGN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Burkina Faso",
|
|
||||||
"currency_code": "XOF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Burundi",
|
|
||||||
"currency_code": "BIF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Cambodia",
|
|
||||||
"currency_code": "KHR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Cameroon",
|
|
||||||
"currency_code": "XAF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Canada",
|
|
||||||
"currency_code": "CAD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Cape Verde",
|
|
||||||
"currency_code": "CVE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Cayman Islands",
|
|
||||||
"currency_code": "KYD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Central African Republic",
|
|
||||||
"currency_code": "XAF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Chad",
|
|
||||||
"currency_code": "XAF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Chile",
|
|
||||||
"currency_code": "CLP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "China",
|
|
||||||
"currency_code": "CNY"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Christmas Island",
|
|
||||||
"currency_code": "AUD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Cocos (Keeling) Islands",
|
|
||||||
"currency_code": "AUD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Colombia",
|
|
||||||
"currency_code": "COP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Comoros",
|
|
||||||
"currency_code": "KMF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Congo",
|
|
||||||
"currency_code": "XAF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Cook Islands",
|
|
||||||
"currency_code": "NZD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Costa Rica",
|
|
||||||
"currency_code": "CRC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Croatia",
|
|
||||||
"currency_code": "HRK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Cuba",
|
|
||||||
"currency_code": "CUP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Cyprus",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Czech Republic",
|
|
||||||
"currency_code": "CZK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Denmark",
|
|
||||||
"currency_code": "DKK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Djibouti",
|
|
||||||
"currency_code": "DJF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Dominica",
|
|
||||||
"currency_code": "XCD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Dominican Republic",
|
|
||||||
"currency_code": "DOP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "East Timor",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Ecuador",
|
|
||||||
"currency_code": "ECS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Egypt",
|
|
||||||
"currency_code": "EGP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "El Salvador",
|
|
||||||
"currency_code": "SVC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "England",
|
|
||||||
"currency_code": "GBP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Equatorial Guinea",
|
|
||||||
"currency_code": "XAF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Eritrea",
|
|
||||||
"currency_code": "ERN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Estonia",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Ethiopia",
|
|
||||||
"currency_code": "ETB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Falkland Islands",
|
|
||||||
"currency_code": "FKP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Faroe Islands",
|
|
||||||
"currency_code": "DKK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Fiji Islands",
|
|
||||||
"currency_code": "FJD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Finland",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "France",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "French Guiana",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "French Polynesia",
|
|
||||||
"currency_code": "XPF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "French Southern territories",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Gabon",
|
|
||||||
"currency_code": "XAF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Gambia",
|
|
||||||
"currency_code": "GMD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Georgia",
|
|
||||||
"currency_code": "GEL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Germany",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Ghana",
|
|
||||||
"currency_code": "GHS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Gibraltar",
|
|
||||||
"currency_code": "GIP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Greece",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Greenland",
|
|
||||||
"currency_code": "DKK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Grenada",
|
|
||||||
"currency_code": "XCD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Guadeloupe",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Guam",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Guatemala",
|
|
||||||
"currency_code": "QTQ"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Guinea",
|
|
||||||
"currency_code": "GNF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Guinea-Bissau",
|
|
||||||
"currency_code": "CFA"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Guyana",
|
|
||||||
"currency_code": "GYD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Haiti",
|
|
||||||
"currency_code": "HTG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Heard Island and McDonald Islands",
|
|
||||||
"currency_code": "AUD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Holy See (Vatican City State)",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Honduras",
|
|
||||||
"currency_code": "HNL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Hong Kong",
|
|
||||||
"currency_code": "HKD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Hungary",
|
|
||||||
"currency_code": "HUF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Iceland",
|
|
||||||
"currency_code": "ISK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "India",
|
|
||||||
"currency_code": "INR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Indonesia",
|
|
||||||
"currency_code": "IDR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Iran",
|
|
||||||
"currency_code": "IRR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Iraq",
|
|
||||||
"currency_code": "IQD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Ireland",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Israel",
|
|
||||||
"currency_code": "ILS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Italy",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Ivory Coast",
|
|
||||||
"currency_code": "XOF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Jamaica",
|
|
||||||
"currency_code": "JMD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Japan",
|
|
||||||
"currency_code": "JPY"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Jordan",
|
|
||||||
"currency_code": "JOD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Kazakhstan",
|
|
||||||
"currency_code": "KZT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Kenya",
|
|
||||||
"currency_code": "KES"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Kiribati",
|
|
||||||
"currency_code": "AUD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Kuwait",
|
|
||||||
"currency_code": "KWD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Kyrgyzstan",
|
|
||||||
"currency_code": "KGS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Laos",
|
|
||||||
"currency_code": "LAK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Latvia",
|
|
||||||
"currency_code": "LVL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Lebanon",
|
|
||||||
"currency_code": "LBP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Lesotho",
|
|
||||||
"currency_code": "LSL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Liberia",
|
|
||||||
"currency_code": "LRD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Libyan Arab Jamahiriya",
|
|
||||||
"currency_code": "LYD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Liechtenstein",
|
|
||||||
"currency_code": "CHF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Lithuania",
|
|
||||||
"currency_code": "LTL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Luxembourg",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Macao",
|
|
||||||
"currency_code": "MOP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "North Macedonia",
|
|
||||||
"currency_code": "MKD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Madagascar",
|
|
||||||
"currency_code": "MGF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Malawi",
|
|
||||||
"currency_code": "MWK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Malaysia",
|
|
||||||
"currency_code": "MYR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Maldives",
|
|
||||||
"currency_code": "MVR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Mali",
|
|
||||||
"currency_code": "XOF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Malta",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Marshall Islands",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Martinique",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Mauritania",
|
|
||||||
"currency_code": "MRO"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Mauritius",
|
|
||||||
"currency_code": "MUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Mayotte",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Mexico",
|
|
||||||
"currency_code": "MXN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Micronesia, Federated States of",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Moldova",
|
|
||||||
"currency_code": "MDL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Monaco",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Mongolia",
|
|
||||||
"currency_code": "MNT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Montserrat",
|
|
||||||
"currency_code": "XCD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Morocco",
|
|
||||||
"currency_code": "MAD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Mozambique",
|
|
||||||
"currency_code": "MZN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Myanmar",
|
|
||||||
"currency_code": "MMR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Namibia",
|
|
||||||
"currency_code": "NAD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Nauru",
|
|
||||||
"currency_code": "AUD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Nepal",
|
|
||||||
"currency_code": "NPR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Netherlands",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Netherlands Antilles",
|
|
||||||
"currency_code": "ANG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "New Caledonia",
|
|
||||||
"currency_code": "XPF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "New Zealand",
|
|
||||||
"currency_code": "NZD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Nicaragua",
|
|
||||||
"currency_code": "NIO"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Niger",
|
|
||||||
"currency_code": "XOF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Nigeria",
|
|
||||||
"currency_code": "NGN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Niue",
|
|
||||||
"currency_code": "NZD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Norfolk Island",
|
|
||||||
"currency_code": "AUD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "North Korea",
|
|
||||||
"currency_code": "KPW"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Northern Ireland",
|
|
||||||
"currency_code": "GBP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Northern Mariana Islands",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Norway",
|
|
||||||
"currency_code": "NOK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Oman",
|
|
||||||
"currency_code": "OMR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Pakistan",
|
|
||||||
"currency_code": "PKR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Palau",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Palestine",
|
|
||||||
"currency_code": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Panama",
|
|
||||||
"currency_code": "PAB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Papua New Guinea",
|
|
||||||
"currency_code": "PGK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Paraguay",
|
|
||||||
"currency_code": "PYG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Peru",
|
|
||||||
"currency_code": "PEN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Philippines",
|
|
||||||
"currency_code": "PHP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Pitcairn",
|
|
||||||
"currency_code": "NZD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Poland",
|
|
||||||
"currency_code": "PLN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Portugal",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Puerto Rico",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Qatar",
|
|
||||||
"currency_code": "QAR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Reunion",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Romania",
|
|
||||||
"currency_code": "RON"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Russian Federation",
|
|
||||||
"currency_code": "RUB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Rwanda",
|
|
||||||
"currency_code": "RWF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Saint Helena",
|
|
||||||
"currency_code": "SHP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Saint Kitts and Nevis",
|
|
||||||
"currency_code": "XCD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Saint Lucia",
|
|
||||||
"currency_code": "XCD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Saint Pierre and Miquelon",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Saint Vincent and the Grenadines",
|
|
||||||
"currency_code": "XCD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Samoa",
|
|
||||||
"currency_code": "WST"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "San Marino",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Sao Tome and Principe",
|
|
||||||
"currency_code": "STD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Saudi Arabia",
|
|
||||||
"currency_code": "SAR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Scotland",
|
|
||||||
"currency_code": "GBP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Senegal",
|
|
||||||
"currency_code": "XOF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Seychelles",
|
|
||||||
"currency_code": "SCR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Sierra Leone",
|
|
||||||
"currency_code": "SLL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Singapore",
|
|
||||||
"currency_code": "SGD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Slovakia",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Slovenia",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Solomon Islands",
|
|
||||||
"currency_code": "SBD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Somalia",
|
|
||||||
"currency_code": "SOS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "South Africa",
|
|
||||||
"currency_code": "ZAR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "South Georgia and the South Sandwich Islands",
|
|
||||||
"currency_code": "GBP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "South Korea",
|
|
||||||
"currency_code": "KRW"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "South Sudan",
|
|
||||||
"currency_code": "SSP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Spain",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "SriLanka",
|
|
||||||
"currency_code": "LKR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Sudan",
|
|
||||||
"currency_code": "SDG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Suriname",
|
|
||||||
"currency_code": "SRD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Svalbard and Jan Mayen",
|
|
||||||
"currency_code": "NOK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Swaziland",
|
|
||||||
"currency_code": "SZL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Sweden",
|
|
||||||
"currency_code": "SEK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Switzerland",
|
|
||||||
"currency_code": "CHF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Syria",
|
|
||||||
"currency_code": "SYP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Tajikistan",
|
|
||||||
"currency_code": "TJS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Tanzania",
|
|
||||||
"currency_code": "TZS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Thailand",
|
|
||||||
"currency_code": "THB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "The Democratic Republic of Congo",
|
|
||||||
"currency_code": "CDF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Togo",
|
|
||||||
"currency_code": "XOF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Tokelau",
|
|
||||||
"currency_code": "NZD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Tonga",
|
|
||||||
"currency_code": "TOP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Trinidad and Tobago",
|
|
||||||
"currency_code": "TTD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Tunisia",
|
|
||||||
"currency_code": "TND"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Turkey",
|
|
||||||
"currency_code": "TRY"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Turkmenistan",
|
|
||||||
"currency_code": "TMT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Turks and Caicos Islands",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Tuvalu",
|
|
||||||
"currency_code": "AUD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Uganda",
|
|
||||||
"currency_code": "UGX"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Ukraine",
|
|
||||||
"currency_code": "UAH"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "United Arab Emirates",
|
|
||||||
"currency_code": "AED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "United Kingdom",
|
|
||||||
"currency_code": "GBP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "United States",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "United States Minor Outlying Islands",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Uruguay",
|
|
||||||
"currency_code": "UYU"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Uzbekistan",
|
|
||||||
"currency_code": "UZS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Vanuatu",
|
|
||||||
"currency_code": "VUV"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Venezuela",
|
|
||||||
"currency_code": "VEF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Vietnam",
|
|
||||||
"currency_code": "VND"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Virgin Islands, British",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Virgin Islands, U.S.",
|
|
||||||
"currency_code": "USD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Wales",
|
|
||||||
"currency_code": "GBP"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Wallis and Futuna",
|
|
||||||
"currency_code": "XPF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Western Sahara",
|
|
||||||
"currency_code": "MAD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Yemen",
|
|
||||||
"currency_code": "YER"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Yugoslavia",
|
|
||||||
"currency_code": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Zambia",
|
|
||||||
"currency_code": "ZMW"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"country": "Zimbabwe",
|
|
||||||
"currency_code": "ZWD"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,50 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the current date and time.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* datetime.format: strftime()-compatible formatting string
|
|
||||||
* date.format : alias for datetime.format
|
|
||||||
* time.format : alias for datetime.format
|
|
||||||
* datetime.locale: locale to use rather than the system default
|
|
||||||
* date.locale : alias for datetime.locale
|
|
||||||
* time.locale : alias for datetime.locale
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
import datetime
|
|
||||||
import locale
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
def default_format(module):
|
|
||||||
default = "%x %X"
|
|
||||||
if module == "date":
|
|
||||||
default = "%x"
|
|
||||||
if module == "time":
|
|
||||||
default = "%X"
|
|
||||||
return default
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.get_time))
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="calendar")
|
|
||||||
self._fmt = self.parameter("format", default_format(self.name))
|
|
||||||
l = locale.getdefaultlocale()
|
|
||||||
if not l or l == (None, None):
|
|
||||||
l = ('en_US', 'UTF-8')
|
|
||||||
lcl = self.parameter("locale", ".".join(l))
|
|
||||||
try:
|
|
||||||
locale.setlocale(locale.LC_TIME, lcl.split("."))
|
|
||||||
except Exception as e:
|
|
||||||
locale.setlocale(locale.LC_TIME, ('en_US', 'UTF-8'))
|
|
||||||
|
|
||||||
def get_time(self, widget):
|
|
||||||
enc = locale.getpreferredencoding()
|
|
||||||
retval = datetime.datetime.now().strftime(self._fmt)
|
|
||||||
if hasattr(retval, "decode"):
|
|
||||||
return retval.decode(enc)
|
|
||||||
return retval
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,93 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the current date and time with timezone options.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* datetimetz.format : strftime()-compatible formatting string
|
|
||||||
* datetimetz.timezone : IANA timezone name
|
|
||||||
* datetz.format : alias for datetimetz.format
|
|
||||||
* timetz.format : alias for datetimetz.format
|
|
||||||
* timetz.timezone : alias for datetimetz.timezone
|
|
||||||
* datetimetz.locale : locale to use rather than the system default
|
|
||||||
* datetz.locale : alias for datetimetz.locale
|
|
||||||
* timetz.locale : alias for datetimetz.locale
|
|
||||||
* timetz.timezone : alias for datetimetz.timezone
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
import datetime
|
|
||||||
import locale
|
|
||||||
import logging
|
|
||||||
try:
|
|
||||||
import pytz
|
|
||||||
import tzlocal
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
def default_format(module):
|
|
||||||
default = "%x %X %Z"
|
|
||||||
if module == "datetz":
|
|
||||||
default = "%x %Z"
|
|
||||||
if module == "timetz":
|
|
||||||
default = "%X %Z"
|
|
||||||
return default
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.get_time))
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self.next_tz)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self.prev_tz)
|
|
||||||
self._fmt = self.parameter("format", default_format(self.name))
|
|
||||||
default_timezone = ""
|
|
||||||
try:
|
|
||||||
default_timezone = tzlocal.get_localzone().zone
|
|
||||||
except Exception as e:
|
|
||||||
logging.error('unable to get default timezone: {}'.format(str(e)))
|
|
||||||
try:
|
|
||||||
self._timezones = self.parameter("timezone", default_timezone).split(",")
|
|
||||||
except:
|
|
||||||
self._timezones = [default_timezone]
|
|
||||||
self._current_tz = 0
|
|
||||||
|
|
||||||
l = locale.getdefaultlocale()
|
|
||||||
if not l or l == (None, None):
|
|
||||||
l = ('en_US', 'UTF-8')
|
|
||||||
lcl = self.parameter("locale", ".".join(l))
|
|
||||||
try:
|
|
||||||
locale.setlocale(locale.LC_TIME, lcl.split("."))
|
|
||||||
except Exception:
|
|
||||||
locale.setlocale(locale.LC_TIME, ('en_US', 'UTF-8'))
|
|
||||||
|
|
||||||
def get_time(self, widget):
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
tz = pytz.timezone(self._timezones[self._current_tz].strip())
|
|
||||||
retval = datetime.datetime.now(tz=tzlocal.get_localzone()).astimezone(tz).strftime(self._fmt)
|
|
||||||
except pytz.exceptions.UnknownTimeZoneError:
|
|
||||||
retval = "[Unknown timezone: {}]".format(self._timezones[self._current_tz].strip())
|
|
||||||
except Exception as e:
|
|
||||||
logging.error('unable to get time: {}'.format(str(e)))
|
|
||||||
retval = "[n/a]"
|
|
||||||
|
|
||||||
enc = locale.getpreferredencoding()
|
|
||||||
if hasattr(retval, "decode"):
|
|
||||||
return retval.decode(enc)
|
|
||||||
return retval
|
|
||||||
|
|
||||||
def next_tz(self, event):
|
|
||||||
next_timezone = self._current_tz + 1
|
|
||||||
if next_timezone >= len(self._timezones):
|
|
||||||
next_timezone = 0 # wraparound
|
|
||||||
self._current_tz = next_timezone
|
|
||||||
|
|
||||||
def prev_tz(self, event):
|
|
||||||
previous_timezone = self._current_tz - 1
|
|
||||||
if previous_timezone < 0:
|
|
||||||
previous_timezone = 0 # wraparound
|
|
||||||
self._current_tz = previous_timezone
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,143 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the current song being played in DeaDBeeF and provides
|
|
||||||
some media control bindings.
|
|
||||||
Left click toggles pause, scroll up skips the current song, scroll
|
|
||||||
down returns to the previous song.
|
|
||||||
|
|
||||||
Requires the following library:
|
|
||||||
* subprocess
|
|
||||||
Parameters:
|
|
||||||
* deadbeef.format: Format string (defaults to "{artist} - {title}")
|
|
||||||
Available values are: {artist}, {title}, {album}, {length},
|
|
||||||
{trackno}, {year}, {comment},
|
|
||||||
{copyright}, {time}
|
|
||||||
This is deprecated, but much simpler.
|
|
||||||
* deadbeef.tf_format: A foobar2000 title formatting-style format string.
|
|
||||||
These can be much more sophisticated than the standard
|
|
||||||
format strings. This is off by default, but specifying
|
|
||||||
any tf_format will enable it. If both deadbeef.format
|
|
||||||
and deadbeef.tf_format are specified, deadbeef.tf_format
|
|
||||||
takes priority.
|
|
||||||
* deadbeef.tf_format_if_stopped: Controls whether or not the tf_format format
|
|
||||||
string should be displayed even if no song is paused or
|
|
||||||
playing. This could be useful if you want to implement
|
|
||||||
your own stop strings with the built in logic. Any non-
|
|
||||||
null value will enable this (by default the module will
|
|
||||||
hide itself when the player is stopped).
|
|
||||||
* deadbeef.previous: Change binding for previous song (default is left click)
|
|
||||||
* deadbeef.next: Change binding for next song (default is right click)
|
|
||||||
* deadbeef.pause: Change binding for toggling pause (default is middle click)
|
|
||||||
Available options for deadbeef.previous, deadbeef.next and deadbeef.pause are:
|
|
||||||
LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN
|
|
||||||
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
from bumblebee.output import scrollable
|
|
||||||
|
|
||||||
try:
|
|
||||||
import subprocess
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.deadbeef)
|
|
||||||
)
|
|
||||||
buttons = {"LEFT_CLICK": bumblebee.input.LEFT_MOUSE,
|
|
||||||
"RIGHT_CLICK": bumblebee.input.RIGHT_MOUSE,
|
|
||||||
"MIDDLE_CLICK": bumblebee.input.MIDDLE_MOUSE,
|
|
||||||
"SCROLL_UP": bumblebee.input.WHEEL_UP,
|
|
||||||
"SCROLL_DOWN": bumblebee.input.WHEEL_DOWN,
|
|
||||||
}
|
|
||||||
|
|
||||||
self._song = ""
|
|
||||||
self._format = self.parameter("format", "{artist} - {title}")
|
|
||||||
self._tf_format = self.parameter("tf_format", "")
|
|
||||||
self._show_tf_when_stopped = bool(self.parameter("tf_format_if_stopped", ""))
|
|
||||||
prev_button = self.parameter("previous", "LEFT_CLICK")
|
|
||||||
next_button = self.parameter("next", "RIGHT_CLICK")
|
|
||||||
pause_button = self.parameter("pause", "MIDDLE_CLICK")
|
|
||||||
|
|
||||||
self.now_playing = ["deadbeef", "--nowplaying", "%a;%t;%b;%l;%n;%y;%c;%r;%e"]
|
|
||||||
self.now_playing_tf = ["deadbeef", "--nowplaying-tf", ""]
|
|
||||||
cmd = "deadbeef "
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=buttons[prev_button],
|
|
||||||
cmd=cmd + "--prev")
|
|
||||||
engine.input.register_callback(self, button=buttons[next_button],
|
|
||||||
cmd=cmd + "--next")
|
|
||||||
engine.input.register_callback(self, button=buttons[pause_button],
|
|
||||||
cmd=cmd + "--play-pause")
|
|
||||||
|
|
||||||
# modify the tf_format if we don't want it to show on stop
|
|
||||||
# this adds conditions to the query itself, rather than
|
|
||||||
# polling to see if deadbeef is running
|
|
||||||
# doing this reduces the number of calls we have to make
|
|
||||||
if self._tf_format and not self._show_tf_when_stopped:
|
|
||||||
self._tf_format = "$if($or(%isplaying%,%ispaused%),{query})".format(query=self._tf_format)
|
|
||||||
|
|
||||||
@scrollable
|
|
||||||
def deadbeef(self, widget):
|
|
||||||
return self.string_song
|
|
||||||
|
|
||||||
def hidden(self):
|
|
||||||
return self.string_song == ""
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
try:
|
|
||||||
if self._tf_format == "": # no tf format set, use the old style
|
|
||||||
return self.update_standard(widgets)
|
|
||||||
return self.update_tf(widgets)
|
|
||||||
except Exception:
|
|
||||||
self._song = "error"
|
|
||||||
|
|
||||||
def update_tf(self, widgets):
|
|
||||||
## ensure that deadbeef is actually running
|
|
||||||
## easiest way to do this is to check --nowplaying for
|
|
||||||
## the string "nothing"
|
|
||||||
if read_process(self.now_playing) == "nothing":
|
|
||||||
self._song = ""
|
|
||||||
return
|
|
||||||
## perform the actual query -- these can be much more sophisticated
|
|
||||||
self.now_playing_tf[-1] = self._tf_format
|
|
||||||
data = read_process(self.now_playing_tf)
|
|
||||||
self._song = data
|
|
||||||
|
|
||||||
def update_standard(self, widgets):
|
|
||||||
data = read_process(self.now_playing)
|
|
||||||
if data == "nothing":
|
|
||||||
self._song = ""
|
|
||||||
else:
|
|
||||||
data = data.split(";")
|
|
||||||
self._song = self._format.format(artist=data[0],
|
|
||||||
title=data[1],
|
|
||||||
album=data[2],
|
|
||||||
length=data[3],
|
|
||||||
trackno=data[4],
|
|
||||||
year=data[5],
|
|
||||||
comment=data[6],
|
|
||||||
copyright=data[7],
|
|
||||||
time=data[8])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def string_song(self):
|
|
||||||
"""\
|
|
||||||
Returns the current song as a string, either as a unicode() (Python <
|
|
||||||
3) or a regular str() (Python >= 3)
|
|
||||||
"""
|
|
||||||
if sys.version_info.major < 3:
|
|
||||||
return unicode(self._song)
|
|
||||||
return str(self._song)
|
|
||||||
|
|
||||||
def read_process(command):
|
|
||||||
proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
|
||||||
return proc.stdout.read()
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,77 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the current song being played
|
|
||||||
Requires the following library:
|
|
||||||
* python-dbus
|
|
||||||
Parameters:
|
|
||||||
* deezer.format: Format string (defaults to "{artist} - {title}")
|
|
||||||
Available values are: {album}, {title}, {artist}, {trackNumber}, {playbackStatus}
|
|
||||||
* deezer.previous: Change binding for previous song (default is left click)
|
|
||||||
* deezer.next: Change binding for next song (default is right click)
|
|
||||||
* deezer.pause: Change binding for toggling pause (default is middle click)
|
|
||||||
Available options for deezer.previous, deezer.next and deezer.pause are:
|
|
||||||
LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
from bumblebee.output import scrollable
|
|
||||||
|
|
||||||
try:
|
|
||||||
import dbus
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.deezer)
|
|
||||||
)
|
|
||||||
buttons = {"LEFT_CLICK":bumblebee.input.LEFT_MOUSE,
|
|
||||||
"RIGHT_CLICK":bumblebee.input.RIGHT_MOUSE,
|
|
||||||
"MIDDLE_CLICK":bumblebee.input.MIDDLE_MOUSE,
|
|
||||||
"SCROLL_UP":bumblebee.input.WHEEL_UP,
|
|
||||||
"SCROLL_DOWN":bumblebee.input.WHEEL_DOWN,
|
|
||||||
}
|
|
||||||
|
|
||||||
self._song = ""
|
|
||||||
self._format = self.parameter("format", "{artist} - {title}")
|
|
||||||
prev_button = self.parameter("previous", "LEFT_CLICK")
|
|
||||||
next_button = self.parameter("next", "RIGHT_CLICK")
|
|
||||||
pause_button = self.parameter("pause", "MIDDLE_CLICK")
|
|
||||||
|
|
||||||
cmd = "dbus-send --session --type=method_call --dest=org.mpris.MediaPlayer2.deezer \
|
|
||||||
/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player."
|
|
||||||
engine.input.register_callback(self, button=buttons[prev_button],
|
|
||||||
cmd=cmd + "Previous")
|
|
||||||
engine.input.register_callback(self, button=buttons[next_button],
|
|
||||||
cmd=cmd + "Next")
|
|
||||||
engine.input.register_callback(self, button=buttons[pause_button],
|
|
||||||
cmd=cmd + "PlayPause")
|
|
||||||
|
|
||||||
## @scrollable
|
|
||||||
def deezer(self, widget):
|
|
||||||
return str(self._song)
|
|
||||||
|
|
||||||
def hidden(self):
|
|
||||||
return str(self._song) == ""
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
try:
|
|
||||||
bus = dbus.SessionBus()
|
|
||||||
deezer = bus.get_object("org.mpris.MediaPlayer2.deezer", "/org/mpris/MediaPlayer2")
|
|
||||||
deezer_iface = dbus.Interface(deezer, 'org.freedesktop.DBus.Properties')
|
|
||||||
props = deezer_iface.Get('org.mpris.MediaPlayer2.Player', 'Metadata')
|
|
||||||
playback_status = str(deezer_iface.Get('org.mpris.MediaPlayer2.Player', 'PlaybackStatus'))
|
|
||||||
self._song = self._format.format(album=str(props.get('xesam:album')),
|
|
||||||
title=str(props.get('xesam:title')),
|
|
||||||
artist=','.join(props.get('xesam:artist')),
|
|
||||||
trackNumber=str(props.get('xesam:trackNumber')),
|
|
||||||
playbackStatus=u"\u25B6" if playback_status=="Playing" else u"\u258D\u258D" if playback_status=="Paused" else "",)
|
|
||||||
except Exception:
|
|
||||||
self._song = ""
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,88 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Shows free diskspace, total diskspace and the percentage of free disk space.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* disk.warning: Warning threshold in % of disk space (defaults to 80%)
|
|
||||||
* disk.critical: Critical threshold in % of disk space (defaults ot 90%)
|
|
||||||
* disk.path: Path to calculate disk usage from (defaults to /)
|
|
||||||
* disk.open: Which application / file manager to launch (default xdg-open)
|
|
||||||
* disk.format: Format string, tags {path}, {used}, {left}, {size} and {percent} (defaults to "{path} {used}/{size} ({percent:05.02f}%)")
|
|
||||||
* (deprecated) disk.showUsed: Show used space (defaults to yes)
|
|
||||||
* (deprecated) disk.showSize: Show total size (defaults to yes)
|
|
||||||
* (deprecated) disk.showPercent: Show usage percentage (defaults to yes)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.util
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.diskspace)
|
|
||||||
)
|
|
||||||
self._path = self.parameter("path", "/")
|
|
||||||
self._format = self.parameter("format", "{used}/{size} ({percent:05.02f}%)")
|
|
||||||
self._app = self.parameter("open", "xdg-open")
|
|
||||||
|
|
||||||
self._used = 0
|
|
||||||
self._left = 0
|
|
||||||
self._size = 0
|
|
||||||
self._percent = 0
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="{} {}".format(self._app,
|
|
||||||
self._path))
|
|
||||||
|
|
||||||
|
|
||||||
def diskspace(self, widget):
|
|
||||||
used_str = bumblebee.util.bytefmt(self._used)
|
|
||||||
size_str = bumblebee.util.bytefmt(self._size)
|
|
||||||
left_str = bumblebee.util.bytefmt(self._left)
|
|
||||||
percent_str = self._percent
|
|
||||||
|
|
||||||
sused = self.has_parameter("showUsed")
|
|
||||||
ssize = self.has_parameter("showSize")
|
|
||||||
spercent = self.has_parameter("showPercent")
|
|
||||||
|
|
||||||
if all(not param for param in (sused, ssize, spercent)):
|
|
||||||
return self._format.format(path=self._path,
|
|
||||||
used=used_str,
|
|
||||||
left=left_str,
|
|
||||||
size=size_str,
|
|
||||||
percent=percent_str)
|
|
||||||
else:
|
|
||||||
rv = ""
|
|
||||||
sused = bumblebee.util.asbool(self.parameter("showUsed", True))
|
|
||||||
ssize = bumblebee.util.asbool(self.parameter("showSize", True))
|
|
||||||
spercent = bumblebee.util.asbool(self.parameter("showPercent", True))
|
|
||||||
|
|
||||||
if sused:
|
|
||||||
rv = "{}{}".format(rv, used_str)
|
|
||||||
if sused and ssize:
|
|
||||||
rv = "{}/".format(rv)
|
|
||||||
if ssize:
|
|
||||||
rv = "{}{}".format(rv, size_str)
|
|
||||||
if spercent:
|
|
||||||
if not sused and not ssize:
|
|
||||||
rv = "{:05.02f}%".format(percent_str)
|
|
||||||
else:
|
|
||||||
rv = "{} ({:05.02f}%)".format(rv, percent_str)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
st = os.statvfs(self._path)
|
|
||||||
self._size = st.f_blocks * st.f_frsize
|
|
||||||
self._used = (st.f_blocks - st.f_bfree) * st.f_frsize
|
|
||||||
self._left = self._size - self._used;
|
|
||||||
self._percent = 100.0 * self._used/self._size
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
return self.threshold_state(self._percent, 80, 90)
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,77 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays DNF package update information (<security>/<bugfixes>/<enhancements>/<other>)
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* dnf
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* dnf.interval: Time in minutes between two consecutive update checks (defaults to 30 minutes)
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import threading
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
def get_dnf_info(widget):
|
|
||||||
try:
|
|
||||||
res = bumblebee.util.execute("dnf updateinfo")
|
|
||||||
except RuntimeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
security = 0
|
|
||||||
bugfixes = 0
|
|
||||||
enhancements = 0
|
|
||||||
other = 0
|
|
||||||
for line in res.split("\n"):
|
|
||||||
|
|
||||||
if not line.startswith(" "): continue
|
|
||||||
elif "ecurity" in line:
|
|
||||||
for s in line.split():
|
|
||||||
if s.isdigit(): security += int(s)
|
|
||||||
elif "ugfix" in line:
|
|
||||||
for s in line.split():
|
|
||||||
if s.isdigit(): bugfixes += int(s)
|
|
||||||
elif "hancement" in line:
|
|
||||||
for s in line.split():
|
|
||||||
if s.isdigit(): enhancements += int(s)
|
|
||||||
else:
|
|
||||||
for s in line.split():
|
|
||||||
if s.isdigit(): other += int(s)
|
|
||||||
|
|
||||||
widget.set("security", security)
|
|
||||||
widget.set("bugfixes", bugfixes)
|
|
||||||
widget.set("enhancements", enhancements)
|
|
||||||
widget.set("other", other)
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widget = bumblebee.output.Widget(full_text=self.updates)
|
|
||||||
super(Module, self).__init__(engine, config, widget)
|
|
||||||
self.interval_factor(60)
|
|
||||||
self.interval(30)
|
|
||||||
|
|
||||||
def updates(self, widget):
|
|
||||||
result = []
|
|
||||||
for t in ["security", "bugfixes", "enhancements", "other"]:
|
|
||||||
result.append(str(widget.get(t, 0)))
|
|
||||||
return "/".join(result)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
thread = threading.Thread(target=get_dnf_info, args=(widgets[0],))
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
cnt = 0
|
|
||||||
for t in ["security", "bugfixes", "enhancements", "other"]:
|
|
||||||
cnt += widget.get(t, 0)
|
|
||||||
if cnt == 0:
|
|
||||||
return "good"
|
|
||||||
if cnt > 50 or widget.get("security", 0) > 0:
|
|
||||||
return "critical"
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,39 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""Displays the number of docker containers running
|
|
||||||
|
|
||||||
Requires the following python packages:
|
|
||||||
* docker
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
import docker
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from requests.exceptions import ConnectionError
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widget = bumblebee.output.Widget(full_text=self.status)
|
|
||||||
super(Module, self).__init__(engine, config, widget)
|
|
||||||
self._status = self.status
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._status = self.status
|
|
||||||
|
|
||||||
def status(self, _):
|
|
||||||
try:
|
|
||||||
cli = docker.DockerClient(base_url='unix://var/run/docker.sock')
|
|
||||||
cli.ping()
|
|
||||||
except ConnectionError:
|
|
||||||
return "Daemon off"
|
|
||||||
except Exception:
|
|
||||||
return "n/a"
|
|
||||||
return "OK - {}".format(len(cli.containers.list(filters={'status': "running"})))
|
|
|
@ -1,39 +0,0 @@
|
||||||
#pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Toggle dunst notifications."""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text="")
|
|
||||||
)
|
|
||||||
self._paused = False
|
|
||||||
# Make sure that dunst is currently not paused
|
|
||||||
try:
|
|
||||||
bumblebee.util.execute("killall -SIGUSR2 dunst")
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self.toggle_status
|
|
||||||
)
|
|
||||||
|
|
||||||
def toggle_status(self, event):
|
|
||||||
self._paused = not self._paused
|
|
||||||
|
|
||||||
try:
|
|
||||||
if self._paused:
|
|
||||||
bumblebee.util.execute("killall -SIGUSR1 dunst")
|
|
||||||
else:
|
|
||||||
bumblebee.util.execute("killall -SIGUSR2 dunst")
|
|
||||||
except:
|
|
||||||
self._paused = not self._paused # toggling failed
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if self._paused:
|
|
||||||
return ["muted", "warning"]
|
|
||||||
return ["unmuted"]
|
|
|
@ -1,26 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Draws an error widget.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.text)
|
|
||||||
)
|
|
||||||
self._text = ""
|
|
||||||
|
|
||||||
def set(self, text):
|
|
||||||
self._text = text
|
|
||||||
|
|
||||||
def text(self, widget):
|
|
||||||
return self._text
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
return ["critical"]
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,75 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the price of a cryptocurrency.
|
|
||||||
|
|
||||||
Requires the following python packages:
|
|
||||||
* requests
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* getcrypto.interval: Interval in seconds for updating the price, default is 120, less than that will probably get your IP banned.
|
|
||||||
* getcrypto.getbtc: 0 for not getting price of BTC, 1 for getting it (default).
|
|
||||||
* getcrypto.geteth: 0 for not getting price of ETH, 1 for getting it (default).
|
|
||||||
* getcrypto.getltc: 0 for not getting price of LTC, 1 for getting it (default).
|
|
||||||
* getcrypto.getcur: Set the currency to display the price in, usd is the default.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import time
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
from requests.exceptions import RequestException
|
|
||||||
def getfromkrak(coin, currency):
|
|
||||||
abbrev = {
|
|
||||||
"Btc": ["xbt", "XXBTZ"],
|
|
||||||
"Eth": ["eth", "XETHZ"],
|
|
||||||
"Ltc": ["ltc", "XLTCZ"],
|
|
||||||
}
|
|
||||||
data = abbrev.get(coin, None)
|
|
||||||
if not data: return
|
|
||||||
epair = "{}{}".format(data[0], currency)
|
|
||||||
tickname = "{}{}".format(data[1], currency.upper())
|
|
||||||
try:
|
|
||||||
krakenget = requests.get('https://api.kraken.com/0/public/Ticker?pair='+epair).json()
|
|
||||||
except (RequestException, Exception):
|
|
||||||
return "No connection"
|
|
||||||
if not 'result' in krakenget:
|
|
||||||
return "No data"
|
|
||||||
kethusdask = float(krakenget['result'][tickname]['a'][0])
|
|
||||||
kethusdbid = float(krakenget['result'][tickname]['b'][0])
|
|
||||||
return coin+": "+str((kethusdask+kethusdbid)/2)[0:6]
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.curprice)
|
|
||||||
)
|
|
||||||
self._curprice = ""
|
|
||||||
self._nextcheck = 0
|
|
||||||
self._interval = int(self.parameter("interval", "120"))
|
|
||||||
self._getbtc = bumblebee.util.asbool(self.parameter("getbtc", True))
|
|
||||||
self._geteth = bumblebee.util.asbool(self.parameter("geteth", True))
|
|
||||||
self._getltc = bumblebee.util.asbool(self.parameter("getltc", True))
|
|
||||||
self._getcur = self.parameter("getcur", "usd")
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="xdg-open https://cryptowat.ch/")
|
|
||||||
|
|
||||||
def curprice(self, widget):
|
|
||||||
return self._curprice
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
if self._nextcheck < int(time.time()):
|
|
||||||
self._nextcheck = int(time.time()) + self._interval
|
|
||||||
currency = self._getcur
|
|
||||||
btcprice, ethprice, ltcprice = "", "", ""
|
|
||||||
if self._getbtc:
|
|
||||||
btcprice = getfromkrak('Btc', currency)
|
|
||||||
if self._geteth:
|
|
||||||
ethprice = getfromkrak('Eth', currency)
|
|
||||||
if self._getltc:
|
|
||||||
ltcprice = getfromkrak('Ltc', currency)
|
|
||||||
self._curprice = btcprice+" "*(self._getbtc*self._geteth)+ethprice+" "*(self._getltc*max(self._getbtc, self._geteth))+ltcprice
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,81 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Print the branch and git status for the
|
|
||||||
currently focused window.
|
|
||||||
|
|
||||||
Requires:
|
|
||||||
* xcwd
|
|
||||||
* Python module 'pygit2'
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import string
|
|
||||||
import logging
|
|
||||||
try:
|
|
||||||
import pygit2
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.util
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widgets = []
|
|
||||||
super(Module, self).__init__(engine, config, widgets)
|
|
||||||
self._engine = engine
|
|
||||||
self._error = False
|
|
||||||
self.update(self.widgets())
|
|
||||||
|
|
||||||
def hidden(self):
|
|
||||||
return self._error
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
state = {}
|
|
||||||
new_widgets = []
|
|
||||||
try:
|
|
||||||
directory = bumblebee.util.execute("xcwd").strip()
|
|
||||||
directory = self._get_git_root(directory)
|
|
||||||
repo = pygit2.Repository(directory)
|
|
||||||
|
|
||||||
new_widgets.append(bumblebee.output.Widget(name='git.main', full_text=repo.head.shorthand))
|
|
||||||
|
|
||||||
for filepath, flags in repo.status().items():
|
|
||||||
if flags == pygit2.GIT_STATUS_WT_NEW or \
|
|
||||||
flags == pygit2.GIT_STATUS_INDEX_NEW:
|
|
||||||
state['new'] = True
|
|
||||||
if flags == pygit2.GIT_STATUS_WT_DELETED or \
|
|
||||||
flags == pygit2.GIT_STATUS_INDEX_DELETED:
|
|
||||||
state['deleted'] = True
|
|
||||||
if flags == pygit2.GIT_STATUS_WT_MODIFIED or \
|
|
||||||
flags == pygit2.GIT_STATUS_INDEX_MODIFIED:
|
|
||||||
state['modified'] = True
|
|
||||||
self._error = False
|
|
||||||
if 'new' in state:
|
|
||||||
new_widgets.append(bumblebee.output.Widget(name='git.new'))
|
|
||||||
if 'modified' in state:
|
|
||||||
new_widgets.append(bumblebee.output.Widget(name='git.modified'))
|
|
||||||
if 'deleted' in state:
|
|
||||||
new_widgets.append(bumblebee.output.Widget(name='git.deleted'))
|
|
||||||
|
|
||||||
while len(widgets) > 0:
|
|
||||||
del widgets[0]
|
|
||||||
for widget in new_widgets:
|
|
||||||
widgets.append(widget)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self._error = True
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
return widget.name.split('.')[1]
|
|
||||||
|
|
||||||
def _get_git_root(self, directory):
|
|
||||||
while len(directory) > 1:
|
|
||||||
if os.path.exists(os.path.join(directory, ".git")):
|
|
||||||
return directory
|
|
||||||
directory = "/".join(directory.split("/")[0:-1])
|
|
||||||
return "/"
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,56 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the unread GitHub notifications for a GitHub user
|
|
||||||
|
|
||||||
Requires the following library:
|
|
||||||
* requests
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* github.token: GitHub user access token, the token needs to have the 'notifications' scope.
|
|
||||||
* github.interval: Interval in minutes between updates, default is 5.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
try:
|
|
||||||
import requests
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.github)
|
|
||||||
)
|
|
||||||
self._count = 0
|
|
||||||
self.interval_factor(60)
|
|
||||||
self.interval(5)
|
|
||||||
self._requests = requests.Session()
|
|
||||||
self._requests.headers.update({"Authorization":"token {}".format(self.parameter("token", ""))})
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="x-www-browser https://github.com/notifications")
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self.update)
|
|
||||||
|
|
||||||
def github(self, _):
|
|
||||||
return str(self._count)
|
|
||||||
|
|
||||||
def update(self, _):
|
|
||||||
try:
|
|
||||||
self._count = 0
|
|
||||||
url = "https://api.github.com/notifications"
|
|
||||||
while True:
|
|
||||||
notifications = self._requests.get(url)
|
|
||||||
self._count += len(list(filter(lambda notification: notification['unread'], notifications.json())))
|
|
||||||
next_link = notifications.links.get('next')
|
|
||||||
if next_link is not None:
|
|
||||||
url = next_link.get('url')
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
self._count = "n/a"
|
|
||||||
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,56 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays information about the current song in Google Play music player.
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* gpmdp-remote
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widgets = [
|
|
||||||
bumblebee.output.Widget(name="gpmdp.prev"),
|
|
||||||
bumblebee.output.Widget(name="gpmdp.main", full_text=self.description),
|
|
||||||
bumblebee.output.Widget(name="gpmdp.next"),
|
|
||||||
]
|
|
||||||
super(Module, self).__init__(engine, config, widgets)
|
|
||||||
|
|
||||||
engine.input.register_callback(widgets[0], button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="playerctl previous")
|
|
||||||
engine.input.register_callback(widgets[1], button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="playerctl play-pause")
|
|
||||||
engine.input.register_callback(widgets[2], button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="playerctl next")
|
|
||||||
|
|
||||||
self._status = None
|
|
||||||
self._tags = None
|
|
||||||
|
|
||||||
def description(self, widget):
|
|
||||||
return self._tags if self._tags else "n/a"
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._load_song()
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if widget.name == "gpmdp.prev":
|
|
||||||
return "prev"
|
|
||||||
if widget.name == "gpmdp.next":
|
|
||||||
return "next"
|
|
||||||
return self._status
|
|
||||||
|
|
||||||
def _load_song(self):
|
|
||||||
info = ""
|
|
||||||
try:
|
|
||||||
info = bumblebee.util.execute("gpmdp-remote current")
|
|
||||||
status = bumblebee.util.execute("gpmdp-remote status")
|
|
||||||
except RuntimeError:
|
|
||||||
pass
|
|
||||||
self._status = status.split("\n")[0].lower()
|
|
||||||
self._tags = info.split("\n")[0]
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,90 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""Fetch hard drive temeperature data from a hddtemp daemon
|
|
||||||
that runs on localhost and default port (7634)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import socket
|
|
||||||
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.output
|
|
||||||
|
|
||||||
HOST = "localhost"
|
|
||||||
PORT = 7634
|
|
||||||
|
|
||||||
CHUNK_SIZE = 1024
|
|
||||||
RECORD_SIZE = 5
|
|
||||||
SEPARATOR = "|"
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widget = bumblebee.output.Widget(full_text=self.hddtemps)
|
|
||||||
super(Module, self).__init__(engine, config, widget)
|
|
||||||
self._hddtemps = self._get_hddtemps()
|
|
||||||
|
|
||||||
def hddtemps(self, __):
|
|
||||||
return self._hddtemps
|
|
||||||
|
|
||||||
def _fetch_data(self):
|
|
||||||
"""fetch data from hddtemp service"""
|
|
||||||
try:
|
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
||||||
sock.connect((HOST, PORT))
|
|
||||||
data = ""
|
|
||||||
while True:
|
|
||||||
chunk = sock.recv(CHUNK_SIZE)
|
|
||||||
if chunk:
|
|
||||||
data += str(chunk)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return data
|
|
||||||
except (AttributeError, socket.error) as e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_parts(data):
|
|
||||||
"""
|
|
||||||
split data using | separator and remove first item
|
|
||||||
(because the first item is empty)
|
|
||||||
"""
|
|
||||||
parts = data.split("|")[1:]
|
|
||||||
return parts
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _partition_parts(parts):
|
|
||||||
"""
|
|
||||||
partition parts: one device record is five (5) items
|
|
||||||
"""
|
|
||||||
per_disk = [parts[i:i+RECORD_SIZE]
|
|
||||||
for i in range(len(parts))[::RECORD_SIZE]]
|
|
||||||
return per_disk
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_name_and_temp(device_record):
|
|
||||||
"""
|
|
||||||
get device name (without /dev part, to save space on bar)
|
|
||||||
and temperature (in °C) as tuple
|
|
||||||
"""
|
|
||||||
device_name = device_record[0].split("/")[-1]
|
|
||||||
device_temp = device_record[2]
|
|
||||||
return (device_name, device_temp)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_hddtemp(device_record):
|
|
||||||
name, temp = device_record
|
|
||||||
hddtemp = "{}+{}°C".format(name, temp)
|
|
||||||
return hddtemp
|
|
||||||
|
|
||||||
def _get_hddtemps(self):
|
|
||||||
data = self._fetch_data()
|
|
||||||
if data is None:
|
|
||||||
return "n/a"
|
|
||||||
parts = self._get_parts(data)
|
|
||||||
per_disk = self._partition_parts(parts)
|
|
||||||
names_and_temps = [self._get_name_and_temp(x) for x in per_disk]
|
|
||||||
hddtemps = [self._get_hddtemp(x) for x in names_and_temps]
|
|
||||||
return SEPARATOR.join(hddtemps)
|
|
||||||
|
|
||||||
def update(self, __):
|
|
||||||
self._hddtemps = self._get_hddtemps()
|
|
|
@ -1,24 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the system hostname."""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.output)
|
|
||||||
)
|
|
||||||
self._hname = ""
|
|
||||||
|
|
||||||
def output(self, _):
|
|
||||||
return self._hname+" "+u"\uf233"
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
with open('/proc/sys/kernel/hostname', 'r') as f:
|
|
||||||
self._hname = f.readline().split()[0]
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,68 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Display HTTP status code
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* http_status.label: Prefix label (optional)
|
|
||||||
* http_status.target: Target to retrieve the HTTP status from
|
|
||||||
* http_status.expect: Expected HTTP status
|
|
||||||
"""
|
|
||||||
|
|
||||||
from requests import head
|
|
||||||
|
|
||||||
import psutil
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
UNK = "UNK"
|
|
||||||
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widget = bumblebee.output.Widget(full_text=self.output)
|
|
||||||
super(Module, self).__init__(engine, config, widget)
|
|
||||||
|
|
||||||
self._label = self.parameter("label")
|
|
||||||
self._target = self.parameter("target")
|
|
||||||
self._expect = self.parameter("expect", "200")
|
|
||||||
self._status = self.getStatus()
|
|
||||||
self._output = self.getOutput()
|
|
||||||
|
|
||||||
def labelize(self, s):
|
|
||||||
if self._label is None:
|
|
||||||
return s
|
|
||||||
return "{}: {}".format(self._label, s)
|
|
||||||
|
|
||||||
def getStatus(self):
|
|
||||||
try:
|
|
||||||
res = head(self._target)
|
|
||||||
except Exception:
|
|
||||||
return self.UNK
|
|
||||||
else:
|
|
||||||
status = str(res.status_code)
|
|
||||||
self._status = status
|
|
||||||
return status
|
|
||||||
|
|
||||||
def getOutput(self):
|
|
||||||
if self._status == self._expect:
|
|
||||||
return self.labelize(self._status)
|
|
||||||
else:
|
|
||||||
reason = " != {}".format(self._expect)
|
|
||||||
return self.labelize("{}{}".format(self._status, reason))
|
|
||||||
|
|
||||||
def output(self, widget):
|
|
||||||
return self._output
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self.getStatus()
|
|
||||||
self._output = self.getOutput()
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if self._status == self.UNK:
|
|
||||||
return "warning"
|
|
||||||
if self._status != self._expect:
|
|
||||||
return "critical"
|
|
||||||
return self._output
|
|
||||||
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,51 +0,0 @@
|
||||||
#pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the indicator status, for numlock, scrolllock and capslock
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* indicator.include: Comma-separated list of interface prefixes to include (defaults to "numlock,capslock")
|
|
||||||
* indicator.signalstype: If you want the signali type color to be "critical" or "warning" (defaults to "warning")
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widgets = []
|
|
||||||
self.status = False
|
|
||||||
super(Module,self).__init__(engine, config, widgets)
|
|
||||||
self._include = tuple(filter(len, self.parameter("include", "NumLock,CapsLock").split(",")))
|
|
||||||
self._signalType = self.parameter("signaltype") if not self.parameter("signaltype") is None else "warning"
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._update_widgets(widgets)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
states = []
|
|
||||||
if widget.status:
|
|
||||||
states.append(self._signalType)
|
|
||||||
elif not widget.status:
|
|
||||||
states.append("normal")
|
|
||||||
return states
|
|
||||||
|
|
||||||
def _update_widgets(self, widgets):
|
|
||||||
status_line = ""
|
|
||||||
for line in bumblebee.util.execute("xset q").replace(" ", "").split("\n"):
|
|
||||||
if "capslock" in line.lower():
|
|
||||||
status_line = line
|
|
||||||
break
|
|
||||||
|
|
||||||
for indicator in self._include:
|
|
||||||
widget = self.widget(indicator)
|
|
||||||
if not widget:
|
|
||||||
widget = bumblebee.output.Widget(name=indicator)
|
|
||||||
widgets.append(widget)
|
|
||||||
|
|
||||||
widget.status = True if indicator.lower()+":on" in status_line.lower() else False
|
|
||||||
widget.full_text(indicator)
|
|
||||||
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,21 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Shows Linux kernel version information"""
|
|
||||||
|
|
||||||
import platform
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.output)
|
|
||||||
)
|
|
||||||
self._release_name = platform.release()
|
|
||||||
|
|
||||||
def output(self, widget):
|
|
||||||
return self._release_name
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,65 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the current keyboard layout using libX11
|
|
||||||
|
|
||||||
Requires the following library:
|
|
||||||
* libX11.so.6
|
|
||||||
and python module:
|
|
||||||
* xkbgroup
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* layout-xkb.showname: Boolean that indicate whether the full name should be displayed. Defaults to false (only the symbol will be displayed)
|
|
||||||
* layout-xkb.show_variant: Boolean that indecates whether the variant name should be displayed. Defaults to true.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
has_xkb = True
|
|
||||||
try:
|
|
||||||
from xkbgroup import *
|
|
||||||
except ImportError:
|
|
||||||
has_xkb = False
|
|
||||||
|
|
||||||
import logging
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.current_layout)
|
|
||||||
)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self._next_keymap)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE,
|
|
||||||
cmd=self._prev_keymap)
|
|
||||||
self._show_variant = bumblebee.util.asbool(self.parameter("show_variant", "true"))
|
|
||||||
|
|
||||||
def _next_keymap(self, event):
|
|
||||||
self._set_keymap(1)
|
|
||||||
|
|
||||||
def _prev_keymap(self, event):
|
|
||||||
self._set_keymap(-1)
|
|
||||||
|
|
||||||
def _set_keymap(self, rotation):
|
|
||||||
if not has_xkb: return
|
|
||||||
|
|
||||||
xkb = XKeyboard()
|
|
||||||
if xkb.groups_count < 2: return # nothing to doA
|
|
||||||
layouts = xkb.groups_symbols
|
|
||||||
idx = layouts.index(xkb.group_symbol)
|
|
||||||
xkb.group_symbol = str(layouts[(idx + rotation) % len(layouts)])
|
|
||||||
|
|
||||||
def current_layout(self, widget):
|
|
||||||
try:
|
|
||||||
xkb = XKeyboard()
|
|
||||||
log.debug("group num: {}".format(xkb.group_num))
|
|
||||||
name = xkb.group_name if bumblebee.util.asbool(self.parameter("showname")) else xkb.group_symbol
|
|
||||||
if self._show_variant:
|
|
||||||
return "{} ({})".format(name, xkb.group_variant) if xkb.group_variant else name
|
|
||||||
return name
|
|
||||||
except Exception:
|
|
||||||
return "n/a"
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,40 +0,0 @@
|
||||||
"""Displays and changes the current keyboard layout
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* xkb-switch
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widget = bumblebee.output.Widget(full_text=self.current_layout)
|
|
||||||
super(Module, self).__init__(engine, config, widget)
|
|
||||||
engine.input.register_callback(
|
|
||||||
self,
|
|
||||||
button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self._next_keymap)
|
|
||||||
self._current_layout = self._get_current_layout()
|
|
||||||
|
|
||||||
def current_layout(self, __):
|
|
||||||
return self._current_layout
|
|
||||||
|
|
||||||
def _next_keymap(self, event):
|
|
||||||
try:
|
|
||||||
bumblebee.util.execute("xkb-switch -n")
|
|
||||||
except RuntimeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _get_current_layout(self):
|
|
||||||
try:
|
|
||||||
res = bumblebee.util.execute("xkb-switch")
|
|
||||||
return res.split("\n")[0]
|
|
||||||
except RuntimeError:
|
|
||||||
return ["n/a"]
|
|
||||||
|
|
||||||
def update(self, __):
|
|
||||||
self._current_layout = self._get_current_layout()
|
|
|
@ -1,72 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays and changes the current keyboard layout
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* setxkbmap
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.current_layout)
|
|
||||||
)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self._next_keymap)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE,
|
|
||||||
cmd=self._prev_keymap)
|
|
||||||
|
|
||||||
def _next_keymap(self, event):
|
|
||||||
self._set_keymap(1)
|
|
||||||
|
|
||||||
def _prev_keymap(self, event):
|
|
||||||
self._set_keymap(-1)
|
|
||||||
|
|
||||||
def _set_keymap(self, rotation):
|
|
||||||
layouts = self.get_layouts()
|
|
||||||
if len(layouts) == 1: return # nothing to do
|
|
||||||
layouts = layouts[rotation:] + layouts[:rotation]
|
|
||||||
|
|
||||||
layout_list = []
|
|
||||||
variant_list = []
|
|
||||||
for l in layouts:
|
|
||||||
tmp = l.split(":")
|
|
||||||
layout_list.append(tmp[0])
|
|
||||||
variant_list.append(tmp[1] if len(tmp) > 1 else "")
|
|
||||||
|
|
||||||
try:
|
|
||||||
bumblebee.util.execute("setxkbmap -layout {} -variant {}".format(",".join(layout_list), ",".join(variant_list)))
|
|
||||||
except RuntimeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_layouts(self):
|
|
||||||
try:
|
|
||||||
res = bumblebee.util.execute("setxkbmap -query")
|
|
||||||
except RuntimeError:
|
|
||||||
return ["n/a"]
|
|
||||||
layouts = []
|
|
||||||
variants = []
|
|
||||||
for line in res.split("\n"):
|
|
||||||
if not line: continue
|
|
||||||
if "layout" in line:
|
|
||||||
layouts = line.split(":")[1].strip().split(",")
|
|
||||||
if "variant" in line:
|
|
||||||
variants = line.split(":")[1].strip().split(",")
|
|
||||||
|
|
||||||
result = []
|
|
||||||
for idx, layout in enumerate(layouts):
|
|
||||||
if len(variants) > idx and variants[idx]:
|
|
||||||
layout = "{}:{}".format(layout, variants[idx])
|
|
||||||
result.append(layout)
|
|
||||||
return result if len(result) > 0 else ["n/a"]
|
|
||||||
|
|
||||||
def current_layout(self, widget):
|
|
||||||
layouts = self.get_layouts()
|
|
||||||
return layouts[0]
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,41 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays system load.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* load.warning : Warning threshold for the one-minute load average (defaults to 70% of the number of CPUs)
|
|
||||||
* load.critical: Critical threshold for the one-minute load average (defaults to 80% of the number of CPUs)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import multiprocessing
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.load)
|
|
||||||
)
|
|
||||||
self._load = [0, 0, 0]
|
|
||||||
try:
|
|
||||||
self._cpus = multiprocessing.cpu_count()
|
|
||||||
except NotImplementedError as e:
|
|
||||||
self._cpus = 1
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="gnome-system-monitor")
|
|
||||||
|
|
||||||
def load(self, widget):
|
|
||||||
return "{:.02f}/{:.02f}/{:.02f}".format(
|
|
||||||
self._load[0], self._load[1], self._load[2]
|
|
||||||
)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._load = os.getloadavg()
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
return self.threshold_state(self._load[0], self._cpus*0.7, self._cpus*0.8)
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,72 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays available RAM, total amount of RAM and percentage available.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* memory.warning : Warning threshold in % of memory used (defaults to 80%)
|
|
||||||
* memory.critical: Critical threshold in % of memory used (defaults to 90%)
|
|
||||||
* memory.format: Format string (defaults to "{used}/{total} ({percent:05.02f}%)")
|
|
||||||
* memory.usedonly: Only show the amount of RAM in use (defaults to False). Same as memory.format="{used}"
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Container(object):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self.__dict__.update(kwargs)
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.memory_usage)
|
|
||||||
)
|
|
||||||
self.update(None)
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="gnome-system-monitor")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _format(self):
|
|
||||||
if bumblebee.util.asbool(self.parameter("usedonly", False)):
|
|
||||||
return "{used}"
|
|
||||||
else:
|
|
||||||
return self.parameter("format", "{used}/{total} ({percent:05.02f}%)")
|
|
||||||
|
|
||||||
def memory_usage(self, widget):
|
|
||||||
return self._format.format(**self._mem)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
data = {}
|
|
||||||
with open("/proc/meminfo", "r") as f:
|
|
||||||
for line in f:
|
|
||||||
tmp = re.split(r"[:\s]+", line)
|
|
||||||
value = int(tmp[1])
|
|
||||||
if tmp[2] == "kB": value = value*1024
|
|
||||||
if tmp[2] == "mB": value = value*1024*1024
|
|
||||||
if tmp[2] == "gB": value = value*1024*1024*1024
|
|
||||||
data[tmp[0]] = value
|
|
||||||
if "MemAvailable" in data:
|
|
||||||
used = data["MemTotal"] - data["MemAvailable"]
|
|
||||||
else:
|
|
||||||
used = data["MemTotal"] - data["MemFree"] - data["Buffers"] - data["Cached"] - data["Slab"]
|
|
||||||
self._mem = {
|
|
||||||
"total": bumblebee.util.bytefmt(data["MemTotal"]),
|
|
||||||
"available": bumblebee.util.bytefmt(data["MemAvailable"]),
|
|
||||||
"free": bumblebee.util.bytefmt(data["MemFree"]),
|
|
||||||
"used": bumblebee.util.bytefmt(used),
|
|
||||||
"percent": float(used)/float(data["MemTotal"])*100.0
|
|
||||||
}
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if self._mem["percent"] > float(self.parameter("critical", 90)):
|
|
||||||
return "critical"
|
|
||||||
if self._mem["percent"] > float(self.parameter("warning", 80)):
|
|
||||||
return "warning"
|
|
||||||
return None
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,60 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""Displays information about the current song in mocp. Left click toggles play/pause. Right click toggles shuffle.
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* mocp
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* mocp.format: Format string for the song information. Replace string sequences with the actual information:
|
|
||||||
%state State
|
|
||||||
%file File
|
|
||||||
%title Title, includes track, artist, song title and album
|
|
||||||
%artist Artist
|
|
||||||
%song SongTitle
|
|
||||||
%album Album
|
|
||||||
%tt TotalTime
|
|
||||||
%tl TimeLeft
|
|
||||||
%ts TotalSec
|
|
||||||
%ct CurrentTime
|
|
||||||
%cs CurrentSec
|
|
||||||
%b Bitrate
|
|
||||||
%r Sample rate
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
from bumblebee.output import scrollable
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(name="mocp.main", full_text=self.description)
|
|
||||||
)
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="mocp -G")
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE,
|
|
||||||
cmd="mocp -t shuffle")
|
|
||||||
self._format = self.parameter("format", "%state %artist - %song | %ct/%tt")
|
|
||||||
self._running = 0
|
|
||||||
|
|
||||||
#@scrollable
|
|
||||||
def description(self, widget):
|
|
||||||
return self._info if self._running == 1 else "Music On Console Player"
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._load_song()
|
|
||||||
|
|
||||||
def _load_song(self):
|
|
||||||
try:
|
|
||||||
self._info = bumblebee.util.execute("mocp -Q '" + self._format + "'" ).strip()
|
|
||||||
self._running = 1
|
|
||||||
except RuntimeError:
|
|
||||||
self._running = 0
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,181 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""Displays information about the current song in mpd.
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* mpc
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* mpd.format: Format string for the song information.
|
|
||||||
Supported tags (see `man mpc` for additional information)
|
|
||||||
* {name}
|
|
||||||
* {artist}
|
|
||||||
* {album}
|
|
||||||
* {albumartist}
|
|
||||||
* {comment}
|
|
||||||
* {composer}
|
|
||||||
* {date}
|
|
||||||
* {originaldate}
|
|
||||||
* {disc}
|
|
||||||
* {genre}
|
|
||||||
* {performer}
|
|
||||||
* {title}
|
|
||||||
* {track}
|
|
||||||
* {time}
|
|
||||||
* {file}
|
|
||||||
* {id}
|
|
||||||
* {prio}
|
|
||||||
* {mtime}
|
|
||||||
* {mdate}
|
|
||||||
Additional tags:
|
|
||||||
* {position} - position of currently playing song
|
|
||||||
not to be confused with %position% mpc tag
|
|
||||||
* {duration} - duration of currently playing song
|
|
||||||
* {file1} - song file name without path prefix
|
|
||||||
if {file} = '/foo/bar.baz', then {file1} = 'bar.baz'
|
|
||||||
* {file2} - song file name without path prefix and extension suffix
|
|
||||||
if {file} = '/foo/bar.baz', then {file2} = 'bar'
|
|
||||||
* mpd.host: MPD host to connect to. (mpc behaviour by default)
|
|
||||||
* mpd.layout: Space-separated list of widgets to add. Possible widgets are the buttons/toggles mpd.prev, mpd.next, mpd.shuffle and mpd.repeat, and the main display with play/pause function mpd.main.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
import string
|
|
||||||
import os
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
from bumblebee.output import scrollable
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config, [])
|
|
||||||
|
|
||||||
self._layout = self.parameter("layout", "mpd.prev mpd.main mpd.next mpd.shuffle mpd.repeat")
|
|
||||||
|
|
||||||
self._fmt = self.parameter("format", "{artist} - {title} {position}/{duration}")
|
|
||||||
self._status = None
|
|
||||||
self._shuffle = False
|
|
||||||
self._repeat = False
|
|
||||||
self._tags = defaultdict(lambda: '')
|
|
||||||
|
|
||||||
if not self.parameter("host"):
|
|
||||||
self._hostcmd = ""
|
|
||||||
else:
|
|
||||||
self._hostcmd = " -h " + self.parameter("host")
|
|
||||||
|
|
||||||
# Create widgets
|
|
||||||
widget_list = []
|
|
||||||
widget_map = {}
|
|
||||||
for widget_name in self._layout.split():
|
|
||||||
widget = bumblebee.output.Widget(name=widget_name)
|
|
||||||
widget_list.append(widget)
|
|
||||||
|
|
||||||
if widget_name == "mpd.prev":
|
|
||||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc prev" + self._hostcmd}
|
|
||||||
elif widget_name == "mpd.main":
|
|
||||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc toggle" + self._hostcmd}
|
|
||||||
widget.full_text(self.description)
|
|
||||||
elif widget_name == "mpd.next":
|
|
||||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc next" + self._hostcmd}
|
|
||||||
elif widget_name == "mpd.shuffle":
|
|
||||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc random" + self._hostcmd}
|
|
||||||
elif widget_name == "mpd.repeat":
|
|
||||||
widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc repeat" + self._hostcmd}
|
|
||||||
else:
|
|
||||||
raise KeyError("The mpd module does not support a {widget_name!r} widget".format(widget_name=widget_name))
|
|
||||||
self.widgets(widget_list)
|
|
||||||
|
|
||||||
# Register input callbacks
|
|
||||||
for widget, callback_options in widget_map.items():
|
|
||||||
engine.input.register_callback(widget, **callback_options)
|
|
||||||
|
|
||||||
def hidden(self):
|
|
||||||
return self._status is None
|
|
||||||
|
|
||||||
@scrollable
|
|
||||||
def description(self, widget):
|
|
||||||
return string.Formatter().vformat(self._fmt, (), self._tags)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._load_song()
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if widget.name == "mpd.shuffle":
|
|
||||||
return "shuffle-on" if self._shuffle else "shuffle-off"
|
|
||||||
if widget.name == "mpd.repeat":
|
|
||||||
return "repeat-on" if self._repeat else "repeat-off"
|
|
||||||
if widget.name == "mpd.prev":
|
|
||||||
return "prev"
|
|
||||||
if widget.name == "mpd.next":
|
|
||||||
return "next"
|
|
||||||
return self._status
|
|
||||||
|
|
||||||
def _load_song(self):
|
|
||||||
info = ""
|
|
||||||
try:
|
|
||||||
tags = ['name',
|
|
||||||
'artist',
|
|
||||||
'album',
|
|
||||||
'albumartist',
|
|
||||||
'comment',
|
|
||||||
'composer',
|
|
||||||
'date',
|
|
||||||
'originaldate',
|
|
||||||
'disc',
|
|
||||||
'genre',
|
|
||||||
'performer',
|
|
||||||
'title',
|
|
||||||
'track',
|
|
||||||
'time',
|
|
||||||
'file',
|
|
||||||
'id',
|
|
||||||
'prio',
|
|
||||||
'mtime',
|
|
||||||
'mdate']
|
|
||||||
joinedtags = "\n".join(["tag {0} %{0}%".format(tag) for tag in tags])
|
|
||||||
info = bumblebee.util.execute('mpc -f ' + '"' + joinedtags + '"' + self._hostcmd)
|
|
||||||
except RuntimeError:
|
|
||||||
pass
|
|
||||||
self._tags = defaultdict(lambda: '')
|
|
||||||
self._status = None
|
|
||||||
for line in info.split("\n"):
|
|
||||||
if line.startswith("[playing]"):
|
|
||||||
self._status = "playing"
|
|
||||||
elif line.startswith("[paused]"):
|
|
||||||
self._status = "paused"
|
|
||||||
|
|
||||||
if line.startswith("["):
|
|
||||||
timer = line.split()[2]
|
|
||||||
position = timer.split("/")[0]
|
|
||||||
dur = timer.split("/")[1]
|
|
||||||
duration = dur.split(" ")[0]
|
|
||||||
self._tags.update({'position': position})
|
|
||||||
self._tags.update({'duration': duration})
|
|
||||||
|
|
||||||
if line.startswith("volume"):
|
|
||||||
value = line.split(" ", 2)[1:]
|
|
||||||
for option in value:
|
|
||||||
if option.startswith("repeat: on"):
|
|
||||||
self._repeat = True
|
|
||||||
elif option.startswith("repeat: off"):
|
|
||||||
self._repeat = False
|
|
||||||
elif option.startswith("random: on"):
|
|
||||||
self._shuffle = True
|
|
||||||
elif option.startswith("random: off"):
|
|
||||||
self._shuffle = False
|
|
||||||
if line.startswith("tag"):
|
|
||||||
key, value = line.split(" ", 2)[1:]
|
|
||||||
self._tags.update({key: value})
|
|
||||||
if key == "file":
|
|
||||||
self._tags.update({"file1": os.path.basename(value)})
|
|
||||||
self._tags.update(
|
|
||||||
{"file2":
|
|
||||||
os.path.splitext(os.path.basename(value))[0]})
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,114 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""Displays network traffic
|
|
||||||
* No extra configuration needed
|
|
||||||
"""
|
|
||||||
|
|
||||||
import psutil
|
|
||||||
import netifaces
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.util
|
|
||||||
|
|
||||||
WIDGET_NAME = 'network_traffic'
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
"""Bumblebee main module """
|
|
||||||
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._bandwidth = BandwidthInfo()
|
|
||||||
|
|
||||||
self._bytes_recv = self._bandwidth.bytes_recv()
|
|
||||||
self._bytes_sent = self._bandwidth.bytes_sent()
|
|
||||||
except Exception:
|
|
||||||
""" We do not want do explode anything """
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def state(cls, widget):
|
|
||||||
"""Return the widget state"""
|
|
||||||
|
|
||||||
if widget.name == '{}.rx'.format(WIDGET_NAME):
|
|
||||||
return 'rx'
|
|
||||||
elif widget.name == '{}.tx'.format(WIDGET_NAME):
|
|
||||||
return 'tx'
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
try:
|
|
||||||
bytes_recv = self._bandwidth.bytes_recv()
|
|
||||||
bytes_sent = self._bandwidth.bytes_sent()
|
|
||||||
|
|
||||||
download_rate = (bytes_recv - self._bytes_recv)
|
|
||||||
upload_rate = (bytes_sent - self._bytes_sent)
|
|
||||||
|
|
||||||
self.update_widgets(widgets, download_rate, upload_rate)
|
|
||||||
|
|
||||||
self._bytes_recv, self._bytes_sent = bytes_recv, bytes_sent
|
|
||||||
except Exception:
|
|
||||||
""" We do not want do explode anything """
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def update_widgets(cls, widgets, download_rate, upload_rate):
|
|
||||||
"""Update tx/rx widgets with new rates"""
|
|
||||||
del widgets[:]
|
|
||||||
|
|
||||||
widgets.extend((
|
|
||||||
TrafficWidget(text=download_rate, direction='rx'),
|
|
||||||
TrafficWidget(text=upload_rate, direction='tx')
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
class BandwidthInfo(object):
|
|
||||||
"""Get received/sent bytes from network adapter"""
|
|
||||||
|
|
||||||
def bytes_recv(self):
|
|
||||||
"""Return received bytes"""
|
|
||||||
return self.bandwidth().bytes_recv
|
|
||||||
|
|
||||||
def bytes_sent(self):
|
|
||||||
"""Return sent bytes"""
|
|
||||||
return self.bandwidth().bytes_sent
|
|
||||||
|
|
||||||
def bandwidth(self):
|
|
||||||
"""Return bandwidth information"""
|
|
||||||
io_counters = self.io_counters()
|
|
||||||
return io_counters[self.default_network_adapter()]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def default_network_adapter(cls):
|
|
||||||
"""Return default active network adapter"""
|
|
||||||
gateway = netifaces.gateways()['default']
|
|
||||||
|
|
||||||
if not gateway:
|
|
||||||
raise 'No default gateway found'
|
|
||||||
|
|
||||||
return gateway[netifaces.AF_INET][1]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def io_counters(cls):
|
|
||||||
"""Return IO counters"""
|
|
||||||
return psutil.net_io_counters(pernic=True)
|
|
||||||
|
|
||||||
class TrafficWidget(object):
|
|
||||||
"""Create a traffic widget with humanized bytes string with proper icon (up/down)"""
|
|
||||||
def __new__(cls, text, direction):
|
|
||||||
widget = bumblebee.output.Widget(name='{0}.{1}'.format(WIDGET_NAME, direction))
|
|
||||||
widget.set('theme.minwidth', '0000000KiB/s')
|
|
||||||
widget.full_text(cls.humanize(text))
|
|
||||||
|
|
||||||
return widget
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def humanize(text):
|
|
||||||
"""Return humanized bytes"""
|
|
||||||
humanized_byte_format = bumblebee.util.bytefmt(text)
|
|
||||||
return '{0}/s'.format(humanized_byte_format)
|
|
|
@ -1,119 +0,0 @@
|
||||||
#pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the name, IP address(es) and status of each available network interface.
|
|
||||||
|
|
||||||
Requires the following python module:
|
|
||||||
* netifaces
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* nic.exclude: Comma-separated list of interface prefixes to exclude (defaults to "lo,virbr,docker,vboxnet,veth,br")
|
|
||||||
* nic.include: Comma-separated list of interfaces to include
|
|
||||||
* nic.states: Comma-separated list of states to show (prefix with "^" to invert - i.e. ^down -> show all devices that are not in state down)
|
|
||||||
* nic.format: Format string (defaults to "{intf} {state} {ip} {ssid}")
|
|
||||||
"""
|
|
||||||
|
|
||||||
import netifaces
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widgets = []
|
|
||||||
super(Module, self).__init__(engine, config, widgets)
|
|
||||||
self._exclude = tuple(filter(len, self.parameter("exclude", "lo,virbr,docker,vboxnet,veth,br").split(",")))
|
|
||||||
self._include = self.parameter("include", "").split(",")
|
|
||||||
|
|
||||||
self._states = {}
|
|
||||||
self._states["include"] = []
|
|
||||||
self._states["exclude"] = []
|
|
||||||
for state in tuple(filter(len, self.parameter("states", "").split(","))):
|
|
||||||
if state[0] == "^":
|
|
||||||
self._states["exclude"].append(state[1:])
|
|
||||||
else:
|
|
||||||
self._states["include"].append(state)
|
|
||||||
self._format = self.parameter("format","{intf} {state} {ip} {ssid}");
|
|
||||||
self._update_widgets(widgets)
|
|
||||||
self.iwgetid = bumblebee.util.which("iwgetid")
|
|
||||||
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._update_widgets(widgets)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
states = []
|
|
||||||
|
|
||||||
if widget.get("state") == "down":
|
|
||||||
states.append("critical")
|
|
||||||
elif widget.get("state") != "up":
|
|
||||||
states.append("warning")
|
|
||||||
|
|
||||||
intf = widget.get("intf")
|
|
||||||
iftype = "wireless" if self._iswlan(intf) else "wired"
|
|
||||||
iftype = "tunnel" if self._istunnel(intf) else iftype
|
|
||||||
|
|
||||||
states.append("{}-{}".format(iftype, widget.get("state")))
|
|
||||||
|
|
||||||
return states
|
|
||||||
|
|
||||||
def _iswlan(self, intf):
|
|
||||||
# wifi, wlan, wlp, seems to work for me
|
|
||||||
if intf.startswith("w"): return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _istunnel(self, intf):
|
|
||||||
return intf.startswith("tun") or intf.startswith("wg")
|
|
||||||
|
|
||||||
def get_addresses(self, intf):
|
|
||||||
retval = []
|
|
||||||
try:
|
|
||||||
for ip in netifaces.ifaddresses(intf).get(netifaces.AF_INET, []):
|
|
||||||
if ip.get("addr", "") != "":
|
|
||||||
retval.append(ip.get("addr"))
|
|
||||||
except Exception:
|
|
||||||
return []
|
|
||||||
return retval
|
|
||||||
|
|
||||||
def _update_widgets(self, widgets):
|
|
||||||
interfaces = [i for i in netifaces.interfaces() if not i.startswith(self._exclude)]
|
|
||||||
interfaces.extend([i for i in netifaces.interfaces() if i in self._include])
|
|
||||||
|
|
||||||
for widget in widgets:
|
|
||||||
widget.set("visited", False)
|
|
||||||
|
|
||||||
for intf in interfaces:
|
|
||||||
addr = []
|
|
||||||
state = "down"
|
|
||||||
for ip in self.get_addresses(intf):
|
|
||||||
addr.append(ip)
|
|
||||||
state = "up"
|
|
||||||
|
|
||||||
if len(self._states["exclude"]) > 0 and state in self._states["exclude"]: continue
|
|
||||||
if len(self._states["include"]) > 0 and state not in self._states["include"]: continue
|
|
||||||
|
|
||||||
widget = self.widget(intf)
|
|
||||||
if not widget:
|
|
||||||
widget = bumblebee.output.Widget(name=intf)
|
|
||||||
widgets.append(widget)
|
|
||||||
# join/split is used to get rid of multiple whitespaces (in case SSID is not available, for instance
|
|
||||||
widget.full_text(" ".join(self._format.format(ip=", ".join(addr),intf=intf,state=state,ssid=self.get_ssid(intf)).split()))
|
|
||||||
widget.set("intf", intf)
|
|
||||||
widget.set("state", state)
|
|
||||||
widget.set("visited", True)
|
|
||||||
|
|
||||||
for widget in widgets:
|
|
||||||
if widget.get("visited") is False:
|
|
||||||
widgets.remove(widget)
|
|
||||||
|
|
||||||
def get_ssid(self, intf):
|
|
||||||
if self._iswlan(intf):
|
|
||||||
try:
|
|
||||||
return subprocess.check_output([self.iwgetid,"-r",intf]).strip().decode('utf-8')
|
|
||||||
except:
|
|
||||||
return ""
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,51 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the result of a notmuch count query
|
|
||||||
default : unread emails which path do not contained "Trash" (notmuch count "tag:unread AND NOT path:/.*Trash.*/")
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* notmuch_count.query: notmuch count query to show result
|
|
||||||
|
|
||||||
Errors:
|
|
||||||
if the notmuch query failed, the shown value is -1
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
notmuch (https://notmuchmail.org/)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import os
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.output)
|
|
||||||
)
|
|
||||||
self._notmuch_count_query = self.parameter("query", "tag:unread AND NOT path:/.*Trash.*/")
|
|
||||||
self._notmuch_count = self.count_notmuch()
|
|
||||||
|
|
||||||
|
|
||||||
def output(self, widget):
|
|
||||||
self._notmuch_count = self.count_notmuch()
|
|
||||||
return str(self._notmuch_count)
|
|
||||||
|
|
||||||
|
|
||||||
def state(self, widgets):
|
|
||||||
if self._notmuch_count == 0:
|
|
||||||
return "empty"
|
|
||||||
return "items"
|
|
||||||
|
|
||||||
|
|
||||||
def count_notmuch(self):
|
|
||||||
try:
|
|
||||||
notmuch_count_cmd = "notmuch count " + self._notmuch_count_query
|
|
||||||
notmuch_count = int(bumblebee.util.execute(notmuch_count_cmd))
|
|
||||||
return notmuch_count
|
|
||||||
except Exception:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,71 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""Displays GPU name, temperature and memory usage.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* nvidiagpu.format: Format string (defaults to "{name}: {temp}°C %{usedmem}/{totalmem} MiB")
|
|
||||||
Available values are: {name} {temp} {mem_used} {mem_total} {fanspeed} {clock_gpu} {clock_mem}
|
|
||||||
|
|
||||||
Requires nvidia-smi
|
|
||||||
"""
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config, bumblebee.output.Widget(full_text=self.utilization))
|
|
||||||
self._utilization = "Not found: 0 0/0"
|
|
||||||
|
|
||||||
def utilization(self, widget):
|
|
||||||
return self._utilization
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
sp = subprocess.Popen(['nvidia-smi', '-q'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
out_str = sp.communicate()
|
|
||||||
out_list = out_str[0].decode("utf-8").split('\n')
|
|
||||||
|
|
||||||
title = ""
|
|
||||||
usedMem = ""
|
|
||||||
totalMem = ""
|
|
||||||
temp = ""
|
|
||||||
name = "not found"
|
|
||||||
clockMem = ""
|
|
||||||
clockGpu = ""
|
|
||||||
fanspeed = ""
|
|
||||||
for item in out_list:
|
|
||||||
try:
|
|
||||||
key, val = item.split(':')
|
|
||||||
key, val = key.strip(), val.strip()
|
|
||||||
if title == "Clocks":
|
|
||||||
if key == "Graphics":
|
|
||||||
clockGpu = val.split(" ")[0]
|
|
||||||
elif key == "Memory":
|
|
||||||
clockMem = val.split(" ")[0]
|
|
||||||
if title == "FB Memory Usage":
|
|
||||||
if key == "Total":
|
|
||||||
totalMem = val.split(" ")[0]
|
|
||||||
elif key == "Used":
|
|
||||||
usedMem = val.split(" ")[0]
|
|
||||||
elif key == "GPU Current Temp":
|
|
||||||
temp = val.split(" ")[0]
|
|
||||||
elif key == "Product Name":
|
|
||||||
name = val
|
|
||||||
elif key == "Fan Speed":
|
|
||||||
fanspeed = val.split(" ")[0]
|
|
||||||
|
|
||||||
except:
|
|
||||||
title = item.strip()
|
|
||||||
|
|
||||||
str_format = self.parameter("format", '{name}: {temp}°C {mem_used}/{mem_total} MiB')
|
|
||||||
self._utilization = str_format.format(
|
|
||||||
name = name,
|
|
||||||
temp = temp,
|
|
||||||
mem_used = usedMem,
|
|
||||||
mem_total = totalMem,
|
|
||||||
clock_gpu = clockGpu,
|
|
||||||
clock_mem = clockMem,
|
|
||||||
fanspeed = fanspeed,
|
|
||||||
)
|
|
|
@ -1,76 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays update information per repository for pacman.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* pacman.sum: If you prefere displaying updates with a single digit (defaults to "False")
|
|
||||||
|
|
||||||
Requires the following executables:
|
|
||||||
* fakeroot
|
|
||||||
* pacman
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import threading
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
#list of repositories.
|
|
||||||
#the last one should always be other
|
|
||||||
repos = ["core", "extra", "community", "multilib", "testing", "other"]
|
|
||||||
|
|
||||||
def get_pacman_info(widget, path):
|
|
||||||
try:
|
|
||||||
result = bumblebee.util.execute("{}/../../bin/pacman-updates".format(path))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
count = len(repos)*[0]
|
|
||||||
|
|
||||||
for line in result.splitlines():
|
|
||||||
if line.startswith(("http", "rsync")):
|
|
||||||
for i in range(len(repos)-1):
|
|
||||||
if "/" + repos[i] + "/" in line:
|
|
||||||
count[i] += 1
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
result[-1] += 1
|
|
||||||
|
|
||||||
for i in range(len(repos)):
|
|
||||||
widget.set(repos[i], count[i])
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.updates)
|
|
||||||
)
|
|
||||||
self._count = 0
|
|
||||||
|
|
||||||
def updates(self, widget):
|
|
||||||
if bumblebee.util.asbool(self.parameter("sum")):
|
|
||||||
return str(sum(map(lambda x: widget.get(x, 0), repos)))
|
|
||||||
return '/'.join(map(lambda x: str(widget.get(x, 0)), repos))
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
path = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
if self._count == 0:
|
|
||||||
thread = threading.Thread(target=get_pacman_info, args=(widgets[0], path))
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
# TODO: improve this waiting mechanism a bit
|
|
||||||
self._count += 1
|
|
||||||
self._count = 0 if self._count > 300 else self._count
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
weightedCount = sum(map(lambda x: (len(repos)-x[0]) * widget.get(x[1], 0), enumerate(repos)))
|
|
||||||
|
|
||||||
if weightedCount < 10:
|
|
||||||
return "good"
|
|
||||||
|
|
||||||
return self.threshold_state(weightedCount, 100, 150)
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,68 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the pi-hole status (up/down) together with the number of ads that were blocked today
|
|
||||||
Parameters:
|
|
||||||
* pihole.address : pi-hole address (e.q: http://192.168.1.3)
|
|
||||||
* pihole.pwhash : pi-hole webinterface password hash (can be obtained from the /etc/pihole/SetupVars.conf file)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import requests
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.pihole_status)
|
|
||||||
)
|
|
||||||
|
|
||||||
buttons = {"LEFT_CLICK":bumblebee.input.LEFT_MOUSE}
|
|
||||||
self._pihole_address = self.parameter("address", "")
|
|
||||||
|
|
||||||
self._pihole_pw_hash = self.parameter("pwhash", "")
|
|
||||||
self._pihole_status = None
|
|
||||||
self._ads_blocked_today = "-"
|
|
||||||
self.update_pihole_status()
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self.toggle_pihole_status)
|
|
||||||
|
|
||||||
def pihole_status(self, widget):
|
|
||||||
if self._pihole_status is None:
|
|
||||||
return "pi-hole unknown"
|
|
||||||
return "pi-hole " + ("up/" + self._ads_blocked_today if self._pihole_status else "down")
|
|
||||||
|
|
||||||
def update_pihole_status(self):
|
|
||||||
try:
|
|
||||||
data = requests.get(self._pihole_address + "/admin/api.php?summary").json()
|
|
||||||
self._pihole_status = True if data["status"] == "enabled" else False
|
|
||||||
self._ads_blocked_today = data["ads_blocked_today"]
|
|
||||||
except:
|
|
||||||
self._pihole_status = None
|
|
||||||
|
|
||||||
def toggle_pihole_status(self, widget):
|
|
||||||
if self._pihole_status is not None:
|
|
||||||
try:
|
|
||||||
req = None
|
|
||||||
if self._pihole_status:
|
|
||||||
req = requests.get(self._pihole_address + "/admin/api.php?disable&auth=" + self._pihole_pw_hash)
|
|
||||||
else:
|
|
||||||
req = requests.get(self._pihole_address + "/admin/api.php?enable&auth=" + self._pihole_pw_hash)
|
|
||||||
if req is not None:
|
|
||||||
if req.status_code == 200:
|
|
||||||
status = req.json()["status"]
|
|
||||||
self._pihole_status = False if status == "disabled" else True
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self.update_pihole_status()
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if self._pihole_status is None:
|
|
||||||
return []
|
|
||||||
elif self._pihole_status:
|
|
||||||
return ["enabled"]
|
|
||||||
return ["disabled", "warning"]
|
|
|
@ -1,84 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Periodically checks the RTT of a configurable host using ICMP echos
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* ping
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* ping.interval: Time in seconds between two RTT checks (defaults to 60)
|
|
||||||
* ping.address : IP address to check
|
|
||||||
* ping.timeout : Timeout for waiting for a reply (defaults to 5.0)
|
|
||||||
* ping.probes : Number of probes to send (defaults to 5)
|
|
||||||
* ping.warning : Threshold for warning state, in seconds (defaults to 1.0)
|
|
||||||
* ping.critical: Threshold for critical state, in seconds (defaults to 2.0)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
def get_rtt(module, widget):
|
|
||||||
try:
|
|
||||||
widget.set("rtt-unreachable", False)
|
|
||||||
res = bumblebee.util.execute("ping -n -q -c {} -W {} {}".format(
|
|
||||||
widget.get("rtt-probes"), widget.get("rtt-timeout"), widget.get("address")
|
|
||||||
))
|
|
||||||
|
|
||||||
for line in res.split("\n"):
|
|
||||||
if line.startswith("{} packets transmitted".format(widget.get("rtt-probes"))):
|
|
||||||
m = re.search(r'(\d+)% packet loss', line)
|
|
||||||
|
|
||||||
widget.set('packet-loss', m.group(1))
|
|
||||||
|
|
||||||
if not line.startswith("rtt"): continue
|
|
||||||
m = re.search(r'([0-9\.]+)/([0-9\.]+)/([0-9\.]+)/([0-9\.]+)\s+(\S+)', line)
|
|
||||||
|
|
||||||
widget.set("rtt-min", float(m.group(1)))
|
|
||||||
widget.set("rtt-avg", float(m.group(2)))
|
|
||||||
widget.set("rtt-max", float(m.group(3)))
|
|
||||||
widget.set("rtt-unit", m.group(5))
|
|
||||||
except Exception as e:
|
|
||||||
widget.set("rtt-unreachable", True)
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widget = bumblebee.output.Widget(full_text=self.rtt)
|
|
||||||
super(Module, self).__init__(engine, config, widget)
|
|
||||||
|
|
||||||
widget.set("address", self.parameter("address", "8.8.8.8"))
|
|
||||||
widget.set("interval", self.parameter("interval", 60))
|
|
||||||
widget.set("rtt-probes", self.parameter("probes", 5))
|
|
||||||
widget.set("rtt-timeout", self.parameter("timeout", 5.0))
|
|
||||||
widget.set("rtt-avg", 0.0)
|
|
||||||
widget.set("rtt-unit", "")
|
|
||||||
widget.set('packet-loss', 0)
|
|
||||||
|
|
||||||
self._next_check = 0
|
|
||||||
|
|
||||||
def rtt(self, widget):
|
|
||||||
if widget.get("rtt-unreachable"):
|
|
||||||
return "{}: unreachable".format(widget.get("address"))
|
|
||||||
return "{}: {:.1f}{} ({}%)".format(
|
|
||||||
widget.get("address"),
|
|
||||||
widget.get("rtt-avg"),
|
|
||||||
widget.get("rtt-unit"),
|
|
||||||
widget.get('packet-loss')
|
|
||||||
)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if widget.get("rtt-unreachable"): return ["critical"]
|
|
||||||
return self.threshold_state(widget.get("rtt-avg"), 1000.0, 2000.0)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
if int(time.time()) < self._next_check:
|
|
||||||
return
|
|
||||||
thread = threading.Thread(target=get_rtt, args=(self, widgets[0],))
|
|
||||||
thread.start()
|
|
||||||
self._next_check = int(time.time()) + int(widgets[0].get("interval"))
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,119 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Display and run a Pomodoro timer.
|
|
||||||
Left click to start timer, left click again to pause.
|
|
||||||
Right click will cancel the timer.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* pomodoro.work: The work duration of timer in minutes (defaults to 25)
|
|
||||||
* pomodoro.break: The break duration of timer in minutes (defaults to 5)
|
|
||||||
* pomodoro.format: Timer display format with "%m" and "%s" for minutes and seconds (defaults to "%m:%s")
|
|
||||||
Examples: "%m min %s sec", "%mm", "", "timer"
|
|
||||||
* pomodoro.notify: Notification command to run when timer ends/starts (defaults to nothing)
|
|
||||||
Example: 'notify-send "Time up!"'
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
import datetime
|
|
||||||
from math import ceil
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widgets = bumblebee.output.Widget(full_text=self.text)
|
|
||||||
|
|
||||||
super(Module, self).__init__(engine, config, widgets)
|
|
||||||
|
|
||||||
# Parameters
|
|
||||||
self._work_period = int(self.parameter("work", 25))
|
|
||||||
self._break_period = int(self.parameter("break", 5))
|
|
||||||
self._time_format = self.parameter("format", "%m:%s")
|
|
||||||
self._notify_cmd = self.parameter("notify", "")
|
|
||||||
|
|
||||||
# TODO: Handle time formats more gracefully. This is kludge.
|
|
||||||
self.display_seconds_p = False
|
|
||||||
self.display_minutes_p = False
|
|
||||||
if "%s" in self._time_format:
|
|
||||||
self.display_seconds_p = True
|
|
||||||
if "%m" in self._time_format:
|
|
||||||
self.display_minutes_p = True
|
|
||||||
|
|
||||||
self.remaining_time = datetime.timedelta(minutes=self._work_period)
|
|
||||||
|
|
||||||
self.time = None
|
|
||||||
self.pomodoro = { "state":"OFF", "type": ""}
|
|
||||||
self._text = self.remaining_time_str() + self.pomodoro["type"]
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self.timer_play_pause)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE,
|
|
||||||
cmd=self.timer_reset)
|
|
||||||
|
|
||||||
def remaining_time_str(self):
|
|
||||||
|
|
||||||
if self.display_seconds_p and self.display_minutes_p:
|
|
||||||
minutes, seconds = divmod(self.remaining_time.seconds, 60)
|
|
||||||
if not self.display_seconds_p:
|
|
||||||
minutes = ceil(self.remaining_time.seconds / 60)
|
|
||||||
seconds = 0
|
|
||||||
if not self.display_minutes_p:
|
|
||||||
minutes = 0
|
|
||||||
seconds = self.remaining_time.seconds
|
|
||||||
|
|
||||||
minutes = "{:2d}".format(minutes)
|
|
||||||
seconds = "{:02d}".format(seconds)
|
|
||||||
return self._time_format.replace("%m",minutes).replace("%s",seconds)+" "
|
|
||||||
|
|
||||||
def text(self, widget):
|
|
||||||
return "{}".format(self._text)
|
|
||||||
|
|
||||||
def update(self, widget):
|
|
||||||
if self.pomodoro["state"] == "ON":
|
|
||||||
timediff = (datetime.datetime.now() - self.time)
|
|
||||||
if timediff.seconds >= 0:
|
|
||||||
self.remaining_time -= timediff
|
|
||||||
self.time = datetime.datetime.now()
|
|
||||||
|
|
||||||
if self.remaining_time.seconds <= 0:
|
|
||||||
self.notify()
|
|
||||||
if self.pomodoro["type"] == "Work":
|
|
||||||
self.pomodoro["type"] = "Break"
|
|
||||||
self.remaining_time = datetime.timedelta(minutes=self._break_period)
|
|
||||||
elif self.pomodoro["type"] == "Break":
|
|
||||||
self.pomodoro["type"] = "Work"
|
|
||||||
self.remaining_time = datetime.timedelta(minutes=self._work_period)
|
|
||||||
|
|
||||||
self._text = self.remaining_time_str() + self.pomodoro["type"]
|
|
||||||
|
|
||||||
def notify(self):
|
|
||||||
if self._notify_cmd:
|
|
||||||
bumblebee.util.execute(self._notify_cmd)
|
|
||||||
|
|
||||||
def timer_play_pause(self, widget):
|
|
||||||
if self.pomodoro["state"] == "OFF":
|
|
||||||
self.pomodoro = {"state": "ON", "type": "Work"}
|
|
||||||
self.remaining_time = datetime.timedelta(minutes=self._work_period)
|
|
||||||
self.time = datetime.datetime.now()
|
|
||||||
elif self.pomodoro["state"] == "ON":
|
|
||||||
self.pomodoro["state"] = "PAUSED"
|
|
||||||
self.remaining_time -= (datetime.datetime.now() - self.time)
|
|
||||||
self.time = datetime.datetime.now()
|
|
||||||
elif self.pomodoro["state"] == "PAUSED":
|
|
||||||
self.pomodoro["state"] = "ON"
|
|
||||||
self.time = datetime.datetime.now()
|
|
||||||
|
|
||||||
def timer_reset(self, widget):
|
|
||||||
if self.pomodoro["state"] == "ON" or self.pomodoro["state"] == "PAUSED":
|
|
||||||
self.pomodoro = {"state":"OFF", "type": "" }
|
|
||||||
self.remaining_time = datetime.timedelta(minutes=self._work_period)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
state = [];
|
|
||||||
state.append(self.pomodoro["state"].lower())
|
|
||||||
if self.pomodoro["state"] == "ON" or self.pomodoro["state"] == "OFF":
|
|
||||||
state.append(self.pomodoro["type"].lower())
|
|
||||||
|
|
||||||
return state
|
|
|
@ -1,69 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays and changes the current selected prime video card
|
|
||||||
|
|
||||||
Left click will call 'sudo prime-select nvidia'
|
|
||||||
Right click will call 'sudo prime-select nvidia'
|
|
||||||
|
|
||||||
Running these commands without a password requires editing your sudoers file
|
|
||||||
(always use visudo, it's very easy to make a mistake and get locked out of your computer!)
|
|
||||||
|
|
||||||
sudo visudo -f /etc/sudoers.d/prime
|
|
||||||
|
|
||||||
Then put a line like this in there:
|
|
||||||
|
|
||||||
user ALL=(ALL) NOPASSWD: /usr/bin/prime-select
|
|
||||||
|
|
||||||
If you can't figure out the sudoers thing, then don't worry, it's still really useful.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* prime.nvidiastring: String to use when nvidia is selected (defaults to "intel")
|
|
||||||
* prime.intelstring: String to use when intel is selected (defaults to "intel")
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* prime-select
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.query)
|
|
||||||
)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self._chooseNvidia)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE,
|
|
||||||
cmd=self._chooseIntel)
|
|
||||||
|
|
||||||
self.nvidiastring = self.parameter("nvidiastring", "nv")
|
|
||||||
self.intelstring = self.parameter("intelstring", "it")
|
|
||||||
|
|
||||||
def _chooseNvidia(self, event):
|
|
||||||
bumblebee.util.execute("sudo prime-select nvidia")
|
|
||||||
|
|
||||||
def _chooseIntel(self, event):
|
|
||||||
bumblebee.util.execute("sudo prime-select intel")
|
|
||||||
|
|
||||||
def _prev_keymap(self, event):
|
|
||||||
self._set_keymap(-1)
|
|
||||||
|
|
||||||
def query(self, widget):
|
|
||||||
try:
|
|
||||||
res = bumblebee.util.execute("prime-select query")
|
|
||||||
except RuntimeError:
|
|
||||||
return "n/a"
|
|
||||||
|
|
||||||
for line in res.split("\n"):
|
|
||||||
if not line: continue
|
|
||||||
if "nvidia" in line:
|
|
||||||
return self.nvidiastring
|
|
||||||
if "intel" in line:
|
|
||||||
return self.intelstring
|
|
||||||
return "n/a"
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,103 +0,0 @@
|
||||||
"""
|
|
||||||
Show progress for cp, mv, dd, ...
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* progress.placeholder: Text to display while no process is running (defaults to "n/a")
|
|
||||||
* progress.barwidth: Width of the progressbar if it is used (defaults to 8)
|
|
||||||
* progress.format: Format string (defaults to "{bar} {cmd} {arg}")
|
|
||||||
Available values are: {bar} {pid} {cmd} {arg} {percentage} {quantity} {speed} {time}
|
|
||||||
* progress.barfilledchar: Character used to draw the filled part of the bar (defaults to "#"), notice that it can be a string
|
|
||||||
* progress.baremptychar: Character used to draw the empty part of the bar (defaults to "-"), notice that it can be a string
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* progress
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.get_progress_text)
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_progress_text(self, widget):
|
|
||||||
if self.update_progress_info(widget):
|
|
||||||
width = self.parameter("barwidth", 8)
|
|
||||||
count = round((width * widget.get("per")) / 100)
|
|
||||||
filledchar = self.parameter("barfilledchar", "#")
|
|
||||||
emptychar = self.parameter("baremptychar", "-")
|
|
||||||
|
|
||||||
bar = "[{}{}]".format(
|
|
||||||
filledchar * count,
|
|
||||||
emptychar * (width - count)
|
|
||||||
)
|
|
||||||
|
|
||||||
str_format = self.parameter("format", '{bar} {cmd} {arg}')
|
|
||||||
return str_format.format(
|
|
||||||
bar = bar,
|
|
||||||
pid = widget.get("pid"),
|
|
||||||
cmd = widget.get("cmd"),
|
|
||||||
arg = widget.get("arg"),
|
|
||||||
percentage = widget.get("per"),
|
|
||||||
quantity = widget.get("qty"),
|
|
||||||
speed = widget.get("spd"),
|
|
||||||
time = widget.get("tim")
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return self.parameter("placeholder", 'n/a')
|
|
||||||
|
|
||||||
def update_progress_info(self, widget):
|
|
||||||
"""Update widget's informations about the copy"""
|
|
||||||
|
|
||||||
# These regex extracts following groups:
|
|
||||||
# 1. pid
|
|
||||||
# 2. command
|
|
||||||
# 3. arguments
|
|
||||||
# 4. progress (xx.x formated)
|
|
||||||
# 5. quantity (.. unit / .. unit formated)
|
|
||||||
# 6. speed
|
|
||||||
# 7. time remaining
|
|
||||||
extract_nospeed = re.compile("\[ *(\d*)\] ([a-zA-Z]*) (.*)\n\t(\d*\.*\d*)% \((.*)\)\n.*")
|
|
||||||
extract_wtspeed = re.compile('\[ *(\d*)\] ([a-zA-Z]*) (.*)\n\t(\d*\.*\d*)% \((.*)\) (\d*\.\d .*) remaining (\d*:\d*:\d*)\n.*')
|
|
||||||
|
|
||||||
try:
|
|
||||||
raw = bumblebee.util.execute("progress -qW 0.1")
|
|
||||||
result = extract_wtspeed.match(raw)
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
# Abord speed measures
|
|
||||||
raw = bumblebee.util.execute("progress -q")
|
|
||||||
result = extract_nospeed.match(raw)
|
|
||||||
|
|
||||||
widget.set("spd", "???.? B/s")
|
|
||||||
widget.set("tim", "??:??:??")
|
|
||||||
else:
|
|
||||||
widget.set("spd", result.group(6))
|
|
||||||
widget.set("tim", result.group(7))
|
|
||||||
|
|
||||||
widget.set("pid", int(result.group(1)))
|
|
||||||
widget.set("cmd", result.group(2))
|
|
||||||
widget.set("arg", result.group(3))
|
|
||||||
widget.set("per", float(result.group(4)))
|
|
||||||
widget.set("qty", result.group(5))
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if self._active():
|
|
||||||
return "copying"
|
|
||||||
return "pending"
|
|
||||||
|
|
||||||
def _active(self):
|
|
||||||
"""Checks wether a copy is running or not"""
|
|
||||||
raw = bumblebee.util.execute("progress -q")
|
|
||||||
return bool(raw)
|
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
"""Displays public IP address
|
|
||||||
|
|
||||||
Requires the following python packages:
|
|
||||||
* requests
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* publicip.region: us-central (default), us-east, us-west, uk, de, pl, nl
|
|
||||||
* publicip.service: web address that returns plaintext ip address (ex. "http://l2.io/ip")
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
from requests import get
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.public_ip)
|
|
||||||
)
|
|
||||||
self._avail_regions = {"us-east":"http://checkip.amazonaws.com",
|
|
||||||
"us-central":"http://whatismyip.akamai.com",
|
|
||||||
"us-west":"http://ipv4bot.whatismyipaddress.com",
|
|
||||||
"pl":"http://ip.42.pl/raw",
|
|
||||||
"de":"http://myexternalip.com/raw",
|
|
||||||
"nl":"http://tnx.nl/ip",
|
|
||||||
"uk":"http://ident.me"}
|
|
||||||
self._region = self.parameter("region", "us-central")
|
|
||||||
self._service = self.parameter("service", "")
|
|
||||||
self._ip = ""
|
|
||||||
|
|
||||||
|
|
||||||
def public_ip(self, widget):
|
|
||||||
return self._ip
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
try:
|
|
||||||
if self._service:
|
|
||||||
self.address = self._service
|
|
||||||
else:
|
|
||||||
self.address = self._avail_regions[self._region]
|
|
||||||
self._ip = get(self.address).text.rstrip()
|
|
||||||
except Exception:
|
|
||||||
self._ip = "No Connection"
|
|
|
@ -1,183 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol.
|
|
||||||
|
|
||||||
Aliases: pasink (use this to control output instead of input), pasource
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* pulseaudio.autostart: If set to "true" (default is "false"), automatically starts the pulseaudio daemon if it is not running
|
|
||||||
* pulseaudio.percent_change: How much to change volume by when scrolling on the module (default is 2%)
|
|
||||||
* pulseaudio.limit: Upper limit for setting the volume (default is 0%, which means "no limit")
|
|
||||||
Note: If the left and right channels have different volumes, the limit might not be reached exactly.
|
|
||||||
* pulseaudio.showbars: 1 for showing volume bars, requires --markup=pango;
|
|
||||||
0 for not showing volume bars (default)
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* pulseaudio
|
|
||||||
* pactl
|
|
||||||
* pavucontrol
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.volume)
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
if bumblebee.util.asbool(self.parameter("autostart", False)):
|
|
||||||
bumblebee.util.execute("pulseaudio --start")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self._change = 2
|
|
||||||
self._change = int(self.parameter("percent_change", "2%").strip("%"))
|
|
||||||
if self._change < 0 or self._change > 100:
|
|
||||||
self._change = 2
|
|
||||||
|
|
||||||
self._limit = 0
|
|
||||||
self._limit = int(self.parameter("limit", "0%").strip("%s"))
|
|
||||||
if self._limit < 0:
|
|
||||||
self._limit = 0
|
|
||||||
|
|
||||||
self._left = 0
|
|
||||||
self._right = 0
|
|
||||||
self._mono = 0
|
|
||||||
self._mute = False
|
|
||||||
self._failed = False
|
|
||||||
self._channel = "sink" if self.name == "pasink" else "source"
|
|
||||||
self._showbars = bumblebee.util.asbool(self.parameter("showbars", 0))
|
|
||||||
|
|
||||||
self._patterns = [
|
|
||||||
{"expr": "Name:", "callback": (lambda line: False)},
|
|
||||||
{"expr": "Mute:", "callback": (lambda line: self.mute(False if " no" in line.lower() else True))},
|
|
||||||
{"expr": "Volume:", "callback": self.getvolume},
|
|
||||||
]
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd="pavucontrol")
|
|
||||||
|
|
||||||
events = [
|
|
||||||
{"type": "mute", "action": self.toggle, "button": bumblebee.input.LEFT_MOUSE},
|
|
||||||
{"type": "volume", "action": self.increase_volume, "button": bumblebee.input.WHEEL_UP},
|
|
||||||
{"type": "volume", "action": self.decrease_volume, "button": bumblebee.input.WHEEL_DOWN},
|
|
||||||
]
|
|
||||||
|
|
||||||
for event in events:
|
|
||||||
engine.input.register_callback(self, button=event["button"], cmd=event["action"])
|
|
||||||
|
|
||||||
def set_volume(self, amount):
|
|
||||||
bumblebee.util.execute("pactl set-{}-{} @DEFAULT_{}@ {}".format(
|
|
||||||
self._channel, "volume", self._channel.upper(), amount))
|
|
||||||
|
|
||||||
def increase_volume(self, event):
|
|
||||||
if self._limit > 0: # we need to check the limit
|
|
||||||
left = int(self._left)
|
|
||||||
right = int(self._right)
|
|
||||||
if left + self._change >= self._limit or right + self._change >= self._limit:
|
|
||||||
if left == right:
|
|
||||||
# easy case, just set to limit
|
|
||||||
self.set_volume("{}%".format(self._limit))
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# don't adjust anymore, since i don't know how to update only one channel
|
|
||||||
return
|
|
||||||
|
|
||||||
self.set_volume("+{}%".format(self._change))
|
|
||||||
|
|
||||||
def decrease_volume(self, event):
|
|
||||||
self.set_volume("-{}%".format(self._change))
|
|
||||||
|
|
||||||
def toggle(self, event):
|
|
||||||
bumblebee.util.execute("pactl set-{}-{} @DEFAULT_{}@ {}".format(
|
|
||||||
self._channel, "mute", self._channel.upper(), "toggle"))
|
|
||||||
|
|
||||||
def mute(self, value):
|
|
||||||
self._mute = value
|
|
||||||
|
|
||||||
def getvolume(self, line):
|
|
||||||
if "mono" in line:
|
|
||||||
m = re.search(r'mono:.*\s*\/\s*(\d+)%', line)
|
|
||||||
if m:
|
|
||||||
self._mono = m.group(1)
|
|
||||||
else:
|
|
||||||
m = re.search(r'left:.*\s*\/\s*(\d+)%.*right:.*\s*\/\s*(\d+)%', line)
|
|
||||||
if m:
|
|
||||||
self._left = m.group(1)
|
|
||||||
self._right = m.group(2)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _default_device(self):
|
|
||||||
output = bumblebee.util.execute("pactl info")
|
|
||||||
pattern = "Default {}: ".format("Sink" if self.name == "pasink" else "Source")
|
|
||||||
for line in output.split("\n"):
|
|
||||||
if line.startswith(pattern):
|
|
||||||
return line.replace(pattern, "")
|
|
||||||
return "n/a"
|
|
||||||
|
|
||||||
def volume(self, widget):
|
|
||||||
if self._failed == True:
|
|
||||||
return "n/a"
|
|
||||||
if int(self._mono) > 0:
|
|
||||||
vol = "{}%".format(self._mono)
|
|
||||||
if self._showbars:
|
|
||||||
vol = "{} {}".format(
|
|
||||||
vol, bumblebee.output.hbar(float(self._mono)))
|
|
||||||
return vol
|
|
||||||
elif self._left == self._right:
|
|
||||||
vol = "{}%".format(self._left)
|
|
||||||
if self._showbars:
|
|
||||||
vol = "{} {}".format(
|
|
||||||
vol, bumblebee.output.hbar(float(self._left)))
|
|
||||||
return vol
|
|
||||||
else:
|
|
||||||
if self._showbars:
|
|
||||||
vol = "{} {}{}".format(
|
|
||||||
vol,
|
|
||||||
bumblebee.output.hbar(float(self._left)),
|
|
||||||
bumblebee.output.hbar(float(self._right)))
|
|
||||||
vol = "{}%/{}%".format(self._left, self._right)
|
|
||||||
return vol
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
try:
|
|
||||||
self._failed = False
|
|
||||||
channel = "sinks" if self.name == "pasink" else "sources"
|
|
||||||
device = self._default_device()
|
|
||||||
|
|
||||||
result = bumblebee.util.execute("pactl list {}".format(channel))
|
|
||||||
found = False
|
|
||||||
|
|
||||||
for line in result.split("\n"):
|
|
||||||
if "Name: {}".format(device) in line:
|
|
||||||
found = True
|
|
||||||
continue
|
|
||||||
if found is False:
|
|
||||||
continue
|
|
||||||
for pattern in self._patterns:
|
|
||||||
if not pattern["expr"] in line:
|
|
||||||
continue
|
|
||||||
if pattern["callback"](line) is False and found == True:
|
|
||||||
return
|
|
||||||
except Exception:
|
|
||||||
self._failed = True
|
|
||||||
if bumblebee.util.asbool(self.parameter("autostart", False)):
|
|
||||||
try:
|
|
||||||
bumblebee.util.execute("pulseaudio --start")
|
|
||||||
self.update(widgets)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if self._mute:
|
|
||||||
return ["warning", "muted"]
|
|
||||||
if int(self._left) > int(100):
|
|
||||||
return ["critical", "unmuted"]
|
|
||||||
return ["unmuted"]
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,129 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the current color temperature of redshift
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* redshift
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* redshift.location : location provider, either of "geoclue2" (default), \
|
|
||||||
"ipinfo" (requires the requests package), or "manual"
|
|
||||||
* redshift.lat : latitude if location is set to "manual"
|
|
||||||
* redshift.lon : longitude if location is set to "manual"
|
|
||||||
"""
|
|
||||||
|
|
||||||
import threading
|
|
||||||
try:
|
|
||||||
import requests
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
|
|
||||||
def is_terminated():
|
|
||||||
for thread in threading.enumerate():
|
|
||||||
if thread.name == "MainThread" and not thread.is_alive():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_redshift_value(widget, location, lat, lon):
|
|
||||||
while True:
|
|
||||||
if is_terminated():
|
|
||||||
return
|
|
||||||
widget.get("condition").acquire()
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
widget.get("condition").wait(1)
|
|
||||||
except RuntimeError:
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
widget.get("condition").release()
|
|
||||||
|
|
||||||
command = ["redshift", "-p", "-l"]
|
|
||||||
if location == "manual":
|
|
||||||
command.append(lat + ":" + lon)
|
|
||||||
else:
|
|
||||||
command.append("geoclue2")
|
|
||||||
|
|
||||||
try:
|
|
||||||
res = bumblebee.util.execute(" ".join(command))
|
|
||||||
except Exception:
|
|
||||||
res = ""
|
|
||||||
widget.set("temp", "n/a")
|
|
||||||
widget.set("transition", None)
|
|
||||||
widget.set("state", "day")
|
|
||||||
for line in res.split("\n"):
|
|
||||||
line = line.lower()
|
|
||||||
if "temperature" in line:
|
|
||||||
widget.set("temp", line.split(" ")[2])
|
|
||||||
if "period" in line:
|
|
||||||
state = line.split(" ")[1]
|
|
||||||
if "day" in state:
|
|
||||||
widget.set("state", "day")
|
|
||||||
elif "night" in state:
|
|
||||||
widget.set("state", "night")
|
|
||||||
else:
|
|
||||||
widget.set("state", "transition")
|
|
||||||
widget.set("transition", " ".join(line.split(" ")[2:]))
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widget = bumblebee.output.Widget(full_text=self.text)
|
|
||||||
super(Module, self).__init__(engine, config, widget)
|
|
||||||
|
|
||||||
self._location = self.parameter("location", "geoclue2")
|
|
||||||
self._lat = self.parameter("lat", None)
|
|
||||||
self._lon = self.parameter("lon", None)
|
|
||||||
|
|
||||||
# Even if location method is set to manual, if we have no lat or lon,
|
|
||||||
# fall back to the geoclue2 method.
|
|
||||||
if self._location == "manual" and (self._lat is None
|
|
||||||
or self._lon is None):
|
|
||||||
self._location == "geoclue2"
|
|
||||||
|
|
||||||
if self._location == "ipinfo":
|
|
||||||
try:
|
|
||||||
location_url = "http://ipinfo.io/json"
|
|
||||||
location = requests.get(location_url).json()
|
|
||||||
self._lat, self._lon = location["loc"].split(",")
|
|
||||||
self._lat = str(float(self._lat))
|
|
||||||
self._lon = str(float(self._lon))
|
|
||||||
self._location = "manual"
|
|
||||||
except Exception:
|
|
||||||
# Fall back to geoclue2.
|
|
||||||
self._location = "geoclue2"
|
|
||||||
|
|
||||||
self._text = ""
|
|
||||||
self._condition = threading.Condition()
|
|
||||||
widget.set("condition", self._condition)
|
|
||||||
self._thread = threading.Thread(target=get_redshift_value,
|
|
||||||
args=(widget, self._location,
|
|
||||||
self._lat, self._lon))
|
|
||||||
self._thread.start()
|
|
||||||
self._condition.acquire()
|
|
||||||
self._condition.notify()
|
|
||||||
self._condition.release()
|
|
||||||
|
|
||||||
def text(self, widget):
|
|
||||||
return "{}".format(self._text)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
widget = widgets[0]
|
|
||||||
self._condition.acquire()
|
|
||||||
self._condition.notify()
|
|
||||||
self._condition.release()
|
|
||||||
temp = widget.get("temp", "n/a")
|
|
||||||
self._text = temp
|
|
||||||
transition = widget.get("transition", None)
|
|
||||||
if transition:
|
|
||||||
self._text = "{} {}".format(temp, transition)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
return widget.get("state", None)
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,61 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Shows a widget for each connected screen and allows the user to loop through different orientations.
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* xrandr
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
possible_orientations = ["normal", "left", "inverted", "right"]
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widgets = []
|
|
||||||
self._engine = engine
|
|
||||||
super(Module, self).__init__(engine, config, widgets)
|
|
||||||
self.update_widgets(widgets)
|
|
||||||
|
|
||||||
def update_widgets(self, widgets):
|
|
||||||
for line in bumblebee.util.execute("xrandr -q").split("\n"):
|
|
||||||
if not " connected" in line:
|
|
||||||
continue
|
|
||||||
display = line.split(" ", 2)[0]
|
|
||||||
|
|
||||||
orientation = "normal"
|
|
||||||
for curr_orient in possible_orientations:
|
|
||||||
if((line.split(" ")).count(curr_orient) > 1):
|
|
||||||
orientation = curr_orient
|
|
||||||
break
|
|
||||||
|
|
||||||
widget = self.widget(display)
|
|
||||||
if not widget:
|
|
||||||
widget = bumblebee.output.Widget(full_text=display, name=display)
|
|
||||||
self._engine.input.register_callback(widget, button=bumblebee.input.LEFT_MOUSE, cmd=self._toggle)
|
|
||||||
widget.set("orientation", orientation)
|
|
||||||
widgets.append(widget)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
if len(widgets) <= 0:
|
|
||||||
self.update_widgets(widgets)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
return widget.get("orientation", "normal")
|
|
||||||
|
|
||||||
def _toggle(self, event):
|
|
||||||
widget = self.widget_by_id(event["instance"])
|
|
||||||
|
|
||||||
# compute new orientation based on current orientation
|
|
||||||
idx = possible_orientations.index(widget.get("orientation"))
|
|
||||||
idx = (idx + 1) % len(possible_orientations)
|
|
||||||
new_orientation = possible_orientations[idx]
|
|
||||||
|
|
||||||
widget.set("orientation", new_orientation)
|
|
||||||
|
|
||||||
bumblebee.util.execute("xrandr --output {} --rotation {}".format(widget.name, new_orientation))
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,312 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""RSS news ticker
|
|
||||||
|
|
||||||
Fetches rss news items and shows these as a news ticker.
|
|
||||||
Left-clicking will open the full story in a browser.
|
|
||||||
New stories are highlighted.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* rss.feeds : Space-separated list of RSS URLs
|
|
||||||
* rss.length : Maximum length of the module, default is 60
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
import feedparser
|
|
||||||
DEPENDENCIES_OK = True
|
|
||||||
except ImportError:
|
|
||||||
DEPENDENCIES_OK = False
|
|
||||||
|
|
||||||
import webbrowser
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
import tempfile
|
|
||||||
import logging
|
|
||||||
import random
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
REFRESH_DELAY = 600
|
|
||||||
SCROLL_SPEED = 3
|
|
||||||
LAYOUT_STYLES_ITEMS = [[1,1,1],[3,3,2],[2,3,3],[3,2,3]]
|
|
||||||
HISTORY_FILENAME = ".config/i3/rss.hist"
|
|
||||||
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.ticker_update if DEPENDENCIES_OK else self._show_error)
|
|
||||||
)
|
|
||||||
# Use BBC newsfeed as demo:
|
|
||||||
self._feeds = self.parameter('feeds', 'https://www.espn.com/espn/rss/news').split(" ")
|
|
||||||
self._feeds_to_update = []
|
|
||||||
self._response = ""
|
|
||||||
|
|
||||||
self._max_title_length = int(self.parameter("length", 60))
|
|
||||||
|
|
||||||
self._items = []
|
|
||||||
self._current_item = None
|
|
||||||
|
|
||||||
self._ticker_offset = 0
|
|
||||||
self._pre_delay = 0
|
|
||||||
self._post_delay = 0
|
|
||||||
|
|
||||||
self._state = []
|
|
||||||
|
|
||||||
self._newspaper_filename = tempfile.mktemp('.html')
|
|
||||||
|
|
||||||
self._last_refresh = 0
|
|
||||||
self._last_update = 0
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self._open)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self._create_newspaper)
|
|
||||||
|
|
||||||
self._history = {'ticker': {}, 'newspaper': {}}
|
|
||||||
self._load_history()
|
|
||||||
|
|
||||||
def _load_history(self):
|
|
||||||
if os.path.isfile(self.HISTORY_FILENAME):
|
|
||||||
self._history = json.loads(open(self.HISTORY_FILENAME, "r").read())
|
|
||||||
|
|
||||||
def _update_history(self, group):
|
|
||||||
sources = set([i['source'] for i in self._items])
|
|
||||||
self._history[group] = dict([[s, [i['title'] for i in self._items if i['source'] == s]] for s in sources])
|
|
||||||
|
|
||||||
def _save_history(self):
|
|
||||||
if not os.path.exists(os.path.dirname(self.HISTORY_FILENAME)):
|
|
||||||
os.makedirs(os.path.dirname(self.HISTORY_FILENAME))
|
|
||||||
open(self.HISTORY_FILENAME, "w").write(json.dumps(self._history))
|
|
||||||
|
|
||||||
def _check_history(self, items, group):
|
|
||||||
for i in items:
|
|
||||||
i['new'] = not (i['source'] in self._history[group] and i['title'] in self._history[group][i['source']])
|
|
||||||
|
|
||||||
def _open(self, _):
|
|
||||||
if self._current_item:
|
|
||||||
webbrowser.open(self._current_item['link'])
|
|
||||||
|
|
||||||
def _check_for_image(self, entry):
|
|
||||||
image = next(iter([l['href'] for l in entry['links'] if l['rel'] == 'enclosure']), None)
|
|
||||||
if not image and 'media_content' in entry:
|
|
||||||
try:
|
|
||||||
media = sorted(entry['media_content'], key=lambda i: i['height'] if 'height' in i else 0, reverse=True)
|
|
||||||
image = next(iter([i['url'] for i in media if i['medium'] == 'image']), None)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if not image:
|
|
||||||
match = re.search(r'<img[^>]*src\s*=["\']*([^\s^>^"^\']*)["\']*', entry['summary'])
|
|
||||||
if match:
|
|
||||||
image = match.group(1)
|
|
||||||
return image if image else ''
|
|
||||||
|
|
||||||
def _remove_tags(self, txt):
|
|
||||||
return re.sub('<[^>]*>', '', txt)
|
|
||||||
|
|
||||||
def _create_item(self, entry, url, feed):
|
|
||||||
return {'title': self._remove_tags(entry['title'].replace('\n', ' ')),
|
|
||||||
'link': entry['link'],
|
|
||||||
'new': True,
|
|
||||||
'source': url,
|
|
||||||
'summary': self._remove_tags(entry['summary']),
|
|
||||||
'feed': feed,
|
|
||||||
'image': self._check_for_image(entry),
|
|
||||||
'published': time.mktime(entry.published_parsed) if hasattr(entry, 'published_parsed') else 0}
|
|
||||||
|
|
||||||
def _update_items_from_feed(self, url):
|
|
||||||
parser = feedparser.parse(url)
|
|
||||||
new_items = [self._create_item(entry, url, parser['feed']['title']) for entry in parser['entries']]
|
|
||||||
# Check history
|
|
||||||
self._check_history(new_items, 'ticker')
|
|
||||||
# Remove the previous items
|
|
||||||
self._items = [i for i in self._items if i['source'] != url]
|
|
||||||
# Add the new items
|
|
||||||
self._items.extend(new_items)
|
|
||||||
# Sort the items on publish date
|
|
||||||
self._items.sort(key=lambda i: i['published'], reverse=True)
|
|
||||||
|
|
||||||
def _check_for_refresh(self):
|
|
||||||
if self._feeds_to_update:
|
|
||||||
# Update one feed at a time to not overload this update cycle
|
|
||||||
url = self._feeds_to_update.pop()
|
|
||||||
self._update_items_from_feed(url)
|
|
||||||
|
|
||||||
if not self._feeds_to_update:
|
|
||||||
self._update_history('ticker')
|
|
||||||
self._save_history()
|
|
||||||
|
|
||||||
if not self._current_item:
|
|
||||||
self._next_item()
|
|
||||||
elif time.time()-self._last_refresh >= self.REFRESH_DELAY:
|
|
||||||
# Populate the list with feeds to update
|
|
||||||
self._feeds_to_update = self._feeds[:]
|
|
||||||
# Update the refresh time
|
|
||||||
self._last_refresh = time.time()
|
|
||||||
|
|
||||||
def _next_item(self):
|
|
||||||
self._ticker_offset = 0
|
|
||||||
self._pre_delay = 2
|
|
||||||
self._post_delay = 4
|
|
||||||
|
|
||||||
if not self._items:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Index of the current element
|
|
||||||
idx = self._items.index(self._current_item) if self._current_item in self._items else - 1
|
|
||||||
|
|
||||||
# First show new items, else show next
|
|
||||||
new_items = [i for i in self._items if i['new']]
|
|
||||||
self._current_item = next(iter(new_items), self._items[(idx+1) % len(self._items)])
|
|
||||||
|
|
||||||
def _check_scroll_done(self):
|
|
||||||
# Check if the complete title has been shown
|
|
||||||
if self._ticker_offset + self._max_title_length > len(self._current_item['title']):
|
|
||||||
# Do not immediately show next item after scroll
|
|
||||||
self._post_delay -= 1
|
|
||||||
if self._post_delay == 0:
|
|
||||||
self._current_item['new'] = False
|
|
||||||
# Mark the previous item as 'old'
|
|
||||||
self._next_item()
|
|
||||||
else:
|
|
||||||
# Increase scroll position
|
|
||||||
self._ticker_offset += self.SCROLL_SPEED
|
|
||||||
|
|
||||||
def _show_error(self, _):
|
|
||||||
return "Please install feedparser first"
|
|
||||||
|
|
||||||
def ticker_update(self, _):
|
|
||||||
# Only update the ticker once a second
|
|
||||||
now = time.time()
|
|
||||||
if now-self._last_update < 1:
|
|
||||||
return self._response
|
|
||||||
|
|
||||||
self._last_update = now
|
|
||||||
|
|
||||||
self._check_for_refresh()
|
|
||||||
|
|
||||||
# If no items were retrieved, return an empty string
|
|
||||||
if not self._current_item:
|
|
||||||
return " "*self._max_title_length
|
|
||||||
|
|
||||||
# Prepare a substring of the item title
|
|
||||||
self._response = self._current_item['title'][self._ticker_offset:self._ticker_offset+self._max_title_length]
|
|
||||||
# Add spaces if too short
|
|
||||||
self._response = self._response.ljust(self._max_title_length)
|
|
||||||
|
|
||||||
# Do not immediately scroll
|
|
||||||
if self._pre_delay > 0:
|
|
||||||
# Change state during pre_delay for new items
|
|
||||||
if self._current_item['new']:
|
|
||||||
self._state = ['warning']
|
|
||||||
self._pre_delay -= 1
|
|
||||||
return self._response
|
|
||||||
|
|
||||||
self._state = []
|
|
||||||
self._check_scroll_done()
|
|
||||||
|
|
||||||
return self._response
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def state(self, _):
|
|
||||||
return self._state
|
|
||||||
|
|
||||||
def _create_news_element(self, item, overlay_title):
|
|
||||||
try:
|
|
||||||
timestr = "" if item['published'] == 0 else str(time.ctime(item['published']))
|
|
||||||
except Exception as exc:
|
|
||||||
logging.error(str(exc))
|
|
||||||
raise e
|
|
||||||
element = "<div class='item' onclick=window.open('"+item['link']+"')>"
|
|
||||||
element += "<div class='titlecontainer'>"
|
|
||||||
element += " <img "+("" if item['image'] else "class='noimg' ")+"src='"+item['image']+"'>"
|
|
||||||
element += " <div class='title"+(" overlay" if overlay_title else "")+"'>"+("<span class='star'>★</span>" if item['new'] else "")+item['title']+"</div>"
|
|
||||||
element += "</div>"
|
|
||||||
element += "<div class='summary'>"+item['summary']+"</div>"
|
|
||||||
element += "<div class='info'><span class='author'>"+item['feed']+"</span><span class='published'>"+timestr+"</span></div>"
|
|
||||||
element += "</div>"
|
|
||||||
return element
|
|
||||||
|
|
||||||
def _create_news_section(self, newspaper_items):
|
|
||||||
style = random.randint(0, 3)
|
|
||||||
section = "<table><tr class='style"+str(style)+"'>"
|
|
||||||
for i in range(0, 3):
|
|
||||||
section += "<td><div class='itemcontainer'>"
|
|
||||||
for _ in range(0, self.LAYOUT_STYLES_ITEMS[style][i]):
|
|
||||||
if newspaper_items:
|
|
||||||
section += self._create_news_element(newspaper_items[0], self.LAYOUT_STYLES_ITEMS[style][i] != 3)
|
|
||||||
del newspaper_items[0]
|
|
||||||
section += "</div></td>"
|
|
||||||
section += "</tr></table>"
|
|
||||||
return section
|
|
||||||
|
|
||||||
def _create_newspaper(self, _):
|
|
||||||
content = ""
|
|
||||||
newspaper_items = self._items[:]
|
|
||||||
self._check_history(newspaper_items, 'newspaper')
|
|
||||||
|
|
||||||
# Make sure new items are always listed first, independent of publish date
|
|
||||||
newspaper_items.sort(key=lambda i: i['published']+(10000000 if i['new'] else 0), reverse=True)
|
|
||||||
|
|
||||||
while newspaper_items:
|
|
||||||
content += self._create_news_section(newspaper_items)
|
|
||||||
open(self._newspaper_filename, "w").write(HTML_TEMPLATE.replace("[[CONTENT]]", content))
|
|
||||||
webbrowser.open("file://"+self._newspaper_filename)
|
|
||||||
self._update_history('newspaper')
|
|
||||||
self._save_history()
|
|
||||||
|
|
||||||
HTML_TEMPLATE = """<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script>
|
|
||||||
window.onload = function() {
|
|
||||||
var images = document.getElementsByTagName('img');
|
|
||||||
// Remove very small images
|
|
||||||
for(var i = 0; i < images.length; i++) {
|
|
||||||
if (images[i].naturalWidth<50 || images[i].naturalHeight<50) {
|
|
||||||
images[i].src = ''
|
|
||||||
images[i].className+=' noimg'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<style>
|
|
||||||
body {background: #eee; font-family: Helvetica neue;}
|
|
||||||
td {background: #fff; height: 100%;}
|
|
||||||
tr.style0 td {width: 33%;}
|
|
||||||
tr.style1 td {width: 20%;}
|
|
||||||
tr.style1 td:last-child {width: 60%;}
|
|
||||||
tr.style2 td {width: 20%;}
|
|
||||||
tr.style2 td:first-child {width: 60%;}
|
|
||||||
tr.style3 td {width: 20%;}
|
|
||||||
tr.style3 td:nth-child(2) {width: 60%;}
|
|
||||||
img {width: 100%; display: block; }
|
|
||||||
img.noimg {min-height:250px; background: #1299c8;}
|
|
||||||
#content {width: 1500px; margin: auto; background: #eee; padding: 1px;}
|
|
||||||
#newspapertitle {text-align: center; font-size: 60px; font-family: Arial Black; background: #1299c8; font-style: Italic; padding: 10px; color: #fff; }
|
|
||||||
.star {color: #ffa515; font-size: 24px;}
|
|
||||||
.section {display: flex;}
|
|
||||||
.column {display: flex;}
|
|
||||||
.itemcontainer {width: 100%; height: 100%; position: relative; display: inline-table;}
|
|
||||||
.item {cursor: pointer; }
|
|
||||||
.titlecontainer {position: relative;}
|
|
||||||
.title.overlay {font-family: Arial; position: absolute; bottom: 10px; color: #fff; font-weight: bold; text-align: right; max-width: 75%; right: 10px; font-size: 23px; text-shadow: 1px 0 0 #000, 0 -1px 0 #000, 0 1px 0 #000, -1px 0 0 #000;}
|
|
||||||
.title:not(.overlay) {font-weight: bold; padding: 0px 10px;}
|
|
||||||
.summary {color: #444; padding: 10px 10px 0px 10px; font-family: Times new roman; font-size: 18px; flex: 1;max-height: 105px; overflow: hidden;}
|
|
||||||
.info {color: #aaa; font-family: arial; font-size: 13px; padding: 10px;}
|
|
||||||
.published {float: right;}
|
|
||||||
</style>
|
|
||||||
<body>
|
|
||||||
<div id='content'>
|
|
||||||
<div id='newspapertitle'>Bumblebee Daily</div>
|
|
||||||
[[CONTENT]]
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>"""
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,128 +0,0 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays sensor temperature
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* sensors.path: path to temperature file (default /sys/class/thermal/thermal_zone0/temp).
|
|
||||||
* sensors.json: if set to "true", interpret sensors.path as JSON "path" in the output
|
|
||||||
of "sensors -j" (i.e. <key1>/<key2>/.../<value>), for example, path could
|
|
||||||
be: "coretemp-isa-00000/Core 0/temp1_input" (defaults to "false")
|
|
||||||
* sensors.match: (fallback) Line to match against output of 'sensors -u' (default: temp1_input)
|
|
||||||
* sensors.match_pattern: (fallback) Line to match against before temperature is read (no default)
|
|
||||||
* sensors.match_number: (fallback) which of the matches you want (default -1: last match).
|
|
||||||
* sensors.show_freq: whether to show CPU frequency. (default: true)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.temperature))
|
|
||||||
self._temperature = "unknown"
|
|
||||||
self._mhz = "n/a"
|
|
||||||
self._match_number = int(self.parameter("match_number", "-1"))
|
|
||||||
self._match_pattern = self.parameter("match_pattern", None)
|
|
||||||
self._pattern = re.compile(r"^\s*{}:\s*([\d.]+)$".format(self.parameter("match", "temp1_input")), re.MULTILINE)
|
|
||||||
self._json = bumblebee.util.asbool(self.parameter("json", "false"))
|
|
||||||
self._freq = bumblebee.util.asbool(self.parameter("show_freq", "true"))
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd="xsensors")
|
|
||||||
self.determine_method()
|
|
||||||
|
|
||||||
def determine_method(self):
|
|
||||||
if self.parameter("path") != None and self._json == False:
|
|
||||||
self.use_sensors = False # use thermal zone
|
|
||||||
else:
|
|
||||||
# try to use output of sensors -u
|
|
||||||
try:
|
|
||||||
output = bumblebee.util.execute("sensors -u")
|
|
||||||
self.use_sensors = True
|
|
||||||
log.debug("Sensors command available")
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
log.info("Sensors command not available, using /sys/class/thermal/thermal_zone*/")
|
|
||||||
self.use_sensors = False
|
|
||||||
|
|
||||||
def _get_temp_from_sensors(self):
|
|
||||||
if self._json == True:
|
|
||||||
try:
|
|
||||||
output = json.loads(bumblebee.util.execute("sensors -j"))
|
|
||||||
for key in self.parameter("path").split("/"):
|
|
||||||
output = output[key]
|
|
||||||
return int(float(output))
|
|
||||||
except Exception as e:
|
|
||||||
logging.error("unable to read sensors: {}".format(str(e)))
|
|
||||||
return "unknown"
|
|
||||||
else:
|
|
||||||
output = bumblebee.util.execute("sensors -u")
|
|
||||||
if self._match_pattern:
|
|
||||||
temp_pattern = self.parameter("match", "temp1_input")
|
|
||||||
match = re.search(r"{}.+{}:\s*([\d.]+)$".format(self._match_pattern, temp_pattern), output.replace("\n", ""))
|
|
||||||
if match:
|
|
||||||
return int(float(match.group(1)))
|
|
||||||
else:
|
|
||||||
return "unknown"
|
|
||||||
match = self._pattern.findall(output)
|
|
||||||
if match:
|
|
||||||
return int(float(match[self._match_number]))
|
|
||||||
return "unknown"
|
|
||||||
|
|
||||||
def get_temp(self):
|
|
||||||
if self.use_sensors:
|
|
||||||
temperature = self._get_temp_from_sensors()
|
|
||||||
log.debug("Retrieve temperature from sensors -u")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
temperature = open(self.parameter("path", "/sys/class/thermal/thermal_zone0/temp")).read()[:2]
|
|
||||||
log.debug("retrieved temperature from /sys/class/")
|
|
||||||
# TODO: Iterate through all thermal zones to determine the correct one and use its value
|
|
||||||
# https://unix.stackexchange.com/questions/304845/discrepancy-between-number-of-cores-and-thermal-zones-in-sys-class-thermal
|
|
||||||
|
|
||||||
except IOError:
|
|
||||||
temperature = "unknown"
|
|
||||||
log.info("Can not determine temperature, please install lm-sensors")
|
|
||||||
|
|
||||||
return temperature
|
|
||||||
|
|
||||||
def get_mhz(self):
|
|
||||||
mhz = None
|
|
||||||
try:
|
|
||||||
output = open("/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq").read()
|
|
||||||
mhz = int(float(output)/1000.0)
|
|
||||||
except:
|
|
||||||
output = open("/proc/cpuinfo").read()
|
|
||||||
m = re.search(r"cpu MHz\s+:\s+(\d+)", output)
|
|
||||||
if m:
|
|
||||||
mhz = int(m.group(1))
|
|
||||||
else:
|
|
||||||
m = re.search(r"BogoMIPS\s+:\s+(\d+)", output)
|
|
||||||
if m:
|
|
||||||
return "{} BogoMIPS".format(int(m.group(1)))
|
|
||||||
if not mhz:
|
|
||||||
return "n/a"
|
|
||||||
|
|
||||||
if mhz < 1000:
|
|
||||||
return "{} MHz".format(mhz)
|
|
||||||
else:
|
|
||||||
return "{:0.01f} GHz".format(float(mhz)/1000.0)
|
|
||||||
|
|
||||||
def temperature(self, _):
|
|
||||||
if self._freq:
|
|
||||||
return u"{}°c @ {}".format(self._temperature, self._mhz)
|
|
||||||
else:
|
|
||||||
return u"{}°c".format(self._temperature)
|
|
||||||
def update(self, widgets):
|
|
||||||
self._temperature = self.get_temp()
|
|
||||||
if self._freq:
|
|
||||||
self._mhz = self.get_mhz()
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,156 +0,0 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
|
|
||||||
"""Displays sensor temperature and CPU frequency
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
|
|
||||||
* sensors2.chip: "sensors -u" compatible filter for chip to display (default to empty - show all chips)
|
|
||||||
* sensors2.showcpu: Enable or disable CPU frequency display (default: true)
|
|
||||||
* sensors2.showtemp: Enable or disable temperature display (default: true)
|
|
||||||
* sensors2.showfan: Enable or disable fan display (default: true)
|
|
||||||
* sensors2.showother: Enable or display "other" sensor readings (default: false)
|
|
||||||
* sensors2.showname: Enable or disable show of sensor name (default: false)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config, None)
|
|
||||||
self._chip = self.parameter("chip", "")
|
|
||||||
self._data = {}
|
|
||||||
self._update()
|
|
||||||
|
|
||||||
self.widgets(self._create_widgets())
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._update()
|
|
||||||
for widget in widgets:
|
|
||||||
self._update_widget(widget)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
widget_type = widget.get("type", "")
|
|
||||||
try:
|
|
||||||
data = self._data[widget.get("adapter")][widget.get("package")][widget.get("field")]
|
|
||||||
if "crit" in data and float(data["input"]) > float(data["crit"]):
|
|
||||||
return ["critical", widget_type]
|
|
||||||
if "max" in data and float(data["input"]) > float(data["max"]):
|
|
||||||
return ["warning", widget_type]
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return [widget_type]
|
|
||||||
|
|
||||||
def _create_widgets(self):
|
|
||||||
widgets = []
|
|
||||||
show_temp = bumblebee.util.asbool(self.parameter("showtemp", "true"))
|
|
||||||
show_fan = bumblebee.util.asbool(self.parameter("showfan", "true"))
|
|
||||||
show_other = bumblebee.util.asbool(self.parameter("showother", "false"))
|
|
||||||
|
|
||||||
if bumblebee.util.asbool(self.parameter("showcpu", "true")):
|
|
||||||
widget = bumblebee.output.Widget(full_text=self._cpu)
|
|
||||||
widget.set("type", "cpu")
|
|
||||||
widgets.append(widget)
|
|
||||||
|
|
||||||
for adapter in self._data:
|
|
||||||
for package in self._data[adapter]:
|
|
||||||
if bumblebee.util.asbool(self.parameter("showname", "false")):
|
|
||||||
widget = bumblebee.output.Widget(full_text=package)
|
|
||||||
widget.set("data", self._data[adapter][package])
|
|
||||||
widget.set("package", package)
|
|
||||||
widget.set("field", "")
|
|
||||||
widget.set("adapter", adapter)
|
|
||||||
widgets.append(widget)
|
|
||||||
for field in self._data[adapter][package]:
|
|
||||||
widget = bumblebee.output.Widget()
|
|
||||||
widget.set("package", package)
|
|
||||||
widget.set("field", field)
|
|
||||||
widget.set("adapter", adapter)
|
|
||||||
if "temp" in field and show_temp:
|
|
||||||
# seems to be a temperature
|
|
||||||
widget.set("type", "temp")
|
|
||||||
widgets.append(widget)
|
|
||||||
if "fan" in field and show_fan:
|
|
||||||
# seems to be a fan
|
|
||||||
widget.set("type", "fan")
|
|
||||||
widgets.append(widget)
|
|
||||||
elif show_other:
|
|
||||||
# everything else
|
|
||||||
widget.set("type", "other")
|
|
||||||
widgets.append(widget)
|
|
||||||
return widgets
|
|
||||||
|
|
||||||
def _update_widget(self, widget):
|
|
||||||
if widget.get("field", "") == "":
|
|
||||||
return # nothing to do
|
|
||||||
data = self._data[widget.get("adapter")][widget.get("package")][widget.get("field")]
|
|
||||||
if "temp" in widget.get("field"):
|
|
||||||
widget.full_text(u"{:0.01f}°C".format(data["input"]))
|
|
||||||
elif "fan" in widget.get("field"):
|
|
||||||
widget.full_text(u"{:0.0f}RPM".format(data["input"]))
|
|
||||||
else:
|
|
||||||
widget.full_text(u"{:0.0f}".format(data["input"]))
|
|
||||||
|
|
||||||
def _update(self):
|
|
||||||
output = bumblebee.util.execute("sensors -u {}".format(self._chip))
|
|
||||||
self._data = self._parse(output)
|
|
||||||
|
|
||||||
def _parse(self, data):
|
|
||||||
output = {}
|
|
||||||
package = ""
|
|
||||||
adapter = None
|
|
||||||
chip = None
|
|
||||||
for line in data.split("\n"):
|
|
||||||
if "Adapter" in line:
|
|
||||||
# new adapter
|
|
||||||
line = line.replace("Adapter: ", "")
|
|
||||||
output[chip + " " + line] = {}
|
|
||||||
adapter = chip + " " + line
|
|
||||||
chip = line #default - line before adapter is always the chip
|
|
||||||
if not adapter: continue
|
|
||||||
key, value = (line.split(":") + ["", ""])[:2]
|
|
||||||
if not line.startswith(" "):
|
|
||||||
# assume this starts a new package
|
|
||||||
if package in output[adapter] and output[adapter][package] == {}:
|
|
||||||
del output[adapter][package]
|
|
||||||
output[adapter][key] = {}
|
|
||||||
package = key
|
|
||||||
else:
|
|
||||||
# feature for this chip
|
|
||||||
try:
|
|
||||||
name, variant = (key.strip().split("_", 1) + ["",""])[:2]
|
|
||||||
if not name in output[adapter][package]:
|
|
||||||
output[adapter][package][name] = { }
|
|
||||||
if variant:
|
|
||||||
output[adapter][package][name][variant] = {}
|
|
||||||
output[adapter][package][name][variant] = float(value)
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
return output
|
|
||||||
|
|
||||||
def _cpu(self, _):
|
|
||||||
mhz = None
|
|
||||||
try:
|
|
||||||
output = open("/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq").read()
|
|
||||||
mhz = int(float(output)/1000.0)
|
|
||||||
except:
|
|
||||||
output = open("/proc/cpuinfo").read()
|
|
||||||
m = re.search(r"cpu MHz\s+:\s+(\d+)", output)
|
|
||||||
if m:
|
|
||||||
mhz = int(m.group(1))
|
|
||||||
else:
|
|
||||||
m = re.search(r"BogoMIPS\s+:\s+(\d+)", output)
|
|
||||||
if m:
|
|
||||||
return "{} BogoMIPS".format(int(m.group(1)))
|
|
||||||
if not mhz:
|
|
||||||
return "n/a"
|
|
||||||
|
|
||||||
if mhz < 1000:
|
|
||||||
return "{} MHz".format(mhz)
|
|
||||||
else:
|
|
||||||
return "{:0.01f} GHz".format(float(mhz)/1000.0)
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,93 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903,W1401
|
|
||||||
|
|
||||||
""" Execute command in shell and print result
|
|
||||||
|
|
||||||
Few command examples:
|
|
||||||
'ping 1.1.1.1 -c 1 | grep -Po "(?<=time=)\d+(\.\d+)? ms"'
|
|
||||||
'echo "BTC=$(curl -s rate.sx/1BTC | grep -Po \"^\d+\")USD"'
|
|
||||||
'curl -s https://wttr.in/London?format=%l+%t+%h+%w'
|
|
||||||
'pip3 freeze | wc -l'
|
|
||||||
'any_custom_script.sh | grep arguments'
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* shell.command: Command to execute
|
|
||||||
Use single parentheses if evaluating anything inside (sh-style)
|
|
||||||
For example shell.command='echo $(date +"%H:%M:%S")'
|
|
||||||
But NOT shell.command="echo $(date +'%H:%M:%S')"
|
|
||||||
Second one will be evaluated only once at startup
|
|
||||||
* shell.interval: Update interval in seconds
|
|
||||||
(defaults to 1s == every bumblebee-status update)
|
|
||||||
* shell.async: Run update in async mode. Won't run next thread if
|
|
||||||
previous one didn't finished yet. Useful for long
|
|
||||||
running scripts to avoid bumblebee-status freezes
|
|
||||||
(defaults to False)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import threading
|
|
||||||
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widget = bumblebee.output.Widget(full_text=self.get_output)
|
|
||||||
super(Module, self).__init__(engine, config, widget)
|
|
||||||
|
|
||||||
if self.parameter('interval'):
|
|
||||||
self.interval(self.parameter('interval'))
|
|
||||||
|
|
||||||
self._command = self.parameter('command')
|
|
||||||
self._async = bumblebee.util.asbool(self.parameter('async'))
|
|
||||||
if self._async:
|
|
||||||
self._output = 'Computing...'
|
|
||||||
self._current_thread = None
|
|
||||||
else:
|
|
||||||
self._output = ''
|
|
||||||
|
|
||||||
# LMB and RMB will update output regardless of timer
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self.update)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self.update)
|
|
||||||
|
|
||||||
def get_output(self, _):
|
|
||||||
return self._output
|
|
||||||
|
|
||||||
def update(self, _):
|
|
||||||
# if requested then run not async version and just execute command in this thread
|
|
||||||
if not self._async:
|
|
||||||
self._output = self._get_command_output_or_error(self._command)
|
|
||||||
return
|
|
||||||
|
|
||||||
# if previous thread didn't end yet then don't do anything
|
|
||||||
if self._current_thread:
|
|
||||||
return
|
|
||||||
|
|
||||||
# spawn new thread to execute command and pass callback method to get output from it
|
|
||||||
self._current_thread = threading.Thread(target=self._run_command_in_thread,
|
|
||||||
args=(self._command, self._output_function))
|
|
||||||
self._current_thread.start()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_command_output_or_error(command):
|
|
||||||
try:
|
|
||||||
command_output = subprocess.check_output(command,
|
|
||||||
executable=os.environ.get('SHELL'),
|
|
||||||
shell=True,
|
|
||||||
stderr=subprocess.STDOUT)
|
|
||||||
return command_output.decode('utf-8').strip()
|
|
||||||
except subprocess.CalledProcessError as exception:
|
|
||||||
exception_output = exception.output.decode('utf-8').replace('\n', '')
|
|
||||||
return 'Status:{} output:{}'.format(exception.returncode, exception_output)
|
|
||||||
|
|
||||||
def _run_command_in_thread(self, command, output_callback):
|
|
||||||
output_callback(self._get_command_output_or_error(command))
|
|
||||||
|
|
||||||
def _output_function(self, text):
|
|
||||||
self._output = text
|
|
||||||
# clear this thread data, so next update will spawn a new one
|
|
||||||
self._current_thread = None
|
|
||||||
|
|
||||||
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,70 +0,0 @@
|
||||||
# pylint: disable=C0112,R0903
|
|
||||||
|
|
||||||
"""Shows a widget per user-defined shortcut and allows to define the behaviour
|
|
||||||
when clicking on it.
|
|
||||||
|
|
||||||
For more than one shortcut, the commands and labels are strings separated by
|
|
||||||
a demiliter (; semicolon by default).
|
|
||||||
|
|
||||||
For example in order to create two shortcuts labeled A and B with commands
|
|
||||||
cmdA and cmdB you could do:
|
|
||||||
|
|
||||||
./bumblebee-status -m shortcut -p shortcut.cmd="ls;ps" shortcut.label="A;B"
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* shortcut.cmds : List of commands to execute
|
|
||||||
* shortcut.labels: List of widgets' labels (text)
|
|
||||||
* shortcut.delim : Commands and labels delimiter (; semicolon by default)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.input
|
|
||||||
|
|
||||||
LINK = "https://github.com/tobi-wan-kenobi/bumblebee-status/wiki"
|
|
||||||
LABEL = "Click me"
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
""" Shortcut module."""
|
|
||||||
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widgets = []
|
|
||||||
self._engine = engine
|
|
||||||
super(Module, self).__init__(engine, config, widgets)
|
|
||||||
|
|
||||||
self._labels = self.parameter("labels", "{}".format(LABEL))
|
|
||||||
self._cmds = self.parameter("cmds", "firefox {}".format(LINK))
|
|
||||||
self._delim = self.parameter("delim", ";")
|
|
||||||
|
|
||||||
self.update_widgets(widgets)
|
|
||||||
|
|
||||||
def update_widgets(self, widgets):
|
|
||||||
""" Creates a set of widget per user define shortcut."""
|
|
||||||
|
|
||||||
cmds = self._cmds.split(self._delim)
|
|
||||||
labels = self._labels.split(self._delim)
|
|
||||||
|
|
||||||
# to be on the safe side create as many widgets as there are data (cmds or labels)
|
|
||||||
num_shortcuts = min(len(cmds), len(labels))
|
|
||||||
|
|
||||||
# report possible problem as a warning
|
|
||||||
if len(cmds) is not len(labels):
|
|
||||||
logging.warning("shortcut: the number of commands does not match "\
|
|
||||||
"the number of provided labels.")
|
|
||||||
logging.warning("cmds : %s, labels : %s", cmds, labels)
|
|
||||||
|
|
||||||
for idx in range(0, num_shortcuts):
|
|
||||||
cmd = cmds[idx]
|
|
||||||
label = labels[idx]
|
|
||||||
|
|
||||||
widget = bumblebee.output.Widget(full_text=label)
|
|
||||||
self._engine.input.register_callback(widget, button=bumblebee.input.LEFT_MOUSE, cmd=cmd)
|
|
||||||
|
|
||||||
widgets.append(widget)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
if len(widgets) <= 0:
|
|
||||||
self.update_widgets(widgets)
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,150 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the state of a Space API endpoint
|
|
||||||
Space API is an API for hackspaces based on JSON. See spaceapi.io for
|
|
||||||
an example.
|
|
||||||
|
|
||||||
Requires the following libraries:
|
|
||||||
* requests
|
|
||||||
* regex
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* spaceapi.url: String representation of the api endpoint
|
|
||||||
* spaceapi.format: Format string for the output
|
|
||||||
|
|
||||||
Format Strings:
|
|
||||||
* Format strings are indicated by double %%
|
|
||||||
* They represent a leaf in the JSON tree, layers seperated by "."
|
|
||||||
* Boolean values can be overwritten by appending "%true%false"
|
|
||||||
in the format string
|
|
||||||
* Example: to reference "open" in "{"state":{"open": true}}"
|
|
||||||
you would write "%%state.open%%", if you also want
|
|
||||||
to say "Open/Closed" depending on the boolean you
|
|
||||||
would write "%%state.open%Open%Closed%%"
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import threading
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
def formatStringBuilder(s, json):
|
|
||||||
"""
|
|
||||||
Parses Format Strings
|
|
||||||
Parameter:
|
|
||||||
s -> format string
|
|
||||||
json -> the spaceapi response object
|
|
||||||
"""
|
|
||||||
identifiers = re.findall("%%.*?%%", s)
|
|
||||||
for i in identifiers:
|
|
||||||
ic = i[2:-2] # Discard %%
|
|
||||||
j = ic.split("%")
|
|
||||||
|
|
||||||
# Only neither of, or both true AND false may be overwritten
|
|
||||||
if len(j) != 3 and len(j) != 1:
|
|
||||||
return "INVALID FORMAT STRING"
|
|
||||||
|
|
||||||
if len(j) == 1: # no overwrite
|
|
||||||
s = s.replace(i, json[j[0]])
|
|
||||||
elif json[j[0]]: # overwrite for True
|
|
||||||
s = s.replace(i, j[1])
|
|
||||||
else: # overwrite for False
|
|
||||||
s = s.replace(i, j[2])
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(
|
|
||||||
engine, config, bumblebee.output.Widget(full_text=self.getState)
|
|
||||||
)
|
|
||||||
|
|
||||||
engine.input.register_callback(
|
|
||||||
self, button=bumblebee.input.LEFT_MOUSE, cmd=self.__forceReload
|
|
||||||
)
|
|
||||||
|
|
||||||
self._data = {}
|
|
||||||
self._error = None
|
|
||||||
|
|
||||||
self._threadingCount = 0
|
|
||||||
|
|
||||||
# The URL representing the api endpoint
|
|
||||||
self._url = self.parameter("url", default="http://club.entropia.de/spaceapi")
|
|
||||||
self._format = self.parameter(
|
|
||||||
"format", default=u" %%space%%: %%state.open%Open%Closed%%"
|
|
||||||
)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
try:
|
|
||||||
if self._error is not None:
|
|
||||||
return ["critical"]
|
|
||||||
elif self._data["state.open"]:
|
|
||||||
return ["warning"]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
except KeyError:
|
|
||||||
return ["critical"]
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
if self._threadingCount == 0:
|
|
||||||
thread = threading.Thread(target=self.get_api_async, args=())
|
|
||||||
thread.start()
|
|
||||||
self._threadingCount = (
|
|
||||||
0 if self._threadingCount > 300 else self._threadingCount + 1
|
|
||||||
)
|
|
||||||
|
|
||||||
def getState(self, widget):
|
|
||||||
text = self._format
|
|
||||||
if self._error is not None:
|
|
||||||
text = self._error
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
text = formatStringBuilder(self._format, self._data)
|
|
||||||
except KeyError:
|
|
||||||
text = "KeyError"
|
|
||||||
return text
|
|
||||||
|
|
||||||
def get_api_async(self):
|
|
||||||
try:
|
|
||||||
with requests.get(self._url, timeout=10) as request:
|
|
||||||
# Can't implement error handling for python2.7 if I use
|
|
||||||
# request.json() as it uses simplejson in newer versions
|
|
||||||
self._data = self.__flatten(json.loads(request.text))
|
|
||||||
self._error = None
|
|
||||||
except requests.exceptions.Timeout:
|
|
||||||
self._error = "Timeout"
|
|
||||||
except requests.exceptions.HTTPError:
|
|
||||||
self._error = "HTTP Error"
|
|
||||||
except ValueError:
|
|
||||||
self._error = "Not a JSON response"
|
|
||||||
|
|
||||||
# left_mouse_button handler
|
|
||||||
def __forceReload(self, event):
|
|
||||||
self._threadingCount += 300
|
|
||||||
self._error = "RELOADING"
|
|
||||||
|
|
||||||
# Flattens the JSON structure recursively, e.g. ["space"]["open"]
|
|
||||||
# becomes ["space.open"]
|
|
||||||
def __flatten(self, json):
|
|
||||||
out = {}
|
|
||||||
for key in json:
|
|
||||||
value = json[key]
|
|
||||||
if type(value) is dict:
|
|
||||||
flattened_key = self.__flatten(value)
|
|
||||||
for fk in flattened_key:
|
|
||||||
out[key + "." + fk] = flattened_key[fk]
|
|
||||||
else:
|
|
||||||
out[key] = value
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
# Author: Tobias Manske <tobias@chaoswg.xyz>
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,23 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Draws a widget with configurable text content.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* spacer.text: Widget contents (defaults to empty string)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.text)
|
|
||||||
)
|
|
||||||
self._text = self.parameter("text", "")
|
|
||||||
|
|
||||||
def text(self, widget):
|
|
||||||
return self._text
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,87 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the current song being played
|
|
||||||
Requires the following library:
|
|
||||||
* python-dbus
|
|
||||||
Parameters:
|
|
||||||
* spotify.format: Format string (defaults to "{artist} - {title}")
|
|
||||||
Available values are: {album}, {title}, {artist}, {trackNumber}, {playbackStatus}
|
|
||||||
* spotify.previous: Change binding for previous song (default is left click)
|
|
||||||
* spotify.next: Change binding for next song (default is right click)
|
|
||||||
* spotify.pause: Change binding for toggling pause (default is middle click)
|
|
||||||
Available options for spotify.previous, spotify.next and spotify.pause are:
|
|
||||||
LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
from bumblebee.output import scrollable
|
|
||||||
|
|
||||||
try:
|
|
||||||
import dbus
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.spotify)
|
|
||||||
)
|
|
||||||
buttons = {"LEFT_CLICK":bumblebee.input.LEFT_MOUSE,
|
|
||||||
"RIGHT_CLICK":bumblebee.input.RIGHT_MOUSE,
|
|
||||||
"MIDDLE_CLICK":bumblebee.input.MIDDLE_MOUSE,
|
|
||||||
"SCROLL_UP":bumblebee.input.WHEEL_UP,
|
|
||||||
"SCROLL_DOWN":bumblebee.input.WHEEL_DOWN,
|
|
||||||
}
|
|
||||||
|
|
||||||
self._song = ""
|
|
||||||
self._format = self.parameter("format", "{artist} - {title}")
|
|
||||||
prev_button = self.parameter("previous", "LEFT_CLICK")
|
|
||||||
next_button = self.parameter("next", "RIGHT_CLICK")
|
|
||||||
pause_button = self.parameter("pause", "MIDDLE_CLICK")
|
|
||||||
|
|
||||||
cmd = "dbus-send --session --type=method_call --dest=org.mpris.MediaPlayer2.spotify \
|
|
||||||
/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player."
|
|
||||||
engine.input.register_callback(self, button=buttons[prev_button],
|
|
||||||
cmd=cmd + "Previous")
|
|
||||||
engine.input.register_callback(self, button=buttons[next_button],
|
|
||||||
cmd=cmd + "Next")
|
|
||||||
engine.input.register_callback(self, button=buttons[pause_button],
|
|
||||||
cmd=cmd + "PlayPause")
|
|
||||||
|
|
||||||
@scrollable
|
|
||||||
def spotify(self, widget):
|
|
||||||
return self.string_song
|
|
||||||
|
|
||||||
def hidden(self):
|
|
||||||
return self.string_song == ""
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
try:
|
|
||||||
bus = dbus.SessionBus()
|
|
||||||
spotify = bus.get_object("org.mpris.MediaPlayer2.spotify", "/org/mpris/MediaPlayer2")
|
|
||||||
spotify_iface = dbus.Interface(spotify, 'org.freedesktop.DBus.Properties')
|
|
||||||
props = spotify_iface.Get('org.mpris.MediaPlayer2.Player', 'Metadata')
|
|
||||||
playback_status = str(spotify_iface.Get('org.mpris.MediaPlayer2.Player', 'PlaybackStatus'))
|
|
||||||
self._song = self._format.format(album=str(props.get('xesam:album')),
|
|
||||||
title=str(props.get('xesam:title')),
|
|
||||||
artist=','.join(props.get('xesam:artist')),
|
|
||||||
trackNumber=str(props.get('xesam:trackNumber')),
|
|
||||||
playbackStatus=u"\u25B6" if playback_status=="Playing" else u"\u258D\u258D" if playback_status=="Paused" else "",)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
self._song = ""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def string_song(self):
|
|
||||||
if sys.version_info.major < 3:
|
|
||||||
return unicode(self._song)
|
|
||||||
return str(self._song)
|
|
||||||
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,61 +0,0 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Display a stock quote from worldtradingdata.com
|
|
||||||
|
|
||||||
Requires the following python packages:
|
|
||||||
* requests
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* stock.symbols : Comma-separated list of symbols to fetch
|
|
||||||
* stock.change : Should we fetch change in stock value (defaults to True)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.util
|
|
||||||
|
|
||||||
import json
|
|
||||||
import requests
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.value)
|
|
||||||
)
|
|
||||||
self._symbols = self.parameter('symbols', '')
|
|
||||||
self._change = bumblebee.util.asbool(self.parameter('change', True))
|
|
||||||
self._value = None
|
|
||||||
self.interval_factor(60)
|
|
||||||
self.interval(60)
|
|
||||||
|
|
||||||
def value(self, widget):
|
|
||||||
results = []
|
|
||||||
if not self._value:
|
|
||||||
return 'n/a'
|
|
||||||
data = json.loads(self._value)
|
|
||||||
|
|
||||||
for symbol in data['quoteResponse']['result']:
|
|
||||||
valkey = 'regularMarketChange' if self._change else 'regularMarketPrice'
|
|
||||||
sym = 'n/a' if not 'symbol' in symbol else symbol['symbol']
|
|
||||||
currency = 'USD' if not 'currency' in symbol else symbol['currency']
|
|
||||||
val = 'n/a' if not valkey in symbol else '{:.2f}'.format(symbol[valkey])
|
|
||||||
results.append('{} {} {}'.format(sym, val, currency))
|
|
||||||
return u' '.join(results)
|
|
||||||
|
|
||||||
def fetch(self):
|
|
||||||
if self._symbols:
|
|
||||||
url = 'https://query1.finance.yahoo.com/v7/finance/quote?symbols='
|
|
||||||
url += self._symbols + '&fields=regularMarketPrice,currency,regularMarketChange'
|
|
||||||
return requests.get(url).text.strip()
|
|
||||||
else:
|
|
||||||
logging.error('unable to retrieve stock exchange rate')
|
|
||||||
return None
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._value = self.fetch()
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,111 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays sunrise and sunset times
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* cpu.lat : Latitude of your location
|
|
||||||
* cpu.lon : Longitude of your location
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
from suntime import Sun, SunTimeException
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
import requests
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
from dateutil.tz import tzlocal
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(
|
|
||||||
engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.suntimes)
|
|
||||||
)
|
|
||||||
self.interval(3600)
|
|
||||||
self._lat = self.parameter("lat", None)
|
|
||||||
self._lon = self.parameter("lon", None)
|
|
||||||
try:
|
|
||||||
if not self._lat or not self._lon:
|
|
||||||
location_url = "http://ipinfo.io/json"
|
|
||||||
location = requests.get(location_url).json()
|
|
||||||
self._lat, self._lon = location["loc"].split(",")
|
|
||||||
self._lat = float(self._lat)
|
|
||||||
self._lon = float(self._lon)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
self.update(None)
|
|
||||||
|
|
||||||
def suntimes(self, _):
|
|
||||||
if self._sunset and self._sunrise:
|
|
||||||
if self._isup:
|
|
||||||
return u"\u21A7{} \u21A5{}".format(
|
|
||||||
self._sunset.strftime('%H:%M'),
|
|
||||||
self._sunrise.strftime('%H:%M'))
|
|
||||||
return u"\u21A5{} \u21A7{}".format(self._sunrise.strftime('%H:%M'),
|
|
||||||
self._sunset.strftime('%H:%M'))
|
|
||||||
return "?"
|
|
||||||
|
|
||||||
def _calculate_times(self):
|
|
||||||
self._isup = False
|
|
||||||
try:
|
|
||||||
sun = Sun(self._lat, self._lon)
|
|
||||||
except Exception:
|
|
||||||
self._sunrise = None
|
|
||||||
self._sunset = None
|
|
||||||
return
|
|
||||||
|
|
||||||
order_matters = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._sunrise = sun.get_local_sunrise_time()
|
|
||||||
except SunTimeException:
|
|
||||||
self._sunrise = "no sunrise"
|
|
||||||
order_matters = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._sunset = sun.get_local_sunset_time()
|
|
||||||
except SunTimeException:
|
|
||||||
self._sunset = "no sunset"
|
|
||||||
order_matters = False
|
|
||||||
|
|
||||||
if not order_matters:
|
|
||||||
return
|
|
||||||
|
|
||||||
now = datetime.datetime.now(tz=tzlocal())
|
|
||||||
if now > self._sunset:
|
|
||||||
tomorrow = (now + datetime.timedelta(days=1)).date()
|
|
||||||
try:
|
|
||||||
self._sunrise = sun.get_local_sunrise_time(tomorrow)
|
|
||||||
self._sunset = sun.get_local_sunset_time(tomorrow)
|
|
||||||
except SunTimeException:
|
|
||||||
self._sunrise = "no sunrise"
|
|
||||||
self._sunset = "no sunset"
|
|
||||||
|
|
||||||
elif now > self._sunrise:
|
|
||||||
tomorrow = (now + datetime.timedelta(days=1)).date()
|
|
||||||
try:
|
|
||||||
self._sunrise = sun.get_local_sunrise_time(tomorrow)
|
|
||||||
except SunTimeException:
|
|
||||||
self._sunrise = "no sunrise"
|
|
||||||
return
|
|
||||||
self._isup = True
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
if not self._lat or not self._lon:
|
|
||||||
self._sunrise = None
|
|
||||||
self._sunset = None
|
|
||||||
self._calculate_times()
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,98 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
""" system module
|
|
||||||
|
|
||||||
adds the possibility to
|
|
||||||
* shutdown
|
|
||||||
* reboot
|
|
||||||
the system.
|
|
||||||
|
|
||||||
Per default a confirmation dialog is shown before the actual action is performed.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* system.confirm: show confirmation dialog before performing any action (default: true)
|
|
||||||
* system.reboot: specify a reboot command (defaults to 'reboot')
|
|
||||||
* system.shutdown: specify a shutdown command (defaults to 'shutdown -h now')
|
|
||||||
* system.logout: specify a logout command (defaults to 'i3exit logout')
|
|
||||||
* system.switch_user: specify a command for switching the user (defaults to 'i3exit switch_user')
|
|
||||||
* system.lock: specify a command for locking the screen (defaults to 'i3exit lock')
|
|
||||||
* system.suspend: specify a command for suspending (defaults to 'i3exit suspend')
|
|
||||||
* system.hibernate: specify a command for hibernating (defaults to 'i3exit hibernate')
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import bumblebee.popup_v2
|
|
||||||
import functools
|
|
||||||
|
|
||||||
try:
|
|
||||||
import Tkinter as tk
|
|
||||||
import tkMessageBox as tkmessagebox
|
|
||||||
except ImportError:
|
|
||||||
# python 3
|
|
||||||
try:
|
|
||||||
import tkinter as tk
|
|
||||||
from tkinter import messagebox as tkmessagebox
|
|
||||||
except ImportError:
|
|
||||||
logging.warning("failed to import tkinter - bumblebee popups won't work!")
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.text)
|
|
||||||
)
|
|
||||||
|
|
||||||
self._confirm = True
|
|
||||||
if self.parameter("confirm", "true") == "false":
|
|
||||||
self._confirm = False
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self.popup)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def text(self, widget):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def _on_command(self, header, text, command):
|
|
||||||
do_it = True
|
|
||||||
if self._confirm:
|
|
||||||
root = tk.Tk()
|
|
||||||
root.withdraw()
|
|
||||||
root.focus_set()
|
|
||||||
|
|
||||||
do_it = tkmessagebox.askyesno(header, text)
|
|
||||||
root.destroy()
|
|
||||||
|
|
||||||
if do_it:
|
|
||||||
bumblebee.util.execute(command)
|
|
||||||
|
|
||||||
|
|
||||||
def popup(self, widget):
|
|
||||||
menu = bumblebee.popup_v2.PopupMenu()
|
|
||||||
reboot_cmd = self.parameter("reboot", "reboot")
|
|
||||||
shutdown_cmd = self.parameter("shutdown", "shutdown -h now")
|
|
||||||
logout_cmd = self.parameter("logout", "i3exit logout")
|
|
||||||
switch_user_cmd = self.parameter("switch_user", "i3exit switch_user")
|
|
||||||
lock_cmd = self.parameter("lock", "i3exit lock")
|
|
||||||
suspend_cmd = self.parameter("suspend", "i3exit suspend")
|
|
||||||
hibernate_cmd = self.parameter("hibernate", "i3exit hibernate")
|
|
||||||
|
|
||||||
menu.add_menuitem("shutdown", callback=functools.partial(self._on_command, "Shutdown", "Shutdown?", shutdown_cmd))
|
|
||||||
menu.add_menuitem("reboot", callback=functools.partial(self._on_command, "Reboot", "Reboot?", reboot_cmd))
|
|
||||||
menu.add_menuitem("log out", callback=functools.partial(self._on_command, "Log out", "Log out?", "i3exit logout"))
|
|
||||||
# don't ask for these
|
|
||||||
menu.add_menuitem("switch user", callback=functools.partial(bumblebee.util.execute, switch_user_cmd))
|
|
||||||
menu.add_menuitem("lock", callback=functools.partial(bumblebee.util.execute, lock_cmd))
|
|
||||||
menu.add_menuitem("suspend", callback=functools.partial(bumblebee.util.execute, suspend_cmd))
|
|
||||||
menu.add_menuitem("hibernate", callback=functools.partial(bumblebee.util.execute, hibernate_cmd))
|
|
||||||
|
|
||||||
menu.show(widget)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
return []
|
|
|
@ -1,42 +0,0 @@
|
||||||
"""Displays the number of pending tasks in TaskWarrior.
|
|
||||||
|
|
||||||
Requires the following library:
|
|
||||||
* taskw
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* taskwarrior.taskrc : path to the taskrc file (defaults to ~/.taskrc)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
try:
|
|
||||||
from taskw import TaskWarrior
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
"""TaskWarrior module."""
|
|
||||||
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
"""Initialize taskwarrior module."""
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(
|
|
||||||
full_text=self.output))
|
|
||||||
self._pending_tasks_count = "0"
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
"""Return a string with the number of pending tasks from TaskWarrior."""
|
|
||||||
try:
|
|
||||||
taskrc = self.parameter("taskrc", "~/.taskrc")
|
|
||||||
w = TaskWarrior(config_filename=taskrc)
|
|
||||||
pending_tasks = w.filter_tasks({'status': 'pending'})
|
|
||||||
self._pending_tasks_count = str(len(pending_tasks))
|
|
||||||
except:
|
|
||||||
self._pending_tasks_count = 'Error'
|
|
||||||
|
|
||||||
def output(self, _):
|
|
||||||
"""Format the task counter to output in bumblebee."""
|
|
||||||
return "{}".format(self._pending_tasks_count)
|
|
|
@ -1,14 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Test module
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text="test")
|
|
||||||
)
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,85 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays focused i3 window title.
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
* i3ipc
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* title.max : Maximum character length for title before truncating. Defaults to 64.
|
|
||||||
* title.placeholder : Placeholder text to be placed if title was truncated. Defaults to "...".
|
|
||||||
* title.scroll : Boolean flag for scrolling title. Defaults to False
|
|
||||||
"""
|
|
||||||
|
|
||||||
import threading
|
|
||||||
|
|
||||||
try:
|
|
||||||
import i3ipc
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
from bumblebee.output import scrollable
|
|
||||||
|
|
||||||
_no_title = "n/a"
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
"""Window title module."""
|
|
||||||
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(
|
|
||||||
engine,
|
|
||||||
config
|
|
||||||
)
|
|
||||||
|
|
||||||
# parsing of parameters
|
|
||||||
self._scroll = bumblebee.util.asbool(self.parameter("scroll", False))
|
|
||||||
self._max = int(self.parameter("max", 64))
|
|
||||||
self._placeholder = self.parameter("placeholder", "...")
|
|
||||||
|
|
||||||
# set output of the module
|
|
||||||
self.widgets(bumblebee.output.Widget(full_text=
|
|
||||||
self._scrolling_focused_title if self._scroll else self._focused_title))
|
|
||||||
|
|
||||||
# create a connection with i3ipc
|
|
||||||
try:
|
|
||||||
self._i3 = i3ipc.Connection()
|
|
||||||
# event is called both on focus change and title change
|
|
||||||
self._i3.on("window", lambda _p_i3, _p_e: self._pollTitle())
|
|
||||||
# begin listening for events
|
|
||||||
threading.Thread(target=self._i3.main).start()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# initialize the first title
|
|
||||||
self._pollTitle()
|
|
||||||
|
|
||||||
def _focused_title(self, widget):
|
|
||||||
return self._title
|
|
||||||
|
|
||||||
@scrollable
|
|
||||||
def _scrolling_focused_title(self, widget):
|
|
||||||
return self._full_title
|
|
||||||
|
|
||||||
def _pollTitle(self):
|
|
||||||
"""Updating current title."""
|
|
||||||
try:
|
|
||||||
self._full_title = self._i3.get_tree().find_focused().name
|
|
||||||
except:
|
|
||||||
self._full_title = _no_title
|
|
||||||
if self._full_title is None:
|
|
||||||
self._full_title = _no_title
|
|
||||||
|
|
||||||
if not self._scroll:
|
|
||||||
# cut the text if it is too long
|
|
||||||
if len(self._full_title) > self._max:
|
|
||||||
self._title = self._full_title[0:self._max - len(self._placeholder)]
|
|
||||||
self._title = "{}{}".format(self._title, self._placeholder)
|
|
||||||
else:
|
|
||||||
self._title = self._full_title
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,45 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the number of todo items from a text file
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* todo.file: File to read TODOs from (defaults to ~/Documents/todo.txt)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.output)
|
|
||||||
)
|
|
||||||
self._doc = os.path.expanduser(self.parameter("file", "~/Documents/todo.txt"))
|
|
||||||
self._todos = self.count_items()
|
|
||||||
|
|
||||||
|
|
||||||
def output(self, widget):
|
|
||||||
self._todos = self.count_items()
|
|
||||||
return str(self._todos)
|
|
||||||
|
|
||||||
|
|
||||||
def state(self, widgets):
|
|
||||||
if self._todos == 0:
|
|
||||||
return "empty"
|
|
||||||
return "items"
|
|
||||||
|
|
||||||
|
|
||||||
def count_items(self):
|
|
||||||
try:
|
|
||||||
i = -1
|
|
||||||
with open(self._doc) as f:
|
|
||||||
for i, l in enumerate(f):
|
|
||||||
pass
|
|
||||||
return i+1
|
|
||||||
except Exception:
|
|
||||||
return 0
|
|
|
@ -1,110 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays network IO for interfaces.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* traffic.exclude: Comma-separated list of interface prefixes to exclude (defaults to "lo,virbr,docker,vboxnet,veth")
|
|
||||||
* traffic.states: Comma-separated list of states to show (prefix with "^" to invert - i.e. ^down -> show all devices that are not in state down)
|
|
||||||
* traffic.showname: If set to False, hide network interface name (defaults to True)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
import psutil
|
|
||||||
import netifaces
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widgets = []
|
|
||||||
super(Module, self).__init__(engine, config, widgets)
|
|
||||||
self._exclude = tuple(filter(len, self.parameter("exclude", "lo,virbr,docker,vboxnet,veth").split(",")))
|
|
||||||
self._status = ""
|
|
||||||
|
|
||||||
self._showname = bumblebee.util.asbool(self.parameter("showname", True))
|
|
||||||
self._prev = {}
|
|
||||||
self._states = {}
|
|
||||||
self._lastcheck = 0
|
|
||||||
self._states["include"] = []
|
|
||||||
self._states["exclude"] = []
|
|
||||||
for state in tuple(filter(len, self.parameter("states", "").split(","))):
|
|
||||||
if state[0] == "^":
|
|
||||||
self._states["exclude"].append(state[1:])
|
|
||||||
else:
|
|
||||||
self._states["include"].append(state)
|
|
||||||
self._update_widgets(widgets)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if "traffic.rx" in widget.name:
|
|
||||||
return "rx"
|
|
||||||
if "traffic.tx" in widget.name:
|
|
||||||
return "tx"
|
|
||||||
return self._status
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._update_widgets(widgets)
|
|
||||||
|
|
||||||
def create_widget(self, widgets, name, txt=None, attributes={}):
|
|
||||||
widget = bumblebee.output.Widget(name=name)
|
|
||||||
widget.full_text(txt)
|
|
||||||
widgets.append(widget)
|
|
||||||
|
|
||||||
for key in attributes:
|
|
||||||
widget.set(key, attributes[key])
|
|
||||||
|
|
||||||
return widget
|
|
||||||
|
|
||||||
def get_addresses(self, intf):
|
|
||||||
retval = []
|
|
||||||
try:
|
|
||||||
for ip in netifaces.ifaddresses(intf).get(netifaces.AF_INET, []):
|
|
||||||
if ip.get("addr", "") != "":
|
|
||||||
retval.append(ip.get("addr"))
|
|
||||||
except Exception:
|
|
||||||
return []
|
|
||||||
return retval
|
|
||||||
|
|
||||||
def _update_widgets(self, widgets):
|
|
||||||
interfaces = [i for i in netifaces.interfaces() if not i.startswith(self._exclude)]
|
|
||||||
|
|
||||||
del widgets[:]
|
|
||||||
|
|
||||||
counters = psutil.net_io_counters(pernic=True)
|
|
||||||
now = time.time()
|
|
||||||
timediff = now - (self._lastcheck if self._lastcheck else now)
|
|
||||||
if timediff <= 0: timediff = 1
|
|
||||||
self._lastcheck = now
|
|
||||||
for interface in interfaces:
|
|
||||||
if not interface: interface = "lo"
|
|
||||||
state = "down"
|
|
||||||
if len(self.get_addresses(interface)) > 0:
|
|
||||||
state = "up"
|
|
||||||
elif bumblebee.util.asbool(self.parameter("hide_down", True)):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if len(self._states["exclude"]) > 0 and state in self._states["exclude"]: continue
|
|
||||||
if len(self._states["include"]) > 0 and state not in self._states["include"]: continue
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"rx": counters[interface].bytes_recv,
|
|
||||||
"tx": counters[interface].bytes_sent,
|
|
||||||
}
|
|
||||||
|
|
||||||
name = "traffic-{}".format(interface)
|
|
||||||
|
|
||||||
if self._showname:
|
|
||||||
self.create_widget(widgets, name, interface)
|
|
||||||
|
|
||||||
for direction in ["rx", "tx"]:
|
|
||||||
name = "traffic.{}-{}".format(direction, interface)
|
|
||||||
widget = self.create_widget(widgets, name, attributes={"theme.minwidth": "1000.00MB"})
|
|
||||||
prev = self._prev.get(name, 0)
|
|
||||||
speed = bumblebee.util.bytefmt((int(data[direction]) - int(prev))/timediff)
|
|
||||||
txtspeed ='{0}/s'.format(speed)
|
|
||||||
widget.full_text(txtspeed)
|
|
||||||
self._prev[name] = data[direction]
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,39 +0,0 @@
|
||||||
#pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Toggle twmn notifications."""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text="")
|
|
||||||
)
|
|
||||||
self._paused = False
|
|
||||||
# Make sure that twmn is currently not paused
|
|
||||||
try:
|
|
||||||
bumblebee.util.execute("killall -SIGUSR2 twmnd")
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self.toggle_status
|
|
||||||
)
|
|
||||||
|
|
||||||
def toggle_status(self, event):
|
|
||||||
self._paused = not self._paused
|
|
||||||
|
|
||||||
try:
|
|
||||||
if self._paused:
|
|
||||||
bumblebee.util.execute("systemctl --user start twmnd")
|
|
||||||
else:
|
|
||||||
bumblebee.util.execute("systemctl --user stop twmnd")
|
|
||||||
except:
|
|
||||||
self._paused = not self._paused # toggling failed
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if self._paused:
|
|
||||||
return ["muted"]
|
|
||||||
return ["unmuted"]
|
|
|
@ -1,30 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the system uptime."""
|
|
||||||
|
|
||||||
# Use absolute_import because there's already a datatime module
|
|
||||||
# in the same directory
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.output)
|
|
||||||
)
|
|
||||||
self._uptime = ""
|
|
||||||
|
|
||||||
def output(self, _):
|
|
||||||
return "{}".format(self._uptime)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
with open('/proc/uptime', 'r') as f:
|
|
||||||
uptime_seconds = int(float(f.readline().split()[0]))
|
|
||||||
self._uptime = timedelta(seconds = uptime_seconds)
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,81 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Copy passwords from a password store into the clipboard (currently supports only "pass")
|
|
||||||
|
|
||||||
Many thanks to [@bbernhard](https://github.com/bbernhard) for the idea!
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* vault.duration: Duration until password is cleared from clipboard (defaults to 30)
|
|
||||||
* vault.location: Location of the password store (defaults to ~/.password-store)
|
|
||||||
* vault.offx: x-axis offset of popup menu (defaults to 0)
|
|
||||||
* vault.offy: y-axis offset of popup menu (defaults to 0)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
|
||||||
# - support multiple backends by abstracting the menu structure into a tree
|
|
||||||
# - build the menu and the actions based on that abstracted tree
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.popup_v2
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
def build_menu(parent, current_directory, callback):
|
|
||||||
with os.scandir(current_directory) as it:
|
|
||||||
for entry in it:
|
|
||||||
if entry.name.startswith("."): continue
|
|
||||||
if entry.is_file():
|
|
||||||
name = entry.name[:entry.name.rfind(".")]
|
|
||||||
parent.add_menuitem(name, callback=lambda : callback(os.path.join(current_directory, name)))
|
|
||||||
|
|
||||||
else:
|
|
||||||
submenu = bumblebee.popup_v2.PopupMenu(parent, leave=False)
|
|
||||||
build_menu(submenu, os.path.join(current_directory, entry.name), callback)
|
|
||||||
parent.add_cascade(entry.name, submenu)
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.text)
|
|
||||||
)
|
|
||||||
self._duration = int(self.parameter("duration", 30))
|
|
||||||
self._offx = int(self.parameter("offx", 0))
|
|
||||||
self._offy = int(self.parameter("offy", 0))
|
|
||||||
self._path = os.path.expanduser(self.parameter("location", "~/.password-store/"))
|
|
||||||
self._reset()
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self.popup)
|
|
||||||
|
|
||||||
def popup(self, widget):
|
|
||||||
menu = bumblebee.popup_v2.PopupMenu(leave=False)
|
|
||||||
|
|
||||||
build_menu(menu, self._path, self._callback)
|
|
||||||
menu.show(widget, offset_x=self._offx, offset_y=self._offy)
|
|
||||||
|
|
||||||
def _reset(self):
|
|
||||||
self._timer = None
|
|
||||||
self._text = str(self.parameter("text", "<click-for-password>"))
|
|
||||||
|
|
||||||
def _callback(self, secret_name):
|
|
||||||
secret_name = secret_name.replace(self._path, "") # remove common path
|
|
||||||
if self._timer:
|
|
||||||
self._timer.cancel()
|
|
||||||
# bumblebee.util.execute hangs for some reason
|
|
||||||
os.system("PASSWORD_STORE_CLIP_TIME={} pass -c {} > /dev/null 2>&1".format(self._duration, secret_name))
|
|
||||||
self._timer = threading.Timer(self._duration, self._reset)
|
|
||||||
self._timer.start()
|
|
||||||
self._start = int(time.time())
|
|
||||||
self._text = secret_name
|
|
||||||
|
|
||||||
def text(self, widget):
|
|
||||||
if self._timer:
|
|
||||||
return "{} ({}s)".format(self._text, self._duration - (int(time.time()) - self._start))
|
|
||||||
return self._text
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,97 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
""" Displays the VPN profile that is currently in use.
|
|
||||||
|
|
||||||
Left click opens a popup menu that lists all available VPN profiles and allows to establish
|
|
||||||
a VPN connection using that profile.
|
|
||||||
|
|
||||||
Prerequisites:
|
|
||||||
* nmcli needs to be installed and configured properly.
|
|
||||||
To quickly test, whether nmcli is working correctly, type "nmcli -g NAME,TYPE,DEVICE con" which
|
|
||||||
lists all the connection profiles that are configured. Make sure that your VPN profile is in that list!
|
|
||||||
|
|
||||||
e.g: to import a openvpn profile via nmcli:
|
|
||||||
sudo nmcli connection import type openvpn file </path/to/your/openvpn/profile.ovpn>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import functools
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.vpn_status)
|
|
||||||
)
|
|
||||||
|
|
||||||
self._connected_vpn_profile = None
|
|
||||||
self._selected_vpn_profile = None
|
|
||||||
|
|
||||||
res = bumblebee.util.execute("nmcli -g NAME,TYPE c")
|
|
||||||
lines = res.splitlines()
|
|
||||||
|
|
||||||
self._vpn_profiles = []
|
|
||||||
for line in lines:
|
|
||||||
info = line.split(':')
|
|
||||||
try:
|
|
||||||
if info[1] == "vpn":
|
|
||||||
self._vpn_profiles.append(info[0])
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd=self.popup)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
try:
|
|
||||||
res = bumblebee.util.execute("nmcli -g NAME,TYPE,DEVICE con")
|
|
||||||
lines = res.splitlines()
|
|
||||||
self._connected_vpn_profile = None
|
|
||||||
for line in lines:
|
|
||||||
info = line.split(':')
|
|
||||||
if info[1] == "vpn" and info[2] != "":
|
|
||||||
self._connected_vpn_profile = info[0]
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logging.exception("Couldn't get VPN status")
|
|
||||||
self._connected_vpn_profile = None
|
|
||||||
|
|
||||||
def vpn_status(self, widget):
|
|
||||||
if self._connected_vpn_profile is None:
|
|
||||||
return "off"
|
|
||||||
return self._connected_vpn_profile
|
|
||||||
|
|
||||||
def _on_vpn_disconnect(self):
|
|
||||||
try:
|
|
||||||
bumblebee.util.execute("nmcli c down \"{vpn}\""
|
|
||||||
.format(vpn=self._connected_vpn_profile))
|
|
||||||
self._connected_vpn_profile = None
|
|
||||||
except Exception as e:
|
|
||||||
logging.exception("Couldn't disconnect VPN connection")
|
|
||||||
|
|
||||||
def _on_vpn_connect(self, name):
|
|
||||||
self._selected_vpn_profile = name
|
|
||||||
|
|
||||||
try:
|
|
||||||
bumblebee.util.execute("nmcli c up \"{vpn}\""
|
|
||||||
.format(vpn=self._connected_vpn_profile))
|
|
||||||
self._connected_vpn_profile = name
|
|
||||||
except Exception as e:
|
|
||||||
logging.exception("Couldn't establish VPN connection")
|
|
||||||
self._connected_vpn_profile = None
|
|
||||||
|
|
||||||
def popup(self, widget):
|
|
||||||
menu = bumblebee.popup_v2.PopupMenu()
|
|
||||||
|
|
||||||
if self._connected_vpn_profile is not None:
|
|
||||||
menu.add_menuitem("Disconnect", callback=self._on_vpn_disconnect)
|
|
||||||
for vpn_profile in self._vpn_profiles:
|
|
||||||
if self._connected_vpn_profile is not None and self._connected_vpn_profile == vpn_profile:
|
|
||||||
continue
|
|
||||||
menu.add_menuitem(vpn_profile, callback=functools.partial(self._on_vpn_connect, vpn_profile))
|
|
||||||
menu.show(widget)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
return []
|
|
|
@ -1,138 +0,0 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Displays the temperature on the current location based on the ip
|
|
||||||
|
|
||||||
Requires the following python packages:
|
|
||||||
* requests
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* weather.location: Set location, defaults to 'auto' for getting location from http://ipinfo.io
|
|
||||||
If set to a comma-separated list, left-click and right-click can be used to rotate the locations.
|
|
||||||
Locations should be city names or city ids.
|
|
||||||
* weather.unit: metric (default), kelvin, imperial
|
|
||||||
* weather.showcity: If set to true, show location information, otherwise hide it (defaults to true)
|
|
||||||
* weather.showminmax: If set to true, show the minimum and maximum temperature, otherwise hide it (defaults to false)
|
|
||||||
* weather.apikey: API key from http://api.openweathermap.org
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
import re
|
|
||||||
|
|
||||||
try:
|
|
||||||
import requests
|
|
||||||
from requests.exceptions import RequestException
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.output)
|
|
||||||
)
|
|
||||||
self._temperature = 0
|
|
||||||
self._apikey = self.parameter("apikey", "af7bfe22287c652d032a3064ffa44088")
|
|
||||||
self._location = self.parameter("location", "auto")
|
|
||||||
if "," in self._location:
|
|
||||||
self._location = self._location.split(",")
|
|
||||||
else:
|
|
||||||
self._location = [self._location]
|
|
||||||
self._index = 0
|
|
||||||
self._showcity = bumblebee.util.asbool(self.parameter("showcity", True))
|
|
||||||
self._showminmax = bumblebee.util.asbool(self.parameter("showminmax", False))
|
|
||||||
self._unit = self.parameter("unit", "metric")
|
|
||||||
self._valid = False
|
|
||||||
self.interval_factor(60)
|
|
||||||
self.interval(15)
|
|
||||||
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self._next_location)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self._prev_location)
|
|
||||||
|
|
||||||
def _next_location(self, event):
|
|
||||||
self._index = 0 if self._index >= len(self._location) - 1 else self._index + 1
|
|
||||||
self.update(self.widgets())
|
|
||||||
|
|
||||||
def _prev_location(self, event):
|
|
||||||
self._index = len(self._location)-1 if self._index <= 0 else self._index - 1
|
|
||||||
self.update(self.widgets())
|
|
||||||
|
|
||||||
def _unit_suffix(self):
|
|
||||||
if self._unit == "metric":
|
|
||||||
return "C"
|
|
||||||
if self._unit == "kelvin":
|
|
||||||
return "K"
|
|
||||||
if self._unit == "imperial":
|
|
||||||
return "F"
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def temperature(self):
|
|
||||||
return u"{}°{}".format(self._temperature, self._unit_suffix())
|
|
||||||
|
|
||||||
def tempmin(self):
|
|
||||||
return u"{}°{}".format(self._tempmin, self._unit_suffix())
|
|
||||||
|
|
||||||
def tempmax(self):
|
|
||||||
return u"{}°{}".format(self._tempmax, self._unit_suffix())
|
|
||||||
|
|
||||||
def city(self):
|
|
||||||
city = re.sub('[_-]', ' ', self._city)
|
|
||||||
return u"{} ".format(city)
|
|
||||||
|
|
||||||
def output(self, widget):
|
|
||||||
if not self._valid:
|
|
||||||
return u"?"
|
|
||||||
if self._showminmax:
|
|
||||||
self._showcity=False
|
|
||||||
return self.city() + self.temperature() + " Hi:" + self.tempmax() + " Lo:" + self.tempmin()
|
|
||||||
elif self._showcity:
|
|
||||||
return self.city() + self.temperature()
|
|
||||||
else:
|
|
||||||
return self.temperature()
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if self._valid:
|
|
||||||
if "thunderstorm" in self._weather:
|
|
||||||
return ['thunder']
|
|
||||||
elif "drizzle" in self._weather:
|
|
||||||
return ['rain']
|
|
||||||
elif "rain" in self._weather:
|
|
||||||
return ['rain']
|
|
||||||
elif "snow" in self._weather:
|
|
||||||
return ['snow']
|
|
||||||
elif "sleet" in self._weather:
|
|
||||||
return ['sleet']
|
|
||||||
elif "clear" in self._weather:
|
|
||||||
return ['clear']
|
|
||||||
elif "cloud" in self._weather:
|
|
||||||
return ['clouds']
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
try:
|
|
||||||
weather_url = "http://api.openweathermap.org/data/2.5/weather?appid={}".format(self._apikey)
|
|
||||||
weather_url = "{}&units={}".format(weather_url, self._unit)
|
|
||||||
if self._location[self._index] == "auto":
|
|
||||||
location_url = "http://ipinfo.io/json"
|
|
||||||
location = requests.get(location_url).json()
|
|
||||||
coord = location["loc"].split(",")
|
|
||||||
weather_url = "{url}&lat={lat}&lon={lon}".format(url=weather_url, lat=coord[0], lon=coord[1])
|
|
||||||
elif self._location[self._index].isdigit():
|
|
||||||
weather_url = "{url}&id={id}".format(url=weather_url, id=self._location[self._index])
|
|
||||||
else:
|
|
||||||
weather_url = "{url}&q={city}".format(url=weather_url, city=self._location[self._index])
|
|
||||||
weather = requests.get(weather_url).json()
|
|
||||||
self._city = weather['name']
|
|
||||||
self._temperature = int(weather['main']['temp'])
|
|
||||||
self._tempmin = int(weather['main']['temp_min'])
|
|
||||||
self._tempmax = int(weather['main']['temp_max'])
|
|
||||||
self._weather = weather['weather'][0]['main'].lower()
|
|
||||||
self._valid = True
|
|
||||||
except Exception:
|
|
||||||
self._valid = False
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,17 +0,0 @@
|
||||||
#pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Opens a random xkcd comic in the browser."""
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text="xkcd")
|
|
||||||
)
|
|
||||||
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
|
|
||||||
cmd="xdg-open https://c.xkcd.com/random/comic/"
|
|
||||||
)
|
|
|
@ -1,118 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Shows a widget for each connected screen and allows the user to enable/disable screens.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* xrandr.overwrite_i3config: If set to 'true', this module assembles a new i3 config
|
|
||||||
every time a screen is enabled or disabled by taking the file "~/.config/i3/config.template"
|
|
||||||
and appending a file "~/.config/i3/config.<screen name>" for every screen.
|
|
||||||
* xrandr.autoupdate: If set to 'false', does *not* invoke xrandr automatically. Instead, the
|
|
||||||
module will only refresh when displays are enabled or disabled (defaults to true)
|
|
||||||
|
|
||||||
Requires the following python module:
|
|
||||||
* (optional) i3 - if present, the need for updating the widget list is auto-detected
|
|
||||||
|
|
||||||
Requires the following executable:
|
|
||||||
* xrandr
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import bumblebee.util
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
try:
|
|
||||||
import i3
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widgets = []
|
|
||||||
self._engine = engine
|
|
||||||
super(Module, self).__init__(engine, config, widgets)
|
|
||||||
self._autoupdate = bumblebee.util.asbool(self.parameter("autoupdate", True))
|
|
||||||
self._needs_update = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
i3.Subscription(self._output_update, "output")
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _output_update(self, event, data, _):
|
|
||||||
self._needs_update = True
|
|
||||||
|
|
||||||
def update_widgets(self, widgets):
|
|
||||||
new_widgets = []
|
|
||||||
|
|
||||||
if self._autoupdate == False and self._needs_update == False:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._needs_update = False
|
|
||||||
|
|
||||||
for line in bumblebee.util.execute("xrandr -q").split("\n"):
|
|
||||||
if not " connected" in line:
|
|
||||||
continue
|
|
||||||
display = line.split(" ", 2)[0]
|
|
||||||
m = re.search(r'\d+x\d+\+(\d+)\+\d+', line)
|
|
||||||
|
|
||||||
widget = self.widget(display)
|
|
||||||
if not widget:
|
|
||||||
widget = bumblebee.output.Widget(full_text=display, name=display)
|
|
||||||
self._engine.input.register_callback(widget, button=1, cmd=self._toggle)
|
|
||||||
self._engine.input.register_callback(widget, button=3, cmd=self._toggle)
|
|
||||||
new_widgets.append(widget)
|
|
||||||
widget.set("state", "on" if m else "off")
|
|
||||||
widget.set("pos", int(m.group(1)) if m else sys.maxsize)
|
|
||||||
|
|
||||||
while len(widgets) > 0:
|
|
||||||
del widgets[0]
|
|
||||||
for widget in new_widgets:
|
|
||||||
widgets.append(widget)
|
|
||||||
|
|
||||||
if self._autoupdate == False:
|
|
||||||
widget = bumblebee.output.Widget(full_text="")
|
|
||||||
widget.set("state", "refresh")
|
|
||||||
self._engine.input.register_callback(widget, button=1, cmd=self._refresh)
|
|
||||||
widgets.append(widget)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self.update_widgets(widgets)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
return widget.get("state", "off")
|
|
||||||
|
|
||||||
def _refresh(self, event):
|
|
||||||
self._needs_update = True
|
|
||||||
|
|
||||||
def _toggle(self, event):
|
|
||||||
self._needs_update = True
|
|
||||||
path = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
if bumblebee.util.asbool(self.parameter("overwrite_i3config", False)) == True:
|
|
||||||
toggle_cmd = "{}/../../bin/toggle-display.sh".format(path)
|
|
||||||
else:
|
|
||||||
toggle_cmd = "xrandr"
|
|
||||||
|
|
||||||
widget = self.widget_by_id(event["instance"])
|
|
||||||
|
|
||||||
if widget.get("state") == "on":
|
|
||||||
bumblebee.util.execute("{} --output {} --off".format(toggle_cmd, widget.name))
|
|
||||||
else:
|
|
||||||
first_neighbor = next((widget for widget in self.widgets() if widget.get("state") == "on"), None)
|
|
||||||
last_neighbor = next((widget for widget in reversed(self.widgets()) if widget.get("state") == "on"), None)
|
|
||||||
|
|
||||||
neighbor = first_neighbor if event["button"] == bumblebee.input.LEFT_MOUSE else last_neighbor
|
|
||||||
|
|
||||||
if neighbor is None:
|
|
||||||
bumblebee.util.execute("{} --output {} --auto".format(toggle_cmd, widget.name))
|
|
||||||
else:
|
|
||||||
bumblebee.util.execute("{} --output {} --auto --{}-of {}".format(toggle_cmd, widget.name,
|
|
||||||
"left" if event.get("button") == bumblebee.input.LEFT_MOUSE else "right",
|
|
||||||
neighbor.name))
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,33 +0,0 @@
|
||||||
# pylint: disable=C0111,R0903
|
|
||||||
|
|
||||||
"""Shows yubikey information
|
|
||||||
|
|
||||||
Requires: https://github.com/Yubico/python-yubico
|
|
||||||
|
|
||||||
The output indicates that a YubiKey is not connected or it displays
|
|
||||||
the corresponding serial number.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import yubico
|
|
||||||
|
|
||||||
import bumblebee.input
|
|
||||||
import bumblebee.output
|
|
||||||
import bumblebee.engine
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
super(Module, self).__init__(engine, config,
|
|
||||||
bumblebee.output.Widget(full_text=self.keystate))
|
|
||||||
self._keystate = "No YubiKey"
|
|
||||||
|
|
||||||
def keystate(self, widget):
|
|
||||||
return self._keystate
|
|
||||||
|
|
||||||
def update(self, widget):
|
|
||||||
try:
|
|
||||||
self._keystate = "YubiKey: " + str(yubico.find_yubikey(debug=False).serial())
|
|
||||||
except yubico.yubico_exception.YubicoError:
|
|
||||||
self._keystate = "No YubiKey"
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,187 +0,0 @@
|
||||||
"""Displays info about zpools present on the system
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* zpool.list: Comma-separated list of zpools to display info for. If empty, info for all zpools
|
|
||||||
is displayed. (Default: "")
|
|
||||||
* zpool.format: Format string, tags {name}, {used}, {left}, {size}, {percentfree}, {percentuse},
|
|
||||||
{status}, {shortstatus}, {fragpercent}, {deduppercent} are supported.
|
|
||||||
(Default: "{name} {used}/{size} ({percentfree}%)")
|
|
||||||
* zpool.showio: Show also widgets detailing current read and write I/O (Default: true)
|
|
||||||
* zpool.ioformat: Format string for I/O widget, tags {ops} (operations per seconds) and {band}
|
|
||||||
(bandwidth) are supported. (Default: "{band}")
|
|
||||||
* zpool.warnfree: Warn if free space is below this percentage (Default: 10)
|
|
||||||
* zpool.sudo: Use sudo when calling the `zpool` binary. (Default: false)
|
|
||||||
|
|
||||||
Option `zpool.sudo` is intended for Linux users using zfsonlinux older than 0.7.0: In pre-0.7.0
|
|
||||||
releases of zfsonlinux regular users couldn't invoke even informative commands such as
|
|
||||||
`zpool list`. If this option is true, command `zpool list` is invoked with sudo. If this option
|
|
||||||
is used, the following (or ekvivalent) must be added to the `sudoers(5)`:
|
|
||||||
|
|
||||||
```
|
|
||||||
<username/ALL> ALL = (root) NOPASSWD: /usr/bin/zpool list
|
|
||||||
```
|
|
||||||
|
|
||||||
Be aware of security implications of doing this!
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
from pkg_resources import parse_version
|
|
||||||
import bumblebee.engine
|
|
||||||
from bumblebee.util import execute, bytefmt, asbool
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class Module(bumblebee.engine.Module):
|
|
||||||
def __init__(self, engine, config):
|
|
||||||
widgets = []
|
|
||||||
super(Module, self).__init__(engine, config, widgets)
|
|
||||||
|
|
||||||
self._includelist = set(filter(lambda x: len(x) > 0,
|
|
||||||
self.parameter("list", default="").split(',')))
|
|
||||||
self._format = self.parameter("format", default="{name} {shortstatus} {used}/{size} " +
|
|
||||||
"({percentfree}%)")
|
|
||||||
self._usesudo = asbool(self.parameter("sudo", default=False))
|
|
||||||
self._showio = asbool(self.parameter("showio", default=True))
|
|
||||||
self._ioformat = self.parameter("ioformat", default="{band}")
|
|
||||||
self._warnfree = int(self.parameter("warnfree", default=10))
|
|
||||||
|
|
||||||
self._update_widgets(widgets)
|
|
||||||
|
|
||||||
def update(self, widgets):
|
|
||||||
self._update_widgets(widgets)
|
|
||||||
|
|
||||||
def state(self, widget):
|
|
||||||
if widget.name.endswith("__read"):
|
|
||||||
return "poolread"
|
|
||||||
elif widget.name.endswith("__write"):
|
|
||||||
return "poolwrite"
|
|
||||||
|
|
||||||
state = widget.get("state")
|
|
||||||
if state == "FAULTED":
|
|
||||||
return [state, "critical"]
|
|
||||||
elif state == "DEGRADED" or widget.get("percentfree") < self._warnfree:
|
|
||||||
return [state, "warning"]
|
|
||||||
|
|
||||||
return state
|
|
||||||
|
|
||||||
def _update_widgets(self, widgets):
|
|
||||||
zfs_version_path = "/sys/module/zfs/version"
|
|
||||||
# zpool list -H: List all zpools, use script mode (no headers and tabs as separators).
|
|
||||||
try:
|
|
||||||
with open(zfs_version_path, 'r') as zfs_mod_version:
|
|
||||||
zfs_version = zfs_mod_version.readline().rstrip().split('-')[0]
|
|
||||||
except IOError:
|
|
||||||
# ZFS isn't installed or the module isn't loaded, stub the version
|
|
||||||
zfs_version = "0.0.0"
|
|
||||||
logging.error("ZFS version information not found at {}, check the module is loaded.".format(zfs_version_path))
|
|
||||||
|
|
||||||
raw_zpools = execute(('sudo ' if self._usesudo else '') + 'zpool list -H').split('\n')
|
|
||||||
|
|
||||||
for widget in widgets:
|
|
||||||
widget.set("visited", False)
|
|
||||||
|
|
||||||
for raw_zpool in raw_zpools:
|
|
||||||
try:
|
|
||||||
# Ignored fields (assigned to _) are "expandsz" and "altroot", also "ckpoint" in ZFS 0.8.0+
|
|
||||||
if parse_version(zfs_version) < parse_version("0.8.0"):
|
|
||||||
name, size, alloc, free, _, frag, cap, dedup, health, _ = raw_zpool.split('\t')
|
|
||||||
else:
|
|
||||||
name, size, alloc, free, _, _, frag, cap, dedup, health, _ = raw_zpool.split('\t')
|
|
||||||
cap = cap.rstrip('%')
|
|
||||||
percentuse=int(cap)
|
|
||||||
percentfree=100-percentuse
|
|
||||||
# There is a command, zpool iostat, which is however blocking and was therefore
|
|
||||||
# causing issues.
|
|
||||||
# Instead, we read file `/proc/spl/kstat/zfs/<poolname>/io` which contains
|
|
||||||
# cumulative I/O statistics since boot (or pool creation). We store these values
|
|
||||||
# (and timestamp) during each widget update, and during the next widget update we
|
|
||||||
# use them to compute delta of transferred bytes, and using the last and current
|
|
||||||
# timestamp the rate at which they have been transferred.
|
|
||||||
with open("/proc/spl/kstat/zfs/{}/io".format(name), "r") as f:
|
|
||||||
# Third row provides data we need, we are interested in the first 4 values.
|
|
||||||
# More info about this file can be found here:
|
|
||||||
# https://github.com/zfsonlinux/zfs/blob/master/lib/libspl/include/sys/kstat.h#L580
|
|
||||||
# The 4 values are:
|
|
||||||
# nread, nwritten, reads, writes
|
|
||||||
iostat = list(map(int, f.readlines()[2].split()[:4]))
|
|
||||||
except (ValueError, IOError):
|
|
||||||
# Unable to parse info about this pool, skip it
|
|
||||||
continue
|
|
||||||
|
|
||||||
if self._includelist and name not in self._includelist:
|
|
||||||
continue
|
|
||||||
|
|
||||||
widget = self.widget(name)
|
|
||||||
if not widget:
|
|
||||||
widget = bumblebee.output.Widget(name=name)
|
|
||||||
widget.set("last_iostat", [0, 0, 0, 0])
|
|
||||||
widget.set("last_timestamp", 0)
|
|
||||||
widgets.append(widget)
|
|
||||||
|
|
||||||
delta_iostat = [b - a for a, b in zip(iostat, widget.get("last_iostat"))]
|
|
||||||
widget.set("last_iostat", iostat)
|
|
||||||
|
|
||||||
# From docs:
|
|
||||||
# > Note that even though the time is always returned as a floating point number, not
|
|
||||||
# > all systems provide time with a better precision than 1 second.
|
|
||||||
# Be aware that that may affect the precision of reported I/O
|
|
||||||
# Also, during one update cycle the reported I/O may be garbage if the system time
|
|
||||||
# was changed.
|
|
||||||
timestamp = time.time()
|
|
||||||
delta_timestamp = widget.get("last_timestamp") - timestamp
|
|
||||||
widget.set("last_timestamp", time.time())
|
|
||||||
|
|
||||||
# abs is there because sometimes the result is -0
|
|
||||||
rate_iostat = [abs(x / delta_timestamp) for x in delta_iostat]
|
|
||||||
nread, nwritten, reads, writes = rate_iostat
|
|
||||||
|
|
||||||
# theme.minwidth is not set since these values are not expected to change
|
|
||||||
# rapidly
|
|
||||||
widget.full_text(self._format.format(name=name, used=alloc, left=free, size=size,
|
|
||||||
percentfree=percentfree, percentuse=percentuse,
|
|
||||||
status=health,
|
|
||||||
shortstatus=self._shortstatus(health),
|
|
||||||
fragpercent=frag, deduppercent=dedup))
|
|
||||||
widget.set("state", health)
|
|
||||||
widget.set("percentfree", percentfree)
|
|
||||||
widget.set("visited", True)
|
|
||||||
|
|
||||||
if self._showio:
|
|
||||||
wname, rname = [name + x for x in ["__write", "__read"]]
|
|
||||||
widget_w = self.widget(wname)
|
|
||||||
widget_r = self.widget(rname)
|
|
||||||
if not widget_w or not widget_r:
|
|
||||||
widget_r = bumblebee.output.Widget(name=rname)
|
|
||||||
widget_w = bumblebee.output.Widget(name=wname)
|
|
||||||
widgets.extend([widget_r, widget_w])
|
|
||||||
for w in [widget_r, widget_w]:
|
|
||||||
w.set("theme.minwidth", self._ioformat.format(ops=9999,
|
|
||||||
band=bytefmt(999.99*(1024**2))))
|
|
||||||
w.set("visited", True)
|
|
||||||
widget_w.full_text(self._ioformat.format(ops=round(writes),
|
|
||||||
band=bytefmt(nwritten)))
|
|
||||||
widget_r.full_text(self._ioformat.format(ops=round(reads),
|
|
||||||
band=bytefmt(nread)))
|
|
||||||
|
|
||||||
for widget in widgets:
|
|
||||||
if widget.get("visited") is False:
|
|
||||||
widgets.remove(widget)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _shortstatus(status):
|
|
||||||
# From `zpool(8)`, section Device Failure and Recovery:
|
|
||||||
# A pool's health status is described by one of three states: online, degraded, or faulted.
|
|
||||||
# An online pool has all devices operating normally. A degraded pool is one in which one
|
|
||||||
# or more devices have failed, but the data is still available due to a redundant
|
|
||||||
# configuration. A faulted pool has corrupted metadata, or one or more faulted devices, and
|
|
||||||
# insufficient replicas to continue functioning.
|
|
||||||
shortstate = {
|
|
||||||
"DEGRADED": "DEG",
|
|
||||||
"FAULTED": "FLT",
|
|
||||||
"ONLINE": "ONL",
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
return shortstate[status]
|
|
||||||
except KeyError:
|
|
||||||
return ""
|
|
|
@ -1,324 +0,0 @@
|
||||||
# pylint: disable=R0201
|
|
||||||
|
|
||||||
"""Output classes"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import uuid
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import bumblebee.store
|
|
||||||
import bumblebee.util
|
|
||||||
|
|
||||||
MAX_PERCENTS = 100.
|
|
||||||
CHARS = 8
|
|
||||||
HBARS = [
|
|
||||||
u"\u2581",
|
|
||||||
u"\u2582",
|
|
||||||
u"\u2583",
|
|
||||||
u"\u2584",
|
|
||||||
u"\u2585",
|
|
||||||
u"\u2586",
|
|
||||||
u"\u2587",
|
|
||||||
u"\u2588"]
|
|
||||||
VBARS = [
|
|
||||||
u"\u258f",
|
|
||||||
u"\u258e",
|
|
||||||
u"\u258d",
|
|
||||||
u"\u258c",
|
|
||||||
u"\u258b",
|
|
||||||
u"\u258a",
|
|
||||||
u"\u2589",
|
|
||||||
u"\u2588"]
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
def scrollable(func):
|
|
||||||
def wrapper(module, widget):
|
|
||||||
text = func(module, widget)
|
|
||||||
if not text:
|
|
||||||
return text
|
|
||||||
width = widget.get("theme.width", int(module.parameter("width", 30)))
|
|
||||||
if bumblebee.util.asbool(module.parameter("scrolling.makewide", "true")):
|
|
||||||
widget.set("theme.minwidth", "A"*width)
|
|
||||||
if width < 0:
|
|
||||||
return text
|
|
||||||
if len(text) <= width:
|
|
||||||
return text
|
|
||||||
# we need to shorten
|
|
||||||
|
|
||||||
try:
|
|
||||||
bounce = int(module.parameter("scrolling.bounce", 1))
|
|
||||||
except ValueError:
|
|
||||||
bounce = 1
|
|
||||||
try:
|
|
||||||
scroll_speed = int(module.parameter("scrolling.speed", 1))
|
|
||||||
except ValueError:
|
|
||||||
scroll_speed = 1
|
|
||||||
start = widget.get("scrolling.start", -1)
|
|
||||||
direction = widget.get("scrolling.direction", "right")
|
|
||||||
start += scroll_speed if direction == "right" else -(scroll_speed)
|
|
||||||
|
|
||||||
if width + start > len(text) + (scroll_speed -1):
|
|
||||||
if bounce:
|
|
||||||
widget.set("scrolling.direction", "left")
|
|
||||||
else:
|
|
||||||
start = 0
|
|
||||||
elif start <= 0:
|
|
||||||
if bounce:
|
|
||||||
widget.set("scrolling.direction", "right")
|
|
||||||
else:
|
|
||||||
start = len(text)
|
|
||||||
widget.set("scrolling.start", start)
|
|
||||||
text = text[start:width+start]
|
|
||||||
|
|
||||||
return text
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
class Bar(object):
|
|
||||||
"""superclass"""
|
|
||||||
bars = None
|
|
||||||
|
|
||||||
def __init__(self, value):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
|
|
||||||
value (float): value between 0. and 100. meaning percents
|
|
||||||
"""
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
|
|
||||||
class HBar(Bar):
|
|
||||||
"""horizontal bar (1 char)"""
|
|
||||||
bars = HBARS
|
|
||||||
|
|
||||||
def __init__(self, value):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
|
|
||||||
value (float): value between 0. and 100. meaning percents
|
|
||||||
"""
|
|
||||||
super(HBar, self).__init__(value)
|
|
||||||
self.step = MAX_PERCENTS / CHARS
|
|
||||||
|
|
||||||
def get_char(self):
|
|
||||||
"""
|
|
||||||
Decide which char to draw
|
|
||||||
|
|
||||||
Return: str
|
|
||||||
"""
|
|
||||||
for i in range(CHARS):
|
|
||||||
left = i * self.step
|
|
||||||
right = (i + 1) * self.step
|
|
||||||
if left <= self.value < right:
|
|
||||||
return self.bars[i]
|
|
||||||
return self.bars[-1]
|
|
||||||
|
|
||||||
|
|
||||||
def hbar(value):
|
|
||||||
"""wrapper function"""
|
|
||||||
return HBar(value).get_char()
|
|
||||||
|
|
||||||
|
|
||||||
class VBar(Bar):
|
|
||||||
"""vertical bar (can be more than 1 char)"""
|
|
||||||
bars = VBARS
|
|
||||||
|
|
||||||
def __init__(self, value, width=1):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
|
|
||||||
value (float): value between 0. and 100. meaning percents
|
|
||||||
|
|
||||||
width (int): width
|
|
||||||
"""
|
|
||||||
super(VBar, self).__init__(value)
|
|
||||||
self.step = MAX_PERCENTS / (CHARS * width)
|
|
||||||
self.width = width
|
|
||||||
|
|
||||||
def get_chars(self):
|
|
||||||
"""
|
|
||||||
Decide which char to draw
|
|
||||||
|
|
||||||
Return: str
|
|
||||||
"""
|
|
||||||
if self.value == 100:
|
|
||||||
return self.bars[-1] * self.width
|
|
||||||
if self.width == 1:
|
|
||||||
for i in range(CHARS):
|
|
||||||
left = i * self.step
|
|
||||||
right = (i + 1) * self.step
|
|
||||||
if left <= self.value < right:
|
|
||||||
return self.bars[i]
|
|
||||||
else:
|
|
||||||
full_parts = int(self.value // (self.step * CHARS))
|
|
||||||
remainder = self.value - full_parts * self.step * CHARS
|
|
||||||
empty_parts = self.width - full_parts
|
|
||||||
if remainder >= 0:
|
|
||||||
empty_parts -= 1
|
|
||||||
part_vbar = VBar(remainder * self.width) # scale to width
|
|
||||||
chars = self.bars[-1] * full_parts
|
|
||||||
chars += part_vbar.get_chars()
|
|
||||||
chars += " " * empty_parts
|
|
||||||
return chars
|
|
||||||
|
|
||||||
|
|
||||||
def vbar(value, width):
|
|
||||||
"""wrapper function"""
|
|
||||||
return VBar(value, width).get_chars()
|
|
||||||
|
|
||||||
|
|
||||||
class Widget(bumblebee.store.Store):
|
|
||||||
"""Represents a single visible block in the status bar"""
|
|
||||||
def __init__(self, full_text="", name=""):
|
|
||||||
super(Widget, self).__init__()
|
|
||||||
self._full_text = full_text
|
|
||||||
self.module = None
|
|
||||||
self._module = None
|
|
||||||
self._minimized = False
|
|
||||||
self.name = name
|
|
||||||
self.id = str(uuid.uuid4())
|
|
||||||
|
|
||||||
def get_module(self):
|
|
||||||
return self._module
|
|
||||||
|
|
||||||
def toggle_minimize(self):
|
|
||||||
self._minimized = not self._minimized
|
|
||||||
|
|
||||||
def link_module(self, module):
|
|
||||||
"""Set the module that spawned this widget
|
|
||||||
|
|
||||||
This is done outside the constructor to avoid having to
|
|
||||||
pass in the module name in every concrete module implementation"""
|
|
||||||
self.module = module.name
|
|
||||||
self._module = module
|
|
||||||
|
|
||||||
def cls(self):
|
|
||||||
if not self._module:
|
|
||||||
return None
|
|
||||||
return self._module.__module__.replace("bumblebee.modules.", "")
|
|
||||||
|
|
||||||
def state(self):
|
|
||||||
"""Return the widget's state"""
|
|
||||||
if self._module and hasattr(self._module, "state"):
|
|
||||||
states = self._module.state(self)
|
|
||||||
if not isinstance(states, list):
|
|
||||||
return [states]
|
|
||||||
return states
|
|
||||||
return []
|
|
||||||
|
|
||||||
def full_text(self, value=None):
|
|
||||||
"""Set or retrieve the full text to display in the widget"""
|
|
||||||
if value:
|
|
||||||
self._full_text = value
|
|
||||||
else:
|
|
||||||
if self._minimized:
|
|
||||||
return u"\u2026"
|
|
||||||
if callable(self._full_text):
|
|
||||||
return self._full_text(self)
|
|
||||||
else:
|
|
||||||
return self._full_text
|
|
||||||
|
|
||||||
class I3BarOutput(object):
|
|
||||||
"""Manage output according to the i3bar protocol"""
|
|
||||||
def __init__(self, theme, config=None):
|
|
||||||
self._theme = theme
|
|
||||||
self._widgets = []
|
|
||||||
self._started = False
|
|
||||||
self._config = config
|
|
||||||
|
|
||||||
def started(self):
|
|
||||||
return self._started
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""Print start preamble for i3bar protocol"""
|
|
||||||
self._started = True
|
|
||||||
sys.stdout.write(json.dumps({"version": 1, "click_events": True}) + "\n[\n")
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""Finish i3bar protocol"""
|
|
||||||
sys.stdout.write("]\n")
|
|
||||||
|
|
||||||
def draw(self, widget, module=None, engine=None):
|
|
||||||
"""Draw a single widget"""
|
|
||||||
full_text = widget.full_text()
|
|
||||||
if widget.get_module() and widget.get_module().hidden():
|
|
||||||
return
|
|
||||||
if widget.get_module() and widget.get_module().name in self._config.autohide():
|
|
||||||
if not any(state in widget.state() for state in ["warning", "critical"]):
|
|
||||||
return
|
|
||||||
padding = self._theme.padding(widget)
|
|
||||||
|
|
||||||
prefix = self._theme.prefix(widget, padding)
|
|
||||||
suffix = self._theme.suffix(widget, padding)
|
|
||||||
|
|
||||||
if self._config.markup() == "pango":
|
|
||||||
# add prefix/suffix colors
|
|
||||||
fg = self._theme.prefix_fg(widget)
|
|
||||||
bg = self._theme.prefix_bg(widget)
|
|
||||||
prefix = "<span {} {}>{}</span>".format(
|
|
||||||
"foreground='{}'".format(fg) if fg else "",
|
|
||||||
"background='{}'".format(bg) if bg else "",
|
|
||||||
prefix
|
|
||||||
)
|
|
||||||
|
|
||||||
if prefix:
|
|
||||||
full_text = u"{}{}".format(prefix, full_text)
|
|
||||||
if suffix:
|
|
||||||
full_text = u"{}{}".format(full_text, suffix)
|
|
||||||
|
|
||||||
separator = self._theme.separator(widget)
|
|
||||||
if separator:
|
|
||||||
self._widgets.append({
|
|
||||||
u"full_text": separator,
|
|
||||||
"separator": False,
|
|
||||||
"color": self._theme.separator_fg(widget),
|
|
||||||
"background": self._theme.separator_bg(widget),
|
|
||||||
"separator_block_width": self._theme.separator_block_width(widget),
|
|
||||||
})
|
|
||||||
width = self._theme.minwidth(widget)
|
|
||||||
|
|
||||||
if width:
|
|
||||||
full_text = full_text.ljust(len(width) + len(prefix) + len(suffix))
|
|
||||||
|
|
||||||
markup = "none" if not self._config else self._config.markup()
|
|
||||||
|
|
||||||
if markup == "pango":
|
|
||||||
full_text = full_text.replace("&", "&")
|
|
||||||
|
|
||||||
self._widgets.append({
|
|
||||||
u"full_text": full_text,
|
|
||||||
"color": self._theme.fg(widget),
|
|
||||||
"background": self._theme.bg(widget),
|
|
||||||
"separator_block_width": self._theme.separator_block_width(widget),
|
|
||||||
"separator": True if separator is None else False,
|
|
||||||
"min_width": None,
|
|
||||||
# "min_width": width + "A"*(len(prefix) + len(suffix)) if width else None,
|
|
||||||
"align": self._theme.align(widget),
|
|
||||||
"instance": widget.id,
|
|
||||||
"name": module.id,
|
|
||||||
"markup": markup,
|
|
||||||
})
|
|
||||||
|
|
||||||
def begin(self):
|
|
||||||
"""Start one output iteration"""
|
|
||||||
self._widgets = []
|
|
||||||
self._theme.reset()
|
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
"""Flushes output"""
|
|
||||||
widgets = self._widgets
|
|
||||||
if self._config and self._config.reverse():
|
|
||||||
widgets = list(reversed(widgets))
|
|
||||||
sys.stdout.write(json.dumps(widgets))
|
|
||||||
if len(self._config.unused_keys()) > 0:
|
|
||||||
for key in self._config.unused_keys():
|
|
||||||
log.warning("unused parameter {} - please check the documentation of the affected module to ensure the parameter exists".format(key))
|
|
||||||
|
|
||||||
def end(self):
|
|
||||||
"""Finalizes output"""
|
|
||||||
sys.stdout.write(",\n")
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,80 +0,0 @@
|
||||||
"""Pop-up menus."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
try:
|
|
||||||
import Tkinter as tk
|
|
||||||
except ImportError:
|
|
||||||
# python 3
|
|
||||||
try:
|
|
||||||
import tkinter as tk
|
|
||||||
except ImportError:
|
|
||||||
logging.warning("failed to import tkinter - bumblebee popups won't work!")
|
|
||||||
|
|
||||||
class PopupMenu:
|
|
||||||
"""The popup-menu."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Initialize."""
|
|
||||||
# menu widget
|
|
||||||
self.root = tk.Tk()
|
|
||||||
self.root.withdraw()
|
|
||||||
self.menu = tk.Menu(self.root)
|
|
||||||
|
|
||||||
# internal state
|
|
||||||
self._item_count = 0
|
|
||||||
self._clicked_item = None
|
|
||||||
self._active = False
|
|
||||||
|
|
||||||
# bind event of popup getting closed by clicking outside of its area
|
|
||||||
self.menu.bind('<Unmap>',
|
|
||||||
lambda event: self.root.after_idle(
|
|
||||||
self._dismiss_callback))
|
|
||||||
|
|
||||||
def add_menuitem(self, menuitem, callback=None):
|
|
||||||
"""Add menu items."""
|
|
||||||
item_count = self._item_count
|
|
||||||
|
|
||||||
def click_callback():
|
|
||||||
# call internal callback with item index
|
|
||||||
self._item_callback(item_count)
|
|
||||||
|
|
||||||
# default to internal callback
|
|
||||||
if callback is None:
|
|
||||||
callback = click_callback
|
|
||||||
self.menu.add_command(label=menuitem,
|
|
||||||
command=callback)
|
|
||||||
|
|
||||||
# track item index
|
|
||||||
self._item_count += 1
|
|
||||||
|
|
||||||
def _item_callback(self, which_item):
|
|
||||||
"""Menu item click callback."""
|
|
||||||
logging.debug('popup: item callback: {}'.format(which_item))
|
|
||||||
self._clicked_item = which_item
|
|
||||||
self.root.destroy()
|
|
||||||
self._active = False
|
|
||||||
|
|
||||||
def _dismiss_callback(self):
|
|
||||||
"""Menu dismissed."""
|
|
||||||
logging.debug('popup: menu dismissed')
|
|
||||||
if self._active is True:
|
|
||||||
self._clicked_item = None
|
|
||||||
self.root.destroy()
|
|
||||||
|
|
||||||
def show(self, event):
|
|
||||||
"""Show popup."""
|
|
||||||
self._clicked_item = None
|
|
||||||
self.menu.tk_popup(event['x'], event['y']-50)
|
|
||||||
self._active = True
|
|
||||||
self.root.mainloop()
|
|
||||||
return self._clicked_item
|
|
||||||
|
|
||||||
|
|
||||||
def create_and_show_menu(event, *menuitems):
|
|
||||||
"""Create a menu object and show."""
|
|
||||||
menu_obj = PopupMenu()
|
|
||||||
for menuitem in menuitems:
|
|
||||||
menu_obj.add_menuitem(*menuitem)
|
|
||||||
|
|
||||||
return menu_obj.show(event)
|
|
|
@ -1,56 +0,0 @@
|
||||||
"""Pop-up menus."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
try:
|
|
||||||
import Tkinter as tk
|
|
||||||
except ImportError:
|
|
||||||
# python 3
|
|
||||||
try:
|
|
||||||
import tkinter as tk
|
|
||||||
except ImportError:
|
|
||||||
logging.warning("failed to import tkinter - bumblebee popups won't work!")
|
|
||||||
|
|
||||||
import functools
|
|
||||||
|
|
||||||
class PopupMenu(object):
|
|
||||||
def __init__(self, parent=None, leave=True):
|
|
||||||
|
|
||||||
if not parent:
|
|
||||||
self._root = tk.Tk()
|
|
||||||
self._root.withdraw()
|
|
||||||
self._menu = tk.Menu(self._root, tearoff=0)
|
|
||||||
self._menu.bind("<FocusOut>", self._on_focus_out)
|
|
||||||
else:
|
|
||||||
self._root = parent.root()
|
|
||||||
self._root.withdraw()
|
|
||||||
self._menu = tk.Menu(self._root, tearoff=0)
|
|
||||||
self._menu.bind("<FocusOut>", self._on_focus_out)
|
|
||||||
if leave:
|
|
||||||
self._menu.bind("<Leave>", self._on_focus_out)
|
|
||||||
|
|
||||||
def root(self):
|
|
||||||
return self._root
|
|
||||||
|
|
||||||
def menu(self):
|
|
||||||
return self._menu
|
|
||||||
|
|
||||||
def _on_focus_out(self, event=None):
|
|
||||||
self._root.destroy()
|
|
||||||
|
|
||||||
def _on_click(self, callback):
|
|
||||||
self._root.destroy()
|
|
||||||
callback()
|
|
||||||
|
|
||||||
def add_cascade(self, menuitem, submenu):
|
|
||||||
self._menu.add_cascade(label=menuitem, menu=submenu.menu())
|
|
||||||
|
|
||||||
def add_menuitem(self, menuitem, callback):
|
|
||||||
self._menu.add_command(label=menuitem, command=functools.partial(self._on_click, callback))
|
|
||||||
|
|
||||||
def show(self, event, offset_x=0, offset_y=0):
|
|
||||||
try:
|
|
||||||
self._menu.tk_popup(event['x'] + offset_x, event['y'] + offset_y)
|
|
||||||
finally:
|
|
||||||
self._menu.grab_release()
|
|
||||||
self._root.mainloop()
|
|
|
@ -1,27 +0,0 @@
|
||||||
"""Store interface
|
|
||||||
|
|
||||||
Allows arbitrary classes to offer a simple get/set
|
|
||||||
store interface by deriving from the Store class in
|
|
||||||
this module
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Store(object):
|
|
||||||
"""Interface for storing and retrieving simple values"""
|
|
||||||
def __init__(self):
|
|
||||||
self._data = {}
|
|
||||||
self._unused = {}
|
|
||||||
|
|
||||||
def set(self, key, value):
|
|
||||||
"""Set 'key' to 'value', overwriting 'key' if it exists already"""
|
|
||||||
self._data[key] = value
|
|
||||||
self._unused[key] = value
|
|
||||||
|
|
||||||
def unused_keys(self):
|
|
||||||
return self._unused.keys()
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
|
||||||
"""Return the current value of 'key', or 'default' if 'key' is not set"""
|
|
||||||
self._unused.pop(key, None)
|
|
||||||
return self._data.get(key, default)
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,310 +0,0 @@
|
||||||
# pylint: disable=C0103
|
|
||||||
|
|
||||||
"""Theme support"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import glob
|
|
||||||
import copy
|
|
||||||
import json
|
|
||||||
import io
|
|
||||||
import re
|
|
||||||
import logging
|
|
||||||
|
|
||||||
try:
|
|
||||||
import requests
|
|
||||||
from requests.exceptions import RequestException
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
import bumblebee.error
|
|
||||||
|
|
||||||
def theme_path():
|
|
||||||
"""Return the path of the theme directory"""
|
|
||||||
return [
|
|
||||||
os.path.dirname("{}/../themes/".format(os.path.dirname(os.path.realpath(__file__)))),
|
|
||||||
os.path.dirname(os.path.expanduser("~/.config/bumblebee-status/themes/")),
|
|
||||||
]
|
|
||||||
|
|
||||||
def themes():
|
|
||||||
themes = {}
|
|
||||||
|
|
||||||
for path in theme_path():
|
|
||||||
for filename in glob.iglob("{}/*.json".format(path)):
|
|
||||||
if "test" not in filename:
|
|
||||||
themes[os.path.basename(filename).replace(".json", "")] = 1
|
|
||||||
result = list(themes.keys())
|
|
||||||
result.sort()
|
|
||||||
return result
|
|
||||||
|
|
||||||
class Theme(object):
|
|
||||||
"""Represents a collection of icons and colors"""
|
|
||||||
def __init__(self, name, iconset="auto"):
|
|
||||||
self._widget = None
|
|
||||||
self._cycle_idx = 0
|
|
||||||
self._cycle = {}
|
|
||||||
self._prevbg = None
|
|
||||||
self._colorset = {}
|
|
||||||
self._iconset = iconset
|
|
||||||
|
|
||||||
self.load_symbols()
|
|
||||||
|
|
||||||
data = self.load(name)
|
|
||||||
if not data:
|
|
||||||
raise bumblebee.error.ThemeLoadError("no such theme")
|
|
||||||
self._init(data)
|
|
||||||
|
|
||||||
def load_symbols(self):
|
|
||||||
self._symbols = {}
|
|
||||||
path = os.path.expanduser("~/.config/bumblebee-status/")
|
|
||||||
try:
|
|
||||||
os.makedirs(path)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if os.path.exists("{}/symbols.json".format(path)):
|
|
||||||
data = json.load(io.open("{}/symbols.json".format(path)))
|
|
||||||
self._symbols = {}
|
|
||||||
for icon in data["icons"]:
|
|
||||||
code = int(icon["unicode"], 16)
|
|
||||||
try:
|
|
||||||
code = unichr(code)
|
|
||||||
except Exception:
|
|
||||||
code = chr(code)
|
|
||||||
self._symbols["${{{}}}".format(icon["id"])] = code
|
|
||||||
self._symbols["${{{}}}".format(icon["name"])] = code
|
|
||||||
except Exception as e:
|
|
||||||
logging.error("failed to load symbols: {}".format(str(e)))
|
|
||||||
|
|
||||||
def _init(self, data):
|
|
||||||
"""Initialize theme from data structure"""
|
|
||||||
self._theme = data
|
|
||||||
if self._iconset != "auto":
|
|
||||||
self._merge(data, self._load_icons(self._iconset))
|
|
||||||
else:
|
|
||||||
for iconset in data.get("icons", []):
|
|
||||||
self._merge(data, self._load_icons(iconset))
|
|
||||||
for colorset in data.get("colors", []):
|
|
||||||
self._merge(self._colorset, self._load_colors(colorset))
|
|
||||||
self._defaults = data.get("defaults", {})
|
|
||||||
self._cycles = self._theme.get("cycle", [])
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def data(self):
|
|
||||||
"""Return the raw theme data"""
|
|
||||||
return self._theme
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
"""Reset theme to initial state"""
|
|
||||||
self._cycle = self._cycles[0] if len(self._cycles) > 0 else {}
|
|
||||||
self._cycle_idx = 0
|
|
||||||
self._widget = None
|
|
||||||
self._prevbg = None
|
|
||||||
|
|
||||||
def icon(self, widget):
|
|
||||||
icon = self._get(widget, "icon", None)
|
|
||||||
if icon is None:
|
|
||||||
return self._get(widget, "prefix", None)
|
|
||||||
|
|
||||||
def get(self, widget, attribute, default_value=""):
|
|
||||||
return self._get(widget, attribute, default_value)
|
|
||||||
|
|
||||||
def padding(self, widget):
|
|
||||||
"""Return padding for widget"""
|
|
||||||
return self._get(widget, "padding", "")
|
|
||||||
|
|
||||||
def prefix(self, widget, default=None):
|
|
||||||
"""Return the theme prefix for a widget's full text"""
|
|
||||||
padding = self.padding(widget)
|
|
||||||
pre = self._get(widget, "prefix", None)
|
|
||||||
return u"{}{}{}".format(padding, pre, padding) if pre else default
|
|
||||||
|
|
||||||
def prefix_fg(self, widget):
|
|
||||||
"""Return the foreground color for the prefix"""
|
|
||||||
return self._get(widget, "prefixfg", None)
|
|
||||||
|
|
||||||
def prefix_bg(self, widget):
|
|
||||||
"""Return the background color for the prefix"""
|
|
||||||
return self._get(widget, "prefixbg", None)
|
|
||||||
|
|
||||||
def suffix_fg(self, widget):
|
|
||||||
"""Return the foreground color for the suffix"""
|
|
||||||
return self._get(widget, "suffixfg", None)
|
|
||||||
|
|
||||||
def suffix_bg(self, widget):
|
|
||||||
"""Return the background color for the suffix"""
|
|
||||||
return self._get(widget, "suffixbg", None)
|
|
||||||
|
|
||||||
def symbol(self, widget, name, default=None):
|
|
||||||
return self._get(widget, name, default)
|
|
||||||
|
|
||||||
def suffix(self, widget, default=None):
|
|
||||||
"""Return the theme suffix for a widget's full text"""
|
|
||||||
padding = self._get(widget, "padding", "")
|
|
||||||
suf = self._get(widget, "suffix", None)
|
|
||||||
return u"{}{}{}".format(padding, suf, padding) if suf else default
|
|
||||||
|
|
||||||
def fg(self, widget):
|
|
||||||
"""Return the foreground color for this widget"""
|
|
||||||
return self._get(widget, "fg", None)
|
|
||||||
|
|
||||||
def bg(self, widget):
|
|
||||||
"""Return the background color for this widget"""
|
|
||||||
return self._get(widget, "bg", None)
|
|
||||||
|
|
||||||
def align(self, widget):
|
|
||||||
"""Return the widget alignment"""
|
|
||||||
return self._get(widget, "align", None)
|
|
||||||
|
|
||||||
def minwidth(self, widget):
|
|
||||||
"""Return the minimum width string for this widget"""
|
|
||||||
return self._get(widget, "minwidth", "")
|
|
||||||
|
|
||||||
def separator(self, widget):
|
|
||||||
"""Return the separator between widgets"""
|
|
||||||
return self._get(widget, "separator", None)
|
|
||||||
|
|
||||||
def separator_fg(self, widget):
|
|
||||||
"""Return the separator's foreground/text color"""
|
|
||||||
return self.bg(widget)
|
|
||||||
|
|
||||||
def separator_bg(self, widget):
|
|
||||||
"""Return the separator's background color"""
|
|
||||||
return self._prevbg
|
|
||||||
|
|
||||||
def separator_block_width(self, widget):
|
|
||||||
"""Return the SBW"""
|
|
||||||
return self._get(widget, "separator-block-width", None)
|
|
||||||
|
|
||||||
def _load_wal_colors(self):
|
|
||||||
walfile = os.path.expanduser("~/.cache/wal/colors.json")
|
|
||||||
result = {}
|
|
||||||
with io.open(walfile) as data:
|
|
||||||
colors = json.load(data)
|
|
||||||
for field in ["special", "colors"]:
|
|
||||||
for key in colors[field]:
|
|
||||||
result[key] = colors[field][key]
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _load_colors(self, name):
|
|
||||||
"""Load colors for a theme"""
|
|
||||||
try:
|
|
||||||
if name == "wal":
|
|
||||||
return self._load_wal_colors()
|
|
||||||
except Exception as e:
|
|
||||||
logging.error("failed to load colors: {}".format(str(e)))
|
|
||||||
|
|
||||||
def _load_icons(self, name):
|
|
||||||
"""Load icons for a theme"""
|
|
||||||
result = {}
|
|
||||||
for path in theme_path():
|
|
||||||
self._merge(result, self.load(name, path="{}/icons/".format(path)))
|
|
||||||
|
|
||||||
return self._replace_symbols(result)
|
|
||||||
|
|
||||||
def _replace_symbols(self, data):
|
|
||||||
rep = json.dumps(data)
|
|
||||||
tokens = re.findall(r"\${[^}]+}", rep)
|
|
||||||
for token in tokens:
|
|
||||||
rep = rep.replace(token, self._symbols[token])
|
|
||||||
return json.loads(rep)
|
|
||||||
|
|
||||||
def load(self, name, path=theme_path()):
|
|
||||||
"""Load and parse a theme file"""
|
|
||||||
result = None
|
|
||||||
|
|
||||||
full_name = os.path.expanduser(name)
|
|
||||||
if os.path.isfile(full_name):
|
|
||||||
path = os.path.dirname(full_name)
|
|
||||||
name = os.path.basename(full_name)
|
|
||||||
name,_,_ = name.rpartition(".json")
|
|
||||||
return self.load(name, path)
|
|
||||||
|
|
||||||
if not isinstance(path, list):
|
|
||||||
path = [path]
|
|
||||||
for p in path:
|
|
||||||
themefile = "{}/{}.json".format(p, name)
|
|
||||||
|
|
||||||
if os.path.isfile(themefile):
|
|
||||||
try:
|
|
||||||
with io.open(themefile, encoding="utf-8") as data:
|
|
||||||
if result is None:
|
|
||||||
result = json.load(data)
|
|
||||||
else:
|
|
||||||
self._merge(result, json.load(data))
|
|
||||||
except ValueError as exception:
|
|
||||||
raise bumblebee.error.ThemeLoadError("JSON error: {}".format(exception))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _get(self, widget, name, default=None):
|
|
||||||
"""Return the config value 'name' for 'widget'"""
|
|
||||||
|
|
||||||
if not self._widget:
|
|
||||||
self._widget = widget
|
|
||||||
|
|
||||||
if name in bumblebee.util.aslist(self._widget.get("theme.exclude", "")):
|
|
||||||
return None
|
|
||||||
|
|
||||||
if self._widget != widget:
|
|
||||||
self._prevbg = self.bg(self._widget)
|
|
||||||
self._widget = widget
|
|
||||||
if len(self._cycles) > 0:
|
|
||||||
self._cycle_idx = (self._cycle_idx + 1) % len(self._cycles)
|
|
||||||
self._cycle = self._cycles[self._cycle_idx]
|
|
||||||
|
|
||||||
module_theme = self._theme.get(widget.module, {})
|
|
||||||
class_theme = self._theme.get(widget.cls(), {})
|
|
||||||
|
|
||||||
state_themes = []
|
|
||||||
# avoid infinite recursion
|
|
||||||
states = widget.state()
|
|
||||||
if name not in states:
|
|
||||||
for state in states:
|
|
||||||
if state:
|
|
||||||
state_themes.append(self._get(widget, state, {}))
|
|
||||||
|
|
||||||
value = self._defaults.get(name, default)
|
|
||||||
value = widget.get("theme.{}".format(name), value)
|
|
||||||
value = self._cycle.get(name, value)
|
|
||||||
value = class_theme.get(name, value)
|
|
||||||
value = module_theme.get(name, value)
|
|
||||||
|
|
||||||
for theme in state_themes:
|
|
||||||
value = theme.get(name, value)
|
|
||||||
|
|
||||||
if isinstance(value, list):
|
|
||||||
key = "{}-idx".format(name)
|
|
||||||
idx = widget.get(key, 0)
|
|
||||||
widget.set(key, (idx + 1) % len(value))
|
|
||||||
value = value[idx]
|
|
||||||
|
|
||||||
mod = widget.get_module()
|
|
||||||
if mod and not mod.parameter("is-unittest"):
|
|
||||||
value = widget.get_module().parameter("theme.{}".format(name), value)
|
|
||||||
|
|
||||||
if isinstance(value, list) or isinstance(value, dict):
|
|
||||||
return value
|
|
||||||
return self._colorset.get(value, value)
|
|
||||||
|
|
||||||
# algorithm copied from
|
|
||||||
# http://blog.impressiver.com/post/31434674390/deep-merge-multiple-python-dicts
|
|
||||||
# nicely done :)
|
|
||||||
def _merge(self, target, *args):
|
|
||||||
"""Merge two arbitrarily nested data structures"""
|
|
||||||
if len(args) > 1:
|
|
||||||
for item in args:
|
|
||||||
self._merge(item)
|
|
||||||
return target
|
|
||||||
|
|
||||||
item = args[0]
|
|
||||||
if not isinstance(item, dict):
|
|
||||||
return item
|
|
||||||
for key, value in item.items():
|
|
||||||
if key in target and isinstance(target[key], dict):
|
|
||||||
self._merge(target[key], value)
|
|
||||||
else:
|
|
||||||
if not key in target:
|
|
||||||
target[key] = copy.deepcopy(value)
|
|
||||||
return target
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
|
@ -1,101 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import shlex
|
|
||||||
import logging
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
try:
|
|
||||||
from exceptions import RuntimeError
|
|
||||||
except ImportError:
|
|
||||||
# Python3 doesn't require this anymore
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def asbool(val):
|
|
||||||
if val is None:
|
|
||||||
return False
|
|
||||||
if isinstance(val, bool):
|
|
||||||
return val
|
|
||||||
val = str(val).strip().lower()
|
|
||||||
return val in ("t", "true", "y", "yes", "on", "1")
|
|
||||||
|
|
||||||
def aslist(val):
|
|
||||||
if val is None:
|
|
||||||
return []
|
|
||||||
if isinstance(val, list):
|
|
||||||
return val
|
|
||||||
return str(val).replace(' ', '').split(',')
|
|
||||||
|
|
||||||
def execute(cmd, wait=True):
|
|
||||||
logging.info("executing command '{}'".format(cmd))
|
|
||||||
args = shlex.split(cmd)
|
|
||||||
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
||||||
rv = None
|
|
||||||
|
|
||||||
if wait:
|
|
||||||
out, _ = proc.communicate()
|
|
||||||
if proc.returncode != 0:
|
|
||||||
raise RuntimeError("{} exited with {}".format(cmd, proc.returncode))
|
|
||||||
|
|
||||||
if hasattr(out, "decode"):
|
|
||||||
rv = out.decode("utf-8", "ignore")
|
|
||||||
else:
|
|
||||||
rv = out
|
|
||||||
|
|
||||||
logging.info(u"command returned '{}'".format("" if not rv else rv))
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def bytefmt(num, fmt="{:.2f}"):
|
|
||||||
"""
|
|
||||||
format a value of bytes to a more human readable pattern
|
|
||||||
example: 15 * 1024 becomes 15KiB
|
|
||||||
|
|
||||||
Args:
|
|
||||||
|
|
||||||
num (int): bytes
|
|
||||||
|
|
||||||
fmt (string): format
|
|
||||||
|
|
||||||
Return: string
|
|
||||||
"""
|
|
||||||
for unit in ["", "Ki", "Mi", "Gi"]:
|
|
||||||
if num < 1024.0:
|
|
||||||
return "{}{}B".format(fmt, unit).format(num)
|
|
||||||
num /= 1024.0
|
|
||||||
return "{}GiB".format(fmt).format(num*1024.0)
|
|
||||||
|
|
||||||
def durationfmt(duration, shorten=False, suffix=False):
|
|
||||||
duration = int(duration)
|
|
||||||
minutes, seconds = divmod(duration, 60)
|
|
||||||
hours, minutes = divmod(minutes, 60)
|
|
||||||
suf = "m"
|
|
||||||
res = "{:02d}:{:02d}".format(minutes, seconds)
|
|
||||||
if hours > 0:
|
|
||||||
if shorten:
|
|
||||||
res = "{:02d}:{:02d}".format(hours, minutes)
|
|
||||||
else:
|
|
||||||
res = "{:02d}:{}".format(hours, res)
|
|
||||||
suf = "h"
|
|
||||||
|
|
||||||
return "{}{}".format(res, suf if suffix else "")
|
|
||||||
|
|
||||||
def which(program):
|
|
||||||
import os
|
|
||||||
def is_exe(fpath):
|
|
||||||
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
|
||||||
|
|
||||||
fpath, fname = os.path.split(program)
|
|
||||||
if fpath:
|
|
||||||
if is_exe(program):
|
|
||||||
return program
|
|
||||||
else:
|
|
||||||
localPATH = os.environ["PATH"].split(os.pathsep)
|
|
||||||
localPATH += ["/sbin", "/usr/sbin/", "/usr/local/sbin"]
|
|
||||||
for path in localPATH:
|
|
||||||
exe_file = os.path.join(path, program)
|
|
||||||
if is_exe(exe_file):
|
|
||||||
return exe_file
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
16
core/output.py
Normal file
16
core/output.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
class i3(object):
|
||||||
|
def start(self):
|
||||||
|
return '{}\n'.format(json.dumps({ 'version': 1, 'click_events': True }))
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
return ']\n'
|
||||||
|
|
||||||
|
def begin_status_line(self):
|
||||||
|
return '['
|
||||||
|
|
||||||
|
def end_status_line(self):
|
||||||
|
return '],\n'
|
||||||
|
|
||||||
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
find . -name "*.py"|xargs pylint --disable=R0903,R0201,C0330
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue