From 21cbbe685d14ce2831c1d0f3c07b18427816e563 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 11 Sep 2022 13:12:51 +0200 Subject: [PATCH 01/93] [modules/scroll] add preliminary version of scrolling module add a scrolling module that can be used to scroll the whole bar to an arbitrary number of widgets. its parameter is "width", which determines the number of widgets to display. see #921 --- bumblebee_status/core/module.py | 9 +++++ bumblebee_status/core/output.py | 23 +++++++++++- bumblebee_status/modules/core/scroll.py | 50 +++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 bumblebee_status/modules/core/scroll.py diff --git a/bumblebee_status/core/module.py b/bumblebee_status/core/module.py index 84c3ab5..56070bf 100644 --- a/bumblebee_status/core/module.py +++ b/bumblebee_status/core/module.py @@ -112,6 +112,15 @@ class Module(core.input.Object): def hidden(self): return False + """Override this to show the module even if it normally would be scrolled away + + :return: True if the module should be hidden, False otherwise + :rtype: boolean + """ + + def scroll(self): + return True + """Retrieve CLI/configuration parameters for this module. For example, if the module is called "test" and the user specifies "-p test.x=123" on the commandline, using self.parameter("x") retrieves the value 123. diff --git a/bumblebee_status/core/output.py b/bumblebee_status/core/output.py index 1bd5038..b17811e 100644 --- a/bumblebee_status/core/output.py +++ b/bumblebee_status/core/output.py @@ -146,11 +146,14 @@ class i3(object): self.__content = {} self.__theme = theme self.__config = config + self.__offset = 0 self.__lock = threading.Lock() core.event.register("update", self.update) core.event.register("start", self.draw, "start") core.event.register("draw", self.draw, "statusline") core.event.register("stop", self.draw, "stop") + core.event.register("output.scroll-left", self.scroll_left) + core.event.register("output.scroll-right", self.scroll_right) def content(self): return self.__content @@ -182,7 +185,7 @@ class i3(object): cb = getattr(self, what) data = cb(args) if args else cb() if "blocks" in data: - sys.stdout.write(json.dumps(data["blocks"], default=dump_json)) + sys.stdout.write(json.dumps(data, default=dump_json)) if "suffix" in data: sys.stdout.write(data["suffix"]) sys.stdout.write("\n") @@ -223,13 +226,29 @@ class i3(object): blk.set("__state", state) return blk + def scroll_left(self): + if self.__offset > 0: + self.__offset -= 1 + + def scroll_right(self): + self.__offset += 1 + def blocks(self, module): blocks = [] if module.minimized: blocks.extend(self.separator_block(module, module.widgets()[0])) blocks.append(self.__content_block(module, module.widgets()[0])) + self.__widgetcount += 1 return blocks + + width = self.__config.get("output.width", 0) for widget in module.widgets(): + if module.scroll() == True and width > 0: + self.__widgetcount += 1 + if self.__widgetcount-1 < self.__offset: + continue + if self.__widgetcount-1 >= self.__offset + width: + continue if widget.module and self.__config.autohide(widget.module.name): if not any( state in widget.state() for state in ["warning", "critical", "no-autohide"] @@ -244,6 +263,7 @@ class i3(object): blocks.extend(self.separator_block(module, widget)) blocks.append(self.__content_block(module, widget)) core.event.trigger("next-widget") + core.event.trigger("output.done", self.__offset, self.__widgetcount) return blocks def update(self, affected_modules=None, redraw_only=False, force=False): @@ -274,6 +294,7 @@ class i3(object): def statusline(self): blocks = [] + self.__widgetcount = 0 for module in self.__modules: blocks.extend(self.blocks(module)) return {"blocks": blocks, "suffix": ","} diff --git a/bumblebee_status/modules/core/scroll.py b/bumblebee_status/modules/core/scroll.py new file mode 100644 index 0000000..44f9c5e --- /dev/null +++ b/bumblebee_status/modules/core/scroll.py @@ -0,0 +1,50 @@ +# pylint: disable=C0111,R0903 + +""" +""" + +import core.module +import core.widget +import core.input +import core.event + +import util.format + +class Module(core.module.Module): + def __init__(self, config, theme): + super().__init__(config, theme, []) + self.__offset = 0 + self.__widgetcount = 0 + w = self.add_widget(full_text = "<") + core.input.register(w, button=core.input.LEFT_MOUSE, cmd=self.scroll_left) + w = self.add_widget(full_text = ">") + core.input.register(w, button=core.input.LEFT_MOUSE, cmd=self.scroll_right) + self.__width = util.format.asint(self.parameter("width")) + config.set("output.width", self.__width) + core.event.register("output.done", self.update_done) + + + def scroll_left(self, _): + if self.__offset > 0: + core.event.trigger("output.scroll-left") + + def scroll_right(self, _): + if self.__offset + self.__width < self.__widgetcount: + core.event.trigger("output.scroll-right") + + def update_done(self, offset, widgetcount): + self.__offset = offset + self.__widgetcount = widgetcount + + def scroll(self): + return False + + def state(self, widget): + if widget.id == self.widgets()[0].id: + if self.__offset == 0: + return ["warning"] + if self.__offset + self.__width >= self.__widgetcount: + return ["warning"] + return [] + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From 3f97ea6a39da58361792750c23eff7640d06c1d7 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 11 Sep 2022 13:16:06 +0200 Subject: [PATCH 02/93] [doc] add scroll menu see #921 --- bumblebee_status/modules/core/scroll.py | 5 ++++- docs/modules.rst | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/bumblebee_status/modules/core/scroll.py b/bumblebee_status/modules/core/scroll.py index 44f9c5e..33fdd7b 100644 --- a/bumblebee_status/modules/core/scroll.py +++ b/bumblebee_status/modules/core/scroll.py @@ -1,6 +1,9 @@ # pylint: disable=C0111,R0903 -""" +"""Displays two widgets that can be used to scroll the whole status bar + +Parameters: + * scroll.width: Width (in number of widgets) to display """ import core.module diff --git a/docs/modules.rst b/docs/modules.rst index e3a055f..0f3aa15 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -303,6 +303,14 @@ Parameters: .. image:: ../screenshots/redshift.png +scroll +~~~~~~ + +Displays two widgets that can be used to scroll the whole status bar + +Parameters: + * scroll.width: Width (in number of widgets) to display + sensors2 ~~~~~~~~ From c40f59f7beae4141e1241cfdcd8417c5d0ad554a Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 11 Sep 2022 16:00:35 +0200 Subject: [PATCH 03/93] [modules/scroll] edge case error --- bumblebee_status/modules/core/scroll.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee_status/modules/core/scroll.py b/bumblebee_status/modules/core/scroll.py index 33fdd7b..b897c5b 100644 --- a/bumblebee_status/modules/core/scroll.py +++ b/bumblebee_status/modules/core/scroll.py @@ -46,7 +46,7 @@ class Module(core.module.Module): if widget.id == self.widgets()[0].id: if self.__offset == 0: return ["warning"] - if self.__offset + self.__width >= self.__widgetcount: + elif self.__offset + self.__width >= self.__widgetcount: return ["warning"] return [] From 0151d2045115b8441c9166d7cbc06234990b2985 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 18 Sep 2022 09:04:08 +0200 Subject: [PATCH 04/93] [doc] update badges --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 27031d1..96a8e99 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,13 @@ logo courtesy of [kellya](https://github.com/kellya) - thank you! [![Documentation Status](https://readthedocs.org/projects/bumblebee-status/badge/?version=main)](https://bumblebee-status.readthedocs.io/en/main/?badge=main) +![Contributors](https://img.shields.io/github/contributors-anon/tobi-wan-kenobi/bumblebee-status) +![Commits since release](https://img.shields.io/github/commits-since/tobi-wan-kenobi/bumblebee-status/latest) ![AUR version (release)](https://img.shields.io/aur/version/bumblebee-status) ![AUR version (git)](https://img.shields.io/aur/version/bumblebee-status-git) -[![PyPI version](https://badge.fury.io/py/bumblebee-status.svg)](https://badge.fury.io/py/bumblebee-status) +![PyPI version](https://img.shields.io/pypi/v/bumblebee-status) [![Tests](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/autotest.yml/badge.svg?branch=main)](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/autotest.yml) + [![Code Climate](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/gpa.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status) [![Test Coverage](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/coverage.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage) [![Issue Count](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/issue_count.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status) From 38d3a6d4c4c7d6a43afab929bad40373b0e8299c Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 18 Sep 2022 09:04:39 +0200 Subject: [PATCH 05/93] [doc] rearrange badges --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 96a8e99..2f41627 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ logo courtesy of [kellya](https://github.com/kellya) - thank you! [![Documentation Status](https://readthedocs.org/projects/bumblebee-status/badge/?version=main)](https://bumblebee-status.readthedocs.io/en/main/?badge=main) -![Contributors](https://img.shields.io/github/contributors-anon/tobi-wan-kenobi/bumblebee-status) ![Commits since release](https://img.shields.io/github/commits-since/tobi-wan-kenobi/bumblebee-status/latest) ![AUR version (release)](https://img.shields.io/aur/version/bumblebee-status) ![AUR version (git)](https://img.shields.io/aur/version/bumblebee-status-git) ![PyPI version](https://img.shields.io/pypi/v/bumblebee-status) +![Contributors](https://img.shields.io/github/contributors-anon/tobi-wan-kenobi/bumblebee-status) [![Tests](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/autotest.yml/badge.svg?branch=main)](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/autotest.yml) [![Code Climate](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/gpa.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status) From 1c19250fe549c036c971d22d84c21fe3e62f48b1 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 18 Sep 2022 16:50:43 +0200 Subject: [PATCH 06/93] [core/output] fix broken output --- bumblebee_status/core/output.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee_status/core/output.py b/bumblebee_status/core/output.py index b17811e..9ff3010 100644 --- a/bumblebee_status/core/output.py +++ b/bumblebee_status/core/output.py @@ -185,7 +185,7 @@ class i3(object): cb = getattr(self, what) data = cb(args) if args else cb() if "blocks" in data: - sys.stdout.write(json.dumps(data, default=dump_json)) + sys.stdout.write(json.dumps(data["blocks"], default=dump_json)) if "suffix" in data: sys.stdout.write(data["suffix"]) sys.stdout.write("\n") From 7ec3adfa4732638d143fa0aa04ed740c662f3637 Mon Sep 17 00:00:00 2001 From: Jose Javier ALONSO MOYA Date: Tue, 20 Sep 2022 14:59:25 +0200 Subject: [PATCH 07/93] contrib/network_traffic dependency in docs --- docs/modules.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/modules.rst b/docs/modules.rst index c211e0c..96b8d21 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -1126,7 +1126,9 @@ network_traffic ~~~~~~~~~~~~~~~ Displays network traffic - * No extra configuration needed + +Requires the following library: + * netifaces contributed by `izn `_ - many thanks! From 7ae95ad6b6f57fae2526e410bac75aa4039c5d93 Mon Sep 17 00:00:00 2001 From: Ben Westover Date: Tue, 20 Sep 2022 23:15:38 -0400 Subject: [PATCH 08/93] Don't install manpages to /usr/usr `data_files` shouldn't have `usr/` in it; this causes the manpages to be installed to `/usr/usr/share/man/man1` instead of `/usr/share/man/man1`. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a63be64..a756c8a 100755 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ setup( ("share/bumblebee-status/themes", glob.glob("themes/*.json")), ("share/bumblebee-status/themes/icons", glob.glob("themes/icons/*.json")), ("share/bumblebee-status/utility", glob.glob("bin/*")), - ("usr/share/man/man1", glob.glob("man/*.1")), + ("share/man/man1", glob.glob("man/*.1")), ], packages=find_packages(exclude=["tests", "tests.*"]) ) From 88f24100ffc89ed443e0c3d40af77ae2f2b9e46f Mon Sep 17 00:00:00 2001 From: Ramon Saraiva Date: Tue, 27 Sep 2022 11:10:33 -0300 Subject: [PATCH 09/93] [themes] add moonlight theme (powerline) --- docs/themes.rst | 5 +++++ screenshots/themes/moonlight-powerline.png | Bin 0 -> 11940 bytes themes/moonlight-powerline.json | 24 +++++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 screenshots/themes/moonlight-powerline.png create mode 100644 themes/moonlight-powerline.json diff --git a/docs/themes.rst b/docs/themes.rst index 759ea86..16f69a6 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -97,3 +97,8 @@ List of available themes :alt: Default Default (nothing or -t default) + +.. figure:: ../screenshots/themes/moonlight-powerline.png + :alt: Moonlight Powerline + + Moonlight Powerline (-t moonlight-powerline) (contributed by `Ramon Saraiva `__) diff --git a/screenshots/themes/moonlight-powerline.png b/screenshots/themes/moonlight-powerline.png new file mode 100644 index 0000000000000000000000000000000000000000..025df6b55ac105e1103be2beced31669c60cd9f3 GIT binary patch literal 11940 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 literal 0 HcmV?d00001 diff --git a/themes/moonlight-powerline.json b/themes/moonlight-powerline.json new file mode 100644 index 0000000..b46b96a --- /dev/null +++ b/themes/moonlight-powerline.json @@ -0,0 +1,24 @@ +{ + "icons": ["awesome-fonts"], + "defaults": { + "separator-block-width": 0, + "warning": { + "fg": "#e4f3fa", + "bg": "#fc7b7b" + }, + "critical": { + "fg": "#e4f3fa", + "bg": "#ff5370" + } + }, + "cycle": [ + { + "fg": "#e4f3fa", + "bg": "#403c64" + }, + { + "fg": "#e4f3fa", + "bg": "#212337" + } + ] +} From 61fe7f6d3e2f9df32f4a58c46b23cb626689f9a8 Mon Sep 17 00:00:00 2001 From: tfwiii Date: Thu, 6 Oct 2022 13:49:37 +0700 Subject: [PATCH 10/93] Handled fail where core.location does not provide values for latitude and longitude. Added handling for coordinates N, S, E, W. --- bumblebee_status/modules/contrib/publicip.py | 25 ++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/bumblebee_status/modules/contrib/publicip.py b/bumblebee_status/modules/contrib/publicip.py index d79c2a1..ca0cd31 100644 --- a/bumblebee_status/modules/contrib/publicip.py +++ b/bumblebee_status/modules/contrib/publicip.py @@ -116,6 +116,8 @@ class Module(core.module.Module): def update(self): widget = self.widget() + __lat = None + __lon = None try: util.location.reset() @@ -123,10 +125,23 @@ class Module(core.module.Module): # Fetch fresh location information __info = util.location.location_info() - # Contstruct coordinates string - __lat = "{:.2f}".format(__info["latitude"]) - __lon = "{:.2f}".format(__info["longitude"]) - __coords = __lat + "°N" + "," + " " + __lon + "°E" + # Contstruct coordinates string if util.location has provided required info + if __lat and __lon: + __lat = "{:.2f}".format(__info["latitude"]) + __lon = "{:.2f}".format(__info["longitude"]) + if __lat < 0: + __coords = __lat + "°S" + else: + __coords = __lat + "°N" + __coords += "," + if __lon < 0: + __coords += __lon + "°W" + else: + __coords += __lon + "°E" + else: + __lat = "Unknown" + __lon = "Unknown" + __coords = "Unknown" # Set widget values widget.set("public_ip", __info["public_ip"]) @@ -139,6 +154,8 @@ class Module(core.module.Module): core.event.trigger("update", [widget.module.id], redraw_only=True) except Exception as ex: widget.set("public_ip", None) + print("OH NOES!") + print(__info) logging.error(str(ex)) def state(self, widget): From 605b749e2269a722c90ed9e5633c0e4715c41a1f Mon Sep 17 00:00:00 2001 From: tfwiii Date: Thu, 6 Oct 2022 14:21:43 +0700 Subject: [PATCH 11/93] Removed debugging prints --- bumblebee_status/modules/contrib/publicip.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bumblebee_status/modules/contrib/publicip.py b/bumblebee_status/modules/contrib/publicip.py index ca0cd31..7f91271 100644 --- a/bumblebee_status/modules/contrib/publicip.py +++ b/bumblebee_status/modules/contrib/publicip.py @@ -154,8 +154,6 @@ class Module(core.module.Module): core.event.trigger("update", [widget.module.id], redraw_only=True) except Exception as ex: widget.set("public_ip", None) - print("OH NOES!") - print(__info) logging.error(str(ex)) def state(self, widget): From cace02909e8e15a00def57fb0215076031581475 Mon Sep 17 00:00:00 2001 From: tfwiii Date: Sat, 8 Oct 2022 10:42:12 +0700 Subject: [PATCH 12/93] Bug fix improvements to publicip and util.location Fixed publicip bug arising from last PR review Simplified ip change detection code Added pause after location.reset() call to allow completion before query util.location - change order of information providers as default was not returning geo coords --- bumblebee_status/modules/contrib/publicip.py | 78 +++++++++----------- bumblebee_status/util/location.py | 22 +++--- 2 files changed, 45 insertions(+), 55 deletions(-) diff --git a/bumblebee_status/modules/contrib/publicip.py b/bumblebee_status/modules/contrib/publicip.py index 7f91271..9a8f468 100644 --- a/bumblebee_status/modules/contrib/publicip.py +++ b/bumblebee_status/modules/contrib/publicip.py @@ -60,43 +60,34 @@ class Module(core.module.Module): self.__monitor.start() def monitor(self): - default_route = None - interfaces = None + __previous_ips = set() + __current_ips = set() # Initially set to True to force an info update on first pass - information_changed = True + __information_changed = True self.update() while threading.main_thread().is_alive(): - # Look for any changes in the netifaces default route information + __current_ips.clear() + # Look for any changes to IP addresses try: - current_default_route = netifaces.gateways()["default"][2] + for interface in netifaces.interfaces(): + __current_ips.add(netifaces.ifaddresses(interface)[2][0]['addr']) + except: - # error reading out default gw -> assume none exists - current_default_route = None - if current_default_route != default_route: - default_route = current_default_route - information_changed = True + # If not ip address information found clear __current_ips + __current_ips.clear() + + # If a change of any interfaces' IP then flag change + if __current_ips.symmetric_difference(__previous_ips): + __previous_ips = __current_ips.copy() + __information_changed = True - # netifaces does not check ALL routing tables which might lead to false negatives - # (ref: http://linux-ip.net/html/routing-tables.html) so additionally... look for - # any changes in the netifaces interfaces information which might also be an inticator - # of a change of route/external IP - if not information_changed: # Only check if no routing table change found - try: - current_interfaces = netifaces.interfaces() - except: - # error reading interfaces information -> assume none exists - current_interfaces = None - if current_interfaces != interfaces: - interfaces = current_interfaces - information_changed = True - - # Update either routing or interface information has changed - if information_changed: - information_changed = False + # Update if change is flagged + if __information_changed: + __information_changed = False self.update() - + # Throttle the calls to netifaces time.sleep(1) @@ -104,11 +95,11 @@ class Module(core.module.Module): if widget.get("public_ip") is None: return "n/a" return self._format.format( - ip=widget.get("public_ip", "-"), - country_name=widget.get("country_name", "-"), - country_code=widget.get("country_code", "-"), - city_name=widget.get("city_name", "-"), - coordinates=widget.get("coordinates", "-"), + ip = widget.get("public_ip", "-"), + country_name = widget.get("country_name", "-"), + country_code = widget.get("country_code", "-"), + city_name = widget.get("city_name", "-"), + coordinates = widget.get("coordinates", "-"), ) def __click_update(self, event): @@ -116,31 +107,30 @@ class Module(core.module.Module): def update(self): widget = self.widget() - __lat = None - __lon = None try: util.location.reset() + time.sleep(5) # wait for reset to complete before querying results # Fetch fresh location information __info = util.location.location_info() + __raw_lat = __info["latitude"] + __raw_lon = __info["longitude"] # Contstruct coordinates string if util.location has provided required info - if __lat and __lon: - __lat = "{:.2f}".format(__info["latitude"]) - __lon = "{:.2f}".format(__info["longitude"]) + if isinstance(__raw_lat, float) and isinstance(__raw_lon, float): + __lat = float("{:.2f}".format(__raw_lat)) + __lon = float("{:.2f}".format(__raw_lon)) if __lat < 0: - __coords = __lat + "°S" + __coords = str(__lat) + "°S" else: - __coords = __lat + "°N" + __coords = str(__lat) + "°N" __coords += "," if __lon < 0: - __coords += __lon + "°W" + __coords += str(__lon) + "°W" else: - __coords += __lon + "°E" + __coords += str(__lon) + "°E" else: - __lat = "Unknown" - __lon = "Unknown" __coords = "Unknown" # Set widget values diff --git a/bumblebee_status/util/location.py b/bumblebee_status/util/location.py index 9cd3a10..3d9494f 100644 --- a/bumblebee_status/util/location.py +++ b/bumblebee_status/util/location.py @@ -18,17 +18,6 @@ __document = None __data = {} __next = 0 __sources = [ - { - "url": "http://ipapi.co/json", - "mapping": { - "latitude": "latitude", - "longitude": "longitude", - "country_name": "country_name", - "country_code": "country_code", - "city": "city_name", - "ip": "public_ip", - }, - }, { "url": "http://free.ipwhois.io/json/", "mapping": { @@ -51,6 +40,17 @@ __sources = [ "query": "public_ip", }, }, + { + "url": "http://ipapi.co/json", + "mapping": { + "latitude": "latitude", + "longitude": "longitude", + "country_name": "country_name", + "country_code": "country_code", + "city": "city_name", + "ip": "public_ip", + }, + } ] From 697c3310a0b8be0866710d59dca434a9a437d2e0 Mon Sep 17 00:00:00 2001 From: tfwiii Date: Wed, 12 Oct 2022 13:52:55 +0700 Subject: [PATCH 13/93] publicip - Bug Fix - IP address changes wer being missed if an interface was present but did not have an IPv4 address associated with it. Added exception handling to mitigate this. --- bumblebee_status/modules/contrib/publicip.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bumblebee_status/modules/contrib/publicip.py b/bumblebee_status/modules/contrib/publicip.py index 9a8f468..8ae10a0 100644 --- a/bumblebee_status/modules/contrib/publicip.py +++ b/bumblebee_status/modules/contrib/publicip.py @@ -72,8 +72,10 @@ class Module(core.module.Module): # Look for any changes to IP addresses try: for interface in netifaces.interfaces(): - __current_ips.add(netifaces.ifaddresses(interface)[2][0]['addr']) - + try: + __current_ips.add(netifaces.ifaddresses(interface)[2][0]['addr']) + except: + pass except: # If not ip address information found clear __current_ips __current_ips.clear() From 07e2364f7897600cd16a52212fbe4b7af5fb31b3 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sat, 26 Nov 2022 10:11:51 +0100 Subject: [PATCH 14/93] [main] fix i3 protocol buf on error messages ("could not parse JSON") Errors during startup currently cause bumblebee-status to mistakenly output the first line of output (the "version" line) of the i3 protocol twice, causing an error message that says "could not parse JSON") see #940 --- bumblebee-status | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee-status b/bumblebee-status index 4a9f7fb..74d2afe 100755 --- a/bumblebee-status +++ b/bumblebee-status @@ -100,6 +100,7 @@ def main(): core.input.register(None, core.input.WHEEL_DOWN, "i3-msg workspace next_on_output") core.event.trigger("start") + started = True update_lock = threading.Lock() event_thread = threading.Thread(target=handle_events, args=(config, update_lock, )) @@ -131,7 +132,6 @@ def main(): if util.format.asbool(config.get("engine.collapsible", True)) == True: core.input.register(None, core.input.MIDDLE_MOUSE, output.toggle_minimize) - started = True signal.signal(10, sig_USR1_handler) while True: if update_lock.acquire(blocking=False) == True: From 0bc2c6b8e1e50ae85ccc0f97d4e98fcb1382a620 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sat, 26 Nov 2022 10:17:09 +0100 Subject: [PATCH 15/93] [tests] remove unsupported python version --- .github/workflows/autotest.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/autotest.yml b/.github/workflows/autotest.yml index 652bab0..5049462 100644 --- a/.github/workflows/autotest.yml +++ b/.github/workflows/autotest.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] + python-version: ['3.7', '3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v3 @@ -24,7 +24,7 @@ jobs: cache: 'pip' - name: Install Ubuntu dependencies run: sudo apt-get install -y libdbus-1-dev libgit2-dev libvirt-dev taskwarrior - - name: Install Python dependencies + - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install -U coverage pytest pytest-mock freezegun @@ -38,7 +38,7 @@ jobs: - name: Run tests run: | coverage run --source=. -m pytest tests -v - - name: Report coverage + - name: Report coverage uses: paambaati/codeclimate-action@v3.0.0 with: coverageCommand: coverage3 xml From 1fef60b32c5fffce0a9242d4609eec1185a37696 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sat, 26 Nov 2022 12:05:42 +0100 Subject: [PATCH 16/93] [tests] fix location tests --- bumblebee_status/util/location.py | 4 ++-- tests/util/test_location.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bumblebee_status/util/location.py b/bumblebee_status/util/location.py index 3d9494f..00ac5fd 100644 --- a/bumblebee_status/util/location.py +++ b/bumblebee_status/util/location.py @@ -32,8 +32,8 @@ __sources = [ { "url": "http://ip-api.com/json", "mapping": { - "latitude": "lat", - "longitude": "lon", + "lat": "latitude", + "lon": "longitude", "country": "country_name", "countryCode": "country_code", "city": "city_name", diff --git a/tests/util/test_location.py b/tests/util/test_location.py index e04e600..b86f62e 100644 --- a/tests/util/test_location.py +++ b/tests/util/test_location.py @@ -14,16 +14,16 @@ def urllib_req(mocker): def secondaryLocation(): return { "country": "Middle Earth", - "longitude": "10.0", - "latitude": "20.5", - "ip": "127.0.0.1", + "lon": "10.0", + "lat": "20.5", + "query": "127.0.0.1", } @pytest.fixture def primaryLocation(): return { - "country_name": "Rivia", + "country": "Rivia", "longitude": "-10.0", "latitude": "-23", "ip": "127.0.0.6", @@ -33,7 +33,7 @@ def primaryLocation(): def test_primary_provider(urllib_req, primaryLocation): urllib_req.urlopen.return_value.read.return_value = json.dumps(primaryLocation) - assert util.location.country() == primaryLocation["country_name"] + assert util.location.country() == primaryLocation["country"] assert util.location.coordinates() == ( primaryLocation["latitude"], primaryLocation["longitude"], @@ -48,10 +48,10 @@ def test_secondary_provider(mocker, urllib_req, secondaryLocation): assert util.location.country() == secondaryLocation["country"] assert util.location.coordinates() == ( - secondaryLocation["latitude"], - secondaryLocation["longitude"], + secondaryLocation["lat"], + secondaryLocation["lon"], ) - assert util.location.public_ip() == secondaryLocation["ip"] + assert util.location.public_ip() == secondaryLocation["query"] # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From 79ce2167b0f829d150c605b8f392b0ce9c828a55 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sat, 26 Nov 2022 12:09:04 +0100 Subject: [PATCH 17/93] [autotest] update codeclimate action --- .github/workflows/autotest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autotest.yml b/.github/workflows/autotest.yml index 5049462..7b6deba 100644 --- a/.github/workflows/autotest.yml +++ b/.github/workflows/autotest.yml @@ -39,7 +39,7 @@ jobs: run: | coverage run --source=. -m pytest tests -v - name: Report coverage - uses: paambaati/codeclimate-action@v3.0.0 + uses: paambaati/codeclimate-action@v3.2.0 with: coverageCommand: coverage3 xml debug: true From 6a93238bda95e74e2ea786502759690212621782 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 27 Nov 2022 09:46:55 +0100 Subject: [PATCH 18/93] [core] log exceptions to enable error investigation, log exceptions. see #940 --- bumblebee-status | 1 + 1 file changed, 1 insertion(+) diff --git a/bumblebee-status b/bumblebee-status index 74d2afe..e714daa 100755 --- a/bumblebee-status +++ b/bumblebee-status @@ -149,6 +149,7 @@ if __name__ == "__main__": main() except Exception as e: # really basic errors -> make sure these are shown in the status bar by minimal config + logging.exception(e) if not started: print("{\"version\":1}") print("[") From 87a2890b4845d839d7cd1bbbe04f4f9422f1d2b4 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 27 Nov 2022 12:02:38 +0100 Subject: [PATCH 19/93] [modules/pulsectl] fix case when no devices are available no devices lead to an exception that completely stopped bumblebee-status from processing data. handle this case more gracefully by defaulting to a volume of 0%. if this proves to be an issue, we can still add error indicators later. see #940 --- bumblebee_status/modules/core/pulsectl.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/bumblebee_status/modules/core/pulsectl.py b/bumblebee_status/modules/core/pulsectl.py index 94f6994..9ea0642 100644 --- a/bumblebee_status/modules/core/pulsectl.py +++ b/bumblebee_status/modules/core/pulsectl.py @@ -51,7 +51,7 @@ class Module(core.module.Module): self.background = True self.__type = type - self.__volume = "n/a" + self.__volume = 0 self.__devicename = "n/a" self.__muted = False self.__showbars = util.format.asbool(self.parameter("showbars", False)) @@ -108,11 +108,15 @@ class Module(core.module.Module): def toggle_mute(self, _): with pulsectl.Pulse(self.id + "vol") as pulse: dev = self.get_device(pulse) + if not dev: + return pulse.mute(dev, not self.__muted) def change_volume(self, amount): with pulsectl.Pulse(self.id + "vol") as pulse: dev = self.get_device(pulse) + if not dev: + return vol = dev.volume vol.value_flat += amount if self.__limit > 0 and vol.value_flat > self.__limit/100: @@ -132,16 +136,22 @@ class Module(core.module.Module): for dev in devs: if dev.name == default: return dev - return devs[0] # fallback + if len(devs) == 0: + return None + return devs[0] # fallback def process(self, _): with pulsectl.Pulse(self.id + "proc") as pulse: dev = self.get_device(pulse) - self.__volume = dev.volume.value_flat - self.__muted = dev.mute - self.__devicename = dev.name + if not dev: + self.__volume = 0 + self.__devicename = "n/a" + else: + self.__volume = dev.volume.value_flat + self.__muted = dev.mute + self.__devicename = dev.name core.event.trigger("update", [self.id], redraw_only=True) core.event.trigger("draw") From a6f2e6fc5e41615ee0216c5f4926e692373ebfda Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 27 Nov 2022 17:41:48 +0100 Subject: [PATCH 20/93] [modules/mpd] make mpd port configurable fixes #941 --- bumblebee_status/modules/contrib/mpd.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bumblebee_status/modules/contrib/mpd.py b/bumblebee_status/modules/contrib/mpd.py index 92568d1..35cb2f6 100644 --- a/bumblebee_status/modules/contrib/mpd.py +++ b/bumblebee_status/modules/contrib/mpd.py @@ -42,6 +42,7 @@ Parameters: if {file} = '/foo/bar.baz', then {file2} = 'bar' * mpd.host: MPD host to connect to. (mpc behaviour by default) + * mpd.port: MPD port 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. contributed by `alrayyes `_ - many thanks! @@ -73,10 +74,12 @@ class Module(core.module.Module): self._repeat = False self._tags = defaultdict(lambda: "") - if not self.parameter("host"): - self._hostcmd = "" - else: - self._hostcmd = " -h " + self.parameter("host") + self._hostcmd = "" + if self.parameter("host"): + self._hostcmd = " -h {}".format(self.parameter("host")) + if self.parameter("port"): + self._hostcmd += " -p {}".format(self.parameter("port")) + # Create widgets widget_map = {} From f0ce6a1f7fdf4e1c2712f170c5d076264d9fdc28 Mon Sep 17 00:00:00 2001 From: pvutov Date: Wed, 30 Nov 2022 21:04:33 +0200 Subject: [PATCH 21/93] Documentation: Fix the default format string for nvidiagpu --- docs/modules.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules.rst b/docs/modules.rst index 96b8d21..d9b853f 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -1155,7 +1155,7 @@ nvidiagpu Displays GPU name, temperature and memory usage. Parameters: - * nvidiagpu.format: Format string (defaults to '{name}: {temp}°C %{usedmem}/{totalmem} MiB') + * nvidiagpu.format: Format string (defaults to '{name}: {temp}°C %{mem_used}/{mem_total} MiB') Available values are: {name} {temp} {mem_used} {mem_total} {fanspeed} {clock_gpu} {clock_mem} {gpu_usage_pct} {mem_usage_pct} {mem_io_pct} Requires nvidia-smi From b327162f3b4c8aecb184a270d0455fecc156ed3c Mon Sep 17 00:00:00 2001 From: arivarton Date: Wed, 4 Jan 2023 21:34:21 +0100 Subject: [PATCH 22/93] Added a max_chars parameter to be able to control the widget width. Also moved the try block a bit further up to catch network errors. --- bumblebee_status/modules/contrib/gcalendar.py | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/bumblebee_status/modules/contrib/gcalendar.py b/bumblebee_status/modules/contrib/gcalendar.py index efaabf7..49805e3 100644 --- a/bumblebee_status/modules/contrib/gcalendar.py +++ b/bumblebee_status/modules/contrib/gcalendar.py @@ -3,7 +3,9 @@ Events that are set as 'all-day' will not be shown. Requires credentials.json from a google api application where the google calendar api is installed. -On first time run the browser will open and google will ask for permission for this app to access the google calendar and then save a .gcalendar_token.json file to the credentials_path directory which stores this permission. +On first time run the browser will open and google will ask for permission for this app to access +the google calendar and then save a .gcalendar_token.json file to the credentials_path directory +which stores this permission. A refresh is done every 15 minutes. @@ -12,10 +14,11 @@ Parameters: * gcalendar.date_format: Format date output. Defaults to "%d.%m.%y". * gcalendar.credentials_path: Path to credentials.json. Defaults to "~/". * gcalendar.locale: locale to use rather than the system default. + * gcalendar.max_chars: Maximum of characters to display. Requires these pip packages: * google-api-python-client >= 1.8.0 - * google-auth-httplib2 + * google-auth-httplib2 * google-auth-oauthlib """ @@ -51,6 +54,13 @@ class Module(core.module.Module): self.__credentials = os.path.join(self.__credentials_path, "credentials.json") self.__token = os.path.join(self.__credentials_path, ".gcalendar_token.json") + self.__max_chars = self.parameter("max_chars", None) + try: + if self.__max_chars: + self.__max_chars = int(self.__max_chars) + except ValueError: + self.__max_chars = None + l = locale.getdefaultlocale() if not l or l == (None, None): l = ("en_US", "UTF-8") @@ -63,29 +73,26 @@ class Module(core.module.Module): def first_event(self, widget): SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"] - """Shows basic usage of the Google Calendar API. - Prints the start and name of the next 10 events on the user's calendar. - """ creds = None - # The file token.json stores the user's access and refresh tokens, and is - # created automatically when the authorization flow completes for the first - # time. - if os.path.exists(self.__token): - creds = Credentials.from_authorized_user_file(self.__token, SCOPES) - # If there are no (valid) credentials available, let the user log in. - if not creds or not creds.valid: - if creds and creds.expired and creds.refresh_token: - creds.refresh(Request()) - else: - flow = InstalledAppFlow.from_client_secrets_file( - self.__credentials, SCOPES - ) - creds = flow.run_local_server(port=0) - # Save the credentials for the next run - with open(self.__token, "w") as token: - token.write(creds.to_json()) - try: + # The file token.json stores the user's access and refresh tokens, and is + # created automatically when the authorization flow completes for the first + # time. + if os.path.exists(self.__token): + creds = Credentials.from_authorized_user_file(self.__token, SCOPES) + # If there are no (valid) credentials available, let the user log in. + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file( + self.__credentials, SCOPES + ) + creds = flow.run_local_server(port=0) + # Save the credentials for the next run + with open(self.__token, "w") as token: + token.write(creds.to_json()) + service = build("calendar", "v3", credentials=creds) # Call the Calendar API @@ -137,7 +144,7 @@ class Module(core.module.Module): .strftime(f"{self.__time_format}"), gevent["summary"], ) - ) + )[: self.__max_chars] else: return str( "%s %s" @@ -147,7 +154,7 @@ class Module(core.module.Module): .strftime(f"{self.__date_format} {self.__time_format}"), gevent["summary"], ) - ) + )[: self.__max_chars] return "No upcoming events found." except: From ae29c1b79f142eafb90d8cf7dfaea381e7cc7cfd Mon Sep 17 00:00:00 2001 From: arivarton Date: Sun, 29 Jan 2023 13:05:13 +0100 Subject: [PATCH 23/93] Divided date/time and summary into two widgets and made the summary widget scrollable. --- bumblebee_status/modules/contrib/gcalendar.py | 77 ++++++++++--------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/bumblebee_status/modules/contrib/gcalendar.py b/bumblebee_status/modules/contrib/gcalendar.py index 49805e3..6848ba8 100644 --- a/bumblebee_status/modules/contrib/gcalendar.py +++ b/bumblebee_status/modules/contrib/gcalendar.py @@ -14,7 +14,6 @@ Parameters: * gcalendar.date_format: Format date output. Defaults to "%d.%m.%y". * gcalendar.credentials_path: Path to credentials.json. Defaults to "~/". * gcalendar.locale: locale to use rather than the system default. - * gcalendar.max_chars: Maximum of characters to display. Requires these pip packages: * google-api-python-client >= 1.8.0 @@ -30,10 +29,12 @@ from dateutil.parser import parse as dtparse import core.module import core.widget import core.decorators +import util.format import datetime import os.path import locale +import time from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials @@ -41,11 +42,15 @@ from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build from googleapiclient.errors import HttpError +# Minutes +update_every = 15 + class Module(core.module.Module): - @core.decorators.every(minutes=15) + @core.decorators.every(minutes=update_every) def __init__(self, config, theme): - super().__init__(config, theme, core.widget.Widget(self.first_event)) + super().__init__(config, theme, [core.widget.Widget(self.__datetime), core.widget.Widget(self.__summary)]) + self.__error = False self.__time_format = self.parameter("time_format", "%H:%M") self.__date_format = self.parameter("date_format", "%d.%m.%y") self.__credentials_path = os.path.expanduser( @@ -54,13 +59,6 @@ class Module(core.module.Module): self.__credentials = os.path.join(self.__credentials_path, "credentials.json") self.__token = os.path.join(self.__credentials_path, ".gcalendar_token.json") - self.__max_chars = self.parameter("max_chars", None) - try: - if self.__max_chars: - self.__max_chars = int(self.__max_chars) - except ValueError: - self.__max_chars = None - l = locale.getdefaultlocale() if not l or l == (None, None): l = ("en_US", "UTF-8") @@ -70,10 +68,25 @@ class Module(core.module.Module): except Exception: locale.setlocale(locale.LC_TIME, ("en_US", "UTF-8")) - def first_event(self, widget): + self.__last_update = time.time() + self.__gcalendar_date, self.__gcalendar_summary = self.__fetch_from_calendar() + + def hidden(self): + return self.__error + + def __datetime(self, _): + return self.__gcalendar_date + + @core.decorators.scrollable + def __summary(self, _): + return self.__gcalendar_summary + + + def __fetch_from_calendar(self): SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"] creds = None + try: # The file token.json stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first @@ -132,33 +145,27 @@ class Module(core.module.Module): } ) sorted_list = sorted(event_list, key=lambda t: t["date"]) + next_event = sorted_list[0] - for gevent in sorted_list: - if gevent["date"] >= datetime.datetime.now(datetime.timezone.utc): - if gevent["date"].date() == datetime.datetime.utcnow().date(): - return str( - "%s %s" - % ( - gevent["date"] - .astimezone() - .strftime(f"{self.__time_format}"), - gevent["summary"], - ) - )[: self.__max_chars] - else: - return str( - "%s %s" - % ( - gevent["date"] - .astimezone() - .strftime(f"{self.__date_format} {self.__time_format}"), - gevent["summary"], - ) - )[: self.__max_chars] - return "No upcoming events found." + if next_event["date"] >= datetime.datetime.now(datetime.timezone.utc): + if next_event["date"].date() == datetime.datetime.utcnow().date(): + dt = next_event["date"].astimezone()\ + .strftime(f"{self.__time_format}") + else: + dt = next_event["date"].astimezone()\ + .strftime(f"{self.__date_format} {self.__time_format}") + return (dt, next_event["summary"]) + return (None, "No upcoming events.") except: - return None + self.__error = True + def update(self): + # Since scrolling runs the update command and therefore negates the + # every decorator, this need to be stopped + # to not break the API rules of google. + if self.__last_update+(update_every*60) < time.time(): + self.__last_update = time.time() + self.__gcalendar_date, self.__gcalendar_summary = self.__fetch_from_calendar() # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From 30362cb1244183b43df37ca5bfc2b0115e455d30 Mon Sep 17 00:00:00 2001 From: James Baumgarten Date: Fri, 3 Feb 2023 21:23:34 -0700 Subject: [PATCH 24/93] add pipewire module --- bumblebee_status/modules/contrib/pipewire.py | 90 ++++++++++++++++++++ themes/icons/awesome-fonts.json | 8 ++ 2 files changed, 98 insertions(+) create mode 100644 bumblebee_status/modules/contrib/pipewire.py diff --git a/bumblebee_status/modules/contrib/pipewire.py b/bumblebee_status/modules/contrib/pipewire.py new file mode 100644 index 0000000..d5f46ea --- /dev/null +++ b/bumblebee_status/modules/contrib/pipewire.py @@ -0,0 +1,90 @@ +"""get volume level or control it + +Requires the following executable: + * wpctl + +Parameters: + * wpctl.percent_change: How much to change volume by when scrolling on the module (default is 4%) + +heavily based on amixer module +""" +import re + +import core.module +import core.widget +import core.input + +import util.cli +import util.format + + +class Module(core.module.Module): + def __init__(self, config, theme): + super().__init__(config, theme, core.widget.Widget(self.volume)) + + self.__level = "N/A" + self.__muted = True + self.__change = ( + util.format.asint(self.parameter("percent_change", "4%").strip("%"), 0, 200) + / 100.0 + ) # divide by 100 because wpctl represents 100% volume as 1.00, 50% as 0.50, etc + + events = [ + { + "type": "mute", + "action": self.toggle, + "button": core.input.LEFT_MOUSE, + }, + { + "type": "volume", + "action": self.increase_volume, + "button": core.input.WHEEL_UP, + }, + { + "type": "volume", + "action": self.decrease_volume, + "button": core.input.WHEEL_DOWN, + }, + ] + + for event in events: + core.input.register(self, button=event["button"], cmd=event["action"]) + + def toggle(self, event): + util.cli.execute("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle") + + def increase_volume(self, event): + util.cli.execute( + "wpctl set-volume --limit 1.0 @DEFAULT_AUDIO_SINK@ {}+".format( + self.__change + ) + ) + + def decrease_volume(self, event): + util.cli.execute( + "wpctl set-volume --limit 1.0 @DEFAULT_AUDIO_SINK@ {}-".format( + self.__change + ) + ) + + def volume(self, widget): + return "{}%".format(int(float(self.__level) * 100)) + + def update(self): + try: + # `wpctl get-volume` will return a string like "Volume: n.nn" or "Volume: n.nn [MUTED]" + volume = util.cli.execute("wpctl get-volume @DEFAULT_AUDIO_SINK@") + v = re.search("\d\.\d+", volume) + m = re.search("MUTED", volume) + self.__level = v.group() + self.__muted = True if m else False + except Exception: + self.__level = "N/A" + + def state(self, widget): + if self.__muted: + return ["warning", "muted"] + return ["unmuted"] + + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index acd1fc3..e3aeebe 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -229,6 +229,14 @@ "prefix": "" } }, + "pipewire": { + "muted": { + "prefix": "" + }, + "unmuted": { + "prefix": "" + } + }, "kernel": { "prefix": "\uf17c" }, From be332005fae58eca48d2f7274403088d15348791 Mon Sep 17 00:00:00 2001 From: James Baumgarten Date: Fri, 10 Feb 2023 08:52:53 -0700 Subject: [PATCH 25/93] fix bug in pipewire module --- bumblebee_status/modules/contrib/pipewire.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bumblebee_status/modules/contrib/pipewire.py b/bumblebee_status/modules/contrib/pipewire.py index d5f46ea..4c0f23a 100644 --- a/bumblebee_status/modules/contrib/pipewire.py +++ b/bumblebee_status/modules/contrib/pipewire.py @@ -68,6 +68,8 @@ class Module(core.module.Module): ) def volume(self, widget): + if self.__level == "N/A": + return self.__level return "{}%".format(int(float(self.__level) * 100)) def update(self): From 7161ef211cbd463a8174169cf17b32c719548827 Mon Sep 17 00:00:00 2001 From: Clemens Beck Date: Sun, 19 Feb 2023 02:37:13 +0100 Subject: [PATCH 26/93] [modules/gitlab] add module --- bumblebee_status/modules/contrib/gitlab.py | 78 +++++++++++++++++++++ requirements/modules/gitlab.txt | 1 + screenshots/gitlab.png | Bin 0 -> 2456 bytes tests/modules/contrib/test_gitlab.py | 42 +++++++++++ themes/icons/ascii.json | 3 + themes/icons/awesome-fonts.json | 3 + 6 files changed, 127 insertions(+) create mode 100644 bumblebee_status/modules/contrib/gitlab.py create mode 100644 requirements/modules/gitlab.txt create mode 100644 screenshots/gitlab.png create mode 100644 tests/modules/contrib/test_gitlab.py diff --git a/bumblebee_status/modules/contrib/gitlab.py b/bumblebee_status/modules/contrib/gitlab.py new file mode 100644 index 0000000..126650d --- /dev/null +++ b/bumblebee_status/modules/contrib/gitlab.py @@ -0,0 +1,78 @@ +# pylint: disable=C0111,R0903 + +""" +Displays the GitLab todo count: + + * https://docs.gitlab.com/ee/user/todos.html + * https://docs.gitlab.com/ee/api/todos.html + +Uses `xdg-open` or `x-www-browser` to open web-pages. + +Requires the following library: + * requests + +Errors: + if the GitLab todo query failed, the shown value is `n/a` + +Parameters: + * gitlab.token: GitLab personal access token, the token needs to have the "read_api" scope. + * gitlab.host: Host of the GitLab instance, default is "gitlab.com". + * gitlab.actions: Comma separated actions to be parsed (e.g.: gitlab.actions=assigned,approval_required) +""" + +import json +import requests +import shutil +import util + +import core.module +import core.widget +import core.decorators +import core.input + + +class Module(core.module.Module): + @core.decorators.every(minutes=5) + def __init__(self, config, theme): + super().__init__(config, theme, core.widget.Widget(self.gitlab)) + + self.background = True + self.__label = "" + self.__host = self.parameter("host", "gitlab.com") + + self.__actions = [] + actions = self.parameter("actions", "") + if actions: + self.__actions = util.format.aslist(actions) + + self.__requests = requests.Session() + self.__requests.headers.update({"PRIVATE-TOKEN": self.parameter("token", "")}) + + cmd = "xdg-open" + if not shutil.which(cmd): + cmd = "x-www-browser" + + core.input.register( + self, + button=core.input.LEFT_MOUSE, + cmd="{cmd} https:/{host}//dashboard/todos".format( + cmd=cmd, host=self.__host + ), + ) + + def gitlab(self, _): + return self.__label + + def update(self): + try: + url = "https://{host}/api/v4/todos".format(host=self.__host) + response = self.__requests.get(url) + todos = response.json() + if self.__actions: + todos = [t for t in todos if t["action_name"] in self.__actions] + self.__label = str(len(todos)) + except Exception as e: + self.__label = "n/a" + + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/requirements/modules/gitlab.txt b/requirements/modules/gitlab.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements/modules/gitlab.txt @@ -0,0 +1 @@ +requests diff --git a/screenshots/gitlab.png b/screenshots/gitlab.png new file mode 100644 index 0000000000000000000000000000000000000000..d488db0dd458860553f6cf7e01cf97432458f41e GIT binary patch literal 2456 zcmZ{mX*|^H8^(XpVMx|2%@O!xU`^01=k~Am#x89I&jI?*I@A2Y^*K0MN<< z0MQ^)ldU#uz~OFgf&xzdJ5O6*X0aTuAX7&?i>#l3EAXn$i_U5|L(D9VIG5RkPD#oa z(;D1a7Qe@Bt1uRWSQ2N6hb4gRrB4C?fPBqR`ZpZ91|3!co$Q5AXp61+Redu=oxcld zS=)jWHVu~)Ff+qQ(JQY&Y{GF$kkVt#C?u#2B9#H{7rpiBR-LK0TzONeWr0@cszw;O zXS+>FWpg%if9p=2&R3b3?jv}?uaJ#*%i+{bQY6*Gt6t1dIh_J0%IOKlA^-@qU9&L> z;Nai~w49dyY)$F_;QXC=C>XTxk@47f9x$pO*pVoV=*!v;noae5sC6L0|E$`){N5is zWhDke8JRNg5jNoWkGRp?1~K%b?Y|N!2^R8m>R)@D^>^=~fcUkh@jLvhh1!%gyHQOD(3AG{=;dM0h} zLfB5Bh6}DK3a8XIn>0t>fa#Y%qJ<~c_{5KPx-q*h8al80Pm59SUv$1|nB@5}0JeM^ z>{2#rCM%x4ltncub}C`#ROvNcagrV{CgKD~d{t^pN-xk#m{@HP|)gRPYW9U7o!D$Qb-&53Zgu7glfF`{7VLR_|SzwFF zEhjrHF-fDv^X~>+_JAxRwY<$VNtW%5VKtq>t?{E+ZM`3}d%M%?6-I7QlT+Z7a7HXdP!r@xSG1A84xIP+?XYVq zd+l{w(FRGVnU%_Vyjnp)fWhnR`KYQJ*Nm9%ttA-z86y4SD`Oc2b8-6WJ3d{p_Ud;v zU7+XjjIoyzI!Bd#gu2F-rg~nv!pKJ-OVo@_w})+{gjeuS+=(~)sWQMmo9WN4YH@x$ zUk(o*NF`Xd;E}OY*);ocr_;gC6S;J6RTC&V)IC~B(sk5?ALMS18B;K#^)jJdP5wpV!VlB~3O-aIt)eCp5LkRc^lthy9t zw+j7AXGgp&VJ`QsySzB2A=j>BQow0gOClu=JJvg_{{WE5Szhp!$JdcCpE_=GprL|k zTE)oRM6St7y%3x{TeKJQw*xzBiBY?*5$* zcE;mEBj#r(qTT#Lemd;cg;R$HmEOfBXYJ}tGyk)jczaJ0e-C=4 zbvwDIZZ}@T$g!#M%yWSsK0#p`*vYnp6%uW&$3<$;k#O+b^AEujraroMBwV#w(XQjoZgWjGG z-esYGD(jNWtv}~A>#FO0E>8WGIIh;Mum-)G>habHL9lCH;N@+WykZM^0=xXe4{ zi>>yr-zXiXEEg@1jt+*DXl;Zu1{vt#rl^sxyjsoIw10(#**AoLS;$Z*Lv(8jF&FFA zZ##lc#~xh=2^q+%YU(fKXRf9Q$#|{z!z)d3}w! zsp;9t8GJ++8Z?HSxIjHSN!Xw9)%cqmL{gCwSkQyD6&b?lQcjel0Q5nOW2z z#Uo~W+rdbgbu!|z(YuZ3i;(wXM6Uhl^r(f#>}T?mcBZnU+oqRAEc-4pvQ-*dg8lb6 z9(Vy`*|l|V0|Nrqn*v(Y2OMj345af--ZtNT4Qt@$fs~0Yr;{7!1eKS&0~BEbTYDzC zYIYNOL-z3#)3Dd6PdkxFZmZdE&ba-6svEE;X;yznYuQT7aa8A5#YWL}MvYg+rsJ5H zsp>@{F?RF=R(q{`DcAlDbrw>&R$$>Xod!-6>iwON5@9$1mi}uMwJrkw_yQBKmJFG4 z-4NncAfkpZ7$ALvTo|)=?*-ng-Q0A1Ds06b*U~)5Ig}%q2=TPsr4dS-EBJm7e_3<5 zvgM+0hk^EXICe23$M#%ZLwS}@GDV*Lcnh6Bskph&-+t Date: Wed, 15 Mar 2023 19:01:48 +0100 Subject: [PATCH 27/93] remove unnecessary playerctl calls to determine whether widgets should be hidden --- bumblebee_status/modules/contrib/playerctl.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) mode change 100755 => 100644 bumblebee_status/modules/contrib/playerctl.py diff --git a/bumblebee_status/modules/contrib/playerctl.py b/bumblebee_status/modules/contrib/playerctl.py old mode 100755 new mode 100644 index bd8876c..c405a0e --- a/bumblebee_status/modules/contrib/playerctl.py +++ b/bumblebee_status/modules/contrib/playerctl.py @@ -34,6 +34,7 @@ class Module(core.module.Module): self.background = True self.__hide = util.format.asbool(self.parameter("hide", "false")); + self.__hidden = self.__hide self.__layout = util.format.aslist( self.parameter( @@ -87,7 +88,7 @@ class Module(core.module.Module): core.input.register(widget, **callback_options) def hidden(self): - return self.__hide and self.status() == None + return self.__hidden def status(self): try: @@ -101,6 +102,10 @@ class Module(core.module.Module): def update(self): playback_status = self.status() + if not playback_status: + self.__hidden = self.__hide + else: + self.__hidden = False for widget in self.widgets(): if playback_status: if widget.name == "playerctl.pause": From 61e38c609426361a19c50526c730a2d67f9ff031 Mon Sep 17 00:00:00 2001 From: C H Date: Sat, 25 Mar 2023 15:27:42 -0700 Subject: [PATCH 28/93] Added path for themes directory when installed via pipx --- bumblebee_status/core/theme.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bumblebee_status/core/theme.py b/bumblebee_status/core/theme.py index d91b060..1426450 100644 --- a/bumblebee_status/core/theme.py +++ b/bumblebee_status/core/theme.py @@ -25,6 +25,7 @@ if os.environ.get("XDG_DATA_DIRS"): PATHS.extend([ os.path.expanduser("~/.config/bumblebee-status/themes"), os.path.expanduser("~/.local/share/bumblebee-status/themes"), # PIP + os.path.expanduser("~/.local/pipx/venvs/bumblebee-status/share/bumblebee-status/themes"), # PIPX "/usr/share/bumblebee-status/themes", ]) From 2e1289f778b047a4dbc22f93e84415eb24160c01 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Tue, 11 Apr 2023 12:42:55 +0200 Subject: [PATCH 29/93] [core] fix importlib.util error add explicit import of importlib.util fixes #962 --- bumblebee_status/core/module.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bumblebee_status/core/module.py b/bumblebee_status/core/module.py index 56070bf..37a3143 100644 --- a/bumblebee_status/core/module.py +++ b/bumblebee_status/core/module.py @@ -1,5 +1,6 @@ import os import importlib +import importlib.util import logging import threading From f34e02d824f98560871a263db4b79ebd76c04284 Mon Sep 17 00:00:00 2001 From: dmturner Date: Thu, 13 Apr 2023 12:49:39 +0100 Subject: [PATCH 30/93] Change OpenWeatherMap request url from HTTP to HTTPS --- bumblebee_status/modules/contrib/weather.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bumblebee_status/modules/contrib/weather.py b/bumblebee_status/modules/contrib/weather.py index 72e0c27..4166995 100644 --- a/bumblebee_status/modules/contrib/weather.py +++ b/bumblebee_status/modules/contrib/weather.py @@ -13,7 +13,7 @@ Parameters: * 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 + * weather.apikey: API key from https://api.openweathermap.org contributed by `TheEdgeOfRage `_ - many thanks! @@ -116,7 +116,7 @@ class Module(core.module.Module): def update(self): try: - weather_url = "http://api.openweathermap.org/data/2.5/weather?appid={}".format( + weather_url = "https://api.openweathermap.org/data/2.5/weather?appid={}".format( self.__apikey ) weather_url = "{}&units={}".format(weather_url, self.__unit) From 42e041ce03613c2176f3187ca26a13f1b276d4a5 Mon Sep 17 00:00:00 2001 From: Duarte Figueiredo Date: Sun, 16 Apr 2023 11:24:51 +0100 Subject: [PATCH 31/93] [modules/wakatime] - New module that connects to https://wakatime.com and displays coding duration stats --- bumblebee_status/modules/contrib/wakatime.py | 95 +++++++++++++++++++ screenshots/wakatime.png | Bin 0 -> 1820 bytes tests/modules/contrib/test_wakatime.py | 56 +++++++++++ themes/icons/awesome-fonts.json | 3 + 4 files changed, 154 insertions(+) create mode 100644 bumblebee_status/modules/contrib/wakatime.py create mode 100644 screenshots/wakatime.png create mode 100644 tests/modules/contrib/test_wakatime.py diff --git a/bumblebee_status/modules/contrib/wakatime.py b/bumblebee_status/modules/contrib/wakatime.py new file mode 100644 index 0000000..7e4e33e --- /dev/null +++ b/bumblebee_status/modules/contrib/wakatime.py @@ -0,0 +1,95 @@ +# pylint: disable=C0111,R0903 + +""" +Displays the WakaTime daily/weekly/monthly times: + + * https://wakatime.com/developers#stats + +Uses `xdg-open` or `x-www-browser` to open web-pages. + +Requires the following library: + * requests + +Errors: + if the Wakatime status query failed, the shown value is `n/a` + +Parameters: + * wakatime.token: Wakatime secret api key, you can get it in https://wakatime.com/settings/account. + * wakatime.range: Range of the output, default is "Today". Can be one of “Today”, “Yesterday”, “Last 7 Days”, “Last 7 Days from Yesterday”, “Last 14 Days”, “Last 30 Days”, “This Week”, “Last Week”, “This Month”, or “Last Month”. + * wakatime.format: Format of the output, default is "digital" + Valid inputs are: + * "decimal" -> 1.37 + * "digital" -> 1:22 + * "seconds" -> 4931.29 + * "text" -> 1 hr 22 mins + * "%H:%M:%S" -> 01:22:31 (or any other valid format) +""" + +import base64 +import shutil +import time +from typing import Final, List + +import requests + +import core.decorators +import core.input +import core.module +import core.widget + +HOST_API: Final[str] = "https://wakatime.com" +SUMMARIES_URL: Final[str] = f"{HOST_API}/api/v1/users/current/summaries" +UTF8: Final[str] = "utf-8" +FORMAT_PARAMETERS: Final[List[str]] = ["decimal", "digital", "seconds", "text"] + + +class Module(core.module.Module): + @core.decorators.every(minutes=5) + def __init__(self, config, theme): + super().__init__(config, theme, core.widget.Widget(self.wakatime)) + + self.background = True + self.__label = "" + + self.__output_format = self.parameter("format", "digital") + self.__range = self.parameter("range", "Today") + + self.__requests = requests.Session() + + token = self.__encode_to_base_64(self.parameter("token", "")) + self.__requests.headers.update({"Authorization": f"Basic {token}"}) + + cmd = "xdg-open" + if not shutil.which(cmd): + cmd = "x-www-browser" + + core.input.register( + self, + button=core.input.LEFT_MOUSE, + cmd=f"{cmd} {HOST_API}/dashboard", + ) + + def wakatime(self, _): + return self.__label + + def update(self): + try: + self.__label = self.__get_waka_time(self.__range) + except Exception: + self.__label = "n/a" + + def __get_waka_time(self, since_date: str) -> str: + response = self.__requests.get(f"{SUMMARIES_URL}?range={since_date}") + + data = response.json() + grand_total = data["cumulative_total"] + + if self.__output_format in FORMAT_PARAMETERS: + return str(grand_total[self.__output_format]) + else: + total_seconds = int(grand_total["seconds"]) + return time.strftime(self.__output_format, time.gmtime(total_seconds)) + + @staticmethod + def __encode_to_base_64(s: str) -> str: + return base64.b64encode(s.encode(UTF8)).decode(UTF8) diff --git a/screenshots/wakatime.png b/screenshots/wakatime.png new file mode 100644 index 0000000000000000000000000000000000000000..82c8c797827652308f6ca4989b90f3d713817063 GIT binary patch literal 1820 zcmV+%2jlpOP)!O6|D$~OQJ#0KB299dD2AOMI=a-1W5=Y#61LYNg~80L4-!gMMNY~ zMNw2qQ9ZKIF6I-ACw6iK-=G7e_VD|P%Fj271=q3T+xWR(AJw^>Dgm#R-~^*cPn!ngm2*X# z)+J}JnOlBveOk&?&c`kI`T6<#ztg^bd%nReu%tm?URG~vkrLd2Ju4A7ZNC>Uh)3WC zgokl|F&lu=Y0OwVol}P{-bidQ`+<)N?nb{rRjOk1~IQ5V7DeAI~4wj)@w2;@Fl~6WqAy z;X8BFaSV8HyP_~}iMD$`-a2;V;JOjR1~k^>9p1l|wqlq?J2IPZl>PR&?p{+l zmY?_f_hRmrLNJOI88iQQS|1YJ74@LN07QdrH3YZd3%6va-nm|efne9(w`&D{tLEtD z&H?_^4O+K5cXPX|EtLdnYD~nzqSY9_%Rsp$4#3yfx2;oi)^_b|l7gENHyZuJ459wy zZuMV3HDoNBfjT;I``0JUMzLP@fwQ)1}G5R{qM@5{^3KzGt)oS z2FfjQAo%$Dw(;~rK}AY%Lk7K%4xyL*`i7!%ygaA0cn$0qPVcI%*ur}oArM@2vyEBF zc#?~EX2RjnK1V!dAcBL!P)9#e@82&J^=n`4+f%TVbJj~5NT7D*XHd(z`kic`+!6;C zi?_Gc+p()k2@Y#DetyZq>F6kCD{0n5(mB)%wl3zqjnESu>{y(z8ldY4-gJI*g&zJBfUaQ=O#`Xvof$iNn9Z51;v{Ep zIY7J=E(I6Xr>DIotKy0_j(SMO(NJ&{B%4U$kO1-xI>?M7fAd27i|>+f*ta`#MrsjY zrr;VXqY@ z*#kFJkVdhd0wm)o5FEO}z8yeUkxODO85a>Uqrejj%jWf|^sdI5d~}k$buz>p!L>3D zpsmI6zm~Shp5gHP*`3)qxg`HFXcIs&)2Bw^u)Jouc=Os=ieL#}L`$^++6l7OLeu`5+*(k8=aXxy;2oCv2RK#HVS_CU7 zxuhpc!$7$`4is6zAyL3jBIzTSy+V7~Uwi=}!?8ozcFkVHCY3BMr%H@r90>x!_2?di z1QTf{`Iqy2L!n;xByl+k}v|F46L;K+-5ZJ~LIVW8X|2a2rV z6biSX1g9_+B{+qtD8VUAMF~z}DoSt)Q}IwJOyz^5992{?t^5sN Date: Sun, 16 Apr 2023 11:29:31 +0100 Subject: [PATCH 32/93] rename mock_summaries_api_response test function --- tests/modules/contrib/test_wakatime.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/modules/contrib/test_wakatime.py b/tests/modules/contrib/test_wakatime.py index d6091b4..b76165c 100644 --- a/tests/modules/contrib/test_wakatime.py +++ b/tests/modules/contrib/test_wakatime.py @@ -20,7 +20,7 @@ def build_wakatime_module(waka_format=None, waka_range=None): return modules.contrib.wakatime.Module(config=config, theme=None) -def mock_todo_api_response(): +def mock_summaries_api_response(): res = mock.Mock() res.json = lambda: { "cumulative_total": { @@ -39,7 +39,7 @@ class TestWakatimeUnit(TestCase): def test_load_module(self): __import__("modules.contrib.wakatime") - @mock.patch.object(Session, "get", return_value=mock_todo_api_response()) + @mock.patch.object(Session, "get", return_value=mock_summaries_api_response()) def test_default_values(self, mock_get): module = build_wakatime_module() module.update() @@ -47,7 +47,7 @@ class TestWakatimeUnit(TestCase): mock_get.assert_called_with('https://wakatime.com/api/v1/users/current/summaries?range=today') - @mock.patch.object(Session, "get", return_value=mock_todo_api_response()) + @mock.patch.object(Session, "get", return_value=mock_summaries_api_response()) def test_custom_configs(self, mock_get): module = build_wakatime_module(waka_format="text", waka_range="last 7 days") module.update() From 1b0478edd421e1c6378abe8c829fd24246253881 Mon Sep 17 00:00:00 2001 From: Duarte Figueiredo Date: Sun, 16 Apr 2023 11:38:13 +0100 Subject: [PATCH 33/93] changed icon from normal w to font-awesome clock --- screenshots/wakatime.png | Bin 1820 -> 1729 bytes themes/icons/awesome-fonts.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/screenshots/wakatime.png b/screenshots/wakatime.png index 82c8c797827652308f6ca4989b90f3d713817063..a40a865881e42115a01e8ba9ef982736cd8f91d3 100644 GIT binary patch literal 1729 zcmV;y20rX0ssI23RA7l000JvNklJ+s90m?7-NY&iDJcGv5Sf(3KrBOCbq;91F=g)0kKCkDxgNAfTAd3 z0Yrm}=zfL#ha}(O`Er!KcAVMd!0LNB+~4iWAvCw4u_+6pKqkg)%f@wD_5;qxBjR6{M#>I zz8n!gURqj5jaq(n>IHsMvyoK&*YKWI$tj0eq|(yI!$yqNsgOK=`~+>%ffU)%9a^>N z%)M3}e*;#H9Umb(n#B9SD%~3U2ZWS7dX$xQQCfb`@RC*i`iHSXuU@?x6c(-thYlaZ zd868chm1sv&A>q;WOoA?vufOg2+pU#pq8?uM<`Y?o-JGBOG-)}J}mlqTLQaTXLkSo z12#kVp8eBL|MB$cQ??B;OVb87ZIh6&v!LK^d3kwZ;k^R~Q`@%hB7e&~Zr;4b+oaqj zU?EdjyY9z3cM2XncsOL}s2w}^Fi0Y)bG~u;@?WomMdc+Upz=fb$O(J*?tfHT>e3Gu zr|BiD7!V`lxN;UU+~VS5?J+IwC^m_|9gy7(V9cuL&z~!2Q5oP<+1&ujo7vepsi}vf z7DRLBF)nVM&J0#%WMnZBRP2*!Ga?Trr)t`B=Q25lSFDVi5;1GZ(v?^a4<9Fg%NXnm z4rbPyP{YJd;JDEDA5ydu-=QKmlHOy$;aR8&+z3tlU2 zTtcymNh|UaF5np6?P_ID{KYIN5 z?^G&MYaU&{=tBTvhh$y2v}yCUy7e1N8<$Y5ikLc^_PqJu(QfG1g!bt(XJMz#*qBwL zMo-iv+js1C_Jj3lddVuzPew-aq#jvaN99Ef)@+DSj>>dib~k`YR)JHePGf1l%v9ba zBh)19SKOyFgHkVQ^mFE$0Yp48ufQ12u{CgT-lj$tpGnBU|(R2C33OoWh0(Ema0E&#GO!_hH$*We2Hp z30VV5Z&v>|TbJE6wEiq~e$=A^hUa|@h zm|wi8V0<@j+@$^t__R4f?zE90M_Z-8BgTxKymZ;B<*}>jOG!c-bm;VkOcb}-**TQr z)WBq>MExgE{-#Qh?{No@-c&GP6(d@h@JJQr)TMn);zI8H zfz%9AQqr{U+NG6jlsuW47uea5m3v}hQu_{FL4vPc~BB*Od<1;>zKqotWUm|+!LQRhQ-Y2Qjb`oa1%EwKuOv}m7@u#;m$ z8$E3#$fdE3j|5g(rBJ(WK&Q~|(4RYPX<*}{f^S%5!-g|#R@v}9Y*yLuJ#1Fl@IC$k X*`jvYTDPW%00000NkvXXu0mjf$P`y{ literal 1820 zcmV+%2jlpOP)!O6|D$~OQJ#0KB299dD2AOMI=a-1W5=Y#61LYNg~80L4-!gMMNY~ zMNw2qQ9ZKIF6I-ACw6iK-=G7e_VD|P%Fj271=q3T+xWR(AJw^>Dgm#R-~^*cPn!ngm2*X# z)+J}JnOlBveOk&?&c`kI`T6<#ztg^bd%nReu%tm?URG~vkrLd2Ju4A7ZNC>Uh)3WC zgokl|F&lu=Y0OwVol}P{-bidQ`+<)N?nb{rRjOk1~IQ5V7DeAI~4wj)@w2;@Fl~6WqAy z;X8BFaSV8HyP_~}iMD$`-a2;V;JOjR1~k^>9p1l|wqlq?J2IPZl>PR&?p{+l zmY?_f_hRmrLNJOI88iQQS|1YJ74@LN07QdrH3YZd3%6va-nm|efne9(w`&D{tLEtD z&H?_^4O+K5cXPX|EtLdnYD~nzqSY9_%Rsp$4#3yfx2;oi)^_b|l7gENHyZuJ459wy zZuMV3HDoNBfjT;I``0JUMzLP@fwQ)1}G5R{qM@5{^3KzGt)oS z2FfjQAo%$Dw(;~rK}AY%Lk7K%4xyL*`i7!%ygaA0cn$0qPVcI%*ur}oArM@2vyEBF zc#?~EX2RjnK1V!dAcBL!P)9#e@82&J^=n`4+f%TVbJj~5NT7D*XHd(z`kic`+!6;C zi?_Gc+p()k2@Y#DetyZq>F6kCD{0n5(mB)%wl3zqjnESu>{y(z8ldY4-gJI*g&zJBfUaQ=O#`Xvof$iNn9Z51;v{Ep zIY7J=E(I6Xr>DIotKy0_j(SMO(NJ&{B%4U$kO1-xI>?M7fAd27i|>+f*ta`#MrsjY zrr;VXqY@ z*#kFJkVdhd0wm)o5FEO}z8yeUkxODO85a>Uqrejj%jWf|^sdI5d~}k$buz>p!L>3D zpsmI6zm~Shp5gHP*`3)qxg`HFXcIs&)2Bw^u)Jouc=Os=ieL#}L`$^++6l7OLeu`5+*(k8=aXxy;2oCv2RK#HVS_CU7 zxuhpc!$7$`4is6zAyL3jBIzTSy+V7~Uwi=}!?8ozcFkVHCY3BMr%H@r90>x!_2?di z1QTf{`Iqy2L!n;xByl+k}v|F46L;K+-5ZJ~LIVW8X|2a2rV z6biSX1g9_+B{+qtD8VUAMF~z}DoSt)Q}IwJOyz^5992{?t^5sN Date: Wed, 19 Apr 2023 11:50:25 +0100 Subject: [PATCH 34/93] [modules/todoist] - New module that connects to https://api.todoist.com and displays number of tasks due --- bumblebee_status/modules/contrib/todoist.py | 77 ++++++++++++++++++++ screenshots/todoist.png | Bin 0 -> 744 bytes tests/modules/contrib/test_todoist.py | 58 +++++++++++++++ themes/icons/awesome-fonts.json | 3 + 4 files changed, 138 insertions(+) create mode 100644 bumblebee_status/modules/contrib/todoist.py create mode 100644 screenshots/todoist.png create mode 100644 tests/modules/contrib/test_todoist.py diff --git a/bumblebee_status/modules/contrib/todoist.py b/bumblebee_status/modules/contrib/todoist.py new file mode 100644 index 0000000..c254139 --- /dev/null +++ b/bumblebee_status/modules/contrib/todoist.py @@ -0,0 +1,77 @@ +# pylint: disable=C0111,R0903 + +""" +Displays the nº of Todoist tasks that are due: + + * https://developer.todoist.com/rest/v2/#get-active-tasks + +Uses `xdg-open` or `x-www-browser` to open web-pages. + +Requires the following library: + * requests + +Errors: + if the Todoist get active tasks query failed, the shown value is `n/a` + +Parameters: + * todoist.token: Todoist api token, you can get it in https://todoist.com/app/settings/integrations/developer. + * todoist.filter: a filter statement defined by Todoist (https://todoist.com/help/articles/introduction-to-filters), eg: "!assigned to: others & (Overdue | due: today)" +""" + +import shutil +from typing import Final + +import requests + +import core.decorators +import core.input +import core.module +import core.widget + +HOST_API: Final[str] = "https://api.todoist.com" +HOST_WEBSITE: Final[str] = "https://todoist.com/app/today" + +TASKS_URL: Final[str] = f"{HOST_API}/rest/v2/tasks" + + +class Module(core.module.Module): + @core.decorators.every(minutes=5) + def __init__(self, config, theme): + super().__init__(config, theme, core.widget.Widget(self.todoist)) + + self.__user_id = None + self.background = True + self.__label = "" + + token = self.parameter("token", "") + self.__filter = self.parameter("filter", "") + + self.__requests = requests.Session() + self.__requests.headers.update({"Authorization": f"Bearer {token}"}) + + cmd = "xdg-open" + if not shutil.which(cmd): + cmd = "x-www-browser" + + core.input.register( + self, + button=core.input.LEFT_MOUSE, + cmd=f"{cmd} {HOST_WEBSITE}", + ) + + def todoist(self, _): + return self.__label + + def update(self): + try: + self.__label = self.__get_pending_tasks() + except Exception: + self.__label = "n/a" + + def __get_pending_tasks(self) -> str: + params = {"filter": self.__filter} if self.__filter else None + + response = self.__requests.get(TASKS_URL, params=params) + data = response.json() + + return str(len(data)) diff --git a/screenshots/todoist.png b/screenshots/todoist.png new file mode 100644 index 0000000000000000000000000000000000000000..66e1bae2c0d08ab0b97d8806d3eb239751557ba4 GIT binary patch literal 744 zcmVP)* z7?waxLUA~uQ7;VzH0q_HfQa&vjGTJ^geli=+Kiw20(Gt5uo+F((<>0&==bk|f|%xDBBH!>|Neb+<8c6>N=)ro z5zsUD?%iLvZsU90URttbMNMrZ9O>zsW154Bi1HG;!%@N6bLXc{n+4}$YR8I6if3)Hk$3L@>4ELHNCtn4AHO8FfuVOpSODXD>E+9p5Ho=R7Zx6isU0gK!As}PUjTZbth@%o zy?F7mt{yaT2Zuz%t;f`k2jTLPn}`2~jay2~YSPkkyL%=8OVqb--v$LoVVZ!6Nbr)E zcMybITi0~?@)bP;3kcUgF#N@f7Z5en@)8Z~+qRvUCSW3>yo8+l@dKbr zOzl`vd_o$qDr;((9UZpQ(DDlxTVMQhfqN7szhTqrK7Jb3Uhu*LNH^&23$bmK_}0000 Date: Wed, 19 Apr 2023 12:01:07 +0100 Subject: [PATCH 35/93] removed Final import because of python3.7 backwards compatibility --- bumblebee_status/modules/contrib/todoist.py | 7 +++---- bumblebee_status/modules/contrib/wakatime.py | 9 ++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/bumblebee_status/modules/contrib/todoist.py b/bumblebee_status/modules/contrib/todoist.py index c254139..5fae228 100644 --- a/bumblebee_status/modules/contrib/todoist.py +++ b/bumblebee_status/modules/contrib/todoist.py @@ -19,7 +19,6 @@ Parameters: """ import shutil -from typing import Final import requests @@ -28,10 +27,10 @@ import core.input import core.module import core.widget -HOST_API: Final[str] = "https://api.todoist.com" -HOST_WEBSITE: Final[str] = "https://todoist.com/app/today" +HOST_API = "https://api.todoist.com" +HOST_WEBSITE = "https://todoist.com/app/today" -TASKS_URL: Final[str] = f"{HOST_API}/rest/v2/tasks" +TASKS_URL = f"{HOST_API}/rest/v2/tasks" class Module(core.module.Module): diff --git a/bumblebee_status/modules/contrib/wakatime.py b/bumblebee_status/modules/contrib/wakatime.py index 7e4e33e..1ac4461 100644 --- a/bumblebee_status/modules/contrib/wakatime.py +++ b/bumblebee_status/modules/contrib/wakatime.py @@ -28,7 +28,6 @@ Parameters: import base64 import shutil import time -from typing import Final, List import requests @@ -37,10 +36,10 @@ import core.input import core.module import core.widget -HOST_API: Final[str] = "https://wakatime.com" -SUMMARIES_URL: Final[str] = f"{HOST_API}/api/v1/users/current/summaries" -UTF8: Final[str] = "utf-8" -FORMAT_PARAMETERS: Final[List[str]] = ["decimal", "digital", "seconds", "text"] +HOST_API = "https://wakatime.com" +SUMMARIES_URL = f"{HOST_API}/api/v1/users/current/summaries" +UTF8 = "utf-8" +FORMAT_PARAMETERS = ["decimal", "digital", "seconds", "text"] class Module(core.module.Module): From a6de61b751b5f35629fc142a702890811f36e2c4 Mon Sep 17 00:00:00 2001 From: Duarte Figueiredo Date: Sat, 6 May 2023 10:56:17 +0100 Subject: [PATCH 36/93] Updated gitlab module to have state of warning when there is at least 1 notification, just like the github module --- bumblebee_status/modules/contrib/gitlab.py | 19 +++++++++--- tests/modules/contrib/test_gitlab.py | 36 +++++++++++++++++++--- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/bumblebee_status/modules/contrib/gitlab.py b/bumblebee_status/modules/contrib/gitlab.py index 126650d..24f2eb8 100644 --- a/bumblebee_status/modules/contrib/gitlab.py +++ b/bumblebee_status/modules/contrib/gitlab.py @@ -20,15 +20,15 @@ Parameters: * gitlab.actions: Comma separated actions to be parsed (e.g.: gitlab.actions=assigned,approval_required) """ -import json -import requests import shutil -import util -import core.module -import core.widget +import requests + import core.decorators import core.input +import core.module +import core.widget +import util class Module(core.module.Module): @@ -74,5 +74,14 @@ class Module(core.module.Module): except Exception as e: self.__label = "n/a" + def state(self, widget): + state = [] + + try: + if int(self.__label) > 0: + state.append("warning") + except ValueError: + pass + return state # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/contrib/test_gitlab.py b/tests/modules/contrib/test_gitlab.py index d3a3b9a..016a2ab 100644 --- a/tests/modules/contrib/test_gitlab.py +++ b/tests/modules/contrib/test_gitlab.py @@ -1,13 +1,12 @@ -import pytest from unittest import TestCase, mock +import pytest +from requests import Session + import core.config import core.widget import modules.contrib.gitlab -from requests import Session -from requests.models import Response - pytest.importorskip("requests") @@ -15,6 +14,7 @@ def build_gitlab_module(actions=""): config = core.config.Config(["-p", "gitlab.actions={}".format(actions)]) return modules.contrib.gitlab.Module(config=config, theme=None) + def mock_todo_api_response(): res = mock.Mock() res.json = lambda: [ @@ -25,6 +25,7 @@ def mock_todo_api_response(): res.status_code = 200 return res + class TestGitlabUnit(TestCase): def test_load_module(self): __import__("modules.contrib.gitlab") @@ -40,3 +41,30 @@ class TestGitlabUnit(TestCase): module = build_gitlab_module(actions="approval_required") module.update() assert module.widgets()[0].full_text() == "1" + + @mock.patch.object(Session, "get", return_value=mock_todo_api_response()) + def test_state_warning(self, _): + module = build_gitlab_module(actions="approval_required") + module.update() + + assert module.state(None) == ["warning"] + + @mock.patch.object(Session, "get", return_value=mock_todo_api_response()) + def test_state_normal(self, _): + module = build_gitlab_module(actions="empty_filter") + module.update() + + assert module.state(None) == [] + + @mock.patch.object(Session, "get", return_value=mock_todo_api_response()) + def test_state_normal_before_update(self, _): + module = build_gitlab_module(actions="approval_required") + + assert module.state(None) == [] + + @mock.patch.object(Session, "get", side_effect=Exception("Something went wrong")) + def test_state_normal_if_na(self, _): + module = build_gitlab_module(actions="approval_required") + module.update() + + assert module.state(None) == [] From 6b4898017f00aa373da6b2e5e053eb21832ec9c8 Mon Sep 17 00:00:00 2001 From: Duarte Figueiredo Date: Sat, 6 May 2023 11:20:45 +0100 Subject: [PATCH 37/93] Fixed typo in 'today' that is currently breaking the tests --- tests/modules/contrib/test_wakatime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modules/contrib/test_wakatime.py b/tests/modules/contrib/test_wakatime.py index b76165c..9ee062e 100644 --- a/tests/modules/contrib/test_wakatime.py +++ b/tests/modules/contrib/test_wakatime.py @@ -45,7 +45,7 @@ class TestWakatimeUnit(TestCase): module.update() assert module.widgets()[0].full_text() == "3:02" - mock_get.assert_called_with('https://wakatime.com/api/v1/users/current/summaries?range=today') + mock_get.assert_called_with('https://wakatime.com/api/v1/users/current/summaries?range=Today') @mock.patch.object(Session, "get", return_value=mock_summaries_api_response()) def test_custom_configs(self, mock_get): From e9696b2150fe5d91ca4418d83676204a8f06032d Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Thu, 11 May 2023 08:30:59 +0200 Subject: [PATCH 38/93] [readthedocs] explicitly specify build OS fixes #970 --- .readthedocs.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 6823857..63fb402 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,4 +3,7 @@ version: 2 python: install: - requirements: docs/requirements.txt - +build: + os: ubuntu-22.04 + tools: + python: "3.11" From 14f19c897a5147f4a99f4928950d719f333e12b2 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Thu, 11 May 2023 08:45:03 +0200 Subject: [PATCH 39/93] [modules/pulsectl] add default device selection re-enable functionality to add a popup that allows the user to select the default source/sink. fixes #965 --- bumblebee-status | 11 +++++++---- bumblebee_status/modules/core/pulsectl.py | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/bumblebee-status b/bumblebee-status index e714daa..a16c747 100755 --- a/bumblebee-status +++ b/bumblebee-status @@ -68,10 +68,13 @@ def handle_commands(config, update_lock): def handle_events(config, update_lock): while True: - line = sys.stdin.readline().strip(",").strip() - if line == "[": continue - logging.info("input event: {}".format(line)) - process_event(line, config, update_lock) + try: + line = sys.stdin.readline().strip(",").strip() + if line == "[": continue + logging.info("input event: {}".format(line)) + process_event(line, config, update_lock) + except Exception as e: + logging.error(e) def main(): diff --git a/bumblebee_status/modules/core/pulsectl.py b/bumblebee_status/modules/core/pulsectl.py index 9ea0642..901202a 100644 --- a/bumblebee_status/modules/core/pulsectl.py +++ b/bumblebee_status/modules/core/pulsectl.py @@ -35,6 +35,8 @@ Requires the following Python module: """ import pulsectl +import logging +import functools import core.module import core.widget @@ -45,6 +47,11 @@ import util.cli import util.graph import util.format +try: + import util.popup +except ImportError as e: + logging.warning("Couldn't import util.popup: %s. Popups won't work!", e) + class Module(core.module.Module): def __init__(self, config, theme, type): super().__init__(config, theme, core.widget.Widget(self.display)) @@ -161,6 +168,21 @@ class Module(core.module.Module): pulse.event_callback_set(self.process) pulse.event_listen() + def select_default_device_popup(self, widget): + with pulsectl.Pulse(self.id) as pulse: + devs = pulse.sink_list() if self.__type == "sink" else pulse.source_list() + menu = util.popup.menu() + for dev in devs: + menu.add_menuitem( + dev.description, + callback=functools.partial(self.__on_default_changed, dev), + ) + menu.show(widget) + + def __on_default_changed(self, dev): + with pulsectl.Pulse(self.id) as pulse: + pulse.default_set(dev) + def state(self, _): if self.__muted: return ["warning", "muted"] From 8967eec44bce74969a1b6ae820ac2d4292c503c4 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Thu, 11 May 2023 14:28:32 +0200 Subject: [PATCH 40/93] [module/watson] Add formatting string Make it possible to customize the watson message in the widget see #963 --- bumblebee_status/modules/contrib/watson.py | 27 +++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/bumblebee_status/modules/contrib/watson.py b/bumblebee_status/modules/contrib/watson.py index d7b260b..a717342 100644 --- a/bumblebee_status/modules/contrib/watson.py +++ b/bumblebee_status/modules/contrib/watson.py @@ -5,6 +5,10 @@ Requires the following executable: * watson +Parameters: + * watson.format: Output format, defaults to "{project} [{tags}]" + Supported fields are: {project}, {tags}, {relative_start}, {absolute_start} + contributed by `bendardenne `_ - many thanks! """ @@ -26,11 +30,11 @@ class Module(core.module.Module): super().__init__(config, theme, core.widget.Widget(self.text)) self.__tracking = False - self.__project = "" + self.__info = {} + self.__format = self.parameter("format", "{project} [{tags}]") core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.toggle) def toggle(self, widget): - self.__project = "hit" if self.__tracking: util.cli.execute("watson stop") else: @@ -39,20 +43,27 @@ class Module(core.module.Module): def text(self, widget): if self.__tracking: - return self.__project + return self.__format.format(**self.__info) else: return "Paused" def update(self): output = util.cli.execute("watson status") - if re.match(r"No project started", output): + + m = re.search(r"Project ([^\[\]]+)(?: \[(.+)\])? started (.+) \((.+)\)", output) + + if m: + self.__tracking = True + self.__info = { + "project": m.group(1), + "tags": m.group(2) or "", + "relative_start": m.group(3), + "absolute_start": m.group(4), + } + else: self.__tracking = False return - self.__tracking = True - m = re.search(r"Project (.+) started", output) - self.__project = m.group(1) - def state(self, widget): return "on" if self.__tracking else "off" From b3007dd04225cc2a30ce45c26f8e878d1d52aff6 Mon Sep 17 00:00:00 2001 From: Bernhard B Date: Sun, 28 May 2023 17:03:58 +0200 Subject: [PATCH 41/93] use API token instead of password hash in pihole module * with newer versions of pi-hole, it is not possible anymore to use the password hash for the API authentication. Instead, one needs to use the dedicated API token for that. In order to stay backwards compatible, and not break existing bumblebee_status setups, the 'pwhash' parameter is still supported (in case someone runs an outdated pi-hole version). * the new pi-hole API endpoints do not allow to access the summary endpoint without an API token. So, therefore '&auth=' was added. --- bumblebee_status/modules/contrib/pihole.py | 34 ++++++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/bumblebee_status/modules/contrib/pihole.py b/bumblebee_status/modules/contrib/pihole.py index 7334abf..6e07287 100644 --- a/bumblebee_status/modules/contrib/pihole.py +++ b/bumblebee_status/modules/contrib/pihole.py @@ -4,13 +4,20 @@ 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) + + + * pihole.apitoken : pi-hole API token (can be obtained in the pi-hole webinterface (Settings -> API) + + OR (deprecated!) + + * pihole.pwhash : pi-hole webinterface password hash (can be obtained from the /etc/pihole/SetupVars.conf file) + contributed by `bbernhard `_ - many thanks! """ import requests - +import logging import core.module import core.widget import core.input @@ -22,7 +29,18 @@ class Module(core.module.Module): super().__init__(config, theme, core.widget.Widget(self.pihole_status)) self._pihole_address = self.parameter("address", "") - self._pihole_pw_hash = self.parameter("pwhash", "") + pihole_pw_hash = self.parameter("pwhash", "") + pihole_api_token = self.parameter("apitoken", "") + + self._pihole_secret = ( + pihole_api_token if pihole_api_token != "" else pihole_pw_hash + ) + + if pihole_pw_hash != "": + logging.warn( + "pihole: The 'pwhash' parameter is deprecated - consider using the 'apitoken' parameter instead!" + ) + self._pihole_status = None self._ads_blocked_today = "-" self.update_pihole_status() @@ -42,7 +60,11 @@ class Module(core.module.Module): def update_pihole_status(self): try: - data = requests.get(self._pihole_address + "/admin/api.php?summary").json() + data = requests.get( + self._pihole_address + + "/admin/api.php?summary&auth=" + + self._pihole_secret + ).json() self._pihole_status = True if data["status"] == "enabled" else False self._ads_blocked_today = data["ads_blocked_today"] except Exception as e: @@ -56,13 +78,13 @@ class Module(core.module.Module): req = requests.get( self._pihole_address + "/admin/api.php?disable&auth=" - + self._pihole_pw_hash + + self._pihole_secret ) else: req = requests.get( self._pihole_address + "/admin/api.php?enable&auth=" - + self._pihole_pw_hash + + self._pihole_secret ) if req is not None: if req.status_code == 200: From 0b4ff04be56fb79bfe4b7b4a7d85b2000486f157 Mon Sep 17 00:00:00 2001 From: SuperQ Date: Mon, 19 Jun 2023 14:03:42 +0200 Subject: [PATCH 42/93] [modules/cpu3] Add new CPU module Based on cpu2 module, but use `sensors -j` and some json path walking to better parse CPU temp and fan speeds. The output of `sensors -u` can contain many duplicates of the same sensor name, but from different sensor device paths. Signed-off-by: SuperQ --- bumblebee_status/modules/contrib/cpu3.py | 158 +++++++++++++++++++++++ requirements/modules/cpu3.txt | 2 + tests/modules/contrib/test_cpu3.py | 6 + 3 files changed, 166 insertions(+) create mode 100644 bumblebee_status/modules/contrib/cpu3.py create mode 100644 requirements/modules/cpu3.txt create mode 100644 tests/modules/contrib/test_cpu3.py diff --git a/bumblebee_status/modules/contrib/cpu3.py b/bumblebee_status/modules/contrib/cpu3.py new file mode 100644 index 0000000..5321012 --- /dev/null +++ b/bumblebee_status/modules/contrib/cpu3.py @@ -0,0 +1,158 @@ +"""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: + * cpu3.layout: Space-separated list of widgets to add. + Possible widgets are: + + * cpu3.maxfreq + * cpu3.cpuload + * cpu3.coresload + * cpu3.temp + * cpu3.fanspeed + * cpu3.colored: 1 for colored per core load graph, 0 for mono (default) + * cpu3.temp_json: json path to look for in the output of 'sensors -j'; + required if cpu3.temp widget is used + * cpu3.fan_json: json path to look for in the output of 'sensors -j'; + required if cpu3.fanspeed widget is used + +Note: if you are getting 'n/a' for CPU temperature / fan speed, then you're +lacking the aforementioned json path settings or they have wrong values. + +Example json paths: + * `cpu3.temp_json="coretemp-isa-0000.Package id 0.temp1_input"` + * `cpu3.fan_json="thinkpad-isa-0000.fan1.fan1_input"` + +contributed by `SuperQ ` +based on cpu2 by `` +""" + +import json +import psutil + +import core.module + +import util.cli +import util.graph +import util.format + + +class Module(core.module.Module): + def __init__(self, config, theme): + super().__init__(config, theme, []) + + self.__layout = self.parameter( + "layout", "cpu3.maxfreq cpu3.cpuload cpu3.coresload cpu3.temp cpu3.fanspeed" + ) + self.__widget_names = self.__layout.split() + self.__colored = util.format.asbool(self.parameter("colored", False)) + for widget_name in self.__widget_names: + if widget_name == "cpu3.maxfreq": + widget = self.add_widget(name=widget_name, full_text=self.maxfreq) + widget.set("type", "freq") + elif widget_name == "cpu3.cpuload": + widget = self.add_widget(name=widget_name, full_text=self.cpuload) + widget.set("type", "load") + elif widget_name == "cpu3.coresload": + widget = self.add_widget(name=widget_name, full_text=self.coresload) + widget.set("type", "loads") + elif widget_name == "cpu3.temp": + widget = self.add_widget(name=widget_name, full_text=self.temp) + widget.set("type", "temp") + elif widget_name == "cpu3.fanspeed": + widget = self.add_widget(name=widget_name, full_text=self.fanspeed) + widget.set("type", "fan") + if self.__colored: + widget.set("pango", True) + self.__temp_json = self.parameter("temp_json") + if self.__temp_json is None: + self.__temp = "n/a" + self.__fan_json = self.parameter("fan_json") + if self.__fan_json is None: + self.__fan = "n/a" + # maxfreq is loaded only once at startup + if "cpu3.maxfreq" in self.__widget_names: + self.__maxfreq = psutil.cpu_freq().max / 1000 + + def maxfreq(self, _): + return "{:.2f}GHz".format(self.__maxfreq) + + def cpuload(self, _): + return "{:>3}%".format(self.__cpuload) + + def add_color(self, bar): + """add color as pango markup to a bar""" + if bar in ["▁", "▂"]: + color = self.theme.color("green", "green") + elif bar in ["▃", "▄"]: + color = self.theme.color("yellow", "yellow") + elif bar in ["▅", "▆"]: + color = self.theme.color("orange", "orange") + elif bar in ["▇", "█"]: + color = self.theme.color("red", "red") + colored_bar = '{}'.format(color, bar) + return colored_bar + + def coresload(self, _): + mono_bars = [util.graph.hbar(x) for x in self.__coresload] + if not self.__colored: + return "".join(mono_bars) + colored_bars = [self.add_color(x) for x in mono_bars] + return "".join(colored_bars) + + def temp(self, _): + if self.__temp == "n/a" or self.__temp == 0: + return "n/a" + return "{}°C".format(self.__temp) + + def fanspeed(self, _): + if self.__fanspeed == "n/a": + return "n/a" + return "{}RPM".format(self.__fanspeed) + + def _parse_sensors_output(self): + output = util.cli.execute("sensors -j") + json_data = json.loads(output) + + temp = "n/a" + fan = "n/a" + temp_json = json_data + fan_json = json_data + for path in self.__temp_json.split('.'): + temp_json = temp_json[path] + for path in self.__fan_json.split('.'): + fan_json = fan_json[path] + if temp_json is not None: + temp = float(temp_json) + if fan_json is not None: + fan = int(fan_json) + return temp, fan + + def update(self): + if "cpu3.maxfreq" in self.__widget_names: + self.__maxfreq = psutil.cpu_freq().max / 1000 + if "cpu3.cpuload" in self.__widget_names: + self.__cpuload = round(psutil.cpu_percent(percpu=False)) + if "cpu3.coresload" in self.__widget_names: + self.__coresload = psutil.cpu_percent(percpu=True) + if "cpu3.temp" in self.__widget_names or "cpu3.fanspeed" in self.__widget_names: + self.__temp, self.__fanspeed = self._parse_sensors_output() + + def state(self, widget): + """for having per-widget icons""" + return [widget.get("type", "")] + + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/requirements/modules/cpu3.txt b/requirements/modules/cpu3.txt new file mode 100644 index 0000000..a28f112 --- /dev/null +++ b/requirements/modules/cpu3.txt @@ -0,0 +1,2 @@ +json +psutil diff --git a/tests/modules/contrib/test_cpu3.py b/tests/modules/contrib/test_cpu3.py new file mode 100644 index 0000000..9eac30d --- /dev/null +++ b/tests/modules/contrib/test_cpu3.py @@ -0,0 +1,6 @@ +import pytest + +pytest.importorskip("psutil") + +def test_load_module(): + __import__("modules.contrib.cpu3") From 2ef6f84df3e369daac29116373dea71a105a3650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Eust=C3=A1quio?= Date: Thu, 29 Jun 2023 14:38:54 -0300 Subject: [PATCH 43/93] Adding suport for multiple sound cards, not only devices. --- bumblebee_status/modules/contrib/amixer.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bumblebee_status/modules/contrib/amixer.py b/bumblebee_status/modules/contrib/amixer.py index b0fda92..6f44e8c 100644 --- a/bumblebee_status/modules/contrib/amixer.py +++ b/bumblebee_status/modules/contrib/amixer.py @@ -4,12 +4,15 @@ Requires the following executable: * amixer Parameters: + * amixer.card: Sound Card to use (default is 0) * amixer.device: Device to use (default is Master,0) * amixer.percent_change: How much to change volume by when scrolling on the module (default is 4%) contributed by `zetxx `_ - many thanks! input handling contributed by `ardadem `_ - many thanks! + +multiple audio cards contributed by `hugoeustaquio `_ - many thanks! """ import re @@ -26,6 +29,7 @@ class Module(core.module.Module): self.__level = "n/a" self.__muted = True + self.__card = self.parameter("card", "0") self.__device = self.parameter("device", "Master,0") self.__change = util.format.asint( self.parameter("percent_change", "4%").strip("%"), 0, 100 @@ -62,7 +66,7 @@ class Module(core.module.Module): self.set_parameter("{}%-".format(self.__change)) def set_parameter(self, parameter): - util.cli.execute("amixer -q set {} {}".format(self.__device, parameter)) + util.cli.execute("amixer -c {} -q set {} {}".format(self.__card, self.__device, parameter)) def volume(self, widget): if self.__level == "n/a": @@ -79,7 +83,7 @@ class Module(core.module.Module): def update(self): try: self.__level = util.cli.execute( - "amixer get {}".format(self.__device) + "amixer -c {} get {}".format(self.__card, self.__device) ) except Exception as e: self.__level = "n/a" From b866ab25b673665d44719e053ca0e6b298604282 Mon Sep 17 00:00:00 2001 From: Lasnik Date: Mon, 10 Jul 2023 15:42:11 +0200 Subject: [PATCH 44/93] create module --- bumblebee_status/modules/contrib/usage.py | 53 +++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 bumblebee_status/modules/contrib/usage.py diff --git a/bumblebee_status/modules/contrib/usage.py b/bumblebee_status/modules/contrib/usage.py new file mode 100644 index 0000000..5f21198 --- /dev/null +++ b/bumblebee_status/modules/contrib/usage.py @@ -0,0 +1,53 @@ +# pylint: disable=C0111,R0903 + +""" +Module for ActivityWatch (https://activitywatch.net/) +Displays the amount of time the system was used actively +""" +import sqlite3 +import os + +import core.module +import core.widget + + +class Module(core.module.Module): + def __init__(self, config, theme): + super().__init__(config, theme, core.widget.Widget(self.output)) + self.__uptime = "" + + def output(self, _): + return "{}".format(self.__uptime) + + def update(self): + database_loc = self.parameter('database', '~/.local/share/activitywatch/aw-server/peewee-sqlite.v2.db') + home = os.path.expanduser('~') + + database = sqlite3.connect(database_loc.replace('~', home)) + cursor = database.cursor() + + cursor.execute('SELECT key, id FROM bucketmodel') + + bucket_id = 1 + + for tuple in cursor.fetchall(): + if 'aw-watcher-afk' in tuple[1]: + bucket_id = tuple[0] + + cursor.execute(f'SELECT duration, datastr FROM eventmodel WHERE bucket_id = {bucket_id} ' + + 'AND strftime(\'%Y,%m,%d\', timestamp) = strftime(\'%Y,%m,%d\', \'now\')') + + duration = 0 + + for tuple in cursor.fetchall(): + if '{"status": "not-afk"}' in tuple[1]: + duration += tuple[0] + + hours = '%.0f' % (duration // 3600) + minutes = '%.0f' % ((duration % 3600) // 60) + seconds = '%.0f' % (duration % 60) + + formatting = self.parameter('format', 'HHh, MMmin') + self.__uptime = formatting.replace('HH', hours).replace('MM', minutes).replace('SS', seconds) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From 305f9cf491583cf4d9359880d71e3383ba10d4ac Mon Sep 17 00:00:00 2001 From: Lasnik Date: Mon, 10 Jul 2023 15:51:00 +0200 Subject: [PATCH 45/93] formatting guidelines --- bumblebee_status/modules/contrib/usage.py | 44 ++++++++++++++--------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/bumblebee_status/modules/contrib/usage.py b/bumblebee_status/modules/contrib/usage.py index 5f21198..b05ace2 100644 --- a/bumblebee_status/modules/contrib/usage.py +++ b/bumblebee_status/modules/contrib/usage.py @@ -4,6 +4,7 @@ Module for ActivityWatch (https://activitywatch.net/) Displays the amount of time the system was used actively """ + import sqlite3 import os @@ -14,40 +15,49 @@ import core.widget class Module(core.module.Module): def __init__(self, config, theme): super().__init__(config, theme, core.widget.Widget(self.output)) - self.__uptime = "" + self.__usage = "" def output(self, _): - return "{}".format(self.__uptime) + return "{}".format(self.__usage) def update(self): - database_loc = self.parameter('database', '~/.local/share/activitywatch/aw-server/peewee-sqlite.v2.db') - home = os.path.expanduser('~') + database_loc = self.parameter( + "database", "~/.local/share/activitywatch/aw-server/peewee-sqlite.v2.db" + ) + home = os.path.expanduser("~") - database = sqlite3.connect(database_loc.replace('~', home)) + database = sqlite3.connect(database_loc.replace("~", home)) cursor = database.cursor() - - cursor.execute('SELECT key, id FROM bucketmodel') + + cursor.execute("SELECT key, id FROM bucketmodel") bucket_id = 1 for tuple in cursor.fetchall(): - if 'aw-watcher-afk' in tuple[1]: + if "aw-watcher-afk" in tuple[1]: bucket_id = tuple[0] - cursor.execute(f'SELECT duration, datastr FROM eventmodel WHERE bucket_id = {bucket_id} ' + - 'AND strftime(\'%Y,%m,%d\', timestamp) = strftime(\'%Y,%m,%d\', \'now\')') - + cursor.execute( + f"SELECT duration, datastr FROM eventmodel WHERE bucket_id = {bucket_id} " + + 'AND strftime("%Y,%m,%d", timestamp) = strftime("%Y,%m,%d", "now")' + ) + duration = 0 for tuple in cursor.fetchall(): if '{"status": "not-afk"}' in tuple[1]: duration += tuple[0] - hours = '%.0f' % (duration // 3600) - minutes = '%.0f' % ((duration % 3600) // 60) - seconds = '%.0f' % (duration % 60) - - formatting = self.parameter('format', 'HHh, MMmin') - self.__uptime = formatting.replace('HH', hours).replace('MM', minutes).replace('SS', seconds) + hours = "%.0f" % (duration // 3600) + minutes = "%.0f" % ((duration % 3600) // 60) + seconds = "%.0f" % (duration % 60) + + formatting = self.parameter("format", "HHh, MMmin") + self.__usage = ( + formatting.replace("HH", hours) + .replace("MM", minutes) + .replace("SS", seconds) + ) + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From 8178919e2c53ccf2652dae3d546e9c5a010cb77f Mon Sep 17 00:00:00 2001 From: Lasnik Date: Mon, 10 Jul 2023 16:52:22 +0200 Subject: [PATCH 46/93] add description --- bumblebee_status/modules/contrib/usage.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/bumblebee_status/modules/contrib/usage.py b/bumblebee_status/modules/contrib/usage.py index b05ace2..d64e3e1 100644 --- a/bumblebee_status/modules/contrib/usage.py +++ b/bumblebee_status/modules/contrib/usage.py @@ -2,7 +2,22 @@ """ Module for ActivityWatch (https://activitywatch.net/) -Displays the amount of time the system was used actively +Displays the amount of time the system was used actively. + +Requirements: + * sqlite3 module for python + * ActivityWatch + +Errors: + * when you get 'error: unable to open database file', modify the parameter 'database' to your ActivityWatch database file + -> often found by running 'locate aw-server/peewee-sqlite.v2.db' + +Parameters: + * usage.database: path to your database file + * usage.format: Specify what gets printed to the bar + -> use 'HH', 'MM' or 'SS', they will get replaced by the number of hours, minutes and seconds, respectively + +contributed by lasnikr (https://github.com/lasnikr) """ import sqlite3 From ee796f658963add7b0b35f33225ea2dbb807b0f8 Mon Sep 17 00:00:00 2001 From: James Baumgarten Date: Sun, 16 Jul 2023 15:06:31 -0600 Subject: [PATCH 47/93] allow setting sink ID in pipewire module --- bumblebee_status/modules/contrib/pipewire.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/bumblebee_status/modules/contrib/pipewire.py b/bumblebee_status/modules/contrib/pipewire.py index 4c0f23a..f07cd55 100644 --- a/bumblebee_status/modules/contrib/pipewire.py +++ b/bumblebee_status/modules/contrib/pipewire.py @@ -29,6 +29,8 @@ class Module(core.module.Module): / 100.0 ) # divide by 100 because wpctl represents 100% volume as 1.00, 50% as 0.50, etc + self.__id = self.parameter("sink_id") or "@DEFAULT_AUDIO_SINK@" + events = [ { "type": "mute", @@ -51,20 +53,16 @@ class Module(core.module.Module): core.input.register(self, button=event["button"], cmd=event["action"]) def toggle(self, event): - util.cli.execute("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle") + util.cli.execute("wpctl set-mute {} toggle".format(self.__id)) def increase_volume(self, event): util.cli.execute( - "wpctl set-volume --limit 1.0 @DEFAULT_AUDIO_SINK@ {}+".format( - self.__change - ) + "wpctl set-volume --limit 1.0 {} {}+".format(self.__change, self.__id) ) def decrease_volume(self, event): util.cli.execute( - "wpctl set-volume --limit 1.0 @DEFAULT_AUDIO_SINK@ {}-".format( - self.__change - ) + "wpctl set-volume --limit 1.0 {} {}-".format(self.__change, self.__id) ) def volume(self, widget): @@ -75,7 +73,7 @@ class Module(core.module.Module): def update(self): try: # `wpctl get-volume` will return a string like "Volume: n.nn" or "Volume: n.nn [MUTED]" - volume = util.cli.execute("wpctl get-volume @DEFAULT_AUDIO_SINK@") + volume = util.cli.execute("wpctl get-volume {}".format(self.__id)) v = re.search("\d\.\d+", volume) m = re.search("MUTED", volume) self.__level = v.group() From 4df527216420716a8568d431f563bbb7270eb852 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Mon, 17 Jul 2023 12:36:32 +0200 Subject: [PATCH 48/93] [workflows] update python versions --- .github/workflows/autotest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autotest.yml b/.github/workflows/autotest.yml index 7b6deba..4877a70 100644 --- a/.github/workflows/autotest.yml +++ b/.github/workflows/autotest.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10'] + python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 From d27986a316702984f9de3238c3b71319e1e856d1 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Mon, 17 Jul 2023 12:43:06 +0200 Subject: [PATCH 49/93] [requirements] remove json requirement - which is a builtin --- requirements/modules/cpu3.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/modules/cpu3.txt b/requirements/modules/cpu3.txt index a28f112..a4d92cc 100644 --- a/requirements/modules/cpu3.txt +++ b/requirements/modules/cpu3.txt @@ -1,2 +1 @@ -json psutil From 2546dbae2e5eede979f9a525a36868813dbc26c0 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Mon, 17 Jul 2023 12:55:06 +0200 Subject: [PATCH 50/93] [tests/amixer] fix tests --- tests/modules/contrib/test_amixer.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/modules/contrib/test_amixer.py b/tests/modules/contrib/test_amixer.py index 4ff37a3..146d586 100644 --- a/tests/modules/contrib/test_amixer.py +++ b/tests/modules/contrib/test_amixer.py @@ -117,29 +117,29 @@ def test_toggle(module_mock, mocker): command = mocker.patch('util.cli.execute') module = module_mock() module.toggle(False) - command.assert_called_once_with('amixer -q set Master,0 toggle') + command.assert_called_once_with('amixer -c 0 -q set Master,0 toggle') def test_default_volume(module_mock, mocker): module = module_mock() command = mocker.patch('util.cli.execute') module.increase_volume(False) - command.assert_called_once_with('amixer -q set Master,0 4%+') + command.assert_called_once_with('amixer -c 0 -q set Master,0 4%+') command = mocker.patch('util.cli.execute') module.decrease_volume(False) - command.assert_called_once_with('amixer -q set Master,0 4%-') + command.assert_called_once_with('amixer -c 0 -q set Master,0 4%-') def test_custom_volume(module_mock, mocker): module = module_mock(['-p', 'amixer.percent_change=25']) command = mocker.patch('util.cli.execute') module.increase_volume(False) - command.assert_called_once_with('amixer -q set Master,0 25%+') + command.assert_called_once_with('amixer -c 0 -q set Master,0 25%+') command = mocker.patch('util.cli.execute') module.decrease_volume(False) - command.assert_called_once_with('amixer -q set Master,0 25%-') + command.assert_called_once_with('amixer -c 0 -q set Master,0 25%-') def test_custom_device(module_mock, mocker): mocker.patch('util.cli.execute') @@ -147,13 +147,13 @@ def test_custom_device(module_mock, mocker): command = mocker.patch('util.cli.execute') module.toggle(False) - command.assert_called_once_with('amixer -q set CustomMaster toggle') + command.assert_called_once_with('amixer -c 0 -q set CustomMaster toggle') command = mocker.patch('util.cli.execute') module.increase_volume(False) - command.assert_called_once_with('amixer -q set CustomMaster 4%+') + command.assert_called_once_with('amixer -c 0 -q set CustomMaster 4%+') command = mocker.patch('util.cli.execute') module.decrease_volume(False) - command.assert_called_once_with('amixer -q set CustomMaster 4%-') + command.assert_called_once_with('amixer -c 0 -q set CustomMaster 4%-') From f855d5c2354c894d132d8e58857407de0903d26c Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Mon, 17 Jul 2023 12:58:48 +0200 Subject: [PATCH 51/93] [modules/aur-update] hide if no packages fixes #978 --- bumblebee_status/modules/contrib/aur-update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee_status/modules/contrib/aur-update.py b/bumblebee_status/modules/contrib/aur-update.py index f1b85ea..9afdc89 100644 --- a/bumblebee_status/modules/contrib/aur-update.py +++ b/bumblebee_status/modules/contrib/aur-update.py @@ -31,7 +31,7 @@ class Module(core.module.Module): return self.__format.format(self.__packages) def hidden(self): - return self.__packages == 0 and not self.__error + return self.__packages == 0 def update(self): self.__error = False From e1f50f4782b50e680c1d1500376f531007a0a631 Mon Sep 17 00:00:00 2001 From: Shahan Arshad Date: Wed, 19 Jul 2023 22:10:05 +0500 Subject: [PATCH 52/93] Add: missing mention of pipewire module on website I was reading the docs on the website(https://bumblebee-status.readthedocs.io/en/latest/modules.html#contrib) and there was no mention of the pipewire module which exists among the contrib modules. This edit will inform users about the pipewire module --- docs/modules.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/modules.rst b/docs/modules.rst index d9b853f..c7a78de 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -1243,6 +1243,19 @@ Parameters: contributed by `bbernhard `_ - many thanks! +pipewire +~~~~~~~ + +get volume level or control it + +Requires the following executable: + * wpctl + +Parameters: + * wpctl.percent_change: How much to change volume by when scrolling on the module (default is 4%) + +heavily based on amixer module + playerctl ~~~~~~~~~ From 04a222f3c8bc7eaa41bdcd2f1ea1c0cfe28a8964 Mon Sep 17 00:00:00 2001 From: Shahan Arshad Date: Thu, 20 Jul 2023 02:16:24 +0500 Subject: [PATCH 53/93] Fix: pipewire.py module mousewheel up and down events were initially not working on my bumblebee status bar. After verifying on the command line, it seems that wpctl set-volume command requires sink ID first and percentage change as the second arg. steps to verify, assuming wireplumber is installed and sink ID is 32 ```sh wpctl set-volume 32 50% ``` will set the volume to 50% ```sh wpctl set-volume 50% 32 ``` will result in `Object '52' not found --- bumblebee_status/modules/contrib/pipewire.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bumblebee_status/modules/contrib/pipewire.py b/bumblebee_status/modules/contrib/pipewire.py index f07cd55..86c9b9d 100644 --- a/bumblebee_status/modules/contrib/pipewire.py +++ b/bumblebee_status/modules/contrib/pipewire.py @@ -57,12 +57,12 @@ class Module(core.module.Module): def increase_volume(self, event): util.cli.execute( - "wpctl set-volume --limit 1.0 {} {}+".format(self.__change, self.__id) + "wpctl set-volume --limit 1.0 {} {}+".format(self.__id, self.__change) ) def decrease_volume(self, event): util.cli.execute( - "wpctl set-volume --limit 1.0 {} {}-".format(self.__change, self.__id) + "wpctl set-volume --limit 1.0 {} {}-".format(self.__id, self.__change) ) def volume(self, widget): From d03e6307f5e8c0b1c0451636ac9b1e84f3529a73 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Fri, 21 Jul 2023 14:18:17 +0200 Subject: [PATCH 54/93] [contrib/stock] change API to alphavantage.co Changed the stock module to work again (with an API key), and make it easier to change the data provider by the user. fixes #971 --- bumblebee_status/modules/contrib/stock.py | 58 +++++++++++++---------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/bumblebee_status/modules/contrib/stock.py b/bumblebee_status/modules/contrib/stock.py index 224a5fb..6b35bf7 100644 --- a/bumblebee_status/modules/contrib/stock.py +++ b/bumblebee_status/modules/contrib/stock.py @@ -5,7 +5,9 @@ Parameters: * stock.symbols : Comma-separated list of symbols to fetch - * stock.change : Should we fetch change in stock value (defaults to True) + * stock.apikey : API key created on https://alphavantage.co + * stock.url : URL to use, defaults to "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={symbol}&apikey={apikey}" + * stock.fields : Fields from the response to show, defaults to "01. symbol,05. price,10. change percent" contributed by `msoulier `_ - many thanks! @@ -22,6 +24,12 @@ import core.decorators import util.format +def flatten(d, result): + for k, v in d.items(): + if type(v) is dict: + flatten(v, result) + else: + result[k] = v class Module(core.module.Module): @core.decorators.every(hours=1) @@ -29,41 +37,41 @@ class Module(core.module.Module): super().__init__(config, theme, core.widget.Widget(self.value)) self.__symbols = self.parameter("symbols", "") + self.__apikey = self.parameter("apikey", None) + self.__fields = self.parameter("fields", "01. symbol,05. price,10. change percent").split(",") + self.__url = self.parameter("url", "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={symbol}&apikey={apikey}") self.__change = util.format.asbool(self.parameter("change", True)) - self.__value = None + self.__values = [] + def value(self, widget): - results = [] - if not self.__value: - return "n/a" - data = json.loads(self.__value) + result = "" - for symbol in data["quoteResponse"]["result"]: - valkey = "regularMarketChange" if self.__change else "regularMarketPrice" - sym = symbol.get("symbol", "n/a") - currency = symbol.get("currency", "USD") - val = "n/a" if not valkey in symbol else "{:.2f}".format(symbol[valkey]) - results.append("{} {} {}".format(sym, val, currency)) - return " ".join(results) + for value in self.__values: + res = {} + flatten(value, res) + for field in self.__fields: + result += res.get(field, "n/a") + " " + result = result[:-1] + return result def fetch(self): + results = [] if self.__symbols: - url = "https://query1.finance.yahoo.com/v7/finance/quote?symbols=" - url += ( - self.__symbols - + "&fields=regularMarketPrice,currency,regularMarketChange" - ) - try: - return urllib.request.urlopen(url).read().strip() - except urllib.request.URLError: - logging.error("unable to open stock exchange url") - return None + for symbol in self.__symbols.split(","): + url = self.__url.format(symbol=symbol, apikey=self.__apikey) + try: + results.append(json.loads(urllib.request.urlopen(url).read().strip())) + except urllib.request.URLError: + logging.error("unable to open stock exchange url") + return [] else: logging.error("unable to retrieve stock exchange rate") - return None + return [] + return results def update(self): - self.__value = self.fetch() + self.__values = self.fetch() # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From 37b5646d65d201a6380395cc8e0502c69939b03d Mon Sep 17 00:00:00 2001 From: Pavle Portic Date: Thu, 15 Jun 2023 19:41:34 +0200 Subject: [PATCH 55/93] Allow adjusting the font size of tk popups --- bumblebee_status/core/config.py | 9 +++++++++ bumblebee_status/modules/contrib/bluetooth.py | 12 ++++-------- bumblebee_status/modules/contrib/system.py | 8 ++++---- bumblebee_status/modules/contrib/vpn.py | 2 +- bumblebee_status/modules/core/pulseaudio.py | 2 +- bumblebee_status/modules/core/pulsectl.py | 2 +- bumblebee_status/modules/core/vault.py | 4 ++-- bumblebee_status/util/popup.py | 9 ++++++--- 8 files changed, 28 insertions(+), 20 deletions(-) diff --git a/bumblebee_status/core/config.py b/bumblebee_status/core/config.py index f191673..1d4ebaa 100644 --- a/bumblebee_status/core/config.py +++ b/bumblebee_status/core/config.py @@ -276,6 +276,15 @@ class Config(util.store.Store): def interval(self, default=1): return util.format.seconds(self.get("interval", default)) + """Returns the global popup menu font size + + :return: popup menu font size + :rtype: int + """ + + def popup_font_size(self, default=12): + return util.format.asint(self.get("popup_font_size", default)) + """Returns whether debug mode is enabled :return: True if debug is enabled, False otherwise diff --git a/bumblebee_status/modules/contrib/bluetooth.py b/bumblebee_status/modules/contrib/bluetooth.py index 481ae88..56f5b8b 100644 --- a/bumblebee_status/modules/contrib/bluetooth.py +++ b/bumblebee_status/modules/contrib/bluetooth.py @@ -75,19 +75,15 @@ class Module(core.module.Module): def popup(self, widget): """Show a popup menu.""" - menu = util.popup.PopupMenu() + menu = util.popup.menu(self.__config) if self._status == "On": - menu.add_menuitem("Disable Bluetooth") + menu.add_menuitem("Disable Bluetooth", callback=self._toggle) elif self._status == "Off": - menu.add_menuitem("Enable Bluetooth") + menu.add_menuitem("Enable Bluetooth", callback=self._toggle) else: return - # show menu and get return code - ret = menu.show(widget) - if ret == 0: - # first (and only) item selected. - self._toggle() + menu.show(widget) def _toggle(self, widget=None): """Toggle bluetooth state.""" diff --git a/bumblebee_status/modules/contrib/system.py b/bumblebee_status/modules/contrib/system.py index 79e8846..8b136a1 100644 --- a/bumblebee_status/modules/contrib/system.py +++ b/bumblebee_status/modules/contrib/system.py @@ -8,11 +8,11 @@ adds the possibility to * reboot the system. - + Per default a confirmation dialog is shown before the actual action is performed. - + Parameters: - * system.confirm: show confirmation dialog before performing any action (default: true) + * system.confirm: show confirmation dialog before performing any action (default: true) * system.reboot: specify a reboot command (defaults to 'reboot') * system.shutdown: specify a shutdown command (defaults to 'shutdown -h now') * system.logout: specify a logout command (defaults to 'i3exit logout') @@ -77,7 +77,7 @@ class Module(core.module.Module): util.cli.execute(popupcmd) return - menu = util.popup.menu() + menu = util.popup.menu(self.__config) reboot_cmd = self.parameter("reboot", "reboot") shutdown_cmd = self.parameter("shutdown", "shutdown -h now") logout_cmd = self.parameter("logout", "i3exit logout") diff --git a/bumblebee_status/modules/contrib/vpn.py b/bumblebee_status/modules/contrib/vpn.py index d9c8793..ae8e748 100644 --- a/bumblebee_status/modules/contrib/vpn.py +++ b/bumblebee_status/modules/contrib/vpn.py @@ -93,7 +93,7 @@ class Module(core.module.Module): self.__connected_vpn_profile = None def popup(self, widget): - menu = util.popup.menu() + menu = util.popup.menu(self.__config) if self.__connected_vpn_profile is not None: menu.add_menuitem("Disconnect", callback=self.__on_vpndisconnect) diff --git a/bumblebee_status/modules/core/pulseaudio.py b/bumblebee_status/modules/core/pulseaudio.py index 431de10..5f74248 100644 --- a/bumblebee_status/modules/core/pulseaudio.py +++ b/bumblebee_status/modules/core/pulseaudio.py @@ -236,7 +236,7 @@ class Module(core.module.Module): channel = "sinks" if self._channel == "sink" else "sources" result = util.cli.execute("pactl list {} short".format(channel)) - menu = util.popup.menu() + menu = util.popup.menu(self.__config) lines = result.splitlines() for line in lines: info = line.split("\t") diff --git a/bumblebee_status/modules/core/pulsectl.py b/bumblebee_status/modules/core/pulsectl.py index 901202a..4342913 100644 --- a/bumblebee_status/modules/core/pulsectl.py +++ b/bumblebee_status/modules/core/pulsectl.py @@ -171,7 +171,7 @@ class Module(core.module.Module): def select_default_device_popup(self, widget): with pulsectl.Pulse(self.id) as pulse: devs = pulse.sink_list() if self.__type == "sink" else pulse.source_list() - menu = util.popup.menu() + menu = util.popup.menu(self.__config) for dev in devs: menu.add_menuitem( dev.description, diff --git a/bumblebee_status/modules/core/vault.py b/bumblebee_status/modules/core/vault.py index 7f3fb75..ba316bc 100644 --- a/bumblebee_status/modules/core/vault.py +++ b/bumblebee_status/modules/core/vault.py @@ -52,7 +52,7 @@ def build_menu(parent, current_directory, callback): ) else: - submenu = util.popup.menu(parent, leave=False) + submenu = util.popup.menu(self.__config, parent, leave=False) build_menu( submenu, os.path.join(current_directory, entry.name), callback ) @@ -73,7 +73,7 @@ class Module(core.module.Module): core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.popup) def popup(self, widget): - menu = util.popup.menu(leave=False) + menu = util.popup.menu(self.__config, leave=False) build_menu(menu, self.__path, self.__callback) menu.show(widget, offset_x=self.__offx, offset_y=self.__offy) diff --git a/bumblebee_status/util/popup.py b/bumblebee_status/util/popup.py index 784a037..cd083f7 100644 --- a/bumblebee_status/util/popup.py +++ b/bumblebee_status/util/popup.py @@ -3,6 +3,7 @@ import logging import tkinter as tk +import tkinter.font as tkFont import functools @@ -10,11 +11,12 @@ import functools class menu(object): """Draws a hierarchical popup menu + :param config: Global config singleton, passed on from modules :param parent: If given, this menu is a leave of the "parent" menu :param leave: If set to True, close this menu when mouse leaves the area (defaults to True) """ - def __init__(self, parent=None, leave=True): + def __init__(self, config, parent=None, leave=True): self.running = True self.parent = parent @@ -23,6 +25,7 @@ class menu(object): self._root.withdraw() self._menu = tk.Menu(self._root, tearoff=0) self._menu.bind("", self.__on_focus_out) + self._font_size = tkFont.Font(size=config.popup_font_size()) if leave: self._menu.bind("", self.__on_focus_out) @@ -68,7 +71,7 @@ class menu(object): """ def add_cascade(self, menuitem, submenu): - self._menu.add_cascade(label=menuitem, menu=submenu.menu()) + self._menu.add_cascade(label=menuitem, menu=submenu.menu(), font=self._font_size) """Adds an item to the current menu @@ -78,7 +81,7 @@ class menu(object): def add_menuitem(self, menuitem, callback): self._menu.add_command( - label=menuitem, command=functools.partial(self.__on_click, callback) + label=menuitem, command=functools.partial(self.__on_click, callback), font=self._font_size, ) """Adds a separator to the menu in the current location""" From 9e6e656fa87eb9e5ae45a790bbd427b88de5b186 Mon Sep 17 00:00:00 2001 From: Pavle Portic Date: Wed, 14 Jun 2023 21:30:16 +0200 Subject: [PATCH 56/93] Add device filter support to pulsectl popup menu --- bumblebee_status/modules/core/pulsectl.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/bumblebee_status/modules/core/pulsectl.py b/bumblebee_status/modules/core/pulsectl.py index 4342913..701f6ef 100644 --- a/bumblebee_status/modules/core/pulsectl.py +++ b/bumblebee_status/modules/core/pulsectl.py @@ -13,6 +13,8 @@ Parameters: * pulsectl.autostart: If set to 'true' (default is 'false'), automatically starts the pulsectl daemon if it is not running * pulsectl.percent_change: How much to change volume by when scrolling on the module (default is 2%) * pulsectl.limit: Upper limit for setting the volume (default is 0%, which means 'no limit') + * pulsectl.popup-filter: Comma-separated list of device strings (if the device name contains it) to exclude + from the default device popup menu (e.g. Monitor for sources) * pulsectl.showbars: 'true' for showing volume bars, requires --markup=pango; 'false' for not showing volume bars (default) * pulsectl.showdevicename: If set to 'true' (default is 'false'), the currently selected default device is shown. @@ -70,6 +72,11 @@ class Module(core.module.Module): self.parameter("percent_change", "2%").strip("%"), 0, 100 ) self.__limit = util.format.asint(self.parameter("limit", "0%").strip("%"), 0) + popup_filter_param = self.parameter("popup-filter", []) + if popup_filter_param == '': + self.__popup_filter = [] + else: + self.__popup_filter = util.format.aslist(popup_filter_param) events = [ { @@ -103,8 +110,8 @@ class Module(core.module.Module): res = f"{res} {util.graph.hbar(self.__volume*100)}" if self.__show_device_name: - friendly_name = self.parameter(self.__devicename, self.__devicename) - icon = self.parameter("icon." + self.__devicename, "") + friendly_name = self.parameter(self.__devicename.lower(), self.__devicename) + icon = self.parameter("icon." + self.__devicename.lower(), "") res = ( icon + " " + friendly_name + " | " + res if icon != "" @@ -170,7 +177,12 @@ class Module(core.module.Module): def select_default_device_popup(self, widget): with pulsectl.Pulse(self.id) as pulse: - devs = pulse.sink_list() if self.__type == "sink" else pulse.source_list() + if self.__type == "sink": + devs = pulse.sink_list() + else: + devs = pulse.source_list() + + devs = filter(lambda dev: not any(filter in dev.description for filter in self.__popup_filter), devs) menu = util.popup.menu(self.__config) for dev in devs: menu.add_menuitem( From fded39fa810ee95565e6e90dcd5da3512f3cebb9 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Mon, 11 Sep 2023 09:36:47 +0200 Subject: [PATCH 57/93] [modules/pulsectl] make device names case sensitive A previous change accidentially changed the "pretty" device name mapping to be required to be in lowercase (rather than the exact name of the devices. Restore previous functionality. fixes #989 --- bumblebee_status/modules/core/pulsectl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bumblebee_status/modules/core/pulsectl.py b/bumblebee_status/modules/core/pulsectl.py index 701f6ef..fd77c26 100644 --- a/bumblebee_status/modules/core/pulsectl.py +++ b/bumblebee_status/modules/core/pulsectl.py @@ -110,8 +110,8 @@ class Module(core.module.Module): res = f"{res} {util.graph.hbar(self.__volume*100)}" if self.__show_device_name: - friendly_name = self.parameter(self.__devicename.lower(), self.__devicename) - icon = self.parameter("icon." + self.__devicename.lower(), "") + friendly_name = self.parameter(self.__devicename, self.__devicename) + icon = self.parameter("icon." + self.__devicename, "") res = ( icon + " " + friendly_name + " | " + res if icon != "" From b76213203792107bde548f497a9b13407cd0e532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20L=C3=BCftinger?= Date: Wed, 13 Sep 2023 18:02:26 +0200 Subject: [PATCH 58/93] poll window title also on workspace change (not only window events) --- bumblebee_status/modules/contrib/title.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bumblebee_status/modules/contrib/title.py b/bumblebee_status/modules/contrib/title.py index 055369e..1c98b42 100644 --- a/bumblebee_status/modules/contrib/title.py +++ b/bumblebee_status/modules/contrib/title.py @@ -50,8 +50,9 @@ class Module(core.module.Module): # create a connection with i3ipc self.__i3 = i3ipc.Connection() - # event is called both on focus change and title change + # event is called both on focus change and title change, and on workspace change self.__i3.on("window", lambda __p_i3, __p_e: self.__pollTitle()) + self.__i3.on("workspace", lambda __p_i3, __p_e: self.__pollTitle()) # begin listening for events threading.Thread(target=self.__i3.main).start() From b42323013d5fdb6dc47b487d2d863e0556fd85a8 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Fri, 15 Sep 2023 15:06:57 +0200 Subject: [PATCH 59/93] fix(config): make config file keys case sensitive Since the configparser library by default parses keys case insensitive (all lowercase), certain mappings, especially in the pulseaudio modules, could fail ("internal" pulseaudio device names are matched against entries in the configuration). fixes #992 --- bumblebee_status/core/config.py | 11 ++++++++--- tests/core/test_config.py | 6 ++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/bumblebee_status/core/config.py b/bumblebee_status/core/config.py index 1d4ebaa..0b564b3 100644 --- a/bumblebee_status/core/config.py +++ b/bumblebee_status/core/config.py @@ -240,11 +240,16 @@ class Config(util.store.Store): :param filename: path to the file to load """ - def load_config(self, filename): - if os.path.exists(filename): + def load_config(self, filename, content=None): + if os.path.exists(filename) or content != None: log.info("loading {}".format(filename)) tmp = RawConfigParser() - tmp.read(u"{}".format(filename)) + tmp.optionxform = str + + if content: + tmp.read_string(content) + else: + tmp.read(u"{}".format(filename)) if tmp.has_section("module-parameters"): for key, value in tmp.items("module-parameters"): diff --git a/tests/core/test_config.py b/tests/core/test_config.py index 762c674..02695cd 100644 --- a/tests/core/test_config.py +++ b/tests/core/test_config.py @@ -113,6 +113,12 @@ def test_missing_parameter(): assert cfg.get("test.key") == None assert cfg.get("test.key", "no-value-set") == "no-value-set" +def test_file_case_sensitivity(): + cfg = core.config.Config([]) + cfg.load_config("", content="[module-parameters]\ntest.key = VaLuE\ntest.KeY2 = value") + + assert cfg.get("test.key") == "VaLuE" + assert cfg.get("test.KeY2") == "value" # # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From d303b794f3147b01662d889a4b2e718256e799af Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Fri, 15 Sep 2023 15:11:23 +0200 Subject: [PATCH 60/93] feat(docs): clarify line continuation fixes #987 --- docs/introduction.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/introduction.rst b/docs/introduction.rst index 3831d4b..891b14c 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -44,6 +44,14 @@ like this: -t } +Line continuations (breaking a single line into multiple lines) is allowed in +the i3 configuration, but please ensure that all lines except the final one need to have a trailing +"\". +This is explained in detail here: +[i3 user guide: line continuation](https://i3wm.org/docs/userguide.html#line_continuation) + + + You can retrieve a list of modules (and their parameters) and themes by entering: From 2e7e75a27c6a9dac00f401540fa337417820df49 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Fri, 15 Sep 2023 15:15:31 +0200 Subject: [PATCH 61/93] feat(shell): add timeout warning and faster update see #990 --- bumblebee_status/modules/contrib/shell.py | 2 ++ bumblebee_status/util/cli.py | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/bumblebee_status/modules/contrib/shell.py b/bumblebee_status/modules/contrib/shell.py index 566de42..7043778 100644 --- a/bumblebee_status/modules/contrib/shell.py +++ b/bumblebee_status/modules/contrib/shell.py @@ -61,6 +61,7 @@ class Module(core.module.Module): # if requested then run not async version and just execute command in this thread if not self.__async: self.__output = util.cli.execute(self.__command, shell=True, ignore_errors=True).strip() + core.event.trigger("update", [self.id], redraw_only=True) return # if previous thread didn't end yet then don't do anything @@ -71,6 +72,7 @@ class Module(core.module.Module): self.__current_thread = threading.Thread( target=lambda obj, cmd: obj.set_output( util.cli.execute(cmd, ignore_errors=True) + core.event.trigger("update", [self.id], redraw_only=True) ), args=(self, self.__command), ) diff --git a/bumblebee_status/util/cli.py b/bumblebee_status/util/cli.py index d14dc6a..4ef25d9 100644 --- a/bumblebee_status/util/cli.py +++ b/bumblebee_status/util/cli.py @@ -52,7 +52,19 @@ def execute( raise RuntimeError("{} not found".format(cmd)) if wait: - out, _ = proc.communicate() + timeout = 60 + try: + out, _ = proc.communicate(timeout=timeout) + except subprocess.TimeoutExpired as e: + logging.warning( + f""" + Communication with process pid={proc.pid} hangs for more + than {timeout} seconds. + If this is not expected, the process is stale, or + you might have run in stdout / stderr deadlock. + """ + ) + out, _ = proc.communicate() if proc.returncode != 0: err = "{} exited with code {}".format(cmd, proc.returncode) logging.warning(err) From 9170785ed52fe43cd7849f0560934b5e54858778 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Fri, 15 Sep 2023 15:21:38 +0200 Subject: [PATCH 62/93] fix(module-shell): fix syntax error --- bumblebee_status/modules/contrib/shell.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bumblebee_status/modules/contrib/shell.py b/bumblebee_status/modules/contrib/shell.py index 7043778..e8461b9 100644 --- a/bumblebee_status/modules/contrib/shell.py +++ b/bumblebee_status/modules/contrib/shell.py @@ -72,7 +72,6 @@ class Module(core.module.Module): self.__current_thread = threading.Thread( target=lambda obj, cmd: obj.set_output( util.cli.execute(cmd, ignore_errors=True) - core.event.trigger("update", [self.id], redraw_only=True) ), args=(self, self.__command), ) From 9251217bb3aff34a0603b94d2755ef99b87d5d4b Mon Sep 17 00:00:00 2001 From: Elin Angelov Date: Wed, 20 Sep 2023 14:59:24 +0300 Subject: [PATCH 63/93] feat: add `disabled` parameter it pauses dunst notification on startup --- bumblebee_status/modules/contrib/dunstctl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bumblebee_status/modules/contrib/dunstctl.py b/bumblebee_status/modules/contrib/dunstctl.py index f082f1b..1b5d3fb 100644 --- a/bumblebee_status/modules/contrib/dunstctl.py +++ b/bumblebee_status/modules/contrib/dunstctl.py @@ -28,6 +28,8 @@ class Module(core.module.Module): self.__states = {"unknown": ["unknown", "critical"], "true": ["muted", "warning"], "false": ["unmuted"]} + if util.format.asbool(self.parameter("disabled", False)): + util.cli.execute("dunstctl set-paused true", ignore_errors=True) def toggle_state(self, event): util.cli.execute("dunstctl set-paused toggle", ignore_errors=True) From 0c2123cfe45a2e19c6edd167bfc2c9fd006a7109 Mon Sep 17 00:00:00 2001 From: Elin Angelov Date: Wed, 20 Sep 2023 15:11:24 +0300 Subject: [PATCH 64/93] fix: identation --- bumblebee_status/modules/contrib/dunstctl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee_status/modules/contrib/dunstctl.py b/bumblebee_status/modules/contrib/dunstctl.py index 1b5d3fb..1ad43df 100644 --- a/bumblebee_status/modules/contrib/dunstctl.py +++ b/bumblebee_status/modules/contrib/dunstctl.py @@ -28,7 +28,7 @@ class Module(core.module.Module): self.__states = {"unknown": ["unknown", "critical"], "true": ["muted", "warning"], "false": ["unmuted"]} - if util.format.asbool(self.parameter("disabled", False)): + if util.format.asbool(self.parameter("disabled", False)): util.cli.execute("dunstctl set-paused true", ignore_errors=True) def toggle_state(self, event): From b217ac9c9ef623e46a2e7d2d76674423d629f350 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 1 Oct 2023 10:07:03 +0200 Subject: [PATCH 65/93] docs: update module documentation --- docs/modules.rst | 181 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 168 insertions(+), 13 deletions(-) diff --git a/docs/modules.rst b/docs/modules.rst index c7a78de..1fb752c 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -264,6 +264,8 @@ Parameters: * pulsectl.autostart: If set to 'true' (default is 'false'), automatically starts the pulsectl daemon if it is not running * pulsectl.percent_change: How much to change volume by when scrolling on the module (default is 2%) * pulsectl.limit: Upper limit for setting the volume (default is 0%, which means 'no limit') + * pulsectl.popup-filter: Comma-separated list of device strings (if the device name contains it) to exclude + from the default device popup menu (e.g. Monitor for sources) * pulsectl.showbars: 'true' for showing volume bars, requires --markup=pango; 'false' for not showing volume bars (default) * pulsectl.showdevicename: If set to 'true' (default is 'false'), the currently selected default device is shown. @@ -424,6 +426,7 @@ Requires the following executable: * amixer Parameters: + * amixer.card: Sound Card to use (default is 0) * amixer.device: Device to use (default is Master,0) * amixer.percent_change: How much to change volume by when scrolling on the module (default is 4%) @@ -431,6 +434,8 @@ contributed by `zetxx `_ - many thanks! input handling contributed by `ardadem `_ - many thanks! +multiple audio cards contributed by `hugoeustaquio `_ - many thanks! + .. image:: ../screenshots/amixer.png apt @@ -685,6 +690,49 @@ lacking the aforementioned pattern settings or they have wrong values. contributed by `somospocos `_ - many thanks! +cpu3 +~~~~ + +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: + * cpu3.layout: Space-separated list of widgets to add. + Possible widgets are: + + * cpu3.maxfreq + * cpu3.cpuload + * cpu3.coresload + * cpu3.temp + * cpu3.fanspeed + * cpu3.colored: 1 for colored per core load graph, 0 for mono (default) + * cpu3.temp_json: json path to look for in the output of 'sensors -j'; + required if cpu3.temp widget is used + * cpu3.fan_json: json path to look for in the output of 'sensors -j'; + required if cpu3.fanspeed widget is used + +Note: if you are getting 'n/a' for CPU temperature / fan speed, then you're +lacking the aforementioned json path settings or they have wrong values. + +Example json paths: + * `cpu3.temp_json="coretemp-isa-0000.Package id 0.temp1_input"` + * `cpu3.fan_json="thinkpad-isa-0000.fan1.fan1_input"` + +contributed by `SuperQ ` +based on cpu2 by `` + currency ~~~~~~~~ @@ -866,7 +914,9 @@ Displays first upcoming event in google calendar. Events that are set as 'all-day' will not be shown. Requires credentials.json from a google api application where the google calendar api is installed. -On first time run the browser will open and google will ask for permission for this app to access the google calendar and then save a .gcalendar_token.json file to the credentials_path directory which stores this permission. +On first time run the browser will open and google will ask for permission for this app to access +the google calendar and then save a .gcalendar_token.json file to the credentials_path directory +which stores this permission. A refresh is done every 15 minutes. @@ -878,7 +928,7 @@ Parameters: Requires these pip packages: * google-api-python-client >= 1.8.0 - * google-auth-httplib2 + * google-auth-httplib2 * google-auth-oauthlib getcrypto @@ -923,6 +973,29 @@ contributed by: .. image:: ../screenshots/github.png +gitlab +~~~~~~ + +Displays the GitLab todo count: + + * https://docs.gitlab.com/ee/user/todos.html + * https://docs.gitlab.com/ee/api/todos.html + +Uses `xdg-open` or `x-www-browser` to open web-pages. + +Requires the following library: + * requests + +Errors: + if the GitLab todo query failed, the shown value is `n/a` + +Parameters: + * gitlab.token: GitLab personal access token, the token needs to have the "read_api" scope. + * gitlab.host: Host of the GitLab instance, default is "gitlab.com". + * gitlab.actions: Comma separated actions to be parsed (e.g.: gitlab.actions=assigned,approval_required) + +.. image:: ../screenshots/gitlab.png + gpmdp ~~~~~ @@ -1107,6 +1180,7 @@ Parameters: if {file} = '/foo/bar.baz', then {file2} = 'bar' * mpd.host: MPD host to connect to. (mpc behaviour by default) + * mpd.port: MPD port 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. contributed by `alrayyes `_ - many thanks! @@ -1126,9 +1200,7 @@ network_traffic ~~~~~~~~~~~~~~~ Displays network traffic - -Requires the following library: - * netifaces + * No extra configuration needed contributed by `izn `_ - many thanks! @@ -1155,7 +1227,7 @@ nvidiagpu Displays GPU name, temperature and memory usage. Parameters: - * nvidiagpu.format: Format string (defaults to '{name}: {temp}°C %{mem_used}/{mem_total} MiB') + * 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} {gpu_usage_pct} {mem_usage_pct} {mem_io_pct} Requires nvidia-smi @@ -1239,12 +1311,19 @@ Displays the pi-hole status (up/down) together with the number of ads that were 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) + + + * pihole.apitoken : pi-hole API token (can be obtained in the pi-hole webinterface (Settings -> API) + + OR (deprecated!) + + * pihole.pwhash : pi-hole webinterface password hash (can be obtained from the /etc/pihole/SetupVars.conf file) + contributed by `bbernhard `_ - many thanks! pipewire -~~~~~~~ +~~~~~~~~ get volume level or control it @@ -1575,7 +1654,9 @@ Display a stock quote from finance.yahoo.com Parameters: * stock.symbols : Comma-separated list of symbols to fetch - * stock.change : Should we fetch change in stock value (defaults to True) + * stock.apikey : API key created on https://alphavantage.co + * stock.url : URL to use, defaults to "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={symbol}&apikey={apikey}" + * stock.fields : Fields from the response to show, defaults to "01. symbol,05. price,10. change percent" contributed by `msoulier `_ - many thanks! @@ -1610,11 +1691,11 @@ adds the possibility to * reboot the system. - + Per default a confirmation dialog is shown before the actual action is performed. - + Parameters: - * system.confirm: show confirmation dialog before performing any action (default: true) + * system.confirm: show confirmation dialog before performing any action (default: true) * system.reboot: specify a reboot command (defaults to 'reboot') * system.shutdown: specify a shutdown command (defaults to 'shutdown -h now') * system.logout: specify a logout command (defaults to 'i3exit logout') @@ -1713,6 +1794,27 @@ Parameters: * todo_org.remaining: False by default. When true, will output the number of remaining todos instead of the number completed (i.e. 1/4 means 1 of 4 todos remaining, rather than 1 of 4 todos completed) Based on the todo module by `codingo ` +todoist +~~~~~~~ + +Displays the nº of Todoist tasks that are due: + + * https://developer.todoist.com/rest/v2/#get-active-tasks + +Uses `xdg-open` or `x-www-browser` to open web-pages. + +Requires the following library: + * requests + +Errors: + if the Todoist get active tasks query failed, the shown value is `n/a` + +Parameters: + * todoist.token: Todoist api token, you can get it in https://todoist.com/app/settings/integrations/developer. + * todoist.filter: a filter statement defined by Todoist (https://todoist.com/help/articles/introduction-to-filters), eg: "!assigned to: others & (Overdue | due: today)" + +.. image:: ../screenshots/todoist.png + traffic ~~~~~~~ @@ -1751,6 +1853,27 @@ contributed by `ccoors `_ - many thanks! .. image:: ../screenshots/uptime.png +usage +~~~~~ + +Module for ActivityWatch (https://activitywatch.net/) +Displays the amount of time the system was used actively. + +Requirements: + * sqlite3 module for python + * ActivityWatch + +Errors: + * when you get 'error: unable to open database file', modify the parameter 'database' to your ActivityWatch database file + -> often found by running 'locate aw-server/peewee-sqlite.v2.db' + +Parameters: + * usage.database: path to your database file + * usage.format: Specify what gets printed to the bar + -> use 'HH', 'MM' or 'SS', they will get replaced by the number of hours, minutes and seconds, respectively + +contributed by lasnikr (https://github.com/lasnikr) + vpn ~~~ @@ -1770,6 +1893,34 @@ Displays the VPN profile that is currently in use. contributed by `bbernhard `_ - many thanks! +wakatime +~~~~~~~~ + +Displays the WakaTime daily/weekly/monthly times: + + * https://wakatime.com/developers#stats + +Uses `xdg-open` or `x-www-browser` to open web-pages. + +Requires the following library: + * requests + +Errors: + if the Wakatime status query failed, the shown value is `n/a` + +Parameters: + * wakatime.token: Wakatime secret api key, you can get it in https://wakatime.com/settings/account. + * wakatime.range: Range of the output, default is "Today". Can be one of “Today”, “Yesterday”, “Last 7 Days”, “Last 7 Days from Yesterday”, “Last 14 Days”, “Last 30 Days”, “This Week”, “Last Week”, “This Month”, or “Last Month”. + * wakatime.format: Format of the output, default is "digital" + Valid inputs are: + * "decimal" -> 1.37 + * "digital" -> 1:22 + * "seconds" -> 4931.29 + * "text" -> 1 hr 22 mins + * "%H:%M:%S" -> 01:22:31 (or any other valid format) + +.. image:: ../screenshots/wakatime.png + watson ~~~~~~ @@ -1778,6 +1929,10 @@ Displays the status of watson (time-tracking tool) Requires the following executable: * watson +Parameters: + * watson.format: Output format, defaults to "{project} [{tags}]" + Supported fields are: {project}, {tags}, {relative_start}, {absolute_start} + contributed by `bendardenne `_ - many thanks! weather @@ -1795,7 +1950,7 @@ Parameters: * 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 + * weather.apikey: API key from https://api.openweathermap.org contributed by `TheEdgeOfRage `_ - many thanks! From d30e5c694ea73c3bfcc2f0ce8c0e34226471ac4d Mon Sep 17 00:00:00 2001 From: Elin Angelov Date: Mon, 2 Oct 2023 12:31:15 +0300 Subject: [PATCH 66/93] Update modules.rst --- docs/modules.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/modules.rst b/docs/modules.rst index 1fb752c..0b3ff9d 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -884,6 +884,9 @@ be running. Scripts will be executed when dunst gets unpaused. Requires: * dunst v1.5.0+ +Parameters: + * dunstctl.disabled(Boolean): dunst state on start + contributed by `cristianmiranda `_ - many thanks! contributed by `joachimmathes `_ - many thanks! From 85760926d7d1f7ddaed9e5096bd0e9ebb70c3267 Mon Sep 17 00:00:00 2001 From: siggi Date: Tue, 3 Oct 2023 22:28:28 +0200 Subject: [PATCH 67/93] Add Power-Profile module --- .../modules/contrib/power-profile.py | 99 +++++++++++++++++++ requirements/modules/power-profile.txt | 2 + tests/modules/contrib/test_power-profile.py | 32 ++++++ themes/icons/ascii.json | 3 + themes/icons/awesome-fonts.json | 3 + 5 files changed, 139 insertions(+) create mode 100644 bumblebee_status/modules/contrib/power-profile.py create mode 100644 requirements/modules/power-profile.txt create mode 100644 tests/modules/contrib/test_power-profile.py diff --git a/bumblebee_status/modules/contrib/power-profile.py b/bumblebee_status/modules/contrib/power-profile.py new file mode 100644 index 0000000..2959391 --- /dev/null +++ b/bumblebee_status/modules/contrib/power-profile.py @@ -0,0 +1,99 @@ +# pylint: disable=C0111,R0903 +""" +Displays the current Power-Profile active + + +Left-Click or Right-Click as well as Scrolling up / down changes the active Power-Profile + +Prerequisites: + * dbus-python + * power-profiles-daemon +""" + +import dbus +import core.module +import core.widget +import core.input + + +class PowerProfileManager: + def __init__(self): + self.POWER_PROFILES_NAME = "net.hadess.PowerProfiles" + self.POWER_PROFILES_PATH = "/net/hadess/PowerProfiles" + self.PP_PROPERTIES_CURRENT_POWER_PROFILE = "ActiveProfile" + self.PP_PROPERTIES_ALL_POWER_PROFILES = "Profiles" + + self.DBUS_PROPERTIES = "org.freedesktop.DBus.Properties" + bus = dbus.SystemBus() + pp_proxy = bus.get_object(self.POWER_PROFILES_NAME, self.POWER_PROFILES_PATH) + self.pp_interface = dbus.Interface(pp_proxy, self.DBUS_PROPERTIES) + + def get_current_power_profile(self): + return self.pp_interface.Get( + self.POWER_PROFILES_NAME, self.PP_PROPERTIES_CURRENT_POWER_PROFILE + ) + + def __get_all_power_profile_names(self): + power_profiles = self.pp_interface.Get( + self.POWER_PROFILES_NAME, self.PP_PROPERTIES_ALL_POWER_PROFILES + ) + power_profiles_names = [] + for pp in power_profiles: + power_profiles_names.append(pp["Profile"]) + + return power_profiles_names + + def next_power_profile(self, event): + all_pp_names = self.__get_all_power_profile_names() + current_pp_index = self.__get_current_pp_index() + next_index = 0 + if current_pp_index != (len(all_pp_names) - 1): + next_index = current_pp_index + 1 + + self.pp_interface.Set( + self.POWER_PROFILES_NAME, + self.PP_PROPERTIES_CURRENT_POWER_PROFILE, + all_pp_names[next_index], + ) + + def prev_power_profile(self, event): + all_pp_names = self.__get_all_power_profile_names() + current_pp_index = self.__get_current_pp_index() + last_index = len(all_pp_names) - 1 + if current_pp_index is not 0: + last_index = current_pp_index - 1 + + self.pp_interface.Set( + self.POWER_PROFILES_NAME, + self.PP_PROPERTIES_CURRENT_POWER_PROFILE, + all_pp_names[last_index], + ) + + def __get_current_pp_index(self): + all_pp_names = self.__get_all_power_profile_names() + current_pp = self.get_current_power_profile() + return all_pp_names.index(current_pp) + + +class Module(core.module.Module): + def __init__(self, config, theme): + super().__init__(config, theme, core.widget.Widget(self.full_text)) + self.pp_manager = PowerProfileManager() + core.input.register( + self, button=core.input.WHEEL_UP, cmd=self.pp_manager.next_power_profile + ) + core.input.register( + self, button=core.input.WHEEL_DOWN, cmd=self.pp_manager.prev_power_profile + ) + core.input.register( + self, button=core.input.LEFT_MOUSE, cmd=self.pp_manager.next_power_profile + ) + core.input.register( + self, button=core.input.RIGHT_MOUSE, cmd=self.pp_manager.prev_power_profile + ) + + def full_text(self, widgets): + return self.pp_manager.get_current_power_profile() + + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/requirements/modules/power-profile.txt b/requirements/modules/power-profile.txt new file mode 100644 index 0000000..8f7d255 --- /dev/null +++ b/requirements/modules/power-profile.txt @@ -0,0 +1,2 @@ +dbus-python +power-profiles-daemon \ No newline at end of file diff --git a/tests/modules/contrib/test_power-profile.py b/tests/modules/contrib/test_power-profile.py new file mode 100644 index 0000000..43cb14f --- /dev/null +++ b/tests/modules/contrib/test_power-profile.py @@ -0,0 +1,32 @@ +from unittest.mock import patch, MagicMock +import unittest +import pytest + +import core.config +import modules.contrib.power_profile + +pytest.importorskip("dbus") + + +def build_powerprofile_module(): + config = core.config.Config([]) + return modules.contrib.power_profile.Module(config=config, theme=None) + + +class TestPowerProfileUnit(unittest.TestCase): + def __get_mock_dbus_get_method(self, mock_system_bus): + return ( + mock_system_bus.return_value.get_object.return_value.get_dbus_method.return_value + ) + + def test_load_module(self): + __import__("modules.contrib.power-profile") + + @patch("dbus.SystemBus") + def test_full_text(self, mock_system_bus): + mock_get = self.__get_mock_dbus_get_method(mock_system_bus) + mock_get.return_value = "balanced" + + module = build_powerprofile_module() + module.update() + assert module.widgets()[0].full_text() == "balanced" diff --git a/themes/icons/ascii.json b/themes/icons/ascii.json index d353386..4f48e4d 100644 --- a/themes/icons/ascii.json +++ b/themes/icons/ascii.json @@ -410,5 +410,8 @@ "speedtest": { "running": { "prefix": [".", "..", "...", ".."] }, "not-running": { "prefix": "[start]" } + }, + "power-profile": { + "prefix": "profile" } } diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index 60f8f51..7b86cb8 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -739,5 +739,8 @@ }, "thunderbird": { "prefix": "" + }, + "power-profile": { + "prefix": "\uF2C1" } } From 05622f985ae15d2814e336c75e9a1749a9ee00e3 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Thu, 26 Oct 2023 08:54:13 +0200 Subject: [PATCH 68/93] fix(module/shell): expand user directory in command spec Make sure that ~/ is expanded to the user's home directory in the command specified for the shell module. fixes #1002 --- bumblebee_status/modules/contrib/shell.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bumblebee_status/modules/contrib/shell.py b/bumblebee_status/modules/contrib/shell.py index e8461b9..46352b2 100644 --- a/bumblebee_status/modules/contrib/shell.py +++ b/bumblebee_status/modules/contrib/shell.py @@ -41,6 +41,7 @@ class Module(core.module.Module): super().__init__(config, theme, core.widget.Widget(self.get_output)) self.__command = self.parameter("command", 'echo "no command configured"') + self.__command = os.path.expanduser(self.__command) self.__async = util.format.asbool(self.parameter("async")) if self.__async: From 025d3fcb515be6f1ed3e462a10654aa0ad7ed13f Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Thu, 26 Oct 2023 09:00:09 +0200 Subject: [PATCH 69/93] fix(module/shell): synchonous invocation was broken For some (unknown) reason, redrawing while in the update loop breaks the update (this probably warrants a closer look). As a quickfix to restore functionality, remove the unnecessary redraw call and move it into the async codepath, where it is actually needed. fixes #1001 --- bumblebee_status/modules/contrib/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee_status/modules/contrib/shell.py b/bumblebee_status/modules/contrib/shell.py index 46352b2..4aabb5a 100644 --- a/bumblebee_status/modules/contrib/shell.py +++ b/bumblebee_status/modules/contrib/shell.py @@ -53,6 +53,7 @@ class Module(core.module.Module): def set_output(self, value): self.__output = value + core.event.trigger("update", [self.id], redraw_only=True) @core.decorators.scrollable def get_output(self, _): @@ -62,7 +63,6 @@ class Module(core.module.Module): # if requested then run not async version and just execute command in this thread if not self.__async: self.__output = util.cli.execute(self.__command, shell=True, ignore_errors=True).strip() - core.event.trigger("update", [self.id], redraw_only=True) return # if previous thread didn't end yet then don't do anything From 9f6c9cc7d2481d24b07ea9b84a16057d90fe99e2 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Thu, 26 Oct 2023 09:08:43 +0200 Subject: [PATCH 70/93] doc: update readthedocs.yaml --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 63fb402..15f1c60 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -6,4 +6,4 @@ python: build: os: ubuntu-22.04 tools: - python: "3.11" + python: "3" From c14ed1166dd2b9a99c896375e989555fcf0c8815 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Thu, 26 Oct 2023 09:11:39 +0200 Subject: [PATCH 71/93] fix: autotest - try to pin down pip versions --- .github/workflows/autotest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autotest.yml b/.github/workflows/autotest.yml index 4877a70..ac236f9 100644 --- a/.github/workflows/autotest.yml +++ b/.github/workflows/autotest.yml @@ -28,7 +28,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -U coverage pytest pytest-mock freezegun - pip install 'pygit2<1' 'libvirt-python<6.3' 'feedparser<6' || true + pip install 'pygit2<1' 'libvirt-python<6.3' 'feedparser<6' 'power<1.4' || true pip install $(cat requirements/modules/*.txt | cut -d ' ' -f 1 | sort -u) - name: Install Code Climate dependency run: | From bfafd936433acc9f2dea47065c4e423d7edb033f Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Thu, 26 Oct 2023 09:19:46 +0200 Subject: [PATCH 72/93] fix: remove power from tests installation as a quickfix --- .github/workflows/autotest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/autotest.yml b/.github/workflows/autotest.yml index ac236f9..ec02237 100644 --- a/.github/workflows/autotest.yml +++ b/.github/workflows/autotest.yml @@ -28,8 +28,8 @@ jobs: run: | python -m pip install --upgrade pip pip install -U coverage pytest pytest-mock freezegun - pip install 'pygit2<1' 'libvirt-python<6.3' 'feedparser<6' 'power<1.4' || true - pip install $(cat requirements/modules/*.txt | cut -d ' ' -f 1 | sort -u) + pip install 'pygit2<1' 'libvirt-python<6.3' 'feedparser<6' || true + pip install $(cat requirements/modules/*.txt | grep -v power | cut -d ' ' -f 1 | sort -u) - name: Install Code Climate dependency run: | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter From b45ff330c9d2044dda0a43503fab5d45973bd983 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Wed, 8 Nov 2023 00:28:29 +0100 Subject: [PATCH 73/93] wlrotation: init --- .../modules/contrib/wlrotation.py | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 bumblebee_status/modules/contrib/wlrotation.py diff --git a/bumblebee_status/modules/contrib/wlrotation.py b/bumblebee_status/modules/contrib/wlrotation.py new file mode 100644 index 0000000..54b43a9 --- /dev/null +++ b/bumblebee_status/modules/contrib/wlrotation.py @@ -0,0 +1,69 @@ +# pylint: disable=C0111,R0903 +# -*- coding: utf-8 -*- + +"""Shows a widget for each connected screen and allows the user to loop through different orientations. + +Requires the following executable: + * swaymsg +""" + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine +import json +from os import environ, path + +possible_orientations = ["normal", "90", "180", "270"] + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + self.service = "sway-auto-rotate@%s" % path.basename(environ['SWAYSOCK']).replace('-', '\\\\x2d') + widgets = [] + self._engine = engine + super(Module, self).__init__(engine, config, widgets) + self.update_widgets(widgets) + + def update_widgets(self, widgets): + for display in json.loads(bumblebee.util.execute("/usr/bin/swaymsg -t get_outputs -r")): + name = display['name'] + widget = self.widget(name) + if not widget: + widget = bumblebee.output.Widget(name=name, full_text="若 "+name) + widget.set("auto", bumblebee.util.execute("systemctl --user show %s -p ActiveState" % self.service).split("\n")[0].split("=")[1]) + self._engine.input.register_callback(widget, button=bumblebee.input.LEFT_MOUSE, cmd=self._toggle) + self._engine.input.register_callback(widget, button=bumblebee.input.RIGHT_MOUSE, cmd=self._toggle_auto) + widget.set("orientation", display['transform']) + widgets.append(widget) + + def update(self, widgets): + if len(widgets) <= 0: + self.update_widgets(widgets) + + def state(self, widget): + curr = widget.get("auto", "inactive") + if curr == "inactive": + return ["warning"] + return [curr] + + def _toggle_auto(self, event): + widget = self.widget_by_id(event["instance"]) + if widget.get("auto") == "inactive": + bumblebee.util.execute("systemctl --user start %s" % self.service) + else: + bumblebee.util.execute("systemctl --user stop %s" % self.service) + widget.set("auto", bumblebee.util.execute("systemctl --user show %s -p ActiveState" % self.service).split("\n")[0].split("=")[1]) + + def _toggle(self, event): + widget = self.widget_by_id(event["instance"]) + + # compute new orientation based on current orientation + idx = possible_orientations.index(widget.get("orientation")) + idx = (idx + 1) % len(possible_orientations) + new_orientation = possible_orientations[idx] + + widget.set("orientation", new_orientation) + + bumblebee.util.execute("swaymsg 'output {} transform {}'".format(widget.name, new_orientation)) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From 559517f345e95a19e0ff010cae4de3324cd9892d Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Wed, 8 Nov 2023 00:31:14 +0100 Subject: [PATCH 74/93] update golor theme and icons --- themes/gruvbox-powerline.json | 14 +-- themes/icons/awesome-fonts.json | 206 +++++++------------------------- 2 files changed, 50 insertions(+), 170 deletions(-) diff --git a/themes/gruvbox-powerline.json b/themes/gruvbox-powerline.json index d199243..e008e3f 100644 --- a/themes/gruvbox-powerline.json +++ b/themes/gruvbox-powerline.json @@ -9,6 +9,10 @@ "fg": "#fbf1c7", "bg": "#cc241d" }, + "good": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, "default-separators": false, "separator-block-width": 0 }, @@ -34,16 +38,6 @@ "bg": "#859900" } }, - "battery": { - "charged": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "AC": { - "fg": "#1d2021", - "bg": "#b8bb26" - } - }, "bluetooth": { "ON": { "fg": "#1d2021", diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index 7b86cb8..69cc583 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -241,46 +241,22 @@ "prefix": "\uf17c" }, "nic": { - "wireless-up": { - "prefix": "" - }, - "wireless-down": { - "prefix": "" - }, - "wired-up": { - "prefix": "" - }, - "wired-down": { - "prefix": "" - }, - "tunnel-up": { - "prefix": "" - }, - "tunnel-down": { - "prefix": "" - } + "wireless-up": { "prefix": "" }, + "wireless-down": { "prefix": "睊" }, + "wired-up": { "prefix": "" }, + "wired-down": { "prefix": "" }, + "tunnel-up": { "prefix": "嬨" }, + "tunnel-down": { "prefix": "嬨" } }, "bluetooth": { - "ON": { - "prefix": "" - }, - "OFF": { - "prefix": "" - }, - "?": { - "prefix": "" - } + "ON": { "prefix": "" }, + "OFF": { "prefix": "" }, + "?": { "prefix": "" } }, "bluetooth2": { - "ON": { - "prefix": "" - }, - "warning": { - "prefix": "" - }, - "critical": { - "prefix": "" - } + "ON": { "prefix": "" }, + "warning": { "prefix": "" }, + "critical": { "prefix": "" } }, "battery-upower": { "charged": { @@ -349,136 +325,46 @@ } }, "battery": { - "charged": { - "prefix": "", - "suffix": "" - }, - "AC": { - "suffix": "" - }, + "charged": { "prefix": "" }, + "AC": { "suffix": "ﮣ" }, + "PEN": { "suffix": "" }, "charging": { - "prefix": [ - "", - "", - "", - "", - "" - ], - "suffix": "" + "prefix": [ "", "", "", "", "", "", "" ], + "suffix": "" }, - "discharging-10": { - "prefix": "", - "suffix": "" - }, - "discharging-25": { - "prefix": "", - "suffix": "" - }, - "discharging-50": { - "prefix": "", - "suffix": "" - }, - "discharging-80": { - "prefix": "", - "suffix": "" - }, - "discharging-100": { - "prefix": "", - "suffix": "" - }, - "unlimited": { - "prefix": "", - "suffix": "" - }, - "estimate": { - "prefix": "" - }, - "unknown-10": { - "prefix": "", - "suffix": "" - }, - "unknown-25": { - "prefix": "", - "suffix": "" - }, - "unknown-50": { - "prefix": "", - "suffix": "" - }, - "unknown-80": { - "prefix": "", - "suffix": "" - }, - "unknown-100": { - "prefix": "", - "suffix": "" - } + "discharging-05": { "prefix": "", "suffix": "" }, + "discharging-10": { "prefix": "", "suffix": "" }, + "discharging-20": { "prefix": "", "suffix": "" }, + "discharging-30": { "prefix": "", "suffix": "" }, + "discharging-40": { "prefix": "", "suffix": "" }, + "discharging-50": { "prefix": "", "suffix": "" }, + "discharging-60": { "prefix": "", "suffix": "" }, + "discharging-70": { "prefix": "", "suffix": "" }, + "discharging-80": { "prefix": "", "suffix": "" }, + "discharging-90": { "prefix": "", "suffix": "" }, + "discharging-100": { "prefix": "" }, + "unlimited": { "prefix": "", "suffix": "" }, + "estimate": { "prefix": "" } }, "battery_all": { - "charged": { - "prefix": "", - "suffix": "" - }, - "AC": { - "suffix": "" - }, + "charged": { "prefix": "", "suffix": "" }, + "AC": { "suffix": "" }, "charging": { - "prefix": [ - "", - "", - "", - "", - "" - ], + "prefix": [ "", "", "", "", "" ], "suffix": "" }, - "discharging-10": { - "prefix": "", - "suffix": "" - }, - "discharging-25": { - "prefix": "", - "suffix": "" - }, - "discharging-50": { - "prefix": "", - "suffix": "" - }, - "discharging-80": { - "prefix": "", - "suffix": "" - }, - "discharging-100": { - "prefix": "", - "suffix": "" - }, - "unlimited": { - "prefix": "", - "suffix": "" - }, - "estimate": { - "prefix": "" - }, - "unknown-10": { - "prefix": "", - "suffix": "" - }, - "unknown-25": { - "prefix": "", - "suffix": "" - }, - "unknown-50": { - "prefix": "", - "suffix": "" - }, - "unknown-80": { - "prefix": "", - "suffix": "" - }, - "unknown-100": { - "prefix": "", - "suffix": "" - } + "discharging-10": { "prefix": "", "suffix": "" }, + "discharging-25": { "prefix": "", "suffix": "" }, + "discharging-50": { "prefix": "", "suffix": "" }, + "discharging-80": { "prefix": "", "suffix": "" }, + "discharging-100": { "prefix": "", "suffix": "" }, + "unlimited": { "prefix": "", "suffix": "" }, + "estimate": { "prefix": "" }, + "unknown-10": { "prefix": "", "suffix": "" }, + "unknown-25": { "prefix": "", "suffix": "" }, + "unknown-50": { "prefix": "", "suffix": "" }, + "unknown-80": { "prefix": "", "suffix": "" }, + "unknown-100": { "prefix": "", "suffix": "" } }, "caffeine": { "activated": { @@ -691,7 +577,7 @@ } }, "vpn": { - "prefix": "" + "prefix": "嬨" }, "system": { "prefix": "  " From 61123eb7a06add02d86a437903987799edac4c9e Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Wed, 8 Nov 2023 21:52:08 +0100 Subject: [PATCH 75/93] fix wlrotation: migrated to new api --- .../modules/contrib/wlrotation.py | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/bumblebee_status/modules/contrib/wlrotation.py b/bumblebee_status/modules/contrib/wlrotation.py index 54b43a9..d94c280 100644 --- a/bumblebee_status/modules/contrib/wlrotation.py +++ b/bumblebee_status/modules/contrib/wlrotation.py @@ -7,38 +7,33 @@ Requires the following executable: * swaymsg """ -import bumblebee.util -import bumblebee.input -import bumblebee.output -import bumblebee.engine +import core.module +import core.input +import util.cli + import json from os import environ, path possible_orientations = ["normal", "90", "180", "270"] -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - self.service = "sway-auto-rotate@%s" % path.basename(environ['SWAYSOCK']).replace('-', '\\\\x2d') - widgets = [] - self._engine = engine - super(Module, self).__init__(engine, config, widgets) - self.update_widgets(widgets) +class Module(core.module.Module): + def __init__(self, config, theme): + super().__init__(config, theme, []) - def update_widgets(self, widgets): - for display in json.loads(bumblebee.util.execute("/usr/bin/swaymsg -t get_outputs -r")): + self.service = "sway-auto-rotate@%s" % path.basename(environ['SWAYSOCK']).replace('-', '\\\\x2d') +# self._widgets = [] + self.update_widgets() + + def update_widgets(self): + for display in json.loads(util.cli.execute("/usr/bin/swaymsg -t get_outputs -r")): name = display['name'] widget = self.widget(name) if not widget: - widget = bumblebee.output.Widget(name=name, full_text="若 "+name) - widget.set("auto", bumblebee.util.execute("systemctl --user show %s -p ActiveState" % self.service).split("\n")[0].split("=")[1]) - self._engine.input.register_callback(widget, button=bumblebee.input.LEFT_MOUSE, cmd=self._toggle) - self._engine.input.register_callback(widget, button=bumblebee.input.RIGHT_MOUSE, cmd=self._toggle_auto) + widget = self.add_widget(name=name, full_text="若 "+name) + widget.set("auto", util.cli.execute("systemctl --user show %s -p ActiveState" % self.service).split("\n")[0].split("=")[1]) + core.input.register(widget, button=core.input.LEFT_MOUSE, cmd=self._toggle) + core.input.register(widget, button=core.input.RIGHT_MOUSE, cmd=self._toggle_auto) widget.set("orientation", display['transform']) - widgets.append(widget) - - def update(self, widgets): - if len(widgets) <= 0: - self.update_widgets(widgets) def state(self, widget): curr = widget.get("auto", "inactive") @@ -47,23 +42,22 @@ class Module(bumblebee.engine.Module): return [curr] def _toggle_auto(self, event): - widget = self.widget_by_id(event["instance"]) + widget = self.widget(widget_id=event["instance"]) if widget.get("auto") == "inactive": - bumblebee.util.execute("systemctl --user start %s" % self.service) + util.cli.execute("systemctl --user start %s" % self.service) else: - bumblebee.util.execute("systemctl --user stop %s" % self.service) - widget.set("auto", bumblebee.util.execute("systemctl --user show %s -p ActiveState" % self.service).split("\n")[0].split("=")[1]) + util.cli.execute("systemctl --user stop %s" % self.service) + widget.set("auto", util.cli.execute("systemctl --user show %s -p ActiveState" % self.service).split("\n")[0].split("=")[1]) def _toggle(self, event): - widget = self.widget_by_id(event["instance"]) + widget = self.widget(widget_id=event["instance"]) # compute new orientation based on current orientation - idx = possible_orientations.index(widget.get("orientation")) - idx = (idx + 1) % len(possible_orientations) + idx = (possible_orientations.index(widget.get("orientation"))+ 1) % len(possible_orientations) new_orientation = possible_orientations[idx] widget.set("orientation", new_orientation) - bumblebee.util.execute("swaymsg 'output {} transform {}'".format(widget.name, new_orientation)) + util.cli.execute("swaymsg 'output {} transform {}'".format(widget.name, new_orientation)) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From 2e18d712844e60adf690ea575b3a73601cb07537 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Wed, 8 Nov 2023 22:46:58 +0100 Subject: [PATCH 76/93] battery: better support for pen battery and some formatting --- bumblebee_status/modules/contrib/battery.py | 38 ++++++++++++++------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/bumblebee_status/modules/contrib/battery.py b/bumblebee_status/modules/contrib/battery.py index 7c65642..21951c1 100644 --- a/bumblebee_status/modules/contrib/battery.py +++ b/bumblebee_status/modules/contrib/battery.py @@ -130,8 +130,14 @@ class Module(core.module.Module): log.debug("adding new widget for {}".format(battery)) widget = self.add_widget(full_text=self.capacity, name=battery) - for w in self.widgets(): - if util.format.asbool(self.parameter("decorate", True)) == False: + try: + with open("/sys/class/power_supply/{}/model_name".format(battery)) as f: + widget.set("pen", ("Pen" in f.read().strip())) + except Exception: + pass + + if util.format.asbool(self.parameter("decorate", True)) == False: + for widget in self.widgets(): widget.set("theme.exclude", "suffix") def hidden(self): @@ -147,15 +153,16 @@ class Module(core.module.Module): capacity = self.__manager.capacity(widget.name) widget.set("capacity", capacity) widget.set("ac", self.__manager.isac_any(self._batteries)) - widget.set("theme.minwidth", "100%") # Read power conumption if util.format.asbool(self.parameter("showpowerconsumption", False)): output = "{}% ({})".format( capacity, self.__manager.consumption(widget.name) ) - else: + elif capacity < 100: output = "{}%".format(capacity) + else: + output = "" if ( util.format.asbool(self.parameter("showremaining", True)) @@ -167,6 +174,16 @@ class Module(core.module.Module): output, util.format.duration(remaining, compact=True, unit=True) ) +# if bumblebee.util.asbool(self.parameter("rate", True)): +# try: +# with open("{}/power_now".format(widget.name)) as f: +# rate = (float(f.read())/1000000) +# if rate > 0: +# output = "{} {:.2f}w".format(output, rate) +# except Exception: +# pass + + if util.format.asbool(self.parameter("showdevice", False)): output = "{} ({})".format(output, widget.name) @@ -176,6 +193,9 @@ class Module(core.module.Module): state = [] capacity = widget.get("capacity") + if widget.get("pen"): + state.append("PEN") + if capacity < 0: log.debug("battery state: {}".format(state)) return ["critical", "unknown"] @@ -187,16 +207,10 @@ class Module(core.module.Module): charge = self.__manager.charge_any(self._batteries) else: charge = self.__manager.charge(widget.name) - if charge == "Discharging": + if charge in ["Discharging", "Unknown"]: state.append( "discharging-{}".format( - min([10, 25, 50, 80, 100], key=lambda i: abs(i - capacity)) - ) - ) - elif charge == "Unknown": - state.append( - "unknown-{}".format( - min([10, 25, 50, 80, 100], key=lambda i: abs(i - capacity)) + min([5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], key=lambda i: abs(i - capacity)) ) ) else: From 900a0710c55983173e3a2556c3b86f83ad8e9ee5 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Wed, 8 Nov 2023 22:56:23 +0100 Subject: [PATCH 77/93] nic: add click handler --- bumblebee_status/modules/core/nic.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bumblebee_status/modules/core/nic.py b/bumblebee_status/modules/core/nic.py index 09fe487..64ee226 100644 --- a/bumblebee_status/modules/core/nic.py +++ b/bumblebee_status/modules/core/nic.py @@ -25,6 +25,7 @@ import subprocess import core.module import core.decorators +import core.input import util.cli import util.format @@ -58,6 +59,8 @@ class Module(core.module.Module): self.iw = shutil.which("iw") self._update_widgets(widgets) + core.input.register(self, button=core.input.LEFT_MOUSE, cmd='/usr/local/bin/wifi-menu') + core.input.register(self, button=core.input.RIGHT_MOUSE, cmd='nm-connection-editor') def update(self): self._update_widgets(self.widgets()) @@ -88,7 +91,7 @@ class Module(core.module.Module): def _iswlan(self, intf): # wifi, wlan, wlp, seems to work for me - if intf.startswith("w"): + if intf.startswith("w") and not intf.startswith("wwan"): return True return False From 3ed26c62a5b63ac6ffb951f88af1474665302109 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Wed, 8 Nov 2023 22:59:56 +0100 Subject: [PATCH 78/93] vpn: shorter default label --- bumblebee_status/modules/contrib/vpn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bumblebee_status/modules/contrib/vpn.py b/bumblebee_status/modules/contrib/vpn.py index ae8e748..3381e4f 100644 --- a/bumblebee_status/modules/contrib/vpn.py +++ b/bumblebee_status/modules/contrib/vpn.py @@ -1,4 +1,5 @@ # pylint: disable=C0111,R0903 +# -*- coding: utf-8 -*- """ Displays the VPN profile that is currently in use. @@ -68,7 +69,7 @@ class Module(core.module.Module): def vpn_status(self, widget): if self.__connected_vpn_profile is None: - return "off" + return "" return self.__connected_vpn_profile def __on_vpndisconnect(self): From 8cc7c9de9b5a185db35bfe645cd1663f4e07be06 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Fri, 10 Nov 2023 16:46:38 +0100 Subject: [PATCH 79/93] icons: fix nerd font battery/ac --- themes/icons/awesome-fonts.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index 69cc583..f5c02c1 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -326,7 +326,7 @@ }, "battery": { "charged": { "prefix": "" }, - "AC": { "suffix": "ﮣ" }, + "AC": { "suffix": "󱐥" }, "PEN": { "suffix": "" }, "charging": { "prefix": [ "", "", "", "", "", "", "" ], From c3d4fce74c5f482919e75da955f274390b9437cc Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Fri, 10 Nov 2023 16:48:05 +0100 Subject: [PATCH 80/93] nic: fix syntax --- bumblebee_status/modules/core/nic.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bumblebee_status/modules/core/nic.py b/bumblebee_status/modules/core/nic.py index 64ee226..3f77c89 100644 --- a/bumblebee_status/modules/core/nic.py +++ b/bumblebee_status/modules/core/nic.py @@ -91,9 +91,7 @@ class Module(core.module.Module): def _iswlan(self, intf): # wifi, wlan, wlp, seems to work for me - if intf.startswith("w") and not intf.startswith("wwan"): - return True - return False + return intf.startswith("w") and not intf.startswith("wwan") def _istunnel(self, intf): return intf.startswith("tun") or intf.startswith("wg") From 53de1b524a9e1182fd23593cdc44eb337f0ea5b3 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Fri, 10 Nov 2023 16:50:16 +0100 Subject: [PATCH 81/93] bluetooth2: using dbus api, shortening output, some refactoring --- .../modules/contrib/bluetooth2.py | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/bumblebee_status/modules/contrib/bluetooth2.py b/bumblebee_status/modules/contrib/bluetooth2.py index 22eae88..76f1da5 100644 --- a/bumblebee_status/modules/contrib/bluetooth2.py +++ b/bumblebee_status/modules/contrib/bluetooth2.py @@ -8,7 +8,6 @@ Parameters: contributed by `martindoublem `_ - many thanks! """ - import os import re import subprocess @@ -22,7 +21,6 @@ import core.input import util.cli - class Module(core.module.Module): def __init__(self, config, theme): super().__init__(config, theme, core.widget.Widget(self.status)) @@ -46,7 +44,7 @@ class Module(core.module.Module): ) if state > 0: connected_devices = self.get_connected_devices() - self._status = "On - {}".format(connected_devices) + self._status = "{}".format(connected_devices) else: self._status = "Off" adapters_cmd = "rfkill list | grep Bluetooth" @@ -58,31 +56,22 @@ class Module(core.module.Module): def _toggle(self, widget=None): """Toggle bluetooth state.""" - if "On" in self._status: - state = "false" - else: - state = "true" - - cmd = ( - "dbus-send --system --print-reply --dest=org.blueman.Mechanism /org/blueman/mechanism org.blueman.Mechanism.SetRfkillState boolean:%s" - % state - ) - logging.debug("bt: toggling bluetooth") - util.cli.execute(cmd, ignore_errors=True) + + SetRfkillState = self._bus.get_object("org.blueman.Mechanism", "/org/blueman/mechanism").get_dbus_method("SetRfkillState", dbus_interface="org.blueman.Mechanism") + SetRfkillState(self._status == "Off") def state(self, widget): """Get current state.""" state = [] - if self._status == "No Adapter Found": + if self._status in [ "No Adapter Found", "Off" ]: state.append("critical") - elif self._status == "On - 0": + elif self._status == "0": state.append("warning") - elif "On" in self._status and not (self._status == "On - 0"): - state.append("ON") else: - state.append("critical") + state.append("ON") + return state def get_connected_devices(self): @@ -92,12 +81,8 @@ class Module(core.module.Module): ).GetManagedObjects() for path, interfaces in objects.items(): if "org.bluez.Device1" in interfaces: - if dbus.Interface( - self._bus.get_object("org.bluez", path), - "org.freedesktop.DBus.Properties", - ).Get("org.bluez.Device1", "Connected"): + if dbus.Interface(self._bus.get_object("org.bluez", path), "org.freedesktop.DBus.Properties", ).Get("org.bluez.Device1", "Connected"): devices += 1 return devices - # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From bbc26c263cb8777a88b8b5e7859c0f5930319cca Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Fri, 10 Nov 2023 16:51:07 +0100 Subject: [PATCH 82/93] wlrotation: fix nerd font icon --- bumblebee_status/modules/contrib/wlrotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee_status/modules/contrib/wlrotation.py b/bumblebee_status/modules/contrib/wlrotation.py index d94c280..ae9dbe8 100644 --- a/bumblebee_status/modules/contrib/wlrotation.py +++ b/bumblebee_status/modules/contrib/wlrotation.py @@ -29,7 +29,7 @@ class Module(core.module.Module): name = display['name'] widget = self.widget(name) if not widget: - widget = self.add_widget(name=name, full_text="若 "+name) + widget = self.add_widget(name=name, full_text="󰑵 "+name) widget.set("auto", util.cli.execute("systemctl --user show %s -p ActiveState" % self.service).split("\n")[0].split("=")[1]) core.input.register(widget, button=core.input.LEFT_MOUSE, cmd=self._toggle) core.input.register(widget, button=core.input.RIGHT_MOUSE, cmd=self._toggle_auto) From cbd0c58b4a67df4caefd03b93b00fe9416baec18 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Fri, 10 Nov 2023 19:44:33 +0100 Subject: [PATCH 83/93] wlrotation: refactored the entire module and integrated external services --- .../modules/contrib/wlrotation.py | 135 +++++++++++++----- 1 file changed, 99 insertions(+), 36 deletions(-) diff --git a/bumblebee_status/modules/contrib/wlrotation.py b/bumblebee_status/modules/contrib/wlrotation.py index ae9dbe8..242c50b 100644 --- a/bumblebee_status/modules/contrib/wlrotation.py +++ b/bumblebee_status/modules/contrib/wlrotation.py @@ -3,6 +3,10 @@ """Shows a widget for each connected screen and allows the user to loop through different orientations. +Parameters: + * wlrotation.display : Name of the output display that will be rotated + + wlrotation.auto : Boolean value if the display should be rotatet automatic by default + Requires the following executable: * swaymsg """ @@ -11,53 +15,112 @@ import core.module import core.input import util.cli +import iio import json +from math import degrees, atan2, sqrt from os import environ, path possible_orientations = ["normal", "90", "180", "270"] +class iioValue: + def __init__(self, channel): + self.channel = channel + self.scale = self.read('scale') + self.offset = self.read('offset') + + def read(self, attr): + return float(self.channel.attrs[attr].value) + + def value(self): + return (self.read('raw') + self.offset) * self.scale + +class iioAccelDevice: + def __init__(self): + self.ctx = iio.Context() # store ctx pointer + d = self.ctx.find_device('accel_3d') + self.x = iioValue(d.find_channel('accel_x')) + self.y = iioValue(d.find_channel('accel_y')) + self.z = iioValue(d.find_channel('accel_z')) + + def orientation(self): + """ + returns tuple of `[success, value]` where `success` indicates, if an accurate value could be meassured and `value` the sway output api compatible value or `normal` if success is `False` + """ + x_deg, y_deg, z_deg = self._deg() + if abs(z_deg) < 70: # checks if device is angled too shallow + if x_deg >= 70: return True, "270" + if x_deg <= -70: return True, "90" + if abs(x_deg) <= 20: + if y_deg < 0: return True, "normal" + if y_deg > 0: return True, "180" + return False, "normal" + + def _deg(self): + gravity = 9.81 + x, y, z = self.x.value() / gravity, self.y.value() / gravity, self.z.value() / gravity + return degrees(atan2(x, sqrt(pow(y, 2) + pow(z, 2)))), degrees(atan2(y, sqrt(pow(z, 2) + pow(x, 2)))), degrees(atan2(z, sqrt(pow(x, 2) + pow(y, 2)))) + +class Display(): + def __init__(self, name, widget, display_data, auto=False): + self.name = name + self.widget = widget + self.accelDevice = iioAccelDevice() + self._lock_auto_rotation(not auto) + + self.widget.set("orientation", display_data['transform']) + + core.input.register(widget, button=core.input.LEFT_MOUSE, cmd=self.rotate_90deg) + core.input.register(widget, button=core.input.RIGHT_MOUSE, cmd=self.toggle) + + def rotate_90deg(self, event): + # compute new orientation based on current orientation + current = self.widget.get("orientation") + self._set_rotation(possible_orientations[(possible_orientations.index(current) + 1) % len(possible_orientations)]) + # disable auto rotation + self._lock_auto_rotation(True) + + def toggle(self, event): + self._lock_auto_rotation(not self.locked) + + def auto_rotate(self): + # automagically rotate the display based on sensor values + # this is only called if rotation lock is disabled + success, value = self.accelDevice.orientation() + if success: + self._set_rotation(value) + + def _set_rotation(self, new_orientation): + self.widget.set("orientation", new_orientation) + util.cli.execute("swaymsg 'output {} transform {}'".format(self.name, new_orientation)) + + def _lock_auto_rotation(self, locked): + self.locked = locked + self.widget.set("locked", self.locked) + class Module(core.module.Module): + @core.decorators.every(seconds=1) def __init__(self, config, theme): super().__init__(config, theme, []) - self.service = "sway-auto-rotate@%s" % path.basename(environ['SWAYSOCK']).replace('-', '\\\\x2d') -# self._widgets = [] - self.update_widgets() - - def update_widgets(self): - for display in json.loads(util.cli.execute("/usr/bin/swaymsg -t get_outputs -r")): + self.display = None + display_filter = self.parameter("display", None) + for display in json.loads(util.cli.execute("swaymsg -t get_outputs -r")): name = display['name'] - widget = self.widget(name) - if not widget: - widget = self.add_widget(name=name, full_text="󰑵 "+name) - widget.set("auto", util.cli.execute("systemctl --user show %s -p ActiveState" % self.service).split("\n")[0].split("=")[1]) - core.input.register(widget, button=core.input.LEFT_MOUSE, cmd=self._toggle) - core.input.register(widget, button=core.input.RIGHT_MOUSE, cmd=self._toggle_auto) - widget.set("orientation", display['transform']) + if display_filter == None or display_filter == name: + self.display = Display(name, self.add_widget(name=name), display, auto=util.format.asbool(self.parameter("auto", False))) + break # I assume that it makes only sense to rotate a single screen + + def update(self): + if self.display == None: + return + if self.display.locked: + return + + self.display.auto_rotate() def state(self, widget): - curr = widget.get("auto", "inactive") - if curr == "inactive": - return ["warning"] - return [curr] - - def _toggle_auto(self, event): - widget = self.widget(widget_id=event["instance"]) - if widget.get("auto") == "inactive": - util.cli.execute("systemctl --user start %s" % self.service) - else: - util.cli.execute("systemctl --user stop %s" % self.service) - widget.set("auto", util.cli.execute("systemctl --user show %s -p ActiveState" % self.service).split("\n")[0].split("=")[1]) - - def _toggle(self, event): - widget = self.widget(widget_id=event["instance"]) - - # compute new orientation based on current orientation - idx = (possible_orientations.index(widget.get("orientation"))+ 1) % len(possible_orientations) - new_orientation = possible_orientations[idx] - - widget.set("orientation", new_orientation) - - util.cli.execute("swaymsg 'output {} transform {}'".format(widget.name, new_orientation)) + state = [] + state.append("locked" if widget.get("locked", True) else "auto") + return state # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From d7d4603855f004d3ec6b907afd19f97d1300e41f Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Fri, 10 Nov 2023 20:03:44 +0100 Subject: [PATCH 84/93] wlrotation: adding awesome-fonts icons --- themes/icons/awesome-fonts.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index f5c02c1..d01f66f 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -628,5 +628,9 @@ }, "power-profile": { "prefix": "\uF2C1" + }, + "wlrotation": { + "auto": {"prefix": "󰑵"}, + "locked": {"prefix": "󰑸"} } } From 60bfb19378cf6885f5b16a14a6af3a535404ea40 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Fri, 10 Nov 2023 20:44:16 +0100 Subject: [PATCH 85/93] pulseout: adding nerd font icons for low, mid, heigh, muted --- bumblebee_status/modules/core/pulsectl.py | 6 +++++- themes/icons/awesome-fonts.json | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/bumblebee_status/modules/core/pulsectl.py b/bumblebee_status/modules/core/pulsectl.py index fd77c26..63f9e36 100644 --- a/bumblebee_status/modules/core/pulsectl.py +++ b/bumblebee_status/modules/core/pulsectl.py @@ -198,6 +198,10 @@ class Module(core.module.Module): def state(self, _): if self.__muted: return ["warning", "muted"] - return ["unmuted"] + if self.__volume >= .5: + return ["unmuted", "unmuted-high"] + if self.__volume >= .1: + return ["unmuted", "unmuted-mid"] + return ["unmuted", "unmuted-low"] # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index d01f66f..dae3d92 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -199,11 +199,14 @@ }, "pulseout": { "muted": { - "prefix": "" + "prefix": "󰝟" }, "unmuted": { "prefix": "" - } + }, + "unmuted-low": { "prefix": "󰕿" }, + "unmuted-mid": { "prefix": "󰖀" }, + "unmuted-high": { "prefix": "󰕾" } }, "amixer": { "muted": { From 2e81eed830fa3e280ca1f7a6c0740e9b44b60062 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Fri, 10 Nov 2023 20:47:35 +0100 Subject: [PATCH 86/93] pulsein: replacing muted nerd font icon with slash in the other direction, just to align it with bluetooth2 --- themes/icons/awesome-fonts.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index dae3d92..77bc4ef 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -226,7 +226,7 @@ }, "pulsein": { "muted": { - "prefix": "" + "prefix": "󰍭" }, "unmuted": { "prefix": "" From 255794cd1c258ab63ae0c52a391546fb58445a93 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Fri, 10 Nov 2023 20:51:31 +0100 Subject: [PATCH 87/93] vpn: updating nerd font icon --- themes/icons/awesome-fonts.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index 77bc4ef..ef08f85 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -580,7 +580,7 @@ } }, "vpn": { - "prefix": "嬨" + "prefix": "󰖂" }, "system": { "prefix": "  " From 2bbac991dbff1b5ae74a59c483de1fd2f4fa9e27 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Fri, 10 Nov 2023 21:03:43 +0100 Subject: [PATCH 88/93] battery: updating nerd font icons --- themes/icons/awesome-fonts.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index ef08f85..8d77244 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -330,22 +330,22 @@ "battery": { "charged": { "prefix": "" }, "AC": { "suffix": "󱐥" }, - "PEN": { "suffix": "" }, + "PEN": { "suffix": "󰏪" }, "charging": { - "prefix": [ "", "", "", "", "", "", "" ], + "prefix": [ "󰢜", "󰂆", "󰂇", "󰂈", "󰢝", "󰂉", "󰢞", "󰂊", "󰂋", "󰂅" ], "suffix": "" }, - "discharging-05": { "prefix": "", "suffix": "" }, - "discharging-10": { "prefix": "", "suffix": "" }, - "discharging-20": { "prefix": "", "suffix": "" }, - "discharging-30": { "prefix": "", "suffix": "" }, - "discharging-40": { "prefix": "", "suffix": "" }, - "discharging-50": { "prefix": "", "suffix": "" }, - "discharging-60": { "prefix": "", "suffix": "" }, - "discharging-70": { "prefix": "", "suffix": "" }, - "discharging-80": { "prefix": "", "suffix": "" }, - "discharging-90": { "prefix": "", "suffix": "" }, - "discharging-100": { "prefix": "" }, + "discharging-05": { "prefix": "󰂎", "suffix": "" }, + "discharging-10": { "prefix": "󰁺", "suffix": "" }, + "discharging-20": { "prefix": "󰁻", "suffix": "" }, + "discharging-30": { "prefix": "󰁼", "suffix": "" }, + "discharging-40": { "prefix": "󰁽", "suffix": "" }, + "discharging-50": { "prefix": "󰁾", "suffix": "" }, + "discharging-60": { "prefix": "󰁿", "suffix": "" }, + "discharging-70": { "prefix": "󰂀", "suffix": "" }, + "discharging-80": { "prefix": "󰂁", "suffix": "" }, + "discharging-90": { "prefix": "󰂂", "suffix": "" }, + "discharging-100": { "prefix": "󰁹" }, "unlimited": { "prefix": "", "suffix": "" }, "estimate": { "prefix": "" } }, From fd4b940d5895298d0f0ecd664cfd47d88940e98a Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Wed, 6 Dec 2023 22:01:20 +0100 Subject: [PATCH 89/93] nic.py: adapting wifi-menu path --- bumblebee_status/modules/core/nic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee_status/modules/core/nic.py b/bumblebee_status/modules/core/nic.py index 3f77c89..f153a94 100644 --- a/bumblebee_status/modules/core/nic.py +++ b/bumblebee_status/modules/core/nic.py @@ -59,7 +59,7 @@ class Module(core.module.Module): self.iw = shutil.which("iw") self._update_widgets(widgets) - core.input.register(self, button=core.input.LEFT_MOUSE, cmd='/usr/local/bin/wifi-menu') + core.input.register(self, button=core.input.LEFT_MOUSE, cmd='wifi-menu') core.input.register(self, button=core.input.RIGHT_MOUSE, cmd='nm-connection-editor') def update(self): From a56b86100a6340bc1dfe7b57667c74ac61f6773f Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Wed, 6 Dec 2023 22:17:49 +0100 Subject: [PATCH 90/93] fix updated nerdfonts --- themes/icons/awesome-fonts.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index 8d77244..1e8d9df 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -252,13 +252,13 @@ "tunnel-down": { "prefix": "嬨" } }, "bluetooth": { - "ON": { "prefix": "" }, - "OFF": { "prefix": "" }, + "ON": { "prefix": "󰂯" }, + "OFF": { "prefix": "󰂲" }, "?": { "prefix": "" } }, "bluetooth2": { - "ON": { "prefix": "" }, - "warning": { "prefix": "" }, + "ON": { "prefix": "󰂯" }, + "warning": { "prefix": "󰂲" }, "critical": { "prefix": "" } }, "battery-upower": { @@ -328,7 +328,7 @@ } }, "battery": { - "charged": { "prefix": "" }, + "charged": { "prefix": "󰂄" }, "AC": { "suffix": "󱐥" }, "PEN": { "suffix": "󰏪" }, "charging": { From 21060a10a01cd6f1d0721090f8b0a53c60265bbc Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Wed, 17 Apr 2024 22:46:18 +0200 Subject: [PATCH 91/93] bluetooth2: only show connection count in cases of excessive numbers of connections --- bumblebee_status/modules/contrib/bluetooth2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee_status/modules/contrib/bluetooth2.py b/bumblebee_status/modules/contrib/bluetooth2.py index 76f1da5..deb2e0c 100644 --- a/bumblebee_status/modules/contrib/bluetooth2.py +++ b/bumblebee_status/modules/contrib/bluetooth2.py @@ -35,7 +35,7 @@ class Module(core.module.Module): def status(self, widget): """Get status.""" - return self._status + return self._status if self._status.isdigit() and int(self._status) > 1 else "" def update(self): """Update current state.""" From 5053bb0f1b300e96ade9bbbc47fb5f662490dcd9 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Wed, 17 Apr 2024 22:47:45 +0200 Subject: [PATCH 92/93] themes/awesome-fonts: fix bluetooth icons --- themes/icons/awesome-fonts.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index 8d77244..d81b8c8 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -252,14 +252,14 @@ "tunnel-down": { "prefix": "嬨" } }, "bluetooth": { - "ON": { "prefix": "" }, - "OFF": { "prefix": "" }, - "?": { "prefix": "" } + "ON": { "prefix": "󰂯" }, + "OFF": { "prefix": "󰂲" }, + "?": { "prefix": "󰂱" } }, "bluetooth2": { - "ON": { "prefix": "" }, - "warning": { "prefix": "" }, - "critical": { "prefix": "" } + "ON": { "prefix": "󰂱" }, + "warning": { "prefix": "󰂯" }, + "critical": { "prefix": "󰂲" } }, "battery-upower": { "charged": { From 676bbebf4ca826c0adab0cc2d2f9ab9a42a572a2 Mon Sep 17 00:00:00 2001 From: Ludwig Behm Date: Wed, 17 Apr 2024 23:05:16 +0200 Subject: [PATCH 93/93] bluetooth2: states and styling --- bumblebee_status/modules/contrib/bluetooth2.py | 5 +++-- themes/icons/awesome-fonts.json | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bumblebee_status/modules/contrib/bluetooth2.py b/bumblebee_status/modules/contrib/bluetooth2.py index deb2e0c..a9742ba 100644 --- a/bumblebee_status/modules/contrib/bluetooth2.py +++ b/bumblebee_status/modules/contrib/bluetooth2.py @@ -68,9 +68,10 @@ class Module(core.module.Module): if self._status in [ "No Adapter Found", "Off" ]: state.append("critical") elif self._status == "0": - state.append("warning") + state.append("enabled") else: - state.append("ON") + state.append("connected") + state.append("good") return state diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json index 9992cee..1b6bb2c 100644 --- a/themes/icons/awesome-fonts.json +++ b/themes/icons/awesome-fonts.json @@ -257,8 +257,8 @@ "?": { "prefix": "󰂱" } }, "bluetooth2": { - "ON": { "prefix": "󰂱" }, - "warning": { "prefix": "󰂯" }, + "connected": { "prefix": "󰂱" }, + "enabled": { "prefix": "󰂯" }, "critical": { "prefix": "󰂲" } }, "battery-upower": {