Using remote

This commit is contained in:
batman-nair 2017-09-21 08:53:57 +05:30
commit 0b86b3df8c
16 changed files with 257 additions and 144 deletions

View file

@ -6,6 +6,7 @@ python:
- "3.5" - "3.5"
- "3.6" - "3.6"
install: install:
- pip install i3ipc
- pip install psutil - pip install psutil
- pip install netifaces - pip install netifaces
- pip install -U coverage==4.3 - pip install -U coverage==4.3

37
PKGBUILD Normal file
View file

@ -0,0 +1,37 @@
# Maintainer: Tobias Witek <tobi@tobi-wan-kenobi.at>
# Contributor: Daniel M. Capella <polycitizen@gmail.com>
pkgname=bumblebee-status
pkgver=1.4.2
pkgrel=1
pkgdesc='Modular, theme-able status line generator for the i3 window manager'
arch=('any')
url=https://github.com/tobi-wan-kenobi/bumblebee-status
license=('MIT')
depends=('python' 'python-netifaces' 'python-psutil' 'python-requests')
optdepends=('xorg-xbacklight: to display a displays brightness'
'xorg-xset: enable/disable automatic screen locking'
'libnotify: enable/disable automatic screen locking'
'dnf: display DNF package update information'
'xorg-setxkbmap: display/change the current keyboard layout'
'redshift: display the redshifts current color'
'pulseaudio: control pulseaudio sink/sources'
'xorg-xrandr: enable/disable screen outputs'
'pacman: display current status of pacman'
'iputils: display a ping'
'i3ipc-python: display titlebar'
'fakeroot: dependency of the pacman module')
source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
sha512sums=('3a66fc469dd3b081337c9e213a1b2262f25f30977ee6ef65b9fa5a8b6aa341637832d1a5dbb74e30d68e2824e0d19d7a911eb3390dc6062707a552f429b483e8')
package() {
install -d "$pkgdir"/usr/bin \
"$pkgdir"/usr/share/$pkgname/{bumblebee/modules,themes/icons}
ln -s /usr/share/$pkgname/$pkgname "$pkgdir"/usr/bin/$pkgname
cd $pkgname-$pkgver
cp -a --parents $pkgname bumblebee/{,modules/}*.py themes/{,icons/}*.json \
"$pkgdir"/usr/share/$pkgname
install -Dm644 LICENSE "$pkgdir"/usr/share/licenses/$pkgname/LICENSE
}

View file

@ -5,7 +5,7 @@
[![Test Coverage](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/coverage.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage) [![Test Coverage](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/coverage.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage)
[![Issue Count](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/issue_count.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status) [![Issue Count](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/issue_count.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status)
**Many, many thanks to all contributors! As of now, 21 of the modules are from various contributors (!), and only 16 from myself.** **Many, many thanks to all contributors! As of now, 22 of the modules are from various contributors (!), and only 16 from myself.**
bumblebee-status is a modular, theme-able status line generator for the [i3 window manager](https://i3wm.org/). bumblebee-status is a modular, theme-able status line generator for the [i3 window manager](https://i3wm.org/).
@ -20,6 +20,8 @@ I hope you like it and appreciate any kind of feedback: Bug reports, Feature req
Thanks a lot! Thanks a lot!
Required i3wm version: 4.12+ (in earlier versions, blocks won't have background colors)
Supported Python versions: 2.7, 3.3, 3.4, 3.5, 3.6 Supported Python versions: 2.7, 3.3, 3.4, 3.5, 3.6
Explicitly unsupported Python versions: 3.2 (missing unicode literals) Explicitly unsupported Python versions: 3.2 (missing unicode literals)
@ -108,6 +110,7 @@ Modules and commandline utilities are only required for modules, the core itself
* requests (for the modules 'weather', 'github', 'getcrypto', 'stock') * requests (for the modules 'weather', 'github', 'getcrypto', 'stock')
* power (for the module 'battery') * power (for the module 'battery')
* dbus (for the module 'spotify') * dbus (for the module 'spotify')
* i3rpc (for the module 'title')
# Required commandline utilities # Required commandline utilities

View file

@ -10,6 +10,13 @@ import bumblebee.output
import bumblebee.input import bumblebee.input
import bumblebee.modules.error import bumblebee.modules.error
try:
reload(sys)
sys.setdefaultencoding('UTF8')
except Exception:
pass
def main(): def main():
config = bumblebee.config.Config(sys.argv[1:]) config = bumblebee.config.Config(sys.argv[1:])

View file

@ -1,5 +1,4 @@
# pylint: disable=C0111,R0903 # pylint: disable=C0111,R0903
# -*- coding: utf-8 -*-
"""Displays information about the current song in cmus. """Displays information about the current song in cmus.
@ -69,10 +68,6 @@ class Module(bumblebee.engine.Module):
return returns.get(widget.name, self._status) return returns.get(widget.name, self._status)
def _eval_line(self, line): def _eval_line(self, line):
# not a typo, use decode detection to see whether we are
# dealing with Python2 or Python3
if hasattr(line, "decode"):
line = line.encode("utf-8", "replace")
name, key, value = (line.split(" ", 2) + [None, None])[:3] name, key, value = (line.split(" ", 2) + [None, None])[:3]
if name == "status": if name == "status":

View file

@ -8,6 +8,13 @@ Requires the following python packages:
Parameters: Parameters:
* currency.interval: Interval in minutes between updates, default is 1. * currency.interval: Interval in minutes between updates, default is 1.
* currency.source: Source currency (defaults to "GBP")
* 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 http://fixer.io
""" """
import bumblebee.input import bumblebee.input
@ -21,40 +28,43 @@ try:
except ImportError: except ImportError:
pass pass
SYMBOL = {
"GBP": u"£", "EUR": u"", "USD": u"$"
}
class Module(bumblebee.engine.Module): class Module(bumblebee.engine.Module):
def __init__(self, engine, config): def __init__(self, engine, config):
super(Module, self).__init__(engine, config, super(Module, self).__init__(engine, config,
bumblebee.output.Widget(full_text=self.price) bumblebee.output.Widget(full_text=self.price)
) )
self._price = "-" self._data = {}
self._interval = int(self.parameter("interval", "1")) self._interval = int(self.parameter("interval", 1))
self._base = self.parameter("source", "GBP")
self._symbols = self.parameter("destination", "USD,EUR")
self._nextcheck = 0 self._nextcheck = 0
self._valid = False
def price(self, widget): def price(self, widget):
if not self._valid: if self._data == {}:
return u"?" return "?"
return self._price
rates = []
for sym in self._data["rates"]:
rates.append(u"{}{}".format(self._data["rates"][sym], SYMBOL[sym] if sym in SYMBOL else sym))
basefmt = u"{}".format(self.parameter("sourceformat", "{}: {}"))
ratefmt = u"{}".format(self.parameter("destinationdelimiter", "|"))
return basefmt.format(SYMBOL[self._base] if self._base in SYMBOL else self._base, ratefmt.join(rates))
def update(self, widgets): def update(self, widgets):
timestamp = int(time.time()) timestamp = int(time.time())
if self._nextcheck < int(time.time()): if self._nextcheck < int(time.time()):
try: self._data = {}
self._nextcheck = int(time.time()) + self._interval*60 self._nextcheck = int(time.time()) + self._interval*60
price_url = "http://api.fixer.io/latest?symbols=USD,EUR&base=GBP" url = "http://api.fixer.io/latest?symbols={}&base={}".format(self._symbols, self._base)
try: try:
price_json = json.loads( requests.get(price_url).text ) self._data = json.loads(requests.get(url).text)
gbpeur = str(price_json['rates']['EUR']) except Exception:
gbpusd = str(price_json['rates']['USD']) pass
except ValueError:
gbpeur = "-"
gbpusd = "-"
self._price = u"£/€ " + gbpeur + u" | £/$ " + gbpusd
self._valid = True
except RequestException:
self._price = u"£/€ - | £/$ -"
self._valid = True
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -49,7 +49,7 @@ class Module(bumblebee.engine.Module):
url = "https://api.github.com/notifications" url = "https://api.github.com/notifications"
while True: while True:
notifications = self._requests.get(url) notifications = self._requests.get(url)
self._count += len(filter(lambda notification: notification.get("unread", False), notifications.json())) self._count += len(list(filter(lambda notification: notification['unread'], notifications.json())))
next_link = notifications.links.get('next') next_link = notifications.links.get('next')
if next_link is not None: if next_link is not None:
url = next_link.get('url') url = next_link.get('url')

View file

@ -4,9 +4,6 @@
Requires the following executable: Requires the following executable:
* setxkbmap * setxkbmap
Parameters:
* layout.lang: pipe-separated list of languages to cycle through (e.g. us|rs|de). Default: en
""" """
import bumblebee.util import bumblebee.util
@ -17,58 +14,59 @@ import bumblebee.engine
class Module(bumblebee.engine.Module): class Module(bumblebee.engine.Module):
def __init__(self, engine, config): def __init__(self, engine, config):
super(Module, self).__init__(engine, config, super(Module, self).__init__(engine, config,
bumblebee.output.Widget(full_text=self.layout) bumblebee.output.Widget(full_text=self.current_layout)
) )
self._languages = self.parameter("lang", "us").split("|")
self._idx = 0
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
cmd=self._next_keymap) cmd=self._next_keymap)
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE,
cmd=self._prev_keymap) cmd=self._prev_keymap)
def _next_keymap(self, event): def _next_keymap(self, event):
self._idx = (self._idx + 1) % len(self._languages) self._set_keymap(1)
self._set_keymap()
def _prev_keymap(self, event): def _prev_keymap(self, event):
self._idx = self._idx - 1 if self._idx > 0 else len(self._languages) - 1 self._set_keymap(-1)
self._set_keymap()
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 "")
def _set_keymap(self):
tmp = self._languages[self._idx].split(":")
layout = tmp[0]
variant = ""
if len(tmp) > 1:
variant = "-variant {}".format(tmp[1])
try: try:
bumblebee.util.execute("setxkbmap -layout {} {}".format(layout, variant)) bumblebee.util.execute("setxkbmap -layout {} -variant {}".format(",".join(layout_list), ",".join(variant_list)))
except RuntimeError: except RuntimeError:
pass pass
def layout(self, widget): def get_layouts(self):
try: try:
res = bumblebee.util.execute("setxkbmap -query") res = bumblebee.util.execute("setxkbmap -query")
except RuntimeError: except RuntimeError:
return "n/a" return ["n/a"]
layout = "" layouts = []
variant = None variants = []
for line in res.split("\n"): for line in res.split("\n"):
if not line: if not line: continue
continue
if "layout" in line: if "layout" in line:
layout = line.split(":")[1].strip() layouts = line.split(":")[1].strip().split(",")
if "variant" in line: if "variant" in line:
variant = line.split(":")[1].strip() variants = line.split(":")[1].strip().split(",")
if variant:
layout += ":" + variant
if layout in self._languages: result = []
self._idx = self._languages.index(layout) for idx, layout in enumerate(layouts):
else: if len(variants) > idx and variants[idx]:
self._languages.append(layout) layout = "{}:{}".format(layout, variants[idx])
self._idx = len(self._languages) - 1 result.append(layout)
return result if len(result) > 0 else ["n/a"]
return layout def current_layout(self, widget):
layouts = self.get_layouts()
return layouts[0]
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -9,22 +9,24 @@ Parameters:
* memory.usedonly: Only show the amount of RAM in use (defaults to False). Same as memory.format="{used}" * memory.usedonly: Only show the amount of RAM in use (defaults to False). Same as memory.format="{used}"
""" """
try: import re
import psutil
except ImportError:
pass
import bumblebee.util import bumblebee.util
import bumblebee.input import bumblebee.input
import bumblebee.output import bumblebee.output
import bumblebee.engine import bumblebee.engine
class Container(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
class Module(bumblebee.engine.Module): class Module(bumblebee.engine.Module):
def __init__(self, engine, config): def __init__(self, engine, config):
super(Module, self).__init__(engine, config, super(Module, self).__init__(engine, config,
bumblebee.output.Widget(full_text=self.memory_usage) bumblebee.output.Widget(full_text=self.memory_usage)
) )
self._mem = psutil.virtual_memory() self.update(None)
engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE,
cmd="gnome-system-monitor") cmd="gnome-system-monitor")
@ -36,18 +38,30 @@ class Module(bumblebee.engine.Module):
return self.parameter("format", "{used}/{total} ({percent:05.02f}%)") return self.parameter("format", "{used}/{total} ({percent:05.02f}%)")
def memory_usage(self, widget): def memory_usage(self, widget):
used = bumblebee.util.bytefmt(self._mem.total - self._mem.available) return self._format.format(**self._mem)
total = bumblebee.util.bytefmt(self._mem.total)
return self._format.format(used=used, total=total, percent=self._mem.percent)
def update(self, widgets): def update(self, widgets):
self._mem = psutil.virtual_memory() 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
self._mem = {
"total": bumblebee.util.bytefmt(data["MemTotal"]),
"available": bumblebee.util.bytefmt(data["MemAvailable"]),
"free": bumblebee.util.bytefmt(data["MemFree"]),
"used": bumblebee.util.bytefmt(data["MemTotal"] - data["MemFree"] - data["Buffers"] - data["Cached"] - data["Slab"]),
"percent": (float(data["MemTotal"] - data["MemAvailable"])/data["MemTotal"])*100
}
def state(self, widget): def state(self, widget):
if self._mem.percent > float(self.parameter("critical", 90)): if self._mem["percent"] > float(self.parameter("critical", 90)):
return "critical" return "critical"
if self._mem.percent > float(self.parameter("warning", 80)): if self._mem["percent"] > float(self.parameter("warning", 80)):
return "warning" return "warning"
return None return None

View file

@ -38,6 +38,8 @@ class Module(bumblebee.engine.Module):
else: else:
self._state = "transition" self._state = "transition"
transition = " ".join(line.split(" ")[2:]) transition = " ".join(line.split(" ")[2:])
self._text = temp
if transition:
self._text = "{} {}".format(temp, transition) self._text = "{} {}".format(temp, transition)
def state(self, widget): def state(self, widget):

View file

@ -0,0 +1,67 @@
# 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
"""
try:
import i3ipc
except ImportError:
pass
import bumblebee.util
import bumblebee.input
import bumblebee.output
import bumblebee.engine
from bumblebee.output import scrollable
class Module(bumblebee.engine.Module):
"""Window title module."""
def __init__(self, engine, config):
super(Module, self).__init__(
engine,
config,
bumblebee.output.Widget(full_text=self.get_title)
)
try:
self._i3 = i3ipc.Connection()
self._full_title = self._i3.get_tree().find_focused().name
except Exception:
self._full_title = "n/a"
def get_title(self, widget):
if bumblebee.util.asbool(self.parameter("scroll", False)):
return self.scrolling_focused_title(widget)
else:
return self.focused_title(widget)
def focused_title(self, widget):
title = self._full_title[0:self.parameter("max", 64)]
placeholder = self.parameter("placeholder", "...")
if title != self._full_title:
title = self._full_title[0:self.parameter("max", 64) - len(placeholder)]
title = "{}{}".format(title, placeholder)
return title
@scrollable
def scrolling_focused_title(self, widget):
return self._full_title
def update(self, widgets):
"""Update current title."""
try:
self._full_title = self._i3.get_tree().find_focused().name
except Exception:
self._full_title = "n/a"
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -5,6 +5,7 @@
Parameters: Parameters:
* traffic.exclude: Comma-separated list of interface prefixes to exclude (defaults to "lo,virbr,docker,vboxnet,veth") * 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.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 re import re
@ -23,7 +24,7 @@ class Module(bumblebee.engine.Module):
self._exclude = tuple(filter(len, self.parameter("exclude", "lo,virbr,docker,vboxnet,veth").split(","))) self._exclude = tuple(filter(len, self.parameter("exclude", "lo,virbr,docker,vboxnet,veth").split(",")))
self._status = "" self._status = ""
self._showname = self.parameter("showname", "True") self._showname = bumblebee.util.asbool(self.parameter("showname", True))
self._prev = {} self._prev = {}
self._states = {} self._states = {}
self._states["include"] = [] self._states["include"] = []
@ -87,12 +88,12 @@ class Module(bumblebee.engine.Module):
name = "traffic-{}".format(interface) name = "traffic-{}".format(interface)
if self._showname != "False": if self._showname:
self.create_widget(widgets, name, interface) self.create_widget(widgets, name, interface)
for direction in ["rx", "tx"]: for direction in ["rx", "tx"]:
name = "traffic.{}-{}".format(direction, interface) name = "traffic.{}-{}".format(direction, interface)
widget = self.create_widget(widgets, name, attributes={"theme.minwidth": "100.00MB"}) widget = self.create_widget(widgets, name, attributes={"theme.minwidth": "1000.00MB"})
prev = self._prev.get(name, 0) prev = self._prev.get(name, 0)
speed = bumblebee.util.bytefmt(int(data[direction]) - int(prev)) speed = bumblebee.util.bytefmt(int(data[direction]) - int(prev))
widget.full_text(speed) widget.full_text(speed)

View file

@ -33,9 +33,9 @@ class Theme(object):
def _init(self, data): def _init(self, data):
"""Initialize theme from data structure""" """Initialize theme from data structure"""
self._theme = data
for iconset in data.get("icons", []): for iconset in data.get("icons", []):
self._merge(data, self._load_icons(iconset)) self._merge(data, self._load_icons(iconset))
self._theme = data
self._defaults = data.get("defaults", {}) self._defaults = data.get("defaults", {})
self._cycles = self._theme.get("cycle", []) self._cycles = self._theme.get("cycle", [])
self.reset() self.reset()
@ -174,6 +174,7 @@ class Theme(object):
if key in target and isinstance(target[key], dict): if key in target and isinstance(target[key], dict):
self._merge(target[key], value) self._merge(target[key], value)
else: else:
if not key in target:
target[key] = copy.deepcopy(value) target[key] = copy.deepcopy(value)
return target return target

BIN
screenshots/title.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -1,61 +0,0 @@
# pylint: disable=C0103,C0111
import mock
import unittest
import tests.mocks as mocks
from bumblebee.input import LEFT_MOUSE
from bumblebee.modules.memory import Module
class VirtualMemory(object):
def __init__(self, percent):
self.percent = percent
class TestMemoryModule(unittest.TestCase):
def setUp(self):
mocks.setup_test(self, Module)
self._psutil = mock.patch("bumblebee.modules.memory.psutil")
self.psutil = self._psutil.start()
def tearDown(self):
self._psutil.stop()
mocks.teardown_test(self)
def test_leftclick(self):
mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module)
self.popen.assert_call("gnome-system-monitor")
def test_warning(self):
self.config.set("memory.critical", "80")
self.config.set("memory.warning", "70")
self.psutil.virtual_memory.return_value = VirtualMemory(75)
self.module.update_all()
self.assertTrue("warning" in self.module.state(self.anyWidget))
def test_critical(self):
self.config.set("memory.critical", "80")
self.config.set("memory.warning", "70")
self.psutil.virtual_memory.return_value = VirtualMemory(81)
self.module.update_all()
self.assertTrue("critical" in self.module.state(self.anyWidget))
def test_format(self):
self.config.set("memory.format", "memory used: {used}")
rv = VirtualMemory(50)
rv.total = 1000
rv.available = 500
self.psutil.virtual_memory.return_value = rv
self.module.update_all()
self.assertEquals("memory used: 500.00B", self.module.memory_usage(self.anyWidget))
def test_usage(self):
rv = VirtualMemory(50)
rv.total = 1000
rv.available = 500
self.psutil.virtual_memory.return_value = rv
self.module.update_all()
self.assertEquals("500.00B/1000.00B (50.00%)", self.module.memory_usage(self.anyWidget))
self.assertEquals(None, self.module.state(self.anyWidget))
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -0,0 +1,38 @@
{
"icons": [ "awesome-fonts" ],
"defaults": {
"separator-block-width": 0,
"separator": "",
"warning": {
"fg": "#002b36",
"bg": "#b58900"
},
"critical": {
"fg": "#002b36",
"bg": "#dc322f"
},
"fg": "#93a1a1", "bg": "#002b36"
},
"dnf": {
"good": {
"fg": "#002b36",
"bg": "#859900"
}
},
"pacman": {
"good": {
"fg": "#002b36",
"bg": "#859900"
}
},
"battery": {
"charged": {
"fg": "#002b36",
"bg": "#859900"
},
"AC": {
"fg": "#002b36",
"bg": "#859900"
}
}
}