diff --git a/README.md b/README.md index 1997f35..d039179 100644 --- a/README.md +++ b/README.md @@ -63,20 +63,20 @@ Here are some screenshots for all themes that currently exist: Gruvbox Powerline (`-t gruvbox-powerline`) (contributed by [@paxy97](https://github.com/paxy97)): -![Gruvbox Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/powerline-gruvbox.png) +![Gruvbox Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-gruvbox.png) Solarized Powerline (`-t solarized-powerline`): -![Solarized Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/powerline-solarized.png) +![Solarized Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-solarized.png) Solarized (`-t solarized`): -![Solarized](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/solarized.png) +![Solarized](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/solarized.png) Powerline (`-t powerline`): -![Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/powerline.png) +![Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline.png) Default (nothing or `-t default`): -![Default](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/default.png) +![Default](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/default.png) diff --git a/bin/load-i3-bars.sh b/bin/load-i3-bars.sh new file mode 100755 index 0000000..cbe0e95 --- /dev/null +++ b/bin/load-i3-bars.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +if [ ! -f ~/.i3/config.template ]; then + cp ~/.i3/config ~/.i3/config.template +else + cp ~/.i3/config.template ~/.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 ~/.i3/config.$screen-$others ]; then + cat ~/.i3/config.$screen-$others >> ~/.i3/config + else + if [ -f ~/.i3/config.$screen ]; then + cat ~/.i3/config.$screen >> ~/.i3/config + fi + fi +done <<< "$screens" + +i3-msg restart diff --git a/bin/toggle-display.sh b/bin/toggle-display.sh new file mode 100755 index 0000000..bd13a29 --- /dev/null +++ b/bin/toggle-display.sh @@ -0,0 +1,12 @@ +#!/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 + $i3bar_update +fi diff --git a/bumblebee/modules/disk.py b/bumblebee/modules/disk.py index 3cc3b6c..679e271 100644 --- a/bumblebee/modules/disk.py +++ b/bumblebee/modules/disk.py @@ -16,7 +16,7 @@ class Module(bumblebee.module.Module): super(Module, self).__init__(output, config, alias) self._path = self._config.parameter("path", "/") - output.add_callback(module=self.instance(), button=1, cmd="nautilus {instance}") + output.add_callback(module=self.instance(), button=1, cmd="nautilus {}".format(self._path)) def widgets(self): st = os.statvfs(self._path) diff --git a/bumblebee/modules/layout.py b/bumblebee/modules/layout.py new file mode 100644 index 0000000..084d27d --- /dev/null +++ b/bumblebee/modules/layout.py @@ -0,0 +1,67 @@ +import subprocess +import shlex +import bumblebee.module +import bumblebee.util + +def description(): + return "Showws current keyboard layout and change it on click." + +def parameters(): + return [ + "layout.lang: pipe-separated list of languages to cycle through (e.g. us|rs|de). Default: en" + ] + + +class Module(bumblebee.module.Module): + def __init__(self, output, config, alias): + super(Module, self).__init__(output, config, alias) + + self._languages = self._config.parameter("lang", "en").split("|") + self._idx = 0 + + output.add_callback(module=self.instance(), button=1, cmd=self.next_keymap) + output.add_callback(module=self.instance(), button=3, cmd=self.prev_keymap) + + def next_keymap(self, event, widget): + self._idx = self._idx + 1 if self._idx < len(self._languages) - 1 else 0 + self.set_keymap() + + def prev_keymap(self, event, widget): + self._idx = self._idx - 1 if self._idx > 0 else len(self._languages) - 1 + self.set_keymap() + + def set_keymap(self): + tmp = self._languages[self._idx].split(":") + layout = tmp[0] + variant = "" + if len(tmp) > 1: + variant = "-variant {}".format(tmp[1]) + bumblebee.util.execute("setxkbmap -layout {} {}".format(layout, variant)) + + def widgets(self): + res = bumblebee.util.execute("setxkbmap -query") + layout = None + variant = None + for line in res.split("\n"): + if not line: + continue + if "layout" in line: + layout = line.split(":")[1].strip() + if "variant" in line: + variant = line.split(":")[1].strip() + if variant: + layout += ":" + variant + + lang = self._languages[self._idx] + + if lang != layout: + if layout in self._languages: + self._idx = self._languages.index(layout) + else: + self._languages.append(layout) + self._idx = len(self._languages) - 1 + lang = layout + + return bumblebee.output.Widget(self, lang) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/load.py b/bumblebee/modules/load.py new file mode 100644 index 0000000..fba5dbd --- /dev/null +++ b/bumblebee/modules/load.py @@ -0,0 +1,37 @@ +import bumblebee.module +import multiprocessing +import os + +def description(): + return "Displays system load." + +def parameters(): + return [ + "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 80% of the number of CPUs)" + ] + +class Module(bumblebee.module.Module): + def __init__(self, output, config, alias): + super(Module, self).__init__(output, config, alias) + self._cpus = 1 + try: + self._cpus = multiprocessing.cpu_count() + except multiprocessing.NotImplementedError as e: + pass + + output.add_callback(module=self.instance(), button=1, cmd="gnome-system-monitor") + + def widgets(self): + self._load = os.getloadavg() + + return bumblebee.output.Widget(self, "{:.02f}/{:.02f}/{:.02f}".format( + self._load[0], self._load[1], self._load[2])) + + def warning(self, widget): + return self._load[0] > self._config.parameter("warning", self._cpus*0.7) + + def critical(self, widget): + return self._load[0] > self._config.parameter("critical", self._cpus*0.8) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/nic.py b/bumblebee/modules/nic.py index af82b08..94f8bc4 100644 --- a/bumblebee/modules/nic.py +++ b/bumblebee/modules/nic.py @@ -5,12 +5,14 @@ def description(): return "Displays the names, IP addresses and status of each available interface." def parameters(): - return [ "none" ] + return [ + "nic.exclude: Comma-separated list of interface prefixes to exlude (defaults to: \"lo,virbr,docker,vboxnet,veth\")" + ] class Module(bumblebee.module.Module): def __init__(self, output, config, alias): super(Module, self).__init__(output, config, alias) - self._exclude = ( "lo", "virbr", "docker", "vboxnet" ) + self._exclude = tuple(filter(len, self._config.parameter("exclude", "lo,virbr,docker,vboxnet,veth").split(","))) self._state = "down" self._typecache = {} diff --git a/bumblebee/modules/xrandr.py b/bumblebee/modules/xrandr.py new file mode 100644 index 0000000..51998a9 --- /dev/null +++ b/bumblebee/modules/xrandr.py @@ -0,0 +1,89 @@ +import bumblebee.module +import bumblebee.util +import re +import os +import sys +import subprocess + +def description(): + return "Shows all connected screens" + +def parameters(): + return [ + ] + +class Module(bumblebee.module.Module): + def __init__(self, output, config, alias): + super(Module, self).__init__(output, config, alias) + + self._widgets = [] + + def toggle(self, event, widget): + path = os.path.dirname(os.path.abspath(__file__)) + toggle_cmd = "{}/../../bin/toggle-display.sh".format(path) + + if widget.get("state") == "on": + bumblebee.util.execute("{} --output {} --off".format(toggle_cmd, widget.get("display"))) + else: + neighbor = None + for w in self._widgets: + if w.get("state") == "on": + neighbor = w + if event.get("button") == 1: + break + + if neighbor == None: + bumblebee.util.execute("{} --output {} --auto".format(toggle_cmd, + widget.get("display"))) + else: + bumblebee.util.execute("{} --output {} --auto --{}-of {}".format(toggle_cmd, + widget.get("display"), "left" if event.get("button") == 1 else "right", + neighbor.get("display"))) + + def widgets(self): + process = subprocess.Popen([ "xrandr", "-q" ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output, error = process.communicate() + + widgets = [] + + for line in output.split("\n"): + if not " connected" in line: + continue + display = line.split(" ", 2)[0] + m = re.search(r'\d+x\d+\+(\d+)\+\d+', line) + + widget = bumblebee.output.Widget(self, display, instance=display) + widget.set("display", display) + + # not optimal (add callback once per interval), but since + # add_callback() just returns if the callback has already + # been registered, it should be "ok" + self._output.add_callback(module=display, button=1, + cmd=self.toggle) + self._output.add_callback(module=display, button=3, + cmd=self.toggle) + if m: + widget.set("state", "on") + widget.set("pos", int(m.group(1))) + else: + widget.set("state", "off") + widget.set("pos", sys.maxint) + + widgets.append(widget) + + widgets.sort(key=lambda widget : widget.get("pos")) + + self._widgets = widgets + + return widgets + + def state(self, widget): + return widget.get("state", "off") + + def warning(self, widget): + return False + + def critical(self, widget): + return False + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/output.py b/bumblebee/output.py index 952a284..1f494ad 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -15,6 +15,8 @@ class Widget(object): self._store = {} self._instance = instance + obj._output.register_widget(self.instance(), self) + def set(self, key, value): self._store[key] = value @@ -40,17 +42,23 @@ class Widget(object): return self._text class Command(object): - def __init__(self, command): + def __init__(self, command, event, widget): self._command = command + self._event = event + self._widget = widget def __call__(self, *args, **kwargs): if not isinstance(self._command, list): self._command = [ self._command ] for cmd in self._command: - c = cmd.format(*args, **kwargs) - DEVNULL = open(os.devnull, 'wb') - subprocess.Popen(shlex.split(c), stdout=DEVNULL, stderr=DEVNULL).communicate() + if not cmd: continue + if inspect.ismethod(cmd): + cmd(self._event, self._widget) + else: + c = cmd.format(*args, **kwargs) + DEVNULL = open(os.devnull, 'wb') + subprocess.Popen(shlex.split(c), stdout=DEVNULL, stderr=DEVNULL) class Output(object): def __init__(self, config): @@ -58,6 +66,10 @@ class Output(object): self._callbacks = {} self._wait = threading.Condition() self._wait.acquire() + self._widgets = {} + + def register_widget(self, identity, widget): + self._widgets[identity] = widget def redraw(self): self._wait.acquire() @@ -82,11 +94,11 @@ class Output(object): ), None) cb = self._callbacks.get(( event.get("button", -1), - event.get("instance", event.get("name", None)), + event.get("instance", event.get("module", None)), ), cb) - if inspect.isfunction(cb) or cb is None: return cb - return Command(cb) + identity = event.get("instance", event.get("module", None)) + return Command(cb, event, self._widgets.get(identity, None)) def wait(self): self._wait.wait(self._config.parameter("interval", 1)) diff --git a/bumblebee/outputs/i3.py b/bumblebee/outputs/i3.py index e6e42b0..c0800dd 100644 --- a/bumblebee/outputs/i3.py +++ b/bumblebee/outputs/i3.py @@ -51,8 +51,12 @@ class Output(bumblebee.output.Output): "separator_block_width": 0, }) + sep = theme.default_separators(widget) + sep = sep if sep else False + width = theme.separator_block_width(widget) + width = width if width else 0 self._data.append({ - u"full_text": " {} {} {} ".format( + u"full_text": " {} {} {}".format( theme.prefix(widget), widget.text(), theme.suffix(widget) @@ -61,8 +65,8 @@ class Output(bumblebee.output.Output): "background": theme.background(widget), "name": widget.module(), "instance": widget.instance(), - "separator": theme.default_separators(widget), - "separator_block_width": theme.separator_block_width(widget), + "separator": sep, + "separator_block_width": width, }) theme.next_widget() diff --git a/bumblebee/theme.py b/bumblebee/theme.py index 047a9ff..cd8d860 100644 --- a/bumblebee/theme.py +++ b/bumblebee/theme.py @@ -46,7 +46,7 @@ class Theme: item = args[0] if not isinstance(item, dict): return item - for key, value in item.iteritems(): + for key, value in item.items(): if key in target and isinstance(target[key], dict): self.merge(target[key], value) else: @@ -64,10 +64,10 @@ class Theme: self._cycle = self._cycles[idx] if len(self._cycles) > idx else {} def prefix(self, widget): - return self._get(widget, "prefix") + return self._get(widget, "prefix", "") def suffix(self, widget): - return self._get(widget, "suffix") + return self._get(widget, "suffix", "") def color(self, widget): result = self._get(widget, "fg") @@ -101,7 +101,7 @@ class Theme: def separator_block_width(self, widget): return self._get(widget, "separator-block-width") - def _get(self, widget, name): + def _get(self, widget, name, default = None): module = widget.module() state = widget.state() inst = widget.instance() @@ -125,6 +125,6 @@ class Theme: self._config.increase(key, len(value), 0) value = value[idx] - return value + return value if value else default # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/util.py b/bumblebee/util.py index ef74266..c38bb79 100644 --- a/bumblebee/util.py +++ b/bumblebee/util.py @@ -1,3 +1,10 @@ +import shlex +import subprocess +try: + from exceptions import RuntimeError +except ImportError: + # Python3 doesn't require this anymore + pass def bytefmt(num): for unit in [ "", "Ki", "Mi", "Gi" ]: @@ -13,3 +20,13 @@ def durationfmt(duration): if hours > 0: res = "{:02d}:{}".format(hours, res) return res + +def execute(cmd): + args = shlex.split(cmd) + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out, err = p.communicate() + + if p.returncode != 0: + raise RuntimeError("{} exited with {}".format(cmd, p.returncode)) + + return out.decode("utf-8") diff --git a/screenshots/battery.png b/screenshots/battery.png index cead423..bdbafee 100644 Binary files a/screenshots/battery.png and b/screenshots/battery.png differ diff --git a/screenshots/brightness.png b/screenshots/brightness.png index 15edc2d..298abf3 100644 Binary files a/screenshots/brightness.png and b/screenshots/brightness.png differ diff --git a/screenshots/caffeine.png b/screenshots/caffeine.png index d2e4111..5444777 100644 Binary files a/screenshots/caffeine.png and b/screenshots/caffeine.png differ diff --git a/screenshots/cmus.png b/screenshots/cmus.png index 1ff4b5c..52c0b51 100644 Binary files a/screenshots/cmus.png and b/screenshots/cmus.png differ diff --git a/screenshots/cpu.png b/screenshots/cpu.png index 9173845..f160ba2 100644 Binary files a/screenshots/cpu.png and b/screenshots/cpu.png differ diff --git a/screenshots/date.png b/screenshots/date.png new file mode 100644 index 0000000..36621e7 Binary files /dev/null and b/screenshots/date.png differ diff --git a/screenshots/datetime.png b/screenshots/datetime.png index df33bd8..0c15843 100644 Binary files a/screenshots/datetime.png and b/screenshots/datetime.png differ diff --git a/screenshots/default.png b/screenshots/default.png deleted file mode 100644 index 5531717..0000000 Binary files a/screenshots/default.png and /dev/null differ diff --git a/screenshots/disk.png b/screenshots/disk.png index 08841a0..7a659b8 100644 Binary files a/screenshots/disk.png and b/screenshots/disk.png differ diff --git a/screenshots/dnf.png b/screenshots/dnf.png index b55d061..4992416 100644 Binary files a/screenshots/dnf.png and b/screenshots/dnf.png differ diff --git a/screenshots/load.png b/screenshots/load.png new file mode 100644 index 0000000..e136e7e Binary files /dev/null and b/screenshots/load.png differ diff --git a/screenshots/memory.png b/screenshots/memory.png index 5498871..db0c59b 100644 Binary files a/screenshots/memory.png and b/screenshots/memory.png differ diff --git a/screenshots/nic.png b/screenshots/nic.png index 4e09c60..d1512c0 100644 Binary files a/screenshots/nic.png and b/screenshots/nic.png differ diff --git a/screenshots/pacman.png b/screenshots/pacman.png new file mode 100644 index 0000000..8f4169e Binary files /dev/null and b/screenshots/pacman.png differ diff --git a/screenshots/pasink.png b/screenshots/pasink.png new file mode 100644 index 0000000..2fd6359 Binary files /dev/null and b/screenshots/pasink.png differ diff --git a/screenshots/pasource.png b/screenshots/pasource.png new file mode 100644 index 0000000..ae2ee77 Binary files /dev/null and b/screenshots/pasource.png differ diff --git a/screenshots/ping.png b/screenshots/ping.png index da897c3..1c8bf2b 100644 Binary files a/screenshots/ping.png and b/screenshots/ping.png differ diff --git a/screenshots/powerline-gruvbox.png b/screenshots/powerline-gruvbox.png deleted file mode 100644 index 77df3fe..0000000 Binary files a/screenshots/powerline-gruvbox.png and /dev/null differ diff --git a/screenshots/powerline-solarized.png b/screenshots/powerline-solarized.png deleted file mode 100644 index abf8f75..0000000 Binary files a/screenshots/powerline-solarized.png and /dev/null differ diff --git a/screenshots/powerline.png b/screenshots/powerline.png deleted file mode 100644 index df21bbd..0000000 Binary files a/screenshots/powerline.png and /dev/null differ diff --git a/screenshots/pulseaudio.png b/screenshots/pulseaudio.png index 2197d62..b38a4aa 100644 Binary files a/screenshots/pulseaudio.png and b/screenshots/pulseaudio.png differ diff --git a/screenshots/solarized.png b/screenshots/solarized.png deleted file mode 100644 index afbbbe1..0000000 Binary files a/screenshots/solarized.png and /dev/null differ diff --git a/screenshots/spacer.png b/screenshots/spacer.png index ed8d2c6..5200d0f 100644 Binary files a/screenshots/spacer.png and b/screenshots/spacer.png differ diff --git a/screenshots/themes/default.png b/screenshots/themes/default.png new file mode 100644 index 0000000..c2680bd Binary files /dev/null and b/screenshots/themes/default.png differ diff --git a/screenshots/themes/powerline-gruvbox.png b/screenshots/themes/powerline-gruvbox.png new file mode 100644 index 0000000..5b80ff3 Binary files /dev/null and b/screenshots/themes/powerline-gruvbox.png differ diff --git a/screenshots/themes/powerline-solarized.png b/screenshots/themes/powerline-solarized.png new file mode 100644 index 0000000..5be0b16 Binary files /dev/null and b/screenshots/themes/powerline-solarized.png differ diff --git a/screenshots/themes/powerline.png b/screenshots/themes/powerline.png new file mode 100644 index 0000000..1bd551a Binary files /dev/null and b/screenshots/themes/powerline.png differ diff --git a/screenshots/themes/solarized.png b/screenshots/themes/solarized.png new file mode 100644 index 0000000..a15d08c Binary files /dev/null and b/screenshots/themes/solarized.png differ diff --git a/screenshots/time.png b/screenshots/time.png new file mode 100644 index 0000000..39d5210 Binary files /dev/null and b/screenshots/time.png differ diff --git a/screenshots/xrandr.png b/screenshots/xrandr.png new file mode 100644 index 0000000..ec1c6de Binary files /dev/null and b/screenshots/xrandr.png differ diff --git a/themes/icons/ascii.json b/themes/icons/ascii.json index aa94461..bd051ce 100644 --- a/themes/icons/ascii.json +++ b/themes/icons/ascii.json @@ -112,5 +112,8 @@ }, "caffeine": { "states": { "activated": {"prefix": "caf-on" }, "deactivated": { "prefix": "caf-off " } } + }, + "xrandr": { + "states": { "on": { "prefix": " off "}, "off": { "prefix": " on "} } } } diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index 9ba437f..62d9294 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -127,5 +127,8 @@ }, "caffeine": { "states": { "activated": {"prefix": " " }, "deactivated": { "prefix": " " } } + }, + "xrandr": { + "states": { "on": { "prefix": " "}, "off": { "prefix": " "} } } } diff --git a/themes/solarized.json b/themes/solarized.json index 6da0c7a..5cc8f86 100644 --- a/themes/solarized.json +++ b/themes/solarized.json @@ -17,7 +17,7 @@ "bg-warning": "#b58900", "default_separators": false, - "separator": "|" + "separator": "" }, "dnf": { "states": {