Merge branch 'master' into icon-themes
* separate color themes from icons * each theme can now use one or more icon themes * icons are chosen in a "first match" fashion fixes #17
10
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)
|
||||
|
|
26
bin/load-i3-bars.sh
Executable file
|
@ -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
|
12
bin/toggle-display.sh
Executable file
|
@ -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
|
|
@ -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)
|
||||
|
|
67
bumblebee/modules/layout.py
Normal file
|
@ -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
|
37
bumblebee/modules/load.py
Normal file
|
@ -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
|
|
@ -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 = {}
|
||||
|
||||
|
|
89
bumblebee/modules/xrandr.py
Normal file
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 992 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
screenshots/date.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
screenshots/load.png
Normal file
After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
screenshots/pacman.png
Normal file
After Width: | Height: | Size: 860 B |
BIN
screenshots/pasink.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
screenshots/pasource.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 592 B After Width: | Height: | Size: 521 B |
BIN
screenshots/themes/default.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
screenshots/themes/powerline-gruvbox.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
screenshots/themes/powerline-solarized.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
screenshots/themes/powerline.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
screenshots/themes/solarized.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
screenshots/time.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
screenshots/xrandr.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
|
@ -112,5 +112,8 @@
|
|||
},
|
||||
"caffeine": {
|
||||
"states": { "activated": {"prefix": "caf-on" }, "deactivated": { "prefix": "caf-off " } }
|
||||
},
|
||||
"xrandr": {
|
||||
"states": { "on": { "prefix": " off "}, "off": { "prefix": " on "} }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,5 +127,8 @@
|
|||
},
|
||||
"caffeine": {
|
||||
"states": { "activated": {"prefix": " " }, "deactivated": { "prefix": " " } }
|
||||
},
|
||||
"xrandr": {
|
||||
"states": { "on": { "prefix": " "}, "off": { "prefix": " "} }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"bg-warning": "#b58900",
|
||||
|
||||
"default_separators": false,
|
||||
"separator": "|"
|
||||
"separator": ""
|
||||
},
|
||||
"dnf": {
|
||||
"states": {
|
||||
|
|