diff --git a/.github/workflows/aurpublish.yml b/.github/workflows/aurpublish.yml deleted file mode 100644 index c2ede44..0000000 --- a/.github/workflows/aurpublish.yml +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: Upload AUR Package - -on: - release: - types: [created] - -jobs: - aur-publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install requests - - name: Create PKGBUILD - run: | - python ./create-pkgbuild.py > ./PKGBUILD - - name: Publish AUR package - uses: KSXGitHub/github-actions-deploy-aur@v2.5.0 - with: - pkgname: bumblebee-status - pkgbuild: ./PKGBUILD - commit_username: ${{ secrets.AUR_USERNAME }} - commit_email: ${{ secrets.AUR_EMAIL }} - ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} - commit_message: Update AUR package - ssh_keyscan_types: rsa,dsa,ecdsa,ed25519 diff --git a/.github/workflows/autotest.yml b/.github/workflows/autotest.yml deleted file mode 100644 index ec02237..0000000 --- a/.github/workflows/autotest.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Tests - -on: - pull_request: - types: [ opened, reopened, edited ] - push: - -env: - CC_TEST_REPORTER_ID: 40cb00907f7a10e04868e856570bb997ab9c42fd3b63d980f2b2269433195fdf - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] - - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - - name: Install Ubuntu dependencies - run: sudo apt-get install -y libdbus-1-dev libgit2-dev libvirt-dev taskwarrior - - name: Install Python dependencies - 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 $(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 - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build - - name: Run tests - run: | - coverage run --source=. -m pytest tests -v - - name: Report coverage - uses: paambaati/codeclimate-action@v3.2.0 - with: - coverageCommand: coverage3 xml - debug: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 5e39422..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,70 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ main ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ main ] - schedule: - - cron: '31 0 * * 4' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.gitignore b/.gitignore index c21fe43..2bf1ac8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -*.o - # Vim swap files *swp *~ diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index 15f1c60..0000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,9 +0,0 @@ -version: 2 - -python: - install: - - requirements: docs/requirements.txt -build: - os: ubuntu-22.04 - tools: - python: "3" diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..369aeb7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,31 @@ +os: linux +language: python +env: + global: + - CC_TEST_REPORTER_ID=40cb00907f7a10e04868e856570bb997ab9c42fd3b63d980f2b2269433195fdf +python: + - "3.4" + - "3.5" + - "3.6" + - "3.7" + - "3.8" + - "3.9" +before_script: + - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + - chmod +x ./cc-test-reporter + - ./cc-test-reporter before-build +addons: + apt: + packages: + libdbus-1-dev + libgit2-dev + libvirt-dev + taskwarrior +install: + - pip install -U coverage pytest pytest-mock freezegun + - pip install 'pygit2<1' 'libvirt-python<6.3' 'feedparser<6' || true + - pip install $(cat requirements/modules/*.txt | cut -d ' ' -f 1 | sort -u) +script: + - coverage run --source=. -m pytest tests -v +after_script: + - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4ee8d31..099a62e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,5 +12,6 @@ But even if you can't provide those, any indicator that something is not working ### Adding a new module or theme If you want to add a new module, please have a look at [how to write a new module](docs/development/module.rst) and [how to write a new theme](docs/development/theme.rst). Then simply create a Pull Request and I will review the changes as soon as possible. +If you want to do me a *big* favour, check the Travis status for any failing unit tests. Oh - and if you happen to add unit tests, that's also something I am very grateful for! Thanks for reading until here! :) diff --git a/PKGBUILD.template b/PKGBUILD.template deleted file mode 100644 index 08b511d..0000000 --- a/PKGBUILD.template +++ /dev/null @@ -1,45 +0,0 @@ -# Maintainer: Tobias Witek -# Contributor: Daniel M. Capella -# Contributor: spookykidmm - -pkgname=bumblebee-status -pkgver= -pkgrel=1 -pkgdesc='Modular, theme-able status line generator for the i3 window manager' -arch=('any') -url=https://github.com/tobi-wan-kenobi/bumblebee-status -license=('MIT') -depends=('python' 'python-netifaces' 'python-psutil' 'python-requests') -optdepends=('xorg-xbacklight: to display a displays brightness' - 'xorg-xset: enable/disable automatic screen locking' - 'libnotify: enable/disable automatic screen locking' - 'dnf: display DNF package update information' - 'xorg-setxkbmap: display/change the current keyboard layout' - 'redshift: display the redshifts current color' - 'pulseaudio: control pulseaudio sink/sources' - 'xorg-xrandr: enable/disable screen outputs' - 'pacman: display current status of pacman' - 'iputils: display a ping' - 'python-i3ipc: display titlebar' - 'fakeroot: dependency of the pacman module' - 'python-pytz: timezone conversion for datetimetz module' - 'python-tzlocal: retrieve system timezone for datetimetz module' -) -source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz") -sha512sums=('') - -package() { - install -d "$pkgdir"/usr/bin \ - "$pkgdir"/usr/share/$pkgname/bumblebee_status/{core,util} \ - "$pkgdir"/usr/share/$pkgname/bumblebee_status/modules/{core,contrib} \ - "$pkgdir"/usr/share/$pkgname/themes/icons - ln -s /usr/share/$pkgname/$pkgname "$pkgdir"/usr/bin/$pkgname - ln -s /usr/share/$pkgname/bumblebee-ctl "$pkgdir"/usr/bin/bumblebee-ctl - - cd $pkgname-$pkgver - cp -a --parents $pkgname bumblebee_status/{,core/,util/,modules/core/,modules/contrib/}*.py \ - themes/{,icons/}*.json $pkgdir/usr/share/$pkgname - cp -r bin $pkgdir/usr/share/$pkgname/ - - install -Dm644 LICENSE "$pkgdir"/usr/share/licenses/$pkgname/LICENSE -} diff --git a/README.md b/README.md index 2f41627..848434e 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,13 @@ -bumblebee-status -===================================================== - -logo courtesy of [kellya](https://github.com/kellya) - thank you! +# bumblebee-status +[![Build Status](https://travis-ci.com/tobi-wan-kenobi/bumblebee-status.svg?branch=main)](https://travis-ci.com/tobi-wan-kenobi/bumblebee-status) [![Documentation Status](https://readthedocs.org/projects/bumblebee-status/badge/?version=main)](https://bumblebee-status.readthedocs.io/en/main/?badge=main) -![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) - +[![PyPI version](https://badge.fury.io/py/bumblebee-status.svg)](https://badge.fury.io/py/bumblebee-status) [![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) -[![CodeQL](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/codeql-analysis.yml) ![License](https://img.shields.io/github/license/tobi-wan-kenobi/bumblebee-status) **Many, many thanks to all contributors! I am still amazed by and deeply grateful for how many PRs this project gets.** @@ -43,7 +36,9 @@ Supported FontAwesome version: 4 (free version of 5 doesn't include some of the --- ***NOTE*** -The default branch for this project is `main`. If you are curious why: [ZDNet:github-master-alternative](https://www.zdnet.com/article/github-to-replace-master-with-alternative-term-to-avoid-slavery-references/) +The default branch for this project is `main` - I'm keeping `master` around for backwards compatibility (I do not want to break anybody's setup), but the default branch is now `main`! + +If you are curious why: [ZDNet:github-master-alternative](https://www.zdnet.com/article/github-to-replace-master-with-alternative-term-to-avoid-slavery-references/) --- @@ -84,14 +79,10 @@ pip install --user bumblebee-status There is also a SlackBuild available here: [slackbuilds:bumblebee-status](http://slackbuilds.org/repository/14.2/desktop/bumblebee-status/) - many thanks to [@Tonus1](https://github.com/Tonus1)! -An ebuild, for Gentoo Linux, is available on [gallifrey overlay](https://github.com/fedeliallalinea/gallifrey/tree/master/x11-misc/bumblebee-status). Instructions for adding the overlay can be found [here](https://github.com/fedeliallalinea/gallifrey/blob/master/README.md). - # Dependencies [Available modules](https://bumblebee-status.readthedocs.io/en/main/modules.html) lists the dependencies (Python modules and external executables) for each module. If you are not using a module, you don't need the dependencies. -Some themes (e.g. all ‘powerline’ themes) require Font Awesome http://fontawesome.io/ and a powerline-compatible font (powerline-fonts) https://github.com/powerline/fonts - # Usage ## Normal usage In your i3wm configuration, modify the *status_command* for your i3bar like this: diff --git a/bumblebee-ctl b/bumblebee-ctl index 435162d..b1c0f37 100755 --- a/bumblebee-ctl +++ b/bumblebee-ctl @@ -12,7 +12,6 @@ button = { "right-mouse": 3, "wheel-up": 4, "wheel-down": 5, - "update": -1, } @@ -21,7 +20,7 @@ def main(): parser.add_argument( "-b", "--button", - choices=["left-mouse", "right-mouse", "middle-mouse", "wheel-up", "wheel-down", "update"], + choices=["left-mouse", "right-mouse", "middle-mouse", "wheel-up", "wheel-down"], help="button to emulate", default="left-mouse", ) diff --git a/bumblebee-status b/bumblebee-status index a16c747..698bcf8 100755 --- a/bumblebee-status +++ b/bumblebee-status @@ -68,18 +68,13 @@ def handle_commands(config, update_lock): def handle_events(config, update_lock): while True: - 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) + line = sys.stdin.readline().strip(",").strip() + if line == "[": continue + logging.info("input event: {}".format(line)) + process_event(line, config, update_lock) def main(): - global started - config = core.config.Config(sys.argv[1:]) level = logging.DEBUG if config.debug() else logging.ERROR if config.logfile(): @@ -102,9 +97,6 @@ def main(): core.input.register(None, core.input.WHEEL_UP, "i3-msg workspace prev_on_output") 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, )) event_thread.daemon = True @@ -135,6 +127,8 @@ def main(): if util.format.asbool(config.get("engine.collapsible", True)) == True: core.input.register(None, core.input.MIDDLE_MOUSE, output.toggle_minimize) + core.event.trigger("start") + started = True signal.signal(10, sig_USR1_handler) while True: if update_lock.acquire(blocking=False) == True: @@ -152,7 +146,6 @@ 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("[") diff --git a/bumblebee_status/_version.py b/bumblebee_status/_version.py index 247364e..154e077 100644 --- a/bumblebee_status/_version.py +++ b/bumblebee_status/_version.py @@ -292,7 +292,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # TAG-NUM-gHEX mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe) if not mo: - # unparsable. Maybe git-describe is misbehaving? + # unparseable. Maybe git-describe is misbehaving? pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out return pieces diff --git a/bumblebee_status/core/config.py b/bumblebee_status/core/config.py index 0b564b3..b5fd5ef 100644 --- a/bumblebee_status/core/config.py +++ b/bumblebee_status/core/config.py @@ -240,16 +240,11 @@ class Config(util.store.Store): :param filename: path to the file to load """ - def load_config(self, filename, content=None): - if os.path.exists(filename) or content != None: + def load_config(self, filename): + if os.path.exists(filename): log.info("loading {}".format(filename)) tmp = RawConfigParser() - tmp.optionxform = str - - if content: - tmp.read_string(content) - else: - tmp.read(u"{}".format(filename)) + tmp.read(u"{}".format(filename)) if tmp.has_section("module-parameters"): for key, value in tmp.items("module-parameters"): @@ -281,15 +276,6 @@ 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 @@ -342,7 +328,7 @@ class Config(util.store.Store): """ def autohide(self, name): - return name in self.__args.autohide or name in util.format.aslist(self.get("autohide", [])) + return name in self.__args.autohide """Returns which modules should be hidden if they are in state error diff --git a/bumblebee_status/core/input.py b/bumblebee_status/core/input.py index 5752dd8..b0d9f23 100644 --- a/bumblebee_status/core/input.py +++ b/bumblebee_status/core/input.py @@ -10,7 +10,6 @@ MIDDLE_MOUSE = 2 RIGHT_MOUSE = 3 WHEEL_UP = 4 WHEEL_DOWN = 5 -UPDATE = -1 def button_name(button): @@ -24,8 +23,6 @@ def button_name(button): return "wheel-up" if button == WHEEL_DOWN: return "wheel-down" - if button == UPDATE: - return "update" return "n/a" @@ -54,11 +51,8 @@ def register(obj, button=None, cmd=None, wait=False): event_id = __event_id(obj.id if obj is not None else "", button) logging.debug("registering callback {}".format(event_id)) core.event.unregister(event_id) # make sure there's always only one input event - if callable(cmd): core.event.register_exclusive(event_id, cmd) - elif obj and hasattr(obj, cmd) and callable(getattr(obj, cmd)): - core.event.register_exclusive(event_id, lambda event: getattr(obj, cmd)(event)) else: core.event.register_exclusive(event_id, lambda event: __execute(event, cmd, wait)) diff --git a/bumblebee_status/core/module.py b/bumblebee_status/core/module.py index 37a3143..5be7a4f 100644 --- a/bumblebee_status/core/module.py +++ b/bumblebee_status/core/module.py @@ -1,6 +1,5 @@ import os import importlib -import importlib.util import logging import threading @@ -28,16 +27,10 @@ def import_user(module_short, config, theme): return getattr(mod, "Module")(config, theme) else: log.debug("importing {} from user via importlib.util".format(module_short)) - try: - spec = importlib.util.spec_from_file_location("modules.{}".format(module_short), usermod) - mod = importlib.util.module_from_spec(spec) - spec.loader.exec_module(mod) - return mod.Module(config, theme) - except Exception as e: - spec = importlib.util.find_spec("modules.{}".format(module_short), usermod) - mod = importlib.util.module_from_spec(spec) - spec.loader.exec_module(mod) - return mod.Module(config, theme) + spec = importlib.util.spec_from_file_location("modules.{}".format(module_short), usermod) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod.Module(config, theme) raise ImportError("not found") """Loads a module by name @@ -96,8 +89,6 @@ class Module(core.input.Object): self.alias = self.__config.get("__alias__", None) self.id = self.alias if self.alias else self.name self.next_update = None - self.minimized = False - self.minimized = self.parameter("start-minimized", False) self.theme = theme @@ -113,15 +104,6 @@ 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. @@ -138,8 +120,6 @@ class Module(core.input.Object): for prefix in [self.name, self.module_name, self.alias]: value = self.__config.get("{}.{}".format(prefix, key), value) - if self.minimized: - value = self.__config.get("{}.minimized.{}".format(prefix, key), value) return value """Set a parameter for this module @@ -305,7 +285,7 @@ class Error(Module): def full_text(self, widget): return "{}: {}".format(self.__module, self.__error) - """Overridden state, always returns critical (it *is* an error, after all""" + """Overriden state, always returns critical (it *is* an error, after all""" def state(self, widget): return ["critical"] diff --git a/bumblebee_status/core/output.py b/bumblebee_status/core/output.py index 9ff3010..1760309 100644 --- a/bumblebee_status/core/output.py +++ b/bumblebee_status/core/output.py @@ -1,7 +1,6 @@ import sys import json import time -import threading import core.theme import core.event @@ -58,9 +57,6 @@ class block(object): def set(self, key, value): self.__attributes[key] = value - def get(self, key, default=None): - return self.__attributes.get(key, default) - def is_pango(self, attr): if isinstance(attr, dict) and "pango" in attr: return True @@ -95,17 +91,9 @@ class block(object): assign(self.__attributes, result, "background", "bg") if "full_text" in self.__attributes: - prefix = self.__pad(self.pangoize(self.__attributes.get("prefix"))) - suffix = self.__pad(self.pangoize(self.__attributes.get("suffix"))) - self.set("_prefix", prefix) - self.set("_suffix", suffix) - self.set("_raw", self.get("full_text")) result["full_text"] = self.pangoize(result["full_text"]) result["full_text"] = self.__format(self.__attributes["full_text"]) - if "min-width" in self.__attributes and "padding" in self.__attributes: - self.set("min-width", self.__format(self.get("min-width"))) - for k in [ "name", "instance", @@ -135,8 +123,11 @@ class block(object): def __format(self, text): if text is None: return None - prefix = self.get("_prefix") - suffix = self.get("_suffix") + prefix = self.__pad(self.pangoize(self.__attributes.get("prefix"))) + suffix = self.__pad(self.pangoize(self.__attributes.get("suffix"))) + self.set("_prefix", prefix) + self.set("_suffix", suffix) + self.set("_raw", text) return "{}{}{}".format(prefix, text, suffix) @@ -146,14 +137,10 @@ 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 @@ -171,25 +158,18 @@ class i3(object): def toggle_minimize(self, event): widget_id = event["instance"] - for module in self.__modules: - if module.widget(widget_id=widget_id) and util.format.asbool(module.parameter("minimize", False)) == True: - # this module can customly minimize - module.minimized = not module.minimized - return - if widget_id in self.__content: self.__content[widget_id]["minimized"] = not self.__content[widget_id]["minimized"] def draw(self, what, args=None): - with self.__lock: - 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)) - if "suffix" in data: - sys.stdout.write(data["suffix"]) - sys.stdout.write("\n") - sys.stdout.flush() + 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)) + if "suffix" in data: + sys.stdout.write(data["suffix"]) + sys.stdout.write("\n") + sys.stdout.flush() def start(self): return { @@ -226,32 +206,12 @@ 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"] + state in widget.state() for state in ["warning", "critical"] ): continue if module.hidden(): @@ -263,14 +223,9 @@ 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): - with self.__lock: - self.update2(affected_modules, redraw_only, force) - - def update2(self, affected_modules=None, redraw_only=False, force=False): now = time.time() for module in self.__modules: if affected_modules and not module.id in affected_modules: @@ -294,7 +249,6 @@ 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/core/theme.py b/bumblebee_status/core/theme.py index 1426450..4de58d3 100644 --- a/bumblebee_status/core/theme.py +++ b/bumblebee_status/core/theme.py @@ -14,20 +14,11 @@ log = logging.getLogger(__name__) THEME_BASE_DIR = os.path.dirname(os.path.realpath(__file__)) PATHS = [ ".", - os.path.join(THEME_BASE_DIR, "../../themes") -] - -if os.environ.get("XDG_DATA_DIRS"): - PATHS.extend([ - os.path.join(p, "bumblebee-status/themes") for p in os.environ["XDG_DATA_DIRS"].split(":") - ]) - -PATHS.extend([ + os.path.join(THEME_BASE_DIR, "../../themes"), 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", -]) +] def themes(): diff --git a/bumblebee_status/modules/contrib/amixer.py b/bumblebee_status/modules/contrib/amixer.py index 6f44e8c..b0fda92 100644 --- a/bumblebee_status/modules/contrib/amixer.py +++ b/bumblebee_status/modules/contrib/amixer.py @@ -4,15 +4,12 @@ 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 @@ -29,7 +26,6 @@ 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 @@ -66,7 +62,7 @@ class Module(core.module.Module): self.set_parameter("{}%-".format(self.__change)) def set_parameter(self, parameter): - util.cli.execute("amixer -c {} -q set {} {}".format(self.__card, self.__device, parameter)) + util.cli.execute("amixer -q set {} {}".format(self.__device, parameter)) def volume(self, widget): if self.__level == "n/a": @@ -83,7 +79,7 @@ class Module(core.module.Module): def update(self): try: self.__level = util.cli.execute( - "amixer -c {} get {}".format(self.__card, self.__device) + "amixer get {}".format(self.__device) ) except Exception as e: self.__level = "n/a" diff --git a/bumblebee_status/modules/contrib/apt.py b/bumblebee_status/modules/contrib/apt.py index 575968f..b7200bb 100644 --- a/bumblebee_status/modules/contrib/apt.py +++ b/bumblebee_status/modules/contrib/apt.py @@ -14,7 +14,6 @@ import threading import core.module import core.widget import core.decorators -import core.input import util.cli @@ -57,8 +56,6 @@ class Module(core.module.Module): def __init__(self, config, theme): super().__init__(config, theme, core.widget.Widget(self.updates)) self.__thread = None - core.input.register(self, button=core.input.RIGHT_MOUSE, - cmd=self.updates) def updates(self, widget): if widget.get("error"): diff --git a/bumblebee_status/modules/contrib/arandr.py b/bumblebee_status/modules/contrib/arandr.py index 8328f7e..7af565d 100644 --- a/bumblebee_status/modules/contrib/arandr.py +++ b/bumblebee_status/modules/contrib/arandr.py @@ -8,9 +8,6 @@ saved screen layout as well as toggle on/off individual connected displays. Parameters: * No configuration parameters -Requires the following python modules: - * tkinter - Requires the following executable: * arandr * xrandr @@ -57,7 +54,7 @@ class Module(core.module.Module): def activate_layout(layout_path): log.debug("activating layout") log.debug(layout_path) - execute(layout_path, ignore_errors=True) + execute(layout_path) def popup(self, widget): """Create Popup that allows the user to control their displays in one @@ -67,7 +64,7 @@ class Module(core.module.Module): menu = popup.menu() menu.add_menuitem( "arandr", - callback=partial(execute, self.manager, ignore_errors=True) + callback=partial(execute, self.manager) ) menu.add_separator() @@ -108,12 +105,11 @@ class Module(core.module.Module): if count_on == 1: log.info("attempted to turn off last display") return - execute("{} --output {} --off".format(self.toggle_cmd, display), ignore_errors=True) + execute("{} --output {} --off".format(self.toggle_cmd, display)) else: log.debug("toggling on {}".format(display)) execute( - "{} --output {} --auto".format(self.toggle_cmd, display), - ignore_errors=True + "{} --output {} --auto".format(self.toggle_cmd, display) ) @staticmethod @@ -124,7 +120,7 @@ class Module(core.module.Module): connected). """ displays = {} - for line in execute("xrandr -q", ignore_errors=True).split("\n"): + for line in execute("xrandr -q").split("\n"): if "connected" not in line: continue is_on = bool(re.search(r"\d+x\d+\+(\d+)\+\d+", line)) @@ -140,19 +136,16 @@ class Module(core.module.Module): def _get_layouts(): """Loads and parses the arandr screen layout scripts.""" layouts = {} - try: - for filename in os.listdir(__screenlayout_dir__): - if fnmatch.fnmatch(filename, '*.sh'): - fullpath = os.path.join(__screenlayout_dir__, filename) - with open(fullpath, "r") as file: - for line in file: - s_line = line.strip() - if "xrandr" not in s_line: - continue - displays_in_file = Module._parse_layout(line) - layouts[filename] = displays_in_file - except Exception as e: - log.error(str(e)) + for filename in os.listdir(__screenlayout_dir__): + if fnmatch.fnmatch(filename, '*.sh'): + fullpath = os.path.join(__screenlayout_dir__, filename) + with open(fullpath, "r") as file: + for line in file: + s_line = line.strip() + if "xrandr" not in s_line: + continue + displays_in_file = Module._parse_layout(line) + layouts[filename] = displays_in_file return layouts @staticmethod diff --git a/bumblebee_status/modules/contrib/arch-update.py b/bumblebee_status/modules/contrib/arch-update.py index d8da8fb..ed9ae58 100644 --- a/bumblebee_status/modules/contrib/arch-update.py +++ b/bumblebee_status/modules/contrib/arch-update.py @@ -7,7 +7,6 @@ contributed by `lucassouto `_ - many thanks! """ import logging -from time import sleep import core.module import core.widget @@ -36,7 +35,6 @@ class Module(core.module.Module): def update(self): self.__error = False - sleep(1) code, result = util.cli.execute( "checkupdates", ignore_errors=True, return_exitcode=True ) diff --git a/bumblebee_status/modules/contrib/aur-update.py b/bumblebee_status/modules/contrib/aur-update.py deleted file mode 100644 index 9afdc89..0000000 --- a/bumblebee_status/modules/contrib/aur-update.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Check updates for AUR. - -Requires the following executable: - * yay (https://github.com/Jguer/yay) - -contributed by `ishaanbhimwal `_ - many thanks! -""" - -import logging - -import core.module -import core.widget -import core.decorators - -import util.cli - - -class Module(core.module.Module): - @core.decorators.every(minutes=60) - def __init__(self, config, theme): - super().__init__(config, theme, core.widget.Widget(self.utilization)) - self.background = True - self.__packages = 0 - self.__error = False - - @property - def __format(self): - return self.parameter("format", "Update AUR: {}") - - def utilization(self, widget): - return self.__format.format(self.__packages) - - def hidden(self): - return self.__packages == 0 - - def update(self): - self.__error = False - code, result = util.cli.execute( - "yay -Qum", ignore_errors=True, return_exitcode=True - ) - - if code == 0: - if result == "": - self.__packages = 0 - else: - self.__packages = len(result.strip().split("\n")) - else: - self.__error = True - logging.error("aur-update exited with {}: {}".format(code, result)) - - def state(self, widget): - if self.__error: - return "warning" - return self.threshold_state(self.__packages, 1, 100) - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/contrib/battery-upower.py b/bumblebee_status/modules/contrib/battery-upower.py index ff65acd..9723058 100644 --- a/bumblebee_status/modules/contrib/battery-upower.py +++ b/bumblebee_status/modules/contrib/battery-upower.py @@ -68,7 +68,7 @@ class UPowerManager: isPresent = battery_proxy_interface.Get( self.UPOWER_NAME + ".Device", "IsPresent" ) - isRechargeable = battery_proxy_interface.Get( + isRechargable = battery_proxy_interface.Get( self.UPOWER_NAME + ".Device", "IsRechargeable" ) online = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Online") @@ -128,7 +128,7 @@ class UPowerManager: "HasHistory": hasHistory, "HasStatistics": hasStatistics, "IsPresent": isPresent, - "IsRechargeable": isRechargeable, + "IsRechargeable": isRechargable, "Online": online, "PowerSupply": powersupply, "Capacity": capacity, @@ -207,14 +207,6 @@ class UPowerManager: data = upower_interface.GetTotal() return data - def is_battery_present(self, battery): - battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery) - battery_proxy_interface = dbus.Interface(battery_proxy, self.DBUS_PROPERTIES) - - return bool( - battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "IsPresent") - ) - def is_loading(self, battery): battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery) battery_proxy_interface = dbus.Interface(battery_proxy, self.DBUS_PROPERTIES) @@ -267,11 +259,6 @@ class Module(core.module.Module): widget.set("capacity", -1) widget.set("ac", False) output = "n/a" - if not self.power.is_battery_present(self.device): - widget.set("ac", True) - widget.set("capacity", 100) - output = "ac" - return output try: capacity = int(self.power.get_device_percentage(self.device)) capacity = capacity if capacity < 100 else 100 @@ -311,6 +298,11 @@ class Module(core.module.Module): if capacity < 0: return ["critical", "unknown"] + if capacity < int(self.parameter("critical", 10)): + state.append("critical") + elif capacity < int(self.parameter("warning", 20)): + state.append("warning") + if widget.get("ac"): state.append("AC") else: @@ -336,16 +328,6 @@ class Module(core.module.Module): state.append("charged") else: state.append("charging") - if ( - capacity < int(self.parameter("critical", 10)) - and self.power.get_state(self.device) == "Discharging" - ): - state.append("critical") - elif ( - capacity < int(self.parameter("warning", 20)) - and self.power.get_state(self.device) == "Discharging" - ): - state.append("warning") return state diff --git a/bumblebee_status/modules/contrib/battery.py b/bumblebee_status/modules/contrib/battery.py index 21951c1..2db2031 100644 --- a/bumblebee_status/modules/contrib/battery.py +++ b/bumblebee_status/modules/contrib/battery.py @@ -130,19 +130,10 @@ class Module(core.module.Module): log.debug("adding new widget for {}".format(battery)) widget = self.add_widget(full_text=self.capacity, name=battery) - 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(): + for w in self.widgets(): + if util.format.asbool(self.parameter("decorate", True)) == False: widget.set("theme.exclude", "suffix") - def hidden(self): - return len(self._batteries) == 0 - def ac(self, widget): return "ac" @@ -153,16 +144,15 @@ 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) ) - elif capacity < 100: - output = "{}%".format(capacity) else: - output = "" + output = "{}%".format(capacity) if ( util.format.asbool(self.parameter("showremaining", True)) @@ -174,16 +164,6 @@ 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) @@ -193,13 +173,15 @@ 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"] + if capacity < int(self.parameter("critical", 10)): + state.append("critical") + elif capacity < int(self.parameter("warning", 20)): + state.append("warning") + if widget.get("ac"): state.append("AC") else: @@ -207,10 +189,16 @@ class Module(core.module.Module): charge = self.__manager.charge_any(self._batteries) else: charge = self.__manager.charge(widget.name) - if charge in ["Discharging", "Unknown"]: + if charge == "Discharging": state.append( "discharging-{}".format( - min([5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], key=lambda i: abs(i - capacity)) + 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)) ) ) else: @@ -218,18 +206,6 @@ class Module(core.module.Module): state.append("charged") else: state.append("charging") - - if ( - capacity < int(self.parameter("critical", 10)) - and self.__manager.charge_any(self._batteries) == "Discharging" - ): - state.append("critical") - elif ( - capacity < int(self.parameter("warning", 20)) - and self.__manager.charge_any(self._batteries) == "Discharging" - ): - state.append("warning") - return state diff --git a/bumblebee_status/modules/contrib/bluetooth.py b/bumblebee_status/modules/contrib/bluetooth.py index 56f5b8b..b565494 100644 --- a/bumblebee_status/modules/contrib/bluetooth.py +++ b/bumblebee_status/modules/contrib/bluetooth.py @@ -75,15 +75,19 @@ class Module(core.module.Module): def popup(self, widget): """Show a popup menu.""" - menu = util.popup.menu(self.__config) + menu = util.popup.PopupMenu() if self._status == "On": - menu.add_menuitem("Disable Bluetooth", callback=self._toggle) + menu.add_menuitem("Disable Bluetooth") elif self._status == "Off": - menu.add_menuitem("Enable Bluetooth", callback=self._toggle) + menu.add_menuitem("Enable Bluetooth") else: return - menu.show(widget) + # show menu and get return code + ret = menu.show(widget) + if ret == 0: + # first (and only) item selected. + self._toggle() def _toggle(self, widget=None): """Toggle bluetooth state.""" @@ -102,7 +106,7 @@ class Module(core.module.Module): ) logging.debug("bt: toggling bluetooth") - util.cli.execute(cmd, ignore_errors=True) + util.cli.execute(cmd) def state(self, widget): """Get current state.""" diff --git a/bumblebee_status/modules/contrib/bluetooth2.py b/bumblebee_status/modules/contrib/bluetooth2.py index a9742ba..52474b9 100644 --- a/bumblebee_status/modules/contrib/bluetooth2.py +++ b/bumblebee_status/modules/contrib/bluetooth2.py @@ -8,6 +8,7 @@ Parameters: contributed by `martindoublem `_ - many thanks! """ + import os import re import subprocess @@ -21,6 +22,7 @@ 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)) @@ -35,7 +37,7 @@ class Module(core.module.Module): def status(self, widget): """Get status.""" - return self._status if self._status.isdigit() and int(self._status) > 1 else "" + return self._status def update(self): """Update current state.""" @@ -44,7 +46,7 @@ class Module(core.module.Module): ) if state > 0: connected_devices = self.get_connected_devices() - self._status = "{}".format(connected_devices) + self._status = "On - {}".format(connected_devices) else: self._status = "Off" adapters_cmd = "rfkill list | grep Bluetooth" @@ -56,23 +58,31 @@ class Module(core.module.Module): def _toggle(self, widget=None): """Toggle bluetooth state.""" - logging.debug("bt: toggling bluetooth") + if "On" in self._status: + state = "false" + else: + state = "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") + 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) def state(self, widget): """Get current state.""" state = [] - if self._status in [ "No Adapter Found", "Off" ]: + if self._status == "No Adapter Found": state.append("critical") - elif self._status == "0": - state.append("enabled") + elif self._status == "On - 0": + state.append("warning") + elif "On" in self._status and not (self._status == "On - 0"): + state.append("ON") else: - state.append("connected") - state.append("good") - + state.append("critical") return state def get_connected_devices(self): @@ -82,8 +92,12 @@ 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 diff --git a/bumblebee_status/modules/contrib/blugon.py b/bumblebee_status/modules/contrib/blugon.py deleted file mode 100644 index 83e0bb6..0000000 --- a/bumblebee_status/modules/contrib/blugon.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Displays temperature of blugon and Controls it. - -Use wheel up and down to change temperature, middle click to toggle and right click to reset temperature. - -Default Values: - * Minimum temperature: 1000 (red) - * Maximum temperature: 20000 (blue) - * Default temperature: 6600 - -Requires the following executable: - * blugon - -Parameters: - * blugon.step: The amount of increase/decrease on scroll (default: 200) - -contributed by `DTan13 ` -""" - -import core.module -import core.widget - -import util.cli -import util.format - -import os - - -class Module(core.module.Module): - def __init__(self, config, theme): - super().__init__(config, theme, core.widget.Widget(self.full_text)) - self.__state = True - self.__default = 6600 - self.__step = ( - util.format.asint(self.parameter("step")) if self.parameter("step") else 200 - ) - self.__max, self.__min = 20000, 1000 - - file = open(os.path.expanduser("~/.config/blugon/current")) - self.__current = int(float(file.read())) - - events = [ - { - "type": "toggle", - "action": self.toggle, - "button": core.input.MIDDLE_MOUSE, - }, - { - "type": "blue", - "action": self.blue, - "button": core.input.WHEEL_UP, - }, - { - "type": "red", - "action": self.red, - "button": core.input.WHEEL_DOWN, - }, - { - "type": "reset", - "action": self.reset, - "button": core.input.RIGHT_MOUSE, - }, - ] - - for event in events: - core.input.register(self, button=event["button"], cmd=event["action"]) - - def set_temp(self): - temp = self.__current if self.__state else self.__default - util.cli.execute("blugon --setcurrent={}".format(temp)) - - def full_text(self, widget): - return self.__current if self.__state else self.__default - - def state(self, widget): - if not self.__state: - return ["critical"] - - def toggle(self, event): - self.__state = not self.__state - self.set_temp() - - def reset(self, event): - self.__current = 6600 - self.set_temp() - - def blue(self, event): - if self.__state and (self.__current < self.__max): - self.__current += self.__step - self.set_temp() - - def red(self, event): - if self.__state and (self.__current > self.__min): - self.__current -= self.__step - self.set_temp() - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/contrib/cpu2.py b/bumblebee_status/modules/contrib/cpu2.py index e017f04..1512681 100644 --- a/bumblebee_status/modules/contrib/cpu2.py +++ b/bumblebee_status/modules/contrib/cpu2.py @@ -24,9 +24,9 @@ Parameters: * cpu2.fanspeed * cpu2.colored: 1 for colored per core load graph, 0 for mono (default) * cpu2.temp_pattern: pattern to look for in the output of 'sensors -u'; - required if cpu2.temp widget is used + required if cpu2.temp widged is used * cpu2.fan_pattern: pattern to look for in the output of 'sensors -u'; - required if cpu2.fanspeed widget is used + required if cpu2.fanspeed widged is used Note: if you are getting 'n/a' for CPU temperature / fan speed, then you're lacking the aforementioned pattern settings or they have wrong values. diff --git a/bumblebee_status/modules/contrib/cpu3.py b/bumblebee_status/modules/contrib/cpu3.py deleted file mode 100644 index 5321012..0000000 --- a/bumblebee_status/modules/contrib/cpu3.py +++ /dev/null @@ -1,158 +0,0 @@ -"""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/bumblebee_status/modules/contrib/dunstctl.py b/bumblebee_status/modules/contrib/dunstctl.py index 1ad43df..3c803a4 100644 --- a/bumblebee_status/modules/contrib/dunstctl.py +++ b/bumblebee_status/modules/contrib/dunstctl.py @@ -24,14 +24,12 @@ import util.cli class Module(core.module.Module): def __init__(self, config, theme): super().__init__(config, theme, core.widget.Widget("")) - core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.toggle_state) + core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.__toggle_state) 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): + def __toggle_state(self, event): util.cli.execute("dunstctl set-paused toggle", ignore_errors=True) def state(self, widget): diff --git a/bumblebee_status/modules/contrib/emerge_status.py b/bumblebee_status/modules/contrib/emerge_status.py deleted file mode 100644 index 3758585..0000000 --- a/bumblebee_status/modules/contrib/emerge_status.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Display information about the currently running emerge process. - -Requires the following executable: - * emerge - -Parameters: - * emerge_status.format: Format string (defaults to '{current}/{total} {action} {category}/{pkg}') - -This code is based on emerge_status module from p3status [1] original created by AnwariasEu. - -[1] https://github.com/ultrabug/py3status/blob/master/py3status/modules/emerge_status.py -""" - -import re -import copy - -import core.module -import core.widget -import core.decorators - -import util.cli -import util.format - - -class Module(core.module.Module): - @core.decorators.every(seconds=10) - def __init__(self, config, theme): - super().__init__(config, theme, []) - self.__format = self.parameter( - "format", "{current}/{total} {action} {category}/{pkg}" - ) - self.__ret_default = { - "action": "", - "category": "", - "current": 0, - "pkg": "", - "total": 0, - } - - def update(self): - response = {} - ret = copy.deepcopy(self.__ret_default) - if self.__emerge_running(): - ret = self.__get_progress() - - widget = self.widget("status") - if not widget: - widget = self.add_widget(name="status") - - if ret["total"] == 0: - widget.full_text("emrg calculating...") - else: - widget.full_text( - " ".join( - self.__format.format( - current=ret["current"], - total=ret["total"], - action=ret["action"], - category=ret["category"], - pkg=ret["pkg"], - ).split() - ) - ) - else: - self.clear_widgets() - - def __emerge_running(self): - """ - Check if emerge is running. - Returns true if at least one instance of emerge is running. - """ - try: - util.cli.execute("pgrep emerge") - return True - except Exception: - return False - - def __get_progress(self): - """ - Get current progress of emerge. - Returns a dict containing current and total value. - """ - input_data = [] - ret = {} - - # traverse emerge.log from bottom up to get latest information - last_lines = util.cli.execute("tail -50 /var/log/emerge.log") - input_data = last_lines.split("\n") - input_data.reverse() - - for line in input_data: - if "*** terminating." in line: - # copy content of ret_default, not only the references - ret = copy.deepcopy(self.__ret_default) - break - else: - status_re = re.compile( - r"\((?P[\d]+) of (?P[\d]+)\) " - r"(?P[a-zA-Z/]+( [a-zA-Z]+)?) " - r"\((?P[\w\-]+)/(?P

[\w.]+)" - ) - res = status_re.search(line) - if res is not None: - ret["action"] = res.group("a").lower() - ret["category"] = res.group("ca") - ret["current"] = res.group("cu") - ret["pkg"] = res.group("p") - ret["total"] = res.group("t") - break - return ret - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/contrib/gcalendar.py b/bumblebee_status/modules/contrib/gcalendar.py deleted file mode 100644 index 6848ba8..0000000 --- a/bumblebee_status/modules/contrib/gcalendar.py +++ /dev/null @@ -1,171 +0,0 @@ -"""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. - -A refresh is done every 15 minutes. - -Parameters: - * gcalendar.time_format: Format time output. Defaults to "%H:%M". - * 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. - -Requires these pip packages: - * google-api-python-client >= 1.8.0 - * google-auth-httplib2 - * google-auth-oauthlib -""" - -# This import belongs to the google code -from __future__ import print_function - -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 -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=update_every) - def __init__(self, config, theme): - 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( - self.parameter("credentials_path", "~/") - ) - self.__credentials = os.path.join(self.__credentials_path, "credentials.json") - self.__token = os.path.join(self.__credentials_path, ".gcalendar_token.json") - - l = locale.getdefaultlocale() - if not l or l == (None, None): - l = ("en_US", "UTF-8") - lcl = self.parameter("locale", ".".join(l)) - try: - locale.setlocale(locale.LC_TIME, lcl.split(".")) - except Exception: - locale.setlocale(locale.LC_TIME, ("en_US", "UTF-8")) - - 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 - # 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 - now = datetime.datetime.utcnow().isoformat() + "Z" # 'Z' indicates UTC time - end = ( - datetime.datetime.utcnow() + datetime.timedelta(days=7) - ).isoformat() + "Z" # 'Z' indicates UTC time - # Get all calendars - calendar_list = service.calendarList().list().execute() - event_list = [] - for calendar_list_entry in calendar_list["items"]: - calendar_id = calendar_list_entry["id"] - events_result = ( - service.events() - .list( - calendarId=calendar_id, - timeMin=now, - timeMax=end, - singleEvents=True, - orderBy="startTime", - ) - .execute() - ) - events = events_result.get("items", []) - - for event in events: - start = dtparse( - event["start"].get("dateTime", event["start"].get("date")) - ) - # Only add to list if not an whole day event - if start.tzinfo: - event_list.append( - { - "date": start, - "summary": event["summary"], - "type": event["eventType"], - } - ) - sorted_list = sorted(event_list, key=lambda t: t["date"]) - next_event = sorted_list[0] - - 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: - 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 diff --git a/bumblebee_status/modules/contrib/gitlab.py b/bumblebee_status/modules/contrib/gitlab.py deleted file mode 100644 index 24f2eb8..0000000 --- a/bumblebee_status/modules/contrib/gitlab.py +++ /dev/null @@ -1,87 +0,0 @@ -# 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 shutil - -import requests - -import core.decorators -import core.input -import core.module -import core.widget -import util - - -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" - - 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/bumblebee_status/modules/contrib/messagereceiver.py b/bumblebee_status/modules/contrib/messagereceiver.py index 74943cd..48c389a 100644 --- a/bumblebee_status/modules/contrib/messagereceiver.py +++ b/bumblebee_status/modules/contrib/messagereceiver.py @@ -3,7 +3,7 @@ """ Displays the message that's received via unix socket. -Parameters: +Parameteres: * messagereceiver : Unix socket address (e.g: /tmp/bumblebee_messagereceiver.sock) Example: diff --git a/bumblebee_status/modules/contrib/mpd.py b/bumblebee_status/modules/contrib/mpd.py index 35cb2f6..3efffff 100644 --- a/bumblebee_status/modules/contrib/mpd.py +++ b/bumblebee_status/modules/contrib/mpd.py @@ -42,7 +42,6 @@ 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! @@ -74,12 +73,10 @@ class Module(core.module.Module): self._repeat = False self._tags = defaultdict(lambda: "") - self._hostcmd = "" - if self.parameter("host"): - self._hostcmd = " -h {}".format(self.parameter("host")) - if self.parameter("port"): - self._hostcmd += " -p {}".format(self.parameter("port")) - + if not self.parameter("host"): + self._hostcmd = "" + else: + self._hostcmd = " -h " + self.parameter("host") # Create widgets widget_map = {} @@ -97,12 +94,6 @@ class Module(core.module.Module): "cmd": "mpc toggle" + self._hostcmd, } widget.full_text(self.description) - elif widget_name == "mpd.toggle": - widget_map[widget] = { - "button": core.input.LEFT_MOUSE, - "cmd": "mpc toggle" + self._hostcmd, - } - widget.full_text(self.toggle) elif widget_name == "mpd.next": widget_map[widget] = { "button": core.input.LEFT_MOUSE, @@ -136,9 +127,6 @@ class Module(core.module.Module): def description(self, widget): return string.Formatter().vformat(self._fmt, (), self._tags) - def toggle(self, widget): - return str(util.cli.execute("mpc status %currenttime%/%totaltime%", ignore_errors=True)).strip() - def update(self): self._load_song() diff --git a/bumblebee_status/modules/contrib/network.py b/bumblebee_status/modules/contrib/network.py deleted file mode 100644 index a91c947..0000000 --- a/bumblebee_status/modules/contrib/network.py +++ /dev/null @@ -1,128 +0,0 @@ -""" -A module to show the currently active network connection (ethernet or wifi) and connection strength if the connection is wireless. - -Requires the Python netifaces package and iw installed on Linux. - -A simpler take on nic and network_traffic. No extra config necessary! - -""" - - -import util.cli -import util.format - -import core.module -import core.widget -import core.input - -import netifaces -import socket - - -class Module(core.module.Module): - @core.decorators.every(seconds=5) - def __init__(self, config, theme): - super().__init__(config, theme, core.widget.Widget(self.network)) - self.__is_wireless = False - self.__is_connected = False - self.__interface = None - self.__message = None - self.__signal = -110 - - # Get network information to display to the user - def network(self, widgets): - # Determine whether there is an internet connection - self.__is_connected = self.__attempt_connection() - - # Attempt to extract a valid network interface device - try: - self.__interface = netifaces.gateways()["default"][netifaces.AF_INET][1] - except Exception: - self.__interface = None - - # Check to see if the interface (if connected to the internet) is wireless - if self.__is_connected and self.__interface: - self.__is_wireless = self.__interface_is_wireless(self.__interface) - - # setup message to send to the user - if not self.__is_connected or not self.__interface: - self.__message = "No connection" - elif not self.__is_wireless: - # Assuming that if user is connected via non-wireless means that it will be ethernet - self.__signal = -30 - self.__message = "Ethernet" - else: - # We have a wireless connection - iw_dat = util.cli.execute("iwgetid") - has_ssid = "ESSID" in iw_dat - signal = self.__compute_signal(self.__interface) - - # If signal is None, that means that we can't compute the default interface's signal strength - self.__signal = ( - util.format.asint(signal, minimum=-110, maximum=-30) if signal else None - ) - - ssid = ( - iw_dat[iw_dat.index(":") + 1 :].replace('"', "").strip() - if has_ssid - else "Unknown" - ) - self.__message = self.__generate_wireles_message(ssid, self.__signal) - - return self.__message - - # State determined by signal strength - def state(self, widget): - if self.__compute_strength(self.__signal) < 50: - return "critical" - if self.__compute_strength(self.__signal) < 75: - return "warning" - - return None - - # manually done for better granularity / ease of parsing strength data - def __generate_wireles_message(self, ssid, signal): - computed_strength = self.__compute_strength(signal) - strength_str = str(computed_strength) if computed_strength else "?" - - return "{} {}%".format(ssid, strength_str) - - def __compute_strength(self, signal): - return int(100 * ((signal + 100) / 70.0)) if signal else None - - # get signal strength in decibels/milliwat - def __compute_signal(self, interface): - # Get connection strength - cmd = "iwconfig {}".format(interface) - config_dat = " ".join(util.cli.execute(cmd).split()) - config_tokens = config_dat.replace("=", " ").split() - - # handle weird output - try: - signal = config_tokens[config_tokens.index("level") + 1] - except Exception: - signal = None - - return signal - - def __attempt_connection(self): - can_connect = False - try: - socket.create_connection(("1.1.1.1", 53)) - can_connect = True - except Exception: - can_connect = False - - return can_connect - - def __interface_is_wireless(self, interface): - is_wireless = False - try: - with open("/proc/net/wireless", "r") as f: - is_wireless = interface in f.read() - f.close() - except Exception: - is_wireless = False - - return is_wireless - diff --git a/bumblebee_status/modules/contrib/nvidiagpu.py b/bumblebee_status/modules/contrib/nvidiagpu.py index 3b929a2..4aa9de9 100644 --- a/bumblebee_status/modules/contrib/nvidiagpu.py +++ b/bumblebee_status/modules/contrib/nvidiagpu.py @@ -4,15 +4,11 @@ Parameters: * nvidiagpu.format: Format string (defaults to '{name}: {temp}°C %{usedmem}/{totalmem} MiB') - Available values are: {name} {temp} {mem_used} {mem_total} {fanspeed} {clock_gpu} {clock_mem} {gpu_usage_pct} {mem_usage_pct} {mem_io_pct} + Available values are: {name} {temp} {mem_used} {mem_total} {fanspeed} {clock_gpu} {clock_mem} Requires nvidia-smi contributed by `RileyRedpath `_ - many thanks! - -Note: mem_io_pct is (from `man nvidia-smi`): -> Percent of time over the past sample period during which global (device) -> memory was being read or written. """ import core.module @@ -45,9 +41,6 @@ class Module(core.module.Module): clockMem = "" clockGpu = "" fanspeed = "" - gpuUsagePct = "" - memIoPct = "" - memUsage = "not found" for item in sp.split("\n"): try: key, val = item.split(":") @@ -68,18 +61,10 @@ class Module(core.module.Module): name = val elif key == "Fan Speed": fanspeed = val.split(" ")[0] - elif title == "Utilization": - if key == "Gpu": - gpuUsagePct = val.split(" ")[0] - elif key == "Memory": - memIoPct = val.split(" ")[0] except: title = item.strip() - if totalMem and usedMem: - memUsage = int(int(usedMem) / int(totalMem) * 100) - str_format = self.parameter( "format", "{name}: {temp}°C {mem_used}/{mem_total} MiB" ) @@ -91,9 +76,6 @@ class Module(core.module.Module): clock_gpu=clockGpu, clock_mem=clockMem, fanspeed=fanspeed, - gpu_usage_pct=gpuUsagePct, - mem_io_pct=memIoPct, - mem_usage_pct=memUsage, ) diff --git a/bumblebee_status/modules/contrib/optman.py b/bumblebee_status/modules/contrib/optman.py deleted file mode 100644 index 337003c..0000000 --- a/bumblebee_status/modules/contrib/optman.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Displays currently active gpu by optimus-manager -Requires the following packages: - - * optimus-manager - -""" - -import core.module -import core.widget - -import util.cli - -class Module(core.module.Module): - def __init__(self, config, theme): - super().__init__(config, theme, core.widget.Widget(self.output)) - self.__gpumode = "" - - def output(self, _): - return "GPU: {}".format(self.__gpumode) - - def update(self): - cmd = "optimus-manager --print-mode" - output = util.cli.execute(cmd).strip() - - if "intel" in output: - self.__gpumode = "Intel" - elif "nvidia" in output: - self.__gpumode = "Nvidia" - elif "amd" in output: - self.__gpumode = "AMD" diff --git a/bumblebee_status/modules/contrib/pacman.py b/bumblebee_status/modules/contrib/pacman.py index cb3de96..b50ed53 100644 --- a/bumblebee_status/modules/contrib/pacman.py +++ b/bumblebee_status/modules/contrib/pacman.py @@ -3,7 +3,7 @@ """Displays update information per repository for pacman. Parameters: - * pacman.sum: If you prefer displaying updates with a single digit (defaults to 'False') + * pacman.sum: If you prefere displaying updates with a single digit (defaults to 'False') Requires the following executables: * fakeroot diff --git a/bumblebee_status/modules/contrib/pamixer.py b/bumblebee_status/modules/contrib/pamixer.py deleted file mode 100644 index 46f3bb5..0000000 --- a/bumblebee_status/modules/contrib/pamixer.py +++ /dev/null @@ -1,88 +0,0 @@ -"""get volume level or control it - -Requires the following executable: - * pamixer - -Parameters: - * pamixer.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 = "volume 0%" - self.__muted = True - self.__change = util.format.asint( - self.parameter("percent_change", "4%").strip("%"), 0, 200 - ) - - 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): - self.set_parameter("--toggle-mute") - - def increase_volume(self, event): - self.set_parameter("--increase {}".format(self.__change)) - - def decrease_volume(self, event): - self.set_parameter("--decrease {}".format(self.__change)) - - def set_parameter(self, parameter): - util.cli.execute("pamixer {}".format(parameter)) - - def volume(self, widget): - if self.__level == "volume 0%": - self.__muted = True - return self.__level - m = re.search(r"([\d]+)\%", self.__level) - if m: - if m.group(1) != "0%" in self.__level: - self.__muted = False - return "volume {}%".format(m.group(1)) - else: - return "volume 0%" - - def update(self): - try: - volume = util.cli.execute("pamixer --get-volume-human".format()) - self.__level = volume - self.__muted = False - except Exception as e: - self.__level = "volume 0%" - - def state(self, widget): - if self.__muted: - return ["warning", "muted"] - return ["unmuted"] - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/contrib/persian_date.py b/bumblebee_status/modules/contrib/persian_date.py deleted file mode 100644 index 9873e1a..0000000 --- a/bumblebee_status/modules/contrib/persian_date.py +++ /dev/null @@ -1,31 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the current date and time in Persian(Jalali) Calendar. - -Requires the following python packages: - * jdatetime - -Parameters: - * datetime.format: strftime()-compatible formatting string. default: "%A %d %B" e.g., "جمعه ۱۳ اسفند" - * datetime.locale: locale to use. default: "fa_IR" -""" - -import jdatetime - -import core.decorators -from modules.core.datetime import Module as dtmodule - - -class Module(dtmodule): - @core.decorators.every(minutes=1) - def __init__(self, config, theme): - super().__init__(config, theme, dtlibrary=jdatetime) - - def default_format(self): - return "%A %d %B" - - def default_locale(self): - return ("fa_IR", "UTF-8") - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/contrib/pihole.py b/bumblebee_status/modules/contrib/pihole.py index 6e07287..7334abf 100644 --- a/bumblebee_status/modules/contrib/pihole.py +++ b/bumblebee_status/modules/contrib/pihole.py @@ -4,20 +4,13 @@ Parameters: * pihole.address : pi-hole address (e.q: http://192.168.1.3) - - - * 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) - + * 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 @@ -29,18 +22,7 @@ class Module(core.module.Module): super().__init__(config, theme, core.widget.Widget(self.pihole_status)) self._pihole_address = self.parameter("address", "") - 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_pw_hash = self.parameter("pwhash", "") self._pihole_status = None self._ads_blocked_today = "-" self.update_pihole_status() @@ -60,11 +42,7 @@ class Module(core.module.Module): def update_pihole_status(self): try: - data = requests.get( - self._pihole_address - + "/admin/api.php?summary&auth=" - + self._pihole_secret - ).json() + data = requests.get(self._pihole_address + "/admin/api.php?summary").json() self._pihole_status = True if data["status"] == "enabled" else False self._ads_blocked_today = data["ads_blocked_today"] except Exception as e: @@ -78,13 +56,13 @@ class Module(core.module.Module): req = requests.get( self._pihole_address + "/admin/api.php?disable&auth=" - + self._pihole_secret + + self._pihole_pw_hash ) else: req = requests.get( self._pihole_address + "/admin/api.php?enable&auth=" - + self._pihole_secret + + self._pihole_pw_hash ) if req is not None: if req.status_code == 200: diff --git a/bumblebee_status/modules/contrib/pipewire.py b/bumblebee_status/modules/contrib/pipewire.py deleted file mode 100644 index 86c9b9d..0000000 --- a/bumblebee_status/modules/contrib/pipewire.py +++ /dev/null @@ -1,90 +0,0 @@ -"""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 - - self.__id = self.parameter("sink_id") or "@DEFAULT_AUDIO_SINK@" - - 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 {} toggle".format(self.__id)) - - def increase_volume(self, event): - util.cli.execute( - "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.__id, self.__change) - ) - - def volume(self, widget): - if self.__level == "N/A": - return self.__level - 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 {}".format(self.__id)) - 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/bumblebee_status/modules/contrib/playerctl.py b/bumblebee_status/modules/contrib/playerctl.py old mode 100644 new mode 100755 index c405a0e..3a0d7e5 --- a/bumblebee_status/modules/contrib/playerctl.py +++ b/bumblebee_status/modules/contrib/playerctl.py @@ -6,15 +6,12 @@ Requires the following executable: * playerctl Parameters: - * playerctl.format: Format string (defaults to '{{artist}} - {{title}} {{duration(position)}}/{{duration(mpris:length)}}'). - The format string is passed to 'playerctl -f' as an argument. Read `the README `_ for more information. + * playerctl.format: Format string (defaults to '{artist} - {title}') + Available values are: {album}, {title}, {artist}, {trackNumber} * playerctl.layout: Comma-separated list to change order of widgets (defaults to song, previous, pause, next) Widget names are: playerctl.song, playerctl.prev, playerctl.pause, playerctl.next - * playerctl.args: The arguments added to playerctl. - You can check 'playerctl --help' or `its README `_. For example, it could be '-p vlc,%any'. - * playerctl.hide: Hide the widgets when no players are found. Defaults to "false". -Parameters are inspired by the `spotify` module, many thanks to its developers! +Parameters are inherited from `spotify` module, many thanks to its developers! contributed by `smitajit `_ - many thanks! """ @@ -33,17 +30,15 @@ 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( "layout", "playerctl.prev, playerctl.song, playerctl.pause, playerctl.next" ) ) - self.__cmd = "playerctl " + self.parameter("args", "") + " " - self.__format = self.parameter("format", "{{artist}} - {{title}} {{duration(position)}}/{{duration(mpris:length)}}") + self.__song = "" + self.__cmd = "playerctl " + self.__format = self.parameter("format", "{artist} - {title}") widget_map = {} for widget_name in self.__layout: @@ -53,6 +48,7 @@ class Module(core.module.Module): "button": core.input.LEFT_MOUSE, "cmd": self.__cmd + "previous", } + widget.set("state", "prev") elif widget_name == "playerctl.pause": widget_map[widget] = { "button": core.input.LEFT_MOUSE, @@ -63,6 +59,7 @@ class Module(core.module.Module): "button": core.input.LEFT_MOUSE, "cmd": self.__cmd + "next", } + widget.set("state", "next") elif widget_name == "playerctl.song": widget_map[widget] = [ { @@ -87,49 +84,34 @@ class Module(core.module.Module): if isinstance(callback_options, dict): core.input.register(widget, **callback_options) - def hidden(self): - return self.__hidden - - def status(self): + def update(self): try: - playback_status = str(util.cli.execute(self.__cmd + "status 2>&1 || true", shell = True)).strip() - if playback_status == "No players found": - return None - return playback_status + self.__get_song() + + for widget in self.widgets(): + if widget.name == "playerctl.pause": + playback_status = str(util.cli.execute(self.__cmd + "status")).strip() + if playback_status != "": + if playback_status == "Playing": + widget.set("state", "playing") + else: + widget.set("state", "paused") + elif widget.name == "playerctl.song": + widget.set("state", "song") + widget.full_text(self.__song) except Exception as e: logging.exception(e) - return None - - 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": - if playback_status == "Playing": - widget.set("state", "playing") - elif playback_status == "Paused": - widget.set("state", "paused") - elif playback_status == "Stopped": - widget.set("state", "stopped") - else: - widget.set("state", "") - elif widget.name == "playerctl.next": - widget.set("state", "next") - elif widget.name == "playerctl.prev": - widget.set("state", "prev") - elif widget.name == "playerctl.song": - widget.full_text(self.__get_song()) - else: - widget.set("state", "") - widget.full_text(" ") + self.__song = "" def __get_song(self): - try: - return str(util.cli.execute(self.__cmd + "metadata -f '" + self.__format + "'")).strip() - except Exception as e: - logging.exception(e) - return " " + album = str(util.cli.execute(self.__cmd + "metadata xesam:album")).strip() + title = str(util.cli.execute(self.__cmd + "metadata xesam:title")).strip() + artist = str(util.cli.execute(self.__cmd + "metadata xesam:albumArtist")).strip() + track_number = str(util.cli.execute(self.__cmd + "metadata xesam:trackNumber")).strip() + + self.__song = self.__format.format( + album = album, + title = title, + artist = artist, + trackNumber = track_number + ) diff --git a/bumblebee_status/modules/contrib/pomodoro.py b/bumblebee_status/modules/contrib/pomodoro.py index 3f1397a..fe5959f 100644 --- a/bumblebee_status/modules/contrib/pomodoro.py +++ b/bumblebee_status/modules/contrib/pomodoro.py @@ -13,7 +13,7 @@ Parameters: Example: 'notify-send 'Time up!''. If you want to chain multiple commands, please use an external wrapper script and invoke that. The module itself does not support command chaining (see https://github.com/tobi-wan-kenobi/bumblebee-status/issues/532 - for a detailed explanation) + for a detailled explanation) contributed by `martindoublem `_, inspired by `karthink `_ - many thanks! """ diff --git a/bumblebee_status/modules/contrib/power-profile.py b/bumblebee_status/modules/contrib/power-profile.py deleted file mode 100644 index 2959391..0000000 --- a/bumblebee_status/modules/contrib/power-profile.py +++ /dev/null @@ -1,99 +0,0 @@ -# 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/bumblebee_status/modules/contrib/progress.py b/bumblebee_status/modules/contrib/progress.py index c8905e7..a1938d2 100644 --- a/bumblebee_status/modules/contrib/progress.py +++ b/bumblebee_status/modules/contrib/progress.py @@ -29,9 +29,6 @@ class Module(core.module.Module): super().__init__(config, theme, core.widget.Widget(self.get_progress_text)) self.__active = False - def hidden(self): - return not self.__active - def get_progress_text(self, widget): if self.update_progress_info(widget): width = util.format.asint(self.parameter("barwidth", 8)) @@ -56,7 +53,7 @@ class Module(core.module.Module): return self.parameter("placeholder", "n/a") def update_progress_info(self, widget): - """Update widget's information about the copy""" + """Update widget's informations about the copy""" if not self.__active: return @@ -64,8 +61,8 @@ class Module(core.module.Module): # 1. pid # 2. command # 3. arguments - # 4. progress (xx.x formatted) - # 5. quantity (.. unit / .. unit formatted) + # 4. progress (xx.x formated) + # 5. quantity (.. unit / .. unit formated) # 6. speed # 7. time remaining extract_nospeed = re.compile( @@ -80,7 +77,7 @@ class Module(core.module.Module): result = extract_wtspeed.match(raw) if not result: - # Abort speed measures + # Abord speed measures raw = util.cli.execute("progress -q") result = extract_nospeed.match(raw) @@ -104,7 +101,7 @@ class Module(core.module.Module): def state(self, widget): if self.__active: - return ["copying", "no-autohide"] + return "copying" return "pending" diff --git a/bumblebee_status/modules/contrib/publicip.py b/bumblebee_status/modules/contrib/publicip.py index 8ae10a0..17a23e3 100644 --- a/bumblebee_status/modules/contrib/publicip.py +++ b/bumblebee_status/modules/contrib/publicip.py @@ -1,155 +1,28 @@ +"""Displays public IP address """ -Displays information about the public IP address associated with the default route: - * Public IP address - * Country Name - * Country Code - * City Name - * Geographic Coordinates - -Left mouse click on the widget forces immediate update. -Any change to the default route will cause the widget to update. - -Requirements: - * netifaces - -Parameters: - * publicip.format: Format string (defaults to ‘{ip} ({country_code})’) - * Available format strings - ip, country_name, country_code, city_name, coordinates - -Examples: - * bumblebee-status -m publicip -p publicip.format="{ip} ({country_code})" - * bumblebee-status -m publicip -p publicip.format="{ip} which is in {city_name}" - * bumblebee-status -m publicip -p publicip.format="Your packets are right here: {coordinates}" - -contributed by `tfwiii ` - many thanks! -""" - -import re -import threading -import netifaces -import time import core.module import core.widget -import core.input import core.decorators -import util.format import util.location -import logging - -log = logging.getLogger(__name__) class Module(core.module.Module): @core.decorators.every(minutes=60) def __init__(self, config, theme): - super().__init__(config, theme, core.widget.Widget(self.publicip)) + super().__init__(config, theme, core.widget.Widget(self.public_ip)) - self.__previous_default_route = None - self.__current_default_route = None - self.background = True + self.__ip = "" - # Immediate update (override default) when left click on widget - core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.__click_update) - - # By default show: (<2 letter country code>) - self._format = self.parameter("format", "{ip} ({country_code})") - - self.__monitor = threading.Thread(target=self.monitor, args=()) - self.__monitor.start() - - def monitor(self): - __previous_ips = set() - __current_ips = set() - # Initially set to True to force an info update on first pass - __information_changed = True - - self.update() - - while threading.main_thread().is_alive(): - __current_ips.clear() - # Look for any changes to IP addresses - try: - for interface in netifaces.interfaces(): - 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() - - # 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 - - # Update if change is flagged - if __information_changed: - __information_changed = False - self.update() - - # Throttle the calls to netifaces - time.sleep(1) - - def publicip(self, widget): - 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", "-"), - ) - - def __click_update(self, event): - util.location.reset() + def public_ip(self, widget): + return self.__ip def update(self): - widget = self.widget() - 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 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 = str(__lat) + "°S" - else: - __coords = str(__lat) + "°N" - __coords += "," - if __lon < 0: - __coords += str(__lon) + "°W" - else: - __coords += str(__lon) + "°E" - else: - __coords = "Unknown" - - # Set widget values - widget.set("public_ip", __info["public_ip"]) - widget.set("country_name", __info["country"]) - widget.set("country_code", __info["country_code"]) - widget.set("city_name", __info["city_name"]) - widget.set("coordinates", __coords) - - # Update widget values - core.event.trigger("update", [widget.module.id], redraw_only=True) - except Exception as ex: - widget.set("public_ip", None) - logging.error(str(ex)) - - def state(self, widget): - return widget.get("state", None) + self.__ip = util.location.public_ip() + except Exception: + self.__ip = "n/a" # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/contrib/rofication.py b/bumblebee_status/modules/contrib/rofication.py index 1fd5355..79e6afd 100644 --- a/bumblebee_status/modules/contrib/rofication.py +++ b/bumblebee_status/modules/contrib/rofication.py @@ -5,10 +5,6 @@ module will have normal highlighting if there are zero notifications, "warning" highlighting if there are nonzero notifications, "critical" highlighting if there are any critical notifications - - Parameters: - * rofication.regolith: Switch to regolith fork of rofication, see . - """ import core.module @@ -24,7 +20,6 @@ class Module(core.module.Module): super().__init__(config, theme, core.widget.Widget(self.full_text)) self.__critical = False self.__numnotifications = 0 - self.__regolith = self.parameter("regolith", False) def full_text(self, widgets): @@ -32,16 +27,10 @@ class Module(core.module.Module): client.connect("/tmp/rofi_notification_daemon") # below code will fetch two numbers in a list, e.g. ['22', '1'] # first is total number of notifications, second is number of critical notifications - if self.__regolith: - client.sendall(bytes("num\n", "utf-8")) - else: - client.sendall(bytes("num", "utf-8")) + client.sendall(bytes("num", "utf-8")) val = client.recv(512) val = val.decode("utf-8") - if self.__regolith: - l = val.split(',',2) - else: - l = val.split('\n',2) + l = val.split('\n',2) self.__numnotifications = int(l[0]) self.__critical = bool(int(l[1])) return self.__numnotifications diff --git a/bumblebee_status/modules/contrib/rotation.py b/bumblebee_status/modules/contrib/rotation.py index d05c463..13f656e 100644 --- a/bumblebee_status/modules/contrib/rotation.py +++ b/bumblebee_status/modules/contrib/rotation.py @@ -31,13 +31,14 @@ class Module(core.module.Module): orientation = curr_orient break - widget = self.widget(name=display) + widget = self.widget(display) if not widget: widget = self.add_widget(full_text=display, name=display) core.input.register( widget, button=core.input.LEFT_MOUSE, cmd=self.__toggle ) widget.set("orientation", orientation) + widgets.append(widget) def state(self, widget): return widget.get("orientation", "normal") diff --git a/bumblebee_status/modules/contrib/rss.py b/bumblebee_status/modules/contrib/rss.py index 7824e2e..7b8c032 100644 --- a/bumblebee_status/modules/contrib/rss.py +++ b/bumblebee_status/modules/contrib/rss.py @@ -55,7 +55,7 @@ class Module(core.module.Module): self._state = [] - self._newspaper_file = tempfile.NamedTemporaryFile(mode="w", suffix=".html") + self._newspaper_filename = tempfile.mktemp(".html") self._last_refresh = 0 self._last_update = 0 @@ -308,11 +308,10 @@ class Module(core.module.Module): while newspaper_items: content += self._create_news_section(newspaper_items) - self._newspaper_file.write( + open(self._newspaper_filename, "w").write( HTML_TEMPLATE.replace("[[CONTENT]]", content) ) - self._newspaper_file.flush() - webbrowser.open("file://" + self._newspaper_file.name) + webbrowser.open("file://" + self._newspaper_filename) self._update_history("newspaper") self._save_history() diff --git a/bumblebee_status/modules/contrib/sensors.py b/bumblebee_status/modules/contrib/sensors.py index 7d42c83..68b792a 100644 --- a/bumblebee_status/modules/contrib/sensors.py +++ b/bumblebee_status/modules/contrib/sensors.py @@ -4,7 +4,6 @@ """Displays sensor temperature Parameters: - * sensors.use_sensors: whether to use the sensors command * sensors.path: path to temperature file (default /sys/class/thermal/thermal_zone0/temp). * sensors.json: if set to 'true', interpret sensors.path as JSON 'path' in the output of 'sensors -j' (i.e. //.../), for example, path could @@ -19,7 +18,6 @@ contributed by `mijoharas `_ - many thanks! """ import re -import os import json import logging @@ -48,25 +46,22 @@ class Module(core.module.Module): self._json = util.format.asbool(self.parameter("json", False)) self._freq = util.format.asbool(self.parameter("show_freq", True)) core.input.register(self, button=core.input.LEFT_MOUSE, cmd="xsensors") - self.use_sensors = self.determine_method() + self.determine_method() def determine_method(self): - if util.format.asbool(self.parameter("use_sensors")) == True: - return True - if util.format.asbool(self.parameter("use_sensors")) == False: - return False if self.parameter("path") != None and self._json == False: - return False - # try to use output of sensors -u - try: - _ = util.cli.execute("sensors -u") - log.debug("Sensors command available") - return True - except FileNotFoundError as e: - log.info( - "Sensors command not available, using /sys/class/thermal/thermal_zone*/" - ) - return False + self.use_sensors = False # use thermal zone + else: + # try to use output of sensors -u + try: + output = util.cli.execute("sensors -u") + self.use_sensors = True + log.debug("Sensors command available") + except FileNotFoundError as e: + log.info( + "Sensors command not available, using /sys/class/thermal/thermal_zone*/" + ) + self.use_sensors = False def _get_temp_from_sensors(self): if self._json == True: @@ -97,31 +92,22 @@ class Module(core.module.Module): def get_temp(self): if self.use_sensors: + temperature = self._get_temp_from_sensors() log.debug("Retrieve temperature from sensors -u") - return self._get_temp_from_sensors() - try: - path = None - # use path provided by the user - if self.parameter("path") is not None: - path = self.parameter("path") - # find the thermal zone that provides cpu temperature - else: - for zone in os.listdir("/sys/class/thermal"): - if not zone.startswith("thermal_zone"): - continue - if open(f"/sys/class/thermal/{zone}/type").read().strip() != "x86_pkg_temp": - continue - path = f"/sys/class/thermal/{zone}/temp" - # use zone 0 as fallback - if path is None: - log.info("Can not determine temperature path, using thermal_zone0") - path = "/sys/class/thermal/thermal_zone0/temp" - log.debug(f"retrieving temperature from {path}") - # the values are t°C * 1000, so divide by 1000 - return str(int(open(path).read()) / 1000) - except IOError: - log.info("Can not determine temperature, please install lm-sensors") - return "unknown" + else: + try: + temperature = open( + self.parameter("path", "/sys/class/thermal/thermal_zone0/temp") + ).read()[:2] + log.debug("retrieved temperature from /sys/class/") + # TODO: Iterate through all thermal zones to determine the correct one and use its value + # https://unix.stackexchange.com/questions/304845/discrepancy-between-number-of-cores-and-thermal-zones-in-sys-class-thermal + + except IOError: + temperature = "unknown" + log.info("Can not determine temperature, please install lm-sensors") + + return temperature def get_mhz(self): mhz = None diff --git a/bumblebee_status/modules/contrib/shell.py b/bumblebee_status/modules/contrib/shell.py index 4aabb5a..566de42 100644 --- a/bumblebee_status/modules/contrib/shell.py +++ b/bumblebee_status/modules/contrib/shell.py @@ -41,7 +41,6 @@ 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: @@ -53,7 +52,6 @@ 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, _): diff --git a/bumblebee_status/modules/contrib/shortcut.py b/bumblebee_status/modules/contrib/shortcut.py index ff5b47f..129bfdc 100644 --- a/bumblebee_status/modules/contrib/shortcut.py +++ b/bumblebee_status/modules/contrib/shortcut.py @@ -9,7 +9,7 @@ a delimiter (; semicolon by default). For example in order to create two shortcuts labeled A and B with commands cmdA and cmdB you could do: - ./bumblebee-status -m shortcut -p shortcut.cmd='firefox https://www.google.com;google-chrome https://google.com' shortcut.label='Google (Firefox);Google (Chrome)' + ./bumblebee-status -m shortcut -p shortcut.cmd='ls;ps' shortcut.label='A;B' Parameters: * shortcut.cmds : List of commands to execute diff --git a/bumblebee_status/modules/contrib/smartstatus.py b/bumblebee_status/modules/contrib/smartstatus.py index ddd187c..81060da 100644 --- a/bumblebee_status/modules/contrib/smartstatus.py +++ b/bumblebee_status/modules/contrib/smartstatus.py @@ -10,7 +10,7 @@ Requires the following executables: * smartctl Parameters: - * smartstatus.display: how to display (defaults to 'combined', other choices: 'combined_singles', 'separate' or 'singles') + * smartstatus.display: how to display (defaults to 'combined', other choices: 'combined_singles', 'seperate' or 'singles') * smartstatus.drives: in the case of singles which drives to display, separated comma list value, multiple accepted (defaults to 'sda', example:'sda,sdc') * smartstatus.show_names: boolean in the form of "True" or "False" to show the name of the drives in the form of sda, sbd, combined or none at all. """ diff --git a/bumblebee_status/modules/contrib/solaar.py b/bumblebee_status/modules/contrib/solaar.py deleted file mode 100644 index b7396f3..0000000 --- a/bumblebee_status/modules/contrib/solaar.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Shows status and load percentage of logitech's unifying device - -Requires the following executable: - * solaar (from community) - -contributed by `cambid `_ - many thanks! -""" - -import logging - -import core.module -import core.widget -import core.decorators - -import util.cli - - -class Module(core.module.Module): - @core.decorators.every(seconds=30) - def __init__(self, config, theme): - super().__init__(config, theme, core.widget.Widget(self.utilization)) - self.__battery = self.parameter("device", "") - self.background = True - self.__battery_status = "" - self.__error = False - if self.__battery != "": - self.__cmd = f"solaar show '{self.__battery}'" - else: - self.__cmd = "solaar show" - - @property - def __format(self): - return self.parameter("format", "{}") - - def utilization(self, widget): - return self.__format.format(self.__battery_status) - - def update(self): - self.__error = False - code, result = util.cli.execute( - self.__cmd, ignore_errors=True, return_exitcode=True - ) - - if code == 0: - for line in result.split('\n'): - if line.count('Battery') > 0: - self.__battery_status = line.split(':')[1].strip() - else: - self.__error = True - logging.error(f"solaar exited with {code}: {result}") - - def state(self, widget): - if self.__error: - return "warning" - return "okay" - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/contrib/spaceapi.py b/bumblebee_status/modules/contrib/spaceapi.py index e36d7bd..a6c2772 100644 --- a/bumblebee_status/modules/contrib/spaceapi.py +++ b/bumblebee_status/modules/contrib/spaceapi.py @@ -16,7 +16,7 @@ Parameters: Format Strings: * Format strings are indicated by double %% - * They represent a leaf in the JSON tree, layers separated by '.' + * They represent a leaf in the JSON tree, layers seperated by '.' * Boolean values can be overwritten by appending '%true%false' in the format string * Example: to reference 'open' in '{'state':{'open': true}}' diff --git a/bumblebee_status/modules/contrib/spotify.py b/bumblebee_status/modules/contrib/spotify.py index d597b47..7544b48 100644 --- a/bumblebee_status/modules/contrib/spotify.py +++ b/bumblebee_status/modules/contrib/spotify.py @@ -110,8 +110,7 @@ class Module(core.module.Module): def hidden(self): return self.string_song == "" - @core.decorators.scrollable - def __get_song(self, widget): + def __get_song(self): bus = self.__bus if self.__bus_name == "spotifyd": spotify = bus.get_object( @@ -129,10 +128,11 @@ class Module(core.module.Module): artist=",".join(props.get("xesam:artist")), trackNumber=str(props.get("xesam:trackNumber")), ) - return self.__song def update(self): try: + self.__get_song() + if self.__bus_name == "spotifyd": bus = self.__bus.get_object( "org.mpris.MediaPlayer2.spotifyd", "/org/mpris/MediaPlayer2" @@ -156,9 +156,10 @@ class Module(core.module.Module): widget.set("state", "paused") elif widget.name == "spotify.song": widget.set("state", "song") - widget.full_text(self.__get_song(widget)) + widget.full_text(self.__song) except Exception as e: + logging.exception(e) self.__song = "" @property diff --git a/bumblebee_status/modules/contrib/stock.py b/bumblebee_status/modules/contrib/stock.py index 6b35bf7..224a5fb 100644 --- a/bumblebee_status/modules/contrib/stock.py +++ b/bumblebee_status/modules/contrib/stock.py @@ -5,9 +5,7 @@ Parameters: * stock.symbols : Comma-separated list of symbols to fetch - * 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" + * stock.change : Should we fetch change in stock value (defaults to True) contributed by `msoulier `_ - many thanks! @@ -24,12 +22,6 @@ 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) @@ -37,41 +29,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.__values = [] - + self.__value = None def value(self, widget): - result = "" + results = [] + if not self.__value: + return "n/a" + data = json.loads(self.__value) - 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 + 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) def fetch(self): - results = [] if self.__symbols: - 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 [] + 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 else: logging.error("unable to retrieve stock exchange rate") - return [] - return results + return None def update(self): - self.__values = self.fetch() + self.__value = self.fetch() # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/contrib/sun.py b/bumblebee_status/modules/contrib/sun.py index 34a4b71..6b0734d 100644 --- a/bumblebee_status/modules/contrib/sun.py +++ b/bumblebee_status/modules/contrib/sun.py @@ -8,8 +8,8 @@ Requires the following python packages: * python-dateutil Parameters: - * sun.lat : Latitude of your location - * sun.lon : Longitude of your location + * cpu.lat : Latitude of your location + * cpu.lon : Longitude of your location (if none of those are set, location is determined automatically via location APIs) @@ -39,11 +39,7 @@ class Module(core.module.Module): self.__sun = None if not lat or not lon: - try: - lat, lon = util.location.coordinates() - except Exception: - pass - + lat, lon = util.location.coordinates() if lat and lon: self.__sun = Sun(float(lat), float(lon)) @@ -59,10 +55,6 @@ class Module(core.module.Module): return "n/a" def __calculate_times(self): - if not self.__sun: - self.__sunset = self.__sunrise = None - return - self.__isup = False order_matters = True diff --git a/bumblebee_status/modules/contrib/system.py b/bumblebee_status/modules/contrib/system.py index 8b136a1..e96ee3f 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') @@ -21,9 +21,6 @@ Parameters: * system.suspend: specify a command for suspending (defaults to 'i3exit suspend') * system.hibernate: specify a command for hibernating (defaults to 'i3exit hibernate') -Requirements: - tkinter (python3-tk package on debian based systems either you can install it as python package) - contributed by `bbernhard `_ - many thanks! """ @@ -72,12 +69,7 @@ class Module(core.module.Module): util.cli.execute(command) def popup(self, widget): - popupcmd = self.parameter("popupcmd", ""); - if (popupcmd != ""): - util.cli.execute(popupcmd) - return - - menu = util.popup.menu(self.__config) + menu = util.popup.menu() reboot_cmd = self.parameter("reboot", "reboot") shutdown_cmd = self.parameter("shutdown", "shutdown -h now") logout_cmd = self.parameter("logout", "i3exit logout") @@ -101,7 +93,7 @@ class Module(core.module.Module): menu.add_menuitem( "log out", callback=functools.partial( - self.__on_command, "Log out", "Log out?", logout_cmd + self.__on_command, "Log out", "Log out?", "i3exit logout" ), ) # don't ask for these diff --git a/bumblebee_status/modules/contrib/taskwarrior.py b/bumblebee_status/modules/contrib/taskwarrior.py index 81040e4..0d540b5 100644 --- a/bumblebee_status/modules/contrib/taskwarrior.py +++ b/bumblebee_status/modules/contrib/taskwarrior.py @@ -5,7 +5,6 @@ Requires the following library: Parameters: * taskwarrior.taskrc : path to the taskrc file (defaults to ~/.taskrc) - * taskwarrior.show_active: true/false(default) to show the active task ID and description when one is active, otherwise show the total number pending. contributed by `chdorb `_ - many thanks! @@ -23,45 +22,20 @@ class Module(core.module.Module): super().__init__(config, theme, core.widget.Widget(self.output)) self.__pending_tasks = "0" - self.__status = "stopped" def update(self): - """Return a string with the number of pending tasks from TaskWarrior - or the descripton of an active task. - - if show.active is set in the config, show the description of the - current active task, otherwise the number of pending tasks will be displayed. - """ + """Return a string with the number of pending tasks from TaskWarrior.""" try: taskrc = self.parameter("taskrc", "~/.taskrc") - show_active = self.parameter("show_active", False) w = TaskWarrior(config_filename=taskrc) - active_tasks = ( - w.filter_tasks({"start.any": "", "status": "pending"}) or None - ) - if show_active and active_tasks: - # this is using the first element of the list, if there happen - # to be other active tasks, they won't be displayed. - reporting_tasks = ( - f"{active_tasks[0]['id']} - {active_tasks[0]['description']}" - ) - self.__status = "active" - else: - reporting_tasks = len(w.filter_tasks({"status": "pending"})) - self.__status = "stopped" - self.__pending_tasks = reporting_tasks + pending_tasks = w.filter_tasks({"status": "pending"}) + self.__pending_tasks = str(len(pending_tasks)) except: self.__pending_tasks = "n/a" - self.__status = "stopped" - @core.decorators.scrollable def output(self, _): """Format the task counter to output in bumblebee.""" return "{}".format(self.__pending_tasks) - def state(self, widget): - """Return the set status to reflect state""" - return self.__status - # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/contrib/title.py b/bumblebee_status/modules/contrib/title.py index 1c98b42..33af47a 100644 --- a/bumblebee_status/modules/contrib/title.py +++ b/bumblebee_status/modules/contrib/title.py @@ -9,7 +9,6 @@ Parameters: * title.max : Maximum character length for title before truncating. Defaults to 64. * title.placeholder : Placeholder text to be placed if title was truncated. Defaults to '...'. * title.scroll : Boolean flag for scrolling title. Defaults to False - * title.short : Boolean flag for short title. Defaults to False contributed by `UltimatePancake `_ - many thanks! @@ -36,7 +35,6 @@ class Module(core.module.Module): # parsing of parameters self.__scroll = util.format.asbool(self.parameter("scroll", False)) - self.__short = util.format.asbool(self.parameter("short", False)) self.__max = int(self.parameter("max", 64)) self.__placeholder = self.parameter("placeholder", "...") self.__title = "" @@ -50,9 +48,8 @@ class Module(core.module.Module): # create a connection with i3ipc self.__i3 = i3ipc.Connection() - # event is called both on focus change and title change, and on workspace change + # event is called both on focus change and title 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() @@ -69,9 +66,7 @@ class Module(core.module.Module): def __pollTitle(self): """Updating current title.""" try: - focused = self.__i3.get_tree().find_focused().name - self.__full_title = focused.split( - "-")[-1].strip() if self.__short else focused + self.__full_title = self.__i3.get_tree().find_focused().name except: self.__full_title = no_title if self.__full_title is None: diff --git a/bumblebee_status/modules/contrib/todo.py b/bumblebee_status/modules/contrib/todo.py index 76e289e..878b63f 100644 --- a/bumblebee_status/modules/contrib/todo.py +++ b/bumblebee_status/modules/contrib/todo.py @@ -21,10 +21,9 @@ class Module(core.module.Module): super().__init__(config, theme, core.widget.Widget(self.output)) self.__doc = os.path.expanduser(self.parameter("file", "~/Documents/todo.txt")) - self.__editor = self.parameter("editor", "xdg-open") self.__todos = self.count_items() core.input.register( - self, button=core.input.LEFT_MOUSE, cmd="{} {}".format(self.__editor, self.__doc) + self, button=core.input.LEFT_MOUSE, cmd="xdg-open {}".format(self.__doc) ) def output(self, widget): @@ -40,12 +39,11 @@ class Module(core.module.Module): def count_items(self): try: - i = 0 + i = -1 with open(self.__doc) as f: - for l in f.readlines(): - if l.strip() != '': - i += 1 - return i + for i, l in enumerate(f): + pass + return i + 1 except Exception: return 0 diff --git a/bumblebee_status/modules/contrib/todoist.py b/bumblebee_status/modules/contrib/todoist.py deleted file mode 100644 index 5fae228..0000000 --- a/bumblebee_status/modules/contrib/todoist.py +++ /dev/null @@ -1,76 +0,0 @@ -# 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 - -import requests - -import core.decorators -import core.input -import core.module -import core.widget - -HOST_API = "https://api.todoist.com" -HOST_WEBSITE = "https://todoist.com/app/today" - -TASKS_URL = 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/bumblebee_status/modules/contrib/traffic.py b/bumblebee_status/modules/contrib/traffic.py index 4c91b61..c8b87db 100644 --- a/bumblebee_status/modules/contrib/traffic.py +++ b/bumblebee_status/modules/contrib/traffic.py @@ -8,7 +8,7 @@ Parameters: * traffic.showname: If set to False, hide network interface name (defaults to True) * traffic.format: Format string for download/upload speeds. Defaults to '{:.2f}' - * traffic.graphlen: Graph length in seconds. Positive even integer. Each + * traffic.graphlen: Graph lenth in seconds. Positive even integer. Each char shows 2 seconds. If set, enables up/down traffic graphs diff --git a/bumblebee_status/modules/contrib/usage.py b/bumblebee_status/modules/contrib/usage.py deleted file mode 100644 index d64e3e1..0000000 --- a/bumblebee_status/modules/contrib/usage.py +++ /dev/null @@ -1,78 +0,0 @@ -# pylint: disable=C0111,R0903 - -""" -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) -""" - -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.__usage = "" - - def output(self, _): - 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 = 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.__usage = ( - formatting.replace("HH", hours) - .replace("MM", minutes) - .replace("SS", seconds) - ) - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/contrib/vpn.py b/bumblebee_status/modules/contrib/vpn.py index 3381e4f..d9c8793 100644 --- a/bumblebee_status/modules/contrib/vpn.py +++ b/bumblebee_status/modules/contrib/vpn.py @@ -1,5 +1,4 @@ # pylint: disable=C0111,R0903 -# -*- coding: utf-8 -*- """ Displays the VPN profile that is currently in use. @@ -69,7 +68,7 @@ class Module(core.module.Module): def vpn_status(self, widget): if self.__connected_vpn_profile is None: - return "" + return "off" return self.__connected_vpn_profile def __on_vpndisconnect(self): @@ -94,7 +93,7 @@ class Module(core.module.Module): self.__connected_vpn_profile = None def popup(self, widget): - menu = util.popup.menu(self.__config) + menu = util.popup.menu() if self.__connected_vpn_profile is not None: menu.add_menuitem("Disconnect", callback=self.__on_vpndisconnect) diff --git a/bumblebee_status/modules/contrib/wakatime.py b/bumblebee_status/modules/contrib/wakatime.py deleted file mode 100644 index 1ac4461..0000000 --- a/bumblebee_status/modules/contrib/wakatime.py +++ /dev/null @@ -1,94 +0,0 @@ -# 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 - -import requests - -import core.decorators -import core.input -import core.module -import core.widget - -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): - @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/bumblebee_status/modules/contrib/watson.py b/bumblebee_status/modules/contrib/watson.py index a717342..d7b260b 100644 --- a/bumblebee_status/modules/contrib/watson.py +++ b/bumblebee_status/modules/contrib/watson.py @@ -5,10 +5,6 @@ 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! """ @@ -30,11 +26,11 @@ class Module(core.module.Module): super().__init__(config, theme, core.widget.Widget(self.text)) self.__tracking = False - self.__info = {} - self.__format = self.parameter("format", "{project} [{tags}]") + self.__project = "" 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: @@ -43,27 +39,20 @@ class Module(core.module.Module): def text(self, widget): if self.__tracking: - return self.__format.format(**self.__info) + return self.__project else: return "Paused" def update(self): output = util.cli.execute("watson status") - - 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: + if re.match(r"No project started", output): 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" diff --git a/bumblebee_status/modules/contrib/weather.py b/bumblebee_status/modules/contrib/weather.py index 4166995..72e0c27 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 https://api.openweathermap.org + * weather.apikey: API key from http://api.openweathermap.org contributed by `TheEdgeOfRage `_ - many thanks! @@ -116,7 +116,7 @@ class Module(core.module.Module): def update(self): try: - weather_url = "https://api.openweathermap.org/data/2.5/weather?appid={}".format( + weather_url = "http://api.openweathermap.org/data/2.5/weather?appid={}".format( self.__apikey ) weather_url = "{}&units={}".format(weather_url, self.__unit) diff --git a/bumblebee_status/modules/contrib/wlrotation.py b/bumblebee_status/modules/contrib/wlrotation.py deleted file mode 100644 index 242c50b..0000000 --- a/bumblebee_status/modules/contrib/wlrotation.py +++ /dev/null @@ -1,126 +0,0 @@ -# pylint: disable=C0111,R0903 -# -*- coding: utf-8 -*- - -"""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 -""" - -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.display = None - display_filter = self.parameter("display", None) - for display in json.loads(util.cli.execute("swaymsg -t get_outputs -r")): - name = display['name'] - 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): - state = [] - state.append("locked" if widget.get("locked", True) else "auto") - return state - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/core/cpu.py b/bumblebee_status/modules/core/cpu.py index 77ac20a..59c6e71 100644 --- a/bumblebee_status/modules/core/cpu.py +++ b/bumblebee_status/modules/core/cpu.py @@ -12,7 +12,6 @@ Parameters: * cpu.warning : Warning threshold in % of CPU usage (defaults to 70%) * cpu.critical: Critical threshold in % of CPU usage (defaults to 80%) * cpu.format : Format string (defaults to '{:.01f}%') - * cpu.percpu : If set to true, show each individual cpu (defaults to false) """ import psutil @@ -21,19 +20,12 @@ import core.module import core.widget import core.input -import util.format - class Module(core.module.Module): def __init__(self, config, theme): - super().__init__(config, theme, []) - self._percpu = util.format.asbool(self.parameter("percpu", False)) - - for idx, cpu_perc in enumerate(self.cpu_utilization()): - widget = self.add_widget(name="cpu#{}".format(idx), full_text=self.utilization) - widget.set("utilization", cpu_perc) - widget.set("theme.minwidth", self._format.format(100.0 - 10e-20)) - + super().__init__(config, theme, core.widget.Widget(self.utilization)) + self.widget().set("theme.minwidth", self._format.format(100.0 - 10e-20)) + self._utilization = psutil.cpu_percent(percpu=False) core.input.register( self, button=core.input.LEFT_MOUSE, cmd="gnome-system-monitor" ) @@ -42,19 +34,14 @@ class Module(core.module.Module): def _format(self): return self.parameter("format", "{:.01f}%") - def utilization(self, widget): - return self._format.format(widget.get("utilization", 0.0)) - - def cpu_utilization(self): - tmp = psutil.cpu_percent(percpu=self._percpu) - return tmp if self._percpu else [tmp] + def utilization(self, _): + return self._format.format(self._utilization) def update(self): - for idx, cpu_perc in enumerate(self.cpu_utilization()): - self.widgets()[idx].set("utilization", cpu_perc) + self._utilization = psutil.cpu_percent(percpu=False) - def state(self, widget): - return self.threshold_state(widget.get("utilization", 0.0), 70, 80) + def state(self, _): + return self.threshold_state(self._utilization, 70, 80) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/core/datetime.py b/bumblebee_status/modules/core/datetime.py index 0a15c70..f421e15 100644 --- a/bumblebee_status/modules/core/datetime.py +++ b/bumblebee_status/modules/core/datetime.py @@ -17,33 +17,26 @@ import core.input class Module(core.module.Module): - def __init__(self, config, theme, dtlibrary=None): + def __init__(self, config, theme): super().__init__(config, theme, core.widget.Widget(self.full_text)) core.input.register(self, button=core.input.LEFT_MOUSE, cmd="calendar") - self.dtlibrary = dtlibrary or datetime - - def set_locale(self): - l = self.default_locale() + self._fmt = self.parameter("format", self.default_format()) + l = locale.getdefaultlocale() if not l or l == (None, None): l = ("en_US", "UTF-8") lcl = self.parameter("locale", ".".join(l)) try: - locale.setlocale(locale.LC_ALL, lcl.split(".")) + locale.setlocale(locale.LC_TIME, lcl.split(".")) except Exception as e: - locale.setlocale(locale.LC_ALL, ("en_US", "UTF-8")) + locale.setlocale(locale.LC_TIME, ("en_US", "UTF-8")) def default_format(self): return "%x %X" - def default_locale(self): - return locale.getdefaultlocale() - def full_text(self, widget): - self.set_locale() enc = locale.getpreferredencoding() - fmt = self.parameter("format", self.default_format()) - retval = self.dtlibrary.datetime.now().strftime(fmt) + retval = datetime.datetime.now().strftime(self._fmt) if hasattr(retval, "decode"): return retval.decode(enc) return retval diff --git a/bumblebee_status/modules/core/disk.py b/bumblebee_status/modules/core/disk.py index 0488535..ff0d4f9 100644 --- a/bumblebee_status/modules/core/disk.py +++ b/bumblebee_status/modules/core/disk.py @@ -4,7 +4,7 @@ Parameters: * disk.warning: Warning threshold in % of disk space (defaults to 80%) - * disk.critical: Critical threshold in % of disk space (defaults to 90%) + * disk.critical: Critical threshold in % of disk space (defaults ot 90%) * disk.path: Path to calculate disk usage from (defaults to /) * disk.open: Which application / file manager to launch (default xdg-open) * disk.format: Format string, tags {path}, {used}, {left}, {size} and {percent} (defaults to '{path} {used}/{size} ({percent:05.02f}%)') diff --git a/bumblebee_status/modules/core/layout.py b/bumblebee_status/modules/core/layout.py deleted file mode 120000 index f2e8037..0000000 --- a/bumblebee_status/modules/core/layout.py +++ /dev/null @@ -1 +0,0 @@ -layout-xkb.py \ No newline at end of file diff --git a/bumblebee_status/modules/core/nic.py b/bumblebee_status/modules/core/nic.py index f153a94..7dde2f0 100644 --- a/bumblebee_status/modules/core/nic.py +++ b/bumblebee_status/modules/core/nic.py @@ -13,9 +13,7 @@ Parameters: * nic.exclude: Comma-separated list of interface prefixes (supporting regular expressions) to exclude (defaults to 'lo,virbr,docker,vboxnet,veth,br,.*:avahi') * nic.include: Comma-separated list of interfaces to include * nic.states: Comma-separated list of states to show (prefix with '^' to invert - i.e. ^down -> show all devices that are not in state down) - * nic.format: Format string (defaults to '{intf} {state} {ip} {ssid} {strength}') - * nic.strength_warning: Integer to set the threshold for warning state (defaults to 50) - * nic.strength_critical: Integer to set the threshold for critical state (defaults to 30) + * nic.format: Format string (defaults to '{intf} {state} {ip} {ssid}') """ import re @@ -25,13 +23,12 @@ import subprocess import core.module import core.decorators -import core.input import util.cli import util.format class Module(core.module.Module): - @core.decorators.every(seconds=5) + @core.decorators.every(seconds=10) def __init__(self, config, theme): widgets = [] super().__init__(config, theme, widgets) @@ -48,19 +45,9 @@ class Module(core.module.Module): self._states["exclude"].append(state[1:]) else: self._states["include"].append(state) - self._format = self.parameter("format", "{intf} {state} {ip} {ssid} {strength}") - - self._strength_threshold_critical = self.parameter("strength_critical", 30) - self._strength_threshold_warning = self.parameter("strength_warning", 50) - - # Limits for the accepted dBm values of wifi strength - self.__strength_dbm_lower_bound = -110 - self.__strength_dbm_upper_bound = -30 - + self._format = self.parameter("format", "{intf} {state} {ip} {ssid}") self.iw = shutil.which("iw") self._update_widgets(widgets) - 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): self._update_widgets(self.widgets()) @@ -77,21 +64,15 @@ class Module(core.module.Module): iftype = "wireless" if self._iswlan(intf) else "wired" iftype = "tunnel" if self._istunnel(intf) else iftype - # "strength" is none if interface type is not wlan - strength = widget.get("strength") - if self._iswlan(intf) and strength: - if strength < self._strength_threshold_critical: - states.append("critical") - elif strength < self._strength_threshold_warning: - states.append("warning") - states.append("{}-{}".format(iftype, widget.get("state"))) return states def _iswlan(self, intf): # wifi, wlan, wlp, seems to work for me - return intf.startswith("w") and not intf.startswith("wwan") + if intf.startswith("w"): + return True + return False def _istunnel(self, intf): return intf.startswith("tun") or intf.startswith("wg") @@ -135,9 +116,6 @@ class Module(core.module.Module): ): continue - strength_dbm = self.get_strength_dbm(intf) - strength_percent = self.convert_strength_dbm_percent(strength_dbm) - widget = self.widget(intf) if not widget: widget = self.add_widget(name=intf) @@ -148,14 +126,12 @@ class Module(core.module.Module): ip=", ".join(addr), intf=intf, state=state, - strength=str(strength_percent) + "%" if strength_percent else "", ssid=self.get_ssid(intf), ).split() ) ) widget.set("intf", intf) widget.set("state", state) - widget.set("strength", strength_percent) def get_ssid(self, intf): if not self._iswlan(intf) or self._istunnel(intf) or not self.iw: @@ -169,23 +145,5 @@ class Module(core.module.Module): return "" - def get_strength_dbm(self, intf): - if not self._iswlan(intf) or self._istunnel(intf) or not self.iw: - return None - - with open("/proc/net/wireless", "r") as file: - for line in file: - if intf in line: - # Remove trailing . by slicing it off ;) - strength_dbm = line.split()[3][:-1] - return util.format.asint(strength_dbm, - minimum=self.__strength_dbm_lower_bound, - maximum=self.__strength_dbm_upper_bound) - - return None - - def convert_strength_dbm_percent(self, signal): - return int(100 * ((signal + 100) / 70.0)) if signal else None - # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/core/pulseaudio.py b/bumblebee_status/modules/core/pulseaudio.py index 5f74248..120470f 100644 --- a/bumblebee_status/modules/core/pulseaudio.py +++ b/bumblebee_status/modules/core/pulseaudio.py @@ -2,8 +2,6 @@ """Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol. -!!! This module will eventually be deprecated (since it has bad performance and high CPU load) and be replaced with "pulsectl", which is a much better drop-in replacement !!! - Aliases: pasink (use this to control output instead of input), pasource Parameters: @@ -13,20 +11,6 @@ Parameters: Note: If the left and right channels have different volumes, the limit might not be reached exactly. * pulseaudio.showbars: 1 for showing volume bars, requires --markup=pango; 0 for not showing volume bars (default) - * pulseaudio.showdevicename: If set to 'true' (default is 'false'), the currently selected default device is shown. - Per default, the sink/source name returned by "pactl list sinks short" is used as display name. - - As this name is usually not particularly nice (e.g "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo"), - its possible to map the name to more a user friendly name. - - e.g to map "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" to the name "Headset", add the following - bumblebee-status config entry: pulseaudio.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=Headset - - Furthermore its possible to specify individual (unicode) icons for all sinks/sources. e.g in order to use the icon 🎧 for the - "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" sink, add the following bumblebee-status config entry: - pulseaudio.icon.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=🎧 - * Per default a left mouse button click mutes/unmutes the device. In case you want to open a dropdown menu to change the current - default device add the following config entry to your bumblebee-status config: pulseaudio.left-click=select_default_device_popup Requires the following executable: * pulseaudio @@ -36,7 +20,6 @@ Requires the following executable: import re import logging -import functools import core.module import core.widget @@ -46,14 +29,10 @@ 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, channel): - super().__init__(config, theme, core.widget.Widget(self.display)) + super().__init__(config, theme, core.widget.Widget(self.volume)) if util.format.asbool(self.parameter("autostart", False)): util.cli.execute("pulseaudio --start", ignore_errors=True) @@ -69,11 +48,7 @@ class Module(core.module.Module): self._mute = False self._failed = False self._channel = channel - self.__selected_default_device = None self._showbars = util.format.asbool(self.parameter("showbars", 0)) - self.__show_device_name = util.format.asbool( - self.parameter("showdevicename", False) - ) self._patterns = [ {"expr": "Name:", "callback": (lambda line: False)}, @@ -148,12 +123,9 @@ class Module(core.module.Module): m = re.search(r"mono:.*\s*\/\s*(\d+)%", line) if m: self._mono = m.group(1) - self._left = 0 - self._right = 0 else: m = re.search(r"left:.*\s*\/\s*(\d+)%.*right:.*\s*\/\s*(\d+)%", line) if m: - self._mono = 0 self._left = m.group(1) self._right = m.group(2) @@ -166,19 +138,19 @@ class Module(core.module.Module): logging.error("no pulseaudio device found") return "n/a" - def display(self, widget): + def volume(self, widget): if self._failed == True: return "n/a" - - vol = None if int(self._mono) > 0: vol = "{}%".format(self._mono) if self._showbars: vol = "{} {}".format(vol, util.graph.hbar(float(self._mono))) + return vol elif self._left == self._right: vol = "{}%".format(self._left) if self._showbars: vol = "{} {}".format(vol, util.graph.hbar(float(self._left))) + return vol else: vol = "{}%/{}%".format(self._left, self._right) if self._showbars: @@ -187,31 +159,19 @@ class Module(core.module.Module): util.graph.hbar(float(self._left)), util.graph.hbar(float(self._right)), ) - - output = vol - if self.__show_device_name: - friendly_name = self.parameter( - self.__selected_default_device, self.__selected_default_device - ) - icon = self.parameter("icon." + self.__selected_default_device, "") - output = ( - icon + " " + friendly_name + " | " + vol - if icon != "" - else friendly_name + " | " + vol - ) - return output + return vol def update(self): try: self._failed = False channel = "sinks" if self._channel == "sink" else "sources" - self.__selected_default_device = self._default_device() + device = self._default_device() result = util.cli.execute("pactl list {}".format(channel)) found = False for line in result.split("\n"): - if "Name: {}".format(self.__selected_default_device) in line: + if "Name: {}".format(device) in line: found = True continue if found is False: @@ -229,32 +189,11 @@ class Module(core.module.Module): else: raise e - def __on_sink_selected(self, sink_name): - util.cli.execute("pactl set-default-{} {}".format(self._channel, sink_name)) - - def select_default_device_popup(self, widget): - channel = "sinks" if self._channel == "sink" else "sources" - result = util.cli.execute("pactl list {} short".format(channel)) - - menu = util.popup.menu(self.__config) - lines = result.splitlines() - for line in lines: - info = line.split("\t") - try: - friendly_name = self.parameter(info[1], info[1]) - menu.add_menuitem( - friendly_name, - callback=functools.partial(self.__on_sink_selected, info[1]), - ) - except: - logging.exception("Couldn't parse {}".format(channel)) - pass - - menu.show(widget) - def state(self, widget): if self._mute: return ["warning", "muted"] + if int(self._left) > int(100): + return ["critical", "unmuted"] return ["unmuted"] diff --git a/bumblebee_status/modules/core/pulsectl.py b/bumblebee_status/modules/core/pulsectl.py deleted file mode 100644 index 63f9e36..0000000 --- a/bumblebee_status/modules/core/pulsectl.py +++ /dev/null @@ -1,207 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol. - -**Please prefer this module over the "pulseaudio" module, which will eventually be deprecated - -Aliases: pulseout (for outputs, such as headsets, speakers), pulsein (for microphones) - -NOTE: Do **not** use this module directly, but rather use either pulseout or pulsein! -NOTE2: For the parameter names below, please also use pulseout or pulsein, instead of pulsectl - -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. - Per default, the sink/source name returned by "pactl list sinks short" is used as display name. - - As this name is usually not particularly nice (e.g "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo"), - its possible to map the name to more a user friendly name. - - e.g to map "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" to the name "Headset", add the following - bumblebee-status config entry: pulsectl.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=Headset - - Furthermore its possible to specify individual (unicode) icons for all sinks/sources. e.g in order to use the icon 🎧 for the - "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" sink, add the following bumblebee-status config entry: - pulsectl.icon.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=🎧 - * Per default a left mouse button click mutes/unmutes the device. In case you want to open a dropdown menu to change the current - default device add the following config entry to your bumblebee-status config: pulsectl.left-click=select_default_device_popup - -Requires the following Python module: - * pulsectl -""" - -import pulsectl -import logging -import functools - -import core.module -import core.widget -import core.input -import core.event - -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)) - self.background = True - - self.__type = type - self.__volume = 0 - self.__devicename = "n/a" - self.__muted = False - self.__showbars = util.format.asbool(self.parameter("showbars", False)) - self.__show_device_name = util.format.asbool( - self.parameter("showdevicename", False) - ) - - self.__change = util.format.asint( - 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 = [ - { - "type": "mute", - "action": self.toggle_mute, - "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"]) - - if util.format.asbool(self.parameter("autostart", False)): - util.cli.execute("pulseaudio --start", ignore_errors=True) - - self.process(None) - - def display(self, _): - res = f"{int(self.__volume*100)}%" - if self.__showbars: - 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, "") - res = ( - icon + " " + friendly_name + " | " + res - if icon != "" - else friendly_name + " | " + res - ) - return res - - 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: - vol.value_flat = self.__limit/100 - pulse.volume_set(dev, vol) - - def increase_volume(self, _): - self.change_volume(self.__change/100.0) - - def decrease_volume(self, _): - self.change_volume(-self.__change/100.0) - - def get_device(self, pulse): - devs = pulse.sink_list() if self.__type == "sink" else pulse.source_list() - default = pulse.server_info().default_sink_name if self.__type == "sink" else pulse.server_info().default_source_name - - for dev in devs: - if dev.name == default: - return dev - 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) - 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") - - def update(self): - with pulsectl.Pulse(self.id) as pulse: - pulse.event_mask_set(self.__type) - pulse.event_callback_set(self.process) - pulse.event_listen() - - def select_default_device_popup(self, widget): - with pulsectl.Pulse(self.id) as pulse: - 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( - 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"] - 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/bumblebee_status/modules/core/pulsein.py b/bumblebee_status/modules/core/pulsein.py deleted file mode 100644 index 0034dd0..0000000 --- a/bumblebee_status/modules/core/pulsein.py +++ /dev/null @@ -1,9 +0,0 @@ -from .pulsectl import Module - - -class Module(Module): - def __init__(self, config, theme): - super().__init__(config, theme, "source") - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/core/pulseout.py b/bumblebee_status/modules/core/pulseout.py deleted file mode 100644 index 31fecec..0000000 --- a/bumblebee_status/modules/core/pulseout.py +++ /dev/null @@ -1,9 +0,0 @@ -from .pulsectl import Module - - -class Module(Module): - def __init__(self, config, theme): - super().__init__(config, theme, "sink") - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/core/redshift.py b/bumblebee_status/modules/core/redshift.py index c5835cd..c6735b1 100644 --- a/bumblebee_status/modules/core/redshift.py +++ b/bumblebee_status/modules/core/redshift.py @@ -12,7 +12,6 @@ Parameters: * redshift.lat : latitude if location is set to 'manual' * redshift.lon : longitude if location is set to 'manual' * redshift.show_transition: information about the transitions (x% day) defaults to True - * redshift.adjust: set this to 'true' (defaults to false) to let bumblebee-status adjust color temperature, instead of just showing the current settings """ import re @@ -39,13 +38,7 @@ def get_redshift_value(module): if location == "manual" and (lat is None or lon is None): location = "geoclue2" - command = ["redshift"] - - if util.format.asbool(module.parameter("adjust", "false")) == True: - command.extend(["-o", "-v"]) - else: - command.append("-p") - + command = ["redshift", "-p"] if location == "manual": command.extend(["-l", "{}:{}".format(lat, lon)]) if location == "geoclue2": @@ -61,7 +54,7 @@ def get_redshift_value(module): for line in res.split("\n"): line = line.lower() if "temperature" in line: - widget.set("temp", line.split(" ")[2].upper()) + widget.set("temp", line.split(" ")[2]) if "period" in line: state = line.split(" ")[1] if "day" in state: diff --git a/bumblebee_status/modules/core/scroll.py b/bumblebee_status/modules/core/scroll.py deleted file mode 100644 index b897c5b..0000000 --- a/bumblebee_status/modules/core/scroll.py +++ /dev/null @@ -1,53 +0,0 @@ -# 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 -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"] - elif self.__offset + self.__width >= self.__widgetcount: - return ["warning"] - return [] - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/core/sensors2.py b/bumblebee_status/modules/core/sensors2.py index 68b58b6..5f101fa 100644 --- a/bumblebee_status/modules/core/sensors2.py +++ b/bumblebee_status/modules/core/sensors2.py @@ -11,7 +11,7 @@ Parameters: * sensors2.showother: Enable or display 'other' sensor readings (default: false) * sensors2.showname: Enable or disable show of sensor name (default: false) * sensors2.chip_include: Comma-separated list of chip to include (defaults to '' will include all by default, example: 'coretemp,bat') - * sensors2.chip_exclude:Comma separated list of chip to exclude (defaults to '' will exclude none by default) + * sensors2.chip_exclude:Comma separated list of chip to exclude (defaults to '' will exlude none by default) * sensors2.field_include: Comma separated list of chip to include (defaults to '' will include all by default, example: 'temp,fan') * sensors2.field_exclude: Comma separated list of chip to exclude (defaults to '' will exclude none by default) * sensors2.chip_field_exclude: Comma separated list of chip field to exclude (defaults to '' will exclude none by default, example: 'coretemp-isa-0000.temp1,coretemp-isa-0000.fan1') diff --git a/bumblebee_status/modules/core/spacer.py b/bumblebee_status/modules/core/spacer.py index e5a2d5e..7e4453a 100644 --- a/bumblebee_status/modules/core/spacer.py +++ b/bumblebee_status/modules/core/spacer.py @@ -9,7 +9,7 @@ Parameters: import core.module import core.widget import core.decorators -import core.input + class Module(core.module.Module): @core.decorators.every(minutes=60) @@ -20,8 +20,5 @@ class Module(core.module.Module): def text(self, _): return self.__text - def update_text(self, event): - self.__text = core.input.button_name(event["button"]) - # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/core/vault.py b/bumblebee_status/modules/core/vault.py index ba316bc..7f3fb75 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(self.__config, parent, leave=False) + submenu = util.popup.menu(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(self.__config, leave=False) + menu = util.popup.menu(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/cli.py b/bumblebee_status/util/cli.py index 4ef25d9..d14dc6a 100644 --- a/bumblebee_status/util/cli.py +++ b/bumblebee_status/util/cli.py @@ -52,19 +52,7 @@ def execute( raise RuntimeError("{} not found".format(cmd)) if wait: - 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() + out, _ = proc.communicate() if proc.returncode != 0: err = "{} exited with code {}".format(cmd, proc.returncode) logging.warning(err) diff --git a/bumblebee_status/util/location.py b/bumblebee_status/util/location.py index 00ac5fd..12242ea 100644 --- a/bumblebee_status/util/location.py +++ b/bumblebee_status/util/location.py @@ -3,10 +3,8 @@ service and caches it for 12h (retries are done every 30m in case of problems) Right now, it uses (in order of preference): - - http://free.ipwhois.io/ - 10k free requests/month - - http://ipapi.co/ - 30k free requests/month - - http://ip-api.com/ - ~2m free requests/month - + - http://free.ipwhois.io/ + - http://ipapi.co/ """ @@ -18,36 +16,21 @@ __document = None __data = {} __next = 0 __sources = [ - { - "url": "http://free.ipwhois.io/json/", - "mapping": { - "latitude": "latitude", - "longitude": "longitude", - "country": "country_name", - "country_code": "country_code", - "city": "city_name", - "ip": "public_ip", - }, - }, - { - "url": "http://ip-api.com/json", - "mapping": { - "lat": "latitude", - "lon": "longitude", - "country": "country_name", - "countryCode": "country_code", - "city": "city_name", - "query": "public_ip", - }, - }, { "url": "http://ipapi.co/json", "mapping": { "latitude": "latitude", "longitude": "longitude", - "country_name": "country_name", - "country_code": "country_code", - "city": "city_name", + "country_name": "country", + "ip": "public_ip", + }, + }, + { + "url": "http://free.ipwhois.io/json/", + "mapping": { + "latitude": "latitude", + "longitude": "longitude", + "country": "country", "ip": "public_ip", }, } @@ -76,22 +59,17 @@ def __load(): __next = time.time() + 60 * 30 # error - try again every 30m -def __get(name): +def __get(name, default=None): global __data if not __data or __expired(): __load() - if name in __data: - return __data[name] - else: - return None + return __data.get(name, default) def reset(): - """Resets the location library, ensuring that a new query will be started""" + """Resets the location library, ensuring that a new query will be started + """ global __next - global __data - - __data = None __next = 0 @@ -110,25 +88,7 @@ def country(): :return: country name :rtype: string """ - return __get("country_name") - - -def country_code(): - """Returns the current country code - - :return: country code - :rtype: string - """ - return __get("country_code") - - -def city_name(): - """Returns the current city name - - :return: city name - :rtype: string - """ - return __get("city_name") + return __get("country") def public_ip(): @@ -140,20 +100,4 @@ def public_ip(): return __get("public_ip") -def location_info(): - """Returns the current location information - - :return: public IP, country name, country code, city name & coordinates - :rtype: dictionary - """ - return { - "public_ip": __get("public_ip"), - "country": __get("country_name"), - "country_code": __get("country_code"), - "city_name": __get("city_name"), - "latitude": __get("latitude"), - "longitude": __get("longitude"), - } - - # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/util/popup.py b/bumblebee_status/util/popup.py index cd083f7..bbabe66 100644 --- a/bumblebee_status/util/popup.py +++ b/bumblebee_status/util/popup.py @@ -3,7 +3,6 @@ import logging import tkinter as tk -import tkinter.font as tkFont import functools @@ -11,12 +10,11 @@ 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, config, parent=None, leave=True): + def __init__(self, parent=None, leave=True): self.running = True self.parent = parent @@ -25,7 +23,6 @@ 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) @@ -52,7 +49,6 @@ class menu(object): return self._menu def __on_focus_out(self, event=None): - self.running = False self._root.destroy() def __on_click(self, callback): @@ -71,7 +67,7 @@ class menu(object): """ def add_cascade(self, menuitem, submenu): - self._menu.add_cascade(label=menuitem, menu=submenu.menu(), font=self._font_size) + self._menu.add_cascade(label=menuitem, menu=submenu.menu()) """Adds an item to the current menu @@ -81,7 +77,7 @@ class menu(object): def add_menuitem(self, menuitem, callback): self._menu.add_command( - label=menuitem, command=functools.partial(self.__on_click, callback), font=self._font_size, + label=menuitem, command=functools.partial(self.__on_click, callback) ) """Adds a separator to the menu in the current location""" diff --git a/create-pkgbuild.py b/create-pkgbuild.py deleted file mode 100644 index 0ab79e3..0000000 --- a/create-pkgbuild.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -import sys -import json -import hashlib -import requests - -rv = requests.request( - "GET", - "https://api.github.com/repos/tobi-wan-kenobi/bumblebee-status/releases/latest", -) - -if rv.status_code != 200: - sys.exit(1) - -release = json.loads(rv.text) - -tar = requests.get(f"https://github.com/tobi-wan-kenobi/bumblebee-status/archive/{release['name']}.tar.gz") -checksum = hashlib.sha512(tar.content).hexdigest() - -template = "" -with open("./PKGBUILD.template") as f: - template = f.read() - -template = template.replace("", release["name"].lstrip("v")) -template = template.replace("", checksum) - -print(template) diff --git a/docs/FAQ.rst b/docs/FAQ.rst index 0aade9b..eeaff82 100644 --- a/docs/FAQ.rst +++ b/docs/FAQ.rst @@ -29,9 +29,9 @@ didn’t have background color support for the status bar. Some of the icons don’t render correctly ---------------------------------------- -Please check that you have `Font Awesome`_ installed (version 4). +Please check that you have |Font Awesome| installed (version 4). -.. note:: The `Font Awesome`_ is required for all themes that +.. note:: The |Font Awesome| is required for all themes that contain icons (because that is the font that includes these icons). Please refer to your distribution’s package management on how to install them, or get them from their website directly. Also, please note that @@ -52,15 +52,4 @@ Please check that you have `Font Awesome`_ installed (version 4). # Other # see https://github.com/gabrielelana/awesome-terminal-fonts -You might also need to add it to the `font` directive in your i3 configuration, for example: - -.. code-block:: - - bar { - font pango:FontAwesome, Fira mono 10 - status_command bumblebee-status -m title pasink pasource cpu memory battery datetime --iconset awesome-fonts - } - -If you are unsure about how the font is named, you can use the ``pango-list`` command line tool to look at the fonts installed on your computer. Also note how you can specify multiple fonts, separated by commas, in the above example. - -.. _Font Awesome: https://fontawesome.com/ +.. |Font Awesome| image:: https://fontawesome.com/ diff --git a/docs/conf.py b/docs/conf.py index 722e6f6..2097441 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,8 +49,6 @@ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # a list of builtin themes. # html_theme = "sphinx_rtd_theme" -html_logo = "logo.png" -html_favicon = "favicon.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/development/module.rst b/docs/development/module.rst index 32a653a..992ef05 100644 --- a/docs/development/module.rst +++ b/docs/development/module.rst @@ -250,5 +250,5 @@ module using ``self.set()`` or via the CLI using the ``--parameter`` flag: - ``scrolling.width``: Integer, defaults to 30, determines the minimum width of the widgets, if ``makewide`` is specified - ``scrolling.makewide``: Boolean, defaults to true, determines whether the widgets should be expanded to their minwidth -- ``scrolling.bounce``: Boolean, defaults to true, determines whether the content should change directions when a scroll is completed, or just marquee through + ``scrolling.bounce``: Boolean, defaults to true, determines whether the content should change directions when a scroll is completed, or just marquee through diff --git a/docs/favicon.ico b/docs/favicon.ico deleted file mode 100644 index 2da8b14..0000000 Binary files a/docs/favicon.ico and /dev/null differ diff --git a/docs/features.rst b/docs/features.rst index 603c7fe..f167033 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -160,7 +160,6 @@ Configuration files have the following format: [core] modules = - autohide = theme = [module-parameters] diff --git a/docs/index.rst b/docs/index.rst index 1ba4303..f405d91 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,8 +9,6 @@ Welcome to bumblebee-status's documentation! bumblebee-status is a modular, theme-able status line generator for the `i3 window manager `__. -Logo courtesy of [kellya](https://github.com/kellya) - thank you! - Focus is on: - ease of use, sane defaults (no mandatory configuration file) diff --git a/docs/introduction.rst b/docs/introduction.rst index 891b14c..3831d4b 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -44,14 +44,6 @@ 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: diff --git a/docs/logo.png b/docs/logo.png deleted file mode 100644 index c0d96c9..0000000 Binary files a/docs/logo.png and /dev/null differ diff --git a/docs/modules.rst b/docs/modules.rst index 0b3ff9d..87a80ad 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -22,7 +22,6 @@ Parameters: * cpu.warning : Warning threshold in % of CPU usage (defaults to 70%) * cpu.critical: Critical threshold in % of CPU usage (defaults to 80%) * cpu.format : Format string (defaults to '{:.01f}%') - * cpu.percpu : If set to true, show each individual cpu (defaults to false) .. image:: ../screenshots/cpu.png @@ -60,7 +59,7 @@ Shows free diskspace, total diskspace and the percentage of free disk space. Parameters: * disk.warning: Warning threshold in % of disk space (defaults to 80%) - * disk.critical: Critical threshold in % of disk space (defaults to 90%) + * disk.critical: Critical threshold in % of disk space (defaults ot 90%) * disk.path: Path to calculate disk usage from (defaults to /) * disk.open: Which application / file manager to launch (default xdg-open) * disk.format: Format string, tags {path}, {used}, {left}, {size} and {percent} (defaults to '{path} {used}/{size} ({percent:05.02f}%)') @@ -85,30 +84,6 @@ Requires: .. image:: ../screenshots/git.png -keys -~~~~ - -Shows when a key is pressed - -Parameters: - * keys.keys: Comma-separated list of keys to monitor (defaults to "") - -layout -~~~~~~ - -Displays the current keyboard layout using libX11 - -Requires the following library: - * libX11.so.6 -and python module: - * xkbgroup - -Parameters: - * layout-xkb.showname: Boolean that indicate whether the full name should be displayed. Defaults to false (only the symbol will be displayed) - * layout-xkb.show_variant: Boolean that indecates whether the variant name should be displayed. Defaults to true. - -.. image:: ../screenshots/layout.png - layout-xkb ~~~~~~~~~~ @@ -187,9 +162,7 @@ Parameters: * nic.exclude: Comma-separated list of interface prefixes (supporting regular expressions) to exclude (defaults to 'lo,virbr,docker,vboxnet,veth,br,.*:avahi') * nic.include: Comma-separated list of interfaces to include * nic.states: Comma-separated list of states to show (prefix with '^' to invert - i.e. ^down -> show all devices that are not in state down) - * nic.format: Format string (defaults to '{intf} {state} {ip} {ssid} {strength}') - * nic.strength_warning: Integer to set the threshold for warning state (defaults to 50) - * nic.strength_critical: Integer to set the threshold for critical state (defaults to 30) + * nic.format: Format string (defaults to '{intf} {state} {ip} {ssid}') .. image:: ../screenshots/nic.png @@ -215,8 +188,6 @@ pulseaudio Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol. -!!! This module will eventually be deprecated (since it has bad performance and high CPU load) and be replaced with "pulsectl", which is a much better drop-in replacement !!! - Aliases: pasink (use this to control output instead of input), pasource Parameters: @@ -226,20 +197,6 @@ Parameters: Note: If the left and right channels have different volumes, the limit might not be reached exactly. * pulseaudio.showbars: 1 for showing volume bars, requires --markup=pango; 0 for not showing volume bars (default) - * pulseaudio.showdevicename: If set to 'true' (default is 'false'), the currently selected default device is shown. - Per default, the sink/source name returned by "pactl list sinks short" is used as display name. - - As this name is usually not particularly nice (e.g "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo"), - its possible to map the name to more a user friendly name. - - e.g to map "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" to the name "Headset", add the following - bumblebee-status config entry: pulseaudio.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=Headset - - Furthermore its possible to specify individual (unicode) icons for all sinks/sources. e.g in order to use the icon 🎧 for the - "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" sink, add the following bumblebee-status config entry: - pulseaudio.icon.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=🎧 - * Per default a left mouse button click mutes/unmutes the device. In case you want to open a dropdown menu to change the current - default device add the following config entry to your bumblebee-status config: pulseaudio.left-click=select_default_device_popup Requires the following executable: * pulseaudio @@ -248,44 +205,6 @@ Requires the following executable: .. image:: ../screenshots/pulseaudio.png -pulsectl -~~~~~~~~ - -Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol. - -**Please prefer this module over the "pulseaudio" module, which will eventually be deprecated - -Aliases: pulseout (for outputs, such as headsets, speakers), pulsein (for microphones) - -NOTE: Do **not** use this module directly, but rather use either pulseout or pulsein! -NOTE2: For the parameter names below, please also use pulseout or pulsein, instead of pulsectl - -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. - Per default, the sink/source name returned by "pactl list sinks short" is used as display name. - - As this name is usually not particularly nice (e.g "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo"), - its possible to map the name to more a user friendly name. - - e.g to map "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" to the name "Headset", add the following - bumblebee-status config entry: pulsectl.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=Headset - - Furthermore its possible to specify individual (unicode) icons for all sinks/sources. e.g in order to use the icon 🎧 for the - "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" sink, add the following bumblebee-status config entry: - pulsectl.icon.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=🎧 - * Per default a left mouse button click mutes/unmutes the device. In case you want to open a dropdown menu to change the current - default device add the following config entry to your bumblebee-status config: pulsectl.left-click=select_default_device_popup - -Requires the following Python module: - * pulsectl - redshift ~~~~~~~~ @@ -301,18 +220,9 @@ Parameters: * redshift.lat : latitude if location is set to 'manual' * redshift.lon : longitude if location is set to 'manual' * redshift.show_transition: information about the transitions (x% day) defaults to True - * redshift.adjust: set this to 'true' (defaults to false) to let bumblebee-status adjust color temperature, instead of just showing the current settings .. 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 ~~~~~~~~ @@ -327,7 +237,7 @@ Parameters: * sensors2.showother: Enable or display 'other' sensor readings (default: false) * sensors2.showname: Enable or disable show of sensor name (default: false) * sensors2.chip_include: Comma-separated list of chip to include (defaults to '' will include all by default, example: 'coretemp,bat') - * sensors2.chip_exclude:Comma separated list of chip to exclude (defaults to '' will exclude none by default) + * sensors2.chip_exclude:Comma separated list of chip to exclude (defaults to '' will exlude none by default) * sensors2.field_include: Comma separated list of chip to include (defaults to '' will include all by default, example: 'temp,fan') * sensors2.field_exclude: Comma separated list of chip to exclude (defaults to '' will exclude none by default) * sensors2.chip_field_exclude: Comma separated list of chip field to exclude (defaults to '' will exclude none by default, example: 'coretemp-isa-0000.temp1,coretemp-isa-0000.fan1') @@ -426,7 +336,6 @@ 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%) @@ -434,8 +343,6 @@ 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 @@ -459,9 +366,6 @@ saved screen layout as well as toggle on/off individual connected displays. Parameters: * No configuration parameters -Requires the following python modules: - * tkinter - Requires the following executable: * arandr * xrandr @@ -478,8 +382,6 @@ Requires the following executable: contributed by `lucassouto `_ - many thanks! -.. image:: ../screenshots/arch-update.png - arch_update ~~~~~~~~~~~ @@ -490,18 +392,6 @@ Requires the following executable: contributed by `lucassouto `_ - many thanks! -aur-update -~~~~~~~~~~ - -Check updates for AUR. - -Requires the following executable: - * yay (https://github.com/Jguer/yay) - -contributed by `ishaanbhimwal `_ - many thanks! - -.. image:: ../screenshots/aur-update.png - battery ~~~~~~~ @@ -573,26 +463,6 @@ Parameters: contributed by `martindoublem `_ - many thanks! -blugon -~~~~~~ - -Displays temperature of blugon and Controls it. - -Use wheel up and down to change temperature, middle click to toggle and right click to reset temperature. - -Default Values: - * Minimum temperature: 1000 (red) - * Maximum temperature: 20000 (blue) - * Default temperature: 6600 - -Requires the following executable: - * blugon - -Parameters: - * blugon.step: The amount of increase/decrease on scroll (default: 200) - -contributed by `DTan13 ` - brightness ~~~~~~~~~~ @@ -681,58 +551,15 @@ Parameters: * cpu2.fanspeed * cpu2.colored: 1 for colored per core load graph, 0 for mono (default) * cpu2.temp_pattern: pattern to look for in the output of 'sensors -u'; - required if cpu2.temp widget is used + required if cpu2.temp widged is used * cpu2.fan_pattern: pattern to look for in the output of 'sensors -u'; - required if cpu2.fanspeed widget is used + required if cpu2.fanspeed widged is used Note: if you are getting 'n/a' for CPU temperature / fan speed, then you're lacking the aforementioned pattern settings or they have wrong values. 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 ~~~~~~~~ @@ -884,56 +711,11 @@ 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! .. image:: ../screenshots/dunstctl.png -emerge_status -~~~~~~~~~~~~~ - -Display information about the currently running emerge process. - -Requires the following executable: - * emerge - -Parameters: - * emerge_status.format: Format string (defaults to '{current}/{total} {action} {category}/{pkg}') - -This code is based on emerge_status module from p3status [1] original created by AnwariasEu. - -[1] https://github.com/ultrabug/py3status/blob/master/py3status/modules/emerge_status.py - -.. image:: ../screenshots/emerge_status.png - -gcalendar -~~~~~~~~~ - -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. - -A refresh is done every 15 minutes. - -Parameters: - * gcalendar.time_format: Format time output. Defaults to "%H:%M". - * 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. - -Requires these pip packages: - * google-api-python-client >= 1.8.0 - * google-auth-httplib2 - * google-auth-oauthlib - getcrypto ~~~~~~~~~ @@ -976,29 +758,6 @@ 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 ~~~~~ @@ -1063,6 +822,18 @@ contributed by `pierre87 `_ - many thanks! .. image:: ../screenshots/kernel.png +layout +~~~~~~ + +Displays and changes the current keyboard layout + +Requires the following executable: + * setxkbmap + +contributed by `Pseudonick47 `_ - many thanks! + +.. image:: ../screenshots/layout.png + layout-xkbswitch ~~~~~~~~~~~~~~~~ @@ -1098,7 +869,7 @@ messagereceiver Displays the message that's received via unix socket. -Parameters: +Parameteres: * messagereceiver : Unix socket address (e.g: /tmp/bumblebee_messagereceiver.sock) Example: @@ -1183,22 +954,12 @@ 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! .. image:: ../screenshots/mpd.png -network -~~~~~~~ - -A module to show the currently active network connection (ethernet or wifi) and connection strength if the connection is wireless. - -Requires the Python netifaces package and iw installed on Linux. - -A simpler take on nic and network_traffic. No extra config necessary! - network_traffic ~~~~~~~~~~~~~~~ @@ -1231,16 +992,12 @@ Displays GPU name, temperature and memory usage. Parameters: * nvidiagpu.format: Format string (defaults to '{name}: {temp}°C %{usedmem}/{totalmem} MiB') - Available values are: {name} {temp} {mem_used} {mem_total} {fanspeed} {clock_gpu} {clock_mem} {gpu_usage_pct} {mem_usage_pct} {mem_io_pct} + Available values are: {name} {temp} {mem_used} {mem_total} {fanspeed} {clock_gpu} {clock_mem} Requires nvidia-smi contributed by `RileyRedpath `_ - many thanks! -Note: mem_io_pct is (from `man nvidia-smi`): -> Percent of time over the past sample period during which global (device) -> memory was being read or written. - octoprint ~~~~~~~~~ @@ -1258,21 +1015,13 @@ Parameters: contributed by `bbernhard `_ - many thanks! -optman -~~~~~~ - -Displays currently active gpu by optimus-manager -Requires the following packages: - - * optimus-manager - pacman ~~~~~~ Displays update information per repository for pacman. Parameters: - * pacman.sum: If you prefer displaying updates with a single digit (defaults to 'False') + * pacman.sum: If you prefere displaying updates with a single digit (defaults to 'False') Requires the following executables: * fakeroot @@ -1282,31 +1031,6 @@ contributed by `Pseudonick47 `_ - many thanks! .. image:: ../screenshots/pacman.png -pamixer -~~~~~~~ - -get volume level or control it - -Requires the following executable: - * pamixer - -Parameters: - * pamixer.percent_change: How much to change volume by when scrolling on the module (default is 4%) - -heavily based on amixer module - -persian_date -~~~~~~~~~~~~ - -Displays the current date and time in Persian(Jalali) Calendar. - -Requires the following python packages: - * jdatetime - -Parameters: - * datetime.format: strftime()-compatible formatting string. default: "%A %d %B" e.g., "جمعه ۱۳ اسفند" - * datetime.locale: locale to use. default: "fa_IR" - pihole ~~~~~~ @@ -1314,30 +1038,10 @@ 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.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) - + * 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 - -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 ~~~~~~~~~ @@ -1347,15 +1051,12 @@ Requires the following executable: * playerctl Parameters: - * playerctl.format: Format string (defaults to '{{artist}} - {{title}} {{duration(position)}}/{{duration(mpris:length)}}'). - The format string is passed to 'playerctl -f' as an argument. Read `the README `_ for more information. + * playerctl.format: Format string (defaults to '{artist} - {title}') + Available values are: {album}, {title}, {artist}, {trackNumber} * playerctl.layout: Comma-separated list to change order of widgets (defaults to song, previous, pause, next) Widget names are: playerctl.song, playerctl.prev, playerctl.pause, playerctl.next - * playerctl.args: The arguments added to playerctl. - You can check 'playerctl --help' or `its README `_. For example, it could be '-p vlc,%any'. - * playerctl.hide: Hide the widgets when no players are found. Defaults to "false". -Parameters are inspired by the `spotify` module, many thanks to its developers! +Parameters are inherited from `spotify` module, many thanks to its developers! contributed by `smitajit `_ - many thanks! @@ -1377,7 +1078,7 @@ Parameters: Example: 'notify-send 'Time up!''. If you want to chain multiple commands, please use an external wrapper script and invoke that. The module itself does not support command chaining (see https://github.com/tobi-wan-kenobi/bumblebee-status/issues/532 - for a detailed explanation) + for a detailled explanation) contributed by `martindoublem `_, inspired by `karthink `_ - many thanks! @@ -1441,29 +1142,7 @@ contributed by `remi-dupre `_ - many thanks! publicip ~~~~~~~~ -Displays information about the public IP address associated with the default route: - * Public IP address - * Country Name - * Country Code - * City Name - * Geographic Coordinates - -Left mouse click on the widget forces immediate update. -Any change to the default route will cause the widget to update. - -Requirements: - * netifaces - -Parameters: - * publicip.format: Format string (defaults to ‘{ip} ({country_code})’) - * Available format strings - ip, country_name, country_code, city_name, coordinates - -Examples: - * bumblebee-status -m publicip -p publicip.format="{ip} ({country_code})" - * bumblebee-status -m publicip -p publicip.format="{ip} which is in {city_name}" - * bumblebee-status -m publicip -p publicip.format="Your packets are right here: {coordinates}" - -contributed by `tfwiii ` - many thanks! +Displays public IP address rofication ~~~~~~~~~~ @@ -1476,9 +1155,6 @@ module will have normal highlighting if there are zero notifications, "warning" highlighting if there are nonzero notifications, "critical" highlighting if there are any critical notifications -Parameters: -* rofication.regolith: Switch to regolith fork of rofication, see . - rotation ~~~~~~~~ @@ -1508,7 +1184,6 @@ sensors Displays sensor temperature Parameters: - * sensors.use_sensors: whether to use the sensors command * sensors.path: path to temperature file (default /sys/class/thermal/thermal_zone0/temp). * sensors.json: if set to 'true', interpret sensors.path as JSON 'path' in the output of 'sensors -j' (i.e. //.../), for example, path could @@ -1562,7 +1237,7 @@ a delimiter (; semicolon by default). For example in order to create two shortcuts labeled A and B with commands cmdA and cmdB you could do: - ./bumblebee-status -m shortcut -p shortcut.cmd='firefox https://www.google.com;google-chrome https://google.com' shortcut.label='Google (Firefox);Google (Chrome)' + ./bumblebee-status -m shortcut -p shortcut.cmd='ls;ps' shortcut.label='A;B' Parameters: * shortcut.cmds : List of commands to execute @@ -1584,20 +1259,10 @@ Requires the following executables: * smartctl Parameters: - * smartstatus.display: how to display (defaults to 'combined', other choices: 'combined_singles', 'separate' or 'singles') + * smartstatus.display: how to display (defaults to 'combined', other choices: 'combined_singles', 'seperate' or 'singles') * smartstatus.drives: in the case of singles which drives to display, separated comma list value, multiple accepted (defaults to 'sda', example:'sda,sdc') * smartstatus.show_names: boolean in the form of "True" or "False" to show the name of the drives in the form of sda, sbd, combined or none at all. -solaar -~~~~~~ - -Shows status and load percentage of logitech's unifying device - -Requires the following executable: - * solaar (from community) - -contributed by `cambid `_ - many thanks! - spaceapi ~~~~~~~~ @@ -1614,7 +1279,7 @@ Parameters: Format Strings: * Format strings are indicated by double %% - * They represent a leaf in the JSON tree, layers separated by '.' + * They represent a leaf in the JSON tree, layers seperated by '.' * Boolean values can be overwritten by appending '%true%false' in the format string * Example: to reference 'open' in '{'state':{'open': true}}' @@ -1655,11 +1320,12 @@ stock Display a stock quote from finance.yahoo.com +Requires the following python packages: + * requests + Parameters: * stock.symbols : Comma-separated list of symbols to fetch - * 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" + * stock.change : Should we fetch change in stock value (defaults to True) contributed by `msoulier `_ - many thanks! @@ -1677,8 +1343,8 @@ Requires the following python packages: * python-dateutil Parameters: - * sun.lat : Latitude of your location - * sun.lon : Longitude of your location + * cpu.lat : Latitude of your location + * cpu.lon : Longitude of your location (if none of those are set, location is determined automatically via location APIs) @@ -1694,11 +1360,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') @@ -1707,9 +1373,6 @@ Parameters: * system.suspend: specify a command for suspending (defaults to 'i3exit suspend') * system.hibernate: specify a command for hibernating (defaults to 'i3exit hibernate') -Requirements: - tkinter (python3-tk package on debian based systems either you can install it as python package) - contributed by `bbernhard `_ - many thanks! taskwarrior @@ -1722,7 +1385,6 @@ Requires the following library: Parameters: * taskwarrior.taskrc : path to the taskrc file (defaults to ~/.taskrc) - * taskwarrior.show_active: true/false(default) to show the active task ID and description when one is active, otherwise show the total number pending. contributed by `chdorb `_ - many thanks! @@ -1768,7 +1430,6 @@ Parameters: * title.max : Maximum character length for title before truncating. Defaults to 64. * title.placeholder : Placeholder text to be placed if title was truncated. Defaults to '...'. * title.scroll : Boolean flag for scrolling title. Defaults to False - * title.short : Boolean flag for short title. Defaults to False contributed by `UltimatePancake `_ - many thanks! @@ -1797,27 +1458,6 @@ 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 ~~~~~~~ @@ -1829,7 +1469,7 @@ Parameters: * traffic.showname: If set to False, hide network interface name (defaults to True) * traffic.format: Format string for download/upload speeds. Defaults to '{:.2f}' - * traffic.graphlen: Graph length in seconds. Positive even integer. Each + * traffic.graphlen: Graph lenth in seconds. Positive even integer. Each char shows 2 seconds. If set, enables up/down traffic graphs @@ -1856,27 +1496,6 @@ 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 ~~~ @@ -1896,34 +1515,6 @@ 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 ~~~~~~ @@ -1932,10 +1523,6 @@ 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 @@ -1953,7 +1540,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 https://api.openweathermap.org + * weather.apikey: API key from http://api.openweathermap.org contributed by `TheEdgeOfRage `_ - many thanks! diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 93120e6..0000000 --- a/docs/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -docutils<0.18 diff --git a/docs/themes.rst b/docs/themes.rst index 16f69a6..759ea86 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -97,8 +97,3 @@ 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/man/bumblebee-ctl.1 b/man/bumblebee-ctl.1 deleted file mode 100644 index 33c50aa..0000000 --- a/man/bumblebee-ctl.1 +++ /dev/null @@ -1,29 +0,0 @@ -.TH BUMBLEBEE-CTL "1" "June 2022" "bumblebee-status" -.SH NAME -bumblebee-ctl \- Send commands to bumblebee-status -.SH SYNOPSIS -.B bumblebee-ctl -[\fB\-h\fR] [\fB\-b\fR \fIbutton\fR] [\fB\-i\fR \fIID\fR] \fB-m\fR \fImodule\fR -.SH DESCRIPTION -.B bumblebee-ctl -can be used to send commands to bumblebee-status. -.SH OPTIONS -.TP -\fB\-h\fR, \fB\-\-help\fR -show this help message and exit -.TP -\fB\-b\fR, \fB\-\-button\fR