From a13339e0e99118870498ecfdea22292c7e267eb2 Mon Sep 17 00:00:00 2001 From: Kumar Damani Date: Fri, 26 Apr 2019 14:25:11 -0400 Subject: dotfiles with stow structure --- i3/.config/i3/bar/.codeclimate.yml | 15 + i3/.config/i3/bar/.coveragerc | 10 + i3/.config/i3/bar/.gitignore | 91 ++++ i3/.config/i3/bar/.travis.yml | 18 + i3/.config/i3/bar/LICENSE | 21 + i3/.config/i3/bar/README.md | 134 +++++ i3/.config/i3/bar/bin/load-i3-bars.sh | 26 + i3/.config/i3/bar/bin/pacman-updates | 22 + i3/.config/i3/bar/bin/toggle-display.sh | 12 + i3/.config/i3/bar/bumblebee-status | 66 +++ i3/.config/i3/bar/bumblebee/__init__.py | 0 i3/.config/i3/bar/bumblebee/config.py | 99 ++++ i3/.config/i3/bar/bumblebee/engine.py | 166 ++++++ i3/.config/i3/bar/bumblebee/error.py | 15 + i3/.config/i3/bar/bumblebee/input.py | 134 +++++ i3/.config/i3/bar/bumblebee/modules/__init__.py | 0 i3/.config/i3/bar/bumblebee/modules/amixer.py | 36 ++ i3/.config/i3/bar/bumblebee/modules/battery.py | 82 +++ i3/.config/i3/bar/bumblebee/modules/battery.py_ori | 75 +++ i3/.config/i3/bar/bumblebee/modules/brightness.py | 37 ++ i3/.config/i3/bar/bumblebee/modules/caffeine.py | 48 ++ i3/.config/i3/bar/bumblebee/modules/cmus.py | 92 ++++ i3/.config/i3/bar/bumblebee/modules/cpu.py | 37 ++ i3/.config/i3/bar/bumblebee/modules/datetime.py | 35 ++ i3/.config/i3/bar/bumblebee/modules/disk.py | 47 ++ i3/.config/i3/bar/bumblebee/modules/disk.py_ori | 45 ++ i3/.config/i3/bar/bumblebee/modules/dnf.py | 80 +++ i3/.config/i3/bar/bumblebee/modules/error.py | 26 + i3/.config/i3/bar/bumblebee/modules/gpmdp.py | 58 +++ i3/.config/i3/bar/bumblebee/modules/kernel.py | 21 + i3/.config/i3/bar/bumblebee/modules/layout.py | 74 +++ i3/.config/i3/bar/bumblebee/modules/load.py | 41 ++ i3/.config/i3/bar/bumblebee/modules/memory.py | 47 ++ i3/.config/i3/bar/bumblebee/modules/nic.py | 83 +++ i3/.config/i3/bar/bumblebee/modules/nic.py_ori | 88 ++++ i3/.config/i3/bar/bumblebee/modules/pacman.py | 70 +++ i3/.config/i3/bar/bumblebee/modules/ping.py | 77 +++ i3/.config/i3/bar/bumblebee/modules/pulseaudio.py | 107 ++++ i3/.config/i3/bar/bumblebee/modules/redshift.py | 46 ++ i3/.config/i3/bar/bumblebee/modules/sensors.py | 45 ++ i3/.config/i3/bar/bumblebee/modules/spacer.py | 23 + i3/.config/i3/bar/bumblebee/modules/stock.py | 62 +++ i3/.config/i3/bar/bumblebee/modules/test.py | 16 + i3/.config/i3/bar/bumblebee/modules/traffic.py | 72 +++ i3/.config/i3/bar/bumblebee/modules/weather.py | 74 +++ i3/.config/i3/bar/bumblebee/modules/xrandr.py | 74 +++ i3/.config/i3/bar/bumblebee/output.py | 140 ++++++ i3/.config/i3/bar/bumblebee/store.py | 21 + i3/.config/i3/bar/bumblebee/theme.py | 180 +++++++ i3/.config/i3/bar/bumblebee/util.py | 47 ++ i3/.config/i3/bar/runlint.sh | 3 + i3/.config/i3/bar/runtests.sh | 11 + i3/.config/i3/bar/screenshots/amixer.png | Bin 0 -> 1645 bytes i3/.config/i3/bar/screenshots/battery.png | Bin 0 -> 1733 bytes i3/.config/i3/bar/screenshots/brightness.png | Bin 0 -> 1323 bytes i3/.config/i3/bar/screenshots/caffeine.png | Bin 0 -> 1143 bytes i3/.config/i3/bar/screenshots/cmus.png | Bin 0 -> 5031 bytes i3/.config/i3/bar/screenshots/cpu.png | Bin 0 -> 2003 bytes i3/.config/i3/bar/screenshots/date.png | Bin 0 -> 2877 bytes i3/.config/i3/bar/screenshots/datetime.png | Bin 0 -> 4889 bytes i3/.config/i3/bar/screenshots/disk.png | Bin 0 -> 3721 bytes i3/.config/i3/bar/screenshots/dnf.png | Bin 0 -> 1113 bytes i3/.config/i3/bar/screenshots/kernel.png | Bin 0 -> 3125 bytes i3/.config/i3/bar/screenshots/layout.png | Bin 0 -> 1198 bytes i3/.config/i3/bar/screenshots/load.png | Bin 0 -> 2005 bytes i3/.config/i3/bar/screenshots/memory.png | Bin 0 -> 3603 bytes i3/.config/i3/bar/screenshots/nic.png | Bin 0 -> 2474 bytes i3/.config/i3/bar/screenshots/pacman.png | Bin 0 -> 860 bytes i3/.config/i3/bar/screenshots/pasink.png | Bin 0 -> 1486 bytes i3/.config/i3/bar/screenshots/pasource.png | Bin 0 -> 1505 bytes i3/.config/i3/bar/screenshots/ping.png | Bin 0 -> 1655 bytes i3/.config/i3/bar/screenshots/pulseaudio.png | Bin 0 -> 2495 bytes i3/.config/i3/bar/screenshots/redshift.png | Bin 0 -> 1325 bytes i3/.config/i3/bar/screenshots/sensors.png | Bin 0 -> 1054 bytes i3/.config/i3/bar/screenshots/spacer.png | Bin 0 -> 521 bytes i3/.config/i3/bar/screenshots/stock.png | Bin 0 -> 2087 bytes i3/.config/i3/bar/screenshots/themes/default.png | Bin 0 -> 7251 bytes i3/.config/i3/bar/screenshots/themes/gruvbox.png | Bin 0 -> 10310 bytes .../bar/screenshots/themes/powerline-gruvbox.png | Bin 0 -> 11594 bytes .../bar/screenshots/themes/powerline-solarized.png | Bin 0 -> 11991 bytes i3/.config/i3/bar/screenshots/themes/powerline.png | Bin 0 -> 10701 bytes i3/.config/i3/bar/screenshots/themes/solarized.png | Bin 0 -> 9612 bytes i3/.config/i3/bar/screenshots/time.png | Bin 0 -> 2456 bytes i3/.config/i3/bar/screenshots/traffic.png | Bin 0 -> 3811 bytes i3/.config/i3/bar/screenshots/weather.png | Bin 0 -> 1111 bytes i3/.config/i3/bar/screenshots/xrandr.png | Bin 0 -> 2693 bytes i3/.config/i3/bar/testjson.sh | 3 + i3/.config/i3/bar/tests/__init__.py | 0 i3/.config/i3/bar/tests/mocks.py | 141 ++++++ i3/.config/i3/bar/tests/modules/__init__.py | 0 i3/.config/i3/bar/tests/modules/test_battery.py | 108 ++++ i3/.config/i3/bar/tests/modules/test_brightness.py | 47 ++ i3/.config/i3/bar/tests/modules/test_caffeine.py | 53 ++ i3/.config/i3/bar/tests/modules/test_cmus.py | 114 +++++ i3/.config/i3/bar/tests/modules/test_cpu.py | 45 ++ i3/.config/i3/bar/tests/modules/test_disk.py | 47 ++ i3/.config/i3/bar/tests/modules/test_load.py | 57 +++ i3/.config/i3/bar/tests/modules/test_memory.py | 52 ++ i3/.config/i3/bar/tests/modules/test_pulseaudio.py | 34 ++ i3/.config/i3/bar/tests/test_config.py | 77 +++ i3/.config/i3/bar/tests/test_engine.py | 90 ++++ i3/.config/i3/bar/tests/test_i3barinput.py | 110 ++++ i3/.config/i3/bar/tests/test_i3baroutput.py | 133 +++++ i3/.config/i3/bar/tests/test_module.py | 89 ++++ i3/.config/i3/bar/tests/test_modules.py | 58 +++ i3/.config/i3/bar/tests/test_store.py | 26 + i3/.config/i3/bar/tests/test_theme.py | 134 +++++ i3/.config/i3/bar/tests/test_util.py | 56 +++ i3/.config/i3/bar/themes/default.json | 7 + i3/.config/i3/bar/themes/gruvbox-powerline.json | 41 ++ i3/.config/i3/bar/themes/gruvbox.json | 41 ++ i3/.config/i3/bar/themes/icons/ascii.json | 70 +++ i3/.config/i3/bar/themes/icons/awesome-fonts.json | 86 ++++ i3/.config/i3/bar/themes/icons/paxy97.json | 5 + i3/.config/i3/bar/themes/icons/test.json | 6 + i3/.config/i3/bar/themes/kulade.json | 46 ++ i3/.config/i3/bar/themes/powerline.json | 41 ++ i3/.config/i3/bar/themes/solarized-powerline.json | 40 ++ i3/.config/i3/bar/themes/solarized.json | 42 ++ i3/.config/i3/bar/themes/test.json | 25 + i3/.config/i3/bar/themes/test_cycle.json | 18 + i3/.config/i3/bar/themes/test_invalid.json | 1 + i3/.config/i3/config | 560 +++++++++++++++++++++ i3/.config/i3/lock.png | Bin 0 -> 5016 bytes i3/.config/i3/lock.sh | 8 + i3/.config/i3blocks/config | 70 +++ 126 files changed, 5530 insertions(+) create mode 100644 i3/.config/i3/bar/.codeclimate.yml create mode 100644 i3/.config/i3/bar/.coveragerc create mode 100644 i3/.config/i3/bar/.gitignore create mode 100644 i3/.config/i3/bar/.travis.yml create mode 100644 i3/.config/i3/bar/LICENSE create mode 100644 i3/.config/i3/bar/README.md create mode 100644 i3/.config/i3/bar/bin/load-i3-bars.sh create mode 100644 i3/.config/i3/bar/bin/pacman-updates create mode 100644 i3/.config/i3/bar/bin/toggle-display.sh create mode 100644 i3/.config/i3/bar/bumblebee-status create mode 100644 i3/.config/i3/bar/bumblebee/__init__.py create mode 100644 i3/.config/i3/bar/bumblebee/config.py create mode 100644 i3/.config/i3/bar/bumblebee/engine.py create mode 100644 i3/.config/i3/bar/bumblebee/error.py create mode 100644 i3/.config/i3/bar/bumblebee/input.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/__init__.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/amixer.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/battery.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/battery.py_ori create mode 100644 i3/.config/i3/bar/bumblebee/modules/brightness.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/caffeine.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/cmus.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/cpu.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/datetime.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/disk.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/disk.py_ori create mode 100644 i3/.config/i3/bar/bumblebee/modules/dnf.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/error.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/gpmdp.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/kernel.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/layout.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/load.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/memory.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/nic.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/nic.py_ori create mode 100644 i3/.config/i3/bar/bumblebee/modules/pacman.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/ping.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/pulseaudio.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/redshift.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/sensors.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/spacer.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/stock.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/test.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/traffic.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/weather.py create mode 100644 i3/.config/i3/bar/bumblebee/modules/xrandr.py create mode 100644 i3/.config/i3/bar/bumblebee/output.py create mode 100644 i3/.config/i3/bar/bumblebee/store.py create mode 100644 i3/.config/i3/bar/bumblebee/theme.py create mode 100644 i3/.config/i3/bar/bumblebee/util.py create mode 100644 i3/.config/i3/bar/runlint.sh create mode 100644 i3/.config/i3/bar/runtests.sh create mode 100644 i3/.config/i3/bar/screenshots/amixer.png create mode 100644 i3/.config/i3/bar/screenshots/battery.png create mode 100644 i3/.config/i3/bar/screenshots/brightness.png create mode 100644 i3/.config/i3/bar/screenshots/caffeine.png create mode 100644 i3/.config/i3/bar/screenshots/cmus.png create mode 100644 i3/.config/i3/bar/screenshots/cpu.png create mode 100644 i3/.config/i3/bar/screenshots/date.png create mode 100644 i3/.config/i3/bar/screenshots/datetime.png create mode 100644 i3/.config/i3/bar/screenshots/disk.png create mode 100644 i3/.config/i3/bar/screenshots/dnf.png create mode 100644 i3/.config/i3/bar/screenshots/kernel.png create mode 100644 i3/.config/i3/bar/screenshots/layout.png create mode 100644 i3/.config/i3/bar/screenshots/load.png create mode 100644 i3/.config/i3/bar/screenshots/memory.png create mode 100644 i3/.config/i3/bar/screenshots/nic.png create mode 100644 i3/.config/i3/bar/screenshots/pacman.png create mode 100644 i3/.config/i3/bar/screenshots/pasink.png create mode 100644 i3/.config/i3/bar/screenshots/pasource.png create mode 100644 i3/.config/i3/bar/screenshots/ping.png create mode 100644 i3/.config/i3/bar/screenshots/pulseaudio.png create mode 100644 i3/.config/i3/bar/screenshots/redshift.png create mode 100644 i3/.config/i3/bar/screenshots/sensors.png create mode 100644 i3/.config/i3/bar/screenshots/spacer.png create mode 100644 i3/.config/i3/bar/screenshots/stock.png create mode 100644 i3/.config/i3/bar/screenshots/themes/default.png create mode 100644 i3/.config/i3/bar/screenshots/themes/gruvbox.png create mode 100644 i3/.config/i3/bar/screenshots/themes/powerline-gruvbox.png create mode 100644 i3/.config/i3/bar/screenshots/themes/powerline-solarized.png create mode 100644 i3/.config/i3/bar/screenshots/themes/powerline.png create mode 100644 i3/.config/i3/bar/screenshots/themes/solarized.png create mode 100644 i3/.config/i3/bar/screenshots/time.png create mode 100644 i3/.config/i3/bar/screenshots/traffic.png create mode 100644 i3/.config/i3/bar/screenshots/weather.png create mode 100644 i3/.config/i3/bar/screenshots/xrandr.png create mode 100644 i3/.config/i3/bar/testjson.sh create mode 100644 i3/.config/i3/bar/tests/__init__.py create mode 100644 i3/.config/i3/bar/tests/mocks.py create mode 100644 i3/.config/i3/bar/tests/modules/__init__.py create mode 100644 i3/.config/i3/bar/tests/modules/test_battery.py create mode 100644 i3/.config/i3/bar/tests/modules/test_brightness.py create mode 100644 i3/.config/i3/bar/tests/modules/test_caffeine.py create mode 100644 i3/.config/i3/bar/tests/modules/test_cmus.py create mode 100644 i3/.config/i3/bar/tests/modules/test_cpu.py create mode 100644 i3/.config/i3/bar/tests/modules/test_disk.py create mode 100644 i3/.config/i3/bar/tests/modules/test_load.py create mode 100644 i3/.config/i3/bar/tests/modules/test_memory.py create mode 100644 i3/.config/i3/bar/tests/modules/test_pulseaudio.py create mode 100644 i3/.config/i3/bar/tests/test_config.py create mode 100644 i3/.config/i3/bar/tests/test_engine.py create mode 100644 i3/.config/i3/bar/tests/test_i3barinput.py create mode 100644 i3/.config/i3/bar/tests/test_i3baroutput.py create mode 100644 i3/.config/i3/bar/tests/test_module.py create mode 100644 i3/.config/i3/bar/tests/test_modules.py create mode 100644 i3/.config/i3/bar/tests/test_store.py create mode 100644 i3/.config/i3/bar/tests/test_theme.py create mode 100644 i3/.config/i3/bar/tests/test_util.py create mode 100644 i3/.config/i3/bar/themes/default.json create mode 100644 i3/.config/i3/bar/themes/gruvbox-powerline.json create mode 100644 i3/.config/i3/bar/themes/gruvbox.json create mode 100644 i3/.config/i3/bar/themes/icons/ascii.json create mode 100644 i3/.config/i3/bar/themes/icons/awesome-fonts.json create mode 100644 i3/.config/i3/bar/themes/icons/paxy97.json create mode 100644 i3/.config/i3/bar/themes/icons/test.json create mode 100644 i3/.config/i3/bar/themes/kulade.json create mode 100644 i3/.config/i3/bar/themes/powerline.json create mode 100644 i3/.config/i3/bar/themes/solarized-powerline.json create mode 100644 i3/.config/i3/bar/themes/solarized.json create mode 100644 i3/.config/i3/bar/themes/test.json create mode 100644 i3/.config/i3/bar/themes/test_cycle.json create mode 100644 i3/.config/i3/bar/themes/test_invalid.json create mode 100644 i3/.config/i3/config create mode 100644 i3/.config/i3/lock.png create mode 100644 i3/.config/i3/lock.sh create mode 100644 i3/.config/i3blocks/config (limited to 'i3') diff --git a/i3/.config/i3/bar/.codeclimate.yml b/i3/.config/i3/bar/.codeclimate.yml new file mode 100644 index 0000000..ec80874 --- /dev/null +++ b/i3/.config/i3/bar/.codeclimate.yml @@ -0,0 +1,15 @@ +engines: + duplication: + enabled: true + config: + languages: + - python + fixme: + enabled: true + radon: + enabled: true +ratings: + paths: + - "**.py" +exclude_paths: +- tests/ diff --git a/i3/.config/i3/bar/.coveragerc b/i3/.config/i3/bar/.coveragerc new file mode 100644 index 0000000..e809714 --- /dev/null +++ b/i3/.config/i3/bar/.coveragerc @@ -0,0 +1,10 @@ +[run] +omit = + tests/* + *mock* + *funcsigs* + *pbr* + *six* + /usr/lib* + +[report] diff --git a/i3/.config/i3/bar/.gitignore b/i3/.config/i3/bar/.gitignore new file mode 100644 index 0000000..d7d9fd4 --- /dev/null +++ b/i3/.config/i3/bar/.gitignore @@ -0,0 +1,91 @@ +*swp + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject diff --git a/i3/.config/i3/bar/.travis.yml b/i3/.config/i3/bar/.travis.yml new file mode 100644 index 0000000..0e198ad --- /dev/null +++ b/i3/.config/i3/bar/.travis.yml @@ -0,0 +1,18 @@ +language: python +python: + - "2.7" + - "3.3" + - "3.4" + - "3.5" + - "3.6" +install: + - pip install psutil + - pip install netifaces + - pip install -U coverage==4.3 + - pip install codeclimate-test-reporter +script: + - nosetests -v --with-coverage --cover-erase tests/ + - CODECLIMATE_REPO_TOKEN=40cb00907f7a10e04868e856570bb997ab9c42fd3b63d980f2b2269433195fdf codeclimate-test-reporter +addons: + code_climate: + repo_token: 40cb00907f7a10e04868e856570bb997ab9c42fd3b63d980f2b2269433195fdf diff --git a/i3/.config/i3/bar/LICENSE b/i3/.config/i3/bar/LICENSE new file mode 100644 index 0000000..b472f2e --- /dev/null +++ b/i3/.config/i3/bar/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 tobi-wan-kenobi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/i3/.config/i3/bar/README.md b/i3/.config/i3/bar/README.md new file mode 100644 index 0000000..3570c33 --- /dev/null +++ b/i3/.config/i3/bar/README.md @@ -0,0 +1,134 @@ +# bumblebee-status + +[![Build Status](https://travis-ci.org/tobi-wan-kenobi/bumblebee-status.svg?branch=master)](https://travis-ci.org/tobi-wan-kenobi/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) + +bumblebee-status is a modular, theme-able status line generator for the [i3 window manager](https://i3wm.org/). + +Focus is on: +* Ease of use (no configuration files!) +* Theme support +* Extensibility (of course...) + +I hope you like it and appreciate any kind of feedback: Bug reports, Feature requests, etc. :) + +Thanks a lot! + +Supported Python versions: 2.7, 3.3, 3.4, 3.5, 3.6 + +Explicitly unsupported Python versions: 3.2 (missing unicode literals) + +# Documentation +See [the wiki](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki) for documentation. + +Other resources: + +* A list of [available modules](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/Available-Modules) +* [How to write a theme](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/How-to-write-a-theme) +* [How to write a module](https://github.com/tobi-wan-kenobi/bumblebee-status/wiki/How-to-write-a-module) + +# Installation +``` +$ git clone git://github.com/tobi-wan-kenobi/bumblebee-status +``` + +# Usage +## Normal usage +Next, open your i3wm configuration and modify the *status_command* for your i3bar like this: + +``` +bar { + status_command = -m -p -t +} +``` + +You can retrieve a list of modules and themes by entering: +``` +$ cd bumblebee-status +$ ./bumblebee-status -l themes +$ ./bumblebee-status -l modules +``` + +To change the update interval, use: +``` +$ ./bumblebee-status -m -p interval= +``` + +As a simple example, this is what my i3 configuration looks like: + +``` +bar { + font pango:Inconsolata 10 + position top + tray_output none + status_command ~/.i3/bumblebee-status/bumblebee-status -m nic disk:root cpu memory battery date time pasink pasource dnf -p root.path=/ time.format="%H:%M CW %V" date.format="%a, %b %d %Y" -t solarized-powerline +} + +``` + +Restart i3wm and - that's it! + +## Errors +If errors occur, you should see them in the i3bar itself. If that does not work, or you need more information for troubleshooting, you can activate a debug log using the `-d` or `--debug` switch: + +``` +$ ./bumblebee-status -d -m +``` + +This will create a file called `debug.log` in the same directory as the executable `bumblebee-status`. + +# Required Modules + +Modules and commandline utilities are only required for modules, the core itself has no external dependencies at all. + +* psutil (for the modules 'cpu', 'memory', 'traffic') +* netifaces (for the module 'nic', 'traffic') +* requests (for the module 'weather') + +# Required commandline utilities + +* xbacklight (for the module 'brightness') +* xset (for the module 'caffeine') +* notify-send (for the module 'caffeine') +* cmus-remote (for the module 'cmus') +* dnf (for the module 'dnf') +* gpmdp-remote (for the module 'gpmdp') +* setxkbmap (for the module 'layout') +* fakeroot (for the module 'pacman') +* pacman (for the module 'pacman') +* pactl (for the module 'pulseaudio') +* ping (for the module 'ping') +* redshift (for the module 'redshift') +* xrandr (for the module 'xrandr') +* sensors (for the module 'sensors') + +# Examples +Here are some screenshots for all themes that currently exist: + +:exclamation: Some themes (all 'Powerline' themes) require [Font Awesome](http://fontawesome.io/) and a powerline-compatible font ([powerline-fonts](https://github.com/powerline/fonts), for example) to display all icons correctly. + +Gruvbox Powerline (`-t gruvbox-powerline`) (contributed by [@paxy97](https://github.com/paxy97)): + +![Gruvbox Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-gruvbox.png) + +Solarized Powerline (`-t solarized-powerline`): + +![Solarized Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-solarized.png) + +Gruvbox (`-t gruvbox`): + +![Gruvbox](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/gruvbox.png) + +Solarized (`-t solarized`): + +![Solarized](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/solarized.png) + +Powerline (`-t powerline`): + +![Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline.png) + +Default (nothing or `-t default`): + +![Default](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/default.png) diff --git a/i3/.config/i3/bar/bin/load-i3-bars.sh b/i3/.config/i3/bar/bin/load-i3-bars.sh new file mode 100644 index 0000000..cbe0e95 --- /dev/null +++ b/i3/.config/i3/bar/bin/load-i3-bars.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +if [ ! -f ~/.i3/config.template ]; then + cp ~/.i3/config ~/.i3/config.template +else + cp ~/.i3/config.template ~/.i3/config +fi + +screens=$(xrandr -q|grep ' connected'| grep -P '\d+x\d+' |cut -d' ' -f1) + +echo "screens: $screens" + +while read -r line; do + screen=$(echo $line | cut -d' ' -f1) + others=$(echo $screens|tr ' ' '\n'|grep -v $screen|tr '\n' '-'|sed 's/.$//') + + if [ -f ~/.i3/config.$screen-$others ]; then + cat ~/.i3/config.$screen-$others >> ~/.i3/config + else + if [ -f ~/.i3/config.$screen ]; then + cat ~/.i3/config.$screen >> ~/.i3/config + fi + fi +done <<< "$screens" + +i3-msg restart diff --git a/i3/.config/i3/bar/bin/pacman-updates b/i3/.config/i3/bar/bin/pacman-updates new file mode 100644 index 0000000..baf0b93 --- /dev/null +++ b/i3/.config/i3/bar/bin/pacman-updates @@ -0,0 +1,22 @@ +#!/usr/bin/bash + +if ! type -P fakeroot >/dev/null; then + error 'Cannot find the fakeroot binary.' + exit 1 +fi + +if [[ -z $CHECKUPDATES_DB ]]; then + CHECKUPDATES_DB="${TMPDIR:-/tmp}/checkup-db-${USER}/" +fi + +trap 'rm -f $CHECKUPDATES_DB/db.lck' INT TERM EXIT + +DBPath="${DBPath:-/var/lib/pacman/}" +eval $(awk -F' *= *' '$1 ~ /DBPath/ { print $1 "=" $2 }' /etc/pacman.conf) + +mkdir -p "$CHECKUPDATES_DB" +ln -s "${DBPath}/local" "$CHECKUPDATES_DB" &> /dev/null +fakeroot -- pacman -Sy --dbpath "$CHECKUPDATES_DB" --logfile /dev/null &> /dev/null +fakeroot pacman -Su -p --dbpath "$CHECKUPDATES_DB" + +exit 0 diff --git a/i3/.config/i3/bar/bin/toggle-display.sh b/i3/.config/i3/bar/bin/toggle-display.sh new file mode 100644 index 0000000..bd13a29 --- /dev/null +++ b/i3/.config/i3/bar/bin/toggle-display.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +echo $(dirname $(readlink -f "$0")) + +i3bar_update=$(dirname $(readlink -f "$0"))/load-i3-bars.sh + +xrandr "$@" + +if [ -f $i3bar_update ]; then + sleep 1 + $i3bar_update +fi diff --git a/i3/.config/i3/bar/bumblebee-status b/i3/.config/i3/bar/bumblebee-status new file mode 100644 index 0000000..582f314 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee-status @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +import os +import sys +import logging +import bumblebee.theme +import bumblebee.engine +import bumblebee.config +import bumblebee.output +import bumblebee.input +import bumblebee.modules.error + +def main(): + config = bumblebee.config.Config(sys.argv[1:]) + + if config.debug(): + logging.basicConfig( + level=logging.DEBUG, + format="[%(asctime)s] %(levelname)-8s %(message)s", + filename=os.path.expanduser(config.logfile()) + ) + else: + logging.basicConfig( + level=logging.DEBUG, + format="[%(asctime)s] %(levelname)-8s %(message)s", + stream=sys.stderr + ) + + theme = bumblebee.theme.Theme(config.theme()) + output = bumblebee.output.I3BarOutput(theme=theme) + inp = bumblebee.input.I3BarInput() + engine = None + + try: + engine = bumblebee.engine.Engine( + config=config, + output=output, + inp=inp, + ) + engine.run() + except KeyboardInterrupt as error: + inp.stop() + sys.exit(0) + except BaseException as e: + logging.exception(e) + if output.started(): + output.flush() + output.end() + else: + output.start() + import time + while True: + output.begin() + error = bumblebee.modules.error.Module(engine, config) + error.set("exception occurred: {}".format(e)) + widget = error.widgets()[0] + widget.link_module(error) + output.draw(widget, error) + output.flush() + output.end() + time.sleep(1) + +if __name__ == "__main__": + main() + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/__init__.py b/i3/.config/i3/bar/bumblebee/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/i3/.config/i3/bar/bumblebee/config.py b/i3/.config/i3/bar/bumblebee/config.py new file mode 100644 index 0000000..4e746bd --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/config.py @@ -0,0 +1,99 @@ +"""Configuration handling + +This module provides configuration information (loaded modules, +module parameters, etc.) to all other components +""" + +import os +import sys +import logging +import argparse +import textwrap +import importlib +import bumblebee.store + +MODULE_HELP = "Specify a space-separated list of modules to load. The order of the list determines their order in the i3bar (from left to right). Use : to provide an alias in case you want to load the same module multiple times, but specify different parameters." +THEME_HELP = "Specify the theme to use for drawing modules" +PARAMETER_HELP = "Provide configuration parameters in the form of .=" +LIST_HELP = "Display a list of either available themes or available modules along with their parameters." +DEBUG_HELP = "Enable debug log ('debug.log' located in the same directory as the bumblebee-status executable)" + +class print_usage(argparse.Action): + def __init__(self, option_strings, dest, nargs=None, **kwargs): + argparse.Action.__init__(self, option_strings, dest, nargs, **kwargs) + self._indent = " "*2 + + def __call__(self, parser, namespace, value, option_string=None): + if value == "modules": + self.print_modules() + elif value == "themes": + self.print_themes() + sys.exit(0) + + def print_themes(self): + print(textwrap.fill(", ".join(bumblebee.theme.themes()), + 80, initial_indent = self._indent, subsequent_indent = self._indent + )) + + def print_modules(self): + for m in bumblebee.engine.all_modules(): + mod = importlib.import_module("bumblebee.modules.{}".format(m["name"])) + print(textwrap.fill("{}:".format(m["name"]), 80, + initial_indent=self._indent*2, subsequent_indent=self._indent*2)) + for line in mod.__doc__.split("\n"): + print(textwrap.fill(line, 80, + initial_indent=self._indent*3, subsequent_indent=self._indent*6)) + +def create_parser(): + """Create the argument parser""" + parser = argparse.ArgumentParser(description="display system data in the i3bar") + parser.add_argument("-m", "--modules", nargs="+", default=[], + help=MODULE_HELP) + parser.add_argument("-t", "--theme", default="default", help=THEME_HELP) + parser.add_argument("-p", "--parameters", nargs="+", default=[], + help=PARAMETER_HELP) + parser.add_argument("-l", "--list", choices=["modules", "themes"], action=print_usage, + help=LIST_HELP) + parser.add_argument("-d", "--debug", action="store_true", + help=DEBUG_HELP) + parser.add_argument("-f", "--logfile", default="~/bumblebee-status-debug.log", + help="Location of the debug log file") + + return parser + +class Config(bumblebee.store.Store): + """Top-level configuration class + + Parses commandline arguments and provides non-module + specific configuration information. + """ + def __init__(self, args=None): + super(Config, self).__init__() + parser = create_parser() + self._args = parser.parse_args(args if args else []) + + if not self._args.debug: + logger = logging.getLogger().disabled = True + + for param in self._args.parameters: + key, value = param.split("=") + self.set(key, value) + + def modules(self): + """Return a list of all activated modules""" + return [{ + "module": x.split(":")[0], + "name": x if not ":" in x else x.split(":")[1], + } for x in self._args.modules] + + def theme(self): + """Return the name of the selected theme""" + return self._args.theme + + def debug(self): + return self._args.debug + + def logfile(self): + return self._args.logfile + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/engine.py b/i3/.config/i3/bar/bumblebee/engine.py new file mode 100644 index 0000000..c0ea9cc --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/engine.py @@ -0,0 +1,166 @@ +"""Core application engine""" + +import os +import time +import pkgutil +import importlib +import bumblebee.error +import bumblebee.modules + +def all_modules(): + """Return a list of available modules""" + result = [] + path = os.path.dirname(bumblebee.modules.__file__) + for mod in [name for _, name, _ in pkgutil.iter_modules([path])]: + result.append({ + "name": mod + }) + return result + +class Module(object): + """Module instance base class + + Objects of this type represent the modules that + the user configures. Concrete module implementations + (e.g. CPU utilization, disk usage, etc.) derive from + this base class. + """ + def __init__(self, engine, config={}, widgets=[]): + self.name = config.get("name", self.__module__.split(".")[-1]) + self._config = config + self.id = self.name + self._widgets = [] + if widgets: + self._widgets = widgets if isinstance(widgets, list) else [widgets] + + def widgets(self): + """Return the widgets to draw for this module""" + return self._widgets + + def widget(self, name): + for widget in self._widgets: + if widget.name == name: + return widget + + def widget_by_id(self, uid): + for widget in self._widgets: + if widget.id == uid: + return widget + return None + + def update(self, widgets): + """By default, update() is a NOP""" + pass + + def update_all(self): + self.update(self._widgets) + + def parameter(self, name, default=None): + """Return the config parameter 'name' for this module""" + name = "{}.{}".format(self.name, name) + return self._config["config"].get(name, default) + + def threshold_state(self, value, warn, crit): + if value > float(self.parameter("critical", crit)): + return "critical" + if value > float(self.parameter("warning", warn)): + return "warning" + return None + +class Engine(object): + """Engine for driving the application + + This class connects input/output, instantiates all + required modules and drives the "event loop" + """ + def __init__(self, config, output=None, inp=None): + self._output = output + self._config = config + self._running = True + self._modules = [] + self.input = inp + self._aliases = self._read_aliases() + self.load_modules(config.modules()) + + self.input.register_callback(None, bumblebee.input.WHEEL_UP, + "i3-msg workspace prev_on_output") + self.input.register_callback(None, bumblebee.input.WHEEL_DOWN, + "i3-msg workspace next_on_output") + + self.input.start() + + def modules(self): + return self._modules + + def load_modules(self, modules): + """Load specified modules and return them as list""" + for module in modules: + mod = self._load_module(module["module"], module["name"]) + self._modules.append(mod) + self._register_module_callbacks(mod) + return self._modules + + def _register_module_callbacks(self, module): + buttons = [ + { "name": "left-click", "id": bumblebee.input.LEFT_MOUSE }, + { "name": "middle-click", "id": bumblebee.input.MIDDLE_MOUSE }, + { "name": "right-click", "id": bumblebee.input.RIGHT_MOUSE }, + { "name": "wheel-up", "id": bumblebee.input.WHEEL_UP }, + { "name": "wheel-down", "id": bumblebee.input.WHEEL_DOWN }, + ] + for button in buttons: + if module.parameter(button["name"], None): + self.input.register_callback(obj=module, + button=button["id"], cmd=module.parameter(button["name"])) + + def _read_aliases(self): + result = {} + for module in all_modules(): + mod = importlib.import_module("bumblebee.modules.{}".format(module["name"])) + for alias in getattr(mod, "ALIASES", []): + result[alias] = module["name"] + return result + + def _load_module(self, module_name, config_name=None): + """Load specified module and return it as object""" + if module_name in self._aliases: + config_name is config_name if config_name else module_name + module_name = self._aliases[module_name] + if config_name is None: + config_name = module_name + try: + module = importlib.import_module("bumblebee.modules.{}".format(module_name)) + except ImportError as error: + raise bumblebee.error.ModuleLoadError(error) + return getattr(module, "Module")(self, { + "name": config_name, + "config": self._config + }) + + def running(self): + """Check whether the event loop is running""" + return self._running + + def stop(self): + """Stop the event loop""" + self._running = False + + def run(self): + """Start the event loop""" + self._output.start() + while self.running(): + self._output.begin() + for module in self._modules: + module.update(module.widgets()) + for widget in module.widgets(): + widget.link_module(module) + self._output.draw(widget=widget, module=module, engine=self) + self._output.flush() + self._output.end() + if self.running(): + self.input.wait(float(self._config.get("interval", 1))) + + self._output.stop() + self.input.stop() + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/error.py b/i3/.config/i3/bar/bumblebee/error.py new file mode 100644 index 0000000..129f02d --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/error.py @@ -0,0 +1,15 @@ +"""Collection of all exceptions raised by this tool""" + +class BaseError(Exception): + """Base class for all exceptions generated by this tool""" + pass + +class ModuleLoadError(BaseError): + """Raised whenever loading a module fails""" + pass + +class ThemeLoadError(BaseError): + """Raised whenever loading a theme fails""" + pass + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/input.py b/i3/.config/i3/bar/bumblebee/input.py new file mode 100644 index 0000000..1678b2e --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/input.py @@ -0,0 +1,134 @@ +"""Input classes""" + +import sys +import json +import uuid +import time +import select +import threading +import bumblebee.util + +LEFT_MOUSE = 1 +MIDDLE_MOUSE = 2 +RIGHT_MOUSE = 3 +WHEEL_UP = 4 +WHEEL_DOWN = 5 + +def is_terminated(): + for thread in threading.enumerate(): + if thread.name == "MainThread" and not thread.is_alive(): + return True + return False + +def read_input(inp): + """Read i3bar input and execute callbacks""" + epoll = select.epoll() + epoll.register(sys.stdin.fileno(), select.EPOLLIN) + while inp.running: + if is_terminated(): + return + + events = epoll.poll(1) + for fileno, event in events: + line = "[" + while "[" in line: + line = sys.stdin.readline().strip(",").strip() + inp.has_event = True + try: + event = json.loads(line) + if "instance" in event: + inp.callback(event) + inp.redraw() + except ValueError: + pass + epoll.unregister(sys.stdin.fileno()) + epoll.close() + inp.has_event = True + inp.clean_exit = True + +class I3BarInput(object): + """Process incoming events from the i3bar""" + def __init__(self): + self.running = True + self._callbacks = {} + self.clean_exit = False + self.global_id = str(uuid.uuid4()) + self.need_event = False + self.has_event = False + self._condition = threading.Condition() + + def start(self): + """Start asynchronous input processing""" + self.has_event = False + self.running = True + self._condition.acquire() + self._thread = threading.Thread(target=read_input, args=(self,)) + self._thread.start() + + def redraw(self): + self._condition.acquire() + self._condition.notify() + self._condition.release() + + def alive(self): + """Check whether the input processing is still active""" + return self._thread.is_alive() + + def wait(self, timeout): + self._condition.wait(timeout) + + def _wait(self): + while not self.has_event: + time.sleep(0.1) + self.has_event = False + + def stop(self): + """Stop asynchronous input processing""" + self._condition.release() + if self.need_event: + self._wait() + self.running = False + self._thread.join() + return self.clean_exit + + def _uuidstr(self, name, button): + return "{}::{}".format(name, button) + + def _uid(self, obj, button): + uid = self.global_id + if obj: + uid = obj.id + return self._uuidstr(uid, button) + + def deregister_callbacks(self, obj): + to_delete = [] + uid = obj.id if obj else self.global_id + for key in self._callbacks: + if uid in key: + to_delete.append(key) + for key in to_delete: + del self._callbacks[key] + + def register_callback(self, obj, button, cmd): + """Register a callback function or system call""" + uid = self._uid(obj, button) + if uid not in self._callbacks: + self._callbacks[uid] = {} + self._callbacks[uid] = cmd + + def callback(self, event): + """Execute callback action for an incoming event""" + button = event["button"] + + cmd = self._callbacks.get(self._uuidstr(self.global_id, button), None) + cmd = self._callbacks.get(self._uuidstr(event["name"], button), cmd) + cmd = self._callbacks.get(self._uuidstr(event["instance"], button), cmd) + + if cmd is None: + return + if callable(cmd): + cmd(event) + else: + bumblebee.util.execute(cmd, False) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/__init__.py b/i3/.config/i3/bar/bumblebee/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/i3/.config/i3/bar/bumblebee/modules/amixer.py b/i3/.config/i3/bar/bumblebee/modules/amixer.py new file mode 100644 index 0000000..fa08ec3 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/amixer.py @@ -0,0 +1,36 @@ +"""get volume level + +""" +import re + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.volume) + ) + self._level = "0" + self._muted = True + device = self.parameter("device", "Master,0") + self._cmdString = "amixer get {}".format(device) + + def volume(self, widget): + m = re.search(r'([\d]+)\%', self._level) + self._muted = True + if m: + if m.group(1) != "0": + self._muted = False + return "{}%".format(m.group(1)) + else: + return "0%" + + def update(self, widgets): + self._level = bumblebee.util.execute(self._cmdString) + + def state(self, widget): + if self._muted: + return [ "warning", "muted" ] + return [ "unmuted" ] diff --git a/i3/.config/i3/bar/bumblebee/modules/battery.py b/i3/.config/i3/bar/bumblebee/modules/battery.py new file mode 100644 index 0000000..f00c603 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/battery.py @@ -0,0 +1,82 @@ +# pylint: disable=C0111,R0903 + +"""Displays battery status, remaining percentage and charging information. + +Parameters: + * battery.device : The device to read information from (defaults to BAT0) + * battery.warning : Warning threshold in % of remaining charge (defaults to 20) + * battery.critical: Critical threshold in % of remaining charge (defaults to 10) +""" + +import os + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.capacity) + ) + battery = self.parameter("device", "BAT0") + self._path = "/sys/class/power_supply/{}".format(battery) + self._capacity = 100 + self._ac = False + + def capacity(self, widget): + if self._ac: + return "ac" + if self._capacity == -1: + return "n/a" + return "{:03d}%".format(self._capacity) + + def update(self, widgets): + widget = widgets[0] + self._ac = False + if not os.path.exists(self._path): + self._ac = True + self._capacity = 100 + return + + try: + with open(self._path + "/capacity") as f: + self._capacity = int(f.read()) + except IOError: + self._capacity = -1 + self._capacity = self._capacity if self._capacity < 100 else 100 + + def state(self, widget): + state = [] + + if self._capacity < 0: + return ["critical", "unknown"] + + if self._capacity < int(self.parameter("critical", 10)): + state.append("critical") + elif self._capacity > int(self.parameter("great", 90)): + state.append("great") + elif self._capacity > int(self.parameter("good", 80)): + state.append("good") + elif self._capacity < int(self.parameter("warning", 20)): + state.append("warning") + elif self._capacity < int(self.parameter("mid", 80)): + state.append("mid") + + if self._ac: + state.append("AC") + else: + charge = "" + with open(self._path + "/status") as f: + charge = f.read().strip() + if charge == "Discharging": + state.append("discharging-{}".format(min([10, 25, 50, 80, 100] , key=lambda i:abs(i-self._capacity)))) + else: + if self._capacity > 95: + state.append("charged") + else: + state.append("charging") + + return state + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/battery.py_ori b/i3/.config/i3/bar/bumblebee/modules/battery.py_ori new file mode 100644 index 0000000..5384716 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/battery.py_ori @@ -0,0 +1,75 @@ +# pylint: disable=C0111,R0903 + +"""Displays battery status, remaining percentage and charging information. + +Parameters: + * battery.device : The device to read information from (defaults to BAT0) + * battery.warning : Warning threshold in % of remaining charge (defaults to 20) + * battery.critical: Critical threshold in % of remaining charge (defaults to 10) +""" + +import os + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.capacity) + ) + battery = self.parameter("device", "BAT0") + self._path = "/sys/class/power_supply/{}".format(battery) + self._capacity = 100 + self._ac = False + + def capacity(self, widget): + if self._ac: + return "ac" + if self._capacity == -1: + return "n/a" + return "{:03d}%".format(self._capacity) + + def update(self, widgets): + self._ac = False + if not os.path.exists(self._path): + self._ac = True + self._capacity = 100 + return + + try: + with open(self._path + "/capacity") as f: + self._capacity = int(f.read()) + except IOError: + self._capacity = -1 + self._capacity = self._capacity if self._capacity < 100 else 100 + + def state(self, widget): + state = [] + + if self._capacity < 0: + return ["critical", "unknown"] + + if self._capacity < int(self.parameter("critical", 10)): + state.append("critical") + elif self._capacity < int(self.parameter("warning", 20)): + state.append("warning") + + if self._ac: + state.append("AC") + else: + charge = "" + with open(self._path + "/status") as f: + charge = f.read().strip() + if charge == "Discharging": + state.append("discharging-{}".format(min([10, 25, 50, 80, 100] , key=lambda i:abs(i-self._capacity)))) + else: + if self._capacity > 95: + state.append("charged") + else: + state.append("charging") + + return state + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/brightness.py b/i3/.config/i3/bar/bumblebee/modules/brightness.py new file mode 100644 index 0000000..716a734 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/brightness.py @@ -0,0 +1,37 @@ +# pylint: disable=C0111,R0903 + +"""Displays the brightness of a display + +Requires the following executable: + * xbacklight + +Parameters: + * brightness.step: The amount of increase/decrease on scroll in % (defaults to 2) + +""" + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.brightness) + ) + self._brightness = 0 + + step = self.parameter("step", 2) + + engine.input.register_callback(self, button=bumblebee.input.WHEEL_UP, + cmd="xbacklight +{}%".format(step)) + engine.input.register_callback(self, button=bumblebee.input.WHEEL_DOWN, + cmd="xbacklight -{}%".format(step)) + + def brightness(self, widget): + return "{:03.0f}%".format(self._brightness) + + def update(self, widgets): + self._brightness = float(bumblebee.util.execute("xbacklight -get")) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/caffeine.py b/i3/.config/i3/bar/bumblebee/modules/caffeine.py new file mode 100644 index 0000000..2354ed0 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/caffeine.py @@ -0,0 +1,48 @@ +# pylint: disable=C0111,R0903 + +"""Enable/disable automatic screen locking. + +Requires the following executables: + * xset + * notify-send +""" + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.caffeine) + ) + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, + cmd=self._toggle + ) + + def caffeine(self, widget): + return "" + + def state(self, widget): + if self._active(): + return "activated" + return "deactivated" + + def _active(self): + for line in bumblebee.util.execute("xset q").split("\n"): + if "timeout" in line: + timeout = int(line.split(" ")[4]) + if timeout == 0: + return True + return False + return False + + def _toggle(self, widget): + if self._active(): + bumblebee.util.execute("xset s default") + bumblebee.util.execute("notify-send \"Out of coffee\"") + else: + bumblebee.util.execute("xset s off") + bumblebee.util.execute("notify-send \"Consuming caffeine\"") + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/cmus.py b/i3/.config/i3/bar/bumblebee/modules/cmus.py new file mode 100644 index 0000000..4bc0e56 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/cmus.py @@ -0,0 +1,92 @@ +# pylint: disable=C0111,R0903 +# -*- coding: utf-8 -*- + +"""Displays information about the current song in cmus. + +Requires the following executable: + * cmus-remote + +Parameters: + * cmus.format: Format string for the song information. Tag values can be put in curly brackets (i.e. {artist}) +""" + +from collections import defaultdict + +import string + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +from bumblebee.output import scrollable + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + widgets = [ + bumblebee.output.Widget(name="cmus.prev"), + bumblebee.output.Widget(name="cmus.main", full_text=self.description), + bumblebee.output.Widget(name="cmus.next"), + bumblebee.output.Widget(name="cmus.shuffle"), + bumblebee.output.Widget(name="cmus.repeat"), + ] + super(Module, self).__init__(engine, config, widgets) + + engine.input.register_callback(widgets[0], button=bumblebee.input.LEFT_MOUSE, + cmd="cmus-remote -r") + engine.input.register_callback(widgets[1], button=bumblebee.input.LEFT_MOUSE, + cmd="cmus-remote -u") + engine.input.register_callback(widgets[2], button=bumblebee.input.LEFT_MOUSE, + cmd="cmus-remote -n") + engine.input.register_callback(widgets[3], button=bumblebee.input.LEFT_MOUSE, + cmd="cmus-remote -S") + engine.input.register_callback(widgets[4], button=bumblebee.input.LEFT_MOUSE, + cmd="cmus-remote -R") + + self._fmt = self.parameter("format", "{artist} - {title} {position}/{duration}") + self._status = None + self._shuffle = False + self._repeat = False + self._tags = defaultdict(lambda: '') + + @scrollable + def description(self, widget): + return string.Formatter().vformat(self._fmt, (), self._tags) + + def update(self, widgets): + self._load_song() + + def state(self, widget): + if widget.name == "cmus.shuffle": + return "shuffle-on" if self._shuffle else "shuffle-off" + if widget.name == "cmus.repeat": + return "repeat-on" if self._repeat else "repeat-off" + if widget.name == "cmus.prev": + return "prev" + if widget.name == "cmus.next": + return "next" + return self._status + + def _load_song(self): + info = "" + try: + info = bumblebee.util.execute("cmus-remote -Q") + except RuntimeError: + pass + self._tags = defaultdict(lambda: '') + for line in info.split("\n"): + if line.startswith("status"): + self._status = line.split(" ", 2)[1] + if line.startswith("tag"): + key, value = line.split(" ", 2)[1:] + self._tags.update({ key: value }) + for key in ["duration", "position"]: + if line.startswith(key): + dur = int(line.split(" ")[1]) + self._tags.update({key:bumblebee.util.durationfmt(dur)}) + if line.startswith("set repeat "): + self._repeat = False if "false" in line else True + if line.startswith("set shuffle "): + self._shuffle = False if "false" in line else True + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/cpu.py b/i3/.config/i3/bar/bumblebee/modules/cpu.py new file mode 100644 index 0000000..6a24bdc --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/cpu.py @@ -0,0 +1,37 @@ +# pylint: disable=C0111,R0903 + +"""Displays CPU utilization across all CPUs. + +Parameters: + * cpu.warning : Warning threshold in % of CPU usage (defaults to 70%) + * cpu.critical: Critical threshold in % of CPU usage (defaults to 80%) +""" + +try: + import psutil +except ImportError: + pass + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.utilization) + ) + self._utilization = psutil.cpu_percent(percpu=False) + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, + cmd="gnome-system-monitor") + + def utilization(self, widget): + return "{:06.02f}%".format(self._utilization) + + def update(self, widgets): + self._utilization = psutil.cpu_percent(percpu=False) + + def state(self, widget): + return self.threshold_state(self._utilization, 70, 80) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/datetime.py b/i3/.config/i3/bar/bumblebee/modules/datetime.py new file mode 100644 index 0000000..4141a53 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/datetime.py @@ -0,0 +1,35 @@ +# pylint: disable=C0111,R0903 + +"""Displays the current date and time. + +Parameters: + * datetime.format: strftime()-compatible formatting string + * date.format : alias for datetime.format + * time.format : alias for datetime.format +""" + +from __future__ import absolute_import +import datetime +import bumblebee.engine + +ALIASES = [ "date", "time" ] + +def default_format(module): + default = "%x %X" + if module == "date": + default = "%x" + if module == "time": + default = "%X" + return default + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.get_time) + ) + self._fmt = self.parameter("format", default_format(self.name)) + + def get_time(self, widget): + return datetime.datetime.now().strftime(self._fmt) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/disk.py b/i3/.config/i3/bar/bumblebee/modules/disk.py new file mode 100644 index 0000000..1f53bda --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/disk.py @@ -0,0 +1,47 @@ +# pylint: disable=C0111,R0903 + +"""Shows free diskspace, total diskspace and the percentage of free disk space. + +Parameters: + * disk.warning: Warning threshold in % of disk space (defaults to 80%) + * disk.critical: Critical threshold in % of disk space (defaults ot 90%) + * disk.path: Path to calculate disk usage from (defaults to /) +""" + +import os + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.diskspace) + ) + self._path = self.parameter("path", "/") + self._perc = 0 + self._used = 0 + self._size = 0 + + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, + cmd="termite -e ranger {}".format(self._path)) + + def diskspace(self, widget): + #return "{} {}/{} ({:05.02f}%)".format(self._path, + #bumblebee.util.bytefmt(self._used), + #bumblebee.util.bytefmt(self._size), self._perc + #) + #return "{} {:05.02f}%".format(self._path, self._perc) + return "{}".format(bumblebee.util.bytefmt(self._size - self._used)) + + def update(self, widgets): + st = os.statvfs(self._path) + self._size = st.f_frsize*st.f_blocks + self._used = self._size - st.f_frsize*st.f_bavail + self._perc = 100.0*self._used/self._size + + def state(self, widget): + return self.threshold_state(self._perc, 90, 95) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/disk.py_ori b/i3/.config/i3/bar/bumblebee/modules/disk.py_ori new file mode 100644 index 0000000..aee40ac --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/disk.py_ori @@ -0,0 +1,45 @@ +# pylint: disable=C0111,R0903 + +"""Shows free diskspace, total diskspace and the percentage of free disk space. + +Parameters: + * disk.warning: Warning threshold in % of disk space (defaults to 80%) + * disk.critical: Critical threshold in % of disk space (defaults ot 90%) + * disk.path: Path to calculate disk usage from (defaults to /) +""" + +import os + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.diskspace) + ) + self._path = self.parameter("path", "/") + self._perc = 0 + self._used = 0 + self._size = 0 + + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, + cmd="nautilus {}".format(self._path)) + + def diskspace(self, widget): + return "{} {}/{} ({:05.02f}%)".format(self._path, + bumblebee.util.bytefmt(self._used), + bumblebee.util.bytefmt(self._size), self._perc + ) + + def update(self, widgets): + st = os.statvfs(self._path) + self._size = st.f_blocks * st.f_frsize + self._used = (st.f_blocks - st.f_bfree) * st.f_frsize + self._perc = 100.0*self._used/self._size + + def state(self, widget): + return self.threshold_state(self._perc, 80, 90) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/dnf.py b/i3/.config/i3/bar/bumblebee/modules/dnf.py new file mode 100644 index 0000000..f36dfa9 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/dnf.py @@ -0,0 +1,80 @@ +# pylint: disable=C0111,R0903 + +"""Displays DNF package update information (///) + +Requires the following executable: + * dnf + +Parameters: + * dnf.interval: Time in seconds between two consecutive update checks (defaulst to 1800) + +""" + +import time +import threading + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +def get_dnf_info(widget): + try: + res = bumblebee.util.execute("dnf updateinfo") + except RuntimeError: + pass + + security = 0 + bugfixes = 0 + enhancements = 0 + other = 0 + for line in res.split("\n"): + + if not line.startswith(" "): continue + elif "ecurity" in line: + for s in line.split(): + if s.isdigit(): security += int(s) + elif "ugfix" in line: + for s in line.split(): + if s.isdigit(): bugfixes += int(s) + elif "hancement" in line: + for s in line.split(): + if s.isdigit(): enhancements += int(s) + else: + for s in line.split(): + if s.isdigit(): other += int(s) + + widget.set("security", security) + widget.set("bugfixes", bugfixes) + widget.set("enhancements", enhancements) + widget.set("other", other) + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + widget = bumblebee.output.Widget(full_text=self.updates) + super(Module, self).__init__(engine, config, widget) + self._next_check = 0 + + def updates(self, widget): + result = [] + for t in ["security", "bugfixes", "enhancements", "other"]: + result.append(str(widget.get(t, 0))) + return "/".join(result) + + def update(self, widgets): + if int(time.time()) < self._next_check: + return + thread = threading.Thread(target=get_dnf_info, args=(widgets[0],)) + thread.start() + self._next_check = int(time.time()) + self.parameter("interval", 30*60) + + def state(self, widget): + cnt = 0 + for t in ["security", "bugfixes", "enhancements", "other"]: + cnt += widget.get(t, 0) + if cnt == 0: + return "good" + if cnt > 50 or widget.get("security", 0) > 0: + return "critical" + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/error.py b/i3/.config/i3/bar/bumblebee/modules/error.py new file mode 100644 index 0000000..16d3834 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/error.py @@ -0,0 +1,26 @@ +# pylint: disable=C0111,R0903 + +"""Draws an error widget. +""" + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.text) + ) + self._text = "" + + def set(self, text): + self._text = text + + def text(self, widget): + return self._text + + def state(self, widget): + return ["critical"] + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/gpmdp.py b/i3/.config/i3/bar/bumblebee/modules/gpmdp.py new file mode 100644 index 0000000..36988e8 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/gpmdp.py @@ -0,0 +1,58 @@ +# pylint: disable=C0111,R0903 + +"""Displays information about the current song in Google Play music player. + +Requires the following executable: + * gpmdp-remote +""" + +import string + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + widgets = [ + bumblebee.output.Widget(name="gpmdp.prev"), + bumblebee.output.Widget(name="gpmdp.main", full_text=self.description), + bumblebee.output.Widget(name="gpmdp.next"), + ] + super(Module, self).__init__(engine, config, widgets) + + engine.input.register_callback(widgets[0], button=bumblebee.input.LEFT_MOUSE, + cmd="playerctl previous") + engine.input.register_callback(widgets[1], button=bumblebee.input.LEFT_MOUSE, + cmd="playerctl play-pause") + engine.input.register_callback(widgets[2], button=bumblebee.input.LEFT_MOUSE, + cmd="playerctl next") + + self._status = None + self._tags = None + + def description(self, widget): + return self._tags if self._tags else "n/a" + + def update(self, widgets): + self._load_song() + + def state(self, widget): + if widget.name == "gpmdp.prev": + return "prev" + if widget.name == "gpmdp.next": + return "next" + return self._status + + def _load_song(self): + info = "" + try: + info = bumblebee.util.execute("gpmdp-remote current") + status = bumblebee.util.execute("gpmdp-remote status") + except RuntimeError: + pass + self._status = status.split("\n")[0].lower() + self._tags = info.split("\n")[0] + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/kernel.py b/i3/.config/i3/bar/bumblebee/modules/kernel.py new file mode 100644 index 0000000..761fa8c --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/kernel.py @@ -0,0 +1,21 @@ +# pylint: disable=C0111,R0903 + +"""Shows Linux kernel version information""" + +import platform + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.output) + ) + self._release_name = platform.release() + + def output(self, widget): + return self._release_name + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/layout.py b/i3/.config/i3/bar/bumblebee/modules/layout.py new file mode 100644 index 0000000..d9daadb --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/layout.py @@ -0,0 +1,74 @@ +# pylint: disable=C0111,R0903 + +"""Displays and changes the current keyboard layout + +Requires the following executable: + * setxkbmap + +Parameters: + * layout.lang: pipe-separated list of languages to cycle through (e.g. us|rs|de). Default: en +""" + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.layout) + ) + self._languages = self.parameter("lang", "us").split("|") + self._idx = 0 + + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, + cmd=self._next_keymap) + engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, + cmd=self._prev_keymap) + + def _next_keymap(self, event): + self._idx = (self._idx + 1) % len(self._languages) + self._set_keymap() + + def _prev_keymap(self, event): + self._idx = self._idx - 1 if self._idx > 0 else len(self._languages) - 1 + self._set_keymap() + + def _set_keymap(self): + tmp = self._languages[self._idx].split(":") + layout = tmp[0] + variant = "" + if len(tmp) > 1: + variant = "-variant {}".format(tmp[1]) + try: + bumblebee.util.execute("setxkbmap -layout {} {}".format(layout, variant)) + except RuntimeError: + pass + + def layout(self, widget): + try: + res = bumblebee.util.execute("setxkbmap -query") + except RuntimeError: + return "n/a" + layout = "" + variant = None + for line in res.split("\n"): + if not line: + continue + if "layout" in line: + layout = line.split(":")[1].strip() + if "variant" in line: + variant = line.split(":")[1].strip() + if variant: + layout += ":" + variant + + if layout in self._languages: + self._idx = self._languages.index(layout) + else: + self._languages.append(layout) + self._idx = len(self._languages) - 1 + + return layout + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/load.py b/i3/.config/i3/bar/bumblebee/modules/load.py new file mode 100644 index 0000000..4d94ee1 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/load.py @@ -0,0 +1,41 @@ +# pylint: disable=C0111,R0903 + +"""Displays system load. + +Parameters: + * load.warning : Warning threshold for the one-minute load average (defaults to 70% of the number of CPUs) + * load.critical: Critical threshold for the one-minute load average (defaults to 80% of the number of CPUs) +""" + +import os +import multiprocessing + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.load) + ) + self._load = [0, 0, 0] + try: + self._cpus = multiprocessing.cpu_count() + except NotImplementedError as e: + self._cpus = 1 + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, + cmd="gnome-system-monitor") + + def load(self, widget): + return "{:.02f}/{:.02f}/{:.02f}".format( + self._load[0], self._load[1], self._load[2] + ) + + def update(self, widgets): + self._load = os.getloadavg() + + def state(self, widget): + return self.threshold_state(self._load[0], self._cpus*0.7, self._cpus*0.8) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/memory.py b/i3/.config/i3/bar/bumblebee/modules/memory.py new file mode 100644 index 0000000..0534f7d --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/memory.py @@ -0,0 +1,47 @@ +# pylint: disable=C0111,R0903 + +"""Displays available RAM, total amount of RAM and percentage available. + +Parameters: + * cpu.warning : Warning threshold in % of memory used (defaults to 80%) + * cpu.critical: Critical threshold in % of memory used (defaults to 90%) +""" + +try: + import psutil +except ImportError: + pass + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.memory_usage) + ) + self._mem = psutil.virtual_memory() + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, + cmd="gnome-system-monitor") + + def memory_usage(self, widget): + used = self._mem.total - self._mem.available + return "{}/{} ({:05.02f}%)".format( + bumblebee.util.bytefmt(used), + bumblebee.util.bytefmt(self._mem.total), + self._mem.percent + ) + + def update(self, widgets): + self._mem = psutil.virtual_memory() + + def state(self, widget): + if self._mem.percent > float(self.parameter("critical", 90)): + return "critical" + if self._mem.percent > float(self.parameter("warning", 80)): + return "warning" + return None + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/nic.py b/i3/.config/i3/bar/bumblebee/modules/nic.py new file mode 100644 index 0000000..4fd8e55 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/nic.py @@ -0,0 +1,83 @@ +#pylint: disable=C0111,R0903 + +"""Displays the name, IP address(es) and status of each available network interface. + +Parameters: + * nic.exclude: Comma-separated list of interface prefixes to exclude (defaults to "lo,virbr,docker,vboxnet,veth") +""" + +import netifaces + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + widgets = [] + super(Module, self).__init__(engine, config, widgets) + self._exclude = tuple(filter(len, self.parameter("exclude", "lo,virbr,docker,vboxnet,veth").split(","))) + self._update_widgets(widgets) + + def update(self, widgets): + self._update_widgets(widgets) + + def state(self, widget): + states = [] + + if widget.get("state") == "down": + states.append("critical") + elif widget.get("state") != "up": + states.append("warning") + else: + states.append("good") + + intf = widget.get("intf") + iftype = "wireless" if self._iswlan(intf) else "wired" + iftype = "tunnel" if self._istunnel(intf) else iftype + + states.append("{}-{}".format(iftype, widget.get("state"))) + + return states + + def _iswlan(self, intf): + # wifi, wlan, wlp, seems to work for me + if intf.startswith("w"): return True + return False + + def _istunnel(self, intf): + return intf.startswith("tun") + + def _update_widgets(self, widgets): + interfaces = [ i for i in netifaces.interfaces() if not i.startswith(self._exclude) ] + + for widget in widgets: + widget.set("visited", False) + + for intf in interfaces: + addr = [] + state = "down" + try: + if netifaces.AF_INET in netifaces.ifaddresses(intf): + for ip in netifaces.ifaddresses(intf)[netifaces.AF_INET]: + if "addr" in ip and ip["addr"] != "": + addr.append(ip["addr"]) + state = "up" + except Exception as e: + addr = [] + widget = self.widget(intf) + if not widget: + widget = bumblebee.output.Widget(name=intf) + widgets.append(widget) + widget.full_text("{}".format(",".join(addr))) + #widget.full_text("{} {} {}".format(intf, state, ", ".join(addr))) + widget.set("intf", intf) + widget.set("state", state) + widget.set("visited", True) + + for widget in widgets: + if widget.get("visited") == False: + widgets.remove(widget) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/nic.py_ori b/i3/.config/i3/bar/bumblebee/modules/nic.py_ori new file mode 100644 index 0000000..2ca87a7 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/nic.py_ori @@ -0,0 +1,88 @@ +#pylint: disable=C0111,R0903 + +"""Displays the name, IP address(es) and status of each available network interface. + +Parameters: + * nic.exclude: Comma-separated list of interface prefixes to exclude (defaults to "lo,virbr,docker,vboxnet,veth") +""" + +try: + import netifaces +except ImportError: + pass + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + widgets = [] + super(Module, self).__init__(engine, config, widgets) + self._exclude = tuple(filter(len, self.parameter("exclude", "lo,virbr,docker,vboxnet,veth").split(","))) + self._update_widgets(widgets) + + def update(self, widgets): + self._update_widgets(widgets) + + def state(self, widget): + states = [] + + if widget.get("state") == "down": + states.append("critical") + elif widget.get("state") != "up": + states.append("warning") + + intf = widget.get("intf") + iftype = "wireless" if self._iswlan(intf) else "wired" + iftype = "tunnel" if self._istunnel(intf) else iftype + + states.append("{}-{}".format(iftype, widget.get("state"))) + + return states + + def _iswlan(self, intf): + # wifi, wlan, wlp, seems to work for me + if intf.startswith("w"): return True + return False + + def _istunnel(self, intf): + return intf.startswith("tun") + + def get_addresses(self, intf): + retval = [] + try: + for ip in netifaces.ifaddresses(intf).get(netifaces.AF_INET, []): + if ip.get("addr", "") != "": + retval.append(ip.get("addr")) + except Exception: + return [] + return retval + + def _update_widgets(self, widgets): + interfaces = [ i for i in netifaces.interfaces() if not i.startswith(self._exclude) ] + + for widget in widgets: + widget.set("visited", False) + + for intf in interfaces: + addr = [] + state = "down" + for ip in self.get_addresses(intf): + addr.append(ip) + state = "up" + widget = self.widget(intf) + if not widget: + widget = bumblebee.output.Widget(name=intf) + widgets.append(widget) + widget.full_text("{} {} {}".format(intf, state, ", ".join(addr))) + widget.set("intf", intf) + widget.set("state", state) + widget.set("visited", True) + + for widget in widgets: + if widget.get("visited") == False: + widgets.remove(widget) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/pacman.py b/i3/.config/i3/bar/bumblebee/modules/pacman.py new file mode 100644 index 0000000..a7780a8 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/pacman.py @@ -0,0 +1,70 @@ +# pylint: disable=C0111,R0903 + +"""Displays update information per repository for pacman." + +Requires the following executables: + * fakeroot + * pacman +""" + +import os +import threading + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +#list of repositories. +#the last one sould always be other +repos = ["core", "extra", "community", "multilib", "testing", "other"] + +def get_pacman_info(widget, path): + try: + result = bumblebee.util.execute("{}/../../bin/pacman-updates".format(path)) + except: + pass + + count = len(repos)*[0] + + for line in result.splitlines(): + if line.startswith(("http", "rsync")): + for i in range(len(repos)-1): + if "/" + repos[i] + "/" in line: + count[i] += 1 + break + else: + result[-1] += 1 + + for i in range(len(repos)): + widget.set(repos[i], count[i]) + + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.updates) + ) + self._count = 0 + + def updates(self, widget): + return '/'.join(map(lambda x: str(widget.get(x,0)), repos)) + + def update(self, widgets): + path = os.path.dirname(os.path.abspath(__file__)) + if self._count == 0: + thread = threading.Thread(target=get_pacman_info, args=(widgets[0],path)) + thread.start() + + # TODO: improve this waiting mechanism a bit + self._count += 1 + self._count = 0 if self._count > 300 else self._count + + def state(self, widget): + weightedCount = sum(map(lambda x: (len(repos)-x[0]) * widget.get(x[1],0), enumerate(repos))) + + if weightedCount < 10: + return "good" + + return self.threshold_state(weightedCount, 100, 150) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/ping.py b/i3/.config/i3/bar/bumblebee/modules/ping.py new file mode 100644 index 0000000..469871a --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/ping.py @@ -0,0 +1,77 @@ +# pylint: disable=C0111,R0903 + +"""Periodically checks the RTT of a configurable host using ICMP echos + +Requires the following executable: + * ping + +Parameters: + * ping.interval: Time in seconds between two RTT checks (defaults to 60) + * ping.address : IP address to check + * ping.timeout : Timeout for waiting for a reply (defaults to 5.0) + * ping.probes : Number of probes to send (defaults to 5) + * ping.warning : Threshold for warning state, in seconds (defaults to 1.0) + * ping.critical: Threshold for critical state, in seconds (defaults to 2.0) +""" + +import re +import time +import threading + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +def get_rtt(module, widget): + try: + widget.set("rtt-unreachable", False) + res = bumblebee.util.execute("ping -n -q -c {} -W {} {}".format( + widget.get("rtt-probes"), widget.get("rtt-timeout"), widget.get("address") + )) + + for line in res.split("\n"): + if not line.startswith("rtt"): continue + m = re.search(r'([0-9\.]+)/([0-9\.]+)/([0-9\.]+)/([0-9\.]+)\s+(\S+)', line) + + widget.set("rtt-min", float(m.group(1))) + widget.set("rtt-avg", float(m.group(2))) + widget.set("rtt-max", float(m.group(3))) + widget.set("rtt-unit", m.group(5)) + except Exception as e: + widget.set("rtt-unreachable", True) + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + widget = bumblebee.output.Widget(full_text=self.rtt) + super(Module, self).__init__(engine, config, widget) + + widget.set("address", self.parameter("address", "8.8.8.8")) + widget.set("interval", self.parameter("interval", 60)) + widget.set("rtt-probes", self.parameter("probes", 5)) + widget.set("rtt-timeout", self.parameter("timeout", 5.0)) + widget.set("rtt-avg", 0.0) + widget.set("rtt-unit", "") + + self._next_check = 0 + + def rtt(self, widget): + if widget.get("rtt-unreachable"): + return "{}: unreachable".format(widget.get("address")) + return "{}: {:.1f}{}".format( + widget.get("address"), + widget.get("rtt-avg"), + widget.get("rtt-unit") + ) + + def state(self, widget): + if widget.get("rtt-unreachable"): return ["critical"] + return self.threshold_state(widget.get("rtt-avg"), 1000.0, 2000.0) + + def update(self, widgets): + if int(time.time()) < self._next_check: + return + thread = threading.Thread(target=get_rtt, args=(self,widgets[0],)) + thread.start() + self._next_check = int(time.time()) + int(widgets[0].get("interval")) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/pulseaudio.py b/i3/.config/i3/bar/bumblebee/modules/pulseaudio.py new file mode 100644 index 0000000..0bf88f4 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/pulseaudio.py @@ -0,0 +1,107 @@ +# pylint: disable=C0111,R0903 + +"""Displays volume and mute status of PulseAudio devices. + +Aliases: pasink, pasource + +Requires the following executable: + * pactl +""" + +import re + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +ALIASES = [ "pasink", "pasource" ] + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.volume) + ) + + self._left = 0 + self._right = 0 + self._mono = 0 + self._mute = False + channel = "sink" if self.name == "pasink" else "source" + + self._patterns = [ + { "expr": "Name:", "callback": (lambda line: False) }, + { "expr": "Mute:", "callback": (lambda line: self.mute(False if " no" in line.lower() else True)) }, + { "expr": "Volume:", "callback": self.getvolume }, + ] + + engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd="pavucontrol") + + events = [ + { "type": "mute", "action": "toggle", "button": bumblebee.input.LEFT_MOUSE }, + { "type": "volume", "action": "+2%", "button": bumblebee.input.WHEEL_UP }, + { "type": "volume", "action": "-2%", "button": bumblebee.input.WHEEL_DOWN }, + ] + + for event in events: + engine.input.register_callback(self, button=event["button"], + cmd="pactl set-{}-{} @DEFAULT_{}@ {}".format(channel, event["type"], + channel.upper(), event["action"])) + + def mute(self, value): + self._mute = value + + def getvolume(self, line): + if "mono" in line: + m = re.search(r'mono:.*\s*\/\s*(\d+)%', line) + if m: + self._mono = m.group(1) + else: + m = re.search(r'left:.*\s*\/\s*(\d+)%.*right:.*\s*\/\s*(\d+)%', line) + if m: + self._left = m.group(1) + self._right = m.group(2) + return True + + def _default_device(self): + output = bumblebee.util.execute("pactl info") + pattern = "Default Sink: " if self.name == "pasink" else "Default Source: " + for line in output.split("\n"): + if line.startswith(pattern): + return line.replace(pattern, "") + return "n/a" + + def volume(self, widget): + if int(self._mono) > 0: + return "{}%".format(self._mono) + elif self._left == self._right: + return "{}%".format(self._left) + else: + return "{}%/{}%".format(self._left, self._right) + return "n/a" + + def update(self, widgets): + channel = "sinks" if self.name == "pasink" else "sources" + device = self._default_device() + + result = bumblebee.util.execute("pactl list {}".format(channel)) + found = False + + for line in result.split("\n"): + if device in line: + found = True + continue + if found == False: + continue + for pattern in self._patterns: + if not pattern["expr"] in line: + continue + if pattern["callback"](line) == False and found == True: + return + + def state(self, widget): + if self._mute: + return [ "warning", "muted" ] + return [ "unmuted" ] + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/redshift.py b/i3/.config/i3/bar/bumblebee/modules/redshift.py new file mode 100644 index 0000000..2fd1dfb --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/redshift.py @@ -0,0 +1,46 @@ +# pylint: disable=C0111,R0903 + +"""Displays the current color temperature of redshift + +Requires the following executable: + * redshift +""" + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.text) + ) + self._text = "" + self._state = "transition" + + def text(self, widget): + return "{}".format(self._text) + + def update(self, widgets): + result = bumblebee.util.execute("redshift -p") + + temp = "" + transition = "" + for line in result.split("\n"): + if "temperature" in line.lower(): + temp = line.split(" ")[2] + if "period" in line.lower(): + state = line.split(" ")[1].lower() + if "day" in state: + self._state = "day" + elif "night" in state: + self._state = "night" + else: + self._state = "transition" + transition = " ".join(line.split(" ")[2:]) + self._text = "{} {}".format(temp, transition) + + def state(self, widget): + return self._state + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/sensors.py b/i3/.config/i3/bar/bumblebee/modules/sensors.py new file mode 100644 index 0000000..36dabbd --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/sensors.py @@ -0,0 +1,45 @@ +"""Displays sensor temperature + +Requires the following executable: + * sensors + +Parameters: + * sensors.match: What line in the output of `sensors -u` should be matched against (default: temp1_input) + * sensors.match_number: which of the matches you want (default -1: last match). +""" + +import os +import re + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.temperature) + ) + self._temperature = "unknown" + pattern = self.parameter("match", "temp1_input") + pattern_string = r"^\s*{}:\s*([\d.]+)$".format(pattern) + self._match_number = int(self.parameter("match_number", "-1")) + self._pattern = re.compile(pattern_string, re.MULTILINE) + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd="xsensors") + + def get_temp(self): + temperatures = bumblebee.util.execute("sensors -u") + matching_temp = self._pattern.findall(temperatures) + temperature = "unknown" + if matching_temp: + temperature = int(float(matching_temp[self._match_number])) + + return temperature + + def temperature(self, widget): + return self._temperature + + def update(self, widgets): + self._temperature = self.get_temp() + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/spacer.py b/i3/.config/i3/bar/bumblebee/modules/spacer.py new file mode 100644 index 0000000..b89c233 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/spacer.py @@ -0,0 +1,23 @@ +# pylint: disable=C0111,R0903 + +"""Draws a widget with configurable text content. + +Parameters: + * spacer.text: Widget contents (defaults to empty string) +""" + +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.text) + ) + self._text = self.parameter("text", "") + + def text(self, widget): + return self._text + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/stock.py b/i3/.config/i3/bar/bumblebee/modules/stock.py new file mode 100644 index 0000000..2297c3a --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/stock.py @@ -0,0 +1,62 @@ +# -*- coding: UTF-8 -*- +# pylint: disable=C0111,R0903 + +"""Display a stock quote from yahoo finance. + +Requires the following python packages: + * requests + +Parameters: + * stock.symbols : Comma-separated list of symbols to fetch + * stock.change : Should we fetch change in stock value (defaults to True) + * stock.currencies : List of symbols to go with the values (default $) +""" + +import bumblebee.input +import bumblebee.output +import bumblebee.engine +import requests +from requests.exceptions import RequestException + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.value) + ) + self._symbols = self.parameter("symbols", "") + self._change = self.parameter("change", True) + self._currencies = self.parameter('currencies', None) + self._baseurl = 'http://download.finance.yahoo.com/d/quotes.csv' + self._value = self.fetch() + + if not self._currencies: + self._currencies = '$' * len(self._symbols) + + # The currencies could be unicode, like the € symbol. Convert to a unicode object. + if hasattr(self._currencies, "decode"): + self._currencies = self._currencies.decode("utf-8", "ignore") + + def value(self, widget): + results = [] + for i, val in enumerate(self._value.split('\n')): + try: + currency_symbol = self._currencies[i] + except: + currency_symbol = '$' + results.append('%s%s' % (currency_symbol, val)) + return u' '.join(results) + + def fetch(self): + if self._symbols: + url = self._baseurl + url += '?s=%s&f=l1' % self._symbols + if self._change: + url += 'c1' + return requests.get(url).text.strip() + else: + return '' + + def update(self, widgets): + self._value = self.fetch() + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/test.py b/i3/.config/i3/bar/bumblebee/modules/test.py new file mode 100644 index 0000000..e4099c6 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/test.py @@ -0,0 +1,16 @@ +# pylint: disable=C0111,R0903 + +"""Test module +""" + +import bumblebee.engine + +ALIASES = [ "test-alias" ] + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text="test") + ) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/traffic.py b/i3/.config/i3/bar/bumblebee/modules/traffic.py new file mode 100644 index 0000000..0da9fd1 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/traffic.py @@ -0,0 +1,72 @@ +# pylint: disable=C0111,R0903 + +"""Displays network IO for interfaces. + +Parameters: + * traffic.exclude: Comma-separated list of interface prefixes to exclude (defaults to "lo,virbr,docker,vboxnet,veth") +""" + +import re +import psutil +import netifaces + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + widgets = [] + super(Module, self).__init__(engine, config, widgets) + self._exclude = tuple(filter(len, self.parameter("exclude", "lo,virbr,docker,vboxnet,veth").split(","))) + self._update_widgets(widgets) + self._status = "" + + def state(self, widget): + if "traffic.rx" in widget.name: + return "rx" + if "traffic.tx" in widget.name: + return "tx" + return self._status + + def update(self, widgets): + self._update_widgets(widgets) + + def create_widget(self, widgets, name, txt=None, attributes={}): + widget = self.widget(name) + if widget: return widget + + widget = bumblebee.output.Widget(name=name) + widget.full_text(txt) + widgets.append(widget) + + for key in attributes: + widget.set(key, attributes[key]) + + return widget + + def _update_widgets(self, widgets): + interfaces = [ i for i in netifaces.interfaces() if not i.startswith(self._exclude) ] + + counters = psutil.net_io_counters(pernic=True) + for interface in interfaces: + if not interface: interface = "lo" + data = { + "rx": counters[interface].bytes_recv, + "tx": counters[interface].bytes_sent, + } + + name = "traffic-{}".format(interface) + + self.create_widget(widgets, name, interface) + + for direction in ["rx", "tx"]: + name = "traffic.{}-{}".format(direction, interface) + widget = self.create_widget(widgets, name, attributes={"theme.minwidth": "1000.00MB"}) + prev = widget.get(direction, 0) + speed = bumblebee.util.bytefmt(int(data[direction]) - int(prev)) + widget.full_text(speed) + widget.set(direction, data[direction]) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/weather.py b/i3/.config/i3/bar/bumblebee/modules/weather.py new file mode 100644 index 0000000..683f61d --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/weather.py @@ -0,0 +1,74 @@ +# -*- coding: UTF-8 -*- +# pylint: disable=C0111,R0903 + +"""Displays the temperature on the current location based on the ip + +Requires the following python packages: + * requests + +Parameters: + * weather.interval: Interval (in minutes) for updating weather information + * weather.location: Set location (ISO 3166 country code), defaults to 'auto' for getting location from http://ipinfo.io + * weather.unit: metric (default), kelvin, imperial + * weather.apikey: API key from http://api.openweathermap.org +""" + +import bumblebee.input +import bumblebee.output +import bumblebee.engine +import json +import time +try: + import requests + from requests.exceptions import RequestException +except ImportError: + pass + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, + bumblebee.output.Widget(full_text=self.temperature) + ) + self._temperature = 0 + self._apikey = self.parameter("apikey", "af7bfe22287c652d032a3064ffa44088") + self._location = self.parameter("location", "auto") + self._interval = int(self.parameter("interval", "15")) + self._unit = self.parameter("unit", "metric") + self._nextcheck = 0 + self._valid = False + + def _unit_suffix(self): + if self._unit == "metric": + return "C" + if self._unit == "kelvin": + return "K" + if self._unit == "imperial": + return "F" + return "" + + def temperature(self, widget): + if not self._valid: + return u"?" + return u"{}°{}".format(self._temperature, self._unit_suffix()) + + def update(self, widgets): + timestamp = int(time.time()) + if self._nextcheck < int(time.time()): + try: + self._nextcheck = int(time.time()) + self._interval*60 + weather_url = "http://api.openweathermap.org/data/2.5/weather?appid={}".format(self._apikey) + weather_url = "{}&units={}".format(weather_url, self._unit) + if self._location == "auto": + location_url = "http://ipinfo.io/json" + location = json.loads(requests.get(location_url).text) + coord = location["loc"].split(",") + weather_url = "{url}&lat={lat}&lon={lon}".format(url=weather_url, lat=coord[0], lon=coord[1]) + else: + weather_url = "{url}&q={city}".format(url=weather_url, city=self._location) + weather = json.loads(requests.get(weather_url).text) + self._temperature = int(weather['main']['temp']) + self._valid = True + except RequestException: + self._valid = False + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/modules/xrandr.py b/i3/.config/i3/bar/bumblebee/modules/xrandr.py new file mode 100644 index 0000000..a1db224 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/modules/xrandr.py @@ -0,0 +1,74 @@ +# pylint: disable=C0111,R0903 + +"""Shows a widget for each connected screen and allows the user to enable/disable screens. + +Requires the following executable: + * xrandr +""" + +import os +import re +import sys + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + widgets = [] + self._engine = engine + super(Module, self).__init__(engine, config, widgets) + self.update_widgets(widgets) + + def update_widgets(self, widgets): + new_widgets = [] + for line in bumblebee.util.execute("xrandr -q").split("\n"): + if not " connected" in line: + continue + display = line.split(" ", 2)[0] + m = re.search(r'\d+x\d+\+(\d+)\+\d+', line) + + widget = self.widget(display) + if not widget: + widget = bumblebee.output.Widget(full_text=display, name=display) + self._engine.input.register_callback(widget, button=1, cmd=self._toggle) + self._engine.input.register_callback(widget, button=3, cmd=self._toggle) + new_widgets.append(widget) + widget.set("state", "on" if m else "off") + widget.set("pos", int(m.group(1)) if m else sys.maxsize) + + while len(widgets) > 0: + del widgets[0] + for widget in new_widgets: + widgets.append(widget) + + def update(self, widgets): + self.update_widgets(widgets) + + def state(self, widget): + return widget.get("state", "off") + + def _toggle(self, event): + path = os.path.dirname(os.path.abspath(__file__)) + toggle_cmd = "{}/../../bin/toggle-display.sh".format(path) + + widget = self.widget_by_id(event["instance"]) + + if widget.get("state") == "on": + bumblebee.util.execute("{} --output {} --off".format(toggle_cmd, widget.name)) + else: + first_neighbor = next((widget for widget in self.widgets() if widget.get("state") == "on"), None) + last_neighbor = next((widget for widget in reversed(self.widgets()) if widget.get("state") == "on"), None) + + neighbor = first_neighbor if event["button"] == bumblebee.input.LEFT_MOUSE else last_neighbor + + if neighbor == None: + bumblebee.util.execute("{} --output {} --auto".format(toggle_cmd, widget.name)) + else: + bumblebee.util.execute("{} --output {} --auto --{}-of {}".format(toggle_cmd, widget.name, + "left" if event.get("button") == bumblebee.input.LEFT_MOUSE else "right", + neighbor.name)) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/output.py b/i3/.config/i3/bar/bumblebee/output.py new file mode 100644 index 0000000..992eec6 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/output.py @@ -0,0 +1,140 @@ +# pylint: disable=R0201 + +"""Output classes""" + +import sys +import json +import uuid + +import bumblebee.store + +def scrollable(func): + def wrapper(module, widget): + text = func(module, widget) + width = widget.get("theme.width", module.parameter("width", 30)) + widget.set("theme.minwidth", "A"*width) + if len(text) <= width: + return text + # we need to shorten + start = widget.get("scrolling.start", -1) + direction = widget.get("scrolling.direction", "right") + start += 1 if direction == "right" else -1 + widget.set("scrolling.start", start) + if width + start >= len(text): + widget.set("scrolling.direction", "left") + if start <= 0: + widget.set("scrolling.direction", "right") + text = text[start:width+start] + + return text + return wrapper + +class Widget(bumblebee.store.Store): + """Represents a single visible block in the status bar""" + def __init__(self, full_text="", name=""): + super(Widget, self).__init__() + self._full_text = full_text + self.module = None + self._module = None + self.name = name + self.id = str(uuid.uuid4()) + + def link_module(self, module): + """Set the module that spawned this widget + + This is done outside the constructor to avoid having to + pass in the module name in every concrete module implementation""" + self.module = module.name + self._module = module + + def cls(self): + if not self._module: + return None + return self._module.__module__.replace("bumblebee.modules.", "") + + def state(self): + """Return the widget's state""" + if self._module and hasattr(self._module, "state"): + states = self._module.state(self) + if not isinstance(states, list): + return [states] + return states + return [] + + def full_text(self, value=None): + """Set or retrieve the full text to display in the widget""" + if value: + self._full_text = value + else: + if callable(self._full_text): + return self._full_text(self) + else: + return self._full_text + +class I3BarOutput(object): + """Manage output according to the i3bar protocol""" + def __init__(self, theme): + self._theme = theme + self._widgets = [] + self._started = False + + def started(self): + return self._started + + def start(self): + """Print start preamble for i3bar protocol""" + self._started = True + sys.stdout.write(json.dumps({"version": 1, "click_events": True}) + "\n[\n") + + def stop(self): + """Finish i3bar protocol""" + sys.stdout.write("]\n") + + def draw(self, widget, module=None, engine=None): + """Draw a single widget""" + full_text = widget.full_text() + padding = self._theme.padding(widget) + prefix = self._theme.prefix(widget, padding) + suffix = self._theme.suffix(widget, padding) + minwidth = self._theme.minwidth(widget) + if prefix: + full_text = u"{}{}".format(prefix, full_text) + if suffix: + full_text = u"{}{}".format(full_text, suffix) + separator = self._theme.separator(widget) + if separator: + self._widgets.append({ + u"full_text": separator, + "separator": False, + "color": self._theme.separator_fg(widget), + "background": self._theme.separator_bg(widget), + "separator_block_width": self._theme.separator_block_width(widget), + }) + width = self._theme.minwidth(widget) + self._widgets.append({ + u"full_text": full_text, + "color": self._theme.fg(widget), + "background": self._theme.bg(widget), + "separator_block_width": self._theme.separator_block_width(widget), + "separator": True if separator is None else False, + "min_width": width + "A"*(len(prefix) + len(suffix)) if width else None, + "align": self._theme.align(widget), + "instance": widget.id, + "name": module.id, + }) + + def begin(self): + """Start one output iteration""" + self._widgets = [] + self._theme.reset() + + def flush(self): + """Flushes output""" + sys.stdout.write(json.dumps(self._widgets)) + + def end(self): + """Finalizes output""" + sys.stdout.write(",\n") + sys.stdout.flush() + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/store.py b/i3/.config/i3/bar/bumblebee/store.py new file mode 100644 index 0000000..8fac71b --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/store.py @@ -0,0 +1,21 @@ +"""Store interface + +Allows arbitrary classes to offer a simple get/set +store interface by deriving from the Store class in +this module +""" + +class Store(object): + """Interface for storing and retrieving simple values""" + def __init__(self): + self._data = {} + + def set(self, key, value): + """Set 'key' to 'value', overwriting 'key' if it exists already""" + self._data[key] = value + + def get(self, key, default=None): + """Return the current value of 'key', or 'default' if 'key' is not set""" + return self._data.get(key, default) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/theme.py b/i3/.config/i3/bar/bumblebee/theme.py new file mode 100644 index 0000000..e16fbb6 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/theme.py @@ -0,0 +1,180 @@ +# pylint: disable=C0103 + +"""Theme support""" + +import os +import glob +import copy +import json +import io + +import bumblebee.error + +def theme_path(): + """Return the path of the theme directory""" + return os.path.dirname("{}/../themes/".format(os.path.dirname(os.path.realpath(__file__)))) + +def themes(): + result = [] + + for filename in glob.iglob("{}/*.json".format(theme_path())): + if "test" not in filename: + result.append(os.path.basename(filename).replace(".json", "")) + return result + +class Theme(object): + """Represents a collection of icons and colors""" + def __init__(self, name): + self._init(self.load(name)) + self._widget = None + self._cycle_idx = 0 + self._cycle = {} + self._prevbg = None + + def _init(self, data): + """Initialize theme from data structure""" + for iconset in data.get("icons", []): + self._merge(data, self._load_icons(iconset)) + self._theme = data + self._defaults = data.get("defaults", {}) + self._cycles = self._theme.get("cycle", []) + self.reset() + + def data(self): + """Return the raw theme data""" + return self._theme + + def reset(self): + """Reset theme to initial state""" + self._cycle = self._cycles[0] if len(self._cycles) > 0 else {} + self._cycle_idx = 0 + self._widget = None + self._prevbg = None + + def padding(self, widget): + """Return padding for widget""" + return self._get(widget, "padding", "") + + def prefix(self, widget, default=None): + """Return the theme prefix for a widget's full text""" + padding = self.padding(widget) + pre = self._get(widget, "prefix", None) + return u"{}{}{}".format(padding, pre, padding) if pre else default + + def suffix(self, widget, default=None): + """Return the theme suffix for a widget's full text""" + padding = self._get(widget, "padding", "") + suf = self._get(widget, "suffix", None) + return u"{}{}{}".format(padding, suf, padding) if suf else default + + def fg(self, widget): + """Return the foreground color for this widget""" + return self._get(widget, "fg", None) + + def bg(self, widget): + """Return the background color for this widget""" + return self._get(widget, "bg", None) + + def align(self, widget): + """Return the widget alignment""" + return self._get(widget, "align", None) + + def minwidth(self, widget): + """Return the minimum width string for this widget""" + return self._get(widget, "minwidth", "") + + def separator(self, widget): + """Return the separator between widgets""" + return self._get(widget, "separator", None) + + def separator_fg(self, widget): + """Return the separator's foreground/text color""" + return self.bg(widget) + + def separator_bg(self, widget): + """Return the separator's background color""" + return self._prevbg + + def separator_block_width(self, widget): + """Return the SBW""" + return self._get(widget, "separator-block-width", None) + + def _load_icons(self, name): + """Load icons for a theme""" + path = "{}/icons/".format(theme_path()) + return self.load(name, path=path) + + def load(self, name, path=theme_path()): + """Load and parse a theme file""" + themefile = "{}/{}.json".format(path, name) + + if os.path.isfile(themefile): + try: + with io.open(themefile,encoding="utf-8") as data: + return json.load(data) + except ValueError as exception: + raise bumblebee.error.ThemeLoadError("JSON error: {}".format(exception)) + else: + raise bumblebee.error.ThemeLoadError("no such theme: {}".format(name)) + + def _get(self, widget, name, default=None): + """Return the config value 'name' for 'widget'""" + + if not self._widget: + self._widget = widget + + if self._widget != widget: + self._prevbg = self.bg(self._widget) + self._widget = widget + if len(self._cycles) > 0: + self._cycle_idx = (self._cycle_idx + 1) % len(self._cycles) + self._cycle = self._cycles[self._cycle_idx] + + module_theme = self._theme.get(widget.module, {}) + class_theme = self._theme.get(widget.cls(), {}) + + state_themes = [] + # avoid infinite recursion + states = widget.state() + if name not in states: + for state in states: + state_themes.append(self._get(widget, state, {})) + + value = self._defaults.get(name, default) + value = widget.get("theme.{}".format(name), value) + value = self._cycle.get(name, value) + value = class_theme.get(name, value) + value = module_theme.get(name, value) + + for theme in state_themes: + value = theme.get(name, value) + + if isinstance(value, list): + key = "{}-idx".format(name) + idx = widget.get(key, 0) + widget.set(key, (idx + 1) % len(value)) + value = value[idx] + + return value + + # algorithm copied from + # http://blog.impressiver.com/post/31434674390/deep-merge-multiple-python-dicts + # nicely done :) + def _merge(self, target, *args): + """Merge two arbitrarily nested data structures""" + if len(args) > 1: + for item in args: + self._merge(item) + return target + + item = args[0] + if not isinstance(item, dict): + return item + for key, value in item.items(): + if key in target and isinstance(target[key], dict): + self._merge(target[key], value) + else: + target[key] = copy.deepcopy(value) + return target + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/bumblebee/util.py b/i3/.config/i3/bar/bumblebee/util.py new file mode 100644 index 0000000..9efa146 --- /dev/null +++ b/i3/.config/i3/bar/bumblebee/util.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +import shlex +import logging +import subprocess + +try: + from exceptions import RuntimeError +except ImportError: + # Python3 doesn't require this anymore + pass + +def execute(cmd, wait=True): + logging.info("executing command '{}'".format(cmd)) + args = shlex.split(cmd) + proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + rv = None + + if wait: + out, _ = proc.communicate() + if proc.returncode != 0: + raise RuntimeError("{} exited with {}".format(cmd, proc.returncode)) + + if hasattr(out, "decode"): + rv = out.decode("utf-8") + else: + rv = out + + logging.info(u"command returned '{}'".format("" if not rv else rv)) + return rv + +def bytefmt(num): + for unit in [ "", "Ki", "Mi", "Gi" ]: + if num < 1024.0: + return "{:.2f}{}B".format(num, unit) + num /= 1024.0 + return "{:.2f}GiB".format(num*1024.0) + +def durationfmt(duration): + minutes, seconds = divmod(duration, 60) + hours, minutes = divmod(minutes, 60) + res = "{:02d}:{:02d}".format(minutes, seconds) + if hours > 0: res = "{:02d}:{}".format(hours, res) + + return res + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/runlint.sh b/i3/.config/i3/bar/runlint.sh new file mode 100644 index 0000000..6902ce9 --- /dev/null +++ b/i3/.config/i3/bar/runlint.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +find . -name "*.py"|xargs pylint --disable=R0903,R0201,C0330 diff --git a/i3/.config/i3/bar/runtests.sh b/i3/.config/i3/bar/runtests.sh new file mode 100644 index 0000000..60f058e --- /dev/null +++ b/i3/.config/i3/bar/runtests.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +echo "testing with $(python2 -V 2>&1)" +python2 $(which nosetests) --rednose -v --with-coverage --cover-erase tests/ + +if [ $? == 0 ]; then + echo + + echo "testing with $(python3 -V 2>&1)" + python3 $(which nosetests-3) --rednose -v --with-coverage --cover-erase tests/ +fi diff --git a/i3/.config/i3/bar/screenshots/amixer.png b/i3/.config/i3/bar/screenshots/amixer.png new file mode 100644 index 0000000..0da57c5 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/amixer.png differ diff --git a/i3/.config/i3/bar/screenshots/battery.png b/i3/.config/i3/bar/screenshots/battery.png new file mode 100644 index 0000000..bdbafee Binary files /dev/null and b/i3/.config/i3/bar/screenshots/battery.png differ diff --git a/i3/.config/i3/bar/screenshots/brightness.png b/i3/.config/i3/bar/screenshots/brightness.png new file mode 100644 index 0000000..298abf3 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/brightness.png differ diff --git a/i3/.config/i3/bar/screenshots/caffeine.png b/i3/.config/i3/bar/screenshots/caffeine.png new file mode 100644 index 0000000..5444777 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/caffeine.png differ diff --git a/i3/.config/i3/bar/screenshots/cmus.png b/i3/.config/i3/bar/screenshots/cmus.png new file mode 100644 index 0000000..52c0b51 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/cmus.png differ diff --git a/i3/.config/i3/bar/screenshots/cpu.png b/i3/.config/i3/bar/screenshots/cpu.png new file mode 100644 index 0000000..0b5527c Binary files /dev/null and b/i3/.config/i3/bar/screenshots/cpu.png differ diff --git a/i3/.config/i3/bar/screenshots/date.png b/i3/.config/i3/bar/screenshots/date.png new file mode 100644 index 0000000..36621e7 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/date.png differ diff --git a/i3/.config/i3/bar/screenshots/datetime.png b/i3/.config/i3/bar/screenshots/datetime.png new file mode 100644 index 0000000..0c15843 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/datetime.png differ diff --git a/i3/.config/i3/bar/screenshots/disk.png b/i3/.config/i3/bar/screenshots/disk.png new file mode 100644 index 0000000..7a659b8 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/disk.png differ diff --git a/i3/.config/i3/bar/screenshots/dnf.png b/i3/.config/i3/bar/screenshots/dnf.png new file mode 100644 index 0000000..4992416 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/dnf.png differ diff --git a/i3/.config/i3/bar/screenshots/kernel.png b/i3/.config/i3/bar/screenshots/kernel.png new file mode 100644 index 0000000..9d20cdc Binary files /dev/null and b/i3/.config/i3/bar/screenshots/kernel.png differ diff --git a/i3/.config/i3/bar/screenshots/layout.png b/i3/.config/i3/bar/screenshots/layout.png new file mode 100644 index 0000000..d09250e Binary files /dev/null and b/i3/.config/i3/bar/screenshots/layout.png differ diff --git a/i3/.config/i3/bar/screenshots/load.png b/i3/.config/i3/bar/screenshots/load.png new file mode 100644 index 0000000..e136e7e Binary files /dev/null and b/i3/.config/i3/bar/screenshots/load.png differ diff --git a/i3/.config/i3/bar/screenshots/memory.png b/i3/.config/i3/bar/screenshots/memory.png new file mode 100644 index 0000000..db0c59b Binary files /dev/null and b/i3/.config/i3/bar/screenshots/memory.png differ diff --git a/i3/.config/i3/bar/screenshots/nic.png b/i3/.config/i3/bar/screenshots/nic.png new file mode 100644 index 0000000..d1512c0 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/nic.png differ diff --git a/i3/.config/i3/bar/screenshots/pacman.png b/i3/.config/i3/bar/screenshots/pacman.png new file mode 100644 index 0000000..8f4169e Binary files /dev/null and b/i3/.config/i3/bar/screenshots/pacman.png differ diff --git a/i3/.config/i3/bar/screenshots/pasink.png b/i3/.config/i3/bar/screenshots/pasink.png new file mode 100644 index 0000000..2fd6359 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/pasink.png differ diff --git a/i3/.config/i3/bar/screenshots/pasource.png b/i3/.config/i3/bar/screenshots/pasource.png new file mode 100644 index 0000000..ae2ee77 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/pasource.png differ diff --git a/i3/.config/i3/bar/screenshots/ping.png b/i3/.config/i3/bar/screenshots/ping.png new file mode 100644 index 0000000..1c8bf2b Binary files /dev/null and b/i3/.config/i3/bar/screenshots/ping.png differ diff --git a/i3/.config/i3/bar/screenshots/pulseaudio.png b/i3/.config/i3/bar/screenshots/pulseaudio.png new file mode 100644 index 0000000..b38a4aa Binary files /dev/null and b/i3/.config/i3/bar/screenshots/pulseaudio.png differ diff --git a/i3/.config/i3/bar/screenshots/redshift.png b/i3/.config/i3/bar/screenshots/redshift.png new file mode 100644 index 0000000..999328f Binary files /dev/null and b/i3/.config/i3/bar/screenshots/redshift.png differ diff --git a/i3/.config/i3/bar/screenshots/sensors.png b/i3/.config/i3/bar/screenshots/sensors.png new file mode 100644 index 0000000..69d2554 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/sensors.png differ diff --git a/i3/.config/i3/bar/screenshots/spacer.png b/i3/.config/i3/bar/screenshots/spacer.png new file mode 100644 index 0000000..5200d0f Binary files /dev/null and b/i3/.config/i3/bar/screenshots/spacer.png differ diff --git a/i3/.config/i3/bar/screenshots/stock.png b/i3/.config/i3/bar/screenshots/stock.png new file mode 100644 index 0000000..427f7cf Binary files /dev/null and b/i3/.config/i3/bar/screenshots/stock.png differ diff --git a/i3/.config/i3/bar/screenshots/themes/default.png b/i3/.config/i3/bar/screenshots/themes/default.png new file mode 100644 index 0000000..c2680bd Binary files /dev/null and b/i3/.config/i3/bar/screenshots/themes/default.png differ diff --git a/i3/.config/i3/bar/screenshots/themes/gruvbox.png b/i3/.config/i3/bar/screenshots/themes/gruvbox.png new file mode 100644 index 0000000..2f2c385 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/themes/gruvbox.png differ diff --git a/i3/.config/i3/bar/screenshots/themes/powerline-gruvbox.png b/i3/.config/i3/bar/screenshots/themes/powerline-gruvbox.png new file mode 100644 index 0000000..5b80ff3 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/themes/powerline-gruvbox.png differ diff --git a/i3/.config/i3/bar/screenshots/themes/powerline-solarized.png b/i3/.config/i3/bar/screenshots/themes/powerline-solarized.png new file mode 100644 index 0000000..5be0b16 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/themes/powerline-solarized.png differ diff --git a/i3/.config/i3/bar/screenshots/themes/powerline.png b/i3/.config/i3/bar/screenshots/themes/powerline.png new file mode 100644 index 0000000..1bd551a Binary files /dev/null and b/i3/.config/i3/bar/screenshots/themes/powerline.png differ diff --git a/i3/.config/i3/bar/screenshots/themes/solarized.png b/i3/.config/i3/bar/screenshots/themes/solarized.png new file mode 100644 index 0000000..a15d08c Binary files /dev/null and b/i3/.config/i3/bar/screenshots/themes/solarized.png differ diff --git a/i3/.config/i3/bar/screenshots/time.png b/i3/.config/i3/bar/screenshots/time.png new file mode 100644 index 0000000..39d5210 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/time.png differ diff --git a/i3/.config/i3/bar/screenshots/traffic.png b/i3/.config/i3/bar/screenshots/traffic.png new file mode 100644 index 0000000..bcb27da Binary files /dev/null and b/i3/.config/i3/bar/screenshots/traffic.png differ diff --git a/i3/.config/i3/bar/screenshots/weather.png b/i3/.config/i3/bar/screenshots/weather.png new file mode 100644 index 0000000..1efee83 Binary files /dev/null and b/i3/.config/i3/bar/screenshots/weather.png differ diff --git a/i3/.config/i3/bar/screenshots/xrandr.png b/i3/.config/i3/bar/screenshots/xrandr.png new file mode 100644 index 0000000..ec1c6de Binary files /dev/null and b/i3/.config/i3/bar/screenshots/xrandr.png differ diff --git a/i3/.config/i3/bar/testjson.sh b/i3/.config/i3/bar/testjson.sh new file mode 100644 index 0000000..d4df9f9 --- /dev/null +++ b/i3/.config/i3/bar/testjson.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +find themes/ -name "*.json"|grep -v invalid|xargs cat|json_verify -s diff --git a/i3/.config/i3/bar/tests/__init__.py b/i3/.config/i3/bar/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/i3/.config/i3/bar/tests/mocks.py b/i3/.config/i3/bar/tests/mocks.py new file mode 100644 index 0000000..f5be753 --- /dev/null +++ b/i3/.config/i3/bar/tests/mocks.py @@ -0,0 +1,141 @@ +# pylint: disable=C0103,C0111 + +import mock +import json +import shlex +import random +import subprocess + +from bumblebee.input import I3BarInput +from bumblebee.output import Widget +from bumblebee.config import Config + +def rand(cnt): + return "".join(random.choice("abcdefghijklmnopqrstuvwxyz0123456789") for i in range(cnt)) + +def setup_test(test, Module): + test._stdin, test._select, test.stdin, test.select = epoll_mock("bumblebee.input") + + test.popen = MockPopen() + + test.config = Config() + test.input = I3BarInput() + test.engine = mock.Mock() + test.engine.input = test.input + test.input.need_event = True + test.module = Module(engine=test.engine, config={ "config": test.config }) + for widget in test.module.widgets(): + widget.link_module(test.module) + test.anyWidget = widget + +def teardown_test(test): + test._stdin.stop() + test._select.stop() + test.popen.cleanup() + +def epoll_mock(module=""): + if len(module) > 0: module = "{}.".format(module) + + stdin = mock.patch("{}sys.stdin".format(module)) + select = mock.patch("{}select".format(module)) + epoll = mock.Mock() + + stdin_mock = stdin.start() + select_mock = select.start() + + stdin_mock.fileno.return_value = 1 + select_mock.epoll.return_value = epoll + epoll.poll.return_value = [(stdin_mock.fileno.return_value, 100)] + + return stdin, select, stdin_mock, select_mock + +def mouseEvent(stdin, button, inp, module=None, instance=None): + stdin.readline.return_value = json.dumps({ + "name": module.id if module else rand(10), + "button": button, + "instance": instance + }) + inp.start() + inp.stop() + stdin.readline.assert_any_call() + +class MockPopen(object): + def __init__(self, module=""): + if len(module) > 0: module = "{}.".format(module) + self._patch = mock.patch("{}subprocess.Popen".format(module)) + self._popen = self._patch.start() + self.mock = mock.Mock() + # for a nicer, more uniform interface + self.mock.popen = self._popen + # for easier command execution checks + self.mock.popen.assert_call = self.assert_call + self._popen.return_value = self.mock + + self.mock.communicate.return_value = [ "", None ] + self.mock.returncode = 0 + + def assert_call(self, cmd): + self.mock.popen.assert_any_call(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + def cleanup(self): + self._patch.stop() + +class MockInput(object): + def __init__(self): + self._callbacks = {} + def start(self): + pass + + def stop(self): + pass + + def get_callback(self, uid): + return self._callbacks.get(uid, None) + + def register_callback(self, obj, button, cmd): + if not obj: + return + self._callbacks[obj.id] = { + "button": button, + "command": cmd, + } + +class MockOutput(object): + def start(self): + pass + + def stop(self): + pass + + def draw(self, widget, engine, module): + engine.stop() + + def begin(self): + pass + + def flush(self): + pass + + def end(self): + pass + +class MockEngine(object): + def __init__(self): + self.input = MockInput() + +class MockWidget(Widget): + def __init__(self, text): + super(MockWidget, self).__init__(text) + self.module = None + self.attr_state = ["state-default"] + self.id = rand(10) + + self.full_text(text) + +# def state(self): +# return self.attr_state + + def update(self, widgets): + pass + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/modules/__init__.py b/i3/.config/i3/bar/tests/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/i3/.config/i3/bar/tests/modules/test_battery.py b/i3/.config/i3/bar/tests/modules/test_battery.py new file mode 100644 index 0000000..0c86cc9 --- /dev/null +++ b/i3/.config/i3/bar/tests/modules/test_battery.py @@ -0,0 +1,108 @@ +# pylint: disable=C0103,C0111 + +import sys +import mock +import unittest + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +import tests.mocks as mocks + +from bumblebee.modules.battery import Module +from bumblebee.config import Config + +class TestBatteryModule(unittest.TestCase): + def setUp(self): + self._stdout = mock.patch("sys.stdout", new_callable=StringIO) + self._exists = mock.patch("bumblebee.modules.battery.os.path.exists") + self._open = mock.patch("bumblebee.modules.battery.open", create=True) + + self.stdout = self._stdout.start() + self.exists = self._exists.start() + self.open = self._open.start() + self.file = mock.Mock() + self.file.__enter__ = lambda x: self.file + self.file.__exit__ = lambda x, a, b, c: "" + self.open.return_value = self.file + + self.exists.return_value = True + self.engine = mock.Mock() + self.config = Config() + self.module = Module(engine=self.engine, config={"config":self.config}) + + self.config.set("battery.critical", "20") + self.config.set("battery.warning", "25") + self.criticalValue = "19" + self.warningValue = "21" + self.normalValue = "26" + self.chargedValue = "96" + + for widget in self.module.widgets(): + widget.link_module(self.module) + self.anyWidget = widget + + def tearDown(self): + self._stdout.stop() + self._exists.stop() + self._open.stop() + + def test_format(self): + for widget in self.module.widgets(): + self.assertEquals(len(widget.full_text()), len("100%")) + + def test_critical(self): + self.file.read.return_value = self.criticalValue + self.module.update_all() + self.assertTrue("critical" in self.module.state(self.anyWidget)) + + def test_warning(self): + self.file.read.return_value = self.warningValue + self.module.update_all() + self.assertTrue("warning" in self.module.state(self.anyWidget)) + + def test_normal(self): + self.file.read.return_value = self.normalValue + self.module.update_all() + self.assertTrue(not "warning" in self.module.state(self.anyWidget)) + self.assertTrue(not "critical" in self.module.state(self.anyWidget)) + + def test_overload(self): + self.file.read.return_value = "120" + self.module.update_all() + self.assertTrue(not "warning" in self.module.state(self.anyWidget)) + self.assertTrue(not "critical" in self.module.state(self.anyWidget)) + self.assertEquals(self.module.capacity(self.anyWidget), "100%") + + def test_ac(self): + self.exists.return_value = False + self.module.update_all() + self.assertEquals(self.module.capacity(self.anyWidget), "ac") + self.assertTrue("AC" in self.module.state(self.anyWidget)) + + def test_error(self): + self.file.read.side_effect = IOError("failed to read") + self.module.update_all() + self.assertEquals(self.module.capacity(self.anyWidget), "n/a") + self.assertTrue("critical" in self.module.state(self.anyWidget)) + self.assertTrue("unknown" in self.module.state(self.anyWidget)) + + def test_charging(self): + self.file.read.return_value = self.chargedValue + self.module.update_all() + self.assertTrue("charged" in self.module.state(self.anyWidget)) + self.file.read.return_value = self.normalValue + self.module.update_all() + self.assertTrue("charging" in self.module.state(self.anyWidget)) + + def test_discharging(self): + for limit in [ 10, 25, 50, 80, 100 ]: + value = limit - 1 + self.file.read.return_value = str(value) + self.module.update_all() + self.file.read.return_value = "Discharging" + self.assertTrue("discharging-{}".format(limit) in self.module.state(self.anyWidget)) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/modules/test_brightness.py b/i3/.config/i3/bar/tests/modules/test_brightness.py new file mode 100644 index 0000000..b636d44 --- /dev/null +++ b/i3/.config/i3/bar/tests/modules/test_brightness.py @@ -0,0 +1,47 @@ +# pylint: disable=C0103,C0111 + +import mock +import unittest + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +import tests.mocks as mocks + +from bumblebee.input import WHEEL_UP, WHEEL_DOWN +from bumblebee.modules.brightness import Module + +class TestBrightnessModule(unittest.TestCase): + def setUp(self): + mocks.setup_test(self, Module) + + def tearDown(self): + mocks.teardown_test(self) + + def test_format(self): + for widget in self.module.widgets(): + self.assertEquals(len(widget.full_text()), len("100%")) + + def test_wheel_up(self): + mocks.mouseEvent(stdin=self.stdin, button=WHEEL_UP, inp=self.input, module=self.module) + self.popen.assert_call("xbacklight +2%") + + def test_wheel_down(self): + mocks.mouseEvent(stdin=self.stdin, button=WHEEL_DOWN, inp=self.input, module=self.module) + self.popen.assert_call("xbacklight -2%") + + def test_custom_step(self): + self.config.set("brightness.step", "10") + module = Module(engine=self.engine, config={"config": self.config}) + mocks.mouseEvent(stdin=self.stdin, button=WHEEL_DOWN, inp=self.input, module=module) + self.popen.assert_call("xbacklight -10%") + + def test_update(self): + self.popen.mock.communicate.return_value = ("20.0", None) + self.module.update_all() + self.assertEquals(self.module.brightness(self.anyWidget), "020%") + + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/modules/test_caffeine.py b/i3/.config/i3/bar/tests/modules/test_caffeine.py new file mode 100644 index 0000000..e95e2ac --- /dev/null +++ b/i3/.config/i3/bar/tests/modules/test_caffeine.py @@ -0,0 +1,53 @@ +# pylint: disable=C0103,C0111 + +import json +import unittest +import mock + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +import tests.mocks as mocks + +from bumblebee.config import Config +from bumblebee.input import LEFT_MOUSE +from bumblebee.modules.caffeine import Module + +class TestCaffeineModule(unittest.TestCase): + def setUp(self): + mocks.setup_test(self, Module) + + self.xset_active = " timeout: 0 cycle: 123" + self.xset_inactive = " timeout: 600 cycle: 123" + + def tearDown(self): + mocks.teardown_test(self) + + def test_text(self): + self.assertEquals(self.module.caffeine(self.anyWidget), "") + + def test_active(self): + self.popen.mock.communicate.return_value = (self.xset_active, None) + self.assertTrue(not "deactivated" in self.module.state(self.anyWidget)) + self.assertTrue("activated" in self.module.state(self.anyWidget)) + + def test_inactive(self): + self.popen.mock.communicate.return_value = (self.xset_inactive, None) + self.assertTrue("deactivated" in self.module.state(self.anyWidget)) + self.popen.mock.communicate.return_value = ("no text", None) + self.assertTrue("deactivated" in self.module.state(self.anyWidget)) + + def test_toggle(self): + self.popen.mock.communicate.return_value = (self.xset_active, None) + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("xset s default") + self.popen.assert_call("notify-send \"Out of coffee\"") + + self.popen.mock.communicate.return_value = (self.xset_inactive, None) + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("xset s off") + self.popen.assert_call("notify-send \"Consuming caffeine\"") + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/modules/test_cmus.py b/i3/.config/i3/bar/tests/modules/test_cmus.py new file mode 100644 index 0000000..ce1f77c --- /dev/null +++ b/i3/.config/i3/bar/tests/modules/test_cmus.py @@ -0,0 +1,114 @@ +# pylint: disable=C0103,C0111 + +import mock +import unittest + +import tests.mocks as mocks + +from bumblebee.input import LEFT_MOUSE +from bumblebee.modules.cmus import Module + +class TestCmusModule(unittest.TestCase): + def setUp(self): + mocks.setup_test(self, Module) + + self.songTemplate = """ +status {status} +file /path/to/file +duration {duration} +position {position} +tag title {title} +tag artist {artist} +tag album {album} +tag tracknumber 1 +tag date 1984 +tag comment comment + """ + + def tearDown(self): + mocks.teardown_test(self) + + def test_read_song(self): + self.popen.mock.communicate.return_value = ("song", None) + self.module.update_all() + self.popen.assert_call("cmus-remote -Q") + + def test_handle_runtimeerror(self): + self.popen.mock.communicate.side_effect = RuntimeError("error loading song") + self.module.update_all() + self.assertEquals(self.module.description(self.anyWidget), " - /") + + def test_format(self): + self.popen.mock.communicate.return_value = (self.songTemplate.format( + artist="an artist", title="a title", duration="100", position="20", + album="an album", status="irrelevant" + ), None) + self.module.update_all() + self.anyWidget.set("theme.width", 1000) + self.assertEquals(self.module.description(self.anyWidget), + "an artist - a title 00:20/01:40" + ) + + def test_scrollable_format(self): + self.popen.mock.communicate.return_value = (self.songTemplate.format( + artist="an artist", title="a title", duration="100", position="20", + album="an album", status="irrelevant" + ), None) + self.module.update_all() + self.anyWidget.set("theme.width", 10) + self.assertEquals(self.module.description(self.anyWidget), + "an artist - a title 00:20/01:40"[:10] + ) + + def test_repeat(self): + self.popen.mock.communicate.return_value = ("set repeat false", None) + self.module.update_all() + self.assertTrue("repeat-off" in self.module.state(self.module.widget("cmus.repeat"))) + self.popen.mock.communicate.return_value = ("set repeat true", None) + self.module.update_all() + self.assertTrue("repeat-on" in self.module.state(self.module.widget("cmus.repeat"))) + + def test_shuffle(self): + self.popen.mock.communicate.return_value = ("set shuffle false", None) + self.module.update_all() + self.assertTrue("shuffle-off" in self.module.state(self.module.widget("cmus.shuffle"))) + self.popen.mock.communicate.return_value = ("set shuffle true", None) + self.module.update_all() + self.assertTrue("shuffle-on" in self.module.state(self.module.widget("cmus.shuffle"))) + + def test_prevnext(self): + self.assertTrue("prev" in self.module.state(self.module.widget("cmus.prev"))) + self.assertTrue("next" in self.module.state(self.module.widget("cmus.next"))) + + def test_main(self): + self.popen.mock.communicate.return_value = ("status paused", None) + self.module.update_all() + self.assertTrue("paused" in self.module.state(self.module.widget("cmus.main"))) + + self.popen.mock.communicate.return_value = ("status playing", None) + self.module.update_all() + self.assertTrue("playing" in self.module.state(self.module.widget("cmus.main"))) + + self.popen.mock.communicate.return_value = ("status stopped", None) + self.module.update_all() + self.assertTrue("stopped" in self.module.state(self.module.widget("cmus.main"))) + + def test_widget(self): + self.assertEquals(len(self.module.widgets()), 5) + + for idx, val in enumerate(["prev", "main", "next", "shuffle", "repeat"]): + self.assertEquals(self.module.widgets()[idx].name, "cmus.{}".format(val)) + + def test_interaction(self): + events = [ + {"widget": "cmus.shuffle", "action": "cmus-remote -S"}, + {"widget": "cmus.repeat", "action": "cmus-remote -R"}, + {"widget": "cmus.next", "action": "cmus-remote -n"}, + {"widget": "cmus.prev", "action": "cmus-remote -r"}, + {"widget": "cmus.main", "action": "cmus-remote -u"}, + ] + for event in events: + mocks.mouseEvent(stdin=self.stdin, inp=self.input, module=self.module, instance=self.module.widget(event["widget"]).id, button=LEFT_MOUSE) + self.popen.assert_call(event["action"]) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/modules/test_cpu.py b/i3/.config/i3/bar/tests/modules/test_cpu.py new file mode 100644 index 0000000..cd5ce89 --- /dev/null +++ b/i3/.config/i3/bar/tests/modules/test_cpu.py @@ -0,0 +1,45 @@ +# pylint: disable=C0103,C0111 + +import mock +import unittest + +import tests.mocks as mocks + +from bumblebee.input import LEFT_MOUSE +from bumblebee.modules.cpu import Module + +class TestCPUModule(unittest.TestCase): + def setUp(self): + mocks.setup_test(self, Module) + self._psutil = mock.patch("bumblebee.modules.cpu.psutil") + self.psutil = self._psutil.start() + + def tearDown(self): + self._psutil.stop() + mocks.teardown_test(self) + + def test_format(self): + self.psutil.cpu_percent.return_value = 21.0 + self.module.update_all() + for widget in self.module.widgets(): + self.assertEquals(len(widget.full_text()), len("100.00%")) + + def test_leftclick(self): + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("gnome-system-monitor") + + def test_warning(self): + self.config.set("cpu.critical", "20") + self.config.set("cpu.warning", "18") + self.psutil.cpu_percent.return_value = 19.0 + self.module.update_all() + self.assertTrue("warning" in self.module.state(self.anyWidget)) + + def test_critical(self): + self.config.set("cpu.critical", "20") + self.config.set("cpu.warning", "19") + self.psutil.cpu_percent.return_value = 21.0 + self.module.update_all() + self.assertTrue("critical" in self.module.state(self.anyWidget)) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/modules/test_disk.py b/i3/.config/i3/bar/tests/modules/test_disk.py new file mode 100644 index 0000000..16f31c4 --- /dev/null +++ b/i3/.config/i3/bar/tests/modules/test_disk.py @@ -0,0 +1,47 @@ +# pylint: disable=C0103,C0111 + +import mock +import unittest + +import tests.mocks as mocks + +from bumblebee.input import LEFT_MOUSE +from bumblebee.modules.disk import Module + +class MockVFS(object): + def __init__(self, perc): + self.f_blocks = 1024*1024 + self.f_frsize = 1 + self.f_bfree = self.f_blocks*(1.0 - perc/100.0) + +class TestDiskModule(unittest.TestCase): + def setUp(self): + mocks.setup_test(self, Module) + self._os = mock.patch("bumblebee.modules.disk.os") + self.os = self._os.start() + self.config.set("disk.path", "somepath") + + def tearDown(self): + self._os.stop() + mocks.teardown_test(self) + + def test_leftclick(self): + module = Module(engine=self.engine, config={"config":self.config}) + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=module) + self.popen.assert_call("nautilus {}".format(self.module.parameter("path"))) + + def test_warning(self): + self.config.set("disk.critical", "80") + self.config.set("disk.warning", "70") + self.os.statvfs.return_value = MockVFS(75.0) + self.module.update_all() + self.assertTrue("warning" in self.module.state(self.anyWidget)) + + def test_critical(self): + self.config.set("disk.critical", "80") + self.config.set("disk.warning", "70") + self.os.statvfs.return_value = MockVFS(85.0) + self.module.update_all() + self.assertTrue("critical" in self.module.state(self.anyWidget)) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/modules/test_load.py b/i3/.config/i3/bar/tests/modules/test_load.py new file mode 100644 index 0000000..e57bc2b --- /dev/null +++ b/i3/.config/i3/bar/tests/modules/test_load.py @@ -0,0 +1,57 @@ +# pylint: disable=C0103,C0111 + +import json +import mock +import unittest + +import tests.mocks as mocks + +from bumblebee.input import LEFT_MOUSE +from bumblebee.modules.load import Module + +class TestLoadModule(unittest.TestCase): + def setUp(self): + mocks.setup_test(self, Module) + + self._mp = mock.patch("bumblebee.modules.load.multiprocessing") + self._os = mock.patch("bumblebee.modules.load.os") + + self.mp = self._mp.start() + self.os = self._os.start() + + self.mp.cpu_count.return_value = 1 + + def tearDown(self): + self._mp.stop() + self._os.stop() + mocks.teardown_test(self) + + def test_leftclick(self): + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("gnome-system-monitor") + + def test_load_format(self): + self.os.getloadavg.return_value = [ 5.9, 1.2, 0.8 ] + self.module.update_all() + self.assertEquals(self.module.load(self.anyWidget), "5.90/1.20/0.80") + + def test_warning(self): + self.config.set("load.critical", "1") + self.config.set("load.warning", "0.8") + self.os.getloadavg.return_value = [ 0.9, 0, 0 ] + self.module.update_all() + self.assertTrue("warning" in self.module.state(self.anyWidget)) + + def test_critical(self): + self.config.set("load.critical", "1") + self.config.set("load.warning", "0.8") + self.os.getloadavg.return_value = [ 1.1, 0, 0 ] + self.module.update_all() + self.assertTrue("critical" in self.module.state(self.anyWidget)) + + def test_assume_single_core(self): + self.mp.cpu_count.side_effect = NotImplementedError + module = Module(engine=self.engine, config={"config": mock.Mock() }) + self.assertEquals(1, module._cpus) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/modules/test_memory.py b/i3/.config/i3/bar/tests/modules/test_memory.py new file mode 100644 index 0000000..94c61f9 --- /dev/null +++ b/i3/.config/i3/bar/tests/modules/test_memory.py @@ -0,0 +1,52 @@ +# pylint: disable=C0103,C0111 + +import mock +import unittest + +import tests.mocks as mocks + +from bumblebee.input import LEFT_MOUSE +from bumblebee.modules.memory import Module + +class VirtualMemory(object): + def __init__(self, percent): + self.percent = percent + +class TestMemoryModule(unittest.TestCase): + def setUp(self): + mocks.setup_test(self, Module) + self._psutil = mock.patch("bumblebee.modules.memory.psutil") + self.psutil = self._psutil.start() + + def tearDown(self): + self._psutil.stop() + mocks.teardown_test(self) + + def test_leftclick(self): + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("gnome-system-monitor") + + def test_warning(self): + self.config.set("memory.critical", "80") + self.config.set("memory.warning", "70") + self.psutil.virtual_memory.return_value = VirtualMemory(75) + self.module.update_all() + self.assertTrue("warning" in self.module.state(self.anyWidget)) + + def test_critical(self): + self.config.set("memory.critical", "80") + self.config.set("memory.warning", "70") + self.psutil.virtual_memory.return_value = VirtualMemory(81) + self.module.update_all() + self.assertTrue("critical" in self.module.state(self.anyWidget)) + + def test_usage(self): + rv = VirtualMemory(50) + rv.total = 1000 + rv.available = 500 + self.psutil.virtual_memory.return_value = rv + self.module.update_all() + self.assertEquals("500.00B/1000.00B (50.00%)", self.module.memory_usage(self.anyWidget)) + self.assertEquals(None, self.module.state(self.anyWidget)) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/modules/test_pulseaudio.py b/i3/.config/i3/bar/tests/modules/test_pulseaudio.py new file mode 100644 index 0000000..f8754c6 --- /dev/null +++ b/i3/.config/i3/bar/tests/modules/test_pulseaudio.py @@ -0,0 +1,34 @@ +# pylint: disable=C0103,C0111 + +import mock +import unittest + +import tests.mocks as mocks + +from bumblebee.input import LEFT_MOUSE, RIGHT_MOUSE, WHEEL_UP, WHEEL_DOWN +from bumblebee.modules.pulseaudio import Module + +class TestPulseAudioModule(unittest.TestCase): + def setUp(self): + mocks.setup_test(self, Module) + + def tearDown(self): + mocks.teardown_test(self) + + def test_leftclick(self): + mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("pactl set-source-mute @DEFAULT_SOURCE@ toggle") + + def test_rightclick(self): + mocks.mouseEvent(stdin=self.stdin, button=RIGHT_MOUSE, inp=self.input, module=self.module) + self.popen.assert_call("pavucontrol") + + def test_wheelup(self): + mocks.mouseEvent(stdin=self.stdin, button=WHEEL_UP, inp=self.input, module=self.module) + self.popen.assert_call("pactl set-source-volume @DEFAULT_SOURCE@ +2%") + + def test_wheeldown(self): + mocks.mouseEvent(stdin=self.stdin, button=WHEEL_DOWN, inp=self.input, module=self.module) + self.popen.assert_call("pactl set-source-volume @DEFAULT_SOURCE@ -2%") + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/test_config.py b/i3/.config/i3/bar/tests/test_config.py new file mode 100644 index 0000000..395dcb7 --- /dev/null +++ b/i3/.config/i3/bar/tests/test_config.py @@ -0,0 +1,77 @@ +# pylint: disable=C0103,C0111 + +import unittest +import mock +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +from bumblebee.config import Config +from bumblebee.theme import themes +from bumblebee.engine import all_modules + +class TestConfig(unittest.TestCase): + def setUp(self): + self._stdout = mock.patch("bumblebee.config.sys.stdout", new_callable=StringIO) + self._stderr = mock.patch("bumblebee.config.sys.stderr", new_callable=StringIO) + + self.stdout = self._stdout.start() + self.stderr = self._stderr.start() + + self.defaultConfig = Config() + self.someSimpleModules = ["foo", "bar", "baz"] + self.someAliasModules = ["foo:a", "bar:b", "baz:c"] + self.someTheme = "some-theme" + + def tearDown(self): + self._stdout.stop() + self._stderr.stop() + + def test_no_modules_by_default(self): + self.assertEquals(self.defaultConfig.modules(), []) + + def test_simple_modules(self): + cfg = Config(["-m"] + self.someSimpleModules) + self.assertEquals(cfg.modules(), [{ + "name": x, "module": x + } for x in self.someSimpleModules]) + + def test_alias_modules(self): + cfg = Config(["-m"] + self.someAliasModules) + self.assertEquals(cfg.modules(), [{ + "module": x.split(":")[0], + "name": x.split(":")[1], + } for x in self.someAliasModules]) + + def test_parameters(self): + cfg = Config(["-m", "module", "-p", "module.key=value"]) + self.assertEquals(cfg.get("module.key"), "value") + + def test_theme(self): + cfg = Config(["-t", self.someTheme]) + self.assertEquals(cfg.theme(), self.someTheme) + + def test_notheme(self): + self.assertEquals(self.defaultConfig.theme(), "default") + + def test_list_themes(self): + with self.assertRaises(SystemExit): + cfg = Config(["-l", "themes"]) + result = self.stdout.getvalue() + for theme in themes(): + self.assertTrue(theme in result) + + def test_list_modules(self): + with self.assertRaises(SystemExit): + cfg = Config(["-l", "modules"]) + result = self.stdout.getvalue() + for module in all_modules(): + self.assertTrue(module["name"] in result) + + def test_invalid_list(self): + with self.assertRaises(SystemExit): + cfg = Config(["-l", "invalid"]) + self.assertTrue("invalid choice" in "".join(self.stderr.getvalue())) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/test_engine.py b/i3/.config/i3/bar/tests/test_engine.py new file mode 100644 index 0000000..946f604 --- /dev/null +++ b/i3/.config/i3/bar/tests/test_engine.py @@ -0,0 +1,90 @@ +# pylint: disable=C0103,C0111,W0703,W0212 + +import shlex +import unittest + +from bumblebee.error import ModuleLoadError +from bumblebee.engine import Engine +from bumblebee.config import Config +import bumblebee.input + +from tests.mocks import MockOutput, MockInput + +class TestEngine(unittest.TestCase): + def setUp(self): + self.engine = Engine(config=Config(), output=MockOutput(), inp=MockInput()) + self.testModule = "test" + self.testAlias = "test-alias" + self.singleWidgetModule = [{"module": self.testModule, "name": "a"}] + self.singleWidgetAlias = [{"module": self.testAlias, "name": "a" }] + self.invalidModule = "no-such-module" + self.testModuleSpec = "bumblebee.modules.{}".format(self.testModule) + self.testModules = [ + {"module": "test", "name": "a"}, + {"module": "test", "name": "b"}, + ] + + def test_stop(self): + self.assertTrue(self.engine.running()) + self.engine.stop() + self.assertFalse(self.engine.running()) + + def test_load_module(self): + module = self.engine._load_module(self.testModule) + self.assertEquals(module.__module__, self.testModuleSpec) + + def test_load_invalid_module(self): + with self.assertRaises(ModuleLoadError): + self.engine._load_module(self.invalidModule) + + def test_load_none(self): + with self.assertRaises(ModuleLoadError): + self.engine._load_module(None) + + def test_load_modules(self): + modules = self.engine.load_modules(self.testModules) + self.assertEquals(len(modules), len(self.testModules)) + self.assertEquals( + [module.__module__ for module in modules], + [self.testModuleSpec for module in modules] + ) + + def test_run(self): + self.engine.load_modules(self.singleWidgetModule) + try: + self.engine.run() + except Exception as e: + self.fail(e) + + def test_aliases(self): + modules = self.engine.load_modules(self.singleWidgetAlias) + self.assertEquals(len(modules), 1) + self.assertEquals(modules[0].__module__, self.testModuleSpec) + + def test_custom_cmd(self): + testmodules = [ + { "name": "test", "button": "test.left-click", "action": "echo" }, + { "name": "test:alias", "button": "alias.right-click", "action": "echo2" }, + ] + cmd = "-m" + for test in testmodules: + cmd += " " + test["name"] + cmd += " -p" + for test in testmodules: + cmd += " " + test["button"] + "=" + test["action"] + cfg = Config(shlex.split(cmd)) + inp = MockInput() + engine = Engine(config=cfg, output=MockOutput(), inp=inp) + + i = 0 + for module in engine.modules(): + callback = inp.get_callback(module.id) + self.assertTrue(callback is not None) + self.assertEquals(callback["command"], testmodules[i]["action"]) + if "left" in testmodules[i]["button"]: + self.assertTrue(callback["button"], bumblebee.input.LEFT_MOUSE) + if "right" in testmodules[i]["button"]: + self.assertTrue(callback["button"], bumblebee.input.RIGHT_MOUSE) + i += 1 + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/test_i3barinput.py b/i3/.config/i3/bar/tests/test_i3barinput.py new file mode 100644 index 0000000..6852f97 --- /dev/null +++ b/i3/.config/i3/bar/tests/test_i3barinput.py @@ -0,0 +1,110 @@ +# pylint: disable=C0103,C0111 + +import json +import mock +import unittest + +import tests.mocks as mocks + +from bumblebee.input import I3BarInput, LEFT_MOUSE, RIGHT_MOUSE + +class TestI3BarInput(unittest.TestCase): + def setUp(self): + self.input = I3BarInput() + self.input.need_event = True + + self._stdin = mock.patch("bumblebee.input.sys.stdin") + self.stdin = self._stdin.start() + self._select = mock.patch("bumblebee.input.select") + self.select = self._select.start() + self.popen = mocks.MockPopen() + + self.stdin.fileno.return_value = 1 + epoll = mock.Mock() + self.select.epoll.return_value = epoll + + epoll.poll.return_value = [(self.stdin.fileno.return_value, 2)] + + self.anyModule = mock.Mock() + self.anyModule.id = mocks.rand(10) + self.anotherModule = mock.Mock() + self.anotherModule.id = mocks.rand(10) + self.anyWidget = mocks.MockWidget("some-widget") + self.anotherWidget = mocks.MockWidget("another-widget") + self.anyData = self.invalidData = "any data" + self.invalidEvent = json.dumps({"name": None, "instance": None, "button": 1}) + self.incompleteEvent = json.dumps({"button": 1}) + self.anyCommand = "this is a command with arguments" + + self._called = 0 + + def tearDown(self): + self._stdin.stop() + self._select.stop() + self.popen.cleanup() + + def callback(self, event): + self._called += 1 + + def calls(self): + rv = self._called + self._called = 0 + return rv + + def test_read_event(self): + self.stdin.readline.return_value = self.anyData + self.input.start() + self.input.stop() + self.stdin.readline.assert_any_call() + + def test_ignore_invalid_input(self): + for data in [ self.invalidData, self.incompleteEvent, self.invalidEvent ]: + self.stdin.readline.return_value = data + self.input.start() + self.assertEquals(self.input.alive(), True) + self.assertEquals(self.input.stop(), True) + self.stdin.readline.assert_any_call() + + def test_global_callback(self): + self.input.register_callback(None, button=LEFT_MOUSE, cmd=self.callback) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin) + self.assertTrue(self.calls() > 0) + + def test_remove_global_callback(self): + self.test_global_callback() + self.input.deregister_callbacks(None) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin) + self.assertTrue(self.calls() == 0) + + def test_global_callback_wrong_button(self): + self.input.register_callback(None, button=LEFT_MOUSE, cmd=self.callback) + mocks.mouseEvent(button=RIGHT_MOUSE, inp=self.input, stdin=self.stdin) + self.assertTrue(self.calls() == 0) + + def test_module_callback(self): + self.input.register_callback(self.anyModule, button=LEFT_MOUSE, cmd=self.callback) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin, module=self.anyModule) + self.assertTrue(self.calls() > 0) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin, module=self.anotherModule) + self.assertTrue(self.calls() == 0) + + def test_remove_module_callback(self): + self.test_module_callback() + self.input.deregister_callbacks(self.anyModule) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin, module=self.anyModule) + self.assertTrue(self.calls() == 0) + + def test_widget_callback(self): + self.input.register_callback(self.anyWidget, button=LEFT_MOUSE, cmd=self.callback) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin, module=self.anyWidget) + self.assertTrue(self.calls() > 0) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin, module=self.anotherWidget) + self.assertTrue(self.calls() == 0) + + def test_widget_cmd_callback(self): + self.input.register_callback(self.anyWidget, button=LEFT_MOUSE, cmd=self.anyCommand) + mocks.mouseEvent(button=LEFT_MOUSE, inp=self.input, stdin=self.stdin, module=self.anyWidget) + self.popen.assert_call(self.anyCommand) + + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/test_i3baroutput.py b/i3/.config/i3/bar/tests/test_i3baroutput.py new file mode 100644 index 0000000..8c566ef --- /dev/null +++ b/i3/.config/i3/bar/tests/test_i3baroutput.py @@ -0,0 +1,133 @@ +# pylint: disable=C0103,C0111 + +import json +import mock +import unittest + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +import tests.mocks as mocks + +from bumblebee.output import I3BarOutput + +class TestI3BarOutput(unittest.TestCase): + def setUp(self): + self.theme = mock.Mock() + self.theme.separator_fg.return_value = "#123456" + self.theme.separator_bg.return_value = "#000000" + self.theme.separator.return_value = "" + self.theme.prefix.return_value = "" + self.theme.suffix.return_value = "" + self.theme.separator_block_width.return_value = 1 + self.theme.fg.return_value = "#ababab" + self.theme.bg.return_value = "#ababab" + self.theme.align.return_value = None + self.theme.minwidth.return_value = "" + self.output = I3BarOutput(self.theme) + + self._stdout = mock.patch("bumblebee.output.sys.stdout", new_callable=StringIO) + self.stdout = self._stdout.start() + + self.anyWidget = mocks.MockWidget("some text") + self.anyModule = mock.Mock() + self.anyModule.id = mocks.rand(10) + self.anyModule.name = mocks.rand(10) + + self.expectedStart = json.dumps({"version": 1, "click_events": True}) + "\n[\n" + self.expectedStop = "]\n" + + self.anyColor = "#ffffff" + self.anotherColor = "#cdcdcd" + + def tearDown(self): + self._stdout.stop() + + def test_start(self): + self.output.start() + self.assertEquals(self.expectedStart, self.stdout.getvalue()) + + def test_stop(self): + self.output.stop() + self.assertEquals(self.expectedStop, self.stdout.getvalue()) + + def test_draw_single_widget(self): + self.output.draw(self.anyWidget, self.anyModule) + self.output.flush() + result = json.loads(self.stdout.getvalue())[0] + self.assertEquals(result["full_text"], self.anyWidget.full_text()) + + def test_draw_multiple_widgets(self): + for i in range(4): + self.output.draw(self.anyWidget, self.anyModule) + self.output.flush() + result = json.loads(self.stdout.getvalue()) + for res in result: + self.assertEquals(res["full_text"], self.anyWidget.full_text()) + + def test_begin(self): + self.output.begin() + self.assertEquals("", self.stdout.getvalue()) + + def test_end(self): + self.output.end() + self.assertEquals(",\n", self.stdout.getvalue()) + + def test_prefix(self): + self.theme.prefix.return_value = " - " + self.output.draw(self.anyWidget, self.anyModule) + self.output.flush() + result = json.loads(self.stdout.getvalue())[0] + self.assertEquals(result["full_text"], " - {}".format(self.anyWidget.full_text())) + + def test_suffix(self): + self.theme.suffix.return_value = " - " + self.output.draw(self.anyWidget, self.anyModule) + self.output.flush() + result = json.loads(self.stdout.getvalue())[0] + self.assertEquals(result["full_text"], "{} - ".format(self.anyWidget.full_text())) + + def test_bothfix(self): + self.theme.prefix.return_value = "*" + self.theme.suffix.return_value = " - " + self.output.draw(self.anyWidget, self.anyModule) + self.output.flush() + result = json.loads(self.stdout.getvalue())[0] + self.assertEquals(result["full_text"], "*{} - ".format(self.anyWidget.full_text())) + + def test_colors(self): + self.theme.fg.return_value = self.anyColor + self.theme.bg.return_value = self.anotherColor + self.output.draw(self.anyWidget, self.anyModule) + self.output.flush() + result = json.loads(self.stdout.getvalue())[0] + self.assertEquals(result["color"], self.anyColor) + self.assertEquals(result["background"], self.anotherColor) + + def test_widget_link(self): + self.anyWidget.link_module(self.anyModule) + self.assertEquals(self.anyWidget._module, self.anyModule) + self.assertEquals(self.anyWidget.module, self.anyModule.name) + + def test_unlinked_widget_state(self): + state = self.anyWidget.state() + self.assertTrue(type(state) == list) + + def test_linked_widget_state(self): + self.anyWidget.link_module(self.anyModule) + for lst in [ "samplestate", ["a", "b", "c"], [] ]: + self.anyModule.state.return_value = lst + state = self.anyWidget.state() + self.assertEquals(type(state), list) + if type(lst) is not list: lst = [lst] + self.assertEquals(state, lst) + + def test_widget_fulltext(self): + self.anyWidget.full_text("some text") + self.assertEquals(self.anyWidget.full_text(), "some text") + self.anyWidget.full_text(lambda x: "callable fulltext") + self.assertEquals(self.anyWidget.full_text(), "callable fulltext") + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/test_module.py b/i3/.config/i3/bar/tests/test_module.py new file mode 100644 index 0000000..cf663dc --- /dev/null +++ b/i3/.config/i3/bar/tests/test_module.py @@ -0,0 +1,89 @@ +# pylint: disable=C0103,C0111,W0703 + +import unittest + +from bumblebee.engine import Module +from bumblebee.config import Config +from tests.mocks import MockWidget + +class TestModule(unittest.TestCase): + def setUp(self): + self.widgetName = "foo" + self.widget = MockWidget(self.widgetName) + self.config = Config() + self.anyWidgetName = "random-widget-name" + self.noSuchModule = "this-module-does-not-exist" + self.moduleWithoutWidgets = Module(engine=None, widgets=None) + self.moduleWithOneWidget = Module(engine=None, widgets=self.widget, config={"config": self.config}) + self.moduleWithMultipleWidgets = Module(engine=None, + widgets=[self.widget, self.widget, self.widget] + ) + + self.anyConfigName = "cfg" + self.anotherConfigName = "cfg2" + self.anyModule = Module(engine=None, widgets=self.widget, config={ + "name": self.anyConfigName, "config": self.config + }) + self.anotherModule = Module(engine=None, widgets=self.widget, config={ + "name": self.anotherConfigName, "config": self.config + }) + self.anyKey = "some-parameter" + self.anyValue = "value" + self.anotherValue = "another-value" + self.emptyKey = "i-do-not-exist" + self.config.set("{}.{}".format(self.anyConfigName, self.anyKey), self.anyValue) + self.config.set("{}.{}".format(self.anotherConfigName, self.anyKey), self.anotherValue) + + def test_empty_widgets(self): + self.assertEquals(self.moduleWithoutWidgets.widgets(), []) + + def test_single_widget(self): + self.assertEquals(self.moduleWithOneWidget.widgets(), [self.widget]) + + def test_multiple_widgets(self): + for widget in self.moduleWithMultipleWidgets.widgets(): + self.assertEquals(widget, self.widget) + + def test_retrieve_widget_by_name(self): + widget = MockWidget(self.anyWidgetName) + widget.name = self.anyWidgetName + module = Module(engine=None, widgets=[self.widget, widget, self.widget]) + retrievedWidget = module.widget(self.anyWidgetName) + self.assertEquals(retrievedWidget, widget) + + def test_retrieve_widget_by_id(self): + widget = MockWidget(self.anyWidgetName) + widget.id = self.anyWidgetName + module = Module(engine=None, widgets=[self.widget, widget, self.widget]) + retrievedWidget = module.widget_by_id(self.anyWidgetName) + self.assertEquals(retrievedWidget, widget) + + def test_retrieve_missing_widget(self): + module = self.moduleWithMultipleWidgets + + widget = module.widget(self.noSuchModule) + self.assertEquals(widget, None) + + widget = module.widget_by_id(self.noSuchModule) + self.assertEquals(widget, None) + + def test_threshold(self): + module = self.moduleWithOneWidget + module.name = self.widgetName + + self.config.set("{}.critical".format(self.widgetName), 10.0) + self.config.set("{}.warning".format(self.widgetName), 8.0) + self.assertEquals("critical", module.threshold_state(10.1, 0, 0)) + self.assertEquals("warning", module.threshold_state(10.0, 0, 0)) + self.assertEquals(None, module.threshold_state(8.0, 0, 0)) + self.assertEquals(None, module.threshold_state(7.9, 0, 0)) + + def test_parameters(self): + self.assertEquals(self.anyModule.parameter(self.anyKey), self.anyValue) + self.assertEquals(self.anotherModule.parameter(self.anyKey), self.anotherValue) + + def test_default_parameters(self): + self.assertEquals(self.anyModule.parameter(self.emptyKey), None) + self.assertEquals(self.anyModule.parameter(self.emptyKey, self.anyValue), self.anyValue) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/test_modules.py b/i3/.config/i3/bar/tests/test_modules.py new file mode 100644 index 0000000..4b280a3 --- /dev/null +++ b/i3/.config/i3/bar/tests/test_modules.py @@ -0,0 +1,58 @@ +# pylint: disable=C0103,C0111 + +import mock +import unittest +import importlib + +import tests.mocks as mocks + +from bumblebee.engine import all_modules +from bumblebee.output import Widget +from bumblebee.config import Config + +class TestGenericModules(unittest.TestCase): + def setUp(self): + engine = mock.Mock() + engine.input = mock.Mock() + config = Config() + self.objects = {} + + self.popen = mocks.MockPopen() + self.popen.mock.communicate.return_value = (str.encode("1"), "error") + self.popen.mock.returncode = 0 + + self._platform = mock.patch("bumblebee.modules.kernel.platform") + self.platform = self._platform.start() + self.platform.release.return_value = "unknown linux v1" + + for mod in all_modules(): + name = "bumblebee.modules.{}".format(mod["name"]) + cls = importlib.import_module(name) + self.objects[mod["name"]] = getattr(cls, "Module")(engine, {"config": config}) + for widget in self.objects[mod["name"]].widgets(): + self.assertEquals(widget.get("variable", None), None) + + def tearDown(self): + self._platform.stop() + self.popen.cleanup() + + def test_widgets(self): + for mod in self.objects: + widgets = self.objects[mod].widgets() + for widget in widgets: + widget.link_module(self.objects[mod]) + self.assertEquals(widget.module, mod) + self.assertTrue(isinstance(widget, Widget)) + self.assertTrue(hasattr(widget, "full_text")) + widget.set("variable", "value") + self.assertEquals(widget.get("variable", None), "value") + self.assertTrue(isinstance(widget.full_text(), str) or isinstance(widget.full_text(), unicode)) + + def test_update(self): + for mod in self.objects: + widgets = self.objects[mod].widgets() + self.objects[mod].update(widgets) + self.test_widgets() + self.assertEquals(widgets, self.objects[mod].widgets()) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/test_store.py b/i3/.config/i3/bar/tests/test_store.py new file mode 100644 index 0000000..faa1efb --- /dev/null +++ b/i3/.config/i3/bar/tests/test_store.py @@ -0,0 +1,26 @@ +# pylint: disable=C0103,C0111,W0703 + +import unittest + +from bumblebee.store import Store + +class TestStore(unittest.TestCase): + def setUp(self): + self.store = Store() + self.anyKey = "some-key" + self.anyValue = "some-value" + self.unsetKey = "invalid-key" + + def test_set_value(self): + self.store.set(self.anyKey, self.anyValue) + self.assertEquals(self.store.get(self.anyKey), self.anyValue) + + def test_get_invalid_value(self): + result = self.store.get(self.unsetKey) + self.assertEquals(result, None) + + def test_get_invalid_with_default_value(self): + result = self.store.get(self.unsetKey, self.anyValue) + self.assertEquals(result, self.anyValue) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/test_theme.py b/i3/.config/i3/bar/tests/test_theme.py new file mode 100644 index 0000000..747d749 --- /dev/null +++ b/i3/.config/i3/bar/tests/test_theme.py @@ -0,0 +1,134 @@ +# pylint: disable=C0103,C0111,W0703 + +import mock +import unittest +from bumblebee.theme import Theme +from bumblebee.error import ThemeLoadError +from tests.mocks import MockWidget + +class TestTheme(unittest.TestCase): + def setUp(self): + self.nonexistentThemeName = "no-such-theme" + self.invalidThemeName = "test_invalid" + self.validThemeName = "test" + self.validThemeSeparator = " * " + self.themedWidget = MockWidget("bla") + self.theme = Theme(self.validThemeName) + self.cycleTheme = Theme("test_cycle") + self.anyModule = mock.Mock() + self.anyWidget = MockWidget("bla") + self.anotherWidget = MockWidget("blub") + + self.anyModule.state.return_value = "state-default" + + self.anyWidget.link_module(self.anyModule) + self.themedWidget.link_module(self.anyModule) + + data = self.theme.data() + self.widgetTheme = "test-widget" + self.themedWidget.module = self.widgetTheme + self.defaultColor = data["defaults"]["fg"] + self.defaultBgColor = data["defaults"]["bg"] + self.widgetColor = data[self.widgetTheme]["fg"] + self.widgetBgColor = data[self.widgetTheme]["bg"] + self.defaultPrefix = data["defaults"]["prefix"] + self.defaultSuffix = data["defaults"]["suffix"] + self.widgetPrefix = data[self.widgetTheme]["prefix"] + self.widgetSuffix = data[self.widgetTheme]["suffix"] + + def test_load_valid_theme(self): + try: + Theme(self.validThemeName) + except Exception as e: + self.fail(e) + + def test_load_nonexistent_theme(self): + with self.assertRaises(ThemeLoadError): + Theme(self.nonexistentThemeName) + + def test_load_invalid_theme(self): + with self.assertRaises(ThemeLoadError): + Theme(self.invalidThemeName) + + def test_default_prefix(self): + self.assertEquals(self.theme.prefix(self.anyWidget), self.defaultPrefix) + + def test_default_suffix(self): + self.assertEquals(self.theme.suffix(self.anyWidget), self.defaultSuffix) + + def test_widget_prefix(self): + self.assertEquals(self.theme.prefix(self.themedWidget), self.widgetPrefix) + + def test_widget_fg(self): + self.assertEquals(self.theme.fg(self.anyWidget), self.defaultColor) + self.anyWidget.module = self.widgetTheme + self.assertEquals(self.theme.fg(self.anyWidget), self.widgetColor) + + def test_widget_bg(self): + self.assertEquals(self.theme.bg(self.anyWidget), self.defaultBgColor) + self.anyWidget.module = self.widgetTheme + self.assertEquals(self.theme.bg(self.anyWidget), self.widgetBgColor) + + def test_absent_cycle(self): + theme = self.theme + try: + theme.fg(self.anyWidget) + theme.fg(self.anotherWidget) + except Exception as e: + self.fail(e) + + def test_reset(self): + theme = self.cycleTheme + data = theme.data() + theme.reset() + self.assertEquals(theme.fg(self.anyWidget), data["cycle"][0]["fg"]) + self.assertEquals(theme.fg(self.anotherWidget), data["cycle"][1]["fg"]) + theme.reset() + self.assertEquals(theme.fg(self.anyWidget), data["cycle"][0]["fg"]) + + def test_separator_block_width(self): + theme = self.theme + data = theme.data() + + self.assertEquals(theme.separator_block_width(self.anyWidget), + data["defaults"]["separator-block-width"] + ) + + def test_separator(self): + for theme in [self.theme, self.cycleTheme]: + theme.reset() + prev_bg = theme.bg(self.anyWidget) + theme.bg(self.anotherWidget) + + self.assertEquals(theme.separator_fg(self.anotherWidget), theme.bg(self.anotherWidget)) + self.assertEquals(theme.separator_bg(self.anotherWidget), prev_bg) + + def test_state(self): + theme = self.theme + data = theme.data() + + self.assertEquals(theme.fg(self.anyWidget), data["defaults"]["fg"]) + self.assertEquals(theme.bg(self.anyWidget), data["defaults"]["bg"]) + + self.anyModule.state.return_value = "critical" + self.assertEquals(theme.fg(self.anyWidget), data["defaults"]["critical"]["fg"]) + self.assertEquals(theme.bg(self.anyWidget), data["defaults"]["critical"]["bg"]) + self.assertEquals(theme.fg(self.themedWidget), data[self.widgetTheme]["critical"]["fg"]) + # if elements are missing in the state theme, they are taken from the + # widget theme instead (i.e. no fallback to a more general state theme) + self.assertEquals(theme.bg(self.themedWidget), data[self.widgetTheme]["bg"]) + + def test_separator(self): + self.assertEquals(self.validThemeSeparator, self.theme.separator(self.anyWidget)) + + def test_list(self): + theme = self.theme + data = theme.data()[self.widgetTheme]["cycle-test"]["fg"] + self.anyModule.state.return_value = "cycle-test" + self.assertTrue(len(data) > 1) + + for idx in range(0, len(data)): + self.assertEquals(theme.fg(self.themedWidget), data[idx]) + self.assertEquals(theme.fg(self.themedWidget), data[0]) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/tests/test_util.py b/i3/.config/i3/bar/tests/test_util.py new file mode 100644 index 0000000..1988756 --- /dev/null +++ b/i3/.config/i3/bar/tests/test_util.py @@ -0,0 +1,56 @@ +# pylint: disable=C0103,C0111 + +import mock +import unittest + +import tests.mocks as mocks + +from bumblebee.util import * + +class TestUtil(unittest.TestCase): + def setUp(self): + self.popen = mocks.MockPopen("bumblebee.util") + self.some_command_with_args = "sample-command -a -b -c" + self.some_utf8 = "some string".encode("utf-8") + + def tearDown(self): + self.popen.cleanup() + + def test_bytefmt(self): + self.assertEquals(bytefmt(10), "10.00B") + self.assertEquals(bytefmt(15*1024), "15.00KiB") + self.assertEquals(bytefmt(20*1024*1024), "20.00MiB") + self.assertEquals(bytefmt(22*1024*1024*1024), "22.00GiB") + self.assertEquals(bytefmt(35*1024*1024*1024*1024), "35840.00GiB") + + def test_durationfmt(self): + self.assertEquals(durationfmt(00), "00:00") + self.assertEquals(durationfmt(25), "00:25") + self.assertEquals(durationfmt(60), "01:00") + self.assertEquals(durationfmt(119), "01:59") + self.assertEquals(durationfmt(3600), "01:00:00") + self.assertEquals(durationfmt(7265), "02:01:05") + + def test_execute(self): + execute(self.some_command_with_args) + self.assertTrue(self.popen.mock.popen.called) + self.popen.mock.popen.assert_call(self.some_command_with_args) + self.assertTrue(self.popen.mock.communicate.called) + + def test_execute_nowait(self): + execute(self.some_command_with_args, False) + self.assertTrue(self.popen.mock.popen.called) + self.popen.mock.popen.assert_call(self.some_command_with_args) + self.assertFalse(self.popen.mock.communicate.called) + + def test_execute_utf8(self): + self.popen.mock.communicate.return_value = [ self.some_utf8, None ] + self.test_execute() + + def test_execute_error(self): + self.popen.mock.returncode = 1 + + with self.assertRaises(RuntimeError): + execute(self.some_command_with_args) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/i3/.config/i3/bar/themes/default.json b/i3/.config/i3/bar/themes/default.json new file mode 100644 index 0000000..ddda5fe --- /dev/null +++ b/i3/.config/i3/bar/themes/default.json @@ -0,0 +1,7 @@ +{ + "icons": [ "ascii" ], + "defaults": { + "urgent": true, + "fg": "#aabbcc" + } +} diff --git a/i3/.config/i3/bar/themes/gruvbox-powerline.json b/i3/.config/i3/bar/themes/gruvbox-powerline.json new file mode 100644 index 0000000..5cbfdc0 --- /dev/null +++ b/i3/.config/i3/bar/themes/gruvbox-powerline.json @@ -0,0 +1,41 @@ +{ + "icons": [ "paxy97", "awesome-fonts" ], + "defaults": { + "warning": { + "fg": "#1d2021", + "bg": "#d79921" + }, + "critical": { + "fg": "#fbf1c7", + "bg": "#cc241d" + }, + "default-separators": false, + "separator-block-width": 0 + }, + "cycle": [ + { + "fg": "#ebdbb2", + "bg": "#1d2021" + }, + { + "fg": "#fbf1c7", + "bg": "#282828" + } + ], + "dnf": { + "good": { + "fg": "#002b36", + "bg": "#859900" + } + }, + "battery": { + "charged": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "AC": { + "fg": "#1d2021", + "bg": "#b8bb26" + } + } +} diff --git a/i3/.config/i3/bar/themes/gruvbox.json b/i3/.config/i3/bar/themes/gruvbox.json new file mode 100644 index 0000000..eee93c1 --- /dev/null +++ b/i3/.config/i3/bar/themes/gruvbox.json @@ -0,0 +1,41 @@ +{ + "icons": [ "paxy97", "ascii" ], + "defaults": { + "warning": { + "fg": "#1d2021", + "bg": "#d79921" + }, + "critical": { + "fg": "#fbf1c7", + "bg": "#cc241d" + }, + "default-separators": false, + "separator-block-width": 0 + }, + "cycle": [ + { + "fg": "#ebdbb2", + "bg": "#1d2021" + }, + { + "fg": "#fbf1c7", + "bg": "#282828" + } + ], + "dnf": { + "good": { + "fg": "#002b36", + "bg": "#859900" + } + }, + "battery": { + "charged": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "AC": { + "fg": "#1d2021", + "bg": "#b8bb26" + } + } +} diff --git a/i3/.config/i3/bar/themes/icons/ascii.json b/i3/.config/i3/bar/themes/icons/ascii.json new file mode 100644 index 0000000..9b03a46 --- /dev/null +++ b/i3/.config/i3/bar/themes/icons/ascii.json @@ -0,0 +1,70 @@ +{ + "defaults": { + "padding": " " + }, + "memory": { "prefix": "ram" }, + "cpu": { "prefix": "cpu" }, + "disk": { "prefix": "hdd" }, + "dnf": { "prefix": "dnf" }, + "brightness": { "prefix": "o" }, + "cmus": { + "playing": { "prefix": ">" }, + "paused": { "prefix": "||" }, + "stopped": { "prefix": "[]" }, + "prev": { "prefix": "|<" }, + "next": { "prefix": ">|" }, + "shuffle-on": { "prefix": "S" }, + "shuffle-off": { "prefix": "[s]" }, + "repeat-on": { "prefix": "R" }, + "repeat-off": { "prefix": "[r]" } + }, + "pasink": { + "muted": { "prefix": "audio(mute)" }, + "unmuted": { "prefix": "audio" } + }, + "amixer": { + "muted": { "prefix": "audio(mute)" }, + "unmuted": { "prefix": "audio" } + }, + "pasource": { + "muted": { "prefix": "mic(mute)" }, + "unmuted": { "prefix": "mic" } + }, + "nic": { + "wireless-up": { "prefix": "wifi" }, + "wireless-down": { "prefix": "wifi" }, + "wired-up": { "prefix": "lan" }, + "wired-down": { "prefix": "lan" }, + "tunnel-up": { "prefix": "tun" }, + "tunnel-down": { "prefix": "tun" } + }, + "battery": { + "charged": { "suffix": "full" }, + "charging": { "suffix": "chr" }, + "AC": { "suffix": "ac" }, + "discharging-10": { + "prefix": "!", + "suffix": "dis" + }, + "discharging-25": { "suffix": "dis" }, + "discharging-50": { "suffix": "dis" }, + "discharging-80": { "suffix": "dis" }, + "discharging-100": { "suffix": "dis" } + }, + "caffeine": { + "activated": {"prefix": "caf-on" }, "deactivated": { "prefix": "caf-off " } + }, + "xrandr": { + "on": { "prefix": " off "}, "off": { "prefix": " on "} + }, + "redshift": { + "day": { "prefix": "day" }, "night": { "prefix": "night" }, "transition": { "prefix": "trans" } + }, + "sensors": { + "prefix": "sensors" + }, + "traffic": { + "rx": { "prefix": "down"}, + "tx": { "prefix": "up"} + } +} diff --git a/i3/.config/i3/bar/themes/icons/awesome-fonts.json b/i3/.config/i3/bar/themes/icons/awesome-fonts.json new file mode 100644 index 0000000..4263a19 --- /dev/null +++ b/i3/.config/i3/bar/themes/icons/awesome-fonts.json @@ -0,0 +1,86 @@ +{ + "defaults": { + "separator": "", "padding": " ", + "unknown": { "prefix": "" } + }, + "date": { "prefix": "" }, + "time": { "prefix": "" }, + "memory": { "prefix": "" }, + "cpu": { "prefix": "" }, + "disk": { "prefix": "" }, + "dnf": { "prefix": "" }, + "pacman": { "prefix": "" }, + "brightness": { "prefix": "" }, + "load": { "prefix": "" }, + "layout": { "prefix": "" }, + "cmus": { + "playing": { "prefix": "" }, + "paused": { "prefix": "" }, + "stopped": { "prefix": "" }, + "prev": { "prefix": "" }, + "next": { "prefix": "" }, + "shuffle-on": { "prefix": "" }, + "shuffle-off": { "prefix": "" }, + "repeat-on": { "prefix": "" }, + "repeat-off": { "prefix": "" } + }, + "gpmdp": { + "playing": { "prefix": "" }, + "paused": { "prefix": "" }, + "stopped": { "prefix": "" }, + "prev": { "prefix": "" }, + "next": { "prefix": "" } + }, + "pasink": { + "muted": { "prefix": "" }, + "unmuted": { "prefix": "" } + }, + "amixer": { + "muted": { "prefix": "" }, + "unmuted": { "prefix": "" } + }, + "pasource": { + "muted": { "prefix": "" }, + "unmuted": { "prefix": "" } + }, + "kernel": { + "prefix": "\uf17c" + }, + "nic": { + "wireless-up": { "prefix": "" }, + "wireless-down": { "prefix": "" }, + "wired-up": { "prefix": "" }, + "wired-down": { "prefix": "" }, + "tunnel-up": { "prefix": "" }, + "tunnel-down": { "prefix": "" } + }, + "battery": { + "charged": { "prefix": "", "suffix": "" }, + "AC": { "suffix": "" }, + "charging": { + "prefix": [ "", "", "", "", "" ], + "suffix": "" + }, + "discharging-10": { "prefix": "", "suffix": "" }, + "discharging-25": { "prefix": "", "suffix": "" }, + "discharging-50": { "prefix": "", "suffix": "" }, + "discharging-80": { "prefix": "", "suffix": "" }, + "discharging-100": { "prefix": "", "suffix": "" } + }, + "caffeine": { + "activated": {"prefix": " " }, "deactivated": { "prefix": " " } + }, + "xrandr": { + "on": { "prefix": " "}, "off": { "prefix": " " } + }, + "redshift": { + "day": { "prefix": "" }, "night": { "prefix": "" }, "transition": { "prefix": "" } + }, + "sensors": { + "prefix": "🌡" + }, + "traffic":{ + "rx": { "prefix": "" }, + "tx": { "prefix": "" } + } +} diff --git a/i3/.config/i3/bar/themes/icons/paxy97.json b/i3/.config/i3/bar/themes/icons/paxy97.json new file mode 100644 index 0000000..9a889ab --- /dev/null +++ b/i3/.config/i3/bar/themes/icons/paxy97.json @@ -0,0 +1,5 @@ +{ + "memory": { + "prefix": "  " + } +} diff --git a/i3/.config/i3/bar/themes/icons/test.json b/i3/.config/i3/bar/themes/icons/test.json new file mode 100644 index 0000000..ad17178 --- /dev/null +++ b/i3/.config/i3/bar/themes/icons/test.json @@ -0,0 +1,6 @@ +{ + "test-widget": { + "prefix": "widget-prefix", + "suffix": "widget-suffix" + } +} diff --git a/i3/.config/i3/bar/themes/kulade.json b/i3/.config/i3/bar/themes/kulade.json new file mode 100644 index 0000000..40f64e9 --- /dev/null +++ b/i3/.config/i3/bar/themes/kulade.json @@ -0,0 +1,46 @@ +{ + "icons": [ "awesome-fonts" ], + "defaults": { + "separator-block-width": 0, + "warning": { + "fg": "#8C001A", + "bg": "#ffffff" + }, + "critical": { + "fg": "#ffffff", + "bg": "#8C001A" + }, + "good": { + "fg": "#000000", + "bg": "#008800" + }, + "great": { + "fg": "#000000", + "bg": "#008080" + }, + "mid": { + "fg": "#000000", + "bg": "#808000" + } + }, + "cycle": [ + { "fg": "#93a1a1", "bg": "#000000" }, + { "fg": "#eee8d5", "bg": "#333333" } + ], + "dnf": { + "good": { + "fg": "#002b36", + "bg": "#859900" + } + }, + "battery": { + "charged": { + "fg": "#000000", + "bg": "#008080" + }, + "AC": { + "fg": "#002b36", + "bg": "#859900" + } + } +} diff --git a/i3/.config/i3/bar/themes/powerline.json b/i3/.config/i3/bar/themes/powerline.json new file mode 100644 index 0000000..80be8cf --- /dev/null +++ b/i3/.config/i3/bar/themes/powerline.json @@ -0,0 +1,41 @@ +{ + "icons": [ "awesome-fonts" ], + "defaults": { + "separator-block-width": 0, + "critical": { + "fg": "#ffffff", + "bg": "#ff0000" + }, + "warning": { + "fg": "#d75f00", + "bg": "#ffd700" + }, + "default_separators": false + }, + "cycle": [ + { + "fg": "#ffd700", + "bg": "#d75f00" + }, + { + "fg": "#ffffff", + "bg": "#0087af" + } + ], + "dnf": { + "good": { + "fg": "#494949", + "bg": "#41db00" + } + }, + "battery": { + "charged": { + "fg": "#494949", + "bg": "#41db00" + }, + "AC": { + "fg": "#494949", + "bg": "#41db00" + } + } +} diff --git a/i3/.config/i3/bar/themes/solarized-powerline.json b/i3/.config/i3/bar/themes/solarized-powerline.json new file mode 100644 index 0000000..e1fbe1d --- /dev/null +++ b/i3/.config/i3/bar/themes/solarized-powerline.json @@ -0,0 +1,40 @@ +{ + "icons": [ "awesome-fonts" ], + "defaults": { + "separator-block-width": 0, + "warning": { + "fg": "#002b36", + "bg": "#b58900" + }, + "critical": { + "fg": "#002b36", + "bg": "#dc322f" + } + }, + "cycle": [ + { "fg": "#93a1a1", "bg": "#002b36" }, + { "fg": "#eee8d5", "bg": "#586e75" } + ], + "dnf": { + "good": { + "fg": "#002b36", + "bg": "#859900" + } + }, + "pacman": { + "good": { + "fg": "#002b36", + "bg": "#859900" + } + }, + "battery": { + "charged": { + "fg": "#002b36", + "bg": "#859900" + }, + "AC": { + "fg": "#002b36", + "bg": "#859900" + } + } +} diff --git a/i3/.config/i3/bar/themes/solarized.json b/i3/.config/i3/bar/themes/solarized.json new file mode 100644 index 0000000..12a71ae --- /dev/null +++ b/i3/.config/i3/bar/themes/solarized.json @@ -0,0 +1,42 @@ +{ + "icons": [ "ascii" ], + "defaults": { + "separator-block-width": 0, + "critical": { + "fg": "#002b36", + "bg": "#dc322f" + }, + "warning": { + "fg": "#002b36", + "bg": "#b58900" + }, + "default_separators": false, + "separator": "" + }, + "cycle": [ + { + "fg": "#93a1a1", + "bg": "#002b36" + }, + { + "fg": "#eee8d5", + "bg": "#586e75" + } + ], + "dnf": { + "good": { + "fg": "#002b36", + "bg": "#859900" + } + }, + "battery": { + "charged": { + "fg": "#002b36", + "bg": "#859900" + }, + "AC": { + "fg": "#002b36", + "bg": "#859900" + } + } +} diff --git a/i3/.config/i3/bar/themes/test.json b/i3/.config/i3/bar/themes/test.json new file mode 100644 index 0000000..1d457a7 --- /dev/null +++ b/i3/.config/i3/bar/themes/test.json @@ -0,0 +1,25 @@ +{ + "icons": [ "test" ], + "defaults": { + "prefix": "default-prefix", + "suffix": "default-suffix", + "fg": "#000000", + "bg": "#111111", + "separator": " * ", + "separator-block-width": 10, + "critical": { + "fg": "#ffffff", + "bg": "#010101" + } + }, + "test-widget": { + "fg": "#ababab", + "bg": "#222222", + "critical": { + "fg": "#bababa" + }, + "cycle-test": { + "fg": [ "#000000", "#111111" ] + } + } +} diff --git a/i3/.config/i3/bar/themes/test_cycle.json b/i3/.config/i3/bar/themes/test_cycle.json new file mode 100644 index 0000000..5fd7e1a --- /dev/null +++ b/i3/.config/i3/bar/themes/test_cycle.json @@ -0,0 +1,18 @@ +{ + "icons": [ "test" ], + "defaults": { + "prefix": "default-prefix", + "suffix": "default-suffix", + "fg": "#000000", + "bg": "#111111" + }, + "cycle": [ + { "fg": "#aa0000" }, + { "fg": "#00aa00" }, + { "fg": "#0000aa" } + ], + "test-widget": { + "fg": "#ababab", + "bg": "#222222" + } +} diff --git a/i3/.config/i3/bar/themes/test_invalid.json b/i3/.config/i3/bar/themes/test_invalid.json new file mode 100644 index 0000000..d510f27 --- /dev/null +++ b/i3/.config/i3/bar/themes/test_invalid.json @@ -0,0 +1 @@ +this is really not json diff --git a/i3/.config/i3/config b/i3/.config/i3/config new file mode 100644 index 0000000..c634a1e --- /dev/null +++ b/i3/.config/i3/config @@ -0,0 +1,560 @@ +# vim: filetype=i3 + +###---Basic Definitions---### +#Needed for i3-gaps +hide_edge_borders none +for_window [class="^.*"] border pixel 0 +gaps inner 15 +gaps outer 15 +font pango:Inconsolata 9 +set $mod Mod4 +#set $term urxvt +set $video --no-startup-id bash ~/.config/Scripts/video.sh +set $stoprec --no-startup-id killall ffmpeg & killall screenkey +set $flash --no-startup-id bash ~/.config/Scripts/flash_win.sh +set $browser firefox +set $term st +#set $browser qutebrowser + + +###---Starting External Scripts---### +#Music player daemon: +exec --no-startup-id mpd + +#Wifi applet: +exec --no-startup-id nm-applet + +#Mount other drives: +exec --no-startup-id sudo mount -a + +#Composite manager: +#exec --no-startup-id compton --xrender-sync-fence +exec --no-startup-id compton + +#Add wallpaper: +exec --no-startup-id wal -c -i ~/.config/wall1.jpg + +#Try to load VGA screen if available: +exec --no-startup-id ~/.config/Scripts/screen.sh d + +#Remaps the caps lock button to escape and uses the US international keyboard with dead keys +exec_always --no-startup-id setxkbmap -layout us -variant altgr-intl -option caps:escape + +#Remap Menu button to Super: +exec_always --no-startup-id xmodmap -e 'keycode 135 = Super_R' +bindsym Menu exec --no-startup-id xmodmap -e 'keycode 135 = Super_R' + +#Start dropbox +#exec --no-startup-id dropbox + +#Start power manager +exec --no-startup-id xfce4-power-manager + +#Torrent daemon: +#exec --no-startup-id transmission-daemon + +#Refresh bash/ranger shortcuts: +#exec --no-startup-id python ~/.config/Scripts/shortcuts.py + +#Launch Polybar where appropriate: +exec_always --no-startup-id launch_polybar.sh + +#Unclutter makes the mouse invisible after a brief period +#exec --no-startup-id unclutter + +# Turn off touchpad +exec --no-startup-id exec synclient TouchpadOff=1 + +# Xflux +exec --no-startup-id /usr/bin/xflux -l 43.6404 -g -79.3995 + +# Set Xresources colors: +set_from_resource $darkblack color0 #000000 +set_from_resource $black color8 #000000 +set_from_resource $darkred color1 #000000 +set_from_resource $red color9 #000000 +set_from_resource $darkgreen color2 #000000 +set_from_resource $green color10 #000000 +set_from_resource $darkyellow color3 #000000 +set_from_resource $yellow color11 #000000 +set_from_resource $darkblue color4 #000000 +set_from_resource $blue color12 #000000 +set_from_resource $darkmagenta color5 #000000 +set_from_resource $magenta color13 #000000 +set_from_resource $darkcyan color6 #000000 +set_from_resource $cyan color14 #000000 +set_from_resource $darkwhite color7 #000000 +set_from_resource $white color15 #000000 +set $transparent #00000000 + +# BORDER BACKGROUND TEXT INDICATOR CHILD_BORDER +client.focused $red $red $magenta $darkmagenta $darkblue +client.unfocused $transparent $blue $white $darkblue $darkblack +client.focused_inactive $transparent $blue $white $darkblue $darkblack +client.urgent $darkred $darkred $black $darkred $darkred +client.background $black + +#Start i3bar +#bar { +# status_command i3blocks +# position top +# mode dock +# modifier None +# tray_padding 0 +# font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 +# font pango:Inconsolata +# #font pango:DejaVu Sans Mono 11 +# colors { +# background $darkblack +# statusline $darkwhite +# separator $cyan +# focused_workspace $blue $darkblue $darkblack +# active_workspace $blue $blue $darkwhite +# inactive_workspace $darkblack $darkblack $white +# urgent_workspace $darkblack $darkblack $white +# } +#} + +###---Audio and Music Definitions---### +##For mpc: +set $music --no-startup-id $term -e ncmpcpp +set $pause --no-startup-id mpc toggle +set $trupause --no-startup-id mpc pause +set $next --no-startup-id mpc next +set $prev --no-startup-id mpc prev +set $lilfor --no-startup-id mpc seek +10 +set $bigfor --no-startup-id mpc seek +120 +set $lilbak --no-startup-id mpc seek -10 +set $bigbak --no-startup-id mpc seek -120 +set $beg --no-startup-id mpc seek 0% + +##For moc: +#set $music --no-startup-id st -e mocp +#set $pause --no-startup-id mocp -G +#set $truepause --no-startup-id mocp -P +#set $next --no-startup-id mocp -f +#set $prev --no-startup-id mocp -r +#set $lilfor --no-startup-id mocp -k 10 +#set $bigfor --no-startup-id mocp -k 120 +#set $lilbak --no-startup-id mocp -k -10 +#set $bigbak --no-startup-id mocp -k -120 +#set $beg --no-startup-id mocp -j 0% + +##For cmus: +#set $music --no-startup-id st -e cmus +#set $pause --no-startup-id cmus-remote -u +#set $next --no-startup-id cmus-remote -n +#set $prev --no-startup-id cmus-remote -r +#set $lilfor --no-startup-id cmus-remote -k +10 +#set $bigfor --no-startup-id cmus-remote -k +120 +#set $lilbak --no-startup-id cmus-remote -k -10 +#set $bigbak --no-startup-id cmus-remote -k -120 +#set $beg --no-startup-id cmus-remote -k 00:00:00 + +#FOR ALSA/AMIXER +#set $inc --no-startup-id amixer sset Master 2%+ +#set $biginc --no-startup-id amixer sset Master 5%+ +#set $dec --no-startup-id amixer sset Master 2%- +#set $bigdec --no-startup-id amixer sset Master 5%- +#set $mute --no-startup-id amixer sset Master toggle +#set $truemute --no-startup-id amixer sset Master mute +#set $screencast --no-startup-id bash ~/.config/Scripts/screencast_alsa.sh +#set $audio --no-startup-id bash ~/.config/Scripts/audio_alsa.sh + +#For PULSEAUDIO/PAMIXER +set $inc --no-startup-id pamixer --allow-boost -i 5 +set $biginc --no-startup-id pamixer --allow-boost -i 15 +set $dec --no-startup-id pamixer --allow-boost -d 5 +set $bigdec --no-startup-id pamixer --allow-boost -d 15 +set $mute --no-startup-id pamixer --allow-boost -t +set $micmute --no-startup-id pamixer --allow-boost -t +set $truemute --no-startup-id pamixer -m +set $screencast --no-startup-id bash ~/.config/Scripts/screencast_pulse.sh +set $audio --no-startup-id bash ~/.config/Scripts/audio_pulse.sh + + +###---Dropdown/Scratchpad Windows---### +# create a misc terminal floating window with tmux +for_window [instance="dropdown"] floating enable +for_window [instance="dropdown"] resize set 675 450 +#for_window [instance="dropdown"] move position center +for_window [instance="dropdown"] move scratchpad +for_window [instance="dropdown"] border pixel 3 +#exec --no-startup-id $term -name dropdown -e tmux -f ~/.config/tmux/tmux_start.conf attach +exec --no-startup-id $term -n dropdown -e tmux -f ~/.config/tmux/tmux_start.conf attach + +# create a math floating window with R +for_window [instance="math"] floating enable +for_window [instance="math"] resize set 600 400 +for_window [instance="math"] move scratchpad +for_window [instance="math"] border pixel 3 +#exec --no-startup-id $term -name dropdown -e tmux -f ~/.config/tmux/tmux_start.conf attach +exec --no-startup-id $term -n math -e R + + +###---Basic Bindings---### +bindsym $mod+Return exec $term +#bindsym $mod+Shift+Return exec $term -e tmux -f ~/.config/tmux/tmux.conf +bindsym $mod+Shift+Return exec $term -e tmux_connect.sh + +bindsym $mod+Shift+space floating toggle +bindsym $mod+space focus mode_toggle + +bindsym $mod+Escape workspace prev +#bindsym $mod+Shift+Escape exec i3-msg exit +set $signout "Are you sure you want to logout?" + +mode $signout { + bindsym y exec --no-startup-id i3exit logout, mode "default" + bindsym n mode default + bindsym Escape mode default +} + +bindsym $mod+Shift+Escape mode $signout + +bindsym $mod+BackSpace exec $flash +bindsym $mod+Shift+BackSpace exec sudo reboot + +bindsym $mod+grave exec $flash +#bindsym $mod+asciitilde +#STOP/HIDE EVERYTHING: +bindsym $mod+Shift+Delete exec $truemute ; exec $truepause ; workspace lmao ; exec $term -e htop ; exec $term -e ranger + + +###---Letter Key Bindings---### +bindsym $mod+q kill +bindsym $mod+Shift+q kill + +bindsym $mod+w exec --no-startup-id $browser -private-window +bindsym $mod+Shift+w exec --no-startup-id $browser + +bindsym $mod+e exec $term -n email -e neomutt +#bindsym $mod+e exec $term -name email -e neomutt +#bindsym $mod+Shift+e + +bindsym $mod+r exec $term -e vifm +bindsym $mod+Shift+r restart + +bindsym $mod+t split toggle +bindsym $mod+Shift+t gaps inner current set 25; gaps outer current set 25 + +#bindsym $mod+y exec $term -e R and +bindsym $mod+y [instance="math"] scratchpad show; move position center +bindsym $mod+Shift+y resize shrink width 10 px or 10ppt + +bindsym $mod+u [instance="dropdown"] scratchpad show; move position center +bindsym $mod+Shift+u resize shrink height 10 px or 10 ppt + +bindsym $mod+i exec $term -e htop +bindsym $mod+Shift+i resize grow height 10 px or 10 ppt + +bindsym $mod+o exec --no-startup-id $beg +bindsym $mod+Shift+o resize grow width 10 px or 10 ppt + +bindsym $mod+p exec $pause +bindsym $mod+Shift+p exec keepass + +#bindsym $mod+a [instance="math"] scratchpad show; move position center +bindsym $mod+Shift+a exec $term -e alsamixer + +bindsym $mod+s gaps inner current plus 5 +bindsym $mod+Shift+s gaps inner current minus 5 + +bindsym $mod+d exec rofi -show run +bindsym $mod+Shift+d gaps inner current set 0; gaps outer current set 0 + +bindsym $mod+f fullscreen toggle +bindsym $mod+Shift+f exec --no-startup-id cat ~/.emoji | rofi -dmenu -i -lines 10 -p Search | awk '{print $1}' | tr -d '\n' | xclip -selection clipboard && notify-send --urgency=low "Copied to Clipboard" +#bindsym $mod+Shift+f exec --no-startup-id exec ~/.config/rofi/rofi-fontawesome/fontawesome-menu/fontawesome-menu -f /home/kdam0/.config/rofi/rofi-fontawesome/fontawesome-menu/fa5-icon-list.txt -o '-i -columns 4' + + +bindsym $mod+g layout toggle tabbed split +#bindsym $mod+Shift+g exec --no-startup-id gimp; workspace $ws5 + +bindsym $mod+h focus left +bindsym $mod+Shift+h move left 30 + +bindsym $mod+j focus down +bindsym $mod+Shift+j move down 30 + +bindsym $mod+k focus up +bindsym $mod+Shift+k move up 30 + +bindsym $mod+l focus right +bindsym $mod+Shift+l move right 30 + +bindsym $mod+z gaps outer current plus 5 +bindsym $mod+Shift+z gaps outer current minus 5 + +bindsym $mod+x exec --no-startup-id ~/.config/i3/lock.sh +bindsym $mod+Shift+x exec --no-startup-id i3exit shutdown + +#bindsym $mod+c exec --no-startup-id firefox -private-window +#bindsym $mod+Shift+c + +bindsym $mod+v exec $term -e ncmpcpp -s visualizer +#bindsym $mod+Shift+v + +bindsym $mod+b exec $prev +#bindsym $mod+Shift+b exec --no-startup-id blender; workspace $ws6 + +bindsym $mod+n exec $next +#bindsym $mod+Shift+n exec xdotool mousemove 1279 0 + +bindsym $mod+m exec --no-startup-id $term -e ncmpcpp +bindsym $mod+Shift+m exec $mute + + +###---Workspace Bindings---### +bindsym $mod+Home workspace $ws1 +bindsym $mod+Shift+Home move container to workspace $ws1 +bindsym $mod+End workspace $ws10 +bindsym $mod+Shift+End move container to workspace $ws10 +bindsym $mod+Prior workspace prev +bindsym $mod+Shift+Prior move container to workspace prev +bindsym $mod+Next workspace next +bindsym $mod+Shift+Next move container to workspace next +bindsym $mod+Tab workspace back_and_forth +bindsym $mod+XF86Back workspace prev +#bindsym $mod+Shift+XF86Back +bindsym $mod+XF86Forward workspace next +#bindsym $mod+Shift+XF86Forward +bindsym $mod+semicolon workspace next +bindsym $mod+apostrophe split horizontal ;; exec $term +bindsym $mod+slash split vertical ;; exec $term +bindsym $mod+Shift+slash kill +bindsym $mod+backslash workspace back_and_forth + +#set $ws1 "1: " +#set $ws2 "2: " +#set $ws3 "3: " +#set $ws4 "4: " +#set $ws5 "5: " +#set $ws6 "6: " +#set $ws7 "7: " +#set $ws8 "8: " +#set $ws9 "9: " +#set $ws10 "10: " + +set $ws1 "1" +set $ws2 "2" +set $ws3 "3" +set $ws4 "4" +set $ws5 "5" +set $ws6 "6" +set $ws7 "7" +set $ws8 "8" +set $ws9 "9" +set $ws10 "10" + +# switch to workspace +bindsym $mod+1 workspace $ws1 +bindsym $mod+2 workspace $ws2 +bindsym $mod+3 workspace $ws3 +bindsym $mod+4 workspace $ws4 +bindsym $mod+5 workspace $ws5 +bindsym $mod+6 workspace $ws6 +bindsym $mod+7 workspace $ws7 +bindsym $mod+8 workspace $ws8 +bindsym $mod+9 workspace $ws9 +bindsym $mod+0 workspace $ws10 + +# move focused container to workspace +bindsym $mod+Shift+1 move container to workspace $ws1 +bindsym $mod+Shift+2 move container to workspace $ws2 +bindsym $mod+Shift+3 move container to workspace $ws3 +bindsym $mod+Shift+4 move container to workspace $ws4 +bindsym $mod+Shift+5 move container to workspace $ws5 +bindsym $mod+Shift+6 move container to workspace $ws6 +bindsym $mod+Shift+7 move container to workspace $ws7 +bindsym $mod+Shift+8 move container to workspace $ws8 +bindsym $mod+Shift+9 move container to workspace $ws9 +bindsym $mod+Shift+0 move container to workspace $ws10 + +for_window [window_role="gimp-dock"] floating disable; move left; resize shrink width 50 px or 50ppt +for_window [window_role="gimp-toolbox"] floating disable; move right; resize grow width 30 px or 30ppt +for_window [window_role="GtkFileChooserDialog"] resize shrink height 10 px +#for_window [class="st"] exec --no-startup-id transset -a --dec .25 +#for_window [class="Firefox"] exec --no-startup-id transset -a --dec .25 + +assign [class="KeePass2"] $ws4 +assign [title="GIMP Startup"] $ws5 +assign [class="Gimp"] $ws5 +assign [class="Ghetto Skype"] $ws6 +assign [class="messenger for desktop"] $ws7 +assign [class="whats-app-nativefier-7bbd2c"] $ws7 +assign [class="Firefox"] $ws8 +assign [class="qutebrowser"] $ws8 +assign [title="neomutt"] $ws9 +#assign [class="Geary"] $ws9 + +#workspace $ws8 gaps inner 0 +#workspace $ws8 gaps outer 0 +#workspace $ws7 gaps inner 0 +#workspace $ws7 gaps outer 0 +#workspace $ws8 gaps inner 0 +#workspace $ws8 gaps outer 0 + + +###---Function Buttons---### +#bindsym F1 nop +#bindsym F2 +#bindsym F3 +#bindsym F4 +#bindsym F5 +#bindsym F6 +#bindsym F7 +#bindsym F8 +#bindsym F9 +#bindsym F10 +bindsym F11 fullscreen toggle +#bindsym F12 + +#bindsym $mod+F1 exec --no-startup-id mupdf ~/.config/Scripts/i3_guide.md.pdf +bindsym $mod+F1 exec --no-startup-id ~/.config/Scripts/get_email.sh +bindsym $mod+F2 exec --no-startup-id python ~/.config/Scripts/shortcuts.py +bindsym $mod+F3 exec --no-startup-id arandr +bindsym $mod+F4 exec --no-startup-id i3exit suspend +bindsym $mod+F5 exec --no-startup-id sudo sv restart NetworkManager +bindsym $mod+F6 exec --no-startup-id transmission-gtk +bindsym $mod+F7 exec --no-startup-id screen.sh l +bindsym $mod+F8 exec --no-startup-id screen.sh d +bindsym $mod+Shift+F7 exec transset-df -a --dec .15 +bindsym $mod+Shift+F8 exec transset-df -a --inc .15 +bindsym $mod+F9 exec --no-startup-id sudo mount -a +#bindsym $mod+F10 exec --no-startup-id ~/.config/Scripts/screen.sh l ;; exec --no-startup-id ~/.config/polybar/launch.sh +#bindsym $mod+F11 exec --no-startup-id ~/.config/Scripts/screen.sh v ;; exec --no-startup-id ~/.config/polybar/launch.sh +#bindsym $mod+F12 exec --no-startup-id ~/.config/Scripts/screen.sh d ;; exec --no-startup-id ~/.config/polybar/launch.sh + + +###---Arrow Keys---### +bindsym $mod+Left focus left +#bindsym $mod+Down focus down +#bindsym $mod+Up focus up +bindsym $mod+Right focus right +bindsym $mod+Shift+Left move left +bindsym $mod+Shift+Down move down +bindsym $mod+Shift+Up move up +bindsym $mod+Shift+Right move right + + +###---Media Keys---### + +#Volume keys +bindsym $mod+plus exec $inc +bindsym $mod+Shift+plus exec $biginc +bindsym $mod+minus exec $dec +bindsym $mod+Shift+minus exec $bigdec +bindsym $mod+greater exec $inc +bindsym $mod+Shift+greater exec $biginc +bindsym $mod+less exec $dec +bindsym $mod+Shift+less exec $bigdec + +#For advancing forward/backward in an mpd song +bindsym $mod+bracketleft exec $lilbak +bindsym $mod+Shift+bracketleft exec $bigbak +bindsym $mod+bracketright exec $lilfor +bindsym $mod+Shift+bracketright exec $bigfor + +#Print screen takes a screenshot. +#With shift, it takes one only of the given window. +bindsym Print exec --no-startup-id scrot +bindsym Shift+Print exec --no-startup-id scrot -u + +#For recording audio/video or stop recording. Also screenkey for visual typing. +bindsym $mod+Print exec --no-startup-id screenkey +bindsym $mod+Scroll_Lock exec $audio +bindsym $mod+Insert exec $screencast +bindsym $mod+Pause exec $video +bindsym $mod+Delete exec $stoprec +bindsym XF86Launch1 exec $stoprec + + + +###---Extra XF86 Keys---### +#These are the extra media keys that some keyboards have. +bindsym XF86AudioMute exec $mute +bindsym XF86AudioLowerVolume exec $dec +bindsym XF86AudioRaiseVolume exec $inc +bindsym XF86PowerOff exec sudo shutdown -h now +#bindsym XF86Copy exec +#bindsym XF86Open exec +#bindsym XF86Paste exec +#bindsym XF86Cut exec +#bindsym XF86MenuKB exec +bindsym XF86Calculator exec $term -e R -q +bindsym XF86Sleep exec sudo zzz +#bindsym XF86WakeUp exec +bindsym XF86Explorer exec $term -e ranger +#bindsym XF86Send exec +#bindsym XF86Xfer exec +bindsym XF86WWW exec --no-startup-id $browser +#bindsym XF86DOS exec +bindsym XF86ScreenSaver exec exec --no-startup-id ~/.config/i3/lock.sh +#bindsym XF86RotateWindows exec ~/.config/Scripts/screen_rotate.sh +bindsym XF86TaskPane exec ~/.config/Scripts/screen_rotate_clk.sh +#bindsym XF86Favorites exec +bindsym XF86MyComputer exec $term -e ranger +#bindsym XF86Back exec +#bindsym XF86Forward exec +bindsym XF86Eject exec sudo umount -R /mnt +bindsym XF86AudioNext exec $next +bindsym XF86AudioPlay exec $pause +bindsym XF86AudioPrev exec $prev +bindsym XF86AudioStop exec $pause +bindsym XF86AudioRecord exec $audio +bindsym XF86AudioRewind exec $lilbak +bindsym XF86AudioForward exec $lilfor +#bindsym XF86Phone exec +#bindsym XF86Tools exec +bindsym XF86Reload restart +#bindsym XF86ScrollUp exec +#bindsym XF86ScrollDown exec +#bindsym XF86New exec +#bindsym XF86LaunchA exec +#bindsym XF86LaunchB exec +#bindsym XF86Launch2 exec +#bindsym XF86Launch3 exec +#bindsym XF86Launch4 exec +#bindsym XF86Launch5 exec +#bindsym XF86Launch6 exec +#bindsym XF86Launch7 exec +#bindsym XF86Launch8 exec +#bindsym XF86Launch9 exec +bindsym XF86AudioMicMute exec $micmute +#bindsym XF86TouchpadToggle exec +bindsym XF86TouchpadOn exec --no-startup-id exec synclient TouchpadOff=0 +bindsym XF86TouchpadOff exec --no-startup-id exec synclient TouchpadOff=1 +bindsym XF86Suspend exec --no-startup-id ~/.config/i3/lock.sh +bindsym XF86Close kill +bindsym XF86WebCam exec mpv /dev/video0 +bindsym XF86Mail exec $term -e mutt +bindsym XF86Messenger exec $term -e weechat +bindsym XF86Search exec $browser https://duckduckgo.com +#bindsym XF86Go exec +#bindsym XF86Finance exec +#bindsym XF86Game exec +bindsym XF86Shop exec $browser https://ebay.com +bindsym XF86MonBrightnessDown exec --no-startup-id xbacklight -dec 15 +bindsym XF86MonBrightnessUp exec --no-startup-id xbacklight -inc 15 +bindsym XF86AudioMedia exec $music +bindsym XF86Display exec --no-startup-id arandr +#bindsym XF86KbdLightOnOff exec +bindsym XF86KbdBrightnessDown exec --no-startup-id xbacklight -dec 15 +bindsym XF86KbdBrightnessUp exec --no-startup-id xbacklight -inc 15 +#bindsym XF86Reply exec +#bindsym XF86MailForward exec +#bindsym XF86Save exec +bindsym XF86Documents exec $term -e ranger ~/Documents +#bindsym XF86Battery exec +#bindsym XF86Bluetooth exec +bindsym XF86WLAN exec --no-startup-id sudo sv restart NetworkManager + +# auto launch workspace apps +#exec $term -name email -e neomutt +exec $term -n email -e neomutt +exec $browser +exec --no-startup-id i3-msg 'workspace 1; focus' +#exec --no-startup-id i3-msg 'workspace 9; exec $term -n email -e neomutt' +#exec whatsapp-web-desktop +#exec --no-startup-id i3-msg 'workspace 1; exec $term' diff --git a/i3/.config/i3/lock.png b/i3/.config/i3/lock.png new file mode 100644 index 0000000..cd8b228 Binary files /dev/null and b/i3/.config/i3/lock.png differ diff --git a/i3/.config/i3/lock.sh b/i3/.config/i3/lock.sh new file mode 100644 index 0000000..6537bef --- /dev/null +++ b/i3/.config/i3/lock.sh @@ -0,0 +1,8 @@ +#!/bin/bash +scrot /tmp/screen.png +#convert /tmp/screen.png -scale 10% -scale 1000% /tmp/screen.png +#convert /tmp/screen.png -paint 3 -modulate 80 /tmp/screen.png +convert /tmp/screen.png -paint 3 /tmp/screen.png +[[ -f ~/.config/i3/lock.png ]] && convert /tmp/screen.png ~/.config/i3/lock.png -gravity center -composite -matte /tmp/screen.png +mocp -P +i3lock -u -e -i /tmp/screen.png diff --git a/i3/.config/i3blocks/config b/i3/.config/i3blocks/config new file mode 100644 index 0000000..d285da3 --- /dev/null +++ b/i3/.config/i3blocks/config @@ -0,0 +1,70 @@ +command=/usr/lib/i3blocks/$BLOCK_NAME +separator_block_width=10 +markup=pango + +[window title] +command=xtitle -s +interval=persist +min_width=1366 +align=center + +[mpd] +label=🎼 +command=i3mpd +interval=3 +align=left +signal=11 + +[memory] +label=🍌 +separator=true +interval=1 +#command=/usr/lib/i3blocks/memory +#command=free -h | awk '/^Mem:/ {print $3 " / " $2}' +command=free -h | awk '/^Mem:/ {print $3}' +color=#dfe3ee + +#[weather] +#command=i3weather +#interval=1800 +##color=#c9c9ff + +[mail] +label=📬 +command=i3mail +interval=60 +align=left + +[volume] +interval=1 +command=i3volume +#command=amixer get Master | grep -E -o '[0-9]{1,3}?%' | head -1 +markup=pango +color=#f19950 + +[iface] +label= +instance=wlp3s0 +##color=#00FF00 +interval=10 + +#[wifi] +#instance=wlp3s0 +#interval=3 + +#[wifi_strength] +##instance=wlp3s0 +#command=echo $(expr `cat /proc/net/wireless | awk 'NR==3 {print $3}' | sed 's/\.//'` \* 100 / 70)% +#label=Wifi: +#separator=true +#interval=3 + +[battery] +command=acpi | cut -d " " -f3-5 | sed -e "s/,//g;s/Discharging/🔋/;s/Charging/🔌/;s/Full/⚡/;s/ 0*/ /g;s/ :/ /g" +interval=5 + +[time] +label=📅 +command=echo $(date '+%a, %b %d | %H:%M ') +interval=5 + -- cgit v1.2.3