From 36c9a24ac4196836d749c4fbad8961b27b40b8ea Mon Sep 17 00:00:00 2001 From: me Date: Mon, 13 Jan 2020 15:53:21 +0200 Subject: [PATCH 001/112] [modules/traffic] add format parameter --- bumblebee/modules/traffic.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bumblebee/modules/traffic.py b/bumblebee/modules/traffic.py index 2eac453..250a1a7 100644 --- a/bumblebee/modules/traffic.py +++ b/bumblebee/modules/traffic.py @@ -6,6 +6,8 @@ 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) + * traffic.format: Format string for download/upload speeds. + Defaults to "{:.2f}" """ import time @@ -25,6 +27,7 @@ class Module(bumblebee.engine.Module): self._status = "" self._showname = bumblebee.util.asbool(self.parameter("showname", True)) + self._format = self.parameter("format", "{:.2f}") self._prev = {} self._states = {} self._lastcheck = 0 @@ -102,8 +105,10 @@ class Module(bumblebee.engine.Module): 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) + speed = bumblebee.util.bytefmt( + (int(data[direction]) - int(prev))/timediff, + self._format) + txtspeed = '{0}/s'.format(speed) widget.full_text(txtspeed) self._prev[name] = data[direction] From 2f6a2285bdffd1d3cf8a4a2eaec8cacb727feece Mon Sep 17 00:00:00 2001 From: me Date: Sat, 18 Jan 2020 19:02:04 +0200 Subject: [PATCH 002/112] [modules/traffic] compute theme.minwidth based on traffic.format --- bumblebee/modules/traffic.py | 17 ++++++++++++++++- tests/modules/test_traffic.py | 23 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/modules/test_traffic.py diff --git a/bumblebee/modules/traffic.py b/bumblebee/modules/traffic.py index 250a1a7..856ac76 100644 --- a/bumblebee/modules/traffic.py +++ b/bumblebee/modules/traffic.py @@ -10,6 +10,7 @@ Parameters: Defaults to "{:.2f}" """ +import re import time import psutil import netifaces @@ -70,6 +71,20 @@ class Module(bumblebee.engine.Module): return [] return retval + def get_minwidth_str(self): + """computes theme.minwidth string based on traffic.format parameter""" + minwidth_str = "1000" + try: + length = int(re.match("{:\.(\d+)f}", self._format).group(1)) + if length > 0: + minwidth_str += "." + "0" * length + except AttributeError: + # return default value + return "1000.00MB" + finally: + minwidth_str += "MB" + return minwidth_str + def _update_widgets(self, widgets): interfaces = [i for i in netifaces.interfaces() if not i.startswith(self._exclude)] @@ -103,7 +118,7 @@ class Module(bumblebee.engine.Module): for direction in ["rx", "tx"]: name = "traffic.{}-{}".format(direction, interface) - widget = self.create_widget(widgets, name, attributes={"theme.minwidth": "1000.00MB"}) + widget = self.create_widget(widgets, name, attributes={"theme.minwidth": self.get_minwidth_str()}) prev = self._prev.get(name, 0) speed = bumblebee.util.bytefmt( (int(data[direction]) - int(prev))/timediff, diff --git a/tests/modules/test_traffic.py b/tests/modules/test_traffic.py new file mode 100644 index 0000000..951a4d8 --- /dev/null +++ b/tests/modules/test_traffic.py @@ -0,0 +1,23 @@ +import mock +import unittest + +import tests.mocks as mocks + +from bumblebee.modules.traffic import Module + +class TestTrafficModule(unittest.TestCase): + def setUp(self): + mocks.setup_test(self, Module) + + def test_default_format(self): + self.assertEqual(self.module._format, "{:.2f}") + + def test_get_minwidth_str(self): + # default value (two digits after dot) + self.assertEqual(self.module.get_minwidth_str(), "1000.00MB") + # integer value + self.module._format = "{:.0f}" + self.assertEqual(self.module.get_minwidth_str(), "1000MB") + # just one digit after dot + self.module._format = "{:.1f}" + self.assertEqual(self.module.get_minwidth_str(), "1000.0MB") From 6bfb0fb513fbc2608f6771113dafea8c79275a8b Mon Sep 17 00:00:00 2001 From: me Date: Sun, 19 Jan 2020 12:32:12 +0200 Subject: [PATCH 003/112] [core/output] add support for drawing graphs using Braille chars --- bumblebee/output.py | 101 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/bumblebee/output.py b/bumblebee/output.py index 10c13e0..6f14f90 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -30,6 +30,33 @@ VBARS = [ u"\u258a", u"\u2589", u"\u2588"] +BRAILLE = { + (0, 0): u" ", + (1, 0): u"\u2840", + (2, 0): u"\u2844", + (3, 0): u"\u2846", + (4, 0): u"\u2847", + (0, 1): u"\u2880", + (0, 2): u"\u28a0", + (0, 3): u"\u28b0", + (0, 4): u"\u28b8", + (1, 1): u"\u28c0", + (2, 1): u"\u28c4", + (3, 1): u"\u28c6", + (4, 1): u"\u28c7", + (1, 2): u"\u28e0", + (2, 2): u"\u28e4", + (3, 2): u"\u28e6", + (4, 2): u"\u28e7", + (1, 3): u"\u28f0", + (2, 3): u"\u28f4", + (3, 3): u"\u28f6", + (4, 3): u"\u28f7", + (1, 4): u"\u28f8", + (2, 4): u"\u28fc", + (3, 4): u"\u28fe", + (4, 4): u"\u28ff" +} log = logging.getLogger(__name__) @@ -169,6 +196,80 @@ def vbar(value, width): return VBar(value, width).get_chars() +class BrailleGraph(object): + """ + graph using Braille chars + scaled to passed values + """ + def __init__(self, values): + """ + Args: + + values (list): list of values + """ + self.values = values + # length of values list must be even + # because one Braille char displays two values + if len(self.values) % 2 == 1: + self.values.append(0) + self.steps = self.get_steps() + self.parts = [tuple(self.steps[i:i+2]) + for i in range(len(self.steps))[::2]] + + @staticmethod + def get_height(value, unit): + """ + Compute height of a value relative to unit + + Args: + + value (number): value + + unit (number): unit + """ + if value < unit / 10.: + return 0 + elif value <= unit: + return 1 + elif value <= unit * 2: + return 2 + elif value <= unit * 3: + return 3 + else: + return 4 + + def get_steps(self): + """ + Convert the list of values to a list of steps + + Return: list + """ + maxval = max(self.values) + unit = maxval / 4. + if unit == 0: + return [0] * len(self.values) + stepslist = [] + for value in self.values: + stepslist.append(self.get_height(value, unit)) + return stepslist + + def get_chars(self): + """ + Decide which chars to draw + + Return: str + """ + chars = [] + for part in self.parts: + chars.append(BRAILLE[part]) + return "".join(chars) + + +def bgraph(values): + """wrapper function""" + return BrailleGraph(values).get_chars() + + class Widget(bumblebee.store.Store): """Represents a single visible block in the status bar""" def __init__(self, full_text="", name=""): From 283f6d632d54b92ef450752f032fe07792a9410a Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sun, 19 Jan 2020 13:03:04 +0100 Subject: [PATCH 004/112] [doc] Fix typo see #486 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c824549..ec8c48d 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,7 @@ Here are some screenshots for all themes that currently exist: :exclamation: Some themes (all 'Powerline' themes) require [Font Awesome](http://fontawesome.io/) and a powerline-compatible font ([powerline-fonts](https://github.com/powerline/fonts), for example) to display all icons correctly. -:exclamation: If you want to add your own team, just drop it into `~/.config/bumblebee-status/themes/` +:exclamation: If you want to add your own theme, just drop it into `~/.config/bumblebee-status/themes/` Gruvbox Powerline (`-t gruvbox-powerline`) (contributed by [@TheEdgeOfRage](https://github.com/TheEdgeOfRage)): From 9ef8e32ba9b22855b80ee392e4c880230cb58ce8 Mon Sep 17 00:00:00 2001 From: me Date: Sun, 19 Jan 2020 15:59:52 +0200 Subject: [PATCH 005/112] [modules/traffic] add option to enable traffic graphs --- bumblebee/modules/traffic.py | 41 +++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/bumblebee/modules/traffic.py b/bumblebee/modules/traffic.py index 856ac76..aa4a4a5 100644 --- a/bumblebee/modules/traffic.py +++ b/bumblebee/modules/traffic.py @@ -8,6 +8,9 @@ Parameters: * traffic.showname: If set to False, hide network interface name (defaults to True) * traffic.format: Format string for download/upload speeds. Defaults to "{:.2f}" + * traffic.graphlen: Graph lenth in seconds. Positive even integer. Each + char shows 2 seconds. If set, enables up/down traffic + graphs """ import re @@ -39,6 +42,10 @@ class Module(bumblebee.engine.Module): self._states["exclude"].append(state[1:]) else: self._states["include"].append(state) + self._graphlen = int(self.parameter("graphlen", 0)) + if self._graphlen > 0: + self._graphdata = {} + self._first_run = True self._update_widgets(widgets) def state(self, widget): @@ -72,8 +79,16 @@ class Module(bumblebee.engine.Module): return retval def get_minwidth_str(self): - """computes theme.minwidth string based on traffic.format parameter""" - minwidth_str = "1000" + """ + computes theme.minwidth string + based on traffic.format and traffic.graphlen parameters + """ + minwidth_str = "" + if self._graphlen > 0: + graph_len = int(self._graphlen / 2) + graph_prefix = "0" * graph_len + minwidth_str += graph_prefix + minwidth_str += "1000" try: length = int(re.match("{:\.(\d+)f}", self._format).group(1)) if length > 0: @@ -96,6 +111,13 @@ class Module(bumblebee.engine.Module): if timediff <= 0: timediff = 1 self._lastcheck = now for interface in interfaces: + if self._graphlen > 0: + import logging + logging.info(self._graphdata) + if interface not in self._graphdata: + self._graphdata[interface] = { + "rx": [0] * self._graphlen, + "tx": [0] * self._graphlen} if not interface: interface = "lo" state = "down" if len(self.get_addresses(interface)) > 0: @@ -120,10 +142,19 @@ class Module(bumblebee.engine.Module): name = "traffic.{}-{}".format(direction, interface) widget = self.create_widget(widgets, name, attributes={"theme.minwidth": self.get_minwidth_str()}) prev = self._prev.get(name, 0) - speed = bumblebee.util.bytefmt( - (int(data[direction]) - int(prev))/timediff, - self._format) + bspeed = (int(data[direction]) - int(prev))/timediff + speed = bumblebee.util.bytefmt(bspeed, self._format) txtspeed = '{0}/s'.format(speed) + if self._graphlen > 0: + # skip first value returned by psutil, because it is + # giant and ruins the grapth ratio until it gets pushed + # out of saved list + if self._first_run is True: + self._first_run = False + else: + self._graphdata[interface][direction] = self._graphdata[interface][direction][1:] + self._graphdata[interface][direction].append(bspeed) + txtspeed = "{}{}".format(bumblebee.output.bgraph(self._graphdata[interface][direction]), txtspeed) widget.full_text(txtspeed) self._prev[name] = data[direction] From 42d9956dd55998994198de7fec0983de41a4ba89 Mon Sep 17 00:00:00 2001 From: me Date: Sun, 19 Jan 2020 16:04:33 +0200 Subject: [PATCH 006/112] [modules/traffic] remove forgotten debug logging --- bumblebee/modules/traffic.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bumblebee/modules/traffic.py b/bumblebee/modules/traffic.py index aa4a4a5..47271e9 100644 --- a/bumblebee/modules/traffic.py +++ b/bumblebee/modules/traffic.py @@ -112,8 +112,6 @@ class Module(bumblebee.engine.Module): self._lastcheck = now for interface in interfaces: if self._graphlen > 0: - import logging - logging.info(self._graphdata) if interface not in self._graphdata: self._graphdata[interface] = { "rx": [0] * self._graphlen, From 9fa8b434776209a021d489cbfaaf900d1969a70a Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sat, 25 Jan 2020 13:47:08 +0100 Subject: [PATCH 007/112] [modules/pomodoro] Add note about command chaining to doc fixes #532 --- bumblebee/modules/pomodoro.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bumblebee/modules/pomodoro.py b/bumblebee/modules/pomodoro.py index 64f1514..3f1c2b7 100644 --- a/bumblebee/modules/pomodoro.py +++ b/bumblebee/modules/pomodoro.py @@ -10,7 +10,10 @@ Parameters: * 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!"' + Example: 'notify-send "Time up!"'. If you want to chain multiple commands, + please use an external wrapper script and invoke that. The module itself does + not support command chaining (see https://github.com/tobi-wan-kenobi/bumblebee-status/issues/532 + for a detailled explanation) """ from __future__ import absolute_import From fff2f3a9dbf70880bbb816b146faade9ce560997 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 26 Jan 2020 14:34:26 -0600 Subject: [PATCH 008/112] Add metadata module --- bumblebee/__about__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 bumblebee/__about__.py diff --git a/bumblebee/__about__.py b/bumblebee/__about__.py new file mode 100644 index 0000000..53c319c --- /dev/null +++ b/bumblebee/__about__.py @@ -0,0 +1,12 @@ +__title__ = 'bumblebee-status' +__package_name__ = 'bumblebee_status' +__version__ = '0.1.0' +__description__ = 'a modular, theme-able status line generator for the i3 window manager.' +__author__ = 'tobi-wan-kenobi' +__email__ = 'tobi@tobi-wan-kenobi.at' +__github__ = 'https://github.com/tobi-wan-kenobi/bumblebee-status' +__docs__ = 'https://github.com/tobi-wan-kenobi/bumblebee-status' +__tracker__ = 'https://github.com/tobi-wan-kenobi/bumblebee-status' +__pypi__ = 'https://pypi.org/project/bumblebee-status/' +__license__ = 'MIT' +__copyright__ = 'Copyright 2016- tobi-wan-kenobi' From 35ce99b4b6298f00118d3c6bb31d8aac281453cd Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 26 Jan 2020 14:35:42 -0600 Subject: [PATCH 009/112] Stub base requirements (empty) --- requirements/base.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 requirements/base.txt diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1 @@ + From 68d7b1b24bbead90c6330f87bf017cbf365c5b21 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 26 Jan 2020 14:41:20 -0600 Subject: [PATCH 010/112] Add requirement files for bumbleebee modules See also: https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/Available-Modules --- requirements/modules/battery_upower_reqs.txt | 1 + requirements/modules/cpu.txt | 1 + requirements/modules/cpu2.txt | 1 + requirements/modules/currency.txt | 1 + requirements/modules/docker_ps.txt | 1 + requirements/modules/dunst.txt | 1 + requirements/modules/getcrypto.txt | 1 + requirements/modules/git.txt | 1 + requirements/modules/github.txt | 1 + requirements/modules/hddtemp.txt | 1 + requirements/modules/layout_xkb.txt | 1 + requirements/modules/memory.txt | 1 + requirements/modules/network_traffic.txt | 2 ++ requirements/modules/nic.txt | 1 + requirements/modules/pihole.txt | 1 + requirements/modules/rss.txt | 1 + requirements/modules/spaceapi.txt | 3 +++ requirements/modules/spotify.txt | 1 + requirements/modules/stock.txt | 1 + requirements/modules/sun.txt | 2 ++ requirements/modules/system.txt | 1 + requirements/modules/taskwarrior.txt | 1 + requirements/modules/title.txt | 1 + requirements/modules/traffic.txt | 2 ++ requirements/modules/weather.txt | 1 + requirements/modules/yubikey.txt | 1 + 26 files changed, 31 insertions(+) create mode 100644 requirements/modules/battery_upower_reqs.txt create mode 100644 requirements/modules/cpu.txt create mode 100644 requirements/modules/cpu2.txt create mode 100644 requirements/modules/currency.txt create mode 100644 requirements/modules/docker_ps.txt create mode 100644 requirements/modules/dunst.txt create mode 100644 requirements/modules/getcrypto.txt create mode 100644 requirements/modules/git.txt create mode 100644 requirements/modules/github.txt create mode 100644 requirements/modules/hddtemp.txt create mode 100644 requirements/modules/layout_xkb.txt create mode 100644 requirements/modules/memory.txt create mode 100644 requirements/modules/network_traffic.txt create mode 100644 requirements/modules/nic.txt create mode 100644 requirements/modules/pihole.txt create mode 100644 requirements/modules/rss.txt create mode 100644 requirements/modules/spaceapi.txt create mode 100644 requirements/modules/spotify.txt create mode 100644 requirements/modules/stock.txt create mode 100644 requirements/modules/sun.txt create mode 100644 requirements/modules/system.txt create mode 100644 requirements/modules/taskwarrior.txt create mode 100644 requirements/modules/title.txt create mode 100644 requirements/modules/traffic.txt create mode 100644 requirements/modules/weather.txt create mode 100644 requirements/modules/yubikey.txt diff --git a/requirements/modules/battery_upower_reqs.txt b/requirements/modules/battery_upower_reqs.txt new file mode 100644 index 0000000..e2182f1 --- /dev/null +++ b/requirements/modules/battery_upower_reqs.txt @@ -0,0 +1 @@ +dbus diff --git a/requirements/modules/cpu.txt b/requirements/modules/cpu.txt new file mode 100644 index 0000000..a4d92cc --- /dev/null +++ b/requirements/modules/cpu.txt @@ -0,0 +1 @@ +psutil diff --git a/requirements/modules/cpu2.txt b/requirements/modules/cpu2.txt new file mode 100644 index 0000000..a4d92cc --- /dev/null +++ b/requirements/modules/cpu2.txt @@ -0,0 +1 @@ +psutil diff --git a/requirements/modules/currency.txt b/requirements/modules/currency.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements/modules/currency.txt @@ -0,0 +1 @@ +requests diff --git a/requirements/modules/docker_ps.txt b/requirements/modules/docker_ps.txt new file mode 100644 index 0000000..bdb9670 --- /dev/null +++ b/requirements/modules/docker_ps.txt @@ -0,0 +1 @@ +docker diff --git a/requirements/modules/dunst.txt b/requirements/modules/dunst.txt new file mode 100644 index 0000000..053749f --- /dev/null +++ b/requirements/modules/dunst.txt @@ -0,0 +1 @@ +dunst diff --git a/requirements/modules/getcrypto.txt b/requirements/modules/getcrypto.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements/modules/getcrypto.txt @@ -0,0 +1 @@ +requests diff --git a/requirements/modules/git.txt b/requirements/modules/git.txt new file mode 100644 index 0000000..6378b19 --- /dev/null +++ b/requirements/modules/git.txt @@ -0,0 +1 @@ +pygit2 diff --git a/requirements/modules/github.txt b/requirements/modules/github.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements/modules/github.txt @@ -0,0 +1 @@ +requests diff --git a/requirements/modules/hddtemp.txt b/requirements/modules/hddtemp.txt new file mode 100644 index 0000000..b4e9f6f --- /dev/null +++ b/requirements/modules/hddtemp.txt @@ -0,0 +1 @@ +hddtemp diff --git a/requirements/modules/layout_xkb.txt b/requirements/modules/layout_xkb.txt new file mode 100644 index 0000000..8085f79 --- /dev/null +++ b/requirements/modules/layout_xkb.txt @@ -0,0 +1 @@ +xkbgroup diff --git a/requirements/modules/memory.txt b/requirements/modules/memory.txt new file mode 100644 index 0000000..a4d92cc --- /dev/null +++ b/requirements/modules/memory.txt @@ -0,0 +1 @@ +psutil diff --git a/requirements/modules/network_traffic.txt b/requirements/modules/network_traffic.txt new file mode 100644 index 0000000..9d4e170 --- /dev/null +++ b/requirements/modules/network_traffic.txt @@ -0,0 +1,2 @@ +psutil +netifaces diff --git a/requirements/modules/nic.txt b/requirements/modules/nic.txt new file mode 100644 index 0000000..3f008fd --- /dev/null +++ b/requirements/modules/nic.txt @@ -0,0 +1 @@ +netifaces diff --git a/requirements/modules/pihole.txt b/requirements/modules/pihole.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements/modules/pihole.txt @@ -0,0 +1 @@ +requests diff --git a/requirements/modules/rss.txt b/requirements/modules/rss.txt new file mode 100644 index 0000000..1b25361 --- /dev/null +++ b/requirements/modules/rss.txt @@ -0,0 +1 @@ +feedparser diff --git a/requirements/modules/spaceapi.txt b/requirements/modules/spaceapi.txt new file mode 100644 index 0000000..3a2dc45 --- /dev/null +++ b/requirements/modules/spaceapi.txt @@ -0,0 +1,3 @@ +requests +json +time diff --git a/requirements/modules/spotify.txt b/requirements/modules/spotify.txt new file mode 100644 index 0000000..e2182f1 --- /dev/null +++ b/requirements/modules/spotify.txt @@ -0,0 +1 @@ +dbus diff --git a/requirements/modules/stock.txt b/requirements/modules/stock.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements/modules/stock.txt @@ -0,0 +1 @@ +requests diff --git a/requirements/modules/sun.txt b/requirements/modules/sun.txt new file mode 100644 index 0000000..3d91ac6 --- /dev/null +++ b/requirements/modules/sun.txt @@ -0,0 +1,2 @@ +requests +suntime diff --git a/requirements/modules/system.txt b/requirements/modules/system.txt new file mode 100644 index 0000000..5d6fce4 --- /dev/null +++ b/requirements/modules/system.txt @@ -0,0 +1 @@ +tkinter diff --git a/requirements/modules/taskwarrior.txt b/requirements/modules/taskwarrior.txt new file mode 100644 index 0000000..55ddadf --- /dev/null +++ b/requirements/modules/taskwarrior.txt @@ -0,0 +1 @@ +taskw diff --git a/requirements/modules/title.txt b/requirements/modules/title.txt new file mode 100644 index 0000000..a623776 --- /dev/null +++ b/requirements/modules/title.txt @@ -0,0 +1 @@ +i3ipc diff --git a/requirements/modules/traffic.txt b/requirements/modules/traffic.txt new file mode 100644 index 0000000..9d4e170 --- /dev/null +++ b/requirements/modules/traffic.txt @@ -0,0 +1,2 @@ +psutil +netifaces diff --git a/requirements/modules/weather.txt b/requirements/modules/weather.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements/modules/weather.txt @@ -0,0 +1 @@ +requests diff --git a/requirements/modules/yubikey.txt b/requirements/modules/yubikey.txt new file mode 100644 index 0000000..0ad4a6b --- /dev/null +++ b/requirements/modules/yubikey.txt @@ -0,0 +1 @@ +yubico From b0a3dc89d15792fc760accb445aaf6eebe1b843b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 26 Jan 2020 14:58:28 -0600 Subject: [PATCH 011/112] Add setup.py (see examples of subpackages) - pip install -e . - pip install -e '.[cpu]' - pip install -e '.[title]' - pip install -e '.[title,weather]' See also: https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies --- setup.py | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100755 setup.py diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..89e52c9 --- /dev/null +++ b/setup.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# -*- coding: utf8 - *- +import sys + +from setuptools import setup + +about = {} +with open("bumblebee/__about__.py") as fp: + exec(fp.read(), about) + +with open('requirements/base.txt') as f: + install_reqs = [line for line in f.read().split('\n') if line] + +# Module packages +def read_module(filename): + with open('requirements/modules/{}.txt'.format(filename)) as f: + return [line for line in f.read().split('\n') if line] + +EXTRAS_REQUIREMENTS_MAP = { + "battery-upower": read_module("battery_upower_reqs"), + "cpu": read_module("cpu"), + "cpu2": read_module("cpu2"), + "currency": read_module("currency"), + "docker_ps": read_module("docker_ps"), + "dunst": read_module("dunst"), + "getcrypto": read_module("getcrypto"), + "git": read_module("git"), + "github": read_module("github"), + "hddtemp": read_module("hddtemp"), + "layout-xkb": read_module("layout_xkb"), + "memory": read_module("memory"), + "network_traffic": read_module("network_traffic"), + "nic": read_module("nic"), + "pihole": read_module("pihole"), + "rss": read_module("rss"), + "spaceapi": read_module("spaceapi"), + "spotify": read_module("spotify"), + "stock": read_module("stock"), + "sun": read_module("sun"), + "system": read_module("system"), + "taskwarrior": read_module("taskwarrior"), + "title": read_module("title"), + "traffic": read_module("traffic"), + "weather": read_module("weather"), + "yubikey": read_module("yubikey"), +} + +if sys.version_info[0] > 2: # LATER: README needs to be reStructuredText for pypi + readme = open('README.md', encoding='utf-8').read() +else: + readme = open('README.md').read() + +setup( + name=about['__title__'], + version=about['__version__'], + url=about['__github__'], + download_url=about['__pypi__'], + project_urls={ + 'Documentation': about['__docs__'], + 'Code': about['__github__'], + 'Issue tracker': about['__tracker__'], + }, + license=about['__license__'], + author=about['__author__'], + author_email=about['__email__'], + description=about['__description__'], + long_description=readme, + packages=['bumblebee'], + include_package_data=True, + install_requires=install_reqs, + extras_require=EXTRAS_REQUIREMENTS_MAP, + zip_safe=False, + keywords=about['__title__'], + classifiers=[ + 'Development Status :: 3 - Alpha', + "License :: OSI Approved :: MIT License", + 'Intended Audience :: Developers', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Internationalization", + "Topic :: Utilities", + ], +) From fbbfc8403199987896c71fc84b68cff7468564ad Mon Sep 17 00:00:00 2001 From: Emma Tinten Date: Mon, 27 Jan 2020 13:11:45 +0100 Subject: [PATCH 012/112] Fixing #543 --- bumblebee/util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bumblebee/util.py b/bumblebee/util.py index 5f4ae44..678f46b 100644 --- a/bumblebee/util.py +++ b/bumblebee/util.py @@ -3,6 +3,7 @@ import shlex import logging import subprocess +import os try: from exceptions import RuntimeError @@ -29,7 +30,9 @@ def aslist(val): 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) + my_env = os.environ.copy() + my_env['LC_ALL'] = "C" + proc = subprocess.Popen(args, env=my_env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) rv = None if wait: From 8ac9cdb913b9eea1188a1e6c53ded948286b072d Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Wed, 29 Jan 2020 21:26:19 +0100 Subject: [PATCH 013/112] [pypi] Update email --- bumblebee/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee/__about__.py b/bumblebee/__about__.py index 53c319c..7e1a72a 100644 --- a/bumblebee/__about__.py +++ b/bumblebee/__about__.py @@ -3,7 +3,7 @@ __package_name__ = 'bumblebee_status' __version__ = '0.1.0' __description__ = 'a modular, theme-able status line generator for the i3 window manager.' __author__ = 'tobi-wan-kenobi' -__email__ = 'tobi@tobi-wan-kenobi.at' +__email__ = 'github@tobi-wan-kenobi.at' __github__ = 'https://github.com/tobi-wan-kenobi/bumblebee-status' __docs__ = 'https://github.com/tobi-wan-kenobi/bumblebee-status' __tracker__ = 'https://github.com/tobi-wan-kenobi/bumblebee-status' From 2a84346221831c7bed1fe568a7d0e63471d8635b Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Wed, 29 Jan 2020 21:33:11 +0100 Subject: [PATCH 014/112] [doc] Add README.rst --- README.rst | 430 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 430 insertions(+) create mode 100644 README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..5a94f5a --- /dev/null +++ b/README.rst @@ -0,0 +1,430 @@ +bumblebee-status +================ + +|Build Status| |Code Climate| |Test Coverage| |Issue Count| + +**Many, many thanks to all contributors! As of now, 54 of the modules +are from various contributors (!), and only 19 from myself.** + +.. figure:: ./Modules.md + :alt: List of modules + + List of modules + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-solarized.png + :alt: Solarized Powerline + + Solarized Powerline + +bumblebee-status is a modular, theme-able status line generator for the +`i3 window manager `__. + +Focus is on: \* Ease of use (no configuration files!) \* Theme support +\* Extensibility (of course…) + +One thing I like in particular: You can use the mouse wheel up/down to +switch workspaces forward and back everywhere throughout the bar (unless +you have mapped the mouse wheel buttons to another action for a widget, +in which case this doesn’t work while hovering that particular widget). + +I hope you like it and appreciate any kind of feedback: Bug reports, +Feature requests, etc. :) + +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 FontAwesome version: 4 (free version of 5 doesn’t include some +of the icons) + +Explicitly unsupported Python versions: 3.2 (missing unicode literals) + +:information_source: The |Font Awesome| is required for all themes that +contain icons (because that is the font that includes these icons). +Please refer to your distribution’s package management on how to install +them, or get them from their website directly. Also, please note that +Font Awesome removed some icons used by ``bumblebee-status`` from the +free set in version 5, so if possible, stick with 4. + +:: + + # Font Awesome installation instructions + + # Arch Linux + $ sudo pacman -S awesome-terminal-fonts + + # FreeBSD + $ sudo pkg install font-awesome + $ sudo pkg install py36-tzlocal py36-pytz py36-netifaces py36-psutil py36-requests #for dependencies + + # Other + # see https://github.com/gabrielelana/awesome-terminal-fonts + +Example usage: + +:: + + bar { + status_command /bumblebee-status -m cpu memory battery time pasink pasource -p time.format="%H:%M" -t solarized + } + +Documentation +============= + +See `the +wiki `__ for +documentation. + +See +`FAQ `__ +for, well, FAQs. + +Other resources: + +- A list of `available + modules `__ +- `How to write a + theme `__ +- `How to write a + module `__ + +Installation +============ + +:: + + $ git clone git://github.com/tobi-wan-kenobi/bumblebee-status + +Dependencies +============ + +`Available +modules `__ +lists the dependencies (Python modules and external executables) for +each module. If you are not using a module, you don’t need the +dependencies. + +Usage +===== + +Normal usage +------------ + +In your i3wm configuration, modify the *status_command* for your i3bar +like this: + +:: + + bar { + status_command \ + -m \ + -p \ + -t + } + +You can retrieve a list of modules (and their parameters) and themes by +entering: + +:: + + $ cd bumblebee-status + $ ./bumblebee-status -l themes + $ ./bumblebee-status -l modules + +Any parameter you can specify with ``-p =``, you can +alternatively specify in ``~/.bumblebee-status.conf`` or +``~/.config/bumblebee-status.conf``. This parameters act as a +**fallback**, so values specified with ``-p`` have priority. + +Parameters can also be used to override theme settings, such as: + +:: + + $ ./bumblebee-status -p .theme.= + # for example, to get a spacer with a red background: + $ ./bumblebee-status -m spacer -p spacer.theme.bg=#ff0000 + +Configuration files have a format like this: + +:: + + $ cat ~/.bumblebee-status.conf + [module-parameters] + = + +For example: + +:: + + $ cat ~/.bumblebee-status.conf + [module-parameters] + github.token=abcdefabcdef12345 + +To change the update interval, use: + +:: + + $ ./bumblebee-status -m -p interval= + +As a simple example, this is what my i3 configuration looks like: + +:: + + bar { + font pango:Inconsolata 10 + position top + tray_output none + status_command ~/.i3/bumblebee-status/bumblebee-status -m nic disk:root cpu memory battery date time pasink pasource dnf -p root.path=/ time.format="%H:%M CW %V" date.format="%a, %b %d %Y" -t solarized-powerline + } + +Restart i3wm and - that’s it! + +Events +------ + +By default, the following events are handled: + +- Mouse-Wheel on any module moves to the next/previous i3 workspace +- Left-click on the “disk” module opens the specified path in nautilus +- Left-click on either “memory” or “cpu” opens gnome-system-monitor +- Left-click on a “pulseaudio” (or pasource/pasink) module toggles the + mute state +- Right-click on a “pulseaudio” module opens pavucontrol +- Mouse-Wheel up/down on a “pulseaudio” module raises/lowers the volume + +By default, the Mouse-Wheel wraps for the current output. You can +disable this behavior by providing the parameter +``engine.workspacewrap=false`` (starting with version 1.4.5). Also, you +can completely disable output switching by using +``engine.workspacewheel=false``. + +You can provide your own handlers to any module by using the following +“special” configuration parameters: + +- left-click +- right-click +- middle-click +- wheel-up +- wheel-down For example, to execute “pavucontrol” whenever you + left-click on the nic module, you could write: + +``$ bumblebee-status -p nic.left-click="pavucontrol"`` + +In the string, you can use the following format identifiers: - name - +instance - button + +For example: + +``$ bumblebee-status -p disk.left-click="nautilus {instance}"`` + +Errors +------ + +If errors occur, you should see them in the i3bar itself. If that does +not work, or you need more information for troubleshooting, you can +activate a debug log using the ``-d`` or ``--debug`` switch: + +:: + + $ ./bumblebee-status -d -m + +This will create a file called ``~/bumblebee-status-debug.log`` by +default. The file name can be changed by using the ``-f`` or +``--logfile`` option. + +Advanced Usage +~~~~~~~~~~~~~~ + +If you want to have a minimal bar that stays out of the way, you can use +the ``-a`` or ``--autohide`` switch to specify a list of module names. +All those modules will only be displayed when (and as long as) their +state is either warning or critical (high CPU usage, low disk space, +etc.). As long as the module is in a “normal” state and does not require +attention, it will remain hidden. Note that this parameter is specified +*in addition* to ``-m`` (i.e. to autohide the CPU module, you would use +``bumblebee-status -m cpu memory traffic -a cpu``). + +Required Modules +================ + +Modules and commandline utilities are only required for modules, the +core itself has no external dependencies at all. + +- psutil (for the modules ‘cpu’, ‘memory’, ‘traffic’) +- netifaces (for the modules ‘nic’, ‘traffic’) +- requests (for the modules ‘weather’, ‘github’, ‘getcrypto’, ‘stock’, + ‘currency’, ‘sun’) +- power (for the module ‘battery’) +- dbus (for the module ‘spotify’, ‘deezer’) +- i3ipc (for the module ‘title’) +- pacman-contrib (for module ‘arch-update’) +- docker (for the module ‘docker_ps’) +- pytz (for the module ‘datetimetz’) +- localtz (for the module ‘datetimetz’) +- suntime (for the module ‘sun’) +- feedparser (for the module ‘rss’) + +Required commandline utilities +============================== + +- xset (for the module ‘caffeine’) +- notify-send (for the module ‘caffeine’) +- cmus-remote (for the module ‘cmus’) +- dnf (for the module ‘dnf’) +- gpmdp-remote (for the module ‘gpmdp’) +- setxkbmap (for the module ‘layout’) +- fakeroot (for the module ‘pacman’) +- pacman (for the module ‘pacman’) +- pactl (for the module ‘pulseaudio’) +- ping (for the module ‘ping’) +- redshift (for the module ‘redshift’) +- xrandr (for the module ‘xrandr’) +- mpc (for the module ‘mpd’) +- bluez / blueman (for module ‘bluetooth’) +- dbus-send (for module ‘bluetooth’) +- nvidia-smi (for module ‘nvidiagpu’) +- sensors (for module ‘sensors’, as fallback) +- zpool (for module ‘zpool’) +- progress (for module ‘progress’) +- i3exit (for module ‘system’) + +Examples +======== + +Here are some screenshots for all themes that currently exist: + +:exclamation: Some themes (all ‘Powerline’ themes) require `Font +Awesome `__ and a powerline-compatible font +(`powerline-fonts `__, for example) +to display all icons correctly. + +:exclamation: If you want to add your own theme, just drop it into +``~/.config/bumblebee-status/themes/`` + +Gruvbox Powerline (``-t gruvbox-powerline``) (contributed by +[@TheEdgeOfRage](https://github.com/TheEdgeOfRage)): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-gruvbox.png + :alt: Gruvbox Powerline + + Gruvbox Powerline + +Gruvbox Powerline Light (``-t gruvbox-powerline-light``) (contributed by +`freed00m `__): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/gruvbox-powerline-light.png + :alt: Gruvbox Powerline Light + + Gruvbox Powerline Light + +Solarized Powerline (``-t solarized-powerline``): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-solarized.png + :alt: Solarized Powerline + + Solarized Powerline + +Gruvbox (``-t gruvbox``): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/gruvbox.png + :alt: Gruvbox + + Gruvbox + +Gruvbox Light (``-t gruvbox-light``) (contributed by +`freed00m `__): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/gruvbox-light.png + :alt: Gruvbox Light + + Gruvbox Light + +Solarized (``-t solarized``): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/solarized.png + :alt: Solarized + + Solarized + +Powerline (``-t powerline``): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline.png + :alt: Powerline + + Powerline + +Greyish Powerline (``-t greyish-powerline``) (contributed by Joshua +Bark): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-greyish.png + :alt: Greyish Powerline + + Greyish Powerline + +Iceberg (``-t iceberg``) (contributed by +`whzup `__): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/iceberg.png + :alt: Iceberg + + Iceberg + +Iceberg Powerline (``-t iceberg-powerline``) (contributed by +`whzup `__): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/iceberg-powerline.png + :alt: Iceberg Powerline + + Iceberg Powerline + +Iceberg Dark Powerline (``-t iceberg-dark-powerline``) (contributed by +`gkeep `__): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/iceberg-dark-powerline.png + :alt: Iceberg Dark Powerline + + Iceberg Dark Powerline + +Iceberg Rainbow (``-t iceberg-rainbow``) (contributed by +`whzup `__): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/iceberg-rainbow.png + :alt: Iceberg Rainbow + + Iceberg Rainbow + +One Dark Powerline (``-t onedark-powerline``) (contributed by +`dillasyx `__): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/onedark-powerline.png + :alt: One Dark Powerline + + One Dark Powerline + +Dracula Powerline (-t dracula-powerline) (contributed by +`xsteadfastx `__): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/dracula-powerline.png + :alt: Dracula Powerline + + Dracula Powerline + +Default (nothing or ``-t default``): + +.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/default.png + :alt: Default + + Default + +.. |Build Status| image:: https://travis-ci.org/tobi-wan-kenobi/bumblebee-status.svg?branch=master + :target: https://travis-ci.org/tobi-wan-kenobi/bumblebee-status +.. |Code Climate| image:: https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/gpa.svg + :target: https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status +.. |Test Coverage| image:: https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/coverage.svg + :target: https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage +.. |Issue Count| image:: https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/issue_count.svg + :target: https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status +.. |Font Awesome| image:: https://fontawesome.com/ From 73ab214654a5e7462ce1a2397b7193a26d511e24 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Wed, 29 Jan 2020 21:37:28 +0100 Subject: [PATCH 015/112] [pypi] Add markdown --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 89e52c9..eaedaa0 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ # -*- coding: utf8 - *- import sys -from setuptools import setup +from setuptools import setup, Extension about = {} with open("bumblebee/__about__.py") as fp: @@ -45,7 +45,7 @@ EXTRAS_REQUIREMENTS_MAP = { "yubikey": read_module("yubikey"), } -if sys.version_info[0] > 2: # LATER: README needs to be reStructuredText for pypi +if sys.version_info[0] > 2: readme = open('README.md', encoding='utf-8').read() else: readme = open('README.md').read() @@ -65,6 +65,7 @@ setup( author_email=about['__email__'], description=about['__description__'], long_description=readme, + long_description_content_type='text/markdown', packages=['bumblebee'], include_package_data=True, install_requires=install_reqs, From 65a9f66b9e97ba05e38654e4b14f258a66e4a4ed Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Thu, 30 Jan 2020 21:04:43 +0100 Subject: [PATCH 016/112] [tests] Make tests run when env is passed to subprocess fixes #538 --- tests/mocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mocks.py b/tests/mocks.py index 38f1583..3940bb0 100644 --- a/tests/mocks.py +++ b/tests/mocks.py @@ -75,7 +75,7 @@ class MockPopen(object): self.mock.returncode = 0 def assert_call(self, cmd): - self.mock.popen.assert_any_call(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + self.mock.popen.assert_any_call(shlex.split(cmd), env=mock.ANY, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) def cleanup(self): self._patch.stop() From da3df1769e2aa4b1bb8cf0a1f8d8322b5033d832 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Thu, 30 Jan 2020 21:11:28 +0100 Subject: [PATCH 017/112] [modules/pacman] Fix update path for AUR installs If bumblebee-status is installed from an AUR package, using the relative binary path might not work, so fall back to the binary installed in /usr/share. fixes #536 --- bumblebee/modules/pacman.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bumblebee/modules/pacman.py b/bumblebee/modules/pacman.py index 546c43a..6fe19a0 100644 --- a/bumblebee/modules/pacman.py +++ b/bumblebee/modules/pacman.py @@ -24,7 +24,10 @@ repos = ["core", "extra", "community", "multilib", "testing", "other"] def get_pacman_info(widget, path): try: - result = bumblebee.util.execute("{}/../../bin/pacman-updates".format(path)) + cmd = "{}/../../bin/pacman-updates".format(path) + if not os.path.exists(cmd): + cmd = "/usr/share/bumblebee-status/bin/pacman-update" + result = bumblebee.util.execute(cmd) except: pass From 6c64ec81dfb5c1ad449d5a535cd49b429016a4b0 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Thu, 30 Jan 2020 21:28:48 +0100 Subject: [PATCH 018/112] [doc] Add installation instructions --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index ec8c48d..40490ec 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,17 @@ bumblebee-status is a modular, theme-able status line generator for the [i3 window manager](https://i3wm.org/). +**How to install** +``` +# from AUR: +git clone https://aur.archlinux.org/bumblebee-status.git +cd bumblebee-status +makepkg -sicr + +# from PyPI (thanks @tony): +pip install --user bumblebee-status +``` + Focus is on: * Ease of use (no configuration files!) * Theme support From 8adb1c4d38059b1f80db870f56ff11360dfcdae2 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Thu, 30 Jan 2020 21:29:41 +0100 Subject: [PATCH 019/112] [doc] Remove obsolete RST doc --- README.rst | 430 ----------------------------------------------------- 1 file changed, 430 deletions(-) delete mode 100644 README.rst diff --git a/README.rst b/README.rst deleted file mode 100644 index 5a94f5a..0000000 --- a/README.rst +++ /dev/null @@ -1,430 +0,0 @@ -bumblebee-status -================ - -|Build Status| |Code Climate| |Test Coverage| |Issue Count| - -**Many, many thanks to all contributors! As of now, 54 of the modules -are from various contributors (!), and only 19 from myself.** - -.. figure:: ./Modules.md - :alt: List of modules - - List of modules - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-solarized.png - :alt: Solarized Powerline - - Solarized Powerline - -bumblebee-status is a modular, theme-able status line generator for the -`i3 window manager `__. - -Focus is on: \* Ease of use (no configuration files!) \* Theme support -\* Extensibility (of course…) - -One thing I like in particular: You can use the mouse wheel up/down to -switch workspaces forward and back everywhere throughout the bar (unless -you have mapped the mouse wheel buttons to another action for a widget, -in which case this doesn’t work while hovering that particular widget). - -I hope you like it and appreciate any kind of feedback: Bug reports, -Feature requests, etc. :) - -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 FontAwesome version: 4 (free version of 5 doesn’t include some -of the icons) - -Explicitly unsupported Python versions: 3.2 (missing unicode literals) - -:information_source: The |Font Awesome| is required for all themes that -contain icons (because that is the font that includes these icons). -Please refer to your distribution’s package management on how to install -them, or get them from their website directly. Also, please note that -Font Awesome removed some icons used by ``bumblebee-status`` from the -free set in version 5, so if possible, stick with 4. - -:: - - # Font Awesome installation instructions - - # Arch Linux - $ sudo pacman -S awesome-terminal-fonts - - # FreeBSD - $ sudo pkg install font-awesome - $ sudo pkg install py36-tzlocal py36-pytz py36-netifaces py36-psutil py36-requests #for dependencies - - # Other - # see https://github.com/gabrielelana/awesome-terminal-fonts - -Example usage: - -:: - - bar { - status_command /bumblebee-status -m cpu memory battery time pasink pasource -p time.format="%H:%M" -t solarized - } - -Documentation -============= - -See `the -wiki `__ for -documentation. - -See -`FAQ `__ -for, well, FAQs. - -Other resources: - -- A list of `available - modules `__ -- `How to write a - theme `__ -- `How to write a - module `__ - -Installation -============ - -:: - - $ git clone git://github.com/tobi-wan-kenobi/bumblebee-status - -Dependencies -============ - -`Available -modules `__ -lists the dependencies (Python modules and external executables) for -each module. If you are not using a module, you don’t need the -dependencies. - -Usage -===== - -Normal usage ------------- - -In your i3wm configuration, modify the *status_command* for your i3bar -like this: - -:: - - bar { - status_command \ - -m \ - -p \ - -t - } - -You can retrieve a list of modules (and their parameters) and themes by -entering: - -:: - - $ cd bumblebee-status - $ ./bumblebee-status -l themes - $ ./bumblebee-status -l modules - -Any parameter you can specify with ``-p =``, you can -alternatively specify in ``~/.bumblebee-status.conf`` or -``~/.config/bumblebee-status.conf``. This parameters act as a -**fallback**, so values specified with ``-p`` have priority. - -Parameters can also be used to override theme settings, such as: - -:: - - $ ./bumblebee-status -p .theme.= - # for example, to get a spacer with a red background: - $ ./bumblebee-status -m spacer -p spacer.theme.bg=#ff0000 - -Configuration files have a format like this: - -:: - - $ cat ~/.bumblebee-status.conf - [module-parameters] - = - -For example: - -:: - - $ cat ~/.bumblebee-status.conf - [module-parameters] - github.token=abcdefabcdef12345 - -To change the update interval, use: - -:: - - $ ./bumblebee-status -m -p interval= - -As a simple example, this is what my i3 configuration looks like: - -:: - - bar { - font pango:Inconsolata 10 - position top - tray_output none - status_command ~/.i3/bumblebee-status/bumblebee-status -m nic disk:root cpu memory battery date time pasink pasource dnf -p root.path=/ time.format="%H:%M CW %V" date.format="%a, %b %d %Y" -t solarized-powerline - } - -Restart i3wm and - that’s it! - -Events ------- - -By default, the following events are handled: - -- Mouse-Wheel on any module moves to the next/previous i3 workspace -- Left-click on the “disk” module opens the specified path in nautilus -- Left-click on either “memory” or “cpu” opens gnome-system-monitor -- Left-click on a “pulseaudio” (or pasource/pasink) module toggles the - mute state -- Right-click on a “pulseaudio” module opens pavucontrol -- Mouse-Wheel up/down on a “pulseaudio” module raises/lowers the volume - -By default, the Mouse-Wheel wraps for the current output. You can -disable this behavior by providing the parameter -``engine.workspacewrap=false`` (starting with version 1.4.5). Also, you -can completely disable output switching by using -``engine.workspacewheel=false``. - -You can provide your own handlers to any module by using the following -“special” configuration parameters: - -- left-click -- right-click -- middle-click -- wheel-up -- wheel-down For example, to execute “pavucontrol” whenever you - left-click on the nic module, you could write: - -``$ bumblebee-status -p nic.left-click="pavucontrol"`` - -In the string, you can use the following format identifiers: - name - -instance - button - -For example: - -``$ bumblebee-status -p disk.left-click="nautilus {instance}"`` - -Errors ------- - -If errors occur, you should see them in the i3bar itself. If that does -not work, or you need more information for troubleshooting, you can -activate a debug log using the ``-d`` or ``--debug`` switch: - -:: - - $ ./bumblebee-status -d -m - -This will create a file called ``~/bumblebee-status-debug.log`` by -default. The file name can be changed by using the ``-f`` or -``--logfile`` option. - -Advanced Usage -~~~~~~~~~~~~~~ - -If you want to have a minimal bar that stays out of the way, you can use -the ``-a`` or ``--autohide`` switch to specify a list of module names. -All those modules will only be displayed when (and as long as) their -state is either warning or critical (high CPU usage, low disk space, -etc.). As long as the module is in a “normal” state and does not require -attention, it will remain hidden. Note that this parameter is specified -*in addition* to ``-m`` (i.e. to autohide the CPU module, you would use -``bumblebee-status -m cpu memory traffic -a cpu``). - -Required Modules -================ - -Modules and commandline utilities are only required for modules, the -core itself has no external dependencies at all. - -- psutil (for the modules ‘cpu’, ‘memory’, ‘traffic’) -- netifaces (for the modules ‘nic’, ‘traffic’) -- requests (for the modules ‘weather’, ‘github’, ‘getcrypto’, ‘stock’, - ‘currency’, ‘sun’) -- power (for the module ‘battery’) -- dbus (for the module ‘spotify’, ‘deezer’) -- i3ipc (for the module ‘title’) -- pacman-contrib (for module ‘arch-update’) -- docker (for the module ‘docker_ps’) -- pytz (for the module ‘datetimetz’) -- localtz (for the module ‘datetimetz’) -- suntime (for the module ‘sun’) -- feedparser (for the module ‘rss’) - -Required commandline utilities -============================== - -- xset (for the module ‘caffeine’) -- notify-send (for the module ‘caffeine’) -- cmus-remote (for the module ‘cmus’) -- dnf (for the module ‘dnf’) -- gpmdp-remote (for the module ‘gpmdp’) -- setxkbmap (for the module ‘layout’) -- fakeroot (for the module ‘pacman’) -- pacman (for the module ‘pacman’) -- pactl (for the module ‘pulseaudio’) -- ping (for the module ‘ping’) -- redshift (for the module ‘redshift’) -- xrandr (for the module ‘xrandr’) -- mpc (for the module ‘mpd’) -- bluez / blueman (for module ‘bluetooth’) -- dbus-send (for module ‘bluetooth’) -- nvidia-smi (for module ‘nvidiagpu’) -- sensors (for module ‘sensors’, as fallback) -- zpool (for module ‘zpool’) -- progress (for module ‘progress’) -- i3exit (for module ‘system’) - -Examples -======== - -Here are some screenshots for all themes that currently exist: - -:exclamation: Some themes (all ‘Powerline’ themes) require `Font -Awesome `__ and a powerline-compatible font -(`powerline-fonts `__, for example) -to display all icons correctly. - -:exclamation: If you want to add your own theme, just drop it into -``~/.config/bumblebee-status/themes/`` - -Gruvbox Powerline (``-t gruvbox-powerline``) (contributed by -[@TheEdgeOfRage](https://github.com/TheEdgeOfRage)): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-gruvbox.png - :alt: Gruvbox Powerline - - Gruvbox Powerline - -Gruvbox Powerline Light (``-t gruvbox-powerline-light``) (contributed by -`freed00m `__): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/gruvbox-powerline-light.png - :alt: Gruvbox Powerline Light - - Gruvbox Powerline Light - -Solarized Powerline (``-t solarized-powerline``): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-solarized.png - :alt: Solarized Powerline - - Solarized Powerline - -Gruvbox (``-t gruvbox``): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/gruvbox.png - :alt: Gruvbox - - Gruvbox - -Gruvbox Light (``-t gruvbox-light``) (contributed by -`freed00m `__): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/gruvbox-light.png - :alt: Gruvbox Light - - Gruvbox Light - -Solarized (``-t solarized``): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/solarized.png - :alt: Solarized - - Solarized - -Powerline (``-t powerline``): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline.png - :alt: Powerline - - Powerline - -Greyish Powerline (``-t greyish-powerline``) (contributed by Joshua -Bark): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-greyish.png - :alt: Greyish Powerline - - Greyish Powerline - -Iceberg (``-t iceberg``) (contributed by -`whzup `__): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/iceberg.png - :alt: Iceberg - - Iceberg - -Iceberg Powerline (``-t iceberg-powerline``) (contributed by -`whzup `__): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/iceberg-powerline.png - :alt: Iceberg Powerline - - Iceberg Powerline - -Iceberg Dark Powerline (``-t iceberg-dark-powerline``) (contributed by -`gkeep `__): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/iceberg-dark-powerline.png - :alt: Iceberg Dark Powerline - - Iceberg Dark Powerline - -Iceberg Rainbow (``-t iceberg-rainbow``) (contributed by -`whzup `__): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/iceberg-rainbow.png - :alt: Iceberg Rainbow - - Iceberg Rainbow - -One Dark Powerline (``-t onedark-powerline``) (contributed by -`dillasyx `__): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/onedark-powerline.png - :alt: One Dark Powerline - - One Dark Powerline - -Dracula Powerline (-t dracula-powerline) (contributed by -`xsteadfastx `__): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/dracula-powerline.png - :alt: Dracula Powerline - - Dracula Powerline - -Default (nothing or ``-t default``): - -.. figure:: https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/default.png - :alt: Default - - Default - -.. |Build Status| image:: https://travis-ci.org/tobi-wan-kenobi/bumblebee-status.svg?branch=master - :target: https://travis-ci.org/tobi-wan-kenobi/bumblebee-status -.. |Code Climate| image:: https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/gpa.svg - :target: https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status -.. |Test Coverage| image:: https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/coverage.svg - :target: https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage -.. |Issue Count| image:: https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/issue_count.svg - :target: https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status -.. |Font Awesome| image:: https://fontawesome.com/ From 69ec06f3d66ccc782625272ae524fe828ab69b3a Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Thu, 30 Jan 2020 21:30:44 +0100 Subject: [PATCH 020/112] [pypi] Bump version --- bumblebee/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee/__about__.py b/bumblebee/__about__.py index 7e1a72a..f6da61f 100644 --- a/bumblebee/__about__.py +++ b/bumblebee/__about__.py @@ -1,6 +1,6 @@ __title__ = 'bumblebee-status' __package_name__ = 'bumblebee_status' -__version__ = '0.1.0' +__version__ = '1.9.0' __description__ = 'a modular, theme-able status line generator for the i3 window manager.' __author__ = 'tobi-wan-kenobi' __email__ = 'github@tobi-wan-kenobi.at' From db23da1019b592d5b8736dfca193013f57dfd8ec Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Thu, 30 Jan 2020 21:31:19 +0100 Subject: [PATCH 021/112] [pypi] Bump version (again...) --- bumblebee/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee/__about__.py b/bumblebee/__about__.py index f6da61f..61f7a87 100644 --- a/bumblebee/__about__.py +++ b/bumblebee/__about__.py @@ -1,6 +1,6 @@ __title__ = 'bumblebee-status' __package_name__ = 'bumblebee_status' -__version__ = '1.9.0' +__version__ = '1.10.0' __description__ = 'a modular, theme-able status line generator for the i3 window manager.' __author__ = 'tobi-wan-kenobi' __email__ = 'github@tobi-wan-kenobi.at' From 11f16bd1aaa496cccd9589ec553a00a2ad956419 Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 10:57:30 +0200 Subject: [PATCH 022/112] [doc] improve docstring --- bumblebee/output.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 6f14f90..6984a80 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -342,7 +342,13 @@ class I3BarOutput(object): sys.stdout.write("]\n") def draw(self, widget, module=None, engine=None): - """Draw a single widget""" + """ + Draw a single widget + + Note: technically, this method doesn't draw anything. It only adds + blocks of JSON text to self._widgets: one for separator, if the + theme contains a separator and one for the widget itself + """ full_text = widget.full_text() if widget.get_module() and widget.get_module().hidden(): return From f5f09bdb1bf35cabdb0c861c78ddb4d00df59036 Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 11:16:26 +0200 Subject: [PATCH 023/112] [core/output] extract I3BarOutput.draw() into a class --- bumblebee/output.py | 68 +++++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 6984a80..64c1eb6 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -321,33 +321,26 @@ class Widget(bumblebee.store.Store): else: return self._full_text -class I3BarOutput(object): - """Manage output according to the i3bar protocol""" + +class WidgetDrawer(object): + """ + Wrapper for I3BarOutput.draw(), + because that function is getting too big + """ def __init__(self, theme, config=None): + """ + Keep the same signature as I3BarOutput.__init__() + """ 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") + self._widgets = [] def draw(self, widget, module=None, engine=None): """ - Draw a single widget - - Note: technically, this method doesn't draw anything. It only adds - blocks of JSON text to self._widgets: one for separator, if the - theme contains a separator and one for the widget itself + Keep the same argument signature as I3BarOutput.draw() + Return: list + list[0] - optional if the theme has a separator + list[1] - JSON text for the widget """ full_text = widget.full_text() if widget.get_module() and widget.get_module().hidden(): @@ -407,6 +400,39 @@ class I3BarOutput(object): "name": module.id, "markup": markup, }) + return self._widgets + + +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 + + Note: technically, this method doesn't draw anything. It only adds + blocks of JSON text to self._widgets: one for separator, if the + theme contains a separator and one for the widget itself + """ + widget_drawer = WidgetDrawer(self._theme, self._config) + self._widgets.extend(widget_drawer.draw(widget, module, engine)) def begin(self): """Start one output iteration""" From e2bc26352080e49b36fa89b40b19beb6f77c8d9d Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 11:19:59 +0200 Subject: [PATCH 024/112] [fix] always return a list, even empty --- bumblebee/output.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 64c1eb6..224e3f0 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -344,10 +344,10 @@ class WidgetDrawer(object): """ full_text = widget.full_text() if widget.get_module() and widget.get_module().hidden(): - return + 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 + return [] padding = self._theme.padding(widget) prefix = self._theme.prefix(widget, padding) From 8a16d3cb3accc0b6eead8b7b4dc933f10bd92c72 Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 11:30:48 +0200 Subject: [PATCH 025/112] extract method --- bumblebee/output.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 224e3f0..696d540 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -335,6 +335,17 @@ class WidgetDrawer(object): self._config = config self._widgets = [] + def add_separator(self, widget, separator): + """Add separator (if theme has one)""" + 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), + }) + def draw(self, widget, module=None, engine=None): """ Keep the same argument signature as I3BarOutput.draw() @@ -369,14 +380,8 @@ class WidgetDrawer(object): 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), - }) + self.add_separator(widget, separator) + width = self._theme.minwidth(widget) if width: From 289a40ff729277bf4a9bbac1295090dd59b8c6d1 Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 11:34:17 +0200 Subject: [PATCH 026/112] add JSON separator block at the beginning of draw() --- bumblebee/output.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 696d540..06b123b 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -353,6 +353,10 @@ class WidgetDrawer(object): list[0] - optional if the theme has a separator list[1] - JSON text for the widget """ + + separator = self._theme.separator(widget) + self.add_separator(widget, separator) + full_text = widget.full_text() if widget.get_module() and widget.get_module().hidden(): return [] @@ -379,9 +383,6 @@ class WidgetDrawer(object): if suffix: full_text = u"{}{}".format(full_text, suffix) - separator = self._theme.separator(widget) - self.add_separator(widget, separator) - width = self._theme.minwidth(widget) if width: From 9d8c1f910ef89cad9e1ec10f8e4268044aa68ba5 Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 11:37:42 +0200 Subject: [PATCH 027/112] when there's nothing to return, do it ASAP --- bumblebee/output.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 06b123b..925fd0b 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -354,15 +354,17 @@ class WidgetDrawer(object): list[1] - JSON text for the widget """ - separator = self._theme.separator(widget) - self.add_separator(widget, separator) - - 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 [] + + separator = self._theme.separator(widget) + self.add_separator(widget, separator) + + full_text = widget.full_text() + padding = self._theme.padding(widget) prefix = self._theme.prefix(widget, padding) From 4a7a26456dfcb806a4d202a9ce68d7c2b080808f Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 11:44:07 +0200 Subject: [PATCH 028/112] make prefix/suffix instance variables they will be used in more than one method --- bumblebee/output.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 925fd0b..c36a37e 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -334,6 +334,8 @@ class WidgetDrawer(object): self._theme = theme self._config = config self._widgets = [] + self._prefix = None + self._suffix = None def add_separator(self, widget, separator): """Add separator (if theme has one)""" @@ -367,28 +369,28 @@ class WidgetDrawer(object): padding = self._theme.padding(widget) - prefix = self._theme.prefix(widget, padding) - suffix = self._theme.suffix(widget, padding) + self._prefix = self._theme.prefix(widget, padding) + self._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 = "{}".format( + self._prefix = "{}".format( "foreground='{}'".format(fg) if fg else "", "background='{}'".format(bg) if bg else "", - prefix + self._prefix ) - if prefix: - full_text = u"{}{}".format(prefix, full_text) - if suffix: - full_text = u"{}{}".format(full_text, suffix) + if self._prefix: + full_text = u"{}{}".format(self._prefix, full_text) + if self._suffix: + full_text = u"{}{}".format(full_text, self._suffix) width = self._theme.minwidth(widget) if width: - full_text = full_text.ljust(len(width) + len(prefix) + len(suffix)) + full_text = full_text.ljust(len(width) + len(self._prefix) + len(self._suffix)) markup = "none" if not self._config else self._config.markup() @@ -402,7 +404,7 @@ class WidgetDrawer(object): "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, +# "min_width": width + "A"*(len(self._prefix) + len(self._suffix)) if width else None, "align": self._theme.align(widget), "instance": widget.id, "name": module.id, From a3cfe0abbedd22a97640bdee92db53c358349d45 Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 11:47:21 +0200 Subject: [PATCH 029/112] create variable just before using it --- bumblebee/output.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index c36a37e..73654bf 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -365,8 +365,6 @@ class WidgetDrawer(object): separator = self._theme.separator(widget) self.add_separator(widget, separator) - full_text = widget.full_text() - padding = self._theme.padding(widget) self._prefix = self._theme.prefix(widget, padding) @@ -382,6 +380,8 @@ class WidgetDrawer(object): self._prefix ) + full_text = widget.full_text() + if self._prefix: full_text = u"{}{}".format(self._prefix, full_text) if self._suffix: From 333a1f9907b28769c132011220361673d1903a97 Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 11:53:25 +0200 Subject: [PATCH 030/112] only query config for markup value once --- bumblebee/output.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 73654bf..021fcc0 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -370,7 +370,9 @@ class WidgetDrawer(object): self._prefix = self._theme.prefix(widget, padding) self._suffix = self._theme.suffix(widget, padding) - if self._config.markup() == "pango": + markup = "none" if not self._config else self._config.markup() + + if markup == "pango": # add prefix/suffix colors fg = self._theme.prefix_fg(widget) bg = self._theme.prefix_bg(widget) @@ -392,8 +394,6 @@ class WidgetDrawer(object): if width: full_text = full_text.ljust(len(width) + len(self._prefix) + len(self._suffix)) - markup = "none" if not self._config else self._config.markup() - if markup == "pango": full_text = full_text.replace("&", "&") From ea1c0b704390521f052db87abfd215653e59066a Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 11:56:07 +0200 Subject: [PATCH 031/112] query config for markup before processing prefix/suffix --- bumblebee/output.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 021fcc0..6d1ccd1 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -365,13 +365,13 @@ class WidgetDrawer(object): separator = self._theme.separator(widget) self.add_separator(widget, separator) + markup = "none" if not self._config else self._config.markup() + padding = self._theme.padding(widget) self._prefix = self._theme.prefix(widget, padding) self._suffix = self._theme.suffix(widget, padding) - markup = "none" if not self._config else self._config.markup() - if markup == "pango": # add prefix/suffix colors fg = self._theme.prefix_fg(widget) From 1f0f1c1722ef719a3bff02645b4dfddc74fdc0ad Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 12:00:15 +0200 Subject: [PATCH 032/112] make markup an instance variable it will be used in more than one method --- bumblebee/output.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 6d1ccd1..54ba854 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -334,6 +334,7 @@ class WidgetDrawer(object): self._theme = theme self._config = config self._widgets = [] + self._markup = None self._prefix = None self._suffix = None @@ -365,14 +366,14 @@ class WidgetDrawer(object): separator = self._theme.separator(widget) self.add_separator(widget, separator) - markup = "none" if not self._config else self._config.markup() + self._markup = "none" if not self._config else self._config.markup() padding = self._theme.padding(widget) self._prefix = self._theme.prefix(widget, padding) self._suffix = self._theme.suffix(widget, padding) - if markup == "pango": + if self._markup == "pango": # add prefix/suffix colors fg = self._theme.prefix_fg(widget) bg = self._theme.prefix_bg(widget) @@ -394,7 +395,7 @@ class WidgetDrawer(object): if width: full_text = full_text.ljust(len(width) + len(self._prefix) + len(self._suffix)) - if markup == "pango": + if self._markup == "pango": full_text = full_text.replace("&", "&") self._widgets.append({ @@ -408,7 +409,7 @@ class WidgetDrawer(object): "align": self._theme.align(widget), "instance": widget.id, "name": module.id, - "markup": markup, + "markup": self._markup, }) return self._widgets From 2166a269673720bba8b4671cfd591839641e184c Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 12:04:44 +0200 Subject: [PATCH 033/112] make full_text an instance variable it will be used in more than one method --- bumblebee/output.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 54ba854..4f5afca 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -335,6 +335,7 @@ class WidgetDrawer(object): self._config = config self._widgets = [] self._markup = None + self._full_text = None self._prefix = None self._suffix = None @@ -368,6 +369,8 @@ class WidgetDrawer(object): self._markup = "none" if not self._config else self._config.markup() + self._full_text = widget.full_text() + padding = self._theme.padding(widget) self._prefix = self._theme.prefix(widget, padding) @@ -383,23 +386,21 @@ class WidgetDrawer(object): self._prefix ) - full_text = widget.full_text() - if self._prefix: - full_text = u"{}{}".format(self._prefix, full_text) + self._full_text = u"{}{}".format(self._prefix, self._full_text) if self._suffix: - full_text = u"{}{}".format(full_text, self._suffix) + self._full_text = u"{}{}".format(self._full_text, self._suffix) width = self._theme.minwidth(widget) if width: - full_text = full_text.ljust(len(width) + len(self._prefix) + len(self._suffix)) + self._full_text = self._full_text.ljust(len(width) + len(self._prefix) + len(self._suffix)) if self._markup == "pango": - full_text = full_text.replace("&", "&") + self._full_text = self._full_text.replace("&", "&") self._widgets.append({ - u"full_text": full_text, + u"full_text": self._full_text, "color": self._theme.fg(widget), "background": self._theme.bg(widget), "separator_block_width": self._theme.separator_block_width(widget), From 4855b1aa0ac15bb27552694c708afcaa926e1e61 Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 12:09:45 +0200 Subject: [PATCH 034/112] extract add_prefix() method --- bumblebee/output.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 4f5afca..89a0fd1 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -350,6 +350,23 @@ class WidgetDrawer(object): "separator_block_width": self._theme.separator_block_width(widget), }) + def add_prefix(self, widget, padding): + """add prefix to full_text""" + self._prefix = self._theme.prefix(widget, padding) + + if self._markup == "pango": + # add prefix/suffix colors + fg = self._theme.prefix_fg(widget) + bg = self._theme.prefix_bg(widget) + self._prefix = "{}".format( + "foreground='{}'".format(fg) if fg else "", + "background='{}'".format(bg) if bg else "", + self._prefix + ) + + if self._prefix: + self._full_text = u"{}{}".format(self._prefix, self._full_text) + def draw(self, widget, module=None, engine=None): """ Keep the same argument signature as I3BarOutput.draw() @@ -373,21 +390,10 @@ class WidgetDrawer(object): padding = self._theme.padding(widget) - self._prefix = self._theme.prefix(widget, padding) + self.add_prefix(widget, padding) + self._suffix = self._theme.suffix(widget, padding) - if self._markup == "pango": - # add prefix/suffix colors - fg = self._theme.prefix_fg(widget) - bg = self._theme.prefix_bg(widget) - self._prefix = "{}".format( - "foreground='{}'".format(fg) if fg else "", - "background='{}'".format(bg) if bg else "", - self._prefix - ) - - if self._prefix: - self._full_text = u"{}{}".format(self._prefix, self._full_text) if self._suffix: self._full_text = u"{}{}".format(self._full_text, self._suffix) From beff99888bf1b25f2595238f38bbef80c3d39992 Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 12:11:51 +0200 Subject: [PATCH 035/112] extract add_suffix() method --- bumblebee/output.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 89a0fd1..4f8fc6b 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -367,6 +367,13 @@ class WidgetDrawer(object): if self._prefix: self._full_text = u"{}{}".format(self._prefix, self._full_text) + def add_suffix(self, widget, padding): + """add suffix to full_text""" + self._suffix = self._theme.suffix(widget, padding) + + if self._suffix: + self._full_text = u"{}{}".format(self._full_text, self._suffix) + def draw(self, widget, module=None, engine=None): """ Keep the same argument signature as I3BarOutput.draw() @@ -392,10 +399,7 @@ class WidgetDrawer(object): self.add_prefix(widget, padding) - self._suffix = self._theme.suffix(widget, padding) - - if self._suffix: - self._full_text = u"{}{}".format(self._full_text, self._suffix) + self.add_suffix(widget, padding) width = self._theme.minwidth(widget) From 64c675150e8f4e9291ad8056c873dd04993f4e78 Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 12:27:39 +0200 Subject: [PATCH 036/112] extract escape_amp() method --- bumblebee/output.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 4f8fc6b..cded3ed 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -374,6 +374,11 @@ class WidgetDrawer(object): if self._suffix: self._full_text = u"{}{}".format(self._full_text, self._suffix) + def escape_amp(self): + """escape & in full_text, because pango requires it""" + if self._markup == "pango": + self._full_text = self._full_text.replace("&", "&") + def draw(self, widget, module=None, engine=None): """ Keep the same argument signature as I3BarOutput.draw() @@ -406,8 +411,7 @@ class WidgetDrawer(object): if width: self._full_text = self._full_text.ljust(len(width) + len(self._prefix) + len(self._suffix)) - if self._markup == "pango": - self._full_text = self._full_text.replace("&", "&") + self.escape_amp() self._widgets.append({ u"full_text": self._full_text, From ed256a960dd95667e811dc147ee6edb4d09b658f Mon Sep 17 00:00:00 2001 From: me Date: Fri, 31 Jan 2020 12:31:38 +0200 Subject: [PATCH 037/112] extract add_prefix_colors() method --- bumblebee/output.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index cded3ed..b2df265 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -350,10 +350,8 @@ class WidgetDrawer(object): "separator_block_width": self._theme.separator_block_width(widget), }) - def add_prefix(self, widget, padding): - """add prefix to full_text""" - self._prefix = self._theme.prefix(widget, padding) - + def add_prefix_colors(self, widget): + """add custom theme colors for prefix""" if self._markup == "pango": # add prefix/suffix colors fg = self._theme.prefix_fg(widget) @@ -364,6 +362,12 @@ class WidgetDrawer(object): self._prefix ) + def add_prefix(self, widget, padding): + """add prefix to full_text""" + self._prefix = self._theme.prefix(widget, padding) + + self.add_prefix_colors(widget) + if self._prefix: self._full_text = u"{}{}".format(self._prefix, self._full_text) From 7a935790e8b060580c5301f027e3c18aa60e9493 Mon Sep 17 00:00:00 2001 From: nginsburg Date: Fri, 31 Jan 2020 18:19:50 -0500 Subject: [PATCH 038/112] python packaging and allowing for pip install usage --- .gitattributes | 1 + .github/workflows/pythonpublish.yml | 27 + MANIFEST.in | 6 + bumblebee/__about__.py | 12 - bumblebee/__init__.py | 4 + bumblebee/_version.py | 520 ++++++++ setup.cfg | 47 + setup.py | 57 +- versioneer.py | 1822 +++++++++++++++++++++++++++ 9 files changed, 2437 insertions(+), 59 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/pythonpublish.yml create mode 100644 MANIFEST.in delete mode 100644 bumblebee/__about__.py create mode 100644 bumblebee/_version.py create mode 100644 setup.cfg create mode 100644 versioneer.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fd47cad --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +bumblebee/_version.py export-subst diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml new file mode 100644 index 0000000..365c526 --- /dev/null +++ b/.github/workflows/pythonpublish.yml @@ -0,0 +1,27 @@ +--- +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..1ad48b8 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include versioneer.py +include bumblebee/_version.py +include requirements/* +include requirements/modules/* +include themes/* +include themes/icons/* diff --git a/bumblebee/__about__.py b/bumblebee/__about__.py deleted file mode 100644 index 61f7a87..0000000 --- a/bumblebee/__about__.py +++ /dev/null @@ -1,12 +0,0 @@ -__title__ = 'bumblebee-status' -__package_name__ = 'bumblebee_status' -__version__ = '1.10.0' -__description__ = 'a modular, theme-able status line generator for the i3 window manager.' -__author__ = 'tobi-wan-kenobi' -__email__ = 'github@tobi-wan-kenobi.at' -__github__ = 'https://github.com/tobi-wan-kenobi/bumblebee-status' -__docs__ = 'https://github.com/tobi-wan-kenobi/bumblebee-status' -__tracker__ = 'https://github.com/tobi-wan-kenobi/bumblebee-status' -__pypi__ = 'https://pypi.org/project/bumblebee-status/' -__license__ = 'MIT' -__copyright__ = 'Copyright 2016- tobi-wan-kenobi' diff --git a/bumblebee/__init__.py b/bumblebee/__init__.py index e69de29..74f4e66 100644 --- a/bumblebee/__init__.py +++ b/bumblebee/__init__.py @@ -0,0 +1,4 @@ + +from ._version import get_versions +__version__ = get_versions()['version'] +del get_versions diff --git a/bumblebee/_version.py b/bumblebee/_version.py new file mode 100644 index 0000000..78dbc96 --- /dev/null +++ b/bumblebee/_version.py @@ -0,0 +1,520 @@ + +# This file helps to compute a version number in source trees obtained from +# git-archive tarball (such as those provided by githubs download-from-tag +# feature). Distribution tarballs (built by setup.py sdist) and build +# directories (produced by setup.py build) will contain a much shorter file +# that just contains the computed version number. + +# This file is released into the public domain. Generated by +# versioneer-0.18 (https://github.com/warner/python-versioneer) + +"""Git implementation of _version.py.""" + +import errno +import os +import re +import subprocess +import sys + + +def get_keywords(): + """Get the keywords needed to look up the version information.""" + # these strings will be replaced by git during git-archive. + # setup.py/versioneer.py will grep for the variable names, so they must + # each be defined on a line of their own. _version.py will just call + # get_keywords(). + git_refnames = "$Format:%d$" + git_full = "$Format:%H$" + git_date = "$Format:%ci$" + keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} + return keywords + + +class VersioneerConfig: + """Container for Versioneer configuration parameters.""" + + +def get_config(): + """Create, populate and return the VersioneerConfig() object.""" + # these strings are filled in when 'setup.py versioneer' creates + # _version.py + cfg = VersioneerConfig() + cfg.VCS = "git" + cfg.style = "pep440" + cfg.tag_prefix = "" + cfg.parentdir_prefix = "bumblebee" + cfg.versionfile_source = "bumblebee/_version.py" + cfg.verbose = False + return cfg + + +class NotThisMethod(Exception): + """Exception raised if a method is not valid for the current scenario.""" + + +LONG_VERSION_PY = {} +HANDLERS = {} + + +def register_vcs_handler(vcs, method): # decorator + """Decorator to mark a method as the handler for a particular VCS.""" + def decorate(f): + """Store f in HANDLERS[vcs][method].""" + if vcs not in HANDLERS: + HANDLERS[vcs] = {} + HANDLERS[vcs][method] = f + return f + return decorate + + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, + env=None): + """Call the given command(s).""" + assert isinstance(commands, list) + p = None + for c in commands: + try: + dispcmd = str([c] + args) + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %s" % dispcmd) + print(e) + return None, None + else: + if verbose: + print("unable to find command, tried %s" % (commands,)) + return None, None + stdout = p.communicate()[0].strip() + if sys.version_info[0] >= 3: + stdout = stdout.decode() + if p.returncode != 0: + if verbose: + print("unable to run %s (error)" % dispcmd) + print("stdout was %s" % stdout) + return None, p.returncode + return stdout, p.returncode + + +def versions_from_parentdir(parentdir_prefix, root, verbose): + """Try to determine the version from the parent directory name. + + Source tarballs conventionally unpack into a directory that includes both + the project name and a version string. We will also support searching up + two directory levels for an appropriately named parent directory + """ + rootdirs = [] + + for i in range(3): + dirname = os.path.basename(root) + if dirname.startswith(parentdir_prefix): + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None, "date": None} + else: + rootdirs.append(root) + root = os.path.dirname(root) # up a level + + if verbose: + print("Tried directories %s but none started with prefix %s" % + (str(rootdirs), parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") + + +@register_vcs_handler("git", "get_keywords") +def git_get_keywords(versionfile_abs): + """Extract version information from the given file.""" + # the code embedded in _version.py can just fetch the value of these + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} + try: + f = open(versionfile_abs, "r") + for line in f.readlines(): + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) + f.close() + except EnvironmentError: + pass + return keywords + + +@register_vcs_handler("git", "keywords") +def git_versions_from_keywords(keywords, tag_prefix, verbose): + """Get version information from git keywords.""" + if not keywords: + raise NotThisMethod("no keywords at all, weird") + date = keywords.get("date") + if date is not None: + # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant + # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 + # -like" string, which we must then edit to make compliant), because + # it's been around since git-1.5.3, and it's too difficult to + # discover which version we're using, or to work around using an + # older one. + date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + refnames = keywords["refnames"].strip() + if refnames.startswith("$Format"): + if verbose: + print("keywords are unexpanded, not using") + raise NotThisMethod("unexpanded keywords, not a git-archive tarball") + refs = set([r.strip() for r in refnames.strip("()").split(",")]) + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%s', no digits" % ",".join(refs - tags)) + if verbose: + print("likely tags: %s" % ",".join(sorted(tags))) + for ref in sorted(tags): + # sorting will prefer e.g. "2.0" over "2.0rc1" + if ref.startswith(tag_prefix): + r = ref[len(tag_prefix):] + if verbose: + print("picking %s" % r) + return {"version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None, + "date": date} + # no suitable tags, so version is "0+unknown", but full hex is still there + if verbose: + print("no suitable tags, using unknown + full revision id") + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags", "date": None} + + +@register_vcs_handler("git", "pieces_from_vcs") +def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): + """Get version from 'git describe' in the root of the source tree. + + This only gets called if the git-archive 'subst' keywords were *not* + expanded, and _version.py hasn't already been rewritten with a short + version string, meaning we're inside a checked out source tree. + """ + GITS = ["git"] + if sys.platform == "win32": + GITS = ["git.cmd", "git.exe"] + + out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=True) + if rc != 0: + if verbose: + print("Directory %s not under git control" % root) + raise NotThisMethod("'git rev-parse --git-dir' returned error") + + # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] + # if there isn't one, this yields HEX[-dirty] (no NUM) + describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long", + "--match", "%s*" % tag_prefix], + cwd=root) + # --long was added in git-1.5.5 + if describe_out is None: + raise NotThisMethod("'git describe' failed") + describe_out = describe_out.strip() + full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + if full_out is None: + raise NotThisMethod("'git rev-parse' failed") + full_out = full_out.strip() + + pieces = {} + pieces["long"] = full_out + pieces["short"] = full_out[:7] # maybe improved later + pieces["error"] = None + + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] + # TAG might have hyphens. + git_describe = describe_out + + # look for -dirty suffix + dirty = git_describe.endswith("-dirty") + pieces["dirty"] = dirty + if dirty: + git_describe = git_describe[:git_describe.rindex("-dirty")] + + # now we have TAG-NUM-gHEX or HEX + + if "-" in git_describe: + # TAG-NUM-gHEX + mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + if not mo: + # unparseable. Maybe git-describe is misbehaving? + pieces["error"] = ("unable to parse git-describe output: '%s'" + % describe_out) + return pieces + + # tag + full_tag = mo.group(1) + if not full_tag.startswith(tag_prefix): + if verbose: + fmt = "tag '%s' doesn't start with prefix '%s'" + print(fmt % (full_tag, tag_prefix)) + pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" + % (full_tag, tag_prefix)) + return pieces + pieces["closest-tag"] = full_tag[len(tag_prefix):] + + # distance: number of commits since tag + pieces["distance"] = int(mo.group(2)) + + # commit: short hex revision ID + pieces["short"] = mo.group(3) + + else: + # HEX: no tags + pieces["closest-tag"] = None + count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) + pieces["distance"] = int(count_out) # total number of commits + + # commit date: see ISO-8601 comment in git_versions_from_keywords() + date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], + cwd=root)[0].strip() + pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + + return pieces + + +def plus_or_dot(pieces): + """Return a + if we don't already have one, else return a .""" + if "+" in pieces.get("closest-tag", ""): + return "." + return "+" + + +def render_pep440(pieces): + """Build up version string, with post-release "local version identifier". + + Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + + Exceptions: + 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_pre(pieces): + """TAG[.post.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post.devDISTANCE + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += ".post.dev%d" % pieces["distance"] + else: + # exception #1 + rendered = "0.post.dev%d" % pieces["distance"] + return rendered + + +def render_pep440_post(pieces): + """TAG[.postDISTANCE[.dev0]+gHEX] . + + The ".dev0" means dirty. Note that .dev0 sorts backwards + (a dirty tree will appear "older" than the corresponding clean one), + but you shouldn't be releasing software with -dirty anyways. + + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + return rendered + + +def render_pep440_old(pieces): + """TAG[.postDISTANCE[.dev0]] . + + The ".dev0" means dirty. + + Eexceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + return rendered + + +def render_git_describe(pieces): + """TAG[-DISTANCE-gHEX][-dirty]. + + Like 'git describe --tags --dirty --always'. + + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render_git_describe_long(pieces): + """TAG-DISTANCE-gHEX[-dirty]. + + Like 'git describe --tags --dirty --always -long'. + The distance/hash is unconditional. + + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render(pieces, style): + """Render the given version pieces into the requested style.""" + if pieces["error"]: + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"], + "date": None} + + if not style or style == "default": + style = "pep440" # the default + + if style == "pep440": + rendered = render_pep440(pieces) + elif style == "pep440-pre": + rendered = render_pep440_pre(pieces) + elif style == "pep440-post": + rendered = render_pep440_post(pieces) + elif style == "pep440-old": + rendered = render_pep440_old(pieces) + elif style == "git-describe": + rendered = render_git_describe(pieces) + elif style == "git-describe-long": + rendered = render_git_describe_long(pieces) + else: + raise ValueError("unknown style '%s'" % style) + + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None, + "date": pieces.get("date")} + + +def get_versions(): + """Get version information or return default if unable to do so.""" + # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have + # __file__, we can work backwards from there to the root. Some + # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which + # case we can only use expanded keywords. + + cfg = get_config() + verbose = cfg.verbose + + try: + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, + verbose) + except NotThisMethod: + pass + + try: + root = os.path.realpath(__file__) + # versionfile_source is the relative path from the top of the source + # tree (where the .git directory might live) to this file. Invert + # this to find the root from __file__. + for i in cfg.versionfile_source.split('/'): + root = os.path.dirname(root) + except NameError: + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree", + "date": None} + + try: + pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) + return render(pieces, cfg.style) + except NotThisMethod: + pass + + try: + if cfg.parentdir_prefix: + return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) + except NotThisMethod: + pass + + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to compute version", "date": None} diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..8763f86 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,47 @@ +[metadata] +name = bumblebee-status +author = tobi-wan-kenobi +author_email = github@tobi-wan-kenobi.at +url = https://github.com/tobi-wan-kenobi/bumblebee-status +download_url = 'https://pypi.org/project/bumblebee-status/' +project_urls = + Documentation = https://github.com/tobi-wan-kenobi/bumblebee-status + Code = https://github.com/tobi-wan-kenobi/bumblebee-status + Issue tracker = 'https://github.com/tobi-wan-kenobi/bumblebee-status' + +description = a modular, theme-able status line generator for the i3 window manager. +long_description = file: README.md +long_description_content_type = text/markdown +license = MIT +classifiers = + Development Status :: 3 - Alpha + License :: OSI Approved :: MIT License + Intended Audience :: Developers + Programming Language :: Python + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Topic :: Software Development :: Libraries + Topic :: Software Development :: Internationalization + Topic :: Utilities +keywords = bumblebee-status + +[options] +include_package_data = True +allow-all-external = yes +trusted-host = + gitlab.* + bitbucket.org + github.com +packages = find: +scripts = + ./bumblebee-status + +[versioneer] +VCS = git +style = pep440 +versionfile_source = bumblebee/_version.py +versionfile_build = bumblebee/_version.py +tag_prefix = +parentdir_prefix = bumblebee diff --git a/setup.py b/setup.py index eaedaa0..ed950ff 100755 --- a/setup.py +++ b/setup.py @@ -1,20 +1,17 @@ #!/usr/bin/env python +"""Setup file for bumbleestatus bar to allow pip install of full package""" # -*- coding: utf8 - *- -import sys - -from setuptools import setup, Extension - -about = {} -with open("bumblebee/__about__.py") as fp: - exec(fp.read(), about) +from setuptools import setup +import versioneer with open('requirements/base.txt') as f: - install_reqs = [line for line in f.read().split('\n') if line] + INSTALL_REQS = [line for line in f.read().split('\n') if line] # Module packages def read_module(filename): - with open('requirements/modules/{}.txt'.format(filename)) as f: - return [line for line in f.read().split('\n') if line] + """Read each in a module's requirements and parse it for extras""" + with open('requirements/modules/{}.txt'.format(filename)) as fname: + return [rline for rline in fname.read().split('\n') if rline] EXTRAS_REQUIREMENTS_MAP = { "battery-upower": read_module("battery_upower_reqs"), @@ -45,44 +42,10 @@ EXTRAS_REQUIREMENTS_MAP = { "yubikey": read_module("yubikey"), } -if sys.version_info[0] > 2: - readme = open('README.md', encoding='utf-8').read() -else: - readme = open('README.md').read() - setup( - name=about['__title__'], - version=about['__version__'], - url=about['__github__'], - download_url=about['__pypi__'], - project_urls={ - 'Documentation': about['__docs__'], - 'Code': about['__github__'], - 'Issue tracker': about['__tracker__'], - }, - license=about['__license__'], - author=about['__author__'], - author_email=about['__email__'], - description=about['__description__'], - long_description=readme, - long_description_content_type='text/markdown', - packages=['bumblebee'], - include_package_data=True, - install_requires=install_reqs, + install_requires=INSTALL_REQS, extras_require=EXTRAS_REQUIREMENTS_MAP, + version=versioneer.get_version(), + cmdclass=versioneer.get_cmdclass(), zip_safe=False, - keywords=about['__title__'], - classifiers=[ - 'Development Status :: 3 - Alpha', - "License :: OSI Approved :: MIT License", - 'Intended Audience :: Developers', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - "Topic :: Software Development :: Libraries", - "Topic :: Software Development :: Internationalization", - "Topic :: Utilities", - ], ) diff --git a/versioneer.py b/versioneer.py new file mode 100644 index 0000000..64fea1c --- /dev/null +++ b/versioneer.py @@ -0,0 +1,1822 @@ + +# Version: 0.18 + +"""The Versioneer - like a rocketeer, but for versions. + +The Versioneer +============== + +* like a rocketeer, but for versions! +* https://github.com/warner/python-versioneer +* Brian Warner +* License: Public Domain +* Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy +* [![Latest Version] +(https://pypip.in/version/versioneer/badge.svg?style=flat) +](https://pypi.python.org/pypi/versioneer/) +* [![Build Status] +(https://travis-ci.org/warner/python-versioneer.png?branch=master) +](https://travis-ci.org/warner/python-versioneer) + +This is a tool for managing a recorded version number in distutils-based +python projects. The goal is to remove the tedious and error-prone "update +the embedded version string" step from your release process. Making a new +release should be as easy as recording a new tag in your version-control +system, and maybe making new tarballs. + + +## Quick Install + +* `pip install versioneer` to somewhere to your $PATH +* add a `[versioneer]` section to your setup.cfg (see below) +* run `versioneer install` in your source tree, commit the results + +## Version Identifiers + +Source trees come from a variety of places: + +* a version-control system checkout (mostly used by developers) +* a nightly tarball, produced by build automation +* a snapshot tarball, produced by a web-based VCS browser, like github's + "tarball from tag" feature +* a release tarball, produced by "setup.py sdist", distributed through PyPI + +Within each source tree, the version identifier (either a string or a number, +this tool is format-agnostic) can come from a variety of places: + +* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows + about recent "tags" and an absolute revision-id +* the name of the directory into which the tarball was unpacked +* an expanded VCS keyword ($Id$, etc) +* a `_version.py` created by some earlier build step + +For released software, the version identifier is closely related to a VCS +tag. Some projects use tag names that include more than just the version +string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool +needs to strip the tag prefix to extract the version identifier. For +unreleased software (between tags), the version identifier should provide +enough information to help developers recreate the same tree, while also +giving them an idea of roughly how old the tree is (after version 1.2, before +version 1.3). Many VCS systems can report a description that captures this, +for example `git describe --tags --dirty --always` reports things like +"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the +0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has +uncommitted changes. + +The version identifier is used for multiple purposes: + +* to allow the module to self-identify its version: `myproject.__version__` +* to choose a name and prefix for a 'setup.py sdist' tarball + +## Theory of Operation + +Versioneer works by adding a special `_version.py` file into your source +tree, where your `__init__.py` can import it. This `_version.py` knows how to +dynamically ask the VCS tool for version information at import time. + +`_version.py` also contains `$Revision$` markers, and the installation +process marks `_version.py` to have this marker rewritten with a tag name +during the `git archive` command. As a result, generated tarballs will +contain enough information to get the proper version. + +To allow `setup.py` to compute a version too, a `versioneer.py` is added to +the top level of your source tree, next to `setup.py` and the `setup.cfg` +that configures it. This overrides several distutils/setuptools commands to +compute the version when invoked, and changes `setup.py build` and `setup.py +sdist` to replace `_version.py` with a small static file that contains just +the generated version data. + +## Installation + +See [INSTALL.md](./INSTALL.md) for detailed installation instructions. + +## Version-String Flavors + +Code which uses Versioneer can learn about its version string at runtime by +importing `_version` from your main `__init__.py` file and running the +`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can +import the top-level `versioneer.py` and run `get_versions()`. + +Both functions return a dictionary with different flavors of version +information: + +* `['version']`: A condensed version string, rendered using the selected + style. This is the most commonly used value for the project's version + string. The default "pep440" style yields strings like `0.11`, + `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section + below for alternative styles. + +* `['full-revisionid']`: detailed revision identifier. For Git, this is the + full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". + +* `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the + commit date in ISO 8601 format. This will be None if the date is not + available. + +* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that + this is only accurate if run in a VCS checkout, otherwise it is likely to + be False or None + +* `['error']`: if the version string could not be computed, this will be set + to a string describing the problem, otherwise it will be None. It may be + useful to throw an exception in setup.py if this is set, to avoid e.g. + creating tarballs with a version string of "unknown". + +Some variants are more useful than others. Including `full-revisionid` in a +bug report should allow developers to reconstruct the exact code being tested +(or indicate the presence of local changes that should be shared with the +developers). `version` is suitable for display in an "about" box or a CLI +`--version` output: it can be easily compared against release notes and lists +of bugs fixed in various releases. + +The installer adds the following text to your `__init__.py` to place a basic +version in `YOURPROJECT.__version__`: + + from ._version import get_versions + __version__ = get_versions()['version'] + del get_versions + +## Styles + +The setup.cfg `style=` configuration controls how the VCS information is +rendered into a version string. + +The default style, "pep440", produces a PEP440-compliant string, equal to the +un-prefixed tag name for actual releases, and containing an additional "local +version" section with more detail for in-between builds. For Git, this is +TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags +--dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the +tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and +that this commit is two revisions ("+2") beyond the "0.11" tag. For released +software (exactly equal to a known tag), the identifier will only contain the +stripped tag, e.g. "0.11". + +Other styles are available. See [details.md](details.md) in the Versioneer +source tree for descriptions. + +## Debugging + +Versioneer tries to avoid fatal errors: if something goes wrong, it will tend +to return a version of "0+unknown". To investigate the problem, run `setup.py +version`, which will run the version-lookup code in a verbose mode, and will +display the full contents of `get_versions()` (including the `error` string, +which may help identify what went wrong). + +## Known Limitations + +Some situations are known to cause problems for Versioneer. This details the +most significant ones. More can be found on Github +[issues page](https://github.com/warner/python-versioneer/issues). + +### Subprojects + +Versioneer has limited support for source trees in which `setup.py` is not in +the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are +two common reasons why `setup.py` might not be in the root: + +* Source trees which contain multiple subprojects, such as + [Buildbot](https://github.com/buildbot/buildbot), which contains both + "master" and "slave" subprojects, each with their own `setup.py`, + `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI + distributions (and upload multiple independently-installable tarballs). +* Source trees whose main purpose is to contain a C library, but which also + provide bindings to Python (and perhaps other langauges) in subdirectories. + +Versioneer will look for `.git` in parent directories, and most operations +should get the right version string. However `pip` and `setuptools` have bugs +and implementation details which frequently cause `pip install .` from a +subproject directory to fail to find a correct version string (so it usually +defaults to `0+unknown`). + +`pip install --editable .` should work correctly. `setup.py install` might +work too. + +Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in +some later version. + +[Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking +this issue. The discussion in +[PR #61](https://github.com/warner/python-versioneer/pull/61) describes the +issue from the Versioneer side in more detail. +[pip PR#3176](https://github.com/pypa/pip/pull/3176) and +[pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve +pip to let Versioneer work correctly. + +Versioneer-0.16 and earlier only looked for a `.git` directory next to the +`setup.cfg`, so subprojects were completely unsupported with those releases. + +### Editable installs with setuptools <= 18.5 + +`setup.py develop` and `pip install --editable .` allow you to install a +project into a virtualenv once, then continue editing the source code (and +test) without re-installing after every change. + +"Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a +convenient way to specify executable scripts that should be installed along +with the python package. + +These both work as expected when using modern setuptools. When using +setuptools-18.5 or earlier, however, certain operations will cause +`pkg_resources.DistributionNotFound` errors when running the entrypoint +script, which must be resolved by re-installing the package. This happens +when the install happens with one version, then the egg_info data is +regenerated while a different version is checked out. Many setup.py commands +cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into +a different virtualenv), so this can be surprising. + +[Bug #83](https://github.com/warner/python-versioneer/issues/83) describes +this one, but upgrading to a newer version of setuptools should probably +resolve it. + +### Unicode version strings + +While Versioneer works (and is continually tested) with both Python 2 and +Python 3, it is not entirely consistent with bytes-vs-unicode distinctions. +Newer releases probably generate unicode version strings on py2. It's not +clear that this is wrong, but it may be surprising for applications when then +write these strings to a network connection or include them in bytes-oriented +APIs like cryptographic checksums. + +[Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates +this question. + + +## Updating Versioneer + +To upgrade your project to a new release of Versioneer, do the following: + +* install the new Versioneer (`pip install -U versioneer` or equivalent) +* edit `setup.cfg`, if necessary, to include any new configuration settings + indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. +* re-run `versioneer install` in your source tree, to replace + `SRC/_version.py` +* commit any changed files + +## Future Directions + +This tool is designed to make it easily extended to other version-control +systems: all VCS-specific components are in separate directories like +src/git/ . The top-level `versioneer.py` script is assembled from these +components by running make-versioneer.py . In the future, make-versioneer.py +will take a VCS name as an argument, and will construct a version of +`versioneer.py` that is specific to the given VCS. It might also take the +configuration arguments that are currently provided manually during +installation by editing setup.py . Alternatively, it might go the other +direction and include code from all supported VCS systems, reducing the +number of intermediate scripts. + + +## License + +To make Versioneer easier to embed, all its code is dedicated to the public +domain. The `_version.py` that it creates is also in the public domain. +Specifically, both are released under the Creative Commons "Public Domain +Dedication" license (CC0-1.0), as described in +https://creativecommons.org/publicdomain/zero/1.0/ . + +""" + +from __future__ import print_function +try: + import configparser +except ImportError: + import ConfigParser as configparser +import errno +import json +import os +import re +import subprocess +import sys + + +class VersioneerConfig: + """Container for Versioneer configuration parameters.""" + + +def get_root(): + """Get the project root directory. + + We require that all commands are run from the project root, i.e. the + directory that contains setup.py, setup.cfg, and versioneer.py . + """ + root = os.path.realpath(os.path.abspath(os.getcwd())) + setup_py = os.path.join(root, "setup.py") + versioneer_py = os.path.join(root, "versioneer.py") + if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): + # allow 'python path/to/setup.py COMMAND' + root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) + setup_py = os.path.join(root, "setup.py") + versioneer_py = os.path.join(root, "versioneer.py") + if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): + err = ("Versioneer was unable to run the project root directory. " + "Versioneer requires setup.py to be executed from " + "its immediate directory (like 'python setup.py COMMAND'), " + "or in a way that lets it use sys.argv[0] to find the root " + "(like 'python path/to/setup.py COMMAND').") + raise VersioneerBadRootError(err) + try: + # Certain runtime workflows (setup.py install/develop in a setuptools + # tree) execute all dependencies in a single python process, so + # "versioneer" may be imported multiple times, and python's shared + # module-import table will cache the first one. So we can't use + # os.path.dirname(__file__), as that will find whichever + # versioneer.py was first imported, even in later projects. + me = os.path.realpath(os.path.abspath(__file__)) + me_dir = os.path.normcase(os.path.splitext(me)[0]) + vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) + if me_dir != vsr_dir: + print("Warning: build in %s is using versioneer.py from %s" + % (os.path.dirname(me), versioneer_py)) + except NameError: + pass + return root + + +def get_config_from_root(root): + """Read the project setup.cfg file to determine Versioneer config.""" + # This might raise EnvironmentError (if setup.cfg is missing), or + # configparser.NoSectionError (if it lacks a [versioneer] section), or + # configparser.NoOptionError (if it lacks "VCS="). See the docstring at + # the top of versioneer.py for instructions on writing your setup.cfg . + setup_cfg = os.path.join(root, "setup.cfg") + parser = configparser.SafeConfigParser() + with open(setup_cfg, "r") as f: + parser.readfp(f) + VCS = parser.get("versioneer", "VCS") # mandatory + + def get(parser, name): + if parser.has_option("versioneer", name): + return parser.get("versioneer", name) + return None + cfg = VersioneerConfig() + cfg.VCS = VCS + cfg.style = get(parser, "style") or "" + cfg.versionfile_source = get(parser, "versionfile_source") + cfg.versionfile_build = get(parser, "versionfile_build") + cfg.tag_prefix = get(parser, "tag_prefix") + if cfg.tag_prefix in ("''", '""'): + cfg.tag_prefix = "" + cfg.parentdir_prefix = get(parser, "parentdir_prefix") + cfg.verbose = get(parser, "verbose") + return cfg + + +class NotThisMethod(Exception): + """Exception raised if a method is not valid for the current scenario.""" + + +# these dictionaries contain VCS-specific tools +LONG_VERSION_PY = {} +HANDLERS = {} + + +def register_vcs_handler(vcs, method): # decorator + """Decorator to mark a method as the handler for a particular VCS.""" + def decorate(f): + """Store f in HANDLERS[vcs][method].""" + if vcs not in HANDLERS: + HANDLERS[vcs] = {} + HANDLERS[vcs][method] = f + return f + return decorate + + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, + env=None): + """Call the given command(s).""" + assert isinstance(commands, list) + p = None + for c in commands: + try: + dispcmd = str([c] + args) + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %s" % dispcmd) + print(e) + return None, None + else: + if verbose: + print("unable to find command, tried %s" % (commands,)) + return None, None + stdout = p.communicate()[0].strip() + if sys.version_info[0] >= 3: + stdout = stdout.decode() + if p.returncode != 0: + if verbose: + print("unable to run %s (error)" % dispcmd) + print("stdout was %s" % stdout) + return None, p.returncode + return stdout, p.returncode + + +LONG_VERSION_PY['git'] = ''' +# This file helps to compute a version number in source trees obtained from +# git-archive tarball (such as those provided by githubs download-from-tag +# feature). Distribution tarballs (built by setup.py sdist) and build +# directories (produced by setup.py build) will contain a much shorter file +# that just contains the computed version number. + +# This file is released into the public domain. Generated by +# versioneer-0.18 (https://github.com/warner/python-versioneer) + +"""Git implementation of _version.py.""" + +import errno +import os +import re +import subprocess +import sys + + +def get_keywords(): + """Get the keywords needed to look up the version information.""" + # these strings will be replaced by git during git-archive. + # setup.py/versioneer.py will grep for the variable names, so they must + # each be defined on a line of their own. _version.py will just call + # get_keywords(). + git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" + git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" + git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s" + keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} + return keywords + + +class VersioneerConfig: + """Container for Versioneer configuration parameters.""" + + +def get_config(): + """Create, populate and return the VersioneerConfig() object.""" + # these strings are filled in when 'setup.py versioneer' creates + # _version.py + cfg = VersioneerConfig() + cfg.VCS = "git" + cfg.style = "%(STYLE)s" + cfg.tag_prefix = "%(TAG_PREFIX)s" + cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" + cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" + cfg.verbose = False + return cfg + + +class NotThisMethod(Exception): + """Exception raised if a method is not valid for the current scenario.""" + + +LONG_VERSION_PY = {} +HANDLERS = {} + + +def register_vcs_handler(vcs, method): # decorator + """Decorator to mark a method as the handler for a particular VCS.""" + def decorate(f): + """Store f in HANDLERS[vcs][method].""" + if vcs not in HANDLERS: + HANDLERS[vcs] = {} + HANDLERS[vcs][method] = f + return f + return decorate + + +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, + env=None): + """Call the given command(s).""" + assert isinstance(commands, list) + p = None + for c in commands: + try: + dispcmd = str([c] + args) + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen([c] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None)) + break + except EnvironmentError: + e = sys.exc_info()[1] + if e.errno == errno.ENOENT: + continue + if verbose: + print("unable to run %%s" %% dispcmd) + print(e) + return None, None + else: + if verbose: + print("unable to find command, tried %%s" %% (commands,)) + return None, None + stdout = p.communicate()[0].strip() + if sys.version_info[0] >= 3: + stdout = stdout.decode() + if p.returncode != 0: + if verbose: + print("unable to run %%s (error)" %% dispcmd) + print("stdout was %%s" %% stdout) + return None, p.returncode + return stdout, p.returncode + + +def versions_from_parentdir(parentdir_prefix, root, verbose): + """Try to determine the version from the parent directory name. + + Source tarballs conventionally unpack into a directory that includes both + the project name and a version string. We will also support searching up + two directory levels for an appropriately named parent directory + """ + rootdirs = [] + + for i in range(3): + dirname = os.path.basename(root) + if dirname.startswith(parentdir_prefix): + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None, "date": None} + else: + rootdirs.append(root) + root = os.path.dirname(root) # up a level + + if verbose: + print("Tried directories %%s but none started with prefix %%s" %% + (str(rootdirs), parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") + + +@register_vcs_handler("git", "get_keywords") +def git_get_keywords(versionfile_abs): + """Extract version information from the given file.""" + # the code embedded in _version.py can just fetch the value of these + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} + try: + f = open(versionfile_abs, "r") + for line in f.readlines(): + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) + f.close() + except EnvironmentError: + pass + return keywords + + +@register_vcs_handler("git", "keywords") +def git_versions_from_keywords(keywords, tag_prefix, verbose): + """Get version information from git keywords.""" + if not keywords: + raise NotThisMethod("no keywords at all, weird") + date = keywords.get("date") + if date is not None: + # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant + # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 + # -like" string, which we must then edit to make compliant), because + # it's been around since git-1.5.3, and it's too difficult to + # discover which version we're using, or to work around using an + # older one. + date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + refnames = keywords["refnames"].strip() + if refnames.startswith("$Format"): + if verbose: + print("keywords are unexpanded, not using") + raise NotThisMethod("unexpanded keywords, not a git-archive tarball") + refs = set([r.strip() for r in refnames.strip("()").split(",")]) + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %%d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%%s', no digits" %% ",".join(refs - tags)) + if verbose: + print("likely tags: %%s" %% ",".join(sorted(tags))) + for ref in sorted(tags): + # sorting will prefer e.g. "2.0" over "2.0rc1" + if ref.startswith(tag_prefix): + r = ref[len(tag_prefix):] + if verbose: + print("picking %%s" %% r) + return {"version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None, + "date": date} + # no suitable tags, so version is "0+unknown", but full hex is still there + if verbose: + print("no suitable tags, using unknown + full revision id") + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags", "date": None} + + +@register_vcs_handler("git", "pieces_from_vcs") +def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): + """Get version from 'git describe' in the root of the source tree. + + This only gets called if the git-archive 'subst' keywords were *not* + expanded, and _version.py hasn't already been rewritten with a short + version string, meaning we're inside a checked out source tree. + """ + GITS = ["git"] + if sys.platform == "win32": + GITS = ["git.cmd", "git.exe"] + + out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=True) + if rc != 0: + if verbose: + print("Directory %%s not under git control" %% root) + raise NotThisMethod("'git rev-parse --git-dir' returned error") + + # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] + # if there isn't one, this yields HEX[-dirty] (no NUM) + describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long", + "--match", "%%s*" %% tag_prefix], + cwd=root) + # --long was added in git-1.5.5 + if describe_out is None: + raise NotThisMethod("'git describe' failed") + describe_out = describe_out.strip() + full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + if full_out is None: + raise NotThisMethod("'git rev-parse' failed") + full_out = full_out.strip() + + pieces = {} + pieces["long"] = full_out + pieces["short"] = full_out[:7] # maybe improved later + pieces["error"] = None + + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] + # TAG might have hyphens. + git_describe = describe_out + + # look for -dirty suffix + dirty = git_describe.endswith("-dirty") + pieces["dirty"] = dirty + if dirty: + git_describe = git_describe[:git_describe.rindex("-dirty")] + + # now we have TAG-NUM-gHEX or HEX + + if "-" in git_describe: + # TAG-NUM-gHEX + mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + if not mo: + # unparseable. Maybe git-describe is misbehaving? + pieces["error"] = ("unable to parse git-describe output: '%%s'" + %% describe_out) + return pieces + + # tag + full_tag = mo.group(1) + if not full_tag.startswith(tag_prefix): + if verbose: + fmt = "tag '%%s' doesn't start with prefix '%%s'" + print(fmt %% (full_tag, tag_prefix)) + pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" + %% (full_tag, tag_prefix)) + return pieces + pieces["closest-tag"] = full_tag[len(tag_prefix):] + + # distance: number of commits since tag + pieces["distance"] = int(mo.group(2)) + + # commit: short hex revision ID + pieces["short"] = mo.group(3) + + else: + # HEX: no tags + pieces["closest-tag"] = None + count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) + pieces["distance"] = int(count_out) # total number of commits + + # commit date: see ISO-8601 comment in git_versions_from_keywords() + date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"], + cwd=root)[0].strip() + pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + + return pieces + + +def plus_or_dot(pieces): + """Return a + if we don't already have one, else return a .""" + if "+" in pieces.get("closest-tag", ""): + return "." + return "+" + + +def render_pep440(pieces): + """Build up version string, with post-release "local version identifier". + + Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + + Exceptions: + 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += plus_or_dot(pieces) + rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_pre(pieces): + """TAG[.post.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post.devDISTANCE + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += ".post.dev%%d" %% pieces["distance"] + else: + # exception #1 + rendered = "0.post.dev%%d" %% pieces["distance"] + return rendered + + +def render_pep440_post(pieces): + """TAG[.postDISTANCE[.dev0]+gHEX] . + + The ".dev0" means dirty. Note that .dev0 sorts backwards + (a dirty tree will appear "older" than the corresponding clean one), + but you shouldn't be releasing software with -dirty anyways. + + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%%d" %% pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%%s" %% pieces["short"] + else: + # exception #1 + rendered = "0.post%%d" %% pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += "+g%%s" %% pieces["short"] + return rendered + + +def render_pep440_old(pieces): + """TAG[.postDISTANCE[.dev0]] . + + The ".dev0" means dirty. + + Eexceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%%d" %% pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + else: + # exception #1 + rendered = "0.post%%d" %% pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + return rendered + + +def render_git_describe(pieces): + """TAG[-DISTANCE-gHEX][-dirty]. + + Like 'git describe --tags --dirty --always'. + + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render_git_describe_long(pieces): + """TAG-DISTANCE-gHEX[-dirty]. + + Like 'git describe --tags --dirty --always -long'. + The distance/hash is unconditional. + + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render(pieces, style): + """Render the given version pieces into the requested style.""" + if pieces["error"]: + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"], + "date": None} + + if not style or style == "default": + style = "pep440" # the default + + if style == "pep440": + rendered = render_pep440(pieces) + elif style == "pep440-pre": + rendered = render_pep440_pre(pieces) + elif style == "pep440-post": + rendered = render_pep440_post(pieces) + elif style == "pep440-old": + rendered = render_pep440_old(pieces) + elif style == "git-describe": + rendered = render_git_describe(pieces) + elif style == "git-describe-long": + rendered = render_git_describe_long(pieces) + else: + raise ValueError("unknown style '%%s'" %% style) + + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None, + "date": pieces.get("date")} + + +def get_versions(): + """Get version information or return default if unable to do so.""" + # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have + # __file__, we can work backwards from there to the root. Some + # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which + # case we can only use expanded keywords. + + cfg = get_config() + verbose = cfg.verbose + + try: + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, + verbose) + except NotThisMethod: + pass + + try: + root = os.path.realpath(__file__) + # versionfile_source is the relative path from the top of the source + # tree (where the .git directory might live) to this file. Invert + # this to find the root from __file__. + for i in cfg.versionfile_source.split('/'): + root = os.path.dirname(root) + except NameError: + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree", + "date": None} + + try: + pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) + return render(pieces, cfg.style) + except NotThisMethod: + pass + + try: + if cfg.parentdir_prefix: + return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) + except NotThisMethod: + pass + + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to compute version", "date": None} +''' + + +@register_vcs_handler("git", "get_keywords") +def git_get_keywords(versionfile_abs): + """Extract version information from the given file.""" + # the code embedded in _version.py can just fetch the value of these + # keywords. When used from setup.py, we don't want to import _version.py, + # so we do it with a regexp instead. This function is not used from + # _version.py. + keywords = {} + try: + f = open(versionfile_abs, "r") + for line in f.readlines(): + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + keywords["date"] = mo.group(1) + f.close() + except EnvironmentError: + pass + return keywords + + +@register_vcs_handler("git", "keywords") +def git_versions_from_keywords(keywords, tag_prefix, verbose): + """Get version information from git keywords.""" + if not keywords: + raise NotThisMethod("no keywords at all, weird") + date = keywords.get("date") + if date is not None: + # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant + # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 + # -like" string, which we must then edit to make compliant), because + # it's been around since git-1.5.3, and it's too difficult to + # discover which version we're using, or to work around using an + # older one. + date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + refnames = keywords["refnames"].strip() + if refnames.startswith("$Format"): + if verbose: + print("keywords are unexpanded, not using") + raise NotThisMethod("unexpanded keywords, not a git-archive tarball") + refs = set([r.strip() for r in refnames.strip("()").split(",")]) + # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of + # just "foo-1.0". If we see a "tag: " prefix, prefer those. + TAG = "tag: " + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + if not tags: + # Either we're using git < 1.8.3, or there really are no tags. We use + # a heuristic: assume all version tags have a digit. The old git %d + # expansion behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us distinguish + # between branches and tags. By ignoring refnames without digits, we + # filter out many common branch names like "release" and + # "stabilization", as well as "HEAD" and "master". + tags = set([r for r in refs if re.search(r'\d', r)]) + if verbose: + print("discarding '%s', no digits" % ",".join(refs - tags)) + if verbose: + print("likely tags: %s" % ",".join(sorted(tags))) + for ref in sorted(tags): + # sorting will prefer e.g. "2.0" over "2.0rc1" + if ref.startswith(tag_prefix): + r = ref[len(tag_prefix):] + if verbose: + print("picking %s" % r) + return {"version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None, + "date": date} + # no suitable tags, so version is "0+unknown", but full hex is still there + if verbose: + print("no suitable tags, using unknown + full revision id") + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags", "date": None} + + +@register_vcs_handler("git", "pieces_from_vcs") +def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): + """Get version from 'git describe' in the root of the source tree. + + This only gets called if the git-archive 'subst' keywords were *not* + expanded, and _version.py hasn't already been rewritten with a short + version string, meaning we're inside a checked out source tree. + """ + GITS = ["git"] + if sys.platform == "win32": + GITS = ["git.cmd", "git.exe"] + + out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=True) + if rc != 0: + if verbose: + print("Directory %s not under git control" % root) + raise NotThisMethod("'git rev-parse --git-dir' returned error") + + # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] + # if there isn't one, this yields HEX[-dirty] (no NUM) + describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", + "--always", "--long", + "--match", "%s*" % tag_prefix], + cwd=root) + # --long was added in git-1.5.5 + if describe_out is None: + raise NotThisMethod("'git describe' failed") + describe_out = describe_out.strip() + full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) + if full_out is None: + raise NotThisMethod("'git rev-parse' failed") + full_out = full_out.strip() + + pieces = {} + pieces["long"] = full_out + pieces["short"] = full_out[:7] # maybe improved later + pieces["error"] = None + + # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] + # TAG might have hyphens. + git_describe = describe_out + + # look for -dirty suffix + dirty = git_describe.endswith("-dirty") + pieces["dirty"] = dirty + if dirty: + git_describe = git_describe[:git_describe.rindex("-dirty")] + + # now we have TAG-NUM-gHEX or HEX + + if "-" in git_describe: + # TAG-NUM-gHEX + mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + if not mo: + # unparseable. Maybe git-describe is misbehaving? + pieces["error"] = ("unable to parse git-describe output: '%s'" + % describe_out) + return pieces + + # tag + full_tag = mo.group(1) + if not full_tag.startswith(tag_prefix): + if verbose: + fmt = "tag '%s' doesn't start with prefix '%s'" + print(fmt % (full_tag, tag_prefix)) + pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" + % (full_tag, tag_prefix)) + return pieces + pieces["closest-tag"] = full_tag[len(tag_prefix):] + + # distance: number of commits since tag + pieces["distance"] = int(mo.group(2)) + + # commit: short hex revision ID + pieces["short"] = mo.group(3) + + else: + # HEX: no tags + pieces["closest-tag"] = None + count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], + cwd=root) + pieces["distance"] = int(count_out) # total number of commits + + # commit date: see ISO-8601 comment in git_versions_from_keywords() + date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], + cwd=root)[0].strip() + pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + + return pieces + + +def do_vcs_install(manifest_in, versionfile_source, ipy): + """Git-specific installation logic for Versioneer. + + For Git, this means creating/changing .gitattributes to mark _version.py + for export-subst keyword substitution. + """ + GITS = ["git"] + if sys.platform == "win32": + GITS = ["git.cmd", "git.exe"] + files = [manifest_in, versionfile_source] + if ipy: + files.append(ipy) + try: + me = __file__ + if me.endswith(".pyc") or me.endswith(".pyo"): + me = os.path.splitext(me)[0] + ".py" + versioneer_file = os.path.relpath(me) + except NameError: + versioneer_file = "versioneer.py" + files.append(versioneer_file) + present = False + try: + f = open(".gitattributes", "r") + for line in f.readlines(): + if line.strip().startswith(versionfile_source): + if "export-subst" in line.strip().split()[1:]: + present = True + f.close() + except EnvironmentError: + pass + if not present: + f = open(".gitattributes", "a+") + f.write("%s export-subst\n" % versionfile_source) + f.close() + files.append(".gitattributes") + run_command(GITS, ["add", "--"] + files) + + +def versions_from_parentdir(parentdir_prefix, root, verbose): + """Try to determine the version from the parent directory name. + + Source tarballs conventionally unpack into a directory that includes both + the project name and a version string. We will also support searching up + two directory levels for an appropriately named parent directory + """ + rootdirs = [] + + for i in range(3): + dirname = os.path.basename(root) + if dirname.startswith(parentdir_prefix): + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None, "date": None} + else: + rootdirs.append(root) + root = os.path.dirname(root) # up a level + + if verbose: + print("Tried directories %s but none started with prefix %s" % + (str(rootdirs), parentdir_prefix)) + raise NotThisMethod("rootdir doesn't start with parentdir_prefix") + + +SHORT_VERSION_PY = """ +# This file was generated by 'versioneer.py' (0.18) from +# revision-control system data, or from the parent directory name of an +# unpacked source archive. Distribution tarballs contain a pre-generated copy +# of this file. + +import json + +version_json = ''' +%s +''' # END VERSION_JSON + + +def get_versions(): + return json.loads(version_json) +""" + + +def versions_from_file(filename): + """Try to determine the version from _version.py if present.""" + try: + with open(filename) as f: + contents = f.read() + except EnvironmentError: + raise NotThisMethod("unable to read _version.py") + mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", + contents, re.M | re.S) + if not mo: + mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", + contents, re.M | re.S) + if not mo: + raise NotThisMethod("no version_json in _version.py") + return json.loads(mo.group(1)) + + +def write_to_version_file(filename, versions): + """Write the given version number to the given _version.py file.""" + os.unlink(filename) + contents = json.dumps(versions, sort_keys=True, + indent=1, separators=(",", ": ")) + with open(filename, "w") as f: + f.write(SHORT_VERSION_PY % contents) + + print("set %s to '%s'" % (filename, versions["version"])) + + +def plus_or_dot(pieces): + """Return a + if we don't already have one, else return a .""" + if "+" in pieces.get("closest-tag", ""): + return "." + return "+" + + +def render_pep440(pieces): + """Build up version string, with post-release "local version identifier". + + Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you + get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty + + Exceptions: + 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += plus_or_dot(pieces) + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + else: + # exception #1 + rendered = "0+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" + return rendered + + +def render_pep440_pre(pieces): + """TAG[.post.devDISTANCE] -- No -dirty. + + Exceptions: + 1: no tags. 0.post.devDISTANCE + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += ".post.dev%d" % pieces["distance"] + else: + # exception #1 + rendered = "0.post.dev%d" % pieces["distance"] + return rendered + + +def render_pep440_post(pieces): + """TAG[.postDISTANCE[.dev0]+gHEX] . + + The ".dev0" means dirty. Note that .dev0 sorts backwards + (a dirty tree will appear "older" than the corresponding clean one), + but you shouldn't be releasing software with -dirty anyways. + + Exceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += plus_or_dot(pieces) + rendered += "g%s" % pieces["short"] + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + return rendered + + +def render_pep440_old(pieces): + """TAG[.postDISTANCE[.dev0]] . + + The ".dev0" means dirty. + + Eexceptions: + 1: no tags. 0.postDISTANCE[.dev0] + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + else: + # exception #1 + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + return rendered + + +def render_git_describe(pieces): + """TAG[-DISTANCE-gHEX][-dirty]. + + Like 'git describe --tags --dirty --always'. + + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render_git_describe_long(pieces): + """TAG-DISTANCE-gHEX[-dirty]. + + Like 'git describe --tags --dirty --always -long'. + The distance/hash is unconditional. + + Exceptions: + 1: no tags. HEX[-dirty] (note: no 'g' prefix) + """ + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + else: + # exception #1 + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" + return rendered + + +def render(pieces, style): + """Render the given version pieces into the requested style.""" + if pieces["error"]: + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"], + "date": None} + + if not style or style == "default": + style = "pep440" # the default + + if style == "pep440": + rendered = render_pep440(pieces) + elif style == "pep440-pre": + rendered = render_pep440_pre(pieces) + elif style == "pep440-post": + rendered = render_pep440_post(pieces) + elif style == "pep440-old": + rendered = render_pep440_old(pieces) + elif style == "git-describe": + rendered = render_git_describe(pieces) + elif style == "git-describe-long": + rendered = render_git_describe_long(pieces) + else: + raise ValueError("unknown style '%s'" % style) + + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None, + "date": pieces.get("date")} + + +class VersioneerBadRootError(Exception): + """The project root directory is unknown or missing key files.""" + + +def get_versions(verbose=False): + """Get the project version from whatever source is available. + + Returns dict with two keys: 'version' and 'full'. + """ + if "versioneer" in sys.modules: + # see the discussion in cmdclass.py:get_cmdclass() + del sys.modules["versioneer"] + + root = get_root() + cfg = get_config_from_root(root) + + assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" + handlers = HANDLERS.get(cfg.VCS) + assert handlers, "unrecognized VCS '%s'" % cfg.VCS + verbose = verbose or cfg.verbose + assert cfg.versionfile_source is not None, \ + "please set versioneer.versionfile_source" + assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" + + versionfile_abs = os.path.join(root, cfg.versionfile_source) + + # extract version from first of: _version.py, VCS command (e.g. 'git + # describe'), parentdir. This is meant to work for developers using a + # source checkout, for users of a tarball created by 'setup.py sdist', + # and for users of a tarball/zipball created by 'git archive' or github's + # download-from-tag feature or the equivalent in other VCSes. + + get_keywords_f = handlers.get("get_keywords") + from_keywords_f = handlers.get("keywords") + if get_keywords_f and from_keywords_f: + try: + keywords = get_keywords_f(versionfile_abs) + ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) + if verbose: + print("got version from expanded keyword %s" % ver) + return ver + except NotThisMethod: + pass + + try: + ver = versions_from_file(versionfile_abs) + if verbose: + print("got version from file %s %s" % (versionfile_abs, ver)) + return ver + except NotThisMethod: + pass + + from_vcs_f = handlers.get("pieces_from_vcs") + if from_vcs_f: + try: + pieces = from_vcs_f(cfg.tag_prefix, root, verbose) + ver = render(pieces, cfg.style) + if verbose: + print("got version from VCS %s" % ver) + return ver + except NotThisMethod: + pass + + try: + if cfg.parentdir_prefix: + ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) + if verbose: + print("got version from parentdir %s" % ver) + return ver + except NotThisMethod: + pass + + if verbose: + print("unable to compute version") + + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, "error": "unable to compute version", + "date": None} + + +def get_version(): + """Get the short version string for this project.""" + return get_versions()["version"] + + +def get_cmdclass(): + """Get the custom setuptools/distutils subclasses used by Versioneer.""" + if "versioneer" in sys.modules: + del sys.modules["versioneer"] + # this fixes the "python setup.py develop" case (also 'install' and + # 'easy_install .'), in which subdependencies of the main project are + # built (using setup.py bdist_egg) in the same python process. Assume + # a main project A and a dependency B, which use different versions + # of Versioneer. A's setup.py imports A's Versioneer, leaving it in + # sys.modules by the time B's setup.py is executed, causing B to run + # with the wrong versioneer. Setuptools wraps the sub-dep builds in a + # sandbox that restores sys.modules to it's pre-build state, so the + # parent is protected against the child's "import versioneer". By + # removing ourselves from sys.modules here, before the child build + # happens, we protect the child from the parent's versioneer too. + # Also see https://github.com/warner/python-versioneer/issues/52 + + cmds = {} + + # we add "version" to both distutils and setuptools + from distutils.core import Command + + class cmd_version(Command): + description = "report generated version string" + user_options = [] + boolean_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + vers = get_versions(verbose=True) + print("Version: %s" % vers["version"]) + print(" full-revisionid: %s" % vers.get("full-revisionid")) + print(" dirty: %s" % vers.get("dirty")) + print(" date: %s" % vers.get("date")) + if vers["error"]: + print(" error: %s" % vers["error"]) + cmds["version"] = cmd_version + + # we override "build_py" in both distutils and setuptools + # + # most invocation pathways end up running build_py: + # distutils/build -> build_py + # distutils/install -> distutils/build ->.. + # setuptools/bdist_wheel -> distutils/install ->.. + # setuptools/bdist_egg -> distutils/install_lib -> build_py + # setuptools/install -> bdist_egg ->.. + # setuptools/develop -> ? + # pip install: + # copies source tree to a tempdir before running egg_info/etc + # if .git isn't copied too, 'git describe' will fail + # then does setup.py bdist_wheel, or sometimes setup.py install + # setup.py egg_info -> ? + + # we override different "build_py" commands for both environments + if "setuptools" in sys.modules: + from setuptools.command.build_py import build_py as _build_py + else: + from distutils.command.build_py import build_py as _build_py + + class cmd_build_py(_build_py): + def run(self): + root = get_root() + cfg = get_config_from_root(root) + versions = get_versions() + _build_py.run(self) + # now locate _version.py in the new build/ directory and replace + # it with an updated value + if cfg.versionfile_build: + target_versionfile = os.path.join(self.build_lib, + cfg.versionfile_build) + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, versions) + cmds["build_py"] = cmd_build_py + + if "cx_Freeze" in sys.modules: # cx_freeze enabled? + from cx_Freeze.dist import build_exe as _build_exe + # nczeczulin reports that py2exe won't like the pep440-style string + # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. + # setup(console=[{ + # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION + # "product_version": versioneer.get_version(), + # ... + + class cmd_build_exe(_build_exe): + def run(self): + root = get_root() + cfg = get_config_from_root(root) + versions = get_versions() + target_versionfile = cfg.versionfile_source + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, versions) + + _build_exe.run(self) + os.unlink(target_versionfile) + with open(cfg.versionfile_source, "w") as f: + LONG = LONG_VERSION_PY[cfg.VCS] + f.write(LONG % + {"DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + }) + cmds["build_exe"] = cmd_build_exe + del cmds["build_py"] + + if 'py2exe' in sys.modules: # py2exe enabled? + try: + from py2exe.distutils_buildexe import py2exe as _py2exe # py3 + except ImportError: + from py2exe.build_exe import py2exe as _py2exe # py2 + + class cmd_py2exe(_py2exe): + def run(self): + root = get_root() + cfg = get_config_from_root(root) + versions = get_versions() + target_versionfile = cfg.versionfile_source + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, versions) + + _py2exe.run(self) + os.unlink(target_versionfile) + with open(cfg.versionfile_source, "w") as f: + LONG = LONG_VERSION_PY[cfg.VCS] + f.write(LONG % + {"DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + }) + cmds["py2exe"] = cmd_py2exe + + # we override different "sdist" commands for both environments + if "setuptools" in sys.modules: + from setuptools.command.sdist import sdist as _sdist + else: + from distutils.command.sdist import sdist as _sdist + + class cmd_sdist(_sdist): + def run(self): + versions = get_versions() + self._versioneer_generated_versions = versions + # unless we update this, the command will keep using the old + # version + self.distribution.metadata.version = versions["version"] + return _sdist.run(self) + + def make_release_tree(self, base_dir, files): + root = get_root() + cfg = get_config_from_root(root) + _sdist.make_release_tree(self, base_dir, files) + # now locate _version.py in the new base_dir directory + # (remembering that it may be a hardlink) and replace it with an + # updated value + target_versionfile = os.path.join(base_dir, cfg.versionfile_source) + print("UPDATING %s" % target_versionfile) + write_to_version_file(target_versionfile, + self._versioneer_generated_versions) + cmds["sdist"] = cmd_sdist + + return cmds + + +CONFIG_ERROR = """ +setup.cfg is missing the necessary Versioneer configuration. You need +a section like: + + [versioneer] + VCS = git + style = pep440 + versionfile_source = src/myproject/_version.py + versionfile_build = myproject/_version.py + tag_prefix = + parentdir_prefix = myproject- + +You will also need to edit your setup.py to use the results: + + import versioneer + setup(version=versioneer.get_version(), + cmdclass=versioneer.get_cmdclass(), ...) + +Please read the docstring in ./versioneer.py for configuration instructions, +edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. +""" + +SAMPLE_CONFIG = """ +# See the docstring in versioneer.py for instructions. Note that you must +# re-run 'versioneer.py setup' after changing this section, and commit the +# resulting files. + +[versioneer] +#VCS = git +#style = pep440 +#versionfile_source = +#versionfile_build = +#tag_prefix = +#parentdir_prefix = + +""" + +INIT_PY_SNIPPET = """ +from ._version import get_versions +__version__ = get_versions()['version'] +del get_versions +""" + + +def do_setup(): + """Main VCS-independent setup function for installing Versioneer.""" + root = get_root() + try: + cfg = get_config_from_root(root) + except (EnvironmentError, configparser.NoSectionError, + configparser.NoOptionError) as e: + if isinstance(e, (EnvironmentError, configparser.NoSectionError)): + print("Adding sample versioneer config to setup.cfg", + file=sys.stderr) + with open(os.path.join(root, "setup.cfg"), "a") as f: + f.write(SAMPLE_CONFIG) + print(CONFIG_ERROR, file=sys.stderr) + return 1 + + print(" creating %s" % cfg.versionfile_source) + with open(cfg.versionfile_source, "w") as f: + LONG = LONG_VERSION_PY[cfg.VCS] + f.write(LONG % {"DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + }) + + ipy = os.path.join(os.path.dirname(cfg.versionfile_source), + "__init__.py") + if os.path.exists(ipy): + try: + with open(ipy, "r") as f: + old = f.read() + except EnvironmentError: + old = "" + if INIT_PY_SNIPPET not in old: + print(" appending to %s" % ipy) + with open(ipy, "a") as f: + f.write(INIT_PY_SNIPPET) + else: + print(" %s unmodified" % ipy) + else: + print(" %s doesn't exist, ok" % ipy) + ipy = None + + # Make sure both the top-level "versioneer.py" and versionfile_source + # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so + # they'll be copied into source distributions. Pip won't be able to + # install the package without this. + manifest_in = os.path.join(root, "MANIFEST.in") + simple_includes = set() + try: + with open(manifest_in, "r") as f: + for line in f: + if line.startswith("include "): + for include in line.split()[1:]: + simple_includes.add(include) + except EnvironmentError: + pass + # That doesn't cover everything MANIFEST.in can do + # (http://docs.python.org/2/distutils/sourcedist.html#commands), so + # it might give some false negatives. Appending redundant 'include' + # lines is safe, though. + if "versioneer.py" not in simple_includes: + print(" appending 'versioneer.py' to MANIFEST.in") + with open(manifest_in, "a") as f: + f.write("include versioneer.py\n") + else: + print(" 'versioneer.py' already in MANIFEST.in") + if cfg.versionfile_source not in simple_includes: + print(" appending versionfile_source ('%s') to MANIFEST.in" % + cfg.versionfile_source) + with open(manifest_in, "a") as f: + f.write("include %s\n" % cfg.versionfile_source) + else: + print(" versionfile_source already in MANIFEST.in") + + # Make VCS-specific changes. For git, this means creating/changing + # .gitattributes to mark _version.py for export-subst keyword + # substitution. + do_vcs_install(manifest_in, cfg.versionfile_source, ipy) + return 0 + + +def scan_setup_py(): + """Validate the contents of setup.py against Versioneer's expectations.""" + found = set() + setters = False + errors = 0 + with open("setup.py", "r") as f: + for line in f.readlines(): + if "import versioneer" in line: + found.add("import") + if "versioneer.get_cmdclass()" in line: + found.add("cmdclass") + if "versioneer.get_version()" in line: + found.add("get_version") + if "versioneer.VCS" in line: + setters = True + if "versioneer.versionfile_source" in line: + setters = True + if len(found) != 3: + print("") + print("Your setup.py appears to be missing some important items") + print("(but I might be wrong). Please make sure it has something") + print("roughly like the following:") + print("") + print(" import versioneer") + print(" setup( version=versioneer.get_version(),") + print(" cmdclass=versioneer.get_cmdclass(), ...)") + print("") + errors += 1 + if setters: + print("You should remove lines like 'versioneer.VCS = ' and") + print("'versioneer.versionfile_source = ' . This configuration") + print("now lives in setup.cfg, and should be removed from setup.py") + print("") + errors += 1 + return errors + + +if __name__ == "__main__": + cmd = sys.argv[1] + if cmd == "setup": + errors = do_setup() + errors += scan_setup_py() + if errors: + sys.exit(1) From 3f38f0c3abdd866223f0059ad7677f9cf66b48c7 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sat, 1 Feb 2020 14:04:34 +0100 Subject: [PATCH 039/112] [pip] Add themes & icons and make lookup work Really ugly hack (extending the lookup logic in theme.py) to make pip themes work, but for now, I am unable to come up with anything better. --- bumblebee/theme.py | 1 + setup.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/bumblebee/theme.py b/bumblebee/theme.py index 2b52f73..9b09231 100644 --- a/bumblebee/theme.py +++ b/bumblebee/theme.py @@ -22,6 +22,7 @@ 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("{}/../../../../share/bumblebee-status/themes/".format(os.path.dirname(os.path.realpath(__file__)))), os.path.dirname(os.path.expanduser("~/.config/bumblebee-status/themes/")), ] diff --git a/setup.py b/setup.py index ed950ff..1dc6adb 100755 --- a/setup.py +++ b/setup.py @@ -42,10 +42,14 @@ EXTRAS_REQUIREMENTS_MAP = { "yubikey": read_module("yubikey"), } +import glob setup( install_requires=INSTALL_REQS, extras_require=EXTRAS_REQUIREMENTS_MAP, version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), zip_safe=False, + data_files=[('share/bumblebee-status/themes', glob.glob('themes/*.json')), + ('share/bumblebee-status/themes/icons', glob.glob('themes/icons/*.json')) + ] ) From cb1b0d77082482c680f58b9da7637f7713b0f3a3 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sat, 1 Feb 2020 14:11:32 +0100 Subject: [PATCH 040/112] [setup] Remove download URL Wrong format, and I cannot figure out how to get the correct format (including tgz) --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 8763f86..4ab2ebe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,6 @@ name = bumblebee-status author = tobi-wan-kenobi author_email = github@tobi-wan-kenobi.at url = https://github.com/tobi-wan-kenobi/bumblebee-status -download_url = 'https://pypi.org/project/bumblebee-status/' project_urls = Documentation = https://github.com/tobi-wan-kenobi/bumblebee-status Code = https://github.com/tobi-wan-kenobi/bumblebee-status From e32f2abfad3d6429f9f9e86d6f7ad8d2c98b9837 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sat, 1 Feb 2020 14:27:45 +0100 Subject: [PATCH 041/112] [setup.cfg] Hopefully fix syntax error --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4ab2ebe..c8cf085 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,7 @@ url = https://github.com/tobi-wan-kenobi/bumblebee-status project_urls = Documentation = https://github.com/tobi-wan-kenobi/bumblebee-status Code = https://github.com/tobi-wan-kenobi/bumblebee-status - Issue tracker = 'https://github.com/tobi-wan-kenobi/bumblebee-status' + Issue tracker = https://github.com/tobi-wan-kenobi/bumblebee-status description = a modular, theme-able status line generator for the i3 window manager. long_description = file: README.md From 3431129536206d8ce7ffd912aea9ce6a1d11aec7 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sat, 1 Feb 2020 14:33:19 +0100 Subject: [PATCH 042/112] [doc] Add note where binary for PIP install is found --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 40490ec..a408399 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ cd bumblebee-status makepkg -sicr # from PyPI (thanks @tony): +# will install bumblebee-status into ~/.local/bin/bumblebee-status pip install --user bumblebee-status ``` From dcc42036e810bfa8fadb603157a213961416a61c Mon Sep 17 00:00:00 2001 From: nginsburg Date: Tue, 4 Feb 2020 19:06:26 -0500 Subject: [PATCH 043/112] using pkg_resources --- .gitignore | 1 + bumblebee/theme.py | 44 +++++++++---------- {themes => bumblebee/themes}/default.json | 0 .../themes}/dracula-powerline.json | 0 .../themes}/firefox-dark-powerline.json | 0 .../themes}/greyish-powerline.json | 0 .../themes}/gruvbox-light.json | 0 .../themes}/gruvbox-powerline-light.json | 0 .../themes}/gruvbox-powerline.json | 0 {themes => bumblebee/themes}/gruvbox.json | 0 .../themes}/iceberg-dark-powerline.json | 0 .../themes}/iceberg-powerline.json | 0 .../themes}/iceberg-rainbow.json | 0 {themes => bumblebee/themes}/iceberg.json | 0 {themes => bumblebee/themes}/icons/ascii.json | 0 .../themes}/icons/awesome-fonts.json | 0 .../themes}/icons/ionicons.json | 0 .../themes}/icons/paxy97.json | 0 {themes => bumblebee/themes}/icons/test.json | 0 .../themes}/onedark-powerline.json | 0 {themes => bumblebee/themes}/powerline.json | 0 {themes => bumblebee/themes}/sac_red.json | 0 .../themes}/solarized-dark-awesome.json | 0 .../themes}/solarized-powerline.json | 0 {themes => bumblebee/themes}/solarized.json | 0 {themes => bumblebee/themes}/test.json | 0 {themes => bumblebee/themes}/test_cycle.json | 0 .../themes}/test_invalid.json | 0 .../themes}/wal-powerline.json | 0 themes | 1 + 30 files changed, 24 insertions(+), 22 deletions(-) rename {themes => bumblebee/themes}/default.json (100%) rename {themes => bumblebee/themes}/dracula-powerline.json (100%) rename {themes => bumblebee/themes}/firefox-dark-powerline.json (100%) rename {themes => bumblebee/themes}/greyish-powerline.json (100%) rename {themes => bumblebee/themes}/gruvbox-light.json (100%) rename {themes => bumblebee/themes}/gruvbox-powerline-light.json (100%) rename {themes => bumblebee/themes}/gruvbox-powerline.json (100%) rename {themes => bumblebee/themes}/gruvbox.json (100%) rename {themes => bumblebee/themes}/iceberg-dark-powerline.json (100%) rename {themes => bumblebee/themes}/iceberg-powerline.json (100%) rename {themes => bumblebee/themes}/iceberg-rainbow.json (100%) rename {themes => bumblebee/themes}/iceberg.json (100%) rename {themes => bumblebee/themes}/icons/ascii.json (100%) rename {themes => bumblebee/themes}/icons/awesome-fonts.json (100%) rename {themes => bumblebee/themes}/icons/ionicons.json (100%) rename {themes => bumblebee/themes}/icons/paxy97.json (100%) rename {themes => bumblebee/themes}/icons/test.json (100%) rename {themes => bumblebee/themes}/onedark-powerline.json (100%) rename {themes => bumblebee/themes}/powerline.json (100%) rename {themes => bumblebee/themes}/sac_red.json (100%) rename {themes => bumblebee/themes}/solarized-dark-awesome.json (100%) rename {themes => bumblebee/themes}/solarized-powerline.json (100%) rename {themes => bumblebee/themes}/solarized.json (100%) rename {themes => bumblebee/themes}/test.json (100%) rename {themes => bumblebee/themes}/test_cycle.json (100%) rename {themes => bumblebee/themes}/test_invalid.json (100%) rename {themes => bumblebee/themes}/wal-powerline.json (100%) create mode 120000 themes diff --git a/.gitignore b/.gitignore index 724c296..4996eaa 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ var/ *.egg-info/ .installed.cfg *.egg +build/ # PyInstaller # Usually these files are written by a python script from a template diff --git a/bumblebee/theme.py b/bumblebee/theme.py index 9b09231..50e0c19 100644 --- a/bumblebee/theme.py +++ b/bumblebee/theme.py @@ -9,31 +9,30 @@ import json import io import re import logging - -try: - import requests - from requests.exceptions import RequestException -except ImportError: - pass +import pkg_resources 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("{}/../../../../share/bumblebee-status/themes/".format(os.path.dirname(os.path.realpath(__file__)))), - os.path.dirname(os.path.expanduser("~/.config/bumblebee-status/themes/")), + os.path.realpath(x) for x in [ + os.path.dirname("{}/../themes/".format(os.path.dirname(os.path.realpath(__file__)))), + os.path.dirname( + "{}/../../../../share/bumblebee-status/themes/".format( + os.path.dirname(os.path.realpath(__file__)))), + os.path.dirname(os.path.expanduser("~/.config/bumblebee-status/themes/")), + pkg_resources.resource_filename('bumblebee', 'themes')] if os.path.exists(x) ] def themes(): - themes = {} + themes_dict = {} 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()) + themes_dict[os.path.basename(filename).replace(".json", "")] = 1 + result = list(themes_dict.keys()) result.sort() return result @@ -59,7 +58,7 @@ class Theme(object): path = os.path.expanduser("~/.config/bumblebee-status/") try: os.makedirs(path) - except Exception: + except OSError: pass try: if os.path.exists("{}/symbols.json".format(path)): @@ -73,8 +72,8 @@ class Theme(object): 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))) + except Exception as err: + logging.error("failed to load symbols: %s", err) def _init(self, data): """Initialize theme from data structure""" @@ -96,7 +95,7 @@ class Theme(object): def reset(self): """Reset theme to initial state""" - self._cycle = self._cycles[0] if len(self._cycles) > 0 else {} + self._cycle = self._cycles[0] if self._cycles else {} self._cycle_idx = 0 self._widget = None self._prevbg = None @@ -105,6 +104,7 @@ class Theme(object): icon = self._get(widget, "icon", None) if icon is None: return self._get(widget, "prefix", None) + return None def get(self, widget, attribute, default_value=""): return self._get(widget, attribute, default_value) @@ -189,10 +189,10 @@ class Theme(object): def _load_colors(self, name): """Load colors for a theme""" try: - if name == "wal": + if name.lower() == "wal": return self._load_wal_colors() - except Exception as e: - logging.error("failed to load colors: {}".format(str(e))) + except Exception as err: + logging.error("failed to load colors: %s", err) def _load_icons(self, name): """Load icons for a theme""" @@ -217,7 +217,7 @@ class Theme(object): if os.path.isfile(full_name): path = os.path.dirname(full_name) name = os.path.basename(full_name) - name,_,_ = name.rpartition(".json") + name, _, _ = name.rpartition(".json") return self.load(name, path) if not isinstance(path, list): @@ -249,7 +249,7 @@ class Theme(object): if self._widget != widget: self._prevbg = self.bg(self._widget) self._widget = widget - if len(self._cycles) > 0: + if self._cycles: self._cycle_idx = (self._cycle_idx + 1) % len(self._cycles) self._cycle = self._cycles[self._cycle_idx] @@ -283,7 +283,7 @@ class Theme(object): 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): + if isinstance(value, (dict, list)): return value return self._colorset.get(value, value) diff --git a/themes/default.json b/bumblebee/themes/default.json similarity index 100% rename from themes/default.json rename to bumblebee/themes/default.json diff --git a/themes/dracula-powerline.json b/bumblebee/themes/dracula-powerline.json similarity index 100% rename from themes/dracula-powerline.json rename to bumblebee/themes/dracula-powerline.json diff --git a/themes/firefox-dark-powerline.json b/bumblebee/themes/firefox-dark-powerline.json similarity index 100% rename from themes/firefox-dark-powerline.json rename to bumblebee/themes/firefox-dark-powerline.json diff --git a/themes/greyish-powerline.json b/bumblebee/themes/greyish-powerline.json similarity index 100% rename from themes/greyish-powerline.json rename to bumblebee/themes/greyish-powerline.json diff --git a/themes/gruvbox-light.json b/bumblebee/themes/gruvbox-light.json similarity index 100% rename from themes/gruvbox-light.json rename to bumblebee/themes/gruvbox-light.json diff --git a/themes/gruvbox-powerline-light.json b/bumblebee/themes/gruvbox-powerline-light.json similarity index 100% rename from themes/gruvbox-powerline-light.json rename to bumblebee/themes/gruvbox-powerline-light.json diff --git a/themes/gruvbox-powerline.json b/bumblebee/themes/gruvbox-powerline.json similarity index 100% rename from themes/gruvbox-powerline.json rename to bumblebee/themes/gruvbox-powerline.json diff --git a/themes/gruvbox.json b/bumblebee/themes/gruvbox.json similarity index 100% rename from themes/gruvbox.json rename to bumblebee/themes/gruvbox.json diff --git a/themes/iceberg-dark-powerline.json b/bumblebee/themes/iceberg-dark-powerline.json similarity index 100% rename from themes/iceberg-dark-powerline.json rename to bumblebee/themes/iceberg-dark-powerline.json diff --git a/themes/iceberg-powerline.json b/bumblebee/themes/iceberg-powerline.json similarity index 100% rename from themes/iceberg-powerline.json rename to bumblebee/themes/iceberg-powerline.json diff --git a/themes/iceberg-rainbow.json b/bumblebee/themes/iceberg-rainbow.json similarity index 100% rename from themes/iceberg-rainbow.json rename to bumblebee/themes/iceberg-rainbow.json diff --git a/themes/iceberg.json b/bumblebee/themes/iceberg.json similarity index 100% rename from themes/iceberg.json rename to bumblebee/themes/iceberg.json diff --git a/themes/icons/ascii.json b/bumblebee/themes/icons/ascii.json similarity index 100% rename from themes/icons/ascii.json rename to bumblebee/themes/icons/ascii.json diff --git a/themes/icons/awesome-fonts.json b/bumblebee/themes/icons/awesome-fonts.json similarity index 100% rename from themes/icons/awesome-fonts.json rename to bumblebee/themes/icons/awesome-fonts.json diff --git a/themes/icons/ionicons.json b/bumblebee/themes/icons/ionicons.json similarity index 100% rename from themes/icons/ionicons.json rename to bumblebee/themes/icons/ionicons.json diff --git a/themes/icons/paxy97.json b/bumblebee/themes/icons/paxy97.json similarity index 100% rename from themes/icons/paxy97.json rename to bumblebee/themes/icons/paxy97.json diff --git a/themes/icons/test.json b/bumblebee/themes/icons/test.json similarity index 100% rename from themes/icons/test.json rename to bumblebee/themes/icons/test.json diff --git a/themes/onedark-powerline.json b/bumblebee/themes/onedark-powerline.json similarity index 100% rename from themes/onedark-powerline.json rename to bumblebee/themes/onedark-powerline.json diff --git a/themes/powerline.json b/bumblebee/themes/powerline.json similarity index 100% rename from themes/powerline.json rename to bumblebee/themes/powerline.json diff --git a/themes/sac_red.json b/bumblebee/themes/sac_red.json similarity index 100% rename from themes/sac_red.json rename to bumblebee/themes/sac_red.json diff --git a/themes/solarized-dark-awesome.json b/bumblebee/themes/solarized-dark-awesome.json similarity index 100% rename from themes/solarized-dark-awesome.json rename to bumblebee/themes/solarized-dark-awesome.json diff --git a/themes/solarized-powerline.json b/bumblebee/themes/solarized-powerline.json similarity index 100% rename from themes/solarized-powerline.json rename to bumblebee/themes/solarized-powerline.json diff --git a/themes/solarized.json b/bumblebee/themes/solarized.json similarity index 100% rename from themes/solarized.json rename to bumblebee/themes/solarized.json diff --git a/themes/test.json b/bumblebee/themes/test.json similarity index 100% rename from themes/test.json rename to bumblebee/themes/test.json diff --git a/themes/test_cycle.json b/bumblebee/themes/test_cycle.json similarity index 100% rename from themes/test_cycle.json rename to bumblebee/themes/test_cycle.json diff --git a/themes/test_invalid.json b/bumblebee/themes/test_invalid.json similarity index 100% rename from themes/test_invalid.json rename to bumblebee/themes/test_invalid.json diff --git a/themes/wal-powerline.json b/bumblebee/themes/wal-powerline.json similarity index 100% rename from themes/wal-powerline.json rename to bumblebee/themes/wal-powerline.json diff --git a/themes b/themes new file mode 120000 index 0000000..fe07097 --- /dev/null +++ b/themes @@ -0,0 +1 @@ +bumblebee/themes/ \ No newline at end of file From a19484289e9f0269edb80a6fb37537071c8dee66 Mon Sep 17 00:00:00 2001 From: nginsburg Date: Fri, 7 Feb 2020 13:33:22 -0500 Subject: [PATCH 044/112] reversing symlinks --- MANIFEST.in | 4 ++-- bumblebee/themes | 1 + themes | 1 - {bumblebee/themes => themes}/default.json | 0 {bumblebee/themes => themes}/dracula-powerline.json | 0 {bumblebee/themes => themes}/firefox-dark-powerline.json | 0 {bumblebee/themes => themes}/greyish-powerline.json | 0 {bumblebee/themes => themes}/gruvbox-light.json | 0 {bumblebee/themes => themes}/gruvbox-powerline-light.json | 0 {bumblebee/themes => themes}/gruvbox-powerline.json | 0 {bumblebee/themes => themes}/gruvbox.json | 0 {bumblebee/themes => themes}/iceberg-dark-powerline.json | 0 {bumblebee/themes => themes}/iceberg-powerline.json | 0 {bumblebee/themes => themes}/iceberg-rainbow.json | 0 {bumblebee/themes => themes}/iceberg.json | 0 {bumblebee/themes => themes}/icons/ascii.json | 0 {bumblebee/themes => themes}/icons/awesome-fonts.json | 0 {bumblebee/themes => themes}/icons/ionicons.json | 0 {bumblebee/themes => themes}/icons/paxy97.json | 0 {bumblebee/themes => themes}/icons/test.json | 0 {bumblebee/themes => themes}/onedark-powerline.json | 0 {bumblebee/themes => themes}/powerline.json | 0 {bumblebee/themes => themes}/sac_red.json | 0 {bumblebee/themes => themes}/solarized-dark-awesome.json | 0 {bumblebee/themes => themes}/solarized-powerline.json | 0 {bumblebee/themes => themes}/solarized.json | 0 {bumblebee/themes => themes}/test.json | 0 {bumblebee/themes => themes}/test_cycle.json | 0 {bumblebee/themes => themes}/test_invalid.json | 0 {bumblebee/themes => themes}/wal-powerline.json | 0 30 files changed, 3 insertions(+), 3 deletions(-) create mode 120000 bumblebee/themes delete mode 120000 themes rename {bumblebee/themes => themes}/default.json (100%) rename {bumblebee/themes => themes}/dracula-powerline.json (100%) rename {bumblebee/themes => themes}/firefox-dark-powerline.json (100%) rename {bumblebee/themes => themes}/greyish-powerline.json (100%) rename {bumblebee/themes => themes}/gruvbox-light.json (100%) rename {bumblebee/themes => themes}/gruvbox-powerline-light.json (100%) rename {bumblebee/themes => themes}/gruvbox-powerline.json (100%) rename {bumblebee/themes => themes}/gruvbox.json (100%) rename {bumblebee/themes => themes}/iceberg-dark-powerline.json (100%) rename {bumblebee/themes => themes}/iceberg-powerline.json (100%) rename {bumblebee/themes => themes}/iceberg-rainbow.json (100%) rename {bumblebee/themes => themes}/iceberg.json (100%) rename {bumblebee/themes => themes}/icons/ascii.json (100%) rename {bumblebee/themes => themes}/icons/awesome-fonts.json (100%) rename {bumblebee/themes => themes}/icons/ionicons.json (100%) rename {bumblebee/themes => themes}/icons/paxy97.json (100%) rename {bumblebee/themes => themes}/icons/test.json (100%) rename {bumblebee/themes => themes}/onedark-powerline.json (100%) rename {bumblebee/themes => themes}/powerline.json (100%) rename {bumblebee/themes => themes}/sac_red.json (100%) rename {bumblebee/themes => themes}/solarized-dark-awesome.json (100%) rename {bumblebee/themes => themes}/solarized-powerline.json (100%) rename {bumblebee/themes => themes}/solarized.json (100%) rename {bumblebee/themes => themes}/test.json (100%) rename {bumblebee/themes => themes}/test_cycle.json (100%) rename {bumblebee/themes => themes}/test_invalid.json (100%) rename {bumblebee/themes => themes}/wal-powerline.json (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 1ad48b8..26a5a3f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,5 +2,5 @@ include versioneer.py include bumblebee/_version.py include requirements/* include requirements/modules/* -include themes/* -include themes/icons/* +include bumblebee/themes/* +include bumblebee/themes/icons/* diff --git a/bumblebee/themes b/bumblebee/themes new file mode 120000 index 0000000..de90031 --- /dev/null +++ b/bumblebee/themes @@ -0,0 +1 @@ +../themes \ No newline at end of file diff --git a/themes b/themes deleted file mode 120000 index fe07097..0000000 --- a/themes +++ /dev/null @@ -1 +0,0 @@ -bumblebee/themes/ \ No newline at end of file diff --git a/bumblebee/themes/default.json b/themes/default.json similarity index 100% rename from bumblebee/themes/default.json rename to themes/default.json diff --git a/bumblebee/themes/dracula-powerline.json b/themes/dracula-powerline.json similarity index 100% rename from bumblebee/themes/dracula-powerline.json rename to themes/dracula-powerline.json diff --git a/bumblebee/themes/firefox-dark-powerline.json b/themes/firefox-dark-powerline.json similarity index 100% rename from bumblebee/themes/firefox-dark-powerline.json rename to themes/firefox-dark-powerline.json diff --git a/bumblebee/themes/greyish-powerline.json b/themes/greyish-powerline.json similarity index 100% rename from bumblebee/themes/greyish-powerline.json rename to themes/greyish-powerline.json diff --git a/bumblebee/themes/gruvbox-light.json b/themes/gruvbox-light.json similarity index 100% rename from bumblebee/themes/gruvbox-light.json rename to themes/gruvbox-light.json diff --git a/bumblebee/themes/gruvbox-powerline-light.json b/themes/gruvbox-powerline-light.json similarity index 100% rename from bumblebee/themes/gruvbox-powerline-light.json rename to themes/gruvbox-powerline-light.json diff --git a/bumblebee/themes/gruvbox-powerline.json b/themes/gruvbox-powerline.json similarity index 100% rename from bumblebee/themes/gruvbox-powerline.json rename to themes/gruvbox-powerline.json diff --git a/bumblebee/themes/gruvbox.json b/themes/gruvbox.json similarity index 100% rename from bumblebee/themes/gruvbox.json rename to themes/gruvbox.json diff --git a/bumblebee/themes/iceberg-dark-powerline.json b/themes/iceberg-dark-powerline.json similarity index 100% rename from bumblebee/themes/iceberg-dark-powerline.json rename to themes/iceberg-dark-powerline.json diff --git a/bumblebee/themes/iceberg-powerline.json b/themes/iceberg-powerline.json similarity index 100% rename from bumblebee/themes/iceberg-powerline.json rename to themes/iceberg-powerline.json diff --git a/bumblebee/themes/iceberg-rainbow.json b/themes/iceberg-rainbow.json similarity index 100% rename from bumblebee/themes/iceberg-rainbow.json rename to themes/iceberg-rainbow.json diff --git a/bumblebee/themes/iceberg.json b/themes/iceberg.json similarity index 100% rename from bumblebee/themes/iceberg.json rename to themes/iceberg.json diff --git a/bumblebee/themes/icons/ascii.json b/themes/icons/ascii.json similarity index 100% rename from bumblebee/themes/icons/ascii.json rename to themes/icons/ascii.json diff --git a/bumblebee/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json similarity index 100% rename from bumblebee/themes/icons/awesome-fonts.json rename to themes/icons/awesome-fonts.json diff --git a/bumblebee/themes/icons/ionicons.json b/themes/icons/ionicons.json similarity index 100% rename from bumblebee/themes/icons/ionicons.json rename to themes/icons/ionicons.json diff --git a/bumblebee/themes/icons/paxy97.json b/themes/icons/paxy97.json similarity index 100% rename from bumblebee/themes/icons/paxy97.json rename to themes/icons/paxy97.json diff --git a/bumblebee/themes/icons/test.json b/themes/icons/test.json similarity index 100% rename from bumblebee/themes/icons/test.json rename to themes/icons/test.json diff --git a/bumblebee/themes/onedark-powerline.json b/themes/onedark-powerline.json similarity index 100% rename from bumblebee/themes/onedark-powerline.json rename to themes/onedark-powerline.json diff --git a/bumblebee/themes/powerline.json b/themes/powerline.json similarity index 100% rename from bumblebee/themes/powerline.json rename to themes/powerline.json diff --git a/bumblebee/themes/sac_red.json b/themes/sac_red.json similarity index 100% rename from bumblebee/themes/sac_red.json rename to themes/sac_red.json diff --git a/bumblebee/themes/solarized-dark-awesome.json b/themes/solarized-dark-awesome.json similarity index 100% rename from bumblebee/themes/solarized-dark-awesome.json rename to themes/solarized-dark-awesome.json diff --git a/bumblebee/themes/solarized-powerline.json b/themes/solarized-powerline.json similarity index 100% rename from bumblebee/themes/solarized-powerline.json rename to themes/solarized-powerline.json diff --git a/bumblebee/themes/solarized.json b/themes/solarized.json similarity index 100% rename from bumblebee/themes/solarized.json rename to themes/solarized.json diff --git a/bumblebee/themes/test.json b/themes/test.json similarity index 100% rename from bumblebee/themes/test.json rename to themes/test.json diff --git a/bumblebee/themes/test_cycle.json b/themes/test_cycle.json similarity index 100% rename from bumblebee/themes/test_cycle.json rename to themes/test_cycle.json diff --git a/bumblebee/themes/test_invalid.json b/themes/test_invalid.json similarity index 100% rename from bumblebee/themes/test_invalid.json rename to themes/test_invalid.json diff --git a/bumblebee/themes/wal-powerline.json b/themes/wal-powerline.json similarity index 100% rename from bumblebee/themes/wal-powerline.json rename to themes/wal-powerline.json From 1ddcbc454b562c898a974e3bc21b86393fa09493 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Fri, 7 Feb 2020 21:05:15 +0100 Subject: [PATCH 045/112] [modules/pulseaudio] Only start daemon if not running Before starting the pulseaudio daemon, ensure that it is not running by using pulseaudio --check. fixes #542 --- bumblebee/modules/pulseaudio.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/bumblebee/modules/pulseaudio.py b/bumblebee/modules/pulseaudio.py index a30b9d5..f581954 100644 --- a/bumblebee/modules/pulseaudio.py +++ b/bumblebee/modules/pulseaudio.py @@ -25,16 +25,22 @@ import bumblebee.input import bumblebee.output import bumblebee.engine +def autostart_daemon(): + try: + bumblebee.util.execute("pulseaudio --check") + except Exception: + try: + bumblebee.util.execute("pulseaudio --start") + except: + pass + 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 + if bumblebee.util.asbool(self.parameter("autostart", False)): + autostart_daemon() self._change = 2 self._change = int(self.parameter("percent_change", "2%").strip("%")) @@ -167,11 +173,8 @@ class Module(bumblebee.engine.Module): 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 + autostart_daemon() + self.update(widgets) def state(self, widget): if self._mute: From 56a482001ea2ff552be68d3a78e5547d728faa2d Mon Sep 17 00:00:00 2001 From: John Young Date: Sat, 8 Feb 2020 11:19:47 +0000 Subject: [PATCH 046/112] Add nord-powerline theme --- themes/nord-powerline.json | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 themes/nord-powerline.json diff --git a/themes/nord-powerline.json b/themes/nord-powerline.json new file mode 100644 index 0000000..1567498 --- /dev/null +++ b/themes/nord-powerline.json @@ -0,0 +1,63 @@ +{ + "icons": [ "awesome-fonts" ], + "defaults": { + "separator-block-width": 0, + "warning": { + "fg": "#2E3440", + "bg": "#D08770" + }, + "critical": { + "fg": "#2E3440", + "bg": "#BF616A" + } + }, + "cycle": [ + { "fg": "#D8DEE9", "bg": "#3B4252"}, + { "fg": "#D8DEE9", "bg": "#434C5E"}, + { "fg": "#D8DEE9", "bg": "#7B8394"}, + { "fg": "#D8DEE9", "bg": "#434C5E"} + ], + "dnf": { + "good": { + "fg": "#A3BE8C", + "bg": "#2E3440" + } + }, + "apt": { + "good": { + "fg": "#A3BE8C", + "bg": "#2E3440" + } + }, + "pacman": { + "good": { + "fg": "#A3BE8C", + "bg": "#2E3440" + } + }, + "battery": { + "charged": { + "fg": "#2E3440", + "bg": "#A3BE8C" + }, + "charging": { + "fg": "#2E3440", + "bg": "#81A1C1" + } + }, + "pomodoro": { + "paused": { + "fg": "#2E3440", + "bg": "#D08770" + }, + "work": { + "fg": "#2E3440", + "bg": "#EBCB8B" + }, + "break": { + "fg": "#A3BE8C", + "bg": "#2E3440" + } + } + +} From d2240b4ed963c09a50badc791de500257e8d6c88 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sat, 8 Feb 2020 12:56:15 +0100 Subject: [PATCH 047/112] [README] Add nord-powerline --- README.md | 4 ++++ screenshots/themes/nord-powerline.png | Bin 0 -> 7074 bytes 2 files changed, 4 insertions(+) create mode 100644 screenshots/themes/nord-powerline.png diff --git a/README.md b/README.md index a408399..72c73d4 100644 --- a/README.md +++ b/README.md @@ -299,6 +299,10 @@ Dracula Powerline (-t dracula-powerline) (contributed by [xsteadfastx](https://g ![Dracula Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/dracula-powerline.png) +Nord Powerline (-t nord-powerline) (contributed by [uselessthird](https://github.com/uselessthird)): + +![Nord Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/nord-powerline.png) + Default (nothing or `-t default`): ![Default](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/default.png) diff --git a/screenshots/themes/nord-powerline.png b/screenshots/themes/nord-powerline.png new file mode 100644 index 0000000000000000000000000000000000000000..9859b427b5cb08aea2a7fb9cf9110d1633f1a4dc GIT binary patch literal 7074 zcmV;T8(rjyP) zd3ambb>Poi@BjkDN`M5x4FLC@BE>}(C5w_Q+43qo@*<~6CiA87Oxw)#OOs4yzI<)^ zI9x348S*5!s`o z7{=l9?tv-~2122zs33o*wYdLKAnyH`X{WV#ps;(%-wm3ktS*&^cd9I1!6$ZFqg}_8 z&Hu)*BaV(KDYEjil1nXZ+pf|*CVG1%;3w^Cs1*qK2IKs->%F(SC-2`^hhb>PmFrfE z^=@jKASxnwbPUHi1Q!6gH`?6q$Yj#|+&l!0VYs9cu~w@ViG>^tcRHO0zUP83Sgg6?t%=8Rmy=lQS!B0MMt8=pE=NCps#{fVsmn|*bf1B|!*{h+|{j{?g)tw)QJ`S>FT^uvn~Y9v#CuIKholmO?I1;wQ!Q@pil2YF&w@<@2YW zdHBHobr;9RCV%uV{}%wb1ot2Q{(GUf7MGU)=5Jq(NtvyamYS;3sH1O>SGqP9KgLVR;ZFGRaD&yFJm!VYgROspOF(hf0bIWiqMFZl9i>eeeB` zmY27Gx)8m+;&HjzIhlc3i{5DR`}|sMwp<|t0C|dRY02XCc-5Nh!h*b2+v@!MVnl%$ zhF0&XP^+^!7(TzS(B0i@cdSK9Vlr76jy?O#;}et9Jv{>vB?Ev^m|U}`LZOgjIJUZC z>mL|07{kW}*GZ13)mEN}UFYaAFDy(?&ur?^aSj*faH5p;@PYl$J^Q53=Z{GPg5do0 z=Wh-S3`K0&(Rp=zd@A^-Fgf{!=TF`o9tmyJ+SWNbww5XF^?8HWt18Q%IB^We@k^K6 z?_QKFGczMIBP06uIEEd6HTEXvQ%l}g0N9yxUJQoGSOAB`xD zCWqh<2BR@ot66tTm7bQJl`*T+udJ@}c)a4Gf}@9JAAWSsPe!|UI5swEGA{)FO@`~0 zoGd6V%%`Y0g9@dnr38)BTJ@W4Kkk_M*~yA1+b_C&=Oz}24M`zy_UOBGE2qlSr993% zL#EeSC%%8In7|MK=r*kWVaVjCnMZOJ$MWRy>A54Ucy9j~%hH5@xliNr)8D^5X>vs6 zbpW7UXL0$d$~1B7Y?#R<%b>B}-3>!}R_8u9=Z@U&P7nkkxP(@tzSPowsioE9^`)gM z>g)EVrKJJ@5@R59GkaB1R8Un}exs+~G(Rs)PCndp@N0ke)$jk{A3RDiPP!?g4YKRip3 z6vSw<@^iHuoHIN;9*txUjz4nbVVY*Tul4x-WI;jhp+o!6pTD%QxD-)xM6I^+L@a%y zN3S>N4F=dO>dqm!7#^)V2Y~#1?FS#7yKu1uL)jMnG|>JKuYdZ*&*kN6qw(l^y-}|> z2LC+LbdX_~?rS$f+sw`BdwToVbyUdZFP}d3<}ZKaa=O3tg_o9>Edzs*KQ1;9M5E40 zOHGZ~FqE3=N|{V*v)M0PYzxfD($mvs=M3ZHQ`Oa##YOpz57oc_!C8+dvi-eqp_OJB zKA*qQqZ^Fosi|3tUURoAtF}W2AJS-Y#>OY3)YD?M>U4UR2DP<&M#d%z3-Y2A7z6>P z8eP(&;d7lL(l>AWao4n;j$xJcX0QHeuDp5jjvw%{%ilI@dAeA6JWl}t3IXxg7slFW zElt`Kv%~wwb={xV<|K2lf9sxA2snGv#qnvog9O~UhooP-)zi9 z5U{xY8$CM9pt0ZG@gZdxcxTGlIq!|!^iD7g6A1Xj!=pd@`C9?!3IHb4d~e@CRb}~? zzx1+DBy_oBm?ea@R{$_HJa(n~I(f^Wo6HM;`d`0LTU*`Q*10{_EOvWE5Np0bE|(P* z=5vWV!YwH&(wN+jK)}z{ss{##Mn=W~z;1V*c;~+K@ecz)N{`??r&geW?oqM+wc7_L=z@Ml`c$9jy1@zuc3BibbNKyw(dHI;E;Zj zVQyP_jV60yVk$6cxO(k|R-3cE9vwx|yxd%wOd3 z)9J5Y?-h%LDwVRZAh)moW<+T;v#D2exm=-t2KG3TlJ3|^K>!Xn9gs;S=bKwrHVi2M zU^qroG!yPqvNBaNiR5x?XRIC_1EwCE&p@Jz|8Jyk-u~mRX+IT%JHKIE)mhztQLkC& z(FYgoGy{88;=Xy?=#u04GF1wn({HvnX;UUGt|CQpb-D-u_Ge4SEw1~*qmwifTrCFN zxtFA0ZJjjQJ+JJ|sgC*)kj>YeEQ7{=cV~yxPcv_iIR}0AO9YV zVl6E#diLa#|MAwZqwkfIotd7l{N&t)b=RML_Nmh^ouVjeaB%3i@0?j#Sp|T3^P)N@ zTPhKIyxx-H!b_Ld;#4A$P$8F3PEBt`e;mUK3JOFbVXPh1*_jA}>SlGTwlE%)0T>t@ zR;iR4O?EUMJ$htP2>>iF-;t7v;dr`Ai2%suDH}a{O-)rp{a%`;4F=QIYdtR4?Ntbl zVYPer^z;tI8X)2Ty&p3vkeI`sZ}l0@Pek6O+lZf{-|92GP_7Dwg@c&v9sp1#^V)UR zL9_k&GEgS-Om;T_qzk!I7T2=d$H%b&v+Y>kn&n#Y_!k_$0=Xa#P3{4|9W?TAG)KZ; zbLSNEYU||Os{3TIQpm->-#YO(O$A&02$n%(zq^A&>ZX|gH*B9=jk#9M(VYjlc&YXHBTZ^`c5nYclw!l$tBqu3ssKP2SXOS$n#FCe0HCL5U~p)JAczC| z>zgmO0>GZC@`1r&k|a}PGA@q@Y!NCjPlLY_> zf|QmNPS4Ca9Jl=?yX1M(g4McGR$Aad81K%-FO3~I2^+&E6Xev>$>fEk|evju1O@~nc2C} z>kKdxlha0{nIuV>Oj1%(c;fhx_dh!4^R6v%%F9dK9?$gjY)oqK5b_{ujIZ>3#T{qC z`k4{4n1}Dn3bQnE`6&WLIT&K|`mG*60Ps1e-bMp}M#?{;m4D-tVFqAdmUK_LxNX+b zF=usn$;S)R;?v|F;HMadp|d4Pf4NWNrZB0FdzsFGa-^*w_q~L1VwWQ$uR= zF+UlynVhjrJpp%bRUl|)6->eNqQB%{iD1v_4+!9eBi@jRclHhQjc>K{h zz2WB22moxwnr+2iZ7&2tP!vtkG>^v%=;QbMCE_rln$1iW0Ayumh(w~xmph}EOf&TP z=9Z?18=raVF#zy*z0DU|H%_F&N@mw;D^C=C-*x!>{*jT9&?VN|M+E+ZAPB)rLRbQA zy8{jfuc@i(>Fp2QkN5lhZ~tm79b<54Sg$w!x37Nrp@#ZTn=b*tCuf^mF1J^dm6n&6 z{Ex4_(m!zXcki4r-^%ui4}u^{ONzPNXbS<>fnyi|P!tIO1eZAZ)MJ1k92^gZfM|xH zC>qDN?Lhjs0%iLmD@$KKh^g?bc1sn~MhBoWbFA`h08?O0U;DtJ8Z`3DZqP?edd;tw!B+ zXn*sC)}=e^2-qdFYqgapV(+^SpU-!5XxQrw6EV8=^JW;DB4f&i+Yu@&%K1r2SFT=T zX)`oDMv_!kW(EMH$fS=xdNeyLb7W-f?D>m49`W>(kJs1MT){bilSIX)dRSZPR>K4Dp}kve${WNOcP~_l03Ick12-n zkTiiID1z`gn2)CY6q6z11$?lZr1=~i0G3?7GsE-KR(DyN@QI={kDvO_L1X)@b$^cZ zWN|wC{NRe-%5uQjUG?aVIWiF!^N{pwZIjd1waDS|JcU}4^oyRkhUn7b*kKto_Pbjd z(zp>3XsHZuGsW-GKY#j^FgaPNOk3Xk5EZ2|t+JwgbaY&AFmBc+ETQ%)l#|aqQBhI) zFaP?}#ig~(#JH{57VQ-P81$w<)IB*_(0#oZHi^1tXJ4%b=4*jivR%U z;6DHQSw4%&N>5Kqk;y*pUcd3mE;%n(lOmIy`Qs;nFty2Ke(K5NRaNDoVU^7!vum}L zCyKJ~ItYT0N+k1U^Tu0iKSBn;@AEPYgLCd(+2~6zJlD9tq50zF=)3G|sI^$E6BFz^ zMht>L;QNhpIGvr{zxws>8HVBW`QQD{|N8wO&X`Q*(=UC_ZgX62>xf(VnVDI?-=CEk z`5U|ulWD%9yjZKr8R#FPXxg&DOyuWj0l+vPRrOj#%1Voh3i2+sbcC(b0D!z)jYJ~u z?HdeRpB4ZC1_20K4T)GR5b#3_*3~)L>YVJ4K5h;iJrNcLpof-w04F6MCn zU~I`5h$KgY-sV|$`L3P0omYHCzj8&t^4-G)8Yw?r$O8bg)7y|KQA?8mVBXyJU$(Ow)95*O_6Ml@(j6JS7^*>{@N*iK6Kn7PUHu z!{N;7*B2t)5rQNAK89g9QJ*Ta4y$#AeSW&}oP^FR-G6xRgDB-orINyeytC&nto!z3 z7)DXIC*YM8Wf+D{&*}gmG#-_%OtY+5uUx$j0PP)D@^ZCX@#p|xFc|$lzgDAWOA8Li z!s3#}VwK6Hb#>KUUDwu4ty2~9veH5ZU}R()UPP9ctEsFgZE5Y?>?{Qw#~T{=cGdlMzhT8g zexp&#+m2)7SO$&#?hX#=(JWpvf&6~b8Ear}YI^2d|L41vWo1W?9NJq`6+8en2#?2m z`SkP67h0~}=+o)u!U_q6#a;#2*HHJwiQ_;0`J3w|5L>inaoely#WZhb7$zqtv#)<> zadA0CCKCwwgCS4KZnETZSz4On(+gqNf$Wl9E*FZTVv#6dqfi8qNJO5Hz%`3xcCEJZ zL=pS4kddL{5S*#$nb0Wx`b{;8^aC&)BDQTUilRg!Vaw(A>o@w?TPozYf3(TTf`x^p z=tJN8>T3}M>Dmx-fAaC;N@d#k#FWeF%E(Y1Xxz8BxYX6zy{_59!eWX{R#sYUHZRoe zt?nP(hBWep#YI0!=I7<2F}}>Ez2oX*j~>a()$o&&din+flQM!J3i7lSm8B?(-W(cP z++?b_4yjZk6ee@I1OP}R;_U29ilhQxopfbdeSOXBtR6+tU_i{_aE8tSDUvi9=VemK zx|wNJRjF7kHk%i`UcXE#&M(Nd+wGILT>iR+I72fYuXnQ;z(%HMk%z$=u=)5-e&yll zw+`gJ);2k@9J&5YBfaBA6gUDR0uQEDr|>zAIkFE%7X%z!!o%MgGAZ~(V@}FCAcoU2;5Ek}gT&Tr;e^J7g-53u-eYhqdyU+_`Sm zJ@4>3eG~xbSKU_)D_jh%REi+dpzYcrZAjw^Imlb1&X|rd005WM-Fc;3tXJ$)E3Lk!W-{9bg&*#h4szpL!d&lik zLl(*GT5aWt*!xB#RiWVE`1r(RFw-JTnhZsf4DBYkyy&NLSG#X~=?kZu4mC#EK7ieB zKYRXSl#LMtX{g^jGoxGF;5KGv=ZXsRPdxSr!QnXUj`ohO4?aA*vChQqaJ>1;-+b=5 zr;`PO8$Ep&F1E&`?Pgf5*5Q%S!oq?;zFU;6tlBPIY&&qEL8VfvR7yWdl75nx#0!l0 z@W^;??_dNGx5(3~i;C8caLU!@1^^>`=7r+WKuvV`Sh#mERQQ>sSH#DRFuo8#f=Hyd+*GZ5RJ zWmvvgmC41iGs7k?MHk71e^QmXvB*ut!@s&W`lcQ3yCOA8IyZIeV*Cux-}bHfv4c6aV;FN#F|mpzYcrEtL^p zEk)lLvb*ET`A1Ro__4!{`x}-mR-f0WP^1uCf}a#y0y(?AQmeC36x~}>wYR1!*rlWE zYA~5DK-|_WK6|Ct8w3030)G9~?jG1=AAMssKv=rFdnl5uC@;Y=%)GF8wYw)+0n2W( z#9~oKrs{I*rith*lI?cqxpNmQtIDdYD{u^3wb|R+yQXK>Vk+#CBO21UpNPG0G)Se= z;-bRQ(J_*u007y4_}Q@SP!#2G?}P03T|yF{UtCx~5V2(;@OZqUqP#qfI&dUDMbV~t z^U(09*&I*R_5Fw#ya#Yo(!I@ozX$wt*Yw?Z^=CvF25v6c%hH6ewoVK!+>ufw;cmwR4R5F}+FbvkXBH#l?k5ym%@;IXI4UaF5$_Uw)gm1|K00qDJ9^^>Xx`^5{W4 zB<=9sF;C**STb?%PMg?vws%NZy!5}{v@f{h^uZ4`)HNMC@Y?G?@<&Qb-nB$;uc|7` zN=pg@aa|O{Xo>;=6hi~mR)8P~APlosdG)LReE06^onRAuKHu>0C|hwV!!SOti{K_9 zF>bniHu3pgyYvnbAeF_3n!9?Q;q41_VKr zmKH}iZh~PLug693cqr>DSP}#vNpg5(q#!R}BDoI^0N*|M0BfN#_F*O^5>I{6XMxXP z@aAsA$B|b`h5s??7_r9o(#Qb*7+*EH3yIQRF#wyLxRPL3f#Wz^)IAg)KjwAkXPJij zx}8?xzT_0X4F!7%62!#U+qeJi Date: Wed, 12 Feb 2020 18:04:45 +0200 Subject: [PATCH 048/112] [core/output] rename variables to more suggestive names Getting ready for next small refactoring. --- bumblebee/output.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index b2df265..daf60ea 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -354,11 +354,11 @@ class WidgetDrawer(object): """add custom theme colors for prefix""" if self._markup == "pango": # add prefix/suffix colors - fg = self._theme.prefix_fg(widget) - bg = self._theme.prefix_bg(widget) + prefix_fg = self._theme.prefix_fg(widget) + prefix_bg = self._theme.prefix_bg(widget) self._prefix = "{}".format( - "foreground='{}'".format(fg) if fg else "", - "background='{}'".format(bg) if bg else "", + "foreground='{}'".format(prefix_fg) if prefix_fg else "", + "background='{}'".format(prefix_bg) if prefix_bg else "", self._prefix ) From d1238f9dca15fa9c689c79d11f28aa37de406064 Mon Sep 17 00:00:00 2001 From: me Date: Wed, 12 Feb 2020 18:08:03 +0200 Subject: [PATCH 049/112] [core/output] make prefix bg/fg variables instance variables There are plans to use them in more than one method --- bumblebee/output.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index daf60ea..920c78c 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -337,6 +337,8 @@ class WidgetDrawer(object): self._markup = None self._full_text = None self._prefix = None + self._prefix_fg = None + self._prefix_bg = None self._suffix = None def add_separator(self, widget, separator): @@ -354,11 +356,11 @@ class WidgetDrawer(object): """add custom theme colors for prefix""" if self._markup == "pango": # add prefix/suffix colors - prefix_fg = self._theme.prefix_fg(widget) - prefix_bg = self._theme.prefix_bg(widget) + self._prefix_fg = self._theme.prefix_fg(widget) + self._prefix_bg = self._theme.prefix_bg(widget) self._prefix = "{}".format( - "foreground='{}'".format(prefix_fg) if prefix_fg else "", - "background='{}'".format(prefix_bg) if prefix_bg else "", + "foreground='{}'".format(self._prefix_fg) if self._prefix_fg else "", + "background='{}'".format(self._prefix_bg) if self._prefix_bg else "", self._prefix ) From f44d48e7bdeadba78d832ab1ef07f35ebcb1292c Mon Sep 17 00:00:00 2001 From: me Date: Wed, 12 Feb 2020 18:11:22 +0200 Subject: [PATCH 050/112] [core/output] micro-optimization refactoring If markup isn't pango, skip the add_prefix_colors() call entirely --- bumblebee/output.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bumblebee/output.py b/bumblebee/output.py index 920c78c..51ae30a 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -354,21 +354,21 @@ class WidgetDrawer(object): def add_prefix_colors(self, widget): """add custom theme colors for prefix""" - if self._markup == "pango": - # add prefix/suffix colors - self._prefix_fg = self._theme.prefix_fg(widget) - self._prefix_bg = self._theme.prefix_bg(widget) - self._prefix = "{}".format( - "foreground='{}'".format(self._prefix_fg) if self._prefix_fg else "", - "background='{}'".format(self._prefix_bg) if self._prefix_bg else "", - self._prefix - ) + self._prefix = "{}".format( + "foreground='{}'".format(self._prefix_fg) if self._prefix_fg else "", + "background='{}'".format(self._prefix_bg) if self._prefix_bg else "", + self._prefix + ) def add_prefix(self, widget, padding): """add prefix to full_text""" self._prefix = self._theme.prefix(widget, padding) - self.add_prefix_colors(widget) + if self._markup == "pango": + # add prefix/suffix colors + self._prefix_fg = self._theme.prefix_fg(widget) + self._prefix_bg = self._theme.prefix_bg(widget) + self.add_prefix_colors(widget) if self._prefix: self._full_text = u"{}{}".format(self._prefix, self._full_text) From 89c6afb49371ae05256c8c2bca9b355e5f011b1e Mon Sep 17 00:00:00 2001 From: me Date: Wed, 12 Feb 2020 19:43:01 +0200 Subject: [PATCH 051/112] implement --iconmarkup argument WARNING: highly experimental This allows fine tuning of icons via raw Pango markup. Is backwards compatible with icon foreground/background support in themes (if those settings are present in the theme but are missing from the icon markup template - they are merged in). --- bumblebee/config.py | 5 +++++ bumblebee/output.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/bumblebee/config.py b/bumblebee/config.py index f9c766a..c40cfae 100644 --- a/bumblebee/config.py +++ b/bumblebee/config.py @@ -17,6 +17,7 @@ THEME_HELP = "Specify the theme to use for drawing modules" PARAMETER_HELP = "Provide configuration parameters in the form of .=" 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" +ICONMARKUP_HELP = "A Python format string that is valid Pango markup used for low level customization of icons on top of themes. There is no validation performed, this is delegated to the user. Used together with --markup=pango. Example: \"{}\". WARNING: highly experimental feature" class print_usage(argparse.Action): def __init__(self, option_strings, dest, nargs=None, **kwargs): @@ -61,6 +62,7 @@ def create_parser(): 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("--iconmarkup", default="none", help=ICONMARKUP_HELP) parser.add_argument("-p", "--parameters", nargs="+", action='append', default=[], help=PARAMETER_HELP) parser.add_argument("-l", "--list", choices=["modules", "themes"], action=print_usage, @@ -129,4 +131,7 @@ class Config(bumblebee.store.Store): def markup(self): return self._args.markup + def iconmarkup(self): + return self._args.iconmarkup + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/output.py b/bumblebee/output.py index 51ae30a..5f5529b 100644 --- a/bumblebee/output.py +++ b/bumblebee/output.py @@ -6,6 +6,7 @@ import sys import json import uuid import logging +import xml.etree.ElementTree import bumblebee.store import bumblebee.util @@ -339,6 +340,7 @@ class WidgetDrawer(object): self._prefix = None self._prefix_fg = None self._prefix_bg = None + self._iconmarkup = None self._suffix = None def add_separator(self, widget, separator): @@ -352,6 +354,19 @@ class WidgetDrawer(object): "separator_block_width": self._theme.separator_block_width(widget), }) + def add_prefix_iconmarkup(self, widget): + """add custom Pango markup for prefix""" + element = xml.etree.ElementTree.XML(self._iconmarkup) + # if the custom markup has neither 'foreground' or 'fgcolor' + # attributes, but theme has prefixfg, merge it + if 'foreground' not in element.keys() and 'fgcolor' not in element.keys() and self._prefix_fg is not None: + element.set("foreground", self._prefix_fg) + # if the custom markup has neither 'background' or 'bgcolor' + # attributes, but theme has prefixbg, merge it + if 'background' not in element.keys() and 'bgcolor' not in element.keys() and self._prefix_bg is not None: + element.set("background", self._prefix_bg) + self._prefix = xml.etree.ElementTree.tostring(element).decode("utf-8").format(self._prefix) + def add_prefix_colors(self, widget): """add custom theme colors for prefix""" self._prefix = "{}".format( @@ -368,15 +383,27 @@ class WidgetDrawer(object): # add prefix/suffix colors self._prefix_fg = self._theme.prefix_fg(widget) self._prefix_bg = self._theme.prefix_bg(widget) - self.add_prefix_colors(widget) + self._iconmarkup = self._config.iconmarkup() + if self._iconmarkup != "none": + self.add_prefix_iconmarkup(widget) + else: + self.add_prefix_colors(widget) if self._prefix: self._full_text = u"{}{}".format(self._prefix, self._full_text) + def add_suffix_iconmarkup(self, widget): + """add custom Pango markup for suffix""" + self._suffix = self._iconmarkup.format(self._suffix) + def add_suffix(self, widget, padding): """add suffix to full_text""" self._suffix = self._theme.suffix(widget, padding) + if self._markup == "pango": + if self._iconmarkup != "none": + self.add_suffix_iconmarkup(widget) + if self._suffix: self._full_text = u"{}{}".format(self._full_text, self._suffix) From 59663e9d1cf727db17f667710fc89f98710415b5 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Wed, 12 Feb 2020 21:17:29 +0100 Subject: [PATCH 052/112] [doc] Update Module.md (escaping) fixes #546 --- Modules.md | 28 ++++++++++++++-------------- bumblebee/config.py | 5 ++++- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Modules.md b/Modules.md index 86b2a16..85d22c5 100644 --- a/Modules.md +++ b/Modules.md @@ -2,23 +2,23 @@ |Name |Description | |-----|------------| |amixer |get volume level

| -|apt |Displays APT package update information (/)
Requires the following debian packages:
* aptitude

* python-parse

| |arch-update |Check updates to Arch Linux.
| |battery-upower |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
| -|battery |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 |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)
| |battery_all |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
| |bluetooth |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)

| |brightness |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)

| |caffeine |Enable/disable automatic screen locking.

Requires the following executables:
* xdg-screensaver
* xdotool
* xprop (as dependency for xdotool)
* notify-send
| -|cmus |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})
* 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.
| +|cmus |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.
| |cpu |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}%")
| +|cpu2 |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.

| |currency |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
| |datetime |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
| |datetimetz |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
| |deadbeef |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

| |deezer |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
| |disk |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)
| -|dnf |Displays DNF package update information (///)

Requires the following executable:
* dnf

Parameters:
* dnf.interval: Time in minutes between two consecutive update checks (defaults to 30 minutes)

| +|dnf |Displays DNF package update information (\/\/\/\)

Requires the following executable:
* dnf

Parameters:
* dnf.interval: Time in minutes between two consecutive update checks (defaults to 30 minutes)

| |docker_ps |Displays the number of docker containers running

Requires the following python packages:
* docker

| |dunst |Toggle dunst notifications. | |error |Draws an error widget.
| @@ -37,25 +37,25 @@ |load |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)
| |memory |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}"
| |mocp |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
| -|mpd |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 |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.
| |network_traffic |Displays network traffic
* No extra configuration needed
| -|nic |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}")
| +|nic |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}")
| |notmuch_count |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/)
| |nvidiagpu |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
| |pacman |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
| |pihole |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)
| |ping |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)
| -|pomodoro |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!"'
| +|pomodoro |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!"'. If you want to chain multiple commands,
please use an external wrapper script and invoke that. The module itself does
not support command chaining (see https://github.com/tobi-wan-kenobi/bumblebee-status/issues/532
for a detailled explanation)
| |prime |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

| |progress |
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
| |publicip |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")
| -|pulseaudio |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.

Requires the following executable:
* pulseaudio
* pactl
* pavucontrol
| +|pulseaudio |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
| |redshift |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"
| |rotation |Shows a widget for each connected screen and allows the user to loop through different orientations.

Requires the following executable:
* xrandr
| |rss |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
| -|sensors |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. //.../), 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)
| +|sensors |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. \/\/.../\), 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)
| |sensors2 |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)
| -|shell | 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
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)
| +|shell | 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)
| |shortcut |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)
| |spaceapi |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%%"
| |spacer |Draws a widget with configurable text content.

Parameters:
* spacer.text: Widget contents (defaults to empty string)
| @@ -67,12 +67,12 @@ |test |Test module
| |title |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
| |todo |Displays the number of todo items from a text file

Parameters:
* todo.file: File to read TODOs from (defaults to ~/Documents/todo.txt)
| -|traffic |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)
| +|traffic |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)
* traffic.format: Format string for download/upload speeds.
Defaults to "{:.2f}"
* traffic.graphlen: Graph lenth in seconds. Positive even integer. Each
char shows 2 seconds. If set, enables up/down traffic
graphs
| |twmn |Toggle twmn notifications. | |uptime |Displays the system uptime. | |vault |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)
| -|vpn | 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

| +|vpn | 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 \

| |weather |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
| |xkcd |Opens a random xkcd comic in the browser. | -|xrandr |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." 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
| -|zpool |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)`:

```
ALL = (root) NOPASSWD: /usr/bin/zpool list
```

Be aware of security implications of doing this!
| +|xrandr |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.\" 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
| +|zpool |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)`:

```
\ ALL = (root) NOPASSWD: /usr/bin/zpool list
```

Be aware of security implications of doing this!
| diff --git a/bumblebee/config.py b/bumblebee/config.py index c40cfae..54ba080 100644 --- a/bumblebee/config.py +++ b/bumblebee/config.py @@ -45,7 +45,10 @@ class print_usage(argparse.Action): try: mod = importlib.import_module("bumblebee.modules.{}".format(m["name"])) if self._args.list_format == "markdown": - print("|{} |{} |".format(m["name"], mod.__doc__.replace("\n", "
"))) + doc = mod.__doc__.replace("<", "\<") + doc = doc.replace(">", "\>") + doc = doc.replace("\n", "
") + print("|{} |{} |".format(m["name"], doc)) else: print(textwrap.fill("{}:".format(m["name"]), 80, initial_indent=self._indent*2, subsequent_indent=self._indent*2)) From 6784aaa0d5c54d7655755b975c6af564b9becbf7 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Fri, 14 Feb 2020 20:48:09 +0100 Subject: [PATCH 053/112] [doc] Add user-contrib link fixes #554 --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 72c73d4..035614d 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ bar { ``` # Documentation + See [the wiki](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki) for documentation. See [FAQ](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/FAQ) for, well, FAQs. @@ -79,6 +80,10 @@ Other resources: * [How to write a theme](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/How-to-write-a-theme) * [How to write a module](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/How-to-write-a-module) +User contributions: + +[@somospocos:bumblebee-status-contrib](https://github.com/somospocos/bumblebee-status-contrib): Collected resources and useful tricks by @somospocos + # Installation ``` $ git clone git://github.com/tobi-wan-kenobi/bumblebee-status From bff77885142cbb45baeceaece987a566a83bf72e Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Fri, 14 Feb 2020 20:54:08 +0100 Subject: [PATCH 054/112] [doc] Add missing executables to README.md fixes #551 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 035614d..91a5077 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,8 @@ Modules and commandline utilities are only required for modules, the core itself * zpool (for module 'zpool') * progress (for module 'progress') * i3exit (for module 'system') +* dunst (for module 'dunst') +* hddtemp (for module 'hddtemp') # Examples Here are some screenshots for all themes that currently exist: From 8ae8fbb98900bcf059ddf4614e673510723a604d Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Fri, 14 Feb 2020 21:39:01 +0100 Subject: [PATCH 055/112] [engine] Add override for widget IDs Add a generic ".id" parameter that allows a user to override the auto-generated IDs for a widget - the parameter is a list of IDs that will replace each widget's ID in turn. see #547 --- bumblebee/engine.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bumblebee/engine.py b/bumblebee/engine.py index 1159884..377dfc4 100644 --- a/bumblebee/engine.py +++ b/bumblebee/engine.py @@ -295,8 +295,12 @@ class Engine(object): self._current_module = module module.update_wrapper(module.widgets()) if module.error is None: + widget_ids = module.parameter('id', '').split(',') + idx = 0 for widget in module.widgets(): widget.link_module(module) + widget.id = widget_ids[idx] if idx < len(widget_ids) else widget.id + idx = idx + 1 self._output.draw(widget=widget, module=module, engine=self) else: self._output.draw(widget=module.errorWidget(), module=module, engine=self) From ef35c957b2b25275ab6e6adc9983fcd11f7b10c3 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Fri, 14 Feb 2020 21:39:55 +0100 Subject: [PATCH 056/112] [input] Add per-PID UNIX socket for additional commands Allow passing in commands / events via a unix socket. This should allow for "emulating" input events. see #547 --- bumblebee/input.py | 80 ++++++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/bumblebee/input.py b/bumblebee/input.py index af86757..adeaa65 100644 --- a/bumblebee/input.py +++ b/bumblebee/input.py @@ -1,9 +1,11 @@ """Input classes""" +import os import sys import json import uuid import time +import socket import select import logging import threading @@ -23,38 +25,62 @@ def is_terminated(): return True return False +class CommandSocket(object): + def __init__(self): + self._name = "/tmp/.bumblebee-status.{}".format(os.getpid()) + self._socket = None + + def __enter__(self): + self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self._socket.bind(self._name) + self._socket.listen(5) + return self._socket + + def __exit__(self, type, value, traceback): + self._socket.close() + os.unlink(self._name) + 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 + with CommandSocket() as cmdsocket: + poll = select.poll() + poll.register(sys.stdin, select.POLLIN) + poll.register(cmdsocket, select.POLLIN) + log.debug("starting click event processing") + while inp.running: + if is_terminated(): + return + try: - event = json.loads(line) - if "instance" in event: - inp.callback(event) - inp.redraw() + events = poll.poll(1000) + except Exception: + continue + for fileno, event in events: + + if fileno == cmdsocket.fileno(): + tmp, _ = cmdsocket.accept() + line = tmp.recv(4096).decode() + tmp.close() 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 + 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""" From 65137f294e97b98723de44bd5363b8fa0e8a6be5 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Fri, 14 Feb 2020 21:52:23 +0100 Subject: [PATCH 057/112] [engine] Small bugfix if no IDs are configured see #547 --- bumblebee/engine.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bumblebee/engine.py b/bumblebee/engine.py index 377dfc4..fd3bbe9 100644 --- a/bumblebee/engine.py +++ b/bumblebee/engine.py @@ -295,11 +295,14 @@ class Engine(object): self._current_module = module module.update_wrapper(module.widgets()) if module.error is None: - widget_ids = module.parameter('id', '').split(',') + widget_ids = [] + if module.parameter('id'): + widget_ids = module.parameter('id').split(',') idx = 0 for widget in module.widgets(): widget.link_module(module) - widget.id = widget_ids[idx] if idx < len(widget_ids) else widget.id + if idx < len(widget_ids): + widget.id = widget_ids[idx] idx = idx + 1 self._output.draw(widget=widget, module=module, engine=self) else: From 95ac72d3050ada53419222b9ce79a545f3eb13ee Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Fri, 14 Feb 2020 21:54:06 +0100 Subject: [PATCH 058/112] [core] Add bumblebee-ctl to trigger remote commands see #547 --- bumblebee-ctl | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100755 bumblebee-ctl diff --git a/bumblebee-ctl b/bumblebee-ctl new file mode 100755 index 0000000..e08d45c --- /dev/null +++ b/bumblebee-ctl @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +import argparse +import json +import glob +import socket + +button = { + 'left-mouse': 1, + 'middle-mouse': 2, + 'right-mouse': 3, + 'wheel-up': 4, + 'wheel-down': 5, +} + +def main(): + parser = argparse.ArgumentParser(description='send commands to bumblebee-status') + parser.add_argument('-b', '--button', choices=['left-mouse', 'right-mouse', 'middle-mouse', 'wheel-up', 'wheel-down'], help='button to emulate', default='left-mouse') + parser.add_argument('-i', '--id', help='ID of widget to trigger', required=True) + parser.add_argument('-n', '--name', help='name of the module to trigger', required=True) + + args = parser.parse_args() + + for f in glob.glob('/tmp/.bumblebee-status.*'): + print('accessing {}'.format(f)) + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(f) + s.sendall(json.dumps({ + 'name': args.name, + 'instance': args.id, + 'button': button[args.button], + }).encode('ascii')) + +if __name__ == "__main__": + main() + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From 6f6ac9772613208f27401b80454b0d3e1284cbbc Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Fri, 14 Feb 2020 21:56:35 +0100 Subject: [PATCH 059/112] [bumblebee-ctl] replace name with module see #547 --- bumblebee-ctl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bumblebee-ctl b/bumblebee-ctl index e08d45c..cbb6ad5 100755 --- a/bumblebee-ctl +++ b/bumblebee-ctl @@ -17,7 +17,7 @@ def main(): parser = argparse.ArgumentParser(description='send commands to bumblebee-status') parser.add_argument('-b', '--button', choices=['left-mouse', 'right-mouse', 'middle-mouse', 'wheel-up', 'wheel-down'], help='button to emulate', default='left-mouse') parser.add_argument('-i', '--id', help='ID of widget to trigger', required=True) - parser.add_argument('-n', '--name', help='name of the module to trigger', required=True) + parser.add_argument('-m', '--module', help='name of the module to trigger', required=True) args = parser.parse_args() @@ -26,7 +26,7 @@ def main(): s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect(f) s.sendall(json.dumps({ - 'name': args.name, + 'name': args.module, 'instance': args.id, 'button': button[args.button], }).encode('ascii')) From 6da755ab84f540103498d325ef39122ba494bb58 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sat, 15 Feb 2020 11:38:36 +0100 Subject: [PATCH 060/112] [core] Fix error widget The parameters for the error widget were wrong (dict vs. Config) --- bumblebee-status | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bumblebee-status b/bumblebee-status index bb31f94..7e65721 100755 --- a/bumblebee-status +++ b/bumblebee-status @@ -67,7 +67,9 @@ def main(): import time while True: output.begin() - error = bumblebee.modules.error.Module(engine, config) + error = bumblebee.modules.error.Module(engine, { + "config": config, "name": "error" + }) error.set("exception occurred: {} in {}".format(e, module)) widget = error.widgets()[0] widget.link_module(error) From 57e1b1eb81b159d137e3ae75276490d81da67570 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sat, 15 Feb 2020 11:39:00 +0100 Subject: [PATCH 061/112] [modules/battery-upower] Fix capacity default value If the capacity isn't set, get() returns None, which is not comparable. Use -1 instead, which makes the capacity unknown. fixes #555 --- bumblebee/modules/battery-upower.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee/modules/battery-upower.py b/bumblebee/modules/battery-upower.py index 24efecf..f3d59ba 100644 --- a/bumblebee/modules/battery-upower.py +++ b/bumblebee/modules/battery-upower.py @@ -240,7 +240,7 @@ class Module(bumblebee.engine.Module): def state(self, widget): state = [] - capacity = widget.get("capacity") + capacity = widget.get("capacity", -1) if capacity < 0: return ["critical", "unknown"] From 2cc6fcc5dc7265fb8c4de9d9951557a89c81be7d Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sun, 16 Feb 2020 13:40:51 +0100 Subject: [PATCH 062/112] [doc] Add bumblebee-ctl fixes #547 --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 91a5077..dd95423 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,25 @@ For example: `$ bumblebee-status -p disk.left-click="nautilus {instance}"` +Advanced usage: + +You can also "programmatically" trigger click events like this: +``` +$ bumblebee-ctl --button