diff --git a/.codeclimate.yml b/.codeclimate.yml index ec80874..d0c140e 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -13,3 +13,4 @@ ratings: - "**.py" exclude_paths: - tests/ +- thirdparty/ diff --git a/bumblebee/modules/layout-xkb.py b/bumblebee/modules/layout-xkb.py new file mode 100644 index 0000000..dfdc6df --- /dev/null +++ b/bumblebee/modules/layout-xkb.py @@ -0,0 +1,64 @@ +# pylint: disable=C0111,R0903 + +"""Displays the current keyboard layout using libX11 + +Requires the following library: + * libX11.so.6 + +Parameters: + * layout-xkb.showname: Boolean that indicate whether the full name should be displayed. Defaults to false (only the symbol will be displayed) +""" + +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) + + 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[rotation:] + xkb.groups_symbols[:rotation] + variants = xkb.groups_variants[rotation:] + xkb.groups_variants[:rotation] + + try: + bumblebee.util.execute("setxkbmap -layout {} -variant {}".format(",".join(layouts), ",".join(variants))) + except RuntimeError: + pass + + 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 + return "{} ({})".format(name, xkb.group_variant) if xkb.group_variant else name + except Exception: + return "n/a" + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/nvidiagpu.py b/bumblebee/modules/nvidiagpu.py index 87c34f5..42f1972 100644 --- a/bumblebee/modules/nvidiagpu.py +++ b/bumblebee/modules/nvidiagpu.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- """Displays GPU name, temperature and memory usage. diff --git a/bumblebee/modules/rotation.py b/bumblebee/modules/rotation.py new file mode 100644 index 0000000..cec8c10 --- /dev/null +++ b/bumblebee/modules/rotation.py @@ -0,0 +1,70 @@ +# 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 os +import re +import sys + +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): + new_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) + new_widgets.append(widget) + widget.set("orientation", orientation) + + while len(widgets) > 0: + del widgets[0] + for widget in new_widgets: + widgets.append(widget) + + def update(self, widgets): + 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 diff --git a/bumblebee/modules/sensors.py b/bumblebee/modules/sensors.py index 7011ae2..2772dff 100644 --- a/bumblebee/modules/sensors.py +++ b/bumblebee/modules/sensors.py @@ -47,9 +47,13 @@ class Module(bumblebee.engine.Module): return temperature def get_mhz( self ): - output = open("/proc/cpuinfo").read() - m = re.search(r"cpu MHz\s+:\s+(\d+)", output) - mhz = int(m.group(1)) + 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) + mhz = int(m.group(1)) if mhz < 1000: return "{} MHz".format(mhz) diff --git a/bumblebee/modules/weather.py b/bumblebee/modules/weather.py index 92a2745..66334dd 100644 --- a/bumblebee/modules/weather.py +++ b/bumblebee/modules/weather.py @@ -16,6 +16,7 @@ Parameters: import bumblebee.input import bumblebee.output import bumblebee.engine +import re import json import time try: @@ -27,11 +28,12 @@ except ImportError: class Module(bumblebee.engine.Module): def __init__(self, engine, config): super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.temperature) + bumblebee.output.Widget(full_text=self.output) ) self._temperature = 0 self._apikey = self.parameter("apikey", "af7bfe22287c652d032a3064ffa44088") self._location = self.parameter("location", "auto") + self._city = self.parameter("location", "") self._interval = int(self.parameter("interval", "15")) self._unit = self.parameter("unit", "metric") self._nextcheck = 0 @@ -46,10 +48,17 @@ class Module(bumblebee.engine.Module): return "F" return "" - def temperature(self, widget): + def temperature(self): + return u"{}°{}".format(self._temperature, self._unit_suffix()) + + def city(self): + self._city = re.sub('[_-]', ' ', self._city) + return u"{} ".format(self._city) + + def output(self, widget): if not self._valid: return u"?" - return u"{}°{}".format(self._temperature, self._unit_suffix()) + return self.city() + self.temperature() def state( self, widget ): if self._valid: @@ -83,6 +92,7 @@ class Module(bumblebee.engine.Module): location_url = "http://ipinfo.io/json" location = json.loads(requests.get(location_url).text) coord = location["loc"].split(",") + self._city = location["city"] weather_url = "{url}&lat={lat}&lon={lon}".format(url=weather_url, lat=coord[0], lon=coord[1]) else: weather_url = "{url}&q={city}".format(url=weather_url, city=self._location) diff --git a/bumblebee/theme.py b/bumblebee/theme.py index 647a823..ee6f330 100644 --- a/bumblebee/theme.py +++ b/bumblebee/theme.py @@ -25,17 +25,20 @@ def themes(): class Theme(object): """Represents a collection of icons and colors""" def __init__(self, name): - self._init(self.load(name)) self._widget = None self._cycle_idx = 0 self._cycle = {} self._prevbg = None + self._colorset = {} + self._init(self.load(name)) def _init(self, data): """Initialize theme from data structure""" self._theme = data 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() @@ -99,6 +102,21 @@ class Theme(object): """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""" + if name == "wal": + return self._load_wal_colors() + def _load_icons(self, name): """Load icons for a theme""" path = "{}/icons/".format(theme_path()) @@ -110,7 +128,7 @@ class Theme(object): if os.path.isfile(themefile): try: - with io.open(themefile,encoding="utf-8") as data: + with io.open(themefile, encoding="utf-8") as data: return json.load(data) except ValueError as exception: raise bumblebee.error.ThemeLoadError("JSON error: {}".format(exception)) @@ -155,7 +173,9 @@ class Theme(object): widget.set(key, (idx + 1) % len(value)) value = value[idx] - return 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 diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index e014fab..267a935 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -14,6 +14,7 @@ "brightness": { "prefix": "" }, "load": { "prefix": "" }, "layout": { "prefix": "" }, + "layout-xkb": { "prefix": "" }, "todo": { "empty": {"prefix": "" }, "items": {"prefix": "" } }, diff --git a/themes/wal-powerline.json b/themes/wal-powerline.json new file mode 100644 index 0000000..049ecf8 --- /dev/null +++ b/themes/wal-powerline.json @@ -0,0 +1,42 @@ +{ + "icons": [ "awesome-fonts" ], + "colors": [ "wal" ], + "defaults": { + "separator-block-width": 0, + "critical": { + "fg": "cursor", + "bg": "color5" + }, + "warning": { + "fg": "cursor", + "bg": "color6" + }, + "default_separators": false + }, + "cycle": [ + { + "fg": "foreground", + "bg": "background" + }, + { + "fg": "background", + "bg": "foreground" + } + ], + "dnf": { + "good": { + "fg": "background", + "bg": "color3" + } + }, + "battery": { + "charged": { + "fg": "background", + "bg": "color3" + }, + "AC": { + "fg": "background", + "bg": "color3" + } + } +}