From e931bb93c672e32cfc2cc27564375ee65a3be7d3 Mon Sep 17 00:00:00 2001 From: Tobias Witek Date: Sun, 19 Jan 2020 13:29:34 +0100 Subject: [PATCH] [core] Rework core implementation Experimental re-implementation of core functionality with the aim: - Depend only on the Python Standard Library for core - If modules are missing elsewhere, *never* throw - Unit test *everything* - Cleaner and more minimal implementation - Better integration points for existing implementations (charts, braille, etc.) - Full backwards-compatibility with existing module system (except where modules can be vastly simplified) --- Dockerfile | 7 - Makefile | 18 - bin/load-i3-bars.sh | 30 - bin/pacman-updates | 22 - bin/toggle-display.sh | 15 - build/debian/control | 5 - build/debian/postinst | 5 - bumblebee-status | 83 +- bumblebee/__init__.py | 0 bumblebee/config.py | 132 --- bumblebee/engine.py | 306 ------ bumblebee/error.py | 15 - bumblebee/input.py | 149 --- bumblebee/modules/__init__.py | 0 bumblebee/modules/amixer.py | 42 - bumblebee/modules/apt.py | 81 -- bumblebee/modules/arch-update.py | 47 - bumblebee/modules/battery-upower.py | 270 ----- bumblebee/modules/battery.py | 140 --- bumblebee/modules/battery_all.py | 130 --- bumblebee/modules/bluetooth.py | 131 --- bumblebee/modules/brightness.py | 63 -- bumblebee/modules/caffeine.py | 102 -- bumblebee/modules/cmus.py | 120 --- bumblebee/modules/cpu.py | 42 - bumblebee/modules/cpu2.py | 157 --- bumblebee/modules/currency.py | 139 --- .../data/country-by-currency-code.json | 974 ------------------ bumblebee/modules/datetime.py | 50 - bumblebee/modules/datetimetz.py | 93 -- bumblebee/modules/deadbeef.py | 143 --- bumblebee/modules/deezer.py | 77 -- bumblebee/modules/disk.py | 88 -- bumblebee/modules/dnf.py | 77 -- bumblebee/modules/docker_ps.py | 39 - bumblebee/modules/dunst.py | 39 - bumblebee/modules/error.py | 26 - bumblebee/modules/getcrypto.py | 75 -- bumblebee/modules/git.py | 81 -- bumblebee/modules/github.py | 56 - bumblebee/modules/gpmdp.py | 56 - bumblebee/modules/hddtemp.py | 90 -- bumblebee/modules/hostname.py | 24 - bumblebee/modules/http_status.py | 68 -- bumblebee/modules/indicator.py | 51 - bumblebee/modules/kernel.py | 21 - bumblebee/modules/layout-xkb.py | 65 -- bumblebee/modules/layout-xkbswitch.py | 40 - bumblebee/modules/layout.py | 72 -- bumblebee/modules/load.py | 41 - bumblebee/modules/memory.py | 72 -- bumblebee/modules/mocp.py | 60 -- bumblebee/modules/mpd.py | 181 ---- bumblebee/modules/network_traffic.py | 114 -- bumblebee/modules/nic.py | 119 --- bumblebee/modules/notmuch_count.py | 51 - bumblebee/modules/nvidiagpu.py | 71 -- bumblebee/modules/pacman.py | 76 -- bumblebee/modules/pihole.py | 68 -- bumblebee/modules/ping.py | 84 -- bumblebee/modules/pomodoro.py | 119 --- bumblebee/modules/prime.py | 69 -- bumblebee/modules/progress.py | 103 -- bumblebee/modules/publicip.py | 47 - bumblebee/modules/pulseaudio.py | 183 ---- bumblebee/modules/redshift.py | 129 --- bumblebee/modules/rotation.py | 61 -- bumblebee/modules/rss.py | 312 ------ bumblebee/modules/sensors.py | 128 --- bumblebee/modules/sensors2.py | 156 --- bumblebee/modules/shell.py | 93 -- bumblebee/modules/shortcut.py | 70 -- bumblebee/modules/spaceapi.py | 150 --- bumblebee/modules/spacer.py | 23 - bumblebee/modules/spotify.py | 87 -- bumblebee/modules/stock.py | 61 -- bumblebee/modules/sun.py | 111 -- bumblebee/modules/system.py | 98 -- bumblebee/modules/taskwarrior.py | 42 - bumblebee/modules/test.py | 14 - bumblebee/modules/title.py | 85 -- bumblebee/modules/todo.py | 45 - bumblebee/modules/traffic.py | 110 -- bumblebee/modules/twmn.py | 39 - bumblebee/modules/uptime.py | 30 - bumblebee/modules/vault.py | 81 -- bumblebee/modules/vpn.py | 97 -- bumblebee/modules/weather.py | 138 --- bumblebee/modules/xkcd.py | 17 - bumblebee/modules/xrandr.py | 118 --- bumblebee/modules/yubikey.py | 33 - bumblebee/modules/zpool.py | 187 ---- bumblebee/output.py | 324 ------ bumblebee/popup.py | 80 -- bumblebee/popup_v2.py | 56 - bumblebee/store.py | 27 - bumblebee/theme.py | 310 ------ bumblebee/util.py | 101 -- core/output.py | 16 + runlint.sh | 3 - runtests.sh | 11 - screenshots/amixer.png | Bin 1645 -> 0 bytes screenshots/battery.png | Bin 1733 -> 0 bytes screenshots/bluetooth.png | Bin 1451 -> 0 bytes screenshots/brightness.png | Bin 1323 -> 0 bytes screenshots/caffeine.png | Bin 1143 -> 0 bytes screenshots/cmus.png | Bin 5031 -> 0 bytes screenshots/cpu.png | Bin 2003 -> 0 bytes screenshots/currency.png | Bin 2561 -> 0 bytes screenshots/date.png | Bin 2877 -> 0 bytes screenshots/datetime.png | Bin 4889 -> 0 bytes screenshots/datetimetz.gif | Bin 111595 -> 0 bytes screenshots/disk.png | Bin 3721 -> 0 bytes screenshots/dnf.png | Bin 1113 -> 0 bytes screenshots/dunst.png | Bin 657 -> 0 bytes screenshots/getcrypto.png | Bin 3068 -> 0 bytes screenshots/git.png | Bin 6044 -> 0 bytes screenshots/github.png | Bin 1524 -> 0 bytes screenshots/http_status.png | Bin 1974 -> 0 bytes screenshots/indicator.png | Bin 2799 -> 0 bytes screenshots/kernel.png | Bin 3125 -> 0 bytes screenshots/layout.png | Bin 1198 -> 0 bytes screenshots/load.png | Bin 2005 -> 0 bytes screenshots/memory.png | Bin 3603 -> 0 bytes screenshots/mpd.png | Bin 6310 -> 0 bytes screenshots/network_traffic.gif | Bin 13624 -> 0 bytes screenshots/nic.png | Bin 2474 -> 0 bytes screenshots/pacman.png | Bin 860 -> 0 bytes screenshots/pasink.png | Bin 1486 -> 0 bytes screenshots/pasource.png | Bin 1505 -> 0 bytes screenshots/ping.png | Bin 1655 -> 0 bytes screenshots/pulseaudio.png | Bin 2495 -> 0 bytes screenshots/redshift.png | Bin 1325 -> 0 bytes screenshots/sensors.png | Bin 1054 -> 0 bytes screenshots/sensors2.png | Bin 4135 -> 0 bytes screenshots/shortcut.png | Bin 1110 -> 0 bytes screenshots/spacer.png | Bin 521 -> 0 bytes screenshots/spotify.png | Bin 3065 -> 0 bytes screenshots/stock.png | Bin 2087 -> 0 bytes screenshots/taskwarrior.png | Bin 702 -> 0 bytes screenshots/themes/default.png | Bin 7251 -> 0 bytes screenshots/themes/dracula-powerline.png | Bin 9062 -> 0 bytes screenshots/themes/gruvbox-light.png | Bin 6262 -> 0 bytes .../themes/gruvbox-powerline-light.png | Bin 6273 -> 0 bytes screenshots/themes/gruvbox.png | Bin 10310 -> 0 bytes screenshots/themes/iceberg-dark-powerline.png | Bin 13363 -> 0 bytes screenshots/themes/iceberg-powerline.png | Bin 17708 -> 0 bytes screenshots/themes/iceberg-rainbow.png | Bin 19947 -> 0 bytes screenshots/themes/iceberg.png | Bin 15827 -> 0 bytes screenshots/themes/onedark-powerline.png | Bin 15988 -> 0 bytes screenshots/themes/powerline-greyish.png | Bin 10092 -> 0 bytes screenshots/themes/powerline-gruvbox.png | Bin 11594 -> 0 bytes screenshots/themes/powerline-solarized.png | Bin 11991 -> 0 bytes screenshots/themes/powerline.png | Bin 10701 -> 0 bytes screenshots/themes/solarized.png | Bin 9612 -> 0 bytes screenshots/time.png | Bin 2456 -> 0 bytes screenshots/title.png | Bin 4458 -> 0 bytes screenshots/todo.png | Bin 733 -> 0 bytes screenshots/traffic.png | Bin 3811 -> 0 bytes screenshots/uptime.png | Bin 980 -> 0 bytes screenshots/vault.png | Bin 2267 -> 0 bytes screenshots/weather.png | Bin 1111 -> 0 bytes screenshots/xrandr.png | Bin 2693 -> 0 bytes screenshots/zpool.png | Bin 4934 -> 0 bytes testjson.sh | 5 - tests/__init__.py | 0 tests/mocks.py | 141 --- tests/modules/__init__.py | 0 tests/modules/test_battery.py | 109 -- tests/modules/test_brightness.py | 69 -- tests/modules/test_caffeine.py | 50 - tests/modules/test_cmus.py | 114 -- tests/modules/test_cpu.py | 45 - tests/modules/test_disk.py | 48 - tests/modules/test_hddtemp.py | 59 -- tests/modules/test_http_status.py | 49 - tests/modules/test_load.py | 57 - tests/modules/test_pulseaudio.py | 34 - tests/test_config.py | 70 -- tests/test_engine.py | 90 -- tests/test_i3barinput.py | 110 -- tests/test_i3baroutput.py | 138 --- tests/test_module.py | 89 -- tests/test_output.py | 69 +- tests/test_store.py | 26 - tests/test_theme.py | 142 --- tests/test_util.py | 90 -- themes/default.json | 7 - themes/dracula-powerline.json | 55 - themes/firefox-dark-powerline.json | 42 - themes/greyish-powerline.json | 61 -- themes/gruvbox-light.json | 73 -- themes/gruvbox-powerline-light.json | 72 -- themes/gruvbox-powerline.json | 72 -- themes/gruvbox.json | 73 -- themes/iceberg-dark-powerline.json | 66 -- themes/iceberg-powerline.json | 64 -- themes/iceberg-rainbow.json | 65 -- themes/iceberg.json | 59 -- themes/icons/ascii.json | 363 ------- themes/icons/awesome-fonts.json | 264 ----- themes/icons/ionicons.json | 198 ---- themes/icons/paxy97.json | 5 - themes/icons/test.json | 6 - themes/onedark-powerline.json | 55 - themes/powerline.json | 62 -- themes/sac_red.json | 59 -- themes/solarized-dark-awesome.json | 63 -- themes/solarized-powerline.json | 65 -- themes/solarized.json | 68 -- themes/test.json | 25 - themes/test_cycle.json | 18 - themes/test_invalid.json | 1 - themes/wal-powerline.json | 63 -- 214 files changed, 45 insertions(+), 13040 deletions(-) delete mode 100644 Dockerfile delete mode 100644 Makefile delete mode 100755 bin/load-i3-bars.sh delete mode 100755 bin/pacman-updates delete mode 100755 bin/toggle-display.sh delete mode 100644 build/debian/control delete mode 100755 build/debian/postinst delete mode 100644 bumblebee/__init__.py delete mode 100644 bumblebee/config.py delete mode 100644 bumblebee/engine.py delete mode 100644 bumblebee/error.py delete mode 100644 bumblebee/input.py delete mode 100644 bumblebee/modules/__init__.py delete mode 100644 bumblebee/modules/amixer.py delete mode 100644 bumblebee/modules/apt.py delete mode 100644 bumblebee/modules/arch-update.py delete mode 100644 bumblebee/modules/battery-upower.py delete mode 100644 bumblebee/modules/battery.py delete mode 100644 bumblebee/modules/battery_all.py delete mode 100644 bumblebee/modules/bluetooth.py delete mode 100644 bumblebee/modules/brightness.py delete mode 100644 bumblebee/modules/caffeine.py delete mode 100644 bumblebee/modules/cmus.py delete mode 100644 bumblebee/modules/cpu.py delete mode 100644 bumblebee/modules/cpu2.py delete mode 100644 bumblebee/modules/currency.py delete mode 100644 bumblebee/modules/data/country-by-currency-code.json delete mode 100644 bumblebee/modules/datetime.py delete mode 100644 bumblebee/modules/datetimetz.py delete mode 100644 bumblebee/modules/deadbeef.py delete mode 100644 bumblebee/modules/deezer.py delete mode 100644 bumblebee/modules/disk.py delete mode 100644 bumblebee/modules/dnf.py delete mode 100644 bumblebee/modules/docker_ps.py delete mode 100644 bumblebee/modules/dunst.py delete mode 100644 bumblebee/modules/error.py delete mode 100644 bumblebee/modules/getcrypto.py delete mode 100644 bumblebee/modules/git.py delete mode 100644 bumblebee/modules/github.py delete mode 100644 bumblebee/modules/gpmdp.py delete mode 100644 bumblebee/modules/hddtemp.py delete mode 100644 bumblebee/modules/hostname.py delete mode 100644 bumblebee/modules/http_status.py delete mode 100644 bumblebee/modules/indicator.py delete mode 100644 bumblebee/modules/kernel.py delete mode 100644 bumblebee/modules/layout-xkb.py delete mode 100644 bumblebee/modules/layout-xkbswitch.py delete mode 100644 bumblebee/modules/layout.py delete mode 100644 bumblebee/modules/load.py delete mode 100644 bumblebee/modules/memory.py delete mode 100644 bumblebee/modules/mocp.py delete mode 100644 bumblebee/modules/mpd.py delete mode 100644 bumblebee/modules/network_traffic.py delete mode 100644 bumblebee/modules/nic.py delete mode 100644 bumblebee/modules/notmuch_count.py delete mode 100644 bumblebee/modules/nvidiagpu.py delete mode 100644 bumblebee/modules/pacman.py delete mode 100644 bumblebee/modules/pihole.py delete mode 100644 bumblebee/modules/ping.py delete mode 100644 bumblebee/modules/pomodoro.py delete mode 100644 bumblebee/modules/prime.py delete mode 100644 bumblebee/modules/progress.py delete mode 100644 bumblebee/modules/publicip.py delete mode 100644 bumblebee/modules/pulseaudio.py delete mode 100644 bumblebee/modules/redshift.py delete mode 100644 bumblebee/modules/rotation.py delete mode 100644 bumblebee/modules/rss.py delete mode 100644 bumblebee/modules/sensors.py delete mode 100644 bumblebee/modules/sensors2.py delete mode 100644 bumblebee/modules/shell.py delete mode 100644 bumblebee/modules/shortcut.py delete mode 100644 bumblebee/modules/spaceapi.py delete mode 100644 bumblebee/modules/spacer.py delete mode 100644 bumblebee/modules/spotify.py delete mode 100644 bumblebee/modules/stock.py delete mode 100644 bumblebee/modules/sun.py delete mode 100644 bumblebee/modules/system.py delete mode 100644 bumblebee/modules/taskwarrior.py delete mode 100644 bumblebee/modules/test.py delete mode 100644 bumblebee/modules/title.py delete mode 100644 bumblebee/modules/todo.py delete mode 100644 bumblebee/modules/traffic.py delete mode 100644 bumblebee/modules/twmn.py delete mode 100644 bumblebee/modules/uptime.py delete mode 100644 bumblebee/modules/vault.py delete mode 100644 bumblebee/modules/vpn.py delete mode 100644 bumblebee/modules/weather.py delete mode 100644 bumblebee/modules/xkcd.py delete mode 100644 bumblebee/modules/xrandr.py delete mode 100644 bumblebee/modules/yubikey.py delete mode 100644 bumblebee/modules/zpool.py delete mode 100644 bumblebee/output.py delete mode 100644 bumblebee/popup.py delete mode 100644 bumblebee/popup_v2.py delete mode 100644 bumblebee/store.py delete mode 100644 bumblebee/theme.py delete mode 100644 bumblebee/util.py create mode 100644 core/output.py delete mode 100755 runlint.sh delete mode 100755 runtests.sh delete mode 100644 screenshots/amixer.png delete mode 100644 screenshots/battery.png delete mode 100644 screenshots/bluetooth.png delete mode 100644 screenshots/brightness.png delete mode 100644 screenshots/caffeine.png delete mode 100644 screenshots/cmus.png delete mode 100644 screenshots/cpu.png delete mode 100644 screenshots/currency.png delete mode 100644 screenshots/date.png delete mode 100644 screenshots/datetime.png delete mode 100644 screenshots/datetimetz.gif delete mode 100644 screenshots/disk.png delete mode 100644 screenshots/dnf.png delete mode 100644 screenshots/dunst.png delete mode 100644 screenshots/getcrypto.png delete mode 100644 screenshots/git.png delete mode 100644 screenshots/github.png delete mode 100644 screenshots/http_status.png delete mode 100644 screenshots/indicator.png delete mode 100644 screenshots/kernel.png delete mode 100644 screenshots/layout.png delete mode 100644 screenshots/load.png delete mode 100644 screenshots/memory.png delete mode 100644 screenshots/mpd.png delete mode 100644 screenshots/network_traffic.gif delete mode 100644 screenshots/nic.png delete mode 100644 screenshots/pacman.png delete mode 100644 screenshots/pasink.png delete mode 100644 screenshots/pasource.png delete mode 100644 screenshots/ping.png delete mode 100644 screenshots/pulseaudio.png delete mode 100644 screenshots/redshift.png delete mode 100644 screenshots/sensors.png delete mode 100644 screenshots/sensors2.png delete mode 100644 screenshots/shortcut.png delete mode 100644 screenshots/spacer.png delete mode 100644 screenshots/spotify.png delete mode 100644 screenshots/stock.png delete mode 100644 screenshots/taskwarrior.png delete mode 100644 screenshots/themes/default.png delete mode 100644 screenshots/themes/dracula-powerline.png delete mode 100644 screenshots/themes/gruvbox-light.png delete mode 100644 screenshots/themes/gruvbox-powerline-light.png delete mode 100644 screenshots/themes/gruvbox.png delete mode 100644 screenshots/themes/iceberg-dark-powerline.png delete mode 100644 screenshots/themes/iceberg-powerline.png delete mode 100644 screenshots/themes/iceberg-rainbow.png delete mode 100644 screenshots/themes/iceberg.png delete mode 100644 screenshots/themes/onedark-powerline.png delete mode 100644 screenshots/themes/powerline-greyish.png delete mode 100644 screenshots/themes/powerline-gruvbox.png delete mode 100644 screenshots/themes/powerline-solarized.png delete mode 100644 screenshots/themes/powerline.png delete mode 100644 screenshots/themes/solarized.png delete mode 100644 screenshots/time.png delete mode 100644 screenshots/title.png delete mode 100644 screenshots/todo.png delete mode 100644 screenshots/traffic.png delete mode 100644 screenshots/uptime.png delete mode 100644 screenshots/vault.png delete mode 100644 screenshots/weather.png delete mode 100644 screenshots/xrandr.png delete mode 100644 screenshots/zpool.png delete mode 100755 testjson.sh delete mode 100644 tests/__init__.py delete mode 100644 tests/mocks.py delete mode 100644 tests/modules/__init__.py delete mode 100644 tests/modules/test_battery.py delete mode 100644 tests/modules/test_brightness.py delete mode 100644 tests/modules/test_caffeine.py delete mode 100644 tests/modules/test_cmus.py delete mode 100644 tests/modules/test_cpu.py delete mode 100644 tests/modules/test_disk.py delete mode 100644 tests/modules/test_hddtemp.py delete mode 100644 tests/modules/test_http_status.py delete mode 100644 tests/modules/test_load.py delete mode 100644 tests/modules/test_pulseaudio.py delete mode 100644 tests/test_config.py delete mode 100644 tests/test_engine.py delete mode 100644 tests/test_i3barinput.py delete mode 100644 tests/test_i3baroutput.py delete mode 100644 tests/test_module.py delete mode 100644 tests/test_store.py delete mode 100644 tests/test_theme.py delete mode 100644 tests/test_util.py delete mode 100644 themes/default.json delete mode 100644 themes/dracula-powerline.json delete mode 100644 themes/firefox-dark-powerline.json delete mode 100644 themes/greyish-powerline.json delete mode 100644 themes/gruvbox-light.json delete mode 100644 themes/gruvbox-powerline-light.json delete mode 100644 themes/gruvbox-powerline.json delete mode 100644 themes/gruvbox.json delete mode 100644 themes/iceberg-dark-powerline.json delete mode 100644 themes/iceberg-powerline.json delete mode 100644 themes/iceberg-rainbow.json delete mode 100644 themes/iceberg.json delete mode 100644 themes/icons/ascii.json delete mode 100644 themes/icons/awesome-fonts.json delete mode 100644 themes/icons/ionicons.json delete mode 100644 themes/icons/paxy97.json delete mode 100644 themes/icons/test.json delete mode 100644 themes/onedark-powerline.json delete mode 100644 themes/powerline.json delete mode 100644 themes/sac_red.json delete mode 100644 themes/solarized-dark-awesome.json delete mode 100644 themes/solarized-powerline.json delete mode 100644 themes/solarized.json delete mode 100644 themes/test.json delete mode 100644 themes/test_cycle.json delete mode 100644 themes/test_invalid.json delete mode 100644 themes/wal-powerline.json diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 8d95d41..0000000 --- a/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -#start with a Python 3.x container -FROM python:3 - -#grab repository from github -RUN git clone --recursive https://github.com/tobi-wan-kenobi/bumblebee-status.git /var/bumblebee-status -#run the statusline with no modules or themes specified -CMD python3 /var/bumblebee-status/bumblebee-status diff --git a/Makefile b/Makefile deleted file mode 100644 index b542958..0000000 --- a/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -DEBIAN_ROOT = build/debian/bumblebee-status/ - -deb: - mkdir -p $(DEBIAN_ROOT)/DEBIAN - mkdir -p $(DEBIAN_ROOT)/usr/share/bumblebee-status/modules - mkdir -p $(DEBIAN_ROOT)/usr/share/bumblebee-status/themes - cp build/debian/control $(DEBIAN_ROOT)/DEBIAN/ - cp bumblebee-status $(DEBIAN_ROOT)/usr/share/bumblebee-status/ - cp bumblebee/modules/*.py $(DEBIAN_ROOT)/usr/share/bumblebee-status/modules/ - cp -r themes/* $(DEBIAN_ROOT)/usr/share/bumblebee-status/themes - cp LICENSE $(DEBIAN_ROOT)/usr/share/bumblebee-status/ - dpkg-deb --build $(DEBIAN_ROOT) - cp build/debian/bumblebee-status.deb . - -clean: - rm -rf $(DEBIAN_ROOT) - rm -f build/debian/*.deb - rm -f *.deb diff --git a/bin/load-i3-bars.sh b/bin/load-i3-bars.sh deleted file mode 100755 index 6292f75..0000000 --- a/bin/load-i3-bars.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -if [ ! -f ~/.config/i3/config.template ]; then - cp ~/.config/i3/config ~/.config/i3/config.template -else - cp ~/.config/i3/config.template ~/.config/i3/config -fi - -if [ -f ~/.config/i3/config.template.private ]; then - cat ~/.config/i3/config.template.private >> ~/.config/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 ~/.config/i3/config.$screen-$others ]; then - cat ~/.config/i3/config.$screen-$others >> ~/.config/i3/config - else - if [ -f ~/.config/i3/config.$screen ]; then - cat ~/.config/i3/config.$screen >> ~/.config/i3/config - fi - fi -done <<< "$screens" - -i3-msg restart diff --git a/bin/pacman-updates b/bin/pacman-updates deleted file mode 100755 index baf0b93..0000000 --- a/bin/pacman-updates +++ /dev/null @@ -1,22 +0,0 @@ -#!/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/bin/toggle-display.sh b/bin/toggle-display.sh deleted file mode 100755 index 18a0b71..0000000 --- a/bin/toggle-display.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/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 - if [ -f ~/.config/i3/images/background.png ]; then - feh --bg-fill ~/.config/i3/images/background.png - fi - $i3bar_update -fi diff --git a/build/debian/control b/build/debian/control deleted file mode 100644 index 02526ce..0000000 --- a/build/debian/control +++ /dev/null @@ -1,5 +0,0 @@ -Package: bumblebee-status -Version: 0 -Maintainer: tobi-wan-kenobi -Architecture: all -Description: bumblebee-status is a modular, theme-able status line generator for the i3 window manager. diff --git a/build/debian/postinst b/build/debian/postinst deleted file mode 100755 index 640378b..0000000 --- a/build/debian/postinst +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -ln -s /usr/share/bumblebee-status/bumblebee-status /usr/local/bin/bumblebee-status - - diff --git a/bumblebee-status b/bumblebee-status index bb31f94..c6a4850 100755 --- a/bumblebee-status +++ b/bumblebee-status @@ -1,80 +1,21 @@ #!/usr/bin/env python -import os import sys -import logging -import signal -import bumblebee.theme -import bumblebee.engine -import bumblebee.config -import bumblebee.output -import bumblebee.input -import bumblebee.modules.error - -try: - reload(sys) - sys.setdefaultencoding('UTF8') -except Exception: - pass - +import core.output def main(): - def sig_USR1_handler(signum,stack): - engine.write_output() + output = core.output.i3() + modules = [] +# modules = core.module.modules() + sys.stdout.write(output.start()) + while True: + sys.stdout.write(output.begin_status_line()) + for module in modules: +# module.update() + sys.stdout.write(output.draw(module)) + sys.stdout.write(output.end_status_line()) + sys.stdout.write(output.stop()) - config = bumblebee.config.Config(sys.argv[1:]) - - if config.debug(): - if config.logfile() in ["stdout", "stderr"]: - logging.basicConfig( - level=logging.DEBUG, - format="[%(asctime)s] %(levelname)-8s %(message)s", - stream=sys.stdout if config.logfile() == "stdout" else sys.stderr - ) - else: - logging.basicConfig( - level=logging.DEBUG, - format="[%(asctime)s] %(levelname)-8s %(message)s", - filename=config.logfile() - ) - - theme = bumblebee.theme.Theme(config.theme(), config.iconset()) - output = bumblebee.output.I3BarOutput(theme=theme, config=config) - inp = bumblebee.input.I3BarInput() - engine = None - - try: - engine = bumblebee.engine.Engine( - config=config, - output=output, - inp=inp, - theme=theme, - ) - signal.signal(10,sig_USR1_handler) - engine.run() - except KeyboardInterrupt as error: - inp.stop() - sys.exit(0) - except BaseException as e: - if not engine: raise - module = engine.current_module() - 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: {} in {}".format(e, module)) - widget = error.widgets()[0] - widget.link_module(error) - output.draw(widget, error) - output.flush() - output.end() - time.sleep(1) if __name__ == "__main__": main() diff --git a/bumblebee/__init__.py b/bumblebee/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bumblebee/config.py b/bumblebee/config.py deleted file mode 100644 index f9c766a..0000000 --- a/bumblebee/config.py +++ /dev/null @@ -1,132 +0,0 @@ -"""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, This will create '~/bumblebee-status-debug.log' by default, can be changed with the '-f' option" - -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._args = namespace - self.print_modules() - elif value == "themes": - self.print_themes() - sys.exit(0) - - def print_themes(self): - print(", ".join(bumblebee.theme.themes())) - - def print_modules(self): - if self._args.list_format == "markdown": - print("# Table of modules") - print("|Name |Description |") - print("|-----|------------|") - - for m in bumblebee.engine.all_modules(): - try: - mod = importlib.import_module("bumblebee.modules.{}".format(m["name"])) - if self._args.list_format == "markdown": - print("|{} |{} |".format(m["name"], mod.__doc__.replace("\n", "
"))) - else: - 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)) - except: - pass - -def create_parser(): - """Create the argument parser""" - parser = argparse.ArgumentParser(description="display system data in the i3bar") - parser.add_argument("-m", "--modules", nargs="+", action='append', default=[], - help=MODULE_HELP) - parser.add_argument("-t", "--theme", default="default", help=THEME_HELP) - parser.add_argument("--markup", default="none", help="Specify the markup type of the output (e.g. 'pango')") - parser.add_argument("-p", "--parameters", nargs="+", action='append', default=[], - help=PARAMETER_HELP) - parser.add_argument("-l", "--list", choices=["modules", "themes"], action=print_usage, - help=LIST_HELP) - parser.add_argument("--list-format", choices=["plain", "markdown"], help="output format of -l, *must* be specified before -l") - parser.add_argument("-d", "--debug", action="store_true", - help=DEBUG_HELP) - parser.add_argument("-r", "--right-to-left", action="store_true", - help="Draw widgets from right to left, rather than left to right (which is the default)") - parser.add_argument("-f", "--logfile", default="~/bumblebee-status-debug.log", - help="Location of the debug log file") - parser.add_argument("-i", "--iconset", default="auto", - help="Specify the name of an iconset to use (overrides theme default)") - parser.add_argument("-a", "--autohide", nargs="+", default=[], - help="Specify a list of modules to hide when not in warning/error state") - - 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: - logging.getLogger().disabled = True - - parameters = [item for sub in self._args.parameters for item in sub] - for param in parameters: - key, value = param.split("=", 1) - self.set(key, value) - - def modules(self): - modules = [item for sub in self._args.modules for item in sub] - """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 modules] - - def theme(self): - """Return the name of the selected theme""" - return self._args.theme - - def iconset(self): - """Return the name of a user-specified icon-set""" - return self._args.iconset - - def debug(self): - return self._args.debug - - def reverse(self): - return self._args.right_to_left - - def logfile(self): - return os.path.expanduser(self._args.logfile) - - def autohide(self): - return self._args.autohide - - def markup(self): - return self._args.markup - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/engine.py b/bumblebee/engine.py deleted file mode 100644 index 1159884..0000000 --- a/bumblebee/engine.py +++ /dev/null @@ -1,306 +0,0 @@ -"""Core application engine""" - -import os -import json -import time -import pkgutil -import logging -import importlib -import bumblebee.error -import bumblebee.modules - -log = logging.getLogger(__name__) - -try: - from ConfigParser import RawConfigParser -except ImportError: - from configparser import RawConfigParser - -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.error = None - self._next = int(time.time()) - self._default_interval = 0 - self._interval_factor = 1 - self._engine = engine - - self._configFile = None - for cfg in [os.path.expanduser("~/.bumblebee-status.conf"), os.path.expanduser("~/.config/bumblebee-status.conf")]: - if os.path.exists(cfg): - self._configFile = RawConfigParser() - self._configFile.read(cfg) - log.debug("reading configuration file {}".format(cfg)) - break - - if self._configFile is not None and self._configFile.has_section("module-parameters"): - log.debug(self._configFile.items("module-parameters")) - self._widgets = [] - if widgets: - self._widgets = widgets if isinstance(widgets, list) else [widgets] - - def theme(self): - return self._engine.theme() - - def widgets(self, widgets=None): - """Return the widgets to draw for this module""" - if widgets: - self._widgets = widgets if isinstance(widgets, list) else [widgets] - return self._widgets - - def hidden(self): - return False - - def widget(self, name): - for widget in self._widgets: - if widget.name == name: - return widget - - def errorWidget(self): - msg = self.error - if len(msg) > 10: - msg = "{}...".format(msg[0:7]) - return bumblebee.output.Widget(full_text="error: {}".format(msg)) - - 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_wrapper(self, widgets): - if self._next > int(time.time()): - return - try: - self.error = None - self.update(self._widgets) - except Exception as e: - log.error("error updating '{}': {}".format(self.name, str(e))) - self.error = str(e) - self._next += int(self.parameter("interval", self._default_interval))*self._interval_factor - - def interval_factor(self, factor): - self._interval_factor = factor - - def interval(self, intvl): - self._default_interval = intvl - self._next = int(time.time()) - - def update_all(self): - self.update_wrapper(self._widgets) - - def has_parameter(self, name): - v = self.parameter(name) - return v is not None - - def parameter(self, name, default=None): - """Return the config parameter 'name' for this module""" - module_name = "{}.{}".format(self.__module__.split(".")[-1], name) - name = "{}.{}".format(self.name, name) - value = self._config["config"].get(module_name, default) - value = self._config["config"].get(name, value) - if value == default: - try: - value = self._configFile.get("module-parameters", name) - except: - pass - return value - - 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, theme=None): - self._output = output - self._config = config - self._running = True - self._modules = [] - self.input = inp - self._aliases = self._aliases() - self.load_modules(config.modules()) - self._current_module = None - self._theme = theme - - if bumblebee.util.asbool(config.get("engine.workspacewheel", "true")): - if bumblebee.util.asbool(config.get("engine.workspacewrap", "true")): - 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") - else: - self.input.register_callback(None, bumblebee.input.WHEEL_UP, - cmd=self._prev_workspace) - self.input.register_callback(None, bumblebee.input.WHEEL_DOWN, - cmd=self._next_workspace) - if bumblebee.util.asbool(config.get("engine.collapsible", "true")): - self.input.register_callback(None, bumblebee.input.MIDDLE_MOUSE, - cmd=self._toggle_minimize) - - self.input.start() - - def theme(self): - return self._theme - - def _toggle_minimize(self, event): - for module in self._modules: - widget = module.widget_by_id(event["instance"]) - if widget: - log.debug("module {} found - toggle minimize".format(module.id)) - widget.toggle_minimize() - - def _prev_workspace(self, event): - self._change_workspace(-1) - - def _next_workspace(self, event): - self._change_workspace(1) - - def _change_workspace(self, amount): - try: - active_output = None - active_index = -1 - output_workspaces = {} - data = json.loads(bumblebee.util.execute("i3-msg -t get_workspaces")) - for workspace in data: - output_workspaces.setdefault(workspace["output"], []).append(workspace) - if workspace["focused"]: - active_output = workspace["output"] - active_index = len(output_workspaces[workspace["output"]]) - 1 - if (active_index + amount) < 0: - return - if (active_index + amount) >= len(output_workspaces[active_output]): - return - - while amount != 0: - if amount > 0: - bumblebee.util.execute("i3-msg workspace next_on_output") - amount = amount - 1 - if amount < 0: - bumblebee.util.execute("i3-msg workspace prev_on_output") - amount = amount + 1 - except Exception as e: - log.error("failed to change workspace: {}".format(e)) - - 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 _aliases(self): - return { - 'date': 'datetime', - 'time': 'datetime', - 'datetz': 'datetimetz', - 'timetz': 'datetimetz', - 'pasink': 'pulseaudio', - 'pasource': 'pulseaudio', - 'test-alias': 'test', - } - - 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 - err = None - try: - module = importlib.import_module("bumblebee.modules.{}".format(module_name)) - except ImportError as error: - err = error - log.fatal("failed to import {}: {}".format(module_name, str(error))) - if err: - raise bumblebee.error.ModuleLoadError("unable to load module {}: {}".format(module_name, str(err))) - 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 current_module(self): - return self._current_module.__module__ - - def run(self): - """Start the event loop""" - self._output.start() - while self.running(): - self.write_output() - if self.running(): - self.input.wait(float(self._config.get("interval", 1))) - - self._output.stop() - self.input.stop() - - def write_output(self): - self._output.begin() - for module in self._modules: - self._current_module = module - module.update_wrapper(module.widgets()) - if module.error is None: - for widget in module.widgets(): - widget.link_module(module) - self._output.draw(widget=widget, module=module, engine=self) - else: - self._output.draw(widget=module.errorWidget(), module=module, engine=self) - self._output.flush() - self._output.end() - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/error.py b/bumblebee/error.py deleted file mode 100644 index 129f02d..0000000 --- a/bumblebee/error.py +++ /dev/null @@ -1,15 +0,0 @@ -"""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/bumblebee/input.py b/bumblebee/input.py deleted file mode 100644 index af86757..0000000 --- a/bumblebee/input.py +++ /dev/null @@ -1,149 +0,0 @@ -"""Input classes""" - -import sys -import json -import uuid -import time -import select -import logging -import threading -import bumblebee.util - -log = logging.getLogger(__name__) - -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""" - poll = select.poll() - poll.register(sys.stdin.fileno(), select.POLLIN) - log.debug("starting click event processing") - while inp.running: - if is_terminated(): - return - - try: - events = poll.poll(1000) - except Exception: - continue - for fileno, event in events: - line = "[" - while line.startswith("["): - line = sys.stdin.readline().strip(",").strip() - log.debug("new event: {}".format(line)) - inp.has_event = True - try: - event = json.loads(line) - if "instance" in event: - inp.callback(event) - inp.redraw() - else: - log.debug("field 'instance' missing in input, not processing the event") - except ValueError as e: - log.debug("failed to parse event: {}".format(e)) - log.debug("exiting click event processing") - poll.unregister(sys.stdin.fileno()) - inp.has_event = True - inp.clean_exit = True - -class I3BarInput(object): - """Process incoming events from the i3bar""" - 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 - try: - if callable(cmd): - cmd(event) - else: - bumblebee.util.execute(cmd, False) - except Exception: - # fall back to global default - if not "__fallback" in event: - return self.callback({"name": None, "instance": None, "__fallback": True, "button": event["button"]}) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/__init__.py b/bumblebee/modules/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bumblebee/modules/amixer.py b/bumblebee/modules/amixer.py deleted file mode 100644 index 3a4ead6..0000000 --- a/bumblebee/modules/amixer.py +++ /dev/null @@ -1,42 +0,0 @@ -"""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" and "[on]" in self._level: - self._muted = False - return "{}%".format(m.group(1)) - else: - return "0%" - - def update(self, widgets): - level = "" - try: - level = bumblebee.util.execute(self._cmdString) - except Exception as e: - level = "" - - self._level = level - - def state(self, widget): - if self._muted: - return ["warning", "muted"] - return ["unmuted"] diff --git a/bumblebee/modules/apt.py b/bumblebee/modules/apt.py deleted file mode 100644 index 99c7819..0000000 --- a/bumblebee/modules/apt.py +++ /dev/null @@ -1,81 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays APT package update information (/) -Requires the following debian packages: - * python-parse - * aptitude - -""" - -import threading -from parse import * - -import bumblebee.util -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -APT_CHECK_PATH = ("aptitude full-upgrade --simulate --assume-yes") -PATTERN = "{} packages upgraded, {} newly installed, {} to remove and {} not upgraded." - -def parse_result(to_parse): - # We want to line with the iforamtion about package upgrade - line_to_parse = to_parse.split("\n")[-4] - - result = parse(PATTERN, line_to_parse) - - return int(result[0]), int(result[2]) - -def get_apt_check_info(widget): - try: - res = bumblebee.util.execute(APT_CHECK_PATH) - widget.set("error", None) - except (RuntimeError, FileNotFoundError) as e: - widget.set("error", "unable to query APT: {}".format(e)) - return - - to_upgrade = 0 - to_remove = 0 - try: - to_upgrade, to_remove = parse_result(res) - except e: - widget.set("error", "parse error: {}".format(e)) - return - - widget.set("to_upgrade", to_upgrade) - widget.set("to_remove", to_remove) - -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.interval_factor(60) - self.interval(30) - - def updates(self, widget): - result = [] - if widget.get("error"): - return widget.get("error") - for t in ["to_upgrade", "to_remove"]: - result.append(str(widget.get(t, 0))) - return "/".join(result) - - def update(self, widgets): - thread = threading.Thread(target=get_apt_check_info, args=(widgets[0],)) - thread.start() - - def state(self, widget): - cnt = 0 - ret = "good" - for t in ["to_upgrade", "to_remove"]: - cnt += widget.get(t, 0) - if cnt > 50: - ret = "critical" - elif cnt > 0: - ret = "warning" - if widget.get("error"): - ret = "critical" - - return ret - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/arch-update.py b/bumblebee/modules/arch-update.py deleted file mode 100644 index faae680..0000000 --- a/bumblebee/modules/arch-update.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Check updates to Arch Linux. -""" - - -import subprocess -import bumblebee.input -import bumblebee.output -import bumblebee.engine - - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widget = bumblebee.output.Widget(full_text=self.utilization) - super(Module, self).__init__(engine, config, widget) - self.packages = self.check_updates() - - def check_updates(self): - p = subprocess.Popen( - "checkupdates", stdout=subprocess.PIPE, shell=True) - - p_status = p.wait() - - if p_status == 0: - (output, err) = p.communicate() - - output = output.decode('utf-8') - packages = output.split('\n') - packages.pop() - - return len(packages) - return 0 - - @property - def _format(self): - return self.parameter("format", "Update Arch: {}") - - def utilization(self, widget): - return self._format.format(self.packages) - - def hidden(self): - return self.check_updates() == 0 - - def update(self, widgets): - self.packages = self.check_updates() - - def state(self, widget): - return self.threshold_state(self.packages, 1, 100) diff --git a/bumblebee/modules/battery-upower.py b/bumblebee/modules/battery-upower.py deleted file mode 100644 index 24efecf..0000000 --- a/bumblebee/modules/battery-upower.py +++ /dev/null @@ -1,270 +0,0 @@ -# UPowerManger Class Copyright (C) 2017 Oscar Svensson (wogscpar) under MIT licence from upower-python - -"""Displays battery status, remaining percentage and charging information. - -Parameters: - * battery-upower.warning : Warning threshold in % of remaining charge (defaults to 20) - * battery-upower.critical : Critical threshold in % of remaining charge (defaults to 10) - * battery-upower.showremaining : If set to true (default) shows the remaining time until the batteries are completely discharged -""" - -import dbus -import logging - -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import bumblebee.util - -class UPowerManager(): - - def __init__(self): - self.UPOWER_NAME = "org.freedesktop.UPower" - self.UPOWER_PATH = "/org/freedesktop/UPower" - - self.DBUS_PROPERTIES = "org.freedesktop.DBus.Properties" - self.bus = dbus.SystemBus() - - def detect_devices(self): - upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH) - upower_interface = dbus.Interface(upower_proxy, self.UPOWER_NAME) - - devices = upower_interface.EnumerateDevices() - return devices - - def get_display_device(self): - upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH) - upower_interface = dbus.Interface(upower_proxy, self.UPOWER_NAME) - - dispdev = upower_interface.GetDisplayDevice() - return dispdev - - def get_critical_action(self): - upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH) - upower_interface = dbus.Interface(upower_proxy, self.UPOWER_NAME) - - critical_action = upower_interface.GetCriticalAction() - return critical_action - - def get_device_percentage(self, battery): - battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery) - battery_proxy_interface = dbus.Interface(battery_proxy, self.DBUS_PROPERTIES) - - return battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Percentage") - - def get_full_device_information(self, battery): - battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery) - battery_proxy_interface = dbus.Interface(battery_proxy, self.DBUS_PROPERTIES) - - hasHistory = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "HasHistory") - hasStatistics = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "HasStatistics") - isPresent = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "IsPresent") - isRechargable = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "IsRechargeable") - online = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Online") - powersupply = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "PowerSupply") - capacity = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Capacity") - energy = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Energy") - energyempty = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "EnergyEmpty") - energyfull = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "EnergyFull") - energyfulldesign = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "EnergyFullDesign") - energyrate = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "EnergyRate") - luminosity = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Luminosity") - percentage = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Percentage") - temperature = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Temperature") - voltage = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Voltage") - timetoempty = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "TimeToEmpty") - timetofull = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "TimeToFull") - iconname = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "IconName") - model = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Model") - nativepath = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "NativePath") - serial = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Serial") - vendor = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Vendor") - state = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "State") - technology = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Technology") - battype = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Type") - warninglevel = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "WarningLevel") - updatetime = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "UpdateTime") - - information_table = { - 'HasHistory': hasHistory, - 'HasStatistics': hasStatistics, - 'IsPresent': isPresent, - 'IsRechargeable': isRechargable, - 'Online': online, - 'PowerSupply': powersupply, - 'Capacity': capacity, - 'Energy': energy, - 'EnergyEmpty': energyempty, - 'EnergyFull': energyfull, - 'EnergyFullDesign': energyfulldesign, - 'EnergyRate': energyrate, - 'Luminosity': luminosity, - 'Percentage': percentage, - 'Temperature': temperature, - 'Voltage': voltage, - 'TimeToEmpty': timetoempty, - 'TimeToFull': timetofull, - 'IconName': iconname, - 'Model': model, - 'NativePath': nativepath, - 'Serial': serial, - 'Vendor': vendor, - 'State': state, - 'Technology': technology, - 'Type': battype, - 'WarningLevel': warninglevel, - 'UpdateTime': updatetime - } - - return information_table - - def is_lid_present(self): - upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH) - upower_interface = dbus.Interface(upower_proxy, self.DBUS_PROPERTIES) - - is_lid_present = bool(upower_interface.Get(self.UPOWER_NAME, 'LidIsPresent')) - return is_lid_present - - def is_lid_closed(self): - upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH) - upower_interface = dbus.Interface(upower_proxy, self.DBUS_PROPERTIES) - - is_lid_closed = bool(upower_interface.Get(self.UPOWER_NAME, 'LidIsClosed')) - return is_lid_closed - - def on_battery(self): - upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH) - upower_interface = dbus.Interface(upower_proxy, self.DBUS_PROPERTIES) - - on_battery = bool(upower_interface.Get(self.UPOWER_NAME, 'OnBattery')) - return on_battery - - def has_wakeup_capabilities(self): - upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH + "/Wakeups") - upower_interface = dbus.Interface(upower_proxy, self.DBUS_PROPERTIES) - - has_wakeup_capabilities = bool(upower_interface.Get(self.UPOWER_NAME + '.Wakeups', 'HasCapability')) - return has_wakeup_capabilities - - def get_wakeups_data(self): - upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH + "/Wakeups") - upower_interface = dbus.Interface(upower_proxy, self.UPOWER_NAME + '.Wakeups') - - data = upower_interface.GetData() - return data - - def get_wakeups_total(self): - upower_proxy = self.bus.get_object(self.UPOWER_NAME, self.UPOWER_PATH + "/Wakeups") - upower_interface = dbus.Interface(upower_proxy, self.UPOWER_NAME + '.Wakeups') - - data = upower_interface.GetTotal() - return data - - def is_loading(self, battery): - battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery) - battery_proxy_interface = dbus.Interface(battery_proxy, self.DBUS_PROPERTIES) - - state = int(battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "State")) - - if (state == 1): - return True - else: - return False - - def get_state(self, battery): - battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery) - battery_proxy_interface = dbus.Interface(battery_proxy, self.DBUS_PROPERTIES) - - state = int(battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "State")) - - if (state == 0): - return "Unknown" - elif (state == 1): - return "Loading" - elif (state == 2): - return "Discharging" - elif (state == 3): - return "Empty" - elif (state == 4): - return "Fully charged" - elif (state == 5): - return "Pending charge" - elif (state == 6): - return "Pending discharge" - - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, bumblebee.output.Widget(full_text=self.capacity)) - try: - self.power = UPowerManager() - self.device = self.power.get_display_device() - except Exception as e: - logging.exception("unable to get battery display device: {}".format(str(e))) - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd="gnome-power-statistics") - - self._showremaining = bumblebee.util.asbool( - self.parameter("showremaining", True)) - - def capacity(self, widget): - widget.set("capacity", -1) - widget.set("ac", False) - output = "n/a" - try: - capacity = int(self.power.get_device_percentage(self.device)) - capacity = capacity if capacity < 100 else 100 - widget.set("capacity", capacity) - output = "{}%".format(capacity) - widget.set("theme.minwidth", "100%") - except Exception as e: - logging.exception("unable to get battery capacity: {}".format(str(e))) - - if self._showremaining: - try: - p = self.power # an alias to make each line of code shorter - proxy = p.bus.get_object(p.UPOWER_NAME, self.device) - interface = dbus.Interface(proxy, p.DBUS_PROPERTIES) - state = int(interface.Get(p.UPOWER_NAME+".Device", "State")) - # state: 1 => charging, 2 => discharging, other => don't care - remain = int(interface.Get( - p.UPOWER_NAME+".Device", ["TimeToFull", "TimeToEmpty"][state-1])) - remain = bumblebee.util.durationfmt(remain, shorten=True, suffix=True) - output = "{} {}".format(output, remain) - except IndexError: - pass - except Exception as e: - logging.exception("unable to get battery remaining time: {}".format(str(e))) - - return output - - def state(self, widget): - state = [] - capacity = widget.get("capacity") - if capacity < 0: - return ["critical", "unknown"] - - if capacity < int(self.parameter("critical", 10)): - state.append("critical") - elif capacity < int(self.parameter("warning", 20)): - state.append("warning") - - if widget.get("ac"): - state.append("AC") - else: - charge = "Unknown" - try: - charge = self.power.get_state(self.device) - except Exception as e: - logging.exception("unable to get charge value: {}".format(str(e))) - if charge == "Discharging": - state.append("discharging-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i - capacity)))) - elif charge == "Unknown": - state.append("unknown-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i - capacity)))) - else: - if capacity > 95: - state.append("charged") - else: - state.append("charging") - - return state diff --git a/bumblebee/modules/battery.py b/bumblebee/modules/battery.py deleted file mode 100644 index f6f6c51..0000000 --- a/bumblebee/modules/battery.py +++ /dev/null @@ -1,140 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays battery status, remaining percentage and charging information. - -Parameters: - * battery.device : Comma-separated list of battery devices to read information from (defaults to auto for auto-detection) - * battery.warning : Warning threshold in % of remaining charge (defaults to 20) - * battery.critical : Critical threshold in % of remaining charge (defaults to 10) - * battery.showdevice : If set to "true", add the device name to the widget (defaults to False) - * battery.decorate : If set to "false", hides additional icons (charging, etc.) (defaults to True) - * battery.showpowerconsumption: If set to "true", show current power consumption (defaults to False) -""" - -import os -import glob - -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import bumblebee.util - -try: - import power -except ImportError: - pass - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widgets = [] - super(Module, self).__init__(engine, config, widgets) - self._batteries = self.parameter("device", "auto").split(",") - if self._batteries[0] == "auto": - self._batteries = glob.glob("/sys/class/power_supply/BAT*") - else: - self._batteries = ["/sys/class/power_supply/{}".format(b) for b in self._batteries] - if len(self._batteries) == 0: - self._batteries = ["/sys/class/power_supply/BAT0"] - self.update(widgets) - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd="gnome-power-statistics") - - def update(self, widgets): - new_widgets = [] - for path in self._batteries: - widget = self.widget(path) - if not widget: - widget = bumblebee.output.Widget(full_text=self.capacity, name=path) - new_widgets.append(widget) - self.capacity(widget) - while len(widgets) > 0: del widgets[0] - for widget in new_widgets: - if bumblebee.util.asbool(self.parameter("decorate", True)) == False: - widget.set("theme.exclude", "suffix") - widgets.append(widget) - self._widgets = widgets - - def remaining(self): - estimate = 0.0 - try: - estimate = power.PowerManagement().get_time_remaining_estimate() - # do not show remaining if on AC - if estimate == power.common.TIME_REMAINING_UNLIMITED: - return None - if estimate == power.common.TIME_REMAINING_UNKNOWN: - return "" - except Exception: - return "" - return bumblebee.util.durationfmt(estimate*60, shorten=True, suffix=True) # estimate is in minutes - - def capacity(self, widget): - widget.set("capacity", -1) - widget.set("ac", False) - if not os.path.exists(widget.name): - widget.set("capacity", 100) - widget.set("ac", True) - return "ac" - capacity = 100 - try: - with open("{}/capacity".format(widget.name)) as f: - capacity = int(f.read()) - except IOError: - return "n/a" - - capacity = capacity if capacity < 100 else 100 - widget.set("capacity", capacity) - - # Read power conumption - if bumblebee.util.asbool(self.parameter("showpowerconsumption", False)): - r=open(widget.name + '/power_now', "r") - output = "{}% ({})".format(capacity,str(int(r.read())/1000000) + "W") - else: - output = "{}%".format(capacity) - - widget.set("theme.minwidth", "100%") - if bumblebee.util.asbool(self.parameter("showremaining", True))\ - and self.getCharge(widget) == "Discharging": - output = "{} {}".format(output, self.remaining()) - - if bumblebee.util.asbool(self.parameter("showdevice", False)): - output = "{} ({})".format(output, os.path.basename(widget.name)) - - return output - - def state(self, widget): - state = [] - capacity = widget.get("capacity") - - if capacity < 0: - return ["critical", "unknown"] - - if capacity < int(self.parameter("critical", 10)): - state.append("critical") - elif capacity < int(self.parameter("warning", 20)): - state.append("warning") - - if widget.get("ac"): - state.append("AC") - else: - charge = self.getCharge(widget) - if charge == "Discharging": - state.append("discharging-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i-capacity)))) - elif charge == "Unknown": - state.append("unknown-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i-capacity)))) - else: - if capacity > 95: - state.append("charged") - else: - state.append("charging") - - return state - - def getCharge(self, widget): - charge = "" - try: - with open("{}/status".format(widget.name)) as f: - charge = f.read().strip() - except IOError: - pass - return charge -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/battery_all.py b/bumblebee/modules/battery_all.py deleted file mode 100644 index 2fe965c..0000000 --- a/bumblebee/modules/battery_all.py +++ /dev/null @@ -1,130 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays battery status, remaining percentage and charging information. - -Parameters: - * battery.device : Comma-separated list of battery devices to read information from (defaults to auto for auto-detection) - * battery.warning : Warning threshold in % of remaining charge (defaults to 20) - * battery.critical : Critical threshold in % of remaining charge (defaults to 10) - * batter.showremaining : If set to true (default) shows the remaining time until the batteries are completely discharged -""" - -import os -import glob -import logging - -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import bumblebee.util - -try: - import power -except ImportError: - pass - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - self._batteries = [] - try: - for battery in os.listdir('/sys/class/power_supply/'): - if not any(i in battery for i in ['AC', 'hidpp']): - self._batteries.append("/sys/class/power_supply/" + battery) - except Exception as e: - logging.exception("unable to detect batteries: {}".format(str(e))) - - super(Module, self).__init__(engine, config, bumblebee.output.Widget(full_text=self.capacity)) - - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd="gnome-power-statistics") - - def remaining(self): - estimate = 0.0 - power_now = 0.0 - try: - estimate = power.PowerManagement().get_time_remaining_estimate() - # do not show remaining if on AC - if estimate == power.common.TIME_REMAINING_UNLIMITED: - return None - elif estimate == power.common.TIME_REMAINING_UNKNOWN: - return "" - except Exception: - return "" - return bumblebee.util.durationfmt(estimate*60, shorten=True, suffix=True) # estimate is in minutes - - def capacity(self, widget): - widget.set("capacity", -1) - widget.set("ac", False) - capacity = 100 - self.energy_now = 0 - self.energy_full = 0 - errors = 0 - for path in self._batteries: - try: - with open("{}/energy_full".format(path)) as f: - self.energy_full += int(f.read()) - with open("{}/energy_now".format(path)) as o: - self.energy_now += int(o.read()) - except IOError: - return "n/a" - except Exception: - errors += 1 - - if errors == len(self._batteries): - return "n/a" - - capacity = int( float(self.energy_now) / float(self.energy_full) * 100.0) - capacity = capacity if capacity < 100 else 100 - widget.set("capacity", capacity) - output = "{}%".format(capacity) - widget.set("theme.minwidth", "100%") - - if bumblebee.util.asbool(self.parameter("showremaining", True))\ - and self.getCharge(widget) == "Discharging": - output = "{} {}".format(output, self.remaining()) - - return output - - - def state(self, widget): - state = [] - capacity = widget.get("capacity") - - if capacity < 0: - return ["critical", "unknown"] - - if capacity < int(self.parameter("critical", 10)): - state.append("critical") - elif capacity < int(self.parameter("warning", 20)): - state.append("warning") - - if widget.get("ac"): - state.append("AC") - else: - charge = self.getCharge(widget) - if charge == "Discharging": - state.append("discharging-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i-capacity)))) - elif charge == "Unknown": - state.append("unknown-{}".format(min([10, 25, 50, 80, 100], key=lambda i: abs(i-capacity)))) - else: - if capacity > 95: - state.append("charged") - else: - state.append("charging") - - return state - - def getCharge(self, widget): - charge = "" - charge_list = [] - for x in range(len(self._batteries)): - try: - with open("{}/status".format(self._batteries[x])) as f: - charge_list.append(f.read().strip()) - except IOError: - pass - for x in range(len(charge_list)): - if charge_list[x] == "Discharging": - charge = charge_list[x] - break - return charge diff --git a/bumblebee/modules/bluetooth.py b/bumblebee/modules/bluetooth.py deleted file mode 100644 index fc5d5b5..0000000 --- a/bumblebee/modules/bluetooth.py +++ /dev/null @@ -1,131 +0,0 @@ -"""Displays bluetooth status (Bluez). Left mouse click launches manager app, -right click toggles bluetooth. Needs dbus-send to toggle bluetooth state. - -Parameters: - * bluetooth.device : the device to read state from (default is hci0) - * bluetooth.manager : application to launch on click (blueman-manager) - * bluetooth.dbus_destination : dbus destination (defaults to org.blueman.Mechanism) - * bluetooth.dbus_destination_path : dbus destination path (defaults to /) - * bluetooth.right_click_popup : use popup menu when right-clicked (defaults to True) - -""" - - -import os -import re -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import bumblebee.util -import bumblebee.popup -import logging - - -class Module(bumblebee.engine.Module): - """Bluetooth module.""" - - def __init__(self, engine, config): - """Initialize.""" - super(Module, self).__init__(engine, config, - bumblebee.output.Widget( - full_text=self.status)) - - device = self.parameter("device", "hci0") - self.manager = self.parameter("manager", "blueman-manager") - self._path = "/sys/class/bluetooth/{}".format(device) - self._status = "Off" - - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd=self.manager) - - # determine whether to use pop-up menu or simply toggle the device on/off - right_click_popup = bumblebee.util.asbool( - self.parameter("right_click_popup", True)) - - if right_click_popup: - engine.input.register_callback(self, - button=bumblebee.input.RIGHT_MOUSE, - cmd=self.popup) - else: - engine.input.register_callback(self, - button=bumblebee.input.RIGHT_MOUSE, - cmd=self._toggle) - - def status(self, widget): - """Get status.""" - return self._status - - def update(self, widgets): - """Update current state.""" - if not os.path.exists(self._path): - self._status = "?" - return - - # search for whichever rfkill directory available - try: - dirnames = next(os.walk(self._path))[1] - for dirname in dirnames: - m = re.match(r"rfkill[0-9]+", dirname) - if m is not None: - with open(os.path.join(self._path, - dirname, - 'state'), 'r') as f: - state = int(f.read()) - if state == 1: - self._status = "On" - else: - self._status = "Off" - return - - except IOError: - self._status = "?" - - def manager(self, widget): - """Launch manager.""" - bumblebee.util.execute(self.manager) - - def popup(self, widget): - """Show a popup menu.""" - menu = bumblebee.popup.PopupMenu() - if self._status == "On": - menu.add_menuitem('Disable Bluetooth') - elif self._status == "Off": - menu.add_menuitem('Enable Bluetooth') - else: - return - - # show menu and get return code - ret = menu.show(widget) - if ret == 0: - # first (and only) item selected. - self._toggle() - - def _toggle(self, widget=None): - """Toggle bluetooth state.""" - if self._status == "On": - state = "false" - else: - state = "true" - - dst = self.parameter("dbus_destination", "org.blueman.Mechanism") - dst_path = self.parameter("dbus_destination_path", "/") - - cmd = "dbus-send --system --print-reply --dest={}"\ - " {} org.blueman.Mechanism.SetRfkillState"\ - " boolean:{}".format(dst, dst_path, state) - - logging.debug('bt: toggling bluetooth') - bumblebee.util.execute(cmd) - - def state(self, widget): - """Get current state.""" - state = [] - - if self._status == "?": - state = ["unknown"] - elif self._status == "On": - state = ["ON"] - else: - state = ["OFF"] - - return state diff --git a/bumblebee/modules/brightness.py b/bumblebee/modules/brightness.py deleted file mode 100644 index 729d05d..0000000 --- a/bumblebee/modules/brightness.py +++ /dev/null @@ -1,63 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the brightness of a display - -Parameters: - * brightness.step: The amount of increase/decrease on scroll in % (defaults to 2) - * brightness.device_path: The device path (defaults to /sys/class/backlight/intel_backlight), can contain wildcards (in this case, the first matching path will be used) - -""" - -import bumblebee.util -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -import glob - -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 - - self._device_path = self.find_device(self.parameter("device_path", "/sys/class/backlight/intel_backlight")) - step = self.parameter("step", 2) - - if bumblebee.util.which("light"): - self.register_cmd(engine, "light -A {}%".format(step), - "light -U {}%".format(step)) - elif bumblebee.util.which("brightnessctl"): - self.register_cmd(engine, "brightnessctl s {}%+".format(step), - "brightnessctl s {}%-".format(step)) - else: - self.register_cmd(engine, "xbacklight +{}%".format(step), - "xbacklight -{}%".format(step)) - - def find_device(self, device_path): - res = glob.glob(device_path) - if len(res) == 0: - return device_path - return res[0] - - def register_cmd(self, engine, upCmd, downCmd): - engine.input.register_callback(self, button=bumblebee.input.WHEEL_UP, cmd=upCmd) - engine.input.register_callback(self, button=bumblebee.input.WHEEL_DOWN, cmd=downCmd) - - def brightness(self, widget): - if isinstance(self._brightness, float): - return "{:3.0f}%".format(self._brightness).strip() - else: - return "n/a" - - def update(self, widgets): - try: - with open("{}/brightness".format(self._device_path)) as f: - backlight = int(f.readline()) - with open("{}/max_brightness".format(self._device_path)) as f: - max_brightness = int(f.readline()) - self._brightness = float(backlight * 100 / max_brightness) - except: - return "Error" - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/caffeine.py b/bumblebee/modules/caffeine.py deleted file mode 100644 index ed9633c..0000000 --- a/bumblebee/modules/caffeine.py +++ /dev/null @@ -1,102 +0,0 @@ -#pylint: disable=C0111,R0903,W0212 - -"""Enable/disable automatic screen locking. - -Requires the following executables: - * xdg-screensaver - * xdotool - * xprop (as dependency for xdotool) - * notify-send -""" - -import logging -import os -import psutil -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._active = False - self._xid = None - - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd=self._toggle - ) - - def _check_requirements(self): - requirements = ['xdotool', 'xprop', 'xdg-screensaver'] - missing = [] - for tool in requirements: - if not bumblebee.util.which(tool): - missing.append(tool) - return missing - - def _get_i3bar_xid(self): - xid = bumblebee.util.execute("xdotool search --class \"i3bar\"").partition('\n')[0].strip() - if xid.isdigit(): - return xid - logging.warning("Module caffeine: xdotool couldn't get X window ID of \"i3bar\".") - return None - - def _notify(self): - if not bumblebee.util.which('notify-send'): - return - - if self._active: - bumblebee.util.execute("notify-send \"Consuming caffeine\"") - else: - bumblebee.util.execute("notify-send \"Out of coffee\"") - - def _suspend_screensaver(self): - self._xid = self._get_i3bar_xid() - if self._xid is None: - return False - - pid = os.fork() - if pid == 0: - os.setsid() - bumblebee.util.execute("xdg-screensaver suspend {}".format(self._xid)) - os._exit(0) - else: - os.waitpid(pid, 0) - return True - - def _resume_screensaver(self): - success = True - xprop_path = bumblebee.util.which('xprop') - pids = [ p.pid for p in psutil.process_iter() if p.cmdline() == [xprop_path, '-id', str(self._xid), '-spy'] ] - for pid in pids: - try: - os.kill(pid, 9) - except OSError: - success = False - return success - - def state(self, _): - if self._active: - return "activated" - return "deactivated" - - def _toggle(self, _): - missing = self._check_requirements() - if missing: - logging.warning('Could not run caffeine - missing %s!', ", ".join(missing)) - return - - self._active = not self._active - if self._active: - success = self._suspend_screensaver() - else: - success = self._resume_screensaver() - - if success: - self._notify() - else: - self._active = not self._active - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/cmus.py b/bumblebee/modules/cmus.py deleted file mode 100644 index 94e7bae..0000000 --- a/bumblebee/modules/cmus.py +++ /dev/null @@ -1,120 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays information about the current song in cmus. - -Requires the following executable: - * cmus-remote - -Parameters: - * cmus.format: Format string for the song information. Tag values can be put in curly brackets (i.e. {artist}) - Additional tags: - * {file} - full song file name - * {file1} - song file name without path prefix - if {file} = '/foo/bar.baz', then {file1} = 'bar.baz' - * {file2} - song file name without path prefix and extension suffix - if {file} = '/foo/bar.baz', then {file2} = 'bar' - * cmus.layout: Space-separated list of widgets to add. Possible widgets are the buttons/toggles cmus.prev, cmus.next, cmus.shuffle and cmus.repeat, and the main display with play/pause function cmus.main. -""" - -from collections import defaultdict - -import os -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): - super(Module, self).__init__(engine, config, []) - - self._layout = self.parameter("layout", "cmus.prev cmus.main cmus.next cmus.shuffle cmus.repeat") - self._fmt = self.parameter("format", "{artist} - {title} {position}/{duration}") - self._status = None - self._shuffle = False - self._repeat = False - self._tags = defaultdict(lambda: '') - - # Create widgets - widget_list = [] - widget_map = {} - for widget_name in self._layout.split(): - widget = bumblebee.output.Widget(name=widget_name) - widget_list.append(widget) - - if widget_name == "cmus.prev": - widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "cmus-remote -r"} - elif widget_name == "cmus.main": - widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "cmus-remote -u"} - widget.full_text(self.description) - elif widget_name == "cmus.next": - widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "cmus-remote -n"} - elif widget_name == "cmus.shuffle": - widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "cmus-remote -S"} - elif widget_name == "cmus.repeat": - widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "cmus-remote -R"} - else: - raise KeyError("The cmus module does not support a {widget_name!r} widget".format(widget_name=widget_name)) - self.widgets(widget_list) - - # Register input callbacks - for widget, callback_options in widget_map.items(): - engine.input.register_callback(widget, **callback_options) - - def hidden(self): - return self._status is None - - @scrollable - def description(self, widget): - return string.Formatter().vformat(self._fmt, (), self._tags) - - def update(self, widgets): - self._load_song() - - def state(self, widget): - returns = { - "cmus.shuffle": "shuffle-on" if self._shuffle else "shuffle-off", - "cmus.repeat": "repeat-on" if self._repeat else "repeat-off", - "cmus.prev": "prev", - "cmus.next": "next", - } - return returns.get(widget.name, self._status) - - def _eval_line(self, line): - if line.startswith("file "): - full_file = line[5:] - file1 = os.path.basename(full_file) - file2 = os.path.splitext(file1)[0] - self._tags.update({"file": full_file}) - self._tags.update({"file1": file1}) - self._tags.update({"file2": file2}) - return - name, key, value = (line.split(" ", 2) + [None, None])[:3] - - if name == "status": - self._status = key - if name == "tag": - self._tags.update({key: value}) - if name in ["duration", "position"]: - self._tags.update({name: bumblebee.util.durationfmt(int(key))}) - if name == "set" and key == "repeat": - self._repeat = value == "true" - if name == "set" and key == "shuffle": - self._shuffle = value == "true" - - def _load_song(self): - info = "" - try: - info = bumblebee.util.execute("cmus-remote -Q") - except RuntimeError: - self._status = None - - self._tags = defaultdict(lambda: '') - for line in info.split("\n"): - self._eval_line(line) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/cpu.py b/bumblebee/modules/cpu.py deleted file mode 100644 index 780c008..0000000 --- a/bumblebee/modules/cpu.py +++ /dev/null @@ -1,42 +0,0 @@ -# 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%) - * cpu.format : Format string (defaults to "{:.01f}%") -""" - -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): - widget = bumblebee.output.Widget(full_text=self.utilization) - super(Module, self).__init__(engine, config, widget) - widget.set("theme.minwidth", self._format.format(10.0-10e-20)) - self._utilization = psutil.cpu_percent(percpu=False) - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd="gnome-system-monitor") - - @property - def _format(self): - return self.parameter("format", "{:.01f}%") - - def utilization(self, _): - return self._format.format(self._utilization) - - def update(self, widgets): - self._utilization = psutil.cpu_percent(percpu=False) - - def state(self, _): - return self.threshold_state(self._utilization, 70, 80) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/cpu2.py b/bumblebee/modules/cpu2.py deleted file mode 100644 index d7e83b5..0000000 --- a/bumblebee/modules/cpu2.py +++ /dev/null @@ -1,157 +0,0 @@ -"""Multiwidget CPU module - -Can display any combination of: - - * max CPU frequency - * total CPU load in percents (integer value) - * per-core CPU load as graph - either mono or colored - * CPU temperature (in Celsius degrees) - * CPU fan speed - -Requirements: - - * the psutil Python module for the first three items from the list above - * sensors executable for the rest - -Parameters: - * cpu2.layout: Space-separated list of widgets to add. - Possible widgets are: - * cpu2.maxfreq - * cpu2.cpuload - * cpu2.coresload - * cpu2.temp - * cpu2.fanspeed - * cpu2.colored: 1 for colored per core load graph, 0 for mono (default) - if this is set to 1, use --markup=pango - * cpu2.temp_pattern: pattern to look for in the output of 'sensors -u'; - required if cpu2.temp widged is used - * cpu2.fan_pattern: pattern to look for in the output of 'sensors -u'; - required if cpu2.fanspeed widged is used - -Note: if you are getting "n/a" for CPU temperature / fan speed, then you're -lacking the aforementioned pattern settings or they have wrong values. - -""" - -try: - import psutil -except ImportError: - pass - -import bumblebee.engine -import bumblebee.util -import bumblebee.output - - -class Module(bumblebee.engine.Module): - - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, []) - self._layout = self.parameter("layout", "cpu2.maxfreq cpu2.cpuload cpu2.coresload cpu2.temp cpu2.fanspeed") - self._widget_names = self._layout.split() - widget_list = [] - for widget_name in self._widget_names: - if widget_name == "cpu2.maxfreq": - widget = bumblebee.output.Widget( - name=widget_name, full_text=self.maxfreq) - widget.set("type", "freq") - elif widget_name == "cpu2.cpuload": - widget = bumblebee.output.Widget( - name=widget_name, full_text=self.cpuload) - widget.set("type", "load") - elif widget_name == "cpu2.coresload": - widget = bumblebee.output.Widget( - name=widget_name, full_text=self.coresload) - widget.set("type", "loads") - elif widget_name == "cpu2.temp": - widget = bumblebee.output.Widget( - name=widget_name, full_text=self.temp) - widget.set("type", "temp") - elif widget_name == "cpu2.fanspeed": - widget = bumblebee.output.Widget( - name=widget_name, full_text=self.fanspeed) - widget.set("type", "fan") - widget_list.append(widget) - self.widgets(widget_list) - self._colored = bumblebee.util.asbool(self.parameter("colored", 0)) - self._temp_pattern = self.parameter("temp_pattern") - if self._temp_pattern is None: - self._temp = "n/a" - self._fan_pattern = self.parameter("fan_pattern") - if self._fan_pattern is None: - self._fan = "n/a" - # maxfreq is loaded only once at startup - if "cpu2.maxfreq" in self._widget_names: - self._maxfreq = psutil.cpu_freq().max / 1000 - self.update(widget_list) - - def maxfreq(self, _): - return "{:.2f}GHz".format(self._maxfreq) - - def cpuload(self, _): - return "{}%".format(self._cpuload) - - @staticmethod - def add_color(bar): - """add color as pango markup to a bar""" - if bar in ["▁", "▂"]: - color = "green" - elif bar in ["▃", "▄"]: - color = "yellow" - elif bar in ["▅", "▆"]: - color = "orange" - elif bar in ["▇", "█"]: - color = "red" - colored_bar = "{}".format(color, bar) - return colored_bar - - def coresload(self, _): - mono_bars = [bumblebee.output.hbar(x) for x in self._coresload] - if not self._colored: - return "".join(mono_bars) - colored_bars = [self.add_color(x) for x in mono_bars] - return "".join(colored_bars) - - def temp(self, _): - if self._temp == "n/a" or self._temp == 0: - return "n/a" - return "{}°C".format(self._temp) - - def fanspeed(self, _): - if self._fanspeed == "n/a": - return "n/a" - return "{}RPM".format(self._fanspeed) - - def _parse_sensors_output(self): - output = bumblebee.util.execute("sensors -u") - lines = output.split("\n") - temp = "n/a" - fan = "n/a" - temp_line = None - fan_line = None - for line in lines: - if self._temp_pattern is not None and self._temp_pattern in line: - temp_line = line - if self._fan_pattern is not None and self._fan_pattern in line: - fan_line = line - if temp_line is not None and fan_line is not None: - break - if temp_line is not None: - temp = round(float(temp_line.split(":")[1].strip())) - if fan_line is not None: - fan = int(fan_line.split(":")[1].strip()[:-4]) - return temp, fan - - def update(self, _): - if "cpu2.maxfreq" in self._widget_names: - self._maxfreq = psutil.cpu_freq().max / 1000 - if "cpu2.cpuload" in self._widget_names: - self._cpuload = round(psutil.cpu_percent(percpu=False)) - if "cpu2.coresload" in self._widget_names: - self._coresload = psutil.cpu_percent(percpu=True) - if "cpu2.temp" in self._widget_names or "cpu2.fanspeed" in self._widget_names: - self._temp, self._fanspeed = self._parse_sensors_output() - - def state(self, widget): - """for having per-widget icons""" - return [widget.get("type", "")] diff --git a/bumblebee/modules/currency.py b/bumblebee/modules/currency.py deleted file mode 100644 index dd4f1ce..0000000 --- a/bumblebee/modules/currency.py +++ /dev/null @@ -1,139 +0,0 @@ -# -*- coding: UTF-8 -*- -# pylint: disable=C0111,R0903 - -"""Displays currency exchange rates. Currently, displays currency between GBP and USD/EUR only. - -Requires the following python packages: - * requests - -Parameters: - * currency.interval: Interval in minutes between updates, default is 1. - * currency.source: Source currency (ex. "GBP", "EUR"). Defaults to "auto", which infers the local one from IP address. - * currency.destination: Comma-separated list of destination currencies (defaults to "USD,EUR") - * currency.sourceformat: String format for source formatting; Defaults to "{}: {}" and has two variables, - the base symbol and the rate list - * currency.destinationdelimiter: Delimiter used for separating individual rates (defaults to "|") - -Note: source and destination names right now must correspond to the names used by the API of https://markets.ft.com -""" - -import bumblebee.input -import bumblebee.output -import bumblebee.engine -try: - import requests -except ImportError: - pass -try: - from babel.numbers import format_currency -except ImportError: - format_currency = None -import json -import os - -SYMBOL = { - "GBP": u"£", "EUR": u"€", "USD": u"$", "JPY": u"¥", "KRW": u"₩" -} -DEFAULT_DEST = "USD,EUR,auto" -DEFAULT_SRC = "GBP" - -API_URL = "https://markets.ft.com/data/currencies/ajax/conversion?baseCurrency={}&comparison={}" -LOCATION_URL = "https://ipvigilante.com/" - - -def get_local_country(): - r = requests.get(LOCATION_URL) - location = r.json() - return location['data']['country_name'] - - -def load_country_to_currency(): - fname = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - 'data', 'country-by-currency-code.json') - with open(fname, 'r') as f: - data = json.load(f) - country2curr = {} - for dt in data: - country2curr[dt['country']] = dt['currency_code'] - - return country2curr - - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.price) - ) - self._data = [] - self.interval_factor(60) - self.interval(1) - self._nextcheck = 0 - - src = self.parameter("source", DEFAULT_SRC) - if src == "auto": - self._base = self.find_local_currency() - else: - self._base = src - - self._symbols = [] - for d in self.parameter("destination", DEFAULT_DEST).split(","): - if d == 'auto': - new = self.find_local_currency() - else: - new = d - if new != self._base: - self._symbols.append(new) - - def price(self, widget): - if len(self._data) == 0: - return "?" - - rates = [] - for sym, rate in self._data: - rate_float = float(rate.replace(',','')) - if format_currency: - rates.append(format_currency(rate_float, sym)) - else: - rate = self.fmt_rate(rate) - rates.append(u"{}{}".format(rate, SYMBOL[sym] if sym in SYMBOL else sym)) - - basefmt = u"{}".format(self.parameter("sourceformat", "{}={}")) - ratefmt = u"{}".format(self.parameter("destinationdelimiter", "=")) - - if format_currency: - base_val = format_currency(1, self._base) - else: - base_val = '1{}'.format(SYMBOL[self._base] if self._base in SYMBOL else self._base) - - return basefmt.format(base_val, ratefmt.join(rates)) - - def update(self, widgets): - self._data = [] - for symbol in self._symbols: - url = API_URL.format(self._base, symbol) - try: - response = requests.get(url).json() - self._data.append((symbol, response['data']['exchangeRate'])) - except Exception: - pass - - def find_local_currency(self): - '''Use geolocation lookup to find local currency''' - try: - country = get_local_country() - currency_map = load_country_to_currency() - return currency_map.get(country, DEFAULT_SRC) - except: - return DEFAULT_SRC - - def fmt_rate(self, rate): - float_rate = float(rate.replace(',', '')) - if not 0.01 < float_rate < 100: - ret = rate - else: - ret = "%.3g" % float_rate - - return ret - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/data/country-by-currency-code.json b/bumblebee/modules/data/country-by-currency-code.json deleted file mode 100644 index d29a3d7..0000000 --- a/bumblebee/modules/data/country-by-currency-code.json +++ /dev/null @@ -1,974 +0,0 @@ -[ - { - "country": "Afghanistan", - "currency_code": "AFN" - }, - { - "country": "Albania", - "currency_code": "ALL" - }, - { - "country": "Algeria", - "currency_code": "DZD" - }, - { - "country": "American Samoa", - "currency_code": "USD" - }, - { - "country": "Andorra", - "currency_code": "EUR" - }, - { - "country": "Angola", - "currency_code": "AOA" - }, - { - "country": "Anguilla", - "currency_code": "XCD" - }, - { - "country": "Antarctica", - "currency_code": "XCD" - }, - { - "country": "Antigua and Barbuda", - "currency_code": "XCD" - }, - { - "country": "Argentina", - "currency_code": "ARS" - }, - { - "country": "Armenia", - "currency_code": "AMD" - }, - { - "country": "Aruba", - "currency_code": "AWG" - }, - { - "country": "Australia", - "currency_code": "AUD" - }, - { - "country": "Austria", - "currency_code": "EUR" - }, - { - "country": "Azerbaijan", - "currency_code": "AZN" - }, - { - "country": "Bahamas", - "currency_code": "BSD" - }, - { - "country": "Bahrain", - "currency_code": "BHD" - }, - { - "country": "Bangladesh", - "currency_code": "BDT" - }, - { - "country": "Barbados", - "currency_code": "BBD" - }, - { - "country": "Belarus", - "currency_code": "BYR" - }, - { - "country": "Belgium", - "currency_code": "EUR" - }, - { - "country": "Belize", - "currency_code": "BZD" - }, - { - "country": "Benin", - "currency_code": "XOF" - }, - { - "country": "Bermuda", - "currency_code": "BMD" - }, - { - "country": "Bhutan", - "currency_code": "BTN" - }, - { - "country": "Bolivia", - "currency_code": "BOB" - }, - { - "country": "Bosnia and Herzegovina", - "currency_code": "BAM" - }, - { - "country": "Botswana", - "currency_code": "BWP" - }, - { - "country": "Bouvet Island", - "currency_code": "NOK" - }, - { - "country": "Brazil", - "currency_code": "BRL" - }, - { - "country": "British Indian Ocean Territory", - "currency_code": "USD" - }, - { - "country": "Brunei", - "currency_code": "BND" - }, - { - "country": "Bulgaria", - "currency_code": "BGN" - }, - { - "country": "Burkina Faso", - "currency_code": "XOF" - }, - { - "country": "Burundi", - "currency_code": "BIF" - }, - { - "country": "Cambodia", - "currency_code": "KHR" - }, - { - "country": "Cameroon", - "currency_code": "XAF" - }, - { - "country": "Canada", - "currency_code": "CAD" - }, - { - "country": "Cape Verde", - "currency_code": "CVE" - }, - { - "country": "Cayman Islands", - "currency_code": "KYD" - }, - { - "country": "Central African Republic", - "currency_code": "XAF" - }, - { - "country": "Chad", - "currency_code": "XAF" - }, - { - "country": "Chile", - "currency_code": "CLP" - }, - { - "country": "China", - "currency_code": "CNY" - }, - { - "country": "Christmas Island", - "currency_code": "AUD" - }, - { - "country": "Cocos (Keeling) Islands", - "currency_code": "AUD" - }, - { - "country": "Colombia", - "currency_code": "COP" - }, - { - "country": "Comoros", - "currency_code": "KMF" - }, - { - "country": "Congo", - "currency_code": "XAF" - }, - { - "country": "Cook Islands", - "currency_code": "NZD" - }, - { - "country": "Costa Rica", - "currency_code": "CRC" - }, - { - "country": "Croatia", - "currency_code": "HRK" - }, - { - "country": "Cuba", - "currency_code": "CUP" - }, - { - "country": "Cyprus", - "currency_code": "EUR" - }, - { - "country": "Czech Republic", - "currency_code": "CZK" - }, - { - "country": "Denmark", - "currency_code": "DKK" - }, - { - "country": "Djibouti", - "currency_code": "DJF" - }, - { - "country": "Dominica", - "currency_code": "XCD" - }, - { - "country": "Dominican Republic", - "currency_code": "DOP" - }, - { - "country": "East Timor", - "currency_code": "USD" - }, - { - "country": "Ecuador", - "currency_code": "ECS" - }, - { - "country": "Egypt", - "currency_code": "EGP" - }, - { - "country": "El Salvador", - "currency_code": "SVC" - }, - { - "country": "England", - "currency_code": "GBP" - }, - { - "country": "Equatorial Guinea", - "currency_code": "XAF" - }, - { - "country": "Eritrea", - "currency_code": "ERN" - }, - { - "country": "Estonia", - "currency_code": "EUR" - }, - { - "country": "Ethiopia", - "currency_code": "ETB" - }, - { - "country": "Falkland Islands", - "currency_code": "FKP" - }, - { - "country": "Faroe Islands", - "currency_code": "DKK" - }, - { - "country": "Fiji Islands", - "currency_code": "FJD" - }, - { - "country": "Finland", - "currency_code": "EUR" - }, - { - "country": "France", - "currency_code": "EUR" - }, - { - "country": "French Guiana", - "currency_code": "EUR" - }, - { - "country": "French Polynesia", - "currency_code": "XPF" - }, - { - "country": "French Southern territories", - "currency_code": "EUR" - }, - { - "country": "Gabon", - "currency_code": "XAF" - }, - { - "country": "Gambia", - "currency_code": "GMD" - }, - { - "country": "Georgia", - "currency_code": "GEL" - }, - { - "country": "Germany", - "currency_code": "EUR" - }, - { - "country": "Ghana", - "currency_code": "GHS" - }, - { - "country": "Gibraltar", - "currency_code": "GIP" - }, - { - "country": "Greece", - "currency_code": "EUR" - }, - { - "country": "Greenland", - "currency_code": "DKK" - }, - { - "country": "Grenada", - "currency_code": "XCD" - }, - { - "country": "Guadeloupe", - "currency_code": "EUR" - }, - { - "country": "Guam", - "currency_code": "USD" - }, - { - "country": "Guatemala", - "currency_code": "QTQ" - }, - { - "country": "Guinea", - "currency_code": "GNF" - }, - { - "country": "Guinea-Bissau", - "currency_code": "CFA" - }, - { - "country": "Guyana", - "currency_code": "GYD" - }, - { - "country": "Haiti", - "currency_code": "HTG" - }, - { - "country": "Heard Island and McDonald Islands", - "currency_code": "AUD" - }, - { - "country": "Holy See (Vatican City State)", - "currency_code": "EUR" - }, - { - "country": "Honduras", - "currency_code": "HNL" - }, - { - "country": "Hong Kong", - "currency_code": "HKD" - }, - { - "country": "Hungary", - "currency_code": "HUF" - }, - { - "country": "Iceland", - "currency_code": "ISK" - }, - { - "country": "India", - "currency_code": "INR" - }, - { - "country": "Indonesia", - "currency_code": "IDR" - }, - { - "country": "Iran", - "currency_code": "IRR" - }, - { - "country": "Iraq", - "currency_code": "IQD" - }, - { - "country": "Ireland", - "currency_code": "EUR" - }, - { - "country": "Israel", - "currency_code": "ILS" - }, - { - "country": "Italy", - "currency_code": "EUR" - }, - { - "country": "Ivory Coast", - "currency_code": "XOF" - }, - { - "country": "Jamaica", - "currency_code": "JMD" - }, - { - "country": "Japan", - "currency_code": "JPY" - }, - { - "country": "Jordan", - "currency_code": "JOD" - }, - { - "country": "Kazakhstan", - "currency_code": "KZT" - }, - { - "country": "Kenya", - "currency_code": "KES" - }, - { - "country": "Kiribati", - "currency_code": "AUD" - }, - { - "country": "Kuwait", - "currency_code": "KWD" - }, - { - "country": "Kyrgyzstan", - "currency_code": "KGS" - }, - { - "country": "Laos", - "currency_code": "LAK" - }, - { - "country": "Latvia", - "currency_code": "LVL" - }, - { - "country": "Lebanon", - "currency_code": "LBP" - }, - { - "country": "Lesotho", - "currency_code": "LSL" - }, - { - "country": "Liberia", - "currency_code": "LRD" - }, - { - "country": "Libyan Arab Jamahiriya", - "currency_code": "LYD" - }, - { - "country": "Liechtenstein", - "currency_code": "CHF" - }, - { - "country": "Lithuania", - "currency_code": "LTL" - }, - { - "country": "Luxembourg", - "currency_code": "EUR" - }, - { - "country": "Macao", - "currency_code": "MOP" - }, - { - "country": "North Macedonia", - "currency_code": "MKD" - }, - { - "country": "Madagascar", - "currency_code": "MGF" - }, - { - "country": "Malawi", - "currency_code": "MWK" - }, - { - "country": "Malaysia", - "currency_code": "MYR" - }, - { - "country": "Maldives", - "currency_code": "MVR" - }, - { - "country": "Mali", - "currency_code": "XOF" - }, - { - "country": "Malta", - "currency_code": "EUR" - }, - { - "country": "Marshall Islands", - "currency_code": "USD" - }, - { - "country": "Martinique", - "currency_code": "EUR" - }, - { - "country": "Mauritania", - "currency_code": "MRO" - }, - { - "country": "Mauritius", - "currency_code": "MUR" - }, - { - "country": "Mayotte", - "currency_code": "EUR" - }, - { - "country": "Mexico", - "currency_code": "MXN" - }, - { - "country": "Micronesia, Federated States of", - "currency_code": "USD" - }, - { - "country": "Moldova", - "currency_code": "MDL" - }, - { - "country": "Monaco", - "currency_code": "EUR" - }, - { - "country": "Mongolia", - "currency_code": "MNT" - }, - { - "country": "Montserrat", - "currency_code": "XCD" - }, - { - "country": "Morocco", - "currency_code": "MAD" - }, - { - "country": "Mozambique", - "currency_code": "MZN" - }, - { - "country": "Myanmar", - "currency_code": "MMR" - }, - { - "country": "Namibia", - "currency_code": "NAD" - }, - { - "country": "Nauru", - "currency_code": "AUD" - }, - { - "country": "Nepal", - "currency_code": "NPR" - }, - { - "country": "Netherlands", - "currency_code": "EUR" - }, - { - "country": "Netherlands Antilles", - "currency_code": "ANG" - }, - { - "country": "New Caledonia", - "currency_code": "XPF" - }, - { - "country": "New Zealand", - "currency_code": "NZD" - }, - { - "country": "Nicaragua", - "currency_code": "NIO" - }, - { - "country": "Niger", - "currency_code": "XOF" - }, - { - "country": "Nigeria", - "currency_code": "NGN" - }, - { - "country": "Niue", - "currency_code": "NZD" - }, - { - "country": "Norfolk Island", - "currency_code": "AUD" - }, - { - "country": "North Korea", - "currency_code": "KPW" - }, - { - "country": "Northern Ireland", - "currency_code": "GBP" - }, - { - "country": "Northern Mariana Islands", - "currency_code": "USD" - }, - { - "country": "Norway", - "currency_code": "NOK" - }, - { - "country": "Oman", - "currency_code": "OMR" - }, - { - "country": "Pakistan", - "currency_code": "PKR" - }, - { - "country": "Palau", - "currency_code": "USD" - }, - { - "country": "Palestine", - "currency_code": null - }, - { - "country": "Panama", - "currency_code": "PAB" - }, - { - "country": "Papua New Guinea", - "currency_code": "PGK" - }, - { - "country": "Paraguay", - "currency_code": "PYG" - }, - { - "country": "Peru", - "currency_code": "PEN" - }, - { - "country": "Philippines", - "currency_code": "PHP" - }, - { - "country": "Pitcairn", - "currency_code": "NZD" - }, - { - "country": "Poland", - "currency_code": "PLN" - }, - { - "country": "Portugal", - "currency_code": "EUR" - }, - { - "country": "Puerto Rico", - "currency_code": "USD" - }, - { - "country": "Qatar", - "currency_code": "QAR" - }, - { - "country": "Reunion", - "currency_code": "EUR" - }, - { - "country": "Romania", - "currency_code": "RON" - }, - { - "country": "Russian Federation", - "currency_code": "RUB" - }, - { - "country": "Rwanda", - "currency_code": "RWF" - }, - { - "country": "Saint Helena", - "currency_code": "SHP" - }, - { - "country": "Saint Kitts and Nevis", - "currency_code": "XCD" - }, - { - "country": "Saint Lucia", - "currency_code": "XCD" - }, - { - "country": "Saint Pierre and Miquelon", - "currency_code": "EUR" - }, - { - "country": "Saint Vincent and the Grenadines", - "currency_code": "XCD" - }, - { - "country": "Samoa", - "currency_code": "WST" - }, - { - "country": "San Marino", - "currency_code": "EUR" - }, - { - "country": "Sao Tome and Principe", - "currency_code": "STD" - }, - { - "country": "Saudi Arabia", - "currency_code": "SAR" - }, - { - "country": "Scotland", - "currency_code": "GBP" - }, - { - "country": "Senegal", - "currency_code": "XOF" - }, - { - "country": "Seychelles", - "currency_code": "SCR" - }, - { - "country": "Sierra Leone", - "currency_code": "SLL" - }, - { - "country": "Singapore", - "currency_code": "SGD" - }, - { - "country": "Slovakia", - "currency_code": "EUR" - }, - { - "country": "Slovenia", - "currency_code": "EUR" - }, - { - "country": "Solomon Islands", - "currency_code": "SBD" - }, - { - "country": "Somalia", - "currency_code": "SOS" - }, - { - "country": "South Africa", - "currency_code": "ZAR" - }, - { - "country": "South Georgia and the South Sandwich Islands", - "currency_code": "GBP" - }, - { - "country": "South Korea", - "currency_code": "KRW" - }, - { - "country": "South Sudan", - "currency_code": "SSP" - }, - { - "country": "Spain", - "currency_code": "EUR" - }, - { - "country": "SriLanka", - "currency_code": "LKR" - }, - { - "country": "Sudan", - "currency_code": "SDG" - }, - { - "country": "Suriname", - "currency_code": "SRD" - }, - { - "country": "Svalbard and Jan Mayen", - "currency_code": "NOK" - }, - { - "country": "Swaziland", - "currency_code": "SZL" - }, - { - "country": "Sweden", - "currency_code": "SEK" - }, - { - "country": "Switzerland", - "currency_code": "CHF" - }, - { - "country": "Syria", - "currency_code": "SYP" - }, - { - "country": "Tajikistan", - "currency_code": "TJS" - }, - { - "country": "Tanzania", - "currency_code": "TZS" - }, - { - "country": "Thailand", - "currency_code": "THB" - }, - { - "country": "The Democratic Republic of Congo", - "currency_code": "CDF" - }, - { - "country": "Togo", - "currency_code": "XOF" - }, - { - "country": "Tokelau", - "currency_code": "NZD" - }, - { - "country": "Tonga", - "currency_code": "TOP" - }, - { - "country": "Trinidad and Tobago", - "currency_code": "TTD" - }, - { - "country": "Tunisia", - "currency_code": "TND" - }, - { - "country": "Turkey", - "currency_code": "TRY" - }, - { - "country": "Turkmenistan", - "currency_code": "TMT" - }, - { - "country": "Turks and Caicos Islands", - "currency_code": "USD" - }, - { - "country": "Tuvalu", - "currency_code": "AUD" - }, - { - "country": "Uganda", - "currency_code": "UGX" - }, - { - "country": "Ukraine", - "currency_code": "UAH" - }, - { - "country": "United Arab Emirates", - "currency_code": "AED" - }, - { - "country": "United Kingdom", - "currency_code": "GBP" - }, - { - "country": "United States", - "currency_code": "USD" - }, - { - "country": "United States Minor Outlying Islands", - "currency_code": "USD" - }, - { - "country": "Uruguay", - "currency_code": "UYU" - }, - { - "country": "Uzbekistan", - "currency_code": "UZS" - }, - { - "country": "Vanuatu", - "currency_code": "VUV" - }, - { - "country": "Venezuela", - "currency_code": "VEF" - }, - { - "country": "Vietnam", - "currency_code": "VND" - }, - { - "country": "Virgin Islands, British", - "currency_code": "USD" - }, - { - "country": "Virgin Islands, U.S.", - "currency_code": "USD" - }, - { - "country": "Wales", - "currency_code": "GBP" - }, - { - "country": "Wallis and Futuna", - "currency_code": "XPF" - }, - { - "country": "Western Sahara", - "currency_code": "MAD" - }, - { - "country": "Yemen", - "currency_code": "YER" - }, - { - "country": "Yugoslavia", - "currency_code": null - }, - { - "country": "Zambia", - "currency_code": "ZMW" - }, - { - "country": "Zimbabwe", - "currency_code": "ZWD" - } -] diff --git a/bumblebee/modules/datetime.py b/bumblebee/modules/datetime.py deleted file mode 100644 index 8899c4a..0000000 --- a/bumblebee/modules/datetime.py +++ /dev/null @@ -1,50 +0,0 @@ -# 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 - * datetime.locale: locale to use rather than the system default - * date.locale : alias for datetime.locale - * time.locale : alias for datetime.locale -""" - -from __future__ import absolute_import -import datetime -import locale -import bumblebee.engine - -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)) - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd="calendar") - self._fmt = self.parameter("format", default_format(self.name)) - l = locale.getdefaultlocale() - if not l or l == (None, None): - l = ('en_US', 'UTF-8') - lcl = self.parameter("locale", ".".join(l)) - try: - locale.setlocale(locale.LC_TIME, lcl.split(".")) - except Exception as e: - locale.setlocale(locale.LC_TIME, ('en_US', 'UTF-8')) - - def get_time(self, widget): - enc = locale.getpreferredencoding() - retval = datetime.datetime.now().strftime(self._fmt) - if hasattr(retval, "decode"): - return retval.decode(enc) - return retval - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/datetimetz.py b/bumblebee/modules/datetimetz.py deleted file mode 100644 index c778be9..0000000 --- a/bumblebee/modules/datetimetz.py +++ /dev/null @@ -1,93 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the current date and time with timezone options. - -Parameters: - * datetimetz.format : strftime()-compatible formatting string - * datetimetz.timezone : IANA timezone name - * datetz.format : alias for datetimetz.format - * timetz.format : alias for datetimetz.format - * timetz.timezone : alias for datetimetz.timezone - * datetimetz.locale : locale to use rather than the system default - * datetz.locale : alias for datetimetz.locale - * timetz.locale : alias for datetimetz.locale - * timetz.timezone : alias for datetimetz.timezone -""" - -from __future__ import absolute_import -import datetime -import locale -import logging -try: - import pytz - import tzlocal -except: - pass -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -def default_format(module): - default = "%x %X %Z" - if module == "datetz": - default = "%x %Z" - if module == "timetz": - default = "%X %Z" - 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)) - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self.next_tz) - engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self.prev_tz) - self._fmt = self.parameter("format", default_format(self.name)) - default_timezone = "" - try: - default_timezone = tzlocal.get_localzone().zone - except Exception as e: - logging.error('unable to get default timezone: {}'.format(str(e))) - try: - self._timezones = self.parameter("timezone", default_timezone).split(",") - except: - self._timezones = [default_timezone] - self._current_tz = 0 - - l = locale.getdefaultlocale() - if not l or l == (None, None): - l = ('en_US', 'UTF-8') - lcl = self.parameter("locale", ".".join(l)) - try: - locale.setlocale(locale.LC_TIME, lcl.split(".")) - except Exception: - locale.setlocale(locale.LC_TIME, ('en_US', 'UTF-8')) - - def get_time(self, widget): - try: - try: - tz = pytz.timezone(self._timezones[self._current_tz].strip()) - retval = datetime.datetime.now(tz=tzlocal.get_localzone()).astimezone(tz).strftime(self._fmt) - except pytz.exceptions.UnknownTimeZoneError: - retval = "[Unknown timezone: {}]".format(self._timezones[self._current_tz].strip()) - except Exception as e: - logging.error('unable to get time: {}'.format(str(e))) - retval = "[n/a]" - - enc = locale.getpreferredencoding() - if hasattr(retval, "decode"): - return retval.decode(enc) - return retval - - def next_tz(self, event): - next_timezone = self._current_tz + 1 - if next_timezone >= len(self._timezones): - next_timezone = 0 # wraparound - self._current_tz = next_timezone - - def prev_tz(self, event): - previous_timezone = self._current_tz - 1 - if previous_timezone < 0: - previous_timezone = 0 # wraparound - self._current_tz = previous_timezone - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/deadbeef.py b/bumblebee/modules/deadbeef.py deleted file mode 100644 index 5b74933..0000000 --- a/bumblebee/modules/deadbeef.py +++ /dev/null @@ -1,143 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the current song being played in DeaDBeeF and provides -some media control bindings. -Left click toggles pause, scroll up skips the current song, scroll -down returns to the previous song. - -Requires the following library: - * subprocess -Parameters: - * deadbeef.format: Format string (defaults to "{artist} - {title}") - Available values are: {artist}, {title}, {album}, {length}, - {trackno}, {year}, {comment}, - {copyright}, {time} - This is deprecated, but much simpler. - * deadbeef.tf_format: A foobar2000 title formatting-style format string. - These can be much more sophisticated than the standard - format strings. This is off by default, but specifying - any tf_format will enable it. If both deadbeef.format - and deadbeef.tf_format are specified, deadbeef.tf_format - takes priority. - * deadbeef.tf_format_if_stopped: Controls whether or not the tf_format format - string should be displayed even if no song is paused or - playing. This could be useful if you want to implement - your own stop strings with the built in logic. Any non- - null value will enable this (by default the module will - hide itself when the player is stopped). - * deadbeef.previous: Change binding for previous song (default is left click) - * deadbeef.next: Change binding for next song (default is right click) - * deadbeef.pause: Change binding for toggling pause (default is middle click) - Available options for deadbeef.previous, deadbeef.next and deadbeef.pause are: - LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN - -""" -import sys - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -from bumblebee.output import scrollable - -try: - import subprocess -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.deadbeef) - ) - buttons = {"LEFT_CLICK": bumblebee.input.LEFT_MOUSE, - "RIGHT_CLICK": bumblebee.input.RIGHT_MOUSE, - "MIDDLE_CLICK": bumblebee.input.MIDDLE_MOUSE, - "SCROLL_UP": bumblebee.input.WHEEL_UP, - "SCROLL_DOWN": bumblebee.input.WHEEL_DOWN, - } - - self._song = "" - self._format = self.parameter("format", "{artist} - {title}") - self._tf_format = self.parameter("tf_format", "") - self._show_tf_when_stopped = bool(self.parameter("tf_format_if_stopped", "")) - prev_button = self.parameter("previous", "LEFT_CLICK") - next_button = self.parameter("next", "RIGHT_CLICK") - pause_button = self.parameter("pause", "MIDDLE_CLICK") - - self.now_playing = ["deadbeef", "--nowplaying", "%a;%t;%b;%l;%n;%y;%c;%r;%e"] - self.now_playing_tf = ["deadbeef", "--nowplaying-tf", ""] - cmd = "deadbeef " - - engine.input.register_callback(self, button=buttons[prev_button], - cmd=cmd + "--prev") - engine.input.register_callback(self, button=buttons[next_button], - cmd=cmd + "--next") - engine.input.register_callback(self, button=buttons[pause_button], - cmd=cmd + "--play-pause") - - # modify the tf_format if we don't want it to show on stop - # this adds conditions to the query itself, rather than - # polling to see if deadbeef is running - # doing this reduces the number of calls we have to make - if self._tf_format and not self._show_tf_when_stopped: - self._tf_format = "$if($or(%isplaying%,%ispaused%),{query})".format(query=self._tf_format) - - @scrollable - def deadbeef(self, widget): - return self.string_song - - def hidden(self): - return self.string_song == "" - - def update(self, widgets): - try: - if self._tf_format == "": # no tf format set, use the old style - return self.update_standard(widgets) - return self.update_tf(widgets) - except Exception: - self._song = "error" - - def update_tf(self, widgets): - ## ensure that deadbeef is actually running - ## easiest way to do this is to check --nowplaying for - ## the string "nothing" - if read_process(self.now_playing) == "nothing": - self._song = "" - return - ## perform the actual query -- these can be much more sophisticated - self.now_playing_tf[-1] = self._tf_format - data = read_process(self.now_playing_tf) - self._song = data - - def update_standard(self, widgets): - data = read_process(self.now_playing) - if data == "nothing": - self._song = "" - else: - data = data.split(";") - self._song = self._format.format(artist=data[0], - title=data[1], - album=data[2], - length=data[3], - trackno=data[4], - year=data[5], - comment=data[6], - copyright=data[7], - time=data[8]) - - @property - def string_song(self): - """\ -Returns the current song as a string, either as a unicode() (Python < -3) or a regular str() (Python >= 3) - """ - if sys.version_info.major < 3: - return unicode(self._song) - return str(self._song) - -def read_process(command): - proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) - return proc.stdout.read() - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/deezer.py b/bumblebee/modules/deezer.py deleted file mode 100644 index 582c7fc..0000000 --- a/bumblebee/modules/deezer.py +++ /dev/null @@ -1,77 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the current song being played -Requires the following library: - * python-dbus -Parameters: - * deezer.format: Format string (defaults to "{artist} - {title}") - Available values are: {album}, {title}, {artist}, {trackNumber}, {playbackStatus} - * deezer.previous: Change binding for previous song (default is left click) - * deezer.next: Change binding for next song (default is right click) - * deezer.pause: Change binding for toggling pause (default is middle click) - Available options for deezer.previous, deezer.next and deezer.pause are: - LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN -""" - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -from bumblebee.output import scrollable - -try: - import dbus -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.deezer) - ) - buttons = {"LEFT_CLICK":bumblebee.input.LEFT_MOUSE, - "RIGHT_CLICK":bumblebee.input.RIGHT_MOUSE, - "MIDDLE_CLICK":bumblebee.input.MIDDLE_MOUSE, - "SCROLL_UP":bumblebee.input.WHEEL_UP, - "SCROLL_DOWN":bumblebee.input.WHEEL_DOWN, - } - - self._song = "" - self._format = self.parameter("format", "{artist} - {title}") - prev_button = self.parameter("previous", "LEFT_CLICK") - next_button = self.parameter("next", "RIGHT_CLICK") - pause_button = self.parameter("pause", "MIDDLE_CLICK") - - cmd = "dbus-send --session --type=method_call --dest=org.mpris.MediaPlayer2.deezer \ - /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player." - engine.input.register_callback(self, button=buttons[prev_button], - cmd=cmd + "Previous") - engine.input.register_callback(self, button=buttons[next_button], - cmd=cmd + "Next") - engine.input.register_callback(self, button=buttons[pause_button], - cmd=cmd + "PlayPause") - -## @scrollable - def deezer(self, widget): - return str(self._song) - - def hidden(self): - return str(self._song) == "" - - def update(self, widgets): - try: - bus = dbus.SessionBus() - deezer = bus.get_object("org.mpris.MediaPlayer2.deezer", "/org/mpris/MediaPlayer2") - deezer_iface = dbus.Interface(deezer, 'org.freedesktop.DBus.Properties') - props = deezer_iface.Get('org.mpris.MediaPlayer2.Player', 'Metadata') - playback_status = str(deezer_iface.Get('org.mpris.MediaPlayer2.Player', 'PlaybackStatus')) - self._song = self._format.format(album=str(props.get('xesam:album')), - title=str(props.get('xesam:title')), - artist=','.join(props.get('xesam:artist')), - trackNumber=str(props.get('xesam:trackNumber')), - playbackStatus=u"\u25B6" if playback_status=="Playing" else u"\u258D\u258D" if playback_status=="Paused" else "",) - except Exception: - self._song = "" - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/disk.py b/bumblebee/modules/disk.py deleted file mode 100644 index db4d522..0000000 --- a/bumblebee/modules/disk.py +++ /dev/null @@ -1,88 +0,0 @@ -# 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 /) - * disk.open: Which application / file manager to launch (default xdg-open) - * disk.format: Format string, tags {path}, {used}, {left}, {size} and {percent} (defaults to "{path} {used}/{size} ({percent:05.02f}%)") - * (deprecated) disk.showUsed: Show used space (defaults to yes) - * (deprecated) disk.showSize: Show total size (defaults to yes) - * (deprecated) disk.showPercent: Show usage percentage (defaults to yes) -""" - -import os - -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import bumblebee.util - -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._format = self.parameter("format", "{used}/{size} ({percent:05.02f}%)") - self._app = self.parameter("open", "xdg-open") - - self._used = 0 - self._left = 0 - self._size = 0 - self._percent = 0 - - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd="{} {}".format(self._app, - self._path)) - - - def diskspace(self, widget): - used_str = bumblebee.util.bytefmt(self._used) - size_str = bumblebee.util.bytefmt(self._size) - left_str = bumblebee.util.bytefmt(self._left) - percent_str = self._percent - - sused = self.has_parameter("showUsed") - ssize = self.has_parameter("showSize") - spercent = self.has_parameter("showPercent") - - if all(not param for param in (sused, ssize, spercent)): - return self._format.format(path=self._path, - used=used_str, - left=left_str, - size=size_str, - percent=percent_str) - else: - rv = "" - sused = bumblebee.util.asbool(self.parameter("showUsed", True)) - ssize = bumblebee.util.asbool(self.parameter("showSize", True)) - spercent = bumblebee.util.asbool(self.parameter("showPercent", True)) - - if sused: - rv = "{}{}".format(rv, used_str) - if sused and ssize: - rv = "{}/".format(rv) - if ssize: - rv = "{}{}".format(rv, size_str) - if spercent: - if not sused and not ssize: - rv = "{:05.02f}%".format(percent_str) - else: - rv = "{} ({:05.02f}%)".format(rv, percent_str) - return rv - - - 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._left = self._size - self._used; - self._percent = 100.0 * self._used/self._size - - def state(self, widget): - return self.threshold_state(self._percent, 80, 90) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/dnf.py b/bumblebee/modules/dnf.py deleted file mode 100644 index d3ceebf..0000000 --- a/bumblebee/modules/dnf.py +++ /dev/null @@ -1,77 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays DNF package update information (///) - -Requires the following executable: - * dnf - -Parameters: - * dnf.interval: Time in minutes between two consecutive update checks (defaults to 30 minutes) - -""" - -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.interval_factor(60) - self.interval(30) - - 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): - thread = threading.Thread(target=get_dnf_info, args=(widgets[0],)) - thread.start() - - 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/bumblebee/modules/docker_ps.py b/bumblebee/modules/docker_ps.py deleted file mode 100644 index c36c0e7..0000000 --- a/bumblebee/modules/docker_ps.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- - -"""Displays the number of docker containers running - -Requires the following python packages: - * docker - -""" - -try: - import docker -except ImportError: - pass - -from requests.exceptions import ConnectionError - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widget = bumblebee.output.Widget(full_text=self.status) - super(Module, self).__init__(engine, config, widget) - self._status = self.status - - def update(self, widgets): - self._status = self.status - - def status(self, _): - try: - cli = docker.DockerClient(base_url='unix://var/run/docker.sock') - cli.ping() - except ConnectionError: - return "Daemon off" - except Exception: - return "n/a" - return "OK - {}".format(len(cli.containers.list(filters={'status': "running"}))) diff --git a/bumblebee/modules/dunst.py b/bumblebee/modules/dunst.py deleted file mode 100644 index e805ef5..0000000 --- a/bumblebee/modules/dunst.py +++ /dev/null @@ -1,39 +0,0 @@ -#pylint: disable=C0111,R0903 - -"""Toggle dunst notifications.""" - -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._paused = False - # Make sure that dunst is currently not paused - try: - bumblebee.util.execute("killall -SIGUSR2 dunst") - except: - pass - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd=self.toggle_status - ) - - def toggle_status(self, event): - self._paused = not self._paused - - try: - if self._paused: - bumblebee.util.execute("killall -SIGUSR1 dunst") - else: - bumblebee.util.execute("killall -SIGUSR2 dunst") - except: - self._paused = not self._paused # toggling failed - - def state(self, widget): - if self._paused: - return ["muted", "warning"] - return ["unmuted"] diff --git a/bumblebee/modules/error.py b/bumblebee/modules/error.py deleted file mode 100644 index 16d3834..0000000 --- a/bumblebee/modules/error.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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/bumblebee/modules/getcrypto.py b/bumblebee/modules/getcrypto.py deleted file mode 100644 index d75b373..0000000 --- a/bumblebee/modules/getcrypto.py +++ /dev/null @@ -1,75 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the price of a cryptocurrency. - -Requires the following python packages: - * requests - -Parameters: - * getcrypto.interval: Interval in seconds for updating the price, default is 120, less than that will probably get your IP banned. - * getcrypto.getbtc: 0 for not getting price of BTC, 1 for getting it (default). - * getcrypto.geteth: 0 for not getting price of ETH, 1 for getting it (default). - * getcrypto.getltc: 0 for not getting price of LTC, 1 for getting it (default). - * getcrypto.getcur: Set the currency to display the price in, usd is the default. -""" - -import requests -import time -import bumblebee.util -import bumblebee.input -import bumblebee.output -import bumblebee.engine -from requests.exceptions import RequestException -def getfromkrak(coin, currency): - abbrev = { - "Btc": ["xbt", "XXBTZ"], - "Eth": ["eth", "XETHZ"], - "Ltc": ["ltc", "XLTCZ"], - } - data = abbrev.get(coin, None) - if not data: return - epair = "{}{}".format(data[0], currency) - tickname = "{}{}".format(data[1], currency.upper()) - try: - krakenget = requests.get('https://api.kraken.com/0/public/Ticker?pair='+epair).json() - except (RequestException, Exception): - return "No connection" - if not 'result' in krakenget: - return "No data" - kethusdask = float(krakenget['result'][tickname]['a'][0]) - kethusdbid = float(krakenget['result'][tickname]['b'][0]) - return coin+": "+str((kethusdask+kethusdbid)/2)[0:6] - - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.curprice) - ) - self._curprice = "" - self._nextcheck = 0 - self._interval = int(self.parameter("interval", "120")) - self._getbtc = bumblebee.util.asbool(self.parameter("getbtc", True)) - self._geteth = bumblebee.util.asbool(self.parameter("geteth", True)) - self._getltc = bumblebee.util.asbool(self.parameter("getltc", True)) - self._getcur = self.parameter("getcur", "usd") - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd="xdg-open https://cryptowat.ch/") - - def curprice(self, widget): - return self._curprice - - def update(self, widgets): - if self._nextcheck < int(time.time()): - self._nextcheck = int(time.time()) + self._interval - currency = self._getcur - btcprice, ethprice, ltcprice = "", "", "" - if self._getbtc: - btcprice = getfromkrak('Btc', currency) - if self._geteth: - ethprice = getfromkrak('Eth', currency) - if self._getltc: - ltcprice = getfromkrak('Ltc', currency) - self._curprice = btcprice+" "*(self._getbtc*self._geteth)+ethprice+" "*(self._getltc*max(self._getbtc, self._geteth))+ltcprice - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/git.py b/bumblebee/modules/git.py deleted file mode 100644 index 468d174..0000000 --- a/bumblebee/modules/git.py +++ /dev/null @@ -1,81 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Print the branch and git status for the -currently focused window. - -Requires: - * xcwd - * Python module 'pygit2' -""" - -import os -import string -import logging -try: - import pygit2 -except ImportError: - pass - -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import bumblebee.util - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widgets = [] - super(Module, self).__init__(engine, config, widgets) - self._engine = engine - self._error = False - self.update(self.widgets()) - - def hidden(self): - return self._error - - def update(self, widgets): - state = {} - new_widgets = [] - try: - directory = bumblebee.util.execute("xcwd").strip() - directory = self._get_git_root(directory) - repo = pygit2.Repository(directory) - - new_widgets.append(bumblebee.output.Widget(name='git.main', full_text=repo.head.shorthand)) - - for filepath, flags in repo.status().items(): - if flags == pygit2.GIT_STATUS_WT_NEW or \ - flags == pygit2.GIT_STATUS_INDEX_NEW: - state['new'] = True - if flags == pygit2.GIT_STATUS_WT_DELETED or \ - flags == pygit2.GIT_STATUS_INDEX_DELETED: - state['deleted'] = True - if flags == pygit2.GIT_STATUS_WT_MODIFIED or \ - flags == pygit2.GIT_STATUS_INDEX_MODIFIED: - state['modified'] = True - self._error = False - if 'new' in state: - new_widgets.append(bumblebee.output.Widget(name='git.new')) - if 'modified' in state: - new_widgets.append(bumblebee.output.Widget(name='git.modified')) - if 'deleted' in state: - new_widgets.append(bumblebee.output.Widget(name='git.deleted')) - - while len(widgets) > 0: - del widgets[0] - for widget in new_widgets: - widgets.append(widget) - - except Exception as e: - self._error = True - - def state(self, widget): - return widget.name.split('.')[1] - - def _get_git_root(self, directory): - while len(directory) > 1: - if os.path.exists(os.path.join(directory, ".git")): - return directory - directory = "/".join(directory.split("/")[0:-1]) - return "/" - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/github.py b/bumblebee/modules/github.py deleted file mode 100644 index 3a3d097..0000000 --- a/bumblebee/modules/github.py +++ /dev/null @@ -1,56 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the unread GitHub notifications for a GitHub user - -Requires the following library: - * requests - -Parameters: - * github.token: GitHub user access token, the token needs to have the 'notifications' scope. - * github.interval: Interval in minutes between updates, default is 5. -""" - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -try: - import requests -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.github) - ) - self._count = 0 - self.interval_factor(60) - self.interval(5) - self._requests = requests.Session() - self._requests.headers.update({"Authorization":"token {}".format(self.parameter("token", ""))}) - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd="x-www-browser https://github.com/notifications") - engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self.update) - - def github(self, _): - return str(self._count) - - def update(self, _): - try: - self._count = 0 - url = "https://api.github.com/notifications" - while True: - notifications = self._requests.get(url) - self._count += len(list(filter(lambda notification: notification['unread'], notifications.json()))) - next_link = notifications.links.get('next') - if next_link is not None: - url = next_link.get('url') - else: - break - - except Exception: - self._count = "n/a" - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/gpmdp.py b/bumblebee/modules/gpmdp.py deleted file mode 100644 index 922145f..0000000 --- a/bumblebee/modules/gpmdp.py +++ /dev/null @@ -1,56 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays information about the current song in Google Play music player. - -Requires the following executable: - * gpmdp-remote -""" - -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/bumblebee/modules/hddtemp.py b/bumblebee/modules/hddtemp.py deleted file mode 100644 index 86800c1..0000000 --- a/bumblebee/modules/hddtemp.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- - -"""Fetch hard drive temeperature data from a hddtemp daemon -that runs on localhost and default port (7634) -""" - -import socket - -import bumblebee.engine -import bumblebee.output - -HOST = "localhost" -PORT = 7634 - -CHUNK_SIZE = 1024 -RECORD_SIZE = 5 -SEPARATOR = "|" - - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widget = bumblebee.output.Widget(full_text=self.hddtemps) - super(Module, self).__init__(engine, config, widget) - self._hddtemps = self._get_hddtemps() - - def hddtemps(self, __): - return self._hddtemps - - def _fetch_data(self): - """fetch data from hddtemp service""" - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - sock.connect((HOST, PORT)) - data = "" - while True: - chunk = sock.recv(CHUNK_SIZE) - if chunk: - data += str(chunk) - else: - break - return data - except (AttributeError, socket.error) as e: - pass - - @staticmethod - def _get_parts(data): - """ - split data using | separator and remove first item - (because the first item is empty) - """ - parts = data.split("|")[1:] - return parts - - @staticmethod - def _partition_parts(parts): - """ - partition parts: one device record is five (5) items - """ - per_disk = [parts[i:i+RECORD_SIZE] - for i in range(len(parts))[::RECORD_SIZE]] - return per_disk - - @staticmethod - def _get_name_and_temp(device_record): - """ - get device name (without /dev part, to save space on bar) - and temperature (in °C) as tuple - """ - device_name = device_record[0].split("/")[-1] - device_temp = device_record[2] - return (device_name, device_temp) - - @staticmethod - def _get_hddtemp(device_record): - name, temp = device_record - hddtemp = "{}+{}°C".format(name, temp) - return hddtemp - - def _get_hddtemps(self): - data = self._fetch_data() - if data is None: - return "n/a" - parts = self._get_parts(data) - per_disk = self._partition_parts(parts) - names_and_temps = [self._get_name_and_temp(x) for x in per_disk] - hddtemps = [self._get_hddtemp(x) for x in names_and_temps] - return SEPARATOR.join(hddtemps) - - def update(self, __): - self._hddtemps = self._get_hddtemps() diff --git a/bumblebee/modules/hostname.py b/bumblebee/modules/hostname.py deleted file mode 100644 index 4b0f987..0000000 --- a/bumblebee/modules/hostname.py +++ /dev/null @@ -1,24 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the system hostname.""" - -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._hname = "" - - def output(self, _): - return self._hname+" "+u"\uf233" - - def update(self, widgets): - with open('/proc/sys/kernel/hostname', 'r') as f: - self._hname = f.readline().split()[0] - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/http_status.py b/bumblebee/modules/http_status.py deleted file mode 100644 index ae84655..0000000 --- a/bumblebee/modules/http_status.py +++ /dev/null @@ -1,68 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Display HTTP status code - -Parameters: - * http_status.label: Prefix label (optional) - * http_status.target: Target to retrieve the HTTP status from - * http_status.expect: Expected HTTP status -""" - -from requests import head - -import psutil -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -class Module(bumblebee.engine.Module): - UNK = "UNK" - - def __init__(self, engine, config): - widget = bumblebee.output.Widget(full_text=self.output) - super(Module, self).__init__(engine, config, widget) - - self._label = self.parameter("label") - self._target = self.parameter("target") - self._expect = self.parameter("expect", "200") - self._status = self.getStatus() - self._output = self.getOutput() - - def labelize(self, s): - if self._label is None: - return s - return "{}: {}".format(self._label, s) - - def getStatus(self): - try: - res = head(self._target) - except Exception: - return self.UNK - else: - status = str(res.status_code) - self._status = status - return status - - def getOutput(self): - if self._status == self._expect: - return self.labelize(self._status) - else: - reason = " != {}".format(self._expect) - return self.labelize("{}{}".format(self._status, reason)) - - def output(self, widget): - return self._output - - def update(self, widgets): - self.getStatus() - self._output = self.getOutput() - - def state(self, widget): - if self._status == self.UNK: - return "warning" - if self._status != self._expect: - return "critical" - return self._output - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/indicator.py b/bumblebee/modules/indicator.py deleted file mode 100644 index 3b57e33..0000000 --- a/bumblebee/modules/indicator.py +++ /dev/null @@ -1,51 +0,0 @@ -#pylint: disable=C0111,R0903 - -"""Displays the indicator status, for numlock, scrolllock and capslock - -Parameters: - * indicator.include: Comma-separated list of interface prefixes to include (defaults to "numlock,capslock") - * indicator.signalstype: If you want the signali type color to be "critical" or "warning" (defaults to "warning") -""" - - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widgets = [] - self.status = False - super(Module,self).__init__(engine, config, widgets) - self._include = tuple(filter(len, self.parameter("include", "NumLock,CapsLock").split(","))) - self._signalType = self.parameter("signaltype") if not self.parameter("signaltype") is None else "warning" - - def update(self, widgets): - self._update_widgets(widgets) - - def state(self, widget): - states = [] - if widget.status: - states.append(self._signalType) - elif not widget.status: - states.append("normal") - return states - - def _update_widgets(self, widgets): - status_line = "" - for line in bumblebee.util.execute("xset q").replace(" ", "").split("\n"): - if "capslock" in line.lower(): - status_line = line - break - - for indicator in self._include: - widget = self.widget(indicator) - if not widget: - widget = bumblebee.output.Widget(name=indicator) - widgets.append(widget) - - widget.status = True if indicator.lower()+":on" in status_line.lower() else False - widget.full_text(indicator) - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/kernel.py b/bumblebee/modules/kernel.py deleted file mode 100644 index 761fa8c..0000000 --- a/bumblebee/modules/kernel.py +++ /dev/null @@ -1,21 +0,0 @@ -# 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/bumblebee/modules/layout-xkb.py b/bumblebee/modules/layout-xkb.py deleted file mode 100644 index 04f4114..0000000 --- a/bumblebee/modules/layout-xkb.py +++ /dev/null @@ -1,65 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the current keyboard layout using libX11 - -Requires the following library: - * libX11.so.6 -and python module: - * xkbgroup - -Parameters: - * layout-xkb.showname: Boolean that indicate whether the full name should be displayed. Defaults to false (only the symbol will be displayed) - * layout-xkb.show_variant: Boolean that indecates whether the variant name should be displayed. Defaults to true. -""" - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -has_xkb = True -try: - from xkbgroup import * -except ImportError: - has_xkb = False - -import logging -log = logging.getLogger(__name__) - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.current_layout) - ) - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd=self._next_keymap) - engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, - cmd=self._prev_keymap) - self._show_variant = bumblebee.util.asbool(self.parameter("show_variant", "true")) - - def _next_keymap(self, event): - self._set_keymap(1) - - def _prev_keymap(self, event): - self._set_keymap(-1) - - def _set_keymap(self, rotation): - if not has_xkb: return - - xkb = XKeyboard() - if xkb.groups_count < 2: return # nothing to doA - layouts = xkb.groups_symbols - idx = layouts.index(xkb.group_symbol) - xkb.group_symbol = str(layouts[(idx + rotation) % len(layouts)]) - - def current_layout(self, widget): - try: - xkb = XKeyboard() - log.debug("group num: {}".format(xkb.group_num)) - name = xkb.group_name if bumblebee.util.asbool(self.parameter("showname")) else xkb.group_symbol - if self._show_variant: - return "{} ({})".format(name, xkb.group_variant) if xkb.group_variant else name - return name - except Exception: - return "n/a" - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/layout-xkbswitch.py b/bumblebee/modules/layout-xkbswitch.py deleted file mode 100644 index e65d30a..0000000 --- a/bumblebee/modules/layout-xkbswitch.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Displays and changes the current keyboard layout - -Requires the following executable: - * xkb-switch -""" - -import bumblebee.util -import bumblebee.input -import bumblebee.output -import bumblebee.engine - - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widget = bumblebee.output.Widget(full_text=self.current_layout) - super(Module, self).__init__(engine, config, widget) - engine.input.register_callback( - self, - button=bumblebee.input.LEFT_MOUSE, - cmd=self._next_keymap) - self._current_layout = self._get_current_layout() - - def current_layout(self, __): - return self._current_layout - - def _next_keymap(self, event): - try: - bumblebee.util.execute("xkb-switch -n") - except RuntimeError: - pass - - def _get_current_layout(self): - try: - res = bumblebee.util.execute("xkb-switch") - return res.split("\n")[0] - except RuntimeError: - return ["n/a"] - - def update(self, __): - self._current_layout = self._get_current_layout() diff --git a/bumblebee/modules/layout.py b/bumblebee/modules/layout.py deleted file mode 100644 index 49c59a4..0000000 --- a/bumblebee/modules/layout.py +++ /dev/null @@ -1,72 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays and changes the current keyboard layout - -Requires the following executable: - * setxkbmap -""" - -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.current_layout) - ) - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd=self._next_keymap) - engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, - cmd=self._prev_keymap) - - def _next_keymap(self, event): - self._set_keymap(1) - - def _prev_keymap(self, event): - self._set_keymap(-1) - - def _set_keymap(self, rotation): - layouts = self.get_layouts() - if len(layouts) == 1: return # nothing to do - layouts = layouts[rotation:] + layouts[:rotation] - - layout_list = [] - variant_list = [] - for l in layouts: - tmp = l.split(":") - layout_list.append(tmp[0]) - variant_list.append(tmp[1] if len(tmp) > 1 else "") - - try: - bumblebee.util.execute("setxkbmap -layout {} -variant {}".format(",".join(layout_list), ",".join(variant_list))) - except RuntimeError: - pass - - def get_layouts(self): - try: - res = bumblebee.util.execute("setxkbmap -query") - except RuntimeError: - return ["n/a"] - layouts = [] - variants = [] - for line in res.split("\n"): - if not line: continue - if "layout" in line: - layouts = line.split(":")[1].strip().split(",") - if "variant" in line: - variants = line.split(":")[1].strip().split(",") - - result = [] - for idx, layout in enumerate(layouts): - if len(variants) > idx and variants[idx]: - layout = "{}:{}".format(layout, variants[idx]) - result.append(layout) - return result if len(result) > 0 else ["n/a"] - - def current_layout(self, widget): - layouts = self.get_layouts() - return layouts[0] - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/load.py b/bumblebee/modules/load.py deleted file mode 100644 index 4d94ee1..0000000 --- a/bumblebee/modules/load.py +++ /dev/null @@ -1,41 +0,0 @@ -# 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/bumblebee/modules/memory.py b/bumblebee/modules/memory.py deleted file mode 100644 index 1ff2f25..0000000 --- a/bumblebee/modules/memory.py +++ /dev/null @@ -1,72 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays available RAM, total amount of RAM and percentage available. - -Parameters: - * memory.warning : Warning threshold in % of memory used (defaults to 80%) - * memory.critical: Critical threshold in % of memory used (defaults to 90%) - * memory.format: Format string (defaults to "{used}/{total} ({percent:05.02f}%)") - * memory.usedonly: Only show the amount of RAM in use (defaults to False). Same as memory.format="{used}" -""" - -import re - -import bumblebee.util -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -class Container(object): - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - -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.update(None) - - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd="gnome-system-monitor") - - @property - def _format(self): - if bumblebee.util.asbool(self.parameter("usedonly", False)): - return "{used}" - else: - return self.parameter("format", "{used}/{total} ({percent:05.02f}%)") - - def memory_usage(self, widget): - return self._format.format(**self._mem) - - def update(self, widgets): - data = {} - with open("/proc/meminfo", "r") as f: - for line in f: - tmp = re.split(r"[:\s]+", line) - value = int(tmp[1]) - if tmp[2] == "kB": value = value*1024 - if tmp[2] == "mB": value = value*1024*1024 - if tmp[2] == "gB": value = value*1024*1024*1024 - data[tmp[0]] = value - if "MemAvailable" in data: - used = data["MemTotal"] - data["MemAvailable"] - else: - used = data["MemTotal"] - data["MemFree"] - data["Buffers"] - data["Cached"] - data["Slab"] - self._mem = { - "total": bumblebee.util.bytefmt(data["MemTotal"]), - "available": bumblebee.util.bytefmt(data["MemAvailable"]), - "free": bumblebee.util.bytefmt(data["MemFree"]), - "used": bumblebee.util.bytefmt(used), - "percent": float(used)/float(data["MemTotal"])*100.0 - } - - 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/bumblebee/modules/mocp.py b/bumblebee/modules/mocp.py deleted file mode 100644 index 3063f50..0000000 --- a/bumblebee/modules/mocp.py +++ /dev/null @@ -1,60 +0,0 @@ -# pylint: disable=C0111,R0903 -# -*- coding: utf-8 -*- - -"""Displays information about the current song in mocp. Left click toggles play/pause. Right click toggles shuffle. - -Requires the following executable: - * mocp - -Parameters: - * mocp.format: Format string for the song information. Replace string sequences with the actual information: - %state State - %file File - %title Title, includes track, artist, song title and album - %artist Artist - %song SongTitle - %album Album - %tt TotalTime - %tl TimeLeft - %ts TotalSec - %ct CurrentTime - %cs CurrentSec - %b Bitrate - %r Sample rate -""" - -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): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(name="mocp.main", full_text=self.description) - ) - - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd="mocp -G") - engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, - cmd="mocp -t shuffle") - self._format = self.parameter("format", "%state %artist - %song | %ct/%tt") - self._running = 0 - - #@scrollable - def description(self, widget): - return self._info if self._running == 1 else "Music On Console Player" - - def update(self, widgets): - self._load_song() - - def _load_song(self): - try: - self._info = bumblebee.util.execute("mocp -Q '" + self._format + "'" ).strip() - self._running = 1 - except RuntimeError: - self._running = 0 - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/mpd.py b/bumblebee/modules/mpd.py deleted file mode 100644 index 8b2650d..0000000 --- a/bumblebee/modules/mpd.py +++ /dev/null @@ -1,181 +0,0 @@ -# pylint: disable=C0111,R0903 -# -*- coding: utf-8 -*- - -"""Displays information about the current song in mpd. - -Requires the following executable: - * mpc - -Parameters: - * mpd.format: Format string for the song information. - Supported tags (see `man mpc` for additional information) - * {name} - * {artist} - * {album} - * {albumartist} - * {comment} - * {composer} - * {date} - * {originaldate} - * {disc} - * {genre} - * {performer} - * {title} - * {track} - * {time} - * {file} - * {id} - * {prio} - * {mtime} - * {mdate} - Additional tags: - * {position} - position of currently playing song - not to be confused with %position% mpc tag - * {duration} - duration of currently playing song - * {file1} - song file name without path prefix - if {file} = '/foo/bar.baz', then {file1} = 'bar.baz' - * {file2} - song file name without path prefix and extension suffix - if {file} = '/foo/bar.baz', then {file2} = 'bar' - * mpd.host: MPD host to connect to. (mpc behaviour by default) - * mpd.layout: Space-separated list of widgets to add. Possible widgets are the buttons/toggles mpd.prev, mpd.next, mpd.shuffle and mpd.repeat, and the main display with play/pause function mpd.main. -""" - -from collections import defaultdict - -import string -import os - -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): - super(Module, self).__init__(engine, config, []) - - self._layout = self.parameter("layout", "mpd.prev mpd.main mpd.next mpd.shuffle mpd.repeat") - - self._fmt = self.parameter("format", "{artist} - {title} {position}/{duration}") - self._status = None - self._shuffle = False - self._repeat = False - self._tags = defaultdict(lambda: '') - - if not self.parameter("host"): - self._hostcmd = "" - else: - self._hostcmd = " -h " + self.parameter("host") - - # Create widgets - widget_list = [] - widget_map = {} - for widget_name in self._layout.split(): - widget = bumblebee.output.Widget(name=widget_name) - widget_list.append(widget) - - if widget_name == "mpd.prev": - widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc prev" + self._hostcmd} - elif widget_name == "mpd.main": - widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc toggle" + self._hostcmd} - widget.full_text(self.description) - elif widget_name == "mpd.next": - widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc next" + self._hostcmd} - elif widget_name == "mpd.shuffle": - widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc random" + self._hostcmd} - elif widget_name == "mpd.repeat": - widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc repeat" + self._hostcmd} - else: - raise KeyError("The mpd module does not support a {widget_name!r} widget".format(widget_name=widget_name)) - self.widgets(widget_list) - - # Register input callbacks - for widget, callback_options in widget_map.items(): - engine.input.register_callback(widget, **callback_options) - - def hidden(self): - return self._status is None - - @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 == "mpd.shuffle": - return "shuffle-on" if self._shuffle else "shuffle-off" - if widget.name == "mpd.repeat": - return "repeat-on" if self._repeat else "repeat-off" - if widget.name == "mpd.prev": - return "prev" - if widget.name == "mpd.next": - return "next" - return self._status - - def _load_song(self): - info = "" - try: - tags = ['name', - 'artist', - 'album', - 'albumartist', - 'comment', - 'composer', - 'date', - 'originaldate', - 'disc', - 'genre', - 'performer', - 'title', - 'track', - 'time', - 'file', - 'id', - 'prio', - 'mtime', - 'mdate'] - joinedtags = "\n".join(["tag {0} %{0}%".format(tag) for tag in tags]) - info = bumblebee.util.execute('mpc -f ' + '"' + joinedtags + '"' + self._hostcmd) - except RuntimeError: - pass - self._tags = defaultdict(lambda: '') - self._status = None - for line in info.split("\n"): - if line.startswith("[playing]"): - self._status = "playing" - elif line.startswith("[paused]"): - self._status = "paused" - - if line.startswith("["): - timer = line.split()[2] - position = timer.split("/")[0] - dur = timer.split("/")[1] - duration = dur.split(" ")[0] - self._tags.update({'position': position}) - self._tags.update({'duration': duration}) - - if line.startswith("volume"): - value = line.split(" ", 2)[1:] - for option in value: - if option.startswith("repeat: on"): - self._repeat = True - elif option.startswith("repeat: off"): - self._repeat = False - elif option.startswith("random: on"): - self._shuffle = True - elif option.startswith("random: off"): - self._shuffle = False - if line.startswith("tag"): - key, value = line.split(" ", 2)[1:] - self._tags.update({key: value}) - if key == "file": - self._tags.update({"file1": os.path.basename(value)}) - self._tags.update( - {"file2": - os.path.splitext(os.path.basename(value))[0]}) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/network_traffic.py b/bumblebee/modules/network_traffic.py deleted file mode 100644 index f15a19f..0000000 --- a/bumblebee/modules/network_traffic.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""Displays network traffic - * No extra configuration needed -""" - -import psutil -import netifaces - -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import bumblebee.util - -WIDGET_NAME = 'network_traffic' - -class Module(bumblebee.engine.Module): - """Bumblebee main module """ - - def __init__(self, engine, config): - super(Module, self).__init__(engine, config) - - try: - self._bandwidth = BandwidthInfo() - - self._bytes_recv = self._bandwidth.bytes_recv() - self._bytes_sent = self._bandwidth.bytes_sent() - except Exception: - """ We do not want do explode anything """ - pass - - @classmethod - def state(cls, widget): - """Return the widget state""" - - if widget.name == '{}.rx'.format(WIDGET_NAME): - return 'rx' - elif widget.name == '{}.tx'.format(WIDGET_NAME): - return 'tx' - - return None - - def update(self, widgets): - try: - bytes_recv = self._bandwidth.bytes_recv() - bytes_sent = self._bandwidth.bytes_sent() - - download_rate = (bytes_recv - self._bytes_recv) - upload_rate = (bytes_sent - self._bytes_sent) - - self.update_widgets(widgets, download_rate, upload_rate) - - self._bytes_recv, self._bytes_sent = bytes_recv, bytes_sent - except Exception: - """ We do not want do explode anything """ - pass - - @classmethod - def update_widgets(cls, widgets, download_rate, upload_rate): - """Update tx/rx widgets with new rates""" - del widgets[:] - - widgets.extend(( - TrafficWidget(text=download_rate, direction='rx'), - TrafficWidget(text=upload_rate, direction='tx') - )) - - -class BandwidthInfo(object): - """Get received/sent bytes from network adapter""" - - def bytes_recv(self): - """Return received bytes""" - return self.bandwidth().bytes_recv - - def bytes_sent(self): - """Return sent bytes""" - return self.bandwidth().bytes_sent - - def bandwidth(self): - """Return bandwidth information""" - io_counters = self.io_counters() - return io_counters[self.default_network_adapter()] - - @classmethod - def default_network_adapter(cls): - """Return default active network adapter""" - gateway = netifaces.gateways()['default'] - - if not gateway: - raise 'No default gateway found' - - return gateway[netifaces.AF_INET][1] - - @classmethod - def io_counters(cls): - """Return IO counters""" - return psutil.net_io_counters(pernic=True) - -class TrafficWidget(object): - """Create a traffic widget with humanized bytes string with proper icon (up/down)""" - def __new__(cls, text, direction): - widget = bumblebee.output.Widget(name='{0}.{1}'.format(WIDGET_NAME, direction)) - widget.set('theme.minwidth', '0000000KiB/s') - widget.full_text(cls.humanize(text)) - - return widget - - @staticmethod - def humanize(text): - """Return humanized bytes""" - humanized_byte_format = bumblebee.util.bytefmt(text) - return '{0}/s'.format(humanized_byte_format) diff --git a/bumblebee/modules/nic.py b/bumblebee/modules/nic.py deleted file mode 100644 index 2023985..0000000 --- a/bumblebee/modules/nic.py +++ /dev/null @@ -1,119 +0,0 @@ -#pylint: disable=C0111,R0903 - -"""Displays the name, IP address(es) and status of each available network interface. - -Requires the following python module: - * netifaces - -Parameters: - * nic.exclude: Comma-separated list of interface prefixes to exclude (defaults to "lo,virbr,docker,vboxnet,veth,br") - * nic.include: Comma-separated list of interfaces to include - * nic.states: Comma-separated list of states to show (prefix with "^" to invert - i.e. ^down -> show all devices that are not in state down) - * nic.format: Format string (defaults to "{intf} {state} {ip} {ssid}") -""" - -import netifaces -import subprocess - -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,br").split(","))) - self._include = self.parameter("include", "").split(",") - - self._states = {} - self._states["include"] = [] - self._states["exclude"] = [] - for state in tuple(filter(len, self.parameter("states", "").split(","))): - if state[0] == "^": - self._states["exclude"].append(state[1:]) - else: - self._states["include"].append(state) - self._format = self.parameter("format","{intf} {state} {ip} {ssid}"); - self._update_widgets(widgets) - self.iwgetid = bumblebee.util.which("iwgetid") - - - 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") or intf.startswith("wg") - - 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)] - interfaces.extend([i for i in netifaces.interfaces() if i in self._include]) - - 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" - - if len(self._states["exclude"]) > 0 and state in self._states["exclude"]: continue - if len(self._states["include"]) > 0 and state not in self._states["include"]: continue - - widget = self.widget(intf) - if not widget: - widget = bumblebee.output.Widget(name=intf) - widgets.append(widget) - # join/split is used to get rid of multiple whitespaces (in case SSID is not available, for instance - widget.full_text(" ".join(self._format.format(ip=", ".join(addr),intf=intf,state=state,ssid=self.get_ssid(intf)).split())) - widget.set("intf", intf) - widget.set("state", state) - widget.set("visited", True) - - for widget in widgets: - if widget.get("visited") is False: - widgets.remove(widget) - - def get_ssid(self, intf): - if self._iswlan(intf): - try: - return subprocess.check_output([self.iwgetid,"-r",intf]).strip().decode('utf-8') - except: - return "" - return "" - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/notmuch_count.py b/bumblebee/modules/notmuch_count.py deleted file mode 100644 index 821118a..0000000 --- a/bumblebee/modules/notmuch_count.py +++ /dev/null @@ -1,51 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the result of a notmuch count query - default : unread emails which path do not contained "Trash" (notmuch count "tag:unread AND NOT path:/.*Trash.*/") - -Parameters: - * notmuch_count.query: notmuch count query to show result - -Errors: - if the notmuch query failed, the shown value is -1 - -Dependencies: - notmuch (https://notmuchmail.org/) -""" - -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import os - -class Module(bumblebee.engine.Module): - - - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.output) - ) - self._notmuch_count_query = self.parameter("query", "tag:unread AND NOT path:/.*Trash.*/") - self._notmuch_count = self.count_notmuch() - - - def output(self, widget): - self._notmuch_count = self.count_notmuch() - return str(self._notmuch_count) - - - def state(self, widgets): - if self._notmuch_count == 0: - return "empty" - return "items" - - - def count_notmuch(self): - try: - notmuch_count_cmd = "notmuch count " + self._notmuch_count_query - notmuch_count = int(bumblebee.util.execute(notmuch_count_cmd)) - return notmuch_count - except Exception: - return -1 - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/nvidiagpu.py b/bumblebee/modules/nvidiagpu.py deleted file mode 100644 index 2ce7d38..0000000 --- a/bumblebee/modules/nvidiagpu.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- - -"""Displays GPU name, temperature and memory usage. - -Parameters: - * nvidiagpu.format: Format string (defaults to "{name}: {temp}°C %{usedmem}/{totalmem} MiB") - Available values are: {name} {temp} {mem_used} {mem_total} {fanspeed} {clock_gpu} {clock_mem} - -Requires nvidia-smi -""" - -import subprocess -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 = "Not found: 0 0/0" - - def utilization(self, widget): - return self._utilization - - def update(self, widgets): - sp = subprocess.Popen(['nvidia-smi', '-q'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out_str = sp.communicate() - out_list = out_str[0].decode("utf-8").split('\n') - - title = "" - usedMem = "" - totalMem = "" - temp = "" - name = "not found" - clockMem = "" - clockGpu = "" - fanspeed = "" - for item in out_list: - try: - key, val = item.split(':') - key, val = key.strip(), val.strip() - if title == "Clocks": - if key == "Graphics": - clockGpu = val.split(" ")[0] - elif key == "Memory": - clockMem = val.split(" ")[0] - if title == "FB Memory Usage": - if key == "Total": - totalMem = val.split(" ")[0] - elif key == "Used": - usedMem = val.split(" ")[0] - elif key == "GPU Current Temp": - temp = val.split(" ")[0] - elif key == "Product Name": - name = val - elif key == "Fan Speed": - fanspeed = val.split(" ")[0] - - except: - title = item.strip() - - str_format = self.parameter("format", '{name}: {temp}°C {mem_used}/{mem_total} MiB') - self._utilization = str_format.format( - name = name, - temp = temp, - mem_used = usedMem, - mem_total = totalMem, - clock_gpu = clockGpu, - clock_mem = clockMem, - fanspeed = fanspeed, - ) diff --git a/bumblebee/modules/pacman.py b/bumblebee/modules/pacman.py deleted file mode 100644 index 546c43a..0000000 --- a/bumblebee/modules/pacman.py +++ /dev/null @@ -1,76 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays update information per repository for pacman. - -Parameters: - * pacman.sum: If you prefere displaying updates with a single digit (defaults to "False") - -Requires the following executables: - * fakeroot - * pacman -""" - -import os -import threading - -import bumblebee.util -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -#list of repositories. -#the last one should 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): - if bumblebee.util.asbool(self.parameter("sum")): - return str(sum(map(lambda x: widget.get(x, 0), repos))) - 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/bumblebee/modules/pihole.py b/bumblebee/modules/pihole.py deleted file mode 100644 index 00fbd22..0000000 --- a/bumblebee/modules/pihole.py +++ /dev/null @@ -1,68 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the pi-hole status (up/down) together with the number of ads that were blocked today -Parameters: - * pihole.address : pi-hole address (e.q: http://192.168.1.3) - * pihole.pwhash : pi-hole webinterface password hash (can be obtained from the /etc/pihole/SetupVars.conf file) -""" - -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import requests - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.pihole_status) - ) - - buttons = {"LEFT_CLICK":bumblebee.input.LEFT_MOUSE} - self._pihole_address = self.parameter("address", "") - - self._pihole_pw_hash = self.parameter("pwhash", "") - self._pihole_status = None - self._ads_blocked_today = "-" - self.update_pihole_status() - - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd=self.toggle_pihole_status) - - def pihole_status(self, widget): - if self._pihole_status is None: - return "pi-hole unknown" - return "pi-hole " + ("up/" + self._ads_blocked_today if self._pihole_status else "down") - - def update_pihole_status(self): - try: - data = requests.get(self._pihole_address + "/admin/api.php?summary").json() - self._pihole_status = True if data["status"] == "enabled" else False - self._ads_blocked_today = data["ads_blocked_today"] - except: - self._pihole_status = None - - def toggle_pihole_status(self, widget): - if self._pihole_status is not None: - try: - req = None - if self._pihole_status: - req = requests.get(self._pihole_address + "/admin/api.php?disable&auth=" + self._pihole_pw_hash) - else: - req = requests.get(self._pihole_address + "/admin/api.php?enable&auth=" + self._pihole_pw_hash) - if req is not None: - if req.status_code == 200: - status = req.json()["status"] - self._pihole_status = False if status == "disabled" else True - except: - pass - - - def update(self, widgets): - self.update_pihole_status() - - def state(self, widget): - if self._pihole_status is None: - return [] - elif self._pihole_status: - return ["enabled"] - return ["disabled", "warning"] diff --git a/bumblebee/modules/ping.py b/bumblebee/modules/ping.py deleted file mode 100644 index 0a1419b..0000000 --- a/bumblebee/modules/ping.py +++ /dev/null @@ -1,84 +0,0 @@ -# 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 line.startswith("{} packets transmitted".format(widget.get("rtt-probes"))): - m = re.search(r'(\d+)% packet loss', line) - - widget.set('packet-loss', m.group(1)) - - 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", "") - widget.set('packet-loss', 0) - - 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"), - widget.get('packet-loss') - ) - - 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/bumblebee/modules/pomodoro.py b/bumblebee/modules/pomodoro.py deleted file mode 100644 index 64f1514..0000000 --- a/bumblebee/modules/pomodoro.py +++ /dev/null @@ -1,119 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Display and run a Pomodoro timer. -Left click to start timer, left click again to pause. -Right click will cancel the timer. - -Parameters: - * pomodoro.work: The work duration of timer in minutes (defaults to 25) - * pomodoro.break: The break duration of timer in minutes (defaults to 5) - * pomodoro.format: Timer display format with "%m" and "%s" for minutes and seconds (defaults to "%m:%s") - Examples: "%m min %s sec", "%mm", "", "timer" - * pomodoro.notify: Notification command to run when timer ends/starts (defaults to nothing) - Example: 'notify-send "Time up!"' -""" - -from __future__ import absolute_import -import datetime -from math import ceil - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widgets = bumblebee.output.Widget(full_text=self.text) - - super(Module, self).__init__(engine, config, widgets) - - # Parameters - self._work_period = int(self.parameter("work", 25)) - self._break_period = int(self.parameter("break", 5)) - self._time_format = self.parameter("format", "%m:%s") - self._notify_cmd = self.parameter("notify", "") - - # TODO: Handle time formats more gracefully. This is kludge. - self.display_seconds_p = False - self.display_minutes_p = False - if "%s" in self._time_format: - self.display_seconds_p = True - if "%m" in self._time_format: - self.display_minutes_p = True - - self.remaining_time = datetime.timedelta(minutes=self._work_period) - - self.time = None - self.pomodoro = { "state":"OFF", "type": ""} - self._text = self.remaining_time_str() + self.pomodoro["type"] - - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd=self.timer_play_pause) - engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, - cmd=self.timer_reset) - - def remaining_time_str(self): - - if self.display_seconds_p and self.display_minutes_p: - minutes, seconds = divmod(self.remaining_time.seconds, 60) - if not self.display_seconds_p: - minutes = ceil(self.remaining_time.seconds / 60) - seconds = 0 - if not self.display_minutes_p: - minutes = 0 - seconds = self.remaining_time.seconds - - minutes = "{:2d}".format(minutes) - seconds = "{:02d}".format(seconds) - return self._time_format.replace("%m",minutes).replace("%s",seconds)+" " - - def text(self, widget): - return "{}".format(self._text) - - def update(self, widget): - if self.pomodoro["state"] == "ON": - timediff = (datetime.datetime.now() - self.time) - if timediff.seconds >= 0: - self.remaining_time -= timediff - self.time = datetime.datetime.now() - - if self.remaining_time.seconds <= 0: - self.notify() - if self.pomodoro["type"] == "Work": - self.pomodoro["type"] = "Break" - self.remaining_time = datetime.timedelta(minutes=self._break_period) - elif self.pomodoro["type"] == "Break": - self.pomodoro["type"] = "Work" - self.remaining_time = datetime.timedelta(minutes=self._work_period) - - self._text = self.remaining_time_str() + self.pomodoro["type"] - - def notify(self): - if self._notify_cmd: - bumblebee.util.execute(self._notify_cmd) - - def timer_play_pause(self, widget): - if self.pomodoro["state"] == "OFF": - self.pomodoro = {"state": "ON", "type": "Work"} - self.remaining_time = datetime.timedelta(minutes=self._work_period) - self.time = datetime.datetime.now() - elif self.pomodoro["state"] == "ON": - self.pomodoro["state"] = "PAUSED" - self.remaining_time -= (datetime.datetime.now() - self.time) - self.time = datetime.datetime.now() - elif self.pomodoro["state"] == "PAUSED": - self.pomodoro["state"] = "ON" - self.time = datetime.datetime.now() - - def timer_reset(self, widget): - if self.pomodoro["state"] == "ON" or self.pomodoro["state"] == "PAUSED": - self.pomodoro = {"state":"OFF", "type": "" } - self.remaining_time = datetime.timedelta(minutes=self._work_period) - - def state(self, widget): - state = []; - state.append(self.pomodoro["state"].lower()) - if self.pomodoro["state"] == "ON" or self.pomodoro["state"] == "OFF": - state.append(self.pomodoro["type"].lower()) - - return state diff --git a/bumblebee/modules/prime.py b/bumblebee/modules/prime.py deleted file mode 100644 index 542ae20..0000000 --- a/bumblebee/modules/prime.py +++ /dev/null @@ -1,69 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays and changes the current selected prime video card - -Left click will call 'sudo prime-select nvidia' -Right click will call 'sudo prime-select nvidia' - -Running these commands without a password requires editing your sudoers file -(always use visudo, it's very easy to make a mistake and get locked out of your computer!) - -sudo visudo -f /etc/sudoers.d/prime - -Then put a line like this in there: - - user ALL=(ALL) NOPASSWD: /usr/bin/prime-select - -If you can't figure out the sudoers thing, then don't worry, it's still really useful. - -Parameters: - * prime.nvidiastring: String to use when nvidia is selected (defaults to "intel") - * prime.intelstring: String to use when intel is selected (defaults to "intel") - -Requires the following executable: - * prime-select - -""" - -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.query) - ) - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd=self._chooseNvidia) - engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, - cmd=self._chooseIntel) - - self.nvidiastring = self.parameter("nvidiastring", "nv") - self.intelstring = self.parameter("intelstring", "it") - - def _chooseNvidia(self, event): - bumblebee.util.execute("sudo prime-select nvidia") - - def _chooseIntel(self, event): - bumblebee.util.execute("sudo prime-select intel") - - def _prev_keymap(self, event): - self._set_keymap(-1) - - def query(self, widget): - try: - res = bumblebee.util.execute("prime-select query") - except RuntimeError: - return "n/a" - - for line in res.split("\n"): - if not line: continue - if "nvidia" in line: - return self.nvidiastring - if "intel" in line: - return self.intelstring - return "n/a" - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/progress.py b/bumblebee/modules/progress.py deleted file mode 100644 index 79396ca..0000000 --- a/bumblebee/modules/progress.py +++ /dev/null @@ -1,103 +0,0 @@ -""" -Show progress for cp, mv, dd, ... - -Parameters: - * progress.placeholder: Text to display while no process is running (defaults to "n/a") - * progress.barwidth: Width of the progressbar if it is used (defaults to 8) - * progress.format: Format string (defaults to "{bar} {cmd} {arg}") - Available values are: {bar} {pid} {cmd} {arg} {percentage} {quantity} {speed} {time} - * progress.barfilledchar: Character used to draw the filled part of the bar (defaults to "#"), notice that it can be a string - * progress.baremptychar: Character used to draw the empty part of the bar (defaults to "-"), notice that it can be a string - -Requires the following executable: - * progress -""" - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -import re - - -class Module(bumblebee.engine.Module): - - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.get_progress_text) - ) - - def get_progress_text(self, widget): - if self.update_progress_info(widget): - width = self.parameter("barwidth", 8) - count = round((width * widget.get("per")) / 100) - filledchar = self.parameter("barfilledchar", "#") - emptychar = self.parameter("baremptychar", "-") - - bar = "[{}{}]".format( - filledchar * count, - emptychar * (width - count) - ) - - str_format = self.parameter("format", '{bar} {cmd} {arg}') - return str_format.format( - bar = bar, - pid = widget.get("pid"), - cmd = widget.get("cmd"), - arg = widget.get("arg"), - percentage = widget.get("per"), - quantity = widget.get("qty"), - speed = widget.get("spd"), - time = widget.get("tim") - ) - else: - return self.parameter("placeholder", 'n/a') - - def update_progress_info(self, widget): - """Update widget's informations about the copy""" - - # These regex extracts following groups: - # 1. pid - # 2. command - # 3. arguments - # 4. progress (xx.x formated) - # 5. quantity (.. unit / .. unit formated) - # 6. speed - # 7. time remaining - extract_nospeed = re.compile("\[ *(\d*)\] ([a-zA-Z]*) (.*)\n\t(\d*\.*\d*)% \((.*)\)\n.*") - extract_wtspeed = re.compile('\[ *(\d*)\] ([a-zA-Z]*) (.*)\n\t(\d*\.*\d*)% \((.*)\) (\d*\.\d .*) remaining (\d*:\d*:\d*)\n.*') - - try: - raw = bumblebee.util.execute("progress -qW 0.1") - result = extract_wtspeed.match(raw) - - if not result: - # Abord speed measures - raw = bumblebee.util.execute("progress -q") - result = extract_nospeed.match(raw) - - widget.set("spd", "???.? B/s") - widget.set("tim", "??:??:??") - else: - widget.set("spd", result.group(6)) - widget.set("tim", result.group(7)) - - widget.set("pid", int(result.group(1))) - widget.set("cmd", result.group(2)) - widget.set("arg", result.group(3)) - widget.set("per", float(result.group(4))) - widget.set("qty", result.group(5)) - return True - except Exception: - return False - - def state(self, widget): - if self._active(): - return "copying" - return "pending" - - def _active(self): - """Checks wether a copy is running or not""" - raw = bumblebee.util.execute("progress -q") - return bool(raw) - diff --git a/bumblebee/modules/publicip.py b/bumblebee/modules/publicip.py deleted file mode 100644 index 73c19d1..0000000 --- a/bumblebee/modules/publicip.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Displays public IP address - -Requires the following python packages: - * requests - -Parameters: - * publicip.region: us-central (default), us-east, us-west, uk, de, pl, nl - * publicip.service: web address that returns plaintext ip address (ex. "http://l2.io/ip") -""" - -try: - from requests import get -except ImportError: - pass - -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.public_ip) - ) - self._avail_regions = {"us-east":"http://checkip.amazonaws.com", - "us-central":"http://whatismyip.akamai.com", - "us-west":"http://ipv4bot.whatismyipaddress.com", - "pl":"http://ip.42.pl/raw", - "de":"http://myexternalip.com/raw", - "nl":"http://tnx.nl/ip", - "uk":"http://ident.me"} - self._region = self.parameter("region", "us-central") - self._service = self.parameter("service", "") - self._ip = "" - - - def public_ip(self, widget): - return self._ip - - def update(self, widgets): - try: - if self._service: - self.address = self._service - else: - self.address = self._avail_regions[self._region] - self._ip = get(self.address).text.rstrip() - except Exception: - self._ip = "No Connection" diff --git a/bumblebee/modules/pulseaudio.py b/bumblebee/modules/pulseaudio.py deleted file mode 100644 index a30b9d5..0000000 --- a/bumblebee/modules/pulseaudio.py +++ /dev/null @@ -1,183 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol. - -Aliases: pasink (use this to control output instead of input), pasource - -Parameters: - * pulseaudio.autostart: If set to "true" (default is "false"), automatically starts the pulseaudio daemon if it is not running - * pulseaudio.percent_change: How much to change volume by when scrolling on the module (default is 2%) - * pulseaudio.limit: Upper limit for setting the volume (default is 0%, which means "no limit") - Note: If the left and right channels have different volumes, the limit might not be reached exactly. - * pulseaudio.showbars: 1 for showing volume bars, requires --markup=pango; - 0 for not showing volume bars (default) - -Requires the following executable: - * pulseaudio - * pactl - * pavucontrol -""" - -import re - -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.volume) - ) - try: - if bumblebee.util.asbool(self.parameter("autostart", False)): - bumblebee.util.execute("pulseaudio --start") - except Exception: - pass - - self._change = 2 - self._change = int(self.parameter("percent_change", "2%").strip("%")) - if self._change < 0 or self._change > 100: - self._change = 2 - - self._limit = 0 - self._limit = int(self.parameter("limit", "0%").strip("%s")) - if self._limit < 0: - self._limit = 0 - - self._left = 0 - self._right = 0 - self._mono = 0 - self._mute = False - self._failed = False - self._channel = "sink" if self.name == "pasink" else "source" - self._showbars = bumblebee.util.asbool(self.parameter("showbars", 0)) - - 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": self.toggle, "button": bumblebee.input.LEFT_MOUSE}, - {"type": "volume", "action": self.increase_volume, "button": bumblebee.input.WHEEL_UP}, - {"type": "volume", "action": self.decrease_volume, "button": bumblebee.input.WHEEL_DOWN}, - ] - - for event in events: - engine.input.register_callback(self, button=event["button"], cmd=event["action"]) - - def set_volume(self, amount): - bumblebee.util.execute("pactl set-{}-{} @DEFAULT_{}@ {}".format( - self._channel, "volume", self._channel.upper(), amount)) - - def increase_volume(self, event): - if self._limit > 0: # we need to check the limit - left = int(self._left) - right = int(self._right) - if left + self._change >= self._limit or right + self._change >= self._limit: - if left == right: - # easy case, just set to limit - self.set_volume("{}%".format(self._limit)) - return - else: - # don't adjust anymore, since i don't know how to update only one channel - return - - self.set_volume("+{}%".format(self._change)) - - def decrease_volume(self, event): - self.set_volume("-{}%".format(self._change)) - - def toggle(self, event): - bumblebee.util.execute("pactl set-{}-{} @DEFAULT_{}@ {}".format( - self._channel, "mute", self._channel.upper(), "toggle")) - - 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 {}: ".format("Sink" if self.name == "pasink" else "Source") - for line in output.split("\n"): - if line.startswith(pattern): - return line.replace(pattern, "") - return "n/a" - - def volume(self, widget): - if self._failed == True: - return "n/a" - if int(self._mono) > 0: - vol = "{}%".format(self._mono) - if self._showbars: - vol = "{} {}".format( - vol, bumblebee.output.hbar(float(self._mono))) - return vol - elif self._left == self._right: - vol = "{}%".format(self._left) - if self._showbars: - vol = "{} {}".format( - vol, bumblebee.output.hbar(float(self._left))) - return vol - else: - if self._showbars: - vol = "{} {}{}".format( - vol, - bumblebee.output.hbar(float(self._left)), - bumblebee.output.hbar(float(self._right))) - vol = "{}%/{}%".format(self._left, self._right) - return vol - - def update(self, widgets): - try: - self._failed = False - 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 "Name: {}".format(device) in line: - found = True - continue - if found is False: - continue - for pattern in self._patterns: - if not pattern["expr"] in line: - continue - if pattern["callback"](line) is False and found == True: - return - except Exception: - self._failed = True - if bumblebee.util.asbool(self.parameter("autostart", False)): - try: - bumblebee.util.execute("pulseaudio --start") - self.update(widgets) - except Exception: - pass - - def state(self, widget): - if self._mute: - return ["warning", "muted"] - if int(self._left) > int(100): - return ["critical", "unmuted"] - return ["unmuted"] - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/redshift.py b/bumblebee/modules/redshift.py deleted file mode 100644 index f6f16c7..0000000 --- a/bumblebee/modules/redshift.py +++ /dev/null @@ -1,129 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the current color temperature of redshift - -Requires the following executable: - * redshift - -Parameters: - * redshift.location : location provider, either of "geoclue2" (default), \ -"ipinfo" (requires the requests package), or "manual" - * redshift.lat : latitude if location is set to "manual" - * redshift.lon : longitude if location is set to "manual" -""" - -import threading -try: - import requests -except ImportError: - pass - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - - -def is_terminated(): - for thread in threading.enumerate(): - if thread.name == "MainThread" and not thread.is_alive(): - return True - return False - - -def get_redshift_value(widget, location, lat, lon): - while True: - if is_terminated(): - return - widget.get("condition").acquire() - while True: - try: - widget.get("condition").wait(1) - except RuntimeError: - continue - break - widget.get("condition").release() - - command = ["redshift", "-p", "-l"] - if location == "manual": - command.append(lat + ":" + lon) - else: - command.append("geoclue2") - - try: - res = bumblebee.util.execute(" ".join(command)) - except Exception: - res = "" - widget.set("temp", "n/a") - widget.set("transition", None) - widget.set("state", "day") - for line in res.split("\n"): - line = line.lower() - if "temperature" in line: - widget.set("temp", line.split(" ")[2]) - if "period" in line: - state = line.split(" ")[1] - if "day" in state: - widget.set("state", "day") - elif "night" in state: - widget.set("state", "night") - else: - widget.set("state", "transition") - widget.set("transition", " ".join(line.split(" ")[2:])) - - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widget = bumblebee.output.Widget(full_text=self.text) - super(Module, self).__init__(engine, config, widget) - - self._location = self.parameter("location", "geoclue2") - self._lat = self.parameter("lat", None) - self._lon = self.parameter("lon", None) - - # Even if location method is set to manual, if we have no lat or lon, - # fall back to the geoclue2 method. - if self._location == "manual" and (self._lat is None - or self._lon is None): - self._location == "geoclue2" - - if self._location == "ipinfo": - try: - location_url = "http://ipinfo.io/json" - location = requests.get(location_url).json() - self._lat, self._lon = location["loc"].split(",") - self._lat = str(float(self._lat)) - self._lon = str(float(self._lon)) - self._location = "manual" - except Exception: - # Fall back to geoclue2. - self._location = "geoclue2" - - self._text = "" - self._condition = threading.Condition() - widget.set("condition", self._condition) - self._thread = threading.Thread(target=get_redshift_value, - args=(widget, self._location, - self._lat, self._lon)) - self._thread.start() - self._condition.acquire() - self._condition.notify() - self._condition.release() - - def text(self, widget): - return "{}".format(self._text) - - def update(self, widgets): - widget = widgets[0] - self._condition.acquire() - self._condition.notify() - self._condition.release() - temp = widget.get("temp", "n/a") - self._text = temp - transition = widget.get("transition", None) - if transition: - self._text = "{} {}".format(temp, transition) - - def state(self, widget): - return widget.get("state", None) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/rotation.py b/bumblebee/modules/rotation.py deleted file mode 100644 index 23552d0..0000000 --- a/bumblebee/modules/rotation.py +++ /dev/null @@ -1,61 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Shows a widget for each connected screen and allows the user to loop through different orientations. - -Requires the following executable: - * xrandr -""" - -import bumblebee.util -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -possible_orientations = ["normal", "left", "inverted", "right"] - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widgets = [] - self._engine = engine - super(Module, self).__init__(engine, config, widgets) - self.update_widgets(widgets) - - def update_widgets(self, widgets): - for line in bumblebee.util.execute("xrandr -q").split("\n"): - if not " connected" in line: - continue - display = line.split(" ", 2)[0] - - orientation = "normal" - for curr_orient in possible_orientations: - if((line.split(" ")).count(curr_orient) > 1): - orientation = curr_orient - break - - widget = self.widget(display) - if not widget: - widget = bumblebee.output.Widget(full_text=display, name=display) - self._engine.input.register_callback(widget, button=bumblebee.input.LEFT_MOUSE, cmd=self._toggle) - widget.set("orientation", orientation) - widgets.append(widget) - - def update(self, widgets): - if len(widgets) <= 0: - self.update_widgets(widgets) - - def state(self, widget): - return widget.get("orientation", "normal") - - def _toggle(self, event): - widget = self.widget_by_id(event["instance"]) - - # compute new orientation based on current orientation - idx = possible_orientations.index(widget.get("orientation")) - idx = (idx + 1) % len(possible_orientations) - new_orientation = possible_orientations[idx] - - widget.set("orientation", new_orientation) - - bumblebee.util.execute("xrandr --output {} --rotation {}".format(widget.name, new_orientation)) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/rss.py b/bumblebee/modules/rss.py deleted file mode 100644 index 2a27d27..0000000 --- a/bumblebee/modules/rss.py +++ /dev/null @@ -1,312 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""RSS news ticker - -Fetches rss news items and shows these as a news ticker. -Left-clicking will open the full story in a browser. -New stories are highlighted. - -Parameters: - * rss.feeds : Space-separated list of RSS URLs - * rss.length : Maximum length of the module, default is 60 -""" - -try: - import feedparser - DEPENDENCIES_OK = True -except ImportError: - DEPENDENCIES_OK = False - -import webbrowser -import time -import os -import tempfile -import logging -import random -import re -import json - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - - -# pylint: disable=too-many-instance-attributes -class Module(bumblebee.engine.Module): - REFRESH_DELAY = 600 - SCROLL_SPEED = 3 - LAYOUT_STYLES_ITEMS = [[1,1,1],[3,3,2],[2,3,3],[3,2,3]] - HISTORY_FILENAME = ".config/i3/rss.hist" - - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.ticker_update if DEPENDENCIES_OK else self._show_error) - ) - # Use BBC newsfeed as demo: - self._feeds = self.parameter('feeds', 'https://www.espn.com/espn/rss/news').split(" ") - self._feeds_to_update = [] - self._response = "" - - self._max_title_length = int(self.parameter("length", 60)) - - self._items = [] - self._current_item = None - - self._ticker_offset = 0 - self._pre_delay = 0 - self._post_delay = 0 - - self._state = [] - - self._newspaper_filename = tempfile.mktemp('.html') - - self._last_refresh = 0 - self._last_update = 0 - - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self._open) - engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self._create_newspaper) - - self._history = {'ticker': {}, 'newspaper': {}} - self._load_history() - - def _load_history(self): - if os.path.isfile(self.HISTORY_FILENAME): - self._history = json.loads(open(self.HISTORY_FILENAME, "r").read()) - - def _update_history(self, group): - sources = set([i['source'] for i in self._items]) - self._history[group] = dict([[s, [i['title'] for i in self._items if i['source'] == s]] for s in sources]) - - def _save_history(self): - if not os.path.exists(os.path.dirname(self.HISTORY_FILENAME)): - os.makedirs(os.path.dirname(self.HISTORY_FILENAME)) - open(self.HISTORY_FILENAME, "w").write(json.dumps(self._history)) - - def _check_history(self, items, group): - for i in items: - i['new'] = not (i['source'] in self._history[group] and i['title'] in self._history[group][i['source']]) - - def _open(self, _): - if self._current_item: - webbrowser.open(self._current_item['link']) - - def _check_for_image(self, entry): - image = next(iter([l['href'] for l in entry['links'] if l['rel'] == 'enclosure']), None) - if not image and 'media_content' in entry: - try: - media = sorted(entry['media_content'], key=lambda i: i['height'] if 'height' in i else 0, reverse=True) - image = next(iter([i['url'] for i in media if i['medium'] == 'image']), None) - except Exception: - pass - if not image: - match = re.search(r']*src\s*=["\']*([^\s^>^"^\']*)["\']*', entry['summary']) - if match: - image = match.group(1) - return image if image else '' - - def _remove_tags(self, txt): - return re.sub('<[^>]*>', '', txt) - - def _create_item(self, entry, url, feed): - return {'title': self._remove_tags(entry['title'].replace('\n', ' ')), - 'link': entry['link'], - 'new': True, - 'source': url, - 'summary': self._remove_tags(entry['summary']), - 'feed': feed, - 'image': self._check_for_image(entry), - 'published': time.mktime(entry.published_parsed) if hasattr(entry, 'published_parsed') else 0} - - def _update_items_from_feed(self, url): - parser = feedparser.parse(url) - new_items = [self._create_item(entry, url, parser['feed']['title']) for entry in parser['entries']] - # Check history - self._check_history(new_items, 'ticker') - # Remove the previous items - self._items = [i for i in self._items if i['source'] != url] - # Add the new items - self._items.extend(new_items) - # Sort the items on publish date - self._items.sort(key=lambda i: i['published'], reverse=True) - - def _check_for_refresh(self): - if self._feeds_to_update: - # Update one feed at a time to not overload this update cycle - url = self._feeds_to_update.pop() - self._update_items_from_feed(url) - - if not self._feeds_to_update: - self._update_history('ticker') - self._save_history() - - if not self._current_item: - self._next_item() - elif time.time()-self._last_refresh >= self.REFRESH_DELAY: - # Populate the list with feeds to update - self._feeds_to_update = self._feeds[:] - # Update the refresh time - self._last_refresh = time.time() - - def _next_item(self): - self._ticker_offset = 0 - self._pre_delay = 2 - self._post_delay = 4 - - if not self._items: - return - - # Index of the current element - idx = self._items.index(self._current_item) if self._current_item in self._items else - 1 - - # First show new items, else show next - new_items = [i for i in self._items if i['new']] - self._current_item = next(iter(new_items), self._items[(idx+1) % len(self._items)]) - - def _check_scroll_done(self): - # Check if the complete title has been shown - if self._ticker_offset + self._max_title_length > len(self._current_item['title']): - # Do not immediately show next item after scroll - self._post_delay -= 1 - if self._post_delay == 0: - self._current_item['new'] = False - # Mark the previous item as 'old' - self._next_item() - else: - # Increase scroll position - self._ticker_offset += self.SCROLL_SPEED - - def _show_error(self, _): - return "Please install feedparser first" - - def ticker_update(self, _): - # Only update the ticker once a second - now = time.time() - if now-self._last_update < 1: - return self._response - - self._last_update = now - - self._check_for_refresh() - - # If no items were retrieved, return an empty string - if not self._current_item: - return " "*self._max_title_length - - # Prepare a substring of the item title - self._response = self._current_item['title'][self._ticker_offset:self._ticker_offset+self._max_title_length] - # Add spaces if too short - self._response = self._response.ljust(self._max_title_length) - - # Do not immediately scroll - if self._pre_delay > 0: - # Change state during pre_delay for new items - if self._current_item['new']: - self._state = ['warning'] - self._pre_delay -= 1 - return self._response - - self._state = [] - self._check_scroll_done() - - return self._response - - def update(self, widgets): - pass - - def state(self, _): - return self._state - - def _create_news_element(self, item, overlay_title): - try: - timestr = "" if item['published'] == 0 else str(time.ctime(item['published'])) - except Exception as exc: - logging.error(str(exc)) - raise e - element = "
" - element += "
" - element += " " - element += "
"+("" if item['new'] else "")+item['title']+"
" - element += "
" - element += "
"+item['summary']+"
" - element += "
"+item['feed']+""+timestr+"
" - element += "
" - return element - - def _create_news_section(self, newspaper_items): - style = random.randint(0, 3) - section = "" - for i in range(0, 3): - section += "" - section += "
" - for _ in range(0, self.LAYOUT_STYLES_ITEMS[style][i]): - if newspaper_items: - section += self._create_news_element(newspaper_items[0], self.LAYOUT_STYLES_ITEMS[style][i] != 3) - del newspaper_items[0] - section += "
" - return section - - def _create_newspaper(self, _): - content = "" - newspaper_items = self._items[:] - self._check_history(newspaper_items, 'newspaper') - - # Make sure new items are always listed first, independent of publish date - newspaper_items.sort(key=lambda i: i['published']+(10000000 if i['new'] else 0), reverse=True) - - while newspaper_items: - content += self._create_news_section(newspaper_items) - open(self._newspaper_filename, "w").write(HTML_TEMPLATE.replace("[[CONTENT]]", content)) - webbrowser.open("file://"+self._newspaper_filename) - self._update_history('newspaper') - self._save_history() - -HTML_TEMPLATE = """ - - - - - - -
-
Bumblebee Daily
- [[CONTENT]] -
- -""" -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/sensors.py b/bumblebee/modules/sensors.py deleted file mode 100644 index 8ad86a3..0000000 --- a/bumblebee/modules/sensors.py +++ /dev/null @@ -1,128 +0,0 @@ -# -*- coding: UTF-8 -*- -# pylint: disable=C0111,R0903 - -"""Displays sensor temperature - -Parameters: - * sensors.path: path to temperature file (default /sys/class/thermal/thermal_zone0/temp). - * sensors.json: if set to "true", interpret sensors.path as JSON "path" in the output - of "sensors -j" (i.e. //.../), for example, path could - be: "coretemp-isa-00000/Core 0/temp1_input" (defaults to "false") - * sensors.match: (fallback) Line to match against output of 'sensors -u' (default: temp1_input) - * sensors.match_pattern: (fallback) Line to match against before temperature is read (no default) - * sensors.match_number: (fallback) which of the matches you want (default -1: last match). - * sensors.show_freq: whether to show CPU frequency. (default: true) -""" - -import re -import json -import logging - -import bumblebee.util -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -log = logging.getLogger(__name__) - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.temperature)) - self._temperature = "unknown" - self._mhz = "n/a" - self._match_number = int(self.parameter("match_number", "-1")) - self._match_pattern = self.parameter("match_pattern", None) - self._pattern = re.compile(r"^\s*{}:\s*([\d.]+)$".format(self.parameter("match", "temp1_input")), re.MULTILINE) - self._json = bumblebee.util.asbool(self.parameter("json", "false")) - self._freq = bumblebee.util.asbool(self.parameter("show_freq", "true")) - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd="xsensors") - self.determine_method() - - def determine_method(self): - if self.parameter("path") != None and self._json == False: - self.use_sensors = False # use thermal zone - else: - # try to use output of sensors -u - try: - output = bumblebee.util.execute("sensors -u") - self.use_sensors = True - log.debug("Sensors command available") - except FileNotFoundError as e: - log.info("Sensors command not available, using /sys/class/thermal/thermal_zone*/") - self.use_sensors = False - - def _get_temp_from_sensors(self): - if self._json == True: - try: - output = json.loads(bumblebee.util.execute("sensors -j")) - for key in self.parameter("path").split("/"): - output = output[key] - return int(float(output)) - except Exception as e: - logging.error("unable to read sensors: {}".format(str(e))) - return "unknown" - else: - output = bumblebee.util.execute("sensors -u") - if self._match_pattern: - temp_pattern = self.parameter("match", "temp1_input") - match = re.search(r"{}.+{}:\s*([\d.]+)$".format(self._match_pattern, temp_pattern), output.replace("\n", "")) - if match: - return int(float(match.group(1))) - else: - return "unknown" - match = self._pattern.findall(output) - if match: - return int(float(match[self._match_number])) - return "unknown" - - def get_temp(self): - if self.use_sensors: - temperature = self._get_temp_from_sensors() - log.debug("Retrieve temperature from sensors -u") - else: - try: - temperature = open(self.parameter("path", "/sys/class/thermal/thermal_zone0/temp")).read()[:2] - log.debug("retrieved temperature from /sys/class/") - # TODO: Iterate through all thermal zones to determine the correct one and use its value - # https://unix.stackexchange.com/questions/304845/discrepancy-between-number-of-cores-and-thermal-zones-in-sys-class-thermal - - except IOError: - temperature = "unknown" - log.info("Can not determine temperature, please install lm-sensors") - - return temperature - - def get_mhz(self): - mhz = None - try: - output = open("/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq").read() - mhz = int(float(output)/1000.0) - except: - output = open("/proc/cpuinfo").read() - m = re.search(r"cpu MHz\s+:\s+(\d+)", output) - if m: - mhz = int(m.group(1)) - else: - m = re.search(r"BogoMIPS\s+:\s+(\d+)", output) - if m: - return "{} BogoMIPS".format(int(m.group(1))) - if not mhz: - return "n/a" - - if mhz < 1000: - return "{} MHz".format(mhz) - else: - return "{:0.01f} GHz".format(float(mhz)/1000.0) - - def temperature(self, _): - if self._freq: - return u"{}°c @ {}".format(self._temperature, self._mhz) - else: - return u"{}°c".format(self._temperature) - def update(self, widgets): - self._temperature = self.get_temp() - if self._freq: - self._mhz = self.get_mhz() - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/sensors2.py b/bumblebee/modules/sensors2.py deleted file mode 100644 index 3bd245a..0000000 --- a/bumblebee/modules/sensors2.py +++ /dev/null @@ -1,156 +0,0 @@ -# -*- coding: UTF-8 -*- - -"""Displays sensor temperature and CPU frequency - -Parameters: - - * sensors2.chip: "sensors -u" compatible filter for chip to display (default to empty - show all chips) - * sensors2.showcpu: Enable or disable CPU frequency display (default: true) - * sensors2.showtemp: Enable or disable temperature display (default: true) - * sensors2.showfan: Enable or disable fan display (default: true) - * sensors2.showother: Enable or display "other" sensor readings (default: false) - * sensors2.showname: Enable or disable show of sensor name (default: false) -""" - -import re - -import bumblebee.util -import bumblebee.output -import bumblebee.engine - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, None) - self._chip = self.parameter("chip", "") - self._data = {} - self._update() - - self.widgets(self._create_widgets()) - - def update(self, widgets): - self._update() - for widget in widgets: - self._update_widget(widget) - - def state(self, widget): - widget_type = widget.get("type", "") - try: - data = self._data[widget.get("adapter")][widget.get("package")][widget.get("field")] - if "crit" in data and float(data["input"]) > float(data["crit"]): - return ["critical", widget_type] - if "max" in data and float(data["input"]) > float(data["max"]): - return ["warning", widget_type] - except: - pass - return [widget_type] - - def _create_widgets(self): - widgets = [] - show_temp = bumblebee.util.asbool(self.parameter("showtemp", "true")) - show_fan = bumblebee.util.asbool(self.parameter("showfan", "true")) - show_other = bumblebee.util.asbool(self.parameter("showother", "false")) - - if bumblebee.util.asbool(self.parameter("showcpu", "true")): - widget = bumblebee.output.Widget(full_text=self._cpu) - widget.set("type", "cpu") - widgets.append(widget) - - for adapter in self._data: - for package in self._data[adapter]: - if bumblebee.util.asbool(self.parameter("showname", "false")): - widget = bumblebee.output.Widget(full_text=package) - widget.set("data", self._data[adapter][package]) - widget.set("package", package) - widget.set("field", "") - widget.set("adapter", adapter) - widgets.append(widget) - for field in self._data[adapter][package]: - widget = bumblebee.output.Widget() - widget.set("package", package) - widget.set("field", field) - widget.set("adapter", adapter) - if "temp" in field and show_temp: - # seems to be a temperature - widget.set("type", "temp") - widgets.append(widget) - if "fan" in field and show_fan: - # seems to be a fan - widget.set("type", "fan") - widgets.append(widget) - elif show_other: - # everything else - widget.set("type", "other") - widgets.append(widget) - return widgets - - def _update_widget(self, widget): - if widget.get("field", "") == "": - return # nothing to do - data = self._data[widget.get("adapter")][widget.get("package")][widget.get("field")] - if "temp" in widget.get("field"): - widget.full_text(u"{:0.01f}°C".format(data["input"])) - elif "fan" in widget.get("field"): - widget.full_text(u"{:0.0f}RPM".format(data["input"])) - else: - widget.full_text(u"{:0.0f}".format(data["input"])) - - def _update(self): - output = bumblebee.util.execute("sensors -u {}".format(self._chip)) - self._data = self._parse(output) - - def _parse(self, data): - output = {} - package = "" - adapter = None - chip = None - for line in data.split("\n"): - if "Adapter" in line: - # new adapter - line = line.replace("Adapter: ", "") - output[chip + " " + line] = {} - adapter = chip + " " + line - chip = line #default - line before adapter is always the chip - if not adapter: continue - key, value = (line.split(":") + ["", ""])[:2] - if not line.startswith(" "): - # assume this starts a new package - if package in output[adapter] and output[adapter][package] == {}: - del output[adapter][package] - output[adapter][key] = {} - package = key - else: - # feature for this chip - try: - name, variant = (key.strip().split("_", 1) + ["",""])[:2] - if not name in output[adapter][package]: - output[adapter][package][name] = { } - if variant: - output[adapter][package][name][variant] = {} - output[adapter][package][name][variant] = float(value) - except Exception as e: - pass - return output - - def _cpu(self, _): - mhz = None - try: - output = open("/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq").read() - mhz = int(float(output)/1000.0) - except: - output = open("/proc/cpuinfo").read() - m = re.search(r"cpu MHz\s+:\s+(\d+)", output) - if m: - mhz = int(m.group(1)) - else: - m = re.search(r"BogoMIPS\s+:\s+(\d+)", output) - if m: - return "{} BogoMIPS".format(int(m.group(1))) - if not mhz: - return "n/a" - - if mhz < 1000: - return "{} MHz".format(mhz) - else: - return "{:0.01f} GHz".format(float(mhz)/1000.0) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/shell.py b/bumblebee/modules/shell.py deleted file mode 100644 index 038e519..0000000 --- a/bumblebee/modules/shell.py +++ /dev/null @@ -1,93 +0,0 @@ -# pylint: disable=C0111,R0903,W1401 - -""" Execute command in shell and print result - -Few command examples: - 'ping 1.1.1.1 -c 1 | grep -Po "(?<=time=)\d+(\.\d+)? ms"' - 'echo "BTC=$(curl -s rate.sx/1BTC | grep -Po \"^\d+\")USD"' - 'curl -s https://wttr.in/London?format=%l+%t+%h+%w' - 'pip3 freeze | wc -l' - 'any_custom_script.sh | grep arguments' - -Parameters: - * shell.command: Command to execute - Use single parentheses if evaluating anything inside (sh-style) - For example shell.command='echo $(date +"%H:%M:%S")' - But NOT shell.command="echo $(date +'%H:%M:%S')" - Second one will be evaluated only once at startup - * shell.interval: Update interval in seconds - (defaults to 1s == every bumblebee-status update) - * shell.async: Run update in async mode. Won't run next thread if - previous one didn't finished yet. Useful for long - running scripts to avoid bumblebee-status freezes - (defaults to False) -""" - -import os -import subprocess -import threading - -import bumblebee.engine -import bumblebee.input -import bumblebee.output - - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widget = bumblebee.output.Widget(full_text=self.get_output) - super(Module, self).__init__(engine, config, widget) - - if self.parameter('interval'): - self.interval(self.parameter('interval')) - - self._command = self.parameter('command') - self._async = bumblebee.util.asbool(self.parameter('async')) - if self._async: - self._output = 'Computing...' - self._current_thread = None - else: - self._output = '' - - # LMB and RMB will update output regardless of timer - engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self.update) - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self.update) - - def get_output(self, _): - return self._output - - def update(self, _): - # if requested then run not async version and just execute command in this thread - if not self._async: - self._output = self._get_command_output_or_error(self._command) - return - - # if previous thread didn't end yet then don't do anything - if self._current_thread: - return - - # spawn new thread to execute command and pass callback method to get output from it - self._current_thread = threading.Thread(target=self._run_command_in_thread, - args=(self._command, self._output_function)) - self._current_thread.start() - - @staticmethod - def _get_command_output_or_error(command): - try: - command_output = subprocess.check_output(command, - executable=os.environ.get('SHELL'), - shell=True, - stderr=subprocess.STDOUT) - return command_output.decode('utf-8').strip() - except subprocess.CalledProcessError as exception: - exception_output = exception.output.decode('utf-8').replace('\n', '') - return 'Status:{} output:{}'.format(exception.returncode, exception_output) - - def _run_command_in_thread(self, command, output_callback): - output_callback(self._get_command_output_or_error(command)) - - def _output_function(self, text): - self._output = text - # clear this thread data, so next update will spawn a new one - self._current_thread = None - -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/shortcut.py b/bumblebee/modules/shortcut.py deleted file mode 100644 index 5c25cfb..0000000 --- a/bumblebee/modules/shortcut.py +++ /dev/null @@ -1,70 +0,0 @@ -# pylint: disable=C0112,R0903 - -"""Shows a widget per user-defined shortcut and allows to define the behaviour -when clicking on it. - -For more than one shortcut, the commands and labels are strings separated by -a demiliter (; semicolon by default). - -For example in order to create two shortcuts labeled A and B with commands -cmdA and cmdB you could do: - - ./bumblebee-status -m shortcut -p shortcut.cmd="ls;ps" shortcut.label="A;B" - -Parameters: - * shortcut.cmds : List of commands to execute - * shortcut.labels: List of widgets' labels (text) - * shortcut.delim : Commands and labels delimiter (; semicolon by default) -""" - -import logging -import bumblebee.engine -import bumblebee.output -import bumblebee.input - -LINK = "https://github.com/tobi-wan-kenobi/bumblebee-status/wiki" -LABEL = "Click me" - -class Module(bumblebee.engine.Module): - """ Shortcut module.""" - - def __init__(self, engine, config): - widgets = [] - self._engine = engine - super(Module, self).__init__(engine, config, widgets) - - self._labels = self.parameter("labels", "{}".format(LABEL)) - self._cmds = self.parameter("cmds", "firefox {}".format(LINK)) - self._delim = self.parameter("delim", ";") - - self.update_widgets(widgets) - - def update_widgets(self, widgets): - """ Creates a set of widget per user define shortcut.""" - - cmds = self._cmds.split(self._delim) - labels = self._labels.split(self._delim) - - # to be on the safe side create as many widgets as there are data (cmds or labels) - num_shortcuts = min(len(cmds), len(labels)) - - # report possible problem as a warning - if len(cmds) is not len(labels): - logging.warning("shortcut: the number of commands does not match "\ - "the number of provided labels.") - logging.warning("cmds : %s, labels : %s", cmds, labels) - - for idx in range(0, num_shortcuts): - cmd = cmds[idx] - label = labels[idx] - - widget = bumblebee.output.Widget(full_text=label) - self._engine.input.register_callback(widget, button=bumblebee.input.LEFT_MOUSE, cmd=cmd) - - widgets.append(widget) - - def update(self, widgets): - if len(widgets) <= 0: - self.update_widgets(widgets) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/spaceapi.py b/bumblebee/modules/spaceapi.py deleted file mode 100644 index 42ff495..0000000 --- a/bumblebee/modules/spaceapi.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# pylint: disable=C0111,R0903 - -"""Displays the state of a Space API endpoint -Space API is an API for hackspaces based on JSON. See spaceapi.io for -an example. - -Requires the following libraries: - * requests - * regex - -Parameters: - * spaceapi.url: String representation of the api endpoint - * spaceapi.format: Format string for the output - -Format Strings: - * Format strings are indicated by double %% - * They represent a leaf in the JSON tree, layers seperated by "." - * Boolean values can be overwritten by appending "%true%false" - in the format string - * Example: to reference "open" in "{"state":{"open": true}}" - you would write "%%state.open%%", if you also want - to say "Open/Closed" depending on the boolean you - would write "%%state.open%Open%Closed%%" -""" - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -import requests -import threading -import re -import json - - -def formatStringBuilder(s, json): - """ - Parses Format Strings - Parameter: - s -> format string - json -> the spaceapi response object - """ - identifiers = re.findall("%%.*?%%", s) - for i in identifiers: - ic = i[2:-2] # Discard %% - j = ic.split("%") - - # Only neither of, or both true AND false may be overwritten - if len(j) != 3 and len(j) != 1: - return "INVALID FORMAT STRING" - - if len(j) == 1: # no overwrite - s = s.replace(i, json[j[0]]) - elif json[j[0]]: # overwrite for True - s = s.replace(i, j[1]) - else: # overwrite for False - s = s.replace(i, j[2]) - return s - - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - super(Module, self).__init__( - engine, config, bumblebee.output.Widget(full_text=self.getState) - ) - - engine.input.register_callback( - self, button=bumblebee.input.LEFT_MOUSE, cmd=self.__forceReload - ) - - self._data = {} - self._error = None - - self._threadingCount = 0 - - # The URL representing the api endpoint - self._url = self.parameter("url", default="http://club.entropia.de/spaceapi") - self._format = self.parameter( - "format", default=u" %%space%%: %%state.open%Open%Closed%%" - ) - - def state(self, widget): - try: - if self._error is not None: - return ["critical"] - elif self._data["state.open"]: - return ["warning"] - else: - return [] - except KeyError: - return ["critical"] - - def update(self, widgets): - if self._threadingCount == 0: - thread = threading.Thread(target=self.get_api_async, args=()) - thread.start() - self._threadingCount = ( - 0 if self._threadingCount > 300 else self._threadingCount + 1 - ) - - def getState(self, widget): - text = self._format - if self._error is not None: - text = self._error - else: - try: - text = formatStringBuilder(self._format, self._data) - except KeyError: - text = "KeyError" - return text - - def get_api_async(self): - try: - with requests.get(self._url, timeout=10) as request: - # Can't implement error handling for python2.7 if I use - # request.json() as it uses simplejson in newer versions - self._data = self.__flatten(json.loads(request.text)) - self._error = None - except requests.exceptions.Timeout: - self._error = "Timeout" - except requests.exceptions.HTTPError: - self._error = "HTTP Error" - except ValueError: - self._error = "Not a JSON response" - - # left_mouse_button handler - def __forceReload(self, event): - self._threadingCount += 300 - self._error = "RELOADING" - - # Flattens the JSON structure recursively, e.g. ["space"]["open"] - # becomes ["space.open"] - def __flatten(self, json): - out = {} - for key in json: - value = json[key] - if type(value) is dict: - flattened_key = self.__flatten(value) - for fk in flattened_key: - out[key + "." + fk] = flattened_key[fk] - else: - out[key] = value - return out - - -# Author: Tobias Manske -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/spacer.py b/bumblebee/modules/spacer.py deleted file mode 100644 index b89c233..0000000 --- a/bumblebee/modules/spacer.py +++ /dev/null @@ -1,23 +0,0 @@ -# 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/bumblebee/modules/spotify.py b/bumblebee/modules/spotify.py deleted file mode 100644 index abc12bd..0000000 --- a/bumblebee/modules/spotify.py +++ /dev/null @@ -1,87 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the current song being played -Requires the following library: - * python-dbus -Parameters: - * spotify.format: Format string (defaults to "{artist} - {title}") - Available values are: {album}, {title}, {artist}, {trackNumber}, {playbackStatus} - * spotify.previous: Change binding for previous song (default is left click) - * spotify.next: Change binding for next song (default is right click) - * spotify.pause: Change binding for toggling pause (default is middle click) - Available options for spotify.previous, spotify.next and spotify.pause are: - LEFT_CLICK, RIGHT_CLICK, MIDDLE_CLICK, SCROLL_UP, SCROLL_DOWN -""" - -import sys - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -from bumblebee.output import scrollable - -try: - import dbus -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.spotify) - ) - buttons = {"LEFT_CLICK":bumblebee.input.LEFT_MOUSE, - "RIGHT_CLICK":bumblebee.input.RIGHT_MOUSE, - "MIDDLE_CLICK":bumblebee.input.MIDDLE_MOUSE, - "SCROLL_UP":bumblebee.input.WHEEL_UP, - "SCROLL_DOWN":bumblebee.input.WHEEL_DOWN, - } - - self._song = "" - self._format = self.parameter("format", "{artist} - {title}") - prev_button = self.parameter("previous", "LEFT_CLICK") - next_button = self.parameter("next", "RIGHT_CLICK") - pause_button = self.parameter("pause", "MIDDLE_CLICK") - - cmd = "dbus-send --session --type=method_call --dest=org.mpris.MediaPlayer2.spotify \ - /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player." - engine.input.register_callback(self, button=buttons[prev_button], - cmd=cmd + "Previous") - engine.input.register_callback(self, button=buttons[next_button], - cmd=cmd + "Next") - engine.input.register_callback(self, button=buttons[pause_button], - cmd=cmd + "PlayPause") - - @scrollable - def spotify(self, widget): - return self.string_song - - def hidden(self): - return self.string_song == "" - - def update(self, widgets): - try: - bus = dbus.SessionBus() - spotify = bus.get_object("org.mpris.MediaPlayer2.spotify", "/org/mpris/MediaPlayer2") - spotify_iface = dbus.Interface(spotify, 'org.freedesktop.DBus.Properties') - props = spotify_iface.Get('org.mpris.MediaPlayer2.Player', 'Metadata') - playback_status = str(spotify_iface.Get('org.mpris.MediaPlayer2.Player', 'PlaybackStatus')) - self._song = self._format.format(album=str(props.get('xesam:album')), - title=str(props.get('xesam:title')), - artist=','.join(props.get('xesam:artist')), - trackNumber=str(props.get('xesam:trackNumber')), - playbackStatus=u"\u25B6" if playback_status=="Playing" else u"\u258D\u258D" if playback_status=="Paused" else "",) - - except Exception: - self._song = "" - - @property - def string_song(self): - if sys.version_info.major < 3: - return unicode(self._song) - return str(self._song) - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/stock.py b/bumblebee/modules/stock.py deleted file mode 100644 index 5333700..0000000 --- a/bumblebee/modules/stock.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: UTF-8 -*- -# pylint: disable=C0111,R0903 - -"""Display a stock quote from worldtradingdata.com - -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) -""" - -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import bumblebee.util - -import json -import requests - -import logging - -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 = bumblebee.util.asbool(self.parameter('change', True)) - self._value = None - self.interval_factor(60) - self.interval(60) - - def value(self, widget): - results = [] - if not self._value: - return 'n/a' - data = json.loads(self._value) - - for symbol in data['quoteResponse']['result']: - valkey = 'regularMarketChange' if self._change else 'regularMarketPrice' - sym = 'n/a' if not 'symbol' in symbol else symbol['symbol'] - currency = 'USD' if not 'currency' in symbol else symbol['currency'] - val = 'n/a' if not valkey in symbol else '{:.2f}'.format(symbol[valkey]) - results.append('{} {} {}'.format(sym, val, currency)) - return u' '.join(results) - - def fetch(self): - if self._symbols: - url = 'https://query1.finance.yahoo.com/v7/finance/quote?symbols=' - url += self._symbols + '&fields=regularMarketPrice,currency,regularMarketChange' - return requests.get(url).text.strip() - else: - logging.error('unable to retrieve stock exchange rate') - return None - - def update(self, widgets): - self._value = self.fetch() - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/sun.py b/bumblebee/modules/sun.py deleted file mode 100644 index e27d750..0000000 --- a/bumblebee/modules/sun.py +++ /dev/null @@ -1,111 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays sunrise and sunset times - -Parameters: - * cpu.lat : Latitude of your location - * cpu.lon : Longitude of your location -""" - -try: - from suntime import Sun, SunTimeException -except ImportError: - pass -try: - import requests -except ImportError: - pass -try: - from dateutil.tz import tzlocal -except ImportError: - pass - -import datetime - -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.suntimes) - ) - self.interval(3600) - self._lat = self.parameter("lat", None) - self._lon = self.parameter("lon", None) - try: - if not self._lat or not self._lon: - location_url = "http://ipinfo.io/json" - location = requests.get(location_url).json() - self._lat, self._lon = location["loc"].split(",") - self._lat = float(self._lat) - self._lon = float(self._lon) - except Exception: - pass - self.update(None) - - def suntimes(self, _): - if self._sunset and self._sunrise: - if self._isup: - return u"\u21A7{} \u21A5{}".format( - self._sunset.strftime('%H:%M'), - self._sunrise.strftime('%H:%M')) - return u"\u21A5{} \u21A7{}".format(self._sunrise.strftime('%H:%M'), - self._sunset.strftime('%H:%M')) - return "?" - - def _calculate_times(self): - self._isup = False - try: - sun = Sun(self._lat, self._lon) - except Exception: - self._sunrise = None - self._sunset = None - return - - order_matters = True - - try: - self._sunrise = sun.get_local_sunrise_time() - except SunTimeException: - self._sunrise = "no sunrise" - order_matters = False - - try: - self._sunset = sun.get_local_sunset_time() - except SunTimeException: - self._sunset = "no sunset" - order_matters = False - - if not order_matters: - return - - now = datetime.datetime.now(tz=tzlocal()) - if now > self._sunset: - tomorrow = (now + datetime.timedelta(days=1)).date() - try: - self._sunrise = sun.get_local_sunrise_time(tomorrow) - self._sunset = sun.get_local_sunset_time(tomorrow) - except SunTimeException: - self._sunrise = "no sunrise" - self._sunset = "no sunset" - - elif now > self._sunrise: - tomorrow = (now + datetime.timedelta(days=1)).date() - try: - self._sunrise = sun.get_local_sunrise_time(tomorrow) - except SunTimeException: - self._sunrise = "no sunrise" - return - self._isup = True - - def update(self, widgets): - if not self._lat or not self._lon: - self._sunrise = None - self._sunset = None - self._calculate_times() - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/system.py b/bumblebee/modules/system.py deleted file mode 100644 index e2f296a..0000000 --- a/bumblebee/modules/system.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=C0111,R0903 - -""" system module - -adds the possibility to - * shutdown - * reboot -the system. - -Per default a confirmation dialog is shown before the actual action is performed. - -Parameters: - * system.confirm: show confirmation dialog before performing any action (default: true) - * system.reboot: specify a reboot command (defaults to 'reboot') - * system.shutdown: specify a shutdown command (defaults to 'shutdown -h now') - * system.logout: specify a logout command (defaults to 'i3exit logout') - * system.switch_user: specify a command for switching the user (defaults to 'i3exit switch_user') - * system.lock: specify a command for locking the screen (defaults to 'i3exit lock') - * system.suspend: specify a command for suspending (defaults to 'i3exit suspend') - * system.hibernate: specify a command for hibernating (defaults to 'i3exit hibernate') -""" - -import logging -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import bumblebee.popup_v2 -import functools - -try: - import Tkinter as tk - import tkMessageBox as tkmessagebox -except ImportError: - # python 3 - try: - import tkinter as tk - from tkinter import messagebox as tkmessagebox - except ImportError: - logging.warning("failed to import tkinter - bumblebee popups won't work!") - - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.text) - ) - - self._confirm = True - if self.parameter("confirm", "true") == "false": - self._confirm = False - - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd=self.popup) - - def update(self, widgets): - pass - - def text(self, widget): - return "" - - def _on_command(self, header, text, command): - do_it = True - if self._confirm: - root = tk.Tk() - root.withdraw() - root.focus_set() - - do_it = tkmessagebox.askyesno(header, text) - root.destroy() - - if do_it: - bumblebee.util.execute(command) - - - def popup(self, widget): - menu = bumblebee.popup_v2.PopupMenu() - reboot_cmd = self.parameter("reboot", "reboot") - shutdown_cmd = self.parameter("shutdown", "shutdown -h now") - logout_cmd = self.parameter("logout", "i3exit logout") - switch_user_cmd = self.parameter("switch_user", "i3exit switch_user") - lock_cmd = self.parameter("lock", "i3exit lock") - suspend_cmd = self.parameter("suspend", "i3exit suspend") - hibernate_cmd = self.parameter("hibernate", "i3exit hibernate") - - menu.add_menuitem("shutdown", callback=functools.partial(self._on_command, "Shutdown", "Shutdown?", shutdown_cmd)) - menu.add_menuitem("reboot", callback=functools.partial(self._on_command, "Reboot", "Reboot?", reboot_cmd)) - menu.add_menuitem("log out", callback=functools.partial(self._on_command, "Log out", "Log out?", "i3exit logout")) - # don't ask for these - menu.add_menuitem("switch user", callback=functools.partial(bumblebee.util.execute, switch_user_cmd)) - menu.add_menuitem("lock", callback=functools.partial(bumblebee.util.execute, lock_cmd)) - menu.add_menuitem("suspend", callback=functools.partial(bumblebee.util.execute, suspend_cmd)) - menu.add_menuitem("hibernate", callback=functools.partial(bumblebee.util.execute, hibernate_cmd)) - - menu.show(widget) - - def state(self, widget): - return [] diff --git a/bumblebee/modules/taskwarrior.py b/bumblebee/modules/taskwarrior.py deleted file mode 100644 index a61ccb2..0000000 --- a/bumblebee/modules/taskwarrior.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Displays the number of pending tasks in TaskWarrior. - -Requires the following library: - * taskw - -Parameters: - * taskwarrior.taskrc : path to the taskrc file (defaults to ~/.taskrc) -""" - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -try: - from taskw import TaskWarrior -except: - pass - - -class Module(bumblebee.engine.Module): - """TaskWarrior module.""" - - def __init__(self, engine, config): - """Initialize taskwarrior module.""" - super(Module, self).__init__(engine, config, - bumblebee.output.Widget( - full_text=self.output)) - self._pending_tasks_count = "0" - - def update(self, widgets): - """Return a string with the number of pending tasks from TaskWarrior.""" - try: - taskrc = self.parameter("taskrc", "~/.taskrc") - w = TaskWarrior(config_filename=taskrc) - pending_tasks = w.filter_tasks({'status': 'pending'}) - self._pending_tasks_count = str(len(pending_tasks)) - except: - self._pending_tasks_count = 'Error' - - def output(self, _): - """Format the task counter to output in bumblebee.""" - return "{}".format(self._pending_tasks_count) diff --git a/bumblebee/modules/test.py b/bumblebee/modules/test.py deleted file mode 100644 index 5e92e0e..0000000 --- a/bumblebee/modules/test.py +++ /dev/null @@ -1,14 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Test module -""" - -import bumblebee.engine - -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/bumblebee/modules/title.py b/bumblebee/modules/title.py deleted file mode 100644 index 2316e9b..0000000 --- a/bumblebee/modules/title.py +++ /dev/null @@ -1,85 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays focused i3 window title. - -Requirements: - * i3ipc - -Parameters: - * title.max : Maximum character length for title before truncating. Defaults to 64. - * title.placeholder : Placeholder text to be placed if title was truncated. Defaults to "...". - * title.scroll : Boolean flag for scrolling title. Defaults to False -""" - -import threading - -try: - import i3ipc -except ImportError: - pass - -import bumblebee.util -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -from bumblebee.output import scrollable - -_no_title = "n/a" - -class Module(bumblebee.engine.Module): - """Window title module.""" - - def __init__(self, engine, config): - super(Module, self).__init__( - engine, - config - ) - - # parsing of parameters - self._scroll = bumblebee.util.asbool(self.parameter("scroll", False)) - self._max = int(self.parameter("max", 64)) - self._placeholder = self.parameter("placeholder", "...") - - # set output of the module - self.widgets(bumblebee.output.Widget(full_text= - self._scrolling_focused_title if self._scroll else self._focused_title)) - - # create a connection with i3ipc - try: - self._i3 = i3ipc.Connection() - # event is called both on focus change and title change - self._i3.on("window", lambda _p_i3, _p_e: self._pollTitle()) - # begin listening for events - threading.Thread(target=self._i3.main).start() - except: - pass - - # initialize the first title - self._pollTitle() - - def _focused_title(self, widget): - return self._title - - @scrollable - def _scrolling_focused_title(self, widget): - return self._full_title - - def _pollTitle(self): - """Updating current title.""" - try: - self._full_title = self._i3.get_tree().find_focused().name - except: - self._full_title = _no_title - if self._full_title is None: - self._full_title = _no_title - - if not self._scroll: - # cut the text if it is too long - if len(self._full_title) > self._max: - self._title = self._full_title[0:self._max - len(self._placeholder)] - self._title = "{}{}".format(self._title, self._placeholder) - else: - self._title = self._full_title - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/todo.py b/bumblebee/modules/todo.py deleted file mode 100644 index 04cd256..0000000 --- a/bumblebee/modules/todo.py +++ /dev/null @@ -1,45 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the number of todo items from a text file - -Parameters: - * todo.file: File to read TODOs from (defaults to ~/Documents/todo.txt) -""" - -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import os.path - - -class Module(bumblebee.engine.Module): - - - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.output) - ) - self._doc = os.path.expanduser(self.parameter("file", "~/Documents/todo.txt")) - self._todos = self.count_items() - - - def output(self, widget): - self._todos = self.count_items() - return str(self._todos) - - - def state(self, widgets): - if self._todos == 0: - return "empty" - return "items" - - - def count_items(self): - try: - i = -1 - with open(self._doc) as f: - for i, l in enumerate(f): - pass - return i+1 - except Exception: - return 0 diff --git a/bumblebee/modules/traffic.py b/bumblebee/modules/traffic.py deleted file mode 100644 index 2eac453..0000000 --- a/bumblebee/modules/traffic.py +++ /dev/null @@ -1,110 +0,0 @@ -# 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") - * traffic.states: Comma-separated list of states to show (prefix with "^" to invert - i.e. ^down -> show all devices that are not in state down) - * traffic.showname: If set to False, hide network interface name (defaults to True) -""" - -import time -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._status = "" - - self._showname = bumblebee.util.asbool(self.parameter("showname", True)) - self._prev = {} - self._states = {} - self._lastcheck = 0 - self._states["include"] = [] - self._states["exclude"] = [] - for state in tuple(filter(len, self.parameter("states", "").split(","))): - if state[0] == "^": - self._states["exclude"].append(state[1:]) - else: - self._states["include"].append(state) - self._update_widgets(widgets) - - 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 = bumblebee.output.Widget(name=name) - widget.full_text(txt) - widgets.append(widget) - - for key in attributes: - widget.set(key, attributes[key]) - - return widget - - 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)] - - del widgets[:] - - counters = psutil.net_io_counters(pernic=True) - now = time.time() - timediff = now - (self._lastcheck if self._lastcheck else now) - if timediff <= 0: timediff = 1 - self._lastcheck = now - for interface in interfaces: - if not interface: interface = "lo" - state = "down" - if len(self.get_addresses(interface)) > 0: - state = "up" - elif bumblebee.util.asbool(self.parameter("hide_down", True)): - continue - - if len(self._states["exclude"]) > 0 and state in self._states["exclude"]: continue - if len(self._states["include"]) > 0 and state not in self._states["include"]: continue - - data = { - "rx": counters[interface].bytes_recv, - "tx": counters[interface].bytes_sent, - } - - name = "traffic-{}".format(interface) - - if self._showname: - 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 = self._prev.get(name, 0) - speed = bumblebee.util.bytefmt((int(data[direction]) - int(prev))/timediff) - txtspeed ='{0}/s'.format(speed) - widget.full_text(txtspeed) - self._prev[name] = data[direction] - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/twmn.py b/bumblebee/modules/twmn.py deleted file mode 100644 index 1ebb61c..0000000 --- a/bumblebee/modules/twmn.py +++ /dev/null @@ -1,39 +0,0 @@ -#pylint: disable=C0111,R0903 - -"""Toggle twmn notifications.""" - -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._paused = False - # Make sure that twmn is currently not paused - try: - bumblebee.util.execute("killall -SIGUSR2 twmnd") - except: - pass - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd=self.toggle_status - ) - - def toggle_status(self, event): - self._paused = not self._paused - - try: - if self._paused: - bumblebee.util.execute("systemctl --user start twmnd") - else: - bumblebee.util.execute("systemctl --user stop twmnd") - except: - self._paused = not self._paused # toggling failed - - def state(self, widget): - if self._paused: - return ["muted"] - return ["unmuted"] diff --git a/bumblebee/modules/uptime.py b/bumblebee/modules/uptime.py deleted file mode 100644 index 34e4085..0000000 --- a/bumblebee/modules/uptime.py +++ /dev/null @@ -1,30 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Displays the system uptime.""" - -# Use absolute_import because there's already a datatime module -# in the same directory -from __future__ import absolute_import - -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -from datetime import timedelta - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.output) - ) - self._uptime = "" - - def output(self, _): - return "{}".format(self._uptime) - - def update(self, widgets): - with open('/proc/uptime', 'r') as f: - uptime_seconds = int(float(f.readline().split()[0])) - self._uptime = timedelta(seconds = uptime_seconds) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/vault.py b/bumblebee/modules/vault.py deleted file mode 100644 index 6505dc2..0000000 --- a/bumblebee/modules/vault.py +++ /dev/null @@ -1,81 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Copy passwords from a password store into the clipboard (currently supports only "pass") - -Many thanks to [@bbernhard](https://github.com/bbernhard) for the idea! - -Parameters: - * vault.duration: Duration until password is cleared from clipboard (defaults to 30) - * vault.location: Location of the password store (defaults to ~/.password-store) - * vault.offx: x-axis offset of popup menu (defaults to 0) - * vault.offy: y-axis offset of popup menu (defaults to 0) -""" - - -# TODO: -# - support multiple backends by abstracting the menu structure into a tree -# - build the menu and the actions based on that abstracted tree -# - -import os -import time -import threading -import bumblebee.util -import bumblebee.popup_v2 -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -def build_menu(parent, current_directory, callback): - with os.scandir(current_directory) as it: - for entry in it: - if entry.name.startswith("."): continue - if entry.is_file(): - name = entry.name[:entry.name.rfind(".")] - parent.add_menuitem(name, callback=lambda : callback(os.path.join(current_directory, name))) - - else: - submenu = bumblebee.popup_v2.PopupMenu(parent, leave=False) - build_menu(submenu, os.path.join(current_directory, entry.name), callback) - parent.add_cascade(entry.name, submenu) - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.text) - ) - self._duration = int(self.parameter("duration", 30)) - self._offx = int(self.parameter("offx", 0)) - self._offy = int(self.parameter("offy", 0)) - self._path = os.path.expanduser(self.parameter("location", "~/.password-store/")) - self._reset() - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd=self.popup) - - def popup(self, widget): - menu = bumblebee.popup_v2.PopupMenu(leave=False) - - build_menu(menu, self._path, self._callback) - menu.show(widget, offset_x=self._offx, offset_y=self._offy) - - def _reset(self): - self._timer = None - self._text = str(self.parameter("text", "")) - - def _callback(self, secret_name): - secret_name = secret_name.replace(self._path, "") # remove common path - if self._timer: - self._timer.cancel() - # bumblebee.util.execute hangs for some reason - os.system("PASSWORD_STORE_CLIP_TIME={} pass -c {} > /dev/null 2>&1".format(self._duration, secret_name)) - self._timer = threading.Timer(self._duration, self._reset) - self._timer.start() - self._start = int(time.time()) - self._text = secret_name - - def text(self, widget): - if self._timer: - return "{} ({}s)".format(self._text, self._duration - (int(time.time()) - self._start)) - return self._text - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/vpn.py b/bumblebee/modules/vpn.py deleted file mode 100644 index a43e36b..0000000 --- a/bumblebee/modules/vpn.py +++ /dev/null @@ -1,97 +0,0 @@ -# pylint: disable=C0111,R0903 - -""" Displays the VPN profile that is currently in use. - - Left click opens a popup menu that lists all available VPN profiles and allows to establish - a VPN connection using that profile. - - Prerequisites: - * nmcli needs to be installed and configured properly. - To quickly test, whether nmcli is working correctly, type "nmcli -g NAME,TYPE,DEVICE con" which - lists all the connection profiles that are configured. Make sure that your VPN profile is in that list! - - e.g: to import a openvpn profile via nmcli: - sudo nmcli connection import type openvpn file -""" - -import logging -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import functools - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - super(Module, self).__init__(engine, config, - bumblebee.output.Widget(full_text=self.vpn_status) - ) - - self._connected_vpn_profile = None - self._selected_vpn_profile = None - - res = bumblebee.util.execute("nmcli -g NAME,TYPE c") - lines = res.splitlines() - - self._vpn_profiles = [] - for line in lines: - info = line.split(':') - try: - if info[1] == "vpn": - self._vpn_profiles.append(info[0]) - except: - pass - - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd=self.popup) - - def update(self, widgets): - try: - res = bumblebee.util.execute("nmcli -g NAME,TYPE,DEVICE con") - lines = res.splitlines() - self._connected_vpn_profile = None - for line in lines: - info = line.split(':') - if info[1] == "vpn" and info[2] != "": - self._connected_vpn_profile = info[0] - - except Exception as e: - logging.exception("Couldn't get VPN status") - self._connected_vpn_profile = None - - def vpn_status(self, widget): - if self._connected_vpn_profile is None: - return "off" - return self._connected_vpn_profile - - def _on_vpn_disconnect(self): - try: - bumblebee.util.execute("nmcli c down \"{vpn}\"" - .format(vpn=self._connected_vpn_profile)) - self._connected_vpn_profile = None - except Exception as e: - logging.exception("Couldn't disconnect VPN connection") - - def _on_vpn_connect(self, name): - self._selected_vpn_profile = name - - try: - bumblebee.util.execute("nmcli c up \"{vpn}\"" - .format(vpn=self._connected_vpn_profile)) - self._connected_vpn_profile = name - except Exception as e: - logging.exception("Couldn't establish VPN connection") - self._connected_vpn_profile = None - - def popup(self, widget): - menu = bumblebee.popup_v2.PopupMenu() - - if self._connected_vpn_profile is not None: - menu.add_menuitem("Disconnect", callback=self._on_vpn_disconnect) - for vpn_profile in self._vpn_profiles: - if self._connected_vpn_profile is not None and self._connected_vpn_profile == vpn_profile: - continue - menu.add_menuitem(vpn_profile, callback=functools.partial(self._on_vpn_connect, vpn_profile)) - menu.show(widget) - - def state(self, widget): - return [] diff --git a/bumblebee/modules/weather.py b/bumblebee/modules/weather.py deleted file mode 100644 index a86f44f..0000000 --- a/bumblebee/modules/weather.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- 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.location: Set location, defaults to 'auto' for getting location from http://ipinfo.io - If set to a comma-separated list, left-click and right-click can be used to rotate the locations. - Locations should be city names or city ids. - * weather.unit: metric (default), kelvin, imperial - * weather.showcity: If set to true, show location information, otherwise hide it (defaults to true) - * weather.showminmax: If set to true, show the minimum and maximum temperature, otherwise hide it (defaults to false) - * weather.apikey: API key from http://api.openweathermap.org -""" - -import bumblebee.input -import bumblebee.output -import bumblebee.engine -import re - -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.output) - ) - self._temperature = 0 - self._apikey = self.parameter("apikey", "af7bfe22287c652d032a3064ffa44088") - self._location = self.parameter("location", "auto") - if "," in self._location: - self._location = self._location.split(",") - else: - self._location = [self._location] - self._index = 0 - self._showcity = bumblebee.util.asbool(self.parameter("showcity", True)) - self._showminmax = bumblebee.util.asbool(self.parameter("showminmax", False)) - self._unit = self.parameter("unit", "metric") - self._valid = False - self.interval_factor(60) - self.interval(15) - - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self._next_location) - engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self._prev_location) - - def _next_location(self, event): - self._index = 0 if self._index >= len(self._location) - 1 else self._index + 1 - self.update(self.widgets()) - - def _prev_location(self, event): - self._index = len(self._location)-1 if self._index <= 0 else self._index - 1 - self.update(self.widgets()) - - 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): - return u"{}°{}".format(self._temperature, self._unit_suffix()) - - def tempmin(self): - return u"{}°{}".format(self._tempmin, self._unit_suffix()) - - def tempmax(self): - return u"{}°{}".format(self._tempmax, self._unit_suffix()) - - def city(self): - city = re.sub('[_-]', ' ', self._city) - return u"{} ".format(city) - - def output(self, widget): - if not self._valid: - return u"?" - if self._showminmax: - self._showcity=False - return self.city() + self.temperature() + " Hi:" + self.tempmax() + " Lo:" + self.tempmin() - elif self._showcity: - return self.city() + self.temperature() - else: - return self.temperature() - - def state(self, widget): - if self._valid: - if "thunderstorm" in self._weather: - return ['thunder'] - elif "drizzle" in self._weather: - return ['rain'] - elif "rain" in self._weather: - return ['rain'] - elif "snow" in self._weather: - return ['snow'] - elif "sleet" in self._weather: - return ['sleet'] - elif "clear" in self._weather: - return ['clear'] - elif "cloud" in self._weather: - return ['clouds'] - else: - return [] - - return [] - - def update(self, widgets): - try: - 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[self._index] == "auto": - location_url = "http://ipinfo.io/json" - location = requests.get(location_url).json() - coord = location["loc"].split(",") - weather_url = "{url}&lat={lat}&lon={lon}".format(url=weather_url, lat=coord[0], lon=coord[1]) - elif self._location[self._index].isdigit(): - weather_url = "{url}&id={id}".format(url=weather_url, id=self._location[self._index]) - else: - weather_url = "{url}&q={city}".format(url=weather_url, city=self._location[self._index]) - weather = requests.get(weather_url).json() - self._city = weather['name'] - self._temperature = int(weather['main']['temp']) - self._tempmin = int(weather['main']['temp_min']) - self._tempmax = int(weather['main']['temp_max']) - self._weather = weather['weather'][0]['main'].lower() - self._valid = True - except Exception: - self._valid = False - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/xkcd.py b/bumblebee/modules/xkcd.py deleted file mode 100644 index 143e590..0000000 --- a/bumblebee/modules/xkcd.py +++ /dev/null @@ -1,17 +0,0 @@ -#pylint: disable=C0111,R0903 - -"""Opens a random xkcd comic in the browser.""" - -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="xkcd") - ) - engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, - cmd="xdg-open https://c.xkcd.com/random/comic/" - ) diff --git a/bumblebee/modules/xrandr.py b/bumblebee/modules/xrandr.py deleted file mode 100644 index b9e5cd7..0000000 --- a/bumblebee/modules/xrandr.py +++ /dev/null @@ -1,118 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Shows a widget for each connected screen and allows the user to enable/disable screens. - -Parameters: - * xrandr.overwrite_i3config: If set to 'true', this module assembles a new i3 config - every time a screen is enabled or disabled by taking the file "~/.config/i3/config.template" - and appending a file "~/.config/i3/config." for every screen. - * xrandr.autoupdate: If set to 'false', does *not* invoke xrandr automatically. Instead, the - module will only refresh when displays are enabled or disabled (defaults to true) - -Requires the following python module: - * (optional) i3 - if present, the need for updating the widget list is auto-detected - -Requires the following executable: - * xrandr -""" - -import os -import re -import sys - -import bumblebee.util -import bumblebee.input -import bumblebee.output -import bumblebee.engine - -try: - import i3 -except: - pass - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widgets = [] - self._engine = engine - super(Module, self).__init__(engine, config, widgets) - self._autoupdate = bumblebee.util.asbool(self.parameter("autoupdate", True)) - self._needs_update = True - - try: - i3.Subscription(self._output_update, "output") - except: - pass - - def _output_update(self, event, data, _): - self._needs_update = True - - def update_widgets(self, widgets): - new_widgets = [] - - if self._autoupdate == False and self._needs_update == False: - return - - self._needs_update = False - - 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) - - if self._autoupdate == False: - widget = bumblebee.output.Widget(full_text="") - widget.set("state", "refresh") - self._engine.input.register_callback(widget, button=1, cmd=self._refresh) - widgets.append(widget) - - def update(self, widgets): - self.update_widgets(widgets) - - def state(self, widget): - return widget.get("state", "off") - - def _refresh(self, event): - self._needs_update = True - - def _toggle(self, event): - self._needs_update = True - path = os.path.dirname(os.path.abspath(__file__)) - - if bumblebee.util.asbool(self.parameter("overwrite_i3config", False)) == True: - toggle_cmd = "{}/../../bin/toggle-display.sh".format(path) - else: - toggle_cmd = "xrandr" - - 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 is 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/bumblebee/modules/yubikey.py b/bumblebee/modules/yubikey.py deleted file mode 100644 index db32843..0000000 --- a/bumblebee/modules/yubikey.py +++ /dev/null @@ -1,33 +0,0 @@ -# pylint: disable=C0111,R0903 - -"""Shows yubikey information - -Requires: https://github.com/Yubico/python-yubico - -The output indicates that a YubiKey is not connected or it displays -the corresponding serial number. - -""" - -import yubico - -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.keystate)) - self._keystate = "No YubiKey" - - def keystate(self, widget): - return self._keystate - - def update(self, widget): - try: - self._keystate = "YubiKey: " + str(yubico.find_yubikey(debug=False).serial()) - except yubico.yubico_exception.YubicoError: - self._keystate = "No YubiKey" - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/modules/zpool.py b/bumblebee/modules/zpool.py deleted file mode 100644 index 1f3e897..0000000 --- a/bumblebee/modules/zpool.py +++ /dev/null @@ -1,187 +0,0 @@ -"""Displays info about zpools present on the system - -Parameters: - * zpool.list: Comma-separated list of zpools to display info for. If empty, info for all zpools - is displayed. (Default: "") - * zpool.format: Format string, tags {name}, {used}, {left}, {size}, {percentfree}, {percentuse}, - {status}, {shortstatus}, {fragpercent}, {deduppercent} are supported. - (Default: "{name} {used}/{size} ({percentfree}%)") - * zpool.showio: Show also widgets detailing current read and write I/O (Default: true) - * zpool.ioformat: Format string for I/O widget, tags {ops} (operations per seconds) and {band} - (bandwidth) are supported. (Default: "{band}") - * zpool.warnfree: Warn if free space is below this percentage (Default: 10) - * zpool.sudo: Use sudo when calling the `zpool` binary. (Default: false) - -Option `zpool.sudo` is intended for Linux users using zfsonlinux older than 0.7.0: In pre-0.7.0 -releases of zfsonlinux regular users couldn't invoke even informative commands such as -`zpool list`. If this option is true, command `zpool list` is invoked with sudo. If this option -is used, the following (or ekvivalent) must be added to the `sudoers(5)`: - -``` - ALL = (root) NOPASSWD: /usr/bin/zpool list -``` - -Be aware of security implications of doing this! -""" - -import time -import logging -from pkg_resources import parse_version -import bumblebee.engine -from bumblebee.util import execute, bytefmt, asbool - -log = logging.getLogger(__name__) - -class Module(bumblebee.engine.Module): - def __init__(self, engine, config): - widgets = [] - super(Module, self).__init__(engine, config, widgets) - - self._includelist = set(filter(lambda x: len(x) > 0, - self.parameter("list", default="").split(','))) - self._format = self.parameter("format", default="{name} {shortstatus} {used}/{size} " + - "({percentfree}%)") - self._usesudo = asbool(self.parameter("sudo", default=False)) - self._showio = asbool(self.parameter("showio", default=True)) - self._ioformat = self.parameter("ioformat", default="{band}") - self._warnfree = int(self.parameter("warnfree", default=10)) - - self._update_widgets(widgets) - - def update(self, widgets): - self._update_widgets(widgets) - - def state(self, widget): - if widget.name.endswith("__read"): - return "poolread" - elif widget.name.endswith("__write"): - return "poolwrite" - - state = widget.get("state") - if state == "FAULTED": - return [state, "critical"] - elif state == "DEGRADED" or widget.get("percentfree") < self._warnfree: - return [state, "warning"] - - return state - - def _update_widgets(self, widgets): - zfs_version_path = "/sys/module/zfs/version" - # zpool list -H: List all zpools, use script mode (no headers and tabs as separators). - try: - with open(zfs_version_path, 'r') as zfs_mod_version: - zfs_version = zfs_mod_version.readline().rstrip().split('-')[0] - except IOError: - # ZFS isn't installed or the module isn't loaded, stub the version - zfs_version = "0.0.0" - logging.error("ZFS version information not found at {}, check the module is loaded.".format(zfs_version_path)) - - raw_zpools = execute(('sudo ' if self._usesudo else '') + 'zpool list -H').split('\n') - - for widget in widgets: - widget.set("visited", False) - - for raw_zpool in raw_zpools: - try: - # Ignored fields (assigned to _) are "expandsz" and "altroot", also "ckpoint" in ZFS 0.8.0+ - if parse_version(zfs_version) < parse_version("0.8.0"): - name, size, alloc, free, _, frag, cap, dedup, health, _ = raw_zpool.split('\t') - else: - name, size, alloc, free, _, _, frag, cap, dedup, health, _ = raw_zpool.split('\t') - cap = cap.rstrip('%') - percentuse=int(cap) - percentfree=100-percentuse - # There is a command, zpool iostat, which is however blocking and was therefore - # causing issues. - # Instead, we read file `/proc/spl/kstat/zfs//io` which contains - # cumulative I/O statistics since boot (or pool creation). We store these values - # (and timestamp) during each widget update, and during the next widget update we - # use them to compute delta of transferred bytes, and using the last and current - # timestamp the rate at which they have been transferred. - with open("/proc/spl/kstat/zfs/{}/io".format(name), "r") as f: - # Third row provides data we need, we are interested in the first 4 values. - # More info about this file can be found here: - # https://github.com/zfsonlinux/zfs/blob/master/lib/libspl/include/sys/kstat.h#L580 - # The 4 values are: - # nread, nwritten, reads, writes - iostat = list(map(int, f.readlines()[2].split()[:4])) - except (ValueError, IOError): - # Unable to parse info about this pool, skip it - continue - - if self._includelist and name not in self._includelist: - continue - - widget = self.widget(name) - if not widget: - widget = bumblebee.output.Widget(name=name) - widget.set("last_iostat", [0, 0, 0, 0]) - widget.set("last_timestamp", 0) - widgets.append(widget) - - delta_iostat = [b - a for a, b in zip(iostat, widget.get("last_iostat"))] - widget.set("last_iostat", iostat) - - # From docs: - # > Note that even though the time is always returned as a floating point number, not - # > all systems provide time with a better precision than 1 second. - # Be aware that that may affect the precision of reported I/O - # Also, during one update cycle the reported I/O may be garbage if the system time - # was changed. - timestamp = time.time() - delta_timestamp = widget.get("last_timestamp") - timestamp - widget.set("last_timestamp", time.time()) - - # abs is there because sometimes the result is -0 - rate_iostat = [abs(x / delta_timestamp) for x in delta_iostat] - nread, nwritten, reads, writes = rate_iostat - - # theme.minwidth is not set since these values are not expected to change - # rapidly - widget.full_text(self._format.format(name=name, used=alloc, left=free, size=size, - percentfree=percentfree, percentuse=percentuse, - status=health, - shortstatus=self._shortstatus(health), - fragpercent=frag, deduppercent=dedup)) - widget.set("state", health) - widget.set("percentfree", percentfree) - widget.set("visited", True) - - if self._showio: - wname, rname = [name + x for x in ["__write", "__read"]] - widget_w = self.widget(wname) - widget_r = self.widget(rname) - if not widget_w or not widget_r: - widget_r = bumblebee.output.Widget(name=rname) - widget_w = bumblebee.output.Widget(name=wname) - widgets.extend([widget_r, widget_w]) - for w in [widget_r, widget_w]: - w.set("theme.minwidth", self._ioformat.format(ops=9999, - band=bytefmt(999.99*(1024**2)))) - w.set("visited", True) - widget_w.full_text(self._ioformat.format(ops=round(writes), - band=bytefmt(nwritten))) - widget_r.full_text(self._ioformat.format(ops=round(reads), - band=bytefmt(nread))) - - for widget in widgets: - if widget.get("visited") is False: - widgets.remove(widget) - - @staticmethod - def _shortstatus(status): - # From `zpool(8)`, section Device Failure and Recovery: - # A pool's health status is described by one of three states: online, degraded, or faulted. - # An online pool has all devices operating normally. A degraded pool is one in which one - # or more devices have failed, but the data is still available due to a redundant - # configuration. A faulted pool has corrupted metadata, or one or more faulted devices, and - # insufficient replicas to continue functioning. - shortstate = { - "DEGRADED": "DEG", - "FAULTED": "FLT", - "ONLINE": "ONL", - } - try: - return shortstate[status] - except KeyError: - return "" diff --git a/bumblebee/output.py b/bumblebee/output.py deleted file mode 100644 index 10c13e0..0000000 --- a/bumblebee/output.py +++ /dev/null @@ -1,324 +0,0 @@ -# pylint: disable=R0201 - -"""Output classes""" - -import sys -import json -import uuid -import logging - -import bumblebee.store -import bumblebee.util - -MAX_PERCENTS = 100. -CHARS = 8 -HBARS = [ - u"\u2581", - u"\u2582", - u"\u2583", - u"\u2584", - u"\u2585", - u"\u2586", - u"\u2587", - u"\u2588"] -VBARS = [ - u"\u258f", - u"\u258e", - u"\u258d", - u"\u258c", - u"\u258b", - u"\u258a", - u"\u2589", - u"\u2588"] - -log = logging.getLogger(__name__) - -def scrollable(func): - def wrapper(module, widget): - text = func(module, widget) - if not text: - return text - width = widget.get("theme.width", int(module.parameter("width", 30))) - if bumblebee.util.asbool(module.parameter("scrolling.makewide", "true")): - widget.set("theme.minwidth", "A"*width) - if width < 0: - return text - if len(text) <= width: - return text - # we need to shorten - - try: - bounce = int(module.parameter("scrolling.bounce", 1)) - except ValueError: - bounce = 1 - try: - scroll_speed = int(module.parameter("scrolling.speed", 1)) - except ValueError: - scroll_speed = 1 - start = widget.get("scrolling.start", -1) - direction = widget.get("scrolling.direction", "right") - start += scroll_speed if direction == "right" else -(scroll_speed) - - if width + start > len(text) + (scroll_speed -1): - if bounce: - widget.set("scrolling.direction", "left") - else: - start = 0 - elif start <= 0: - if bounce: - widget.set("scrolling.direction", "right") - else: - start = len(text) - widget.set("scrolling.start", start) - text = text[start:width+start] - - return text - return wrapper - - -class Bar(object): - """superclass""" - bars = None - - def __init__(self, value): - """ - Args: - - value (float): value between 0. and 100. meaning percents - """ - self.value = value - - -class HBar(Bar): - """horizontal bar (1 char)""" - bars = HBARS - - def __init__(self, value): - """ - Args: - - value (float): value between 0. and 100. meaning percents - """ - super(HBar, self).__init__(value) - self.step = MAX_PERCENTS / CHARS - - def get_char(self): - """ - Decide which char to draw - - Return: str - """ - for i in range(CHARS): - left = i * self.step - right = (i + 1) * self.step - if left <= self.value < right: - return self.bars[i] - return self.bars[-1] - - -def hbar(value): - """wrapper function""" - return HBar(value).get_char() - - -class VBar(Bar): - """vertical bar (can be more than 1 char)""" - bars = VBARS - - def __init__(self, value, width=1): - """ - Args: - - value (float): value between 0. and 100. meaning percents - - width (int): width - """ - super(VBar, self).__init__(value) - self.step = MAX_PERCENTS / (CHARS * width) - self.width = width - - def get_chars(self): - """ - Decide which char to draw - - Return: str - """ - if self.value == 100: - return self.bars[-1] * self.width - if self.width == 1: - for i in range(CHARS): - left = i * self.step - right = (i + 1) * self.step - if left <= self.value < right: - return self.bars[i] - else: - full_parts = int(self.value // (self.step * CHARS)) - remainder = self.value - full_parts * self.step * CHARS - empty_parts = self.width - full_parts - if remainder >= 0: - empty_parts -= 1 - part_vbar = VBar(remainder * self.width) # scale to width - chars = self.bars[-1] * full_parts - chars += part_vbar.get_chars() - chars += " " * empty_parts - return chars - - -def vbar(value, width): - """wrapper function""" - return VBar(value, width).get_chars() - - -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._minimized = False - self.name = name - self.id = str(uuid.uuid4()) - - def get_module(self): - return self._module - - def toggle_minimize(self): - self._minimized = not self._minimized - - 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 self._minimized: - return u"\u2026" - 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, config=None): - self._theme = theme - self._widgets = [] - self._started = False - self._config = config - - def started(self): - return self._started - - def start(self): - """Print start preamble for i3bar protocol""" - self._started = True - sys.stdout.write(json.dumps({"version": 1, "click_events": True}) + "\n[\n") - - def stop(self): - """Finish i3bar protocol""" - sys.stdout.write("]\n") - - def draw(self, widget, module=None, engine=None): - """Draw a single widget""" - full_text = widget.full_text() - if widget.get_module() and widget.get_module().hidden(): - return - if widget.get_module() and widget.get_module().name in self._config.autohide(): - if not any(state in widget.state() for state in ["warning", "critical"]): - return - padding = self._theme.padding(widget) - - prefix = self._theme.prefix(widget, padding) - suffix = self._theme.suffix(widget, padding) - - if self._config.markup() == "pango": - # add prefix/suffix colors - fg = self._theme.prefix_fg(widget) - bg = self._theme.prefix_bg(widget) - prefix = "{}".format( - "foreground='{}'".format(fg) if fg else "", - "background='{}'".format(bg) if bg else "", - prefix - ) - - 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) - - if width: - full_text = full_text.ljust(len(width) + len(prefix) + len(suffix)) - - markup = "none" if not self._config else self._config.markup() - - if markup == "pango": - full_text = full_text.replace("&", "&") - - 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": None, -# "min_width": width + "A"*(len(prefix) + len(suffix)) if width else None, - "align": self._theme.align(widget), - "instance": widget.id, - "name": module.id, - "markup": markup, - }) - - def begin(self): - """Start one output iteration""" - self._widgets = [] - self._theme.reset() - - def flush(self): - """Flushes output""" - widgets = self._widgets - if self._config and self._config.reverse(): - widgets = list(reversed(widgets)) - sys.stdout.write(json.dumps(widgets)) - if len(self._config.unused_keys()) > 0: - for key in self._config.unused_keys(): - log.warning("unused parameter {} - please check the documentation of the affected module to ensure the parameter exists".format(key)) - - def end(self): - """Finalizes output""" - sys.stdout.write(",\n") - sys.stdout.flush() - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/popup.py b/bumblebee/popup.py deleted file mode 100644 index 46d6a30..0000000 --- a/bumblebee/popup.py +++ /dev/null @@ -1,80 +0,0 @@ -"""Pop-up menus.""" - -import logging - -try: - import Tkinter as tk -except ImportError: - # python 3 - try: - import tkinter as tk - except ImportError: - logging.warning("failed to import tkinter - bumblebee popups won't work!") - -class PopupMenu: - """The popup-menu.""" - - def __init__(self): - """Initialize.""" - # menu widget - self.root = tk.Tk() - self.root.withdraw() - self.menu = tk.Menu(self.root) - - # internal state - self._item_count = 0 - self._clicked_item = None - self._active = False - - # bind event of popup getting closed by clicking outside of its area - self.menu.bind('', - lambda event: self.root.after_idle( - self._dismiss_callback)) - - def add_menuitem(self, menuitem, callback=None): - """Add menu items.""" - item_count = self._item_count - - def click_callback(): - # call internal callback with item index - self._item_callback(item_count) - - # default to internal callback - if callback is None: - callback = click_callback - self.menu.add_command(label=menuitem, - command=callback) - - # track item index - self._item_count += 1 - - def _item_callback(self, which_item): - """Menu item click callback.""" - logging.debug('popup: item callback: {}'.format(which_item)) - self._clicked_item = which_item - self.root.destroy() - self._active = False - - def _dismiss_callback(self): - """Menu dismissed.""" - logging.debug('popup: menu dismissed') - if self._active is True: - self._clicked_item = None - self.root.destroy() - - def show(self, event): - """Show popup.""" - self._clicked_item = None - self.menu.tk_popup(event['x'], event['y']-50) - self._active = True - self.root.mainloop() - return self._clicked_item - - -def create_and_show_menu(event, *menuitems): - """Create a menu object and show.""" - menu_obj = PopupMenu() - for menuitem in menuitems: - menu_obj.add_menuitem(*menuitem) - - return menu_obj.show(event) diff --git a/bumblebee/popup_v2.py b/bumblebee/popup_v2.py deleted file mode 100644 index 4d08c73..0000000 --- a/bumblebee/popup_v2.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Pop-up menus.""" - -import logging - -try: - import Tkinter as tk -except ImportError: - # python 3 - try: - import tkinter as tk - except ImportError: - logging.warning("failed to import tkinter - bumblebee popups won't work!") - -import functools - -class PopupMenu(object): - def __init__(self, parent=None, leave=True): - - if not parent: - self._root = tk.Tk() - self._root.withdraw() - self._menu = tk.Menu(self._root, tearoff=0) - self._menu.bind("", self._on_focus_out) - else: - self._root = parent.root() - self._root.withdraw() - self._menu = tk.Menu(self._root, tearoff=0) - self._menu.bind("", self._on_focus_out) - if leave: - self._menu.bind("", self._on_focus_out) - - def root(self): - return self._root - - def menu(self): - return self._menu - - def _on_focus_out(self, event=None): - self._root.destroy() - - def _on_click(self, callback): - self._root.destroy() - callback() - - def add_cascade(self, menuitem, submenu): - self._menu.add_cascade(label=menuitem, menu=submenu.menu()) - - def add_menuitem(self, menuitem, callback): - self._menu.add_command(label=menuitem, command=functools.partial(self._on_click, callback)) - - def show(self, event, offset_x=0, offset_y=0): - try: - self._menu.tk_popup(event['x'] + offset_x, event['y'] + offset_y) - finally: - self._menu.grab_release() - self._root.mainloop() diff --git a/bumblebee/store.py b/bumblebee/store.py deleted file mode 100644 index 6b35347..0000000 --- a/bumblebee/store.py +++ /dev/null @@ -1,27 +0,0 @@ -"""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 = {} - self._unused = {} - - def set(self, key, value): - """Set 'key' to 'value', overwriting 'key' if it exists already""" - self._data[key] = value - self._unused[key] = value - - def unused_keys(self): - return self._unused.keys() - - def get(self, key, default=None): - """Return the current value of 'key', or 'default' if 'key' is not set""" - self._unused.pop(key, None) - return self._data.get(key, default) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/theme.py b/bumblebee/theme.py deleted file mode 100644 index 2b52f73..0000000 --- a/bumblebee/theme.py +++ /dev/null @@ -1,310 +0,0 @@ -# pylint: disable=C0103 - -"""Theme support""" - -import os -import glob -import copy -import json -import io -import re -import logging - -try: - import requests - from requests.exceptions import RequestException -except ImportError: - pass - -import bumblebee.error - -def theme_path(): - """Return the path of the theme directory""" - return [ - os.path.dirname("{}/../themes/".format(os.path.dirname(os.path.realpath(__file__)))), - os.path.dirname(os.path.expanduser("~/.config/bumblebee-status/themes/")), - ] - -def themes(): - themes = {} - - for path in theme_path(): - for filename in glob.iglob("{}/*.json".format(path)): - if "test" not in filename: - themes[os.path.basename(filename).replace(".json", "")] = 1 - result = list(themes.keys()) - result.sort() - return result - -class Theme(object): - """Represents a collection of icons and colors""" - def __init__(self, name, iconset="auto"): - self._widget = None - self._cycle_idx = 0 - self._cycle = {} - self._prevbg = None - self._colorset = {} - self._iconset = iconset - - self.load_symbols() - - data = self.load(name) - if not data: - raise bumblebee.error.ThemeLoadError("no such theme") - self._init(data) - - def load_symbols(self): - self._symbols = {} - path = os.path.expanduser("~/.config/bumblebee-status/") - try: - os.makedirs(path) - except Exception: - pass - try: - if os.path.exists("{}/symbols.json".format(path)): - data = json.load(io.open("{}/symbols.json".format(path))) - self._symbols = {} - for icon in data["icons"]: - code = int(icon["unicode"], 16) - try: - code = unichr(code) - except Exception: - code = chr(code) - self._symbols["${{{}}}".format(icon["id"])] = code - self._symbols["${{{}}}".format(icon["name"])] = code - except Exception as e: - logging.error("failed to load symbols: {}".format(str(e))) - - def _init(self, data): - """Initialize theme from data structure""" - self._theme = data - if self._iconset != "auto": - self._merge(data, self._load_icons(self._iconset)) - else: - for iconset in data.get("icons", []): - self._merge(data, self._load_icons(iconset)) - for colorset in data.get("colors", []): - self._merge(self._colorset, self._load_colors(colorset)) - self._defaults = data.get("defaults", {}) - self._cycles = self._theme.get("cycle", []) - self.reset() - - 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 icon(self, widget): - icon = self._get(widget, "icon", None) - if icon is None: - return self._get(widget, "prefix", None) - - def get(self, widget, attribute, default_value=""): - return self._get(widget, attribute, default_value) - - 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 prefix_fg(self, widget): - """Return the foreground color for the prefix""" - return self._get(widget, "prefixfg", None) - - def prefix_bg(self, widget): - """Return the background color for the prefix""" - return self._get(widget, "prefixbg", None) - - def suffix_fg(self, widget): - """Return the foreground color for the suffix""" - return self._get(widget, "suffixfg", None) - - def suffix_bg(self, widget): - """Return the background color for the suffix""" - return self._get(widget, "suffixbg", None) - - def symbol(self, widget, name, default=None): - return self._get(widget, name, 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_wal_colors(self): - walfile = os.path.expanduser("~/.cache/wal/colors.json") - result = {} - with io.open(walfile) as data: - colors = json.load(data) - for field in ["special", "colors"]: - for key in colors[field]: - result[key] = colors[field][key] - return result - - def _load_colors(self, name): - """Load colors for a theme""" - try: - if name == "wal": - return self._load_wal_colors() - except Exception as e: - logging.error("failed to load colors: {}".format(str(e))) - - def _load_icons(self, name): - """Load icons for a theme""" - result = {} - for path in theme_path(): - self._merge(result, self.load(name, path="{}/icons/".format(path))) - - return self._replace_symbols(result) - - def _replace_symbols(self, data): - rep = json.dumps(data) - tokens = re.findall(r"\${[^}]+}", rep) - for token in tokens: - rep = rep.replace(token, self._symbols[token]) - return json.loads(rep) - - def load(self, name, path=theme_path()): - """Load and parse a theme file""" - result = None - - full_name = os.path.expanduser(name) - if os.path.isfile(full_name): - path = os.path.dirname(full_name) - name = os.path.basename(full_name) - name,_,_ = name.rpartition(".json") - return self.load(name, path) - - if not isinstance(path, list): - path = [path] - for p in path: - themefile = "{}/{}.json".format(p, name) - - if os.path.isfile(themefile): - try: - with io.open(themefile, encoding="utf-8") as data: - if result is None: - result = json.load(data) - else: - self._merge(result, json.load(data)) - except ValueError as exception: - raise bumblebee.error.ThemeLoadError("JSON error: {}".format(exception)) - - return result - - def _get(self, widget, name, default=None): - """Return the config value 'name' for 'widget'""" - - if not self._widget: - self._widget = widget - - if name in bumblebee.util.aslist(self._widget.get("theme.exclude", "")): - return None - - 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: - if state: - 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] - - mod = widget.get_module() - if mod and not mod.parameter("is-unittest"): - value = widget.get_module().parameter("theme.{}".format(name), value) - - if isinstance(value, list) or isinstance(value, dict): - return value - return self._colorset.get(value, 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: - if not key in target: - target[key] = copy.deepcopy(value) - return target - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee/util.py b/bumblebee/util.py deleted file mode 100644 index 5f4ae44..0000000 --- a/bumblebee/util.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*- - -import shlex -import logging -import subprocess - -try: - from exceptions import RuntimeError -except ImportError: - # Python3 doesn't require this anymore - pass - - -def asbool(val): - if val is None: - return False - if isinstance(val, bool): - return val - val = str(val).strip().lower() - return val in ("t", "true", "y", "yes", "on", "1") - -def aslist(val): - if val is None: - return [] - if isinstance(val, list): - return val - return str(val).replace(' ', '').split(',') - -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", "ignore") - else: - rv = out - - logging.info(u"command returned '{}'".format("" if not rv else rv)) - return rv - -def bytefmt(num, fmt="{:.2f}"): - """ - format a value of bytes to a more human readable pattern - example: 15 * 1024 becomes 15KiB - - Args: - - num (int): bytes - - fmt (string): format - - Return: string - """ - for unit in ["", "Ki", "Mi", "Gi"]: - if num < 1024.0: - return "{}{}B".format(fmt, unit).format(num) - num /= 1024.0 - return "{}GiB".format(fmt).format(num*1024.0) - -def durationfmt(duration, shorten=False, suffix=False): - duration = int(duration) - minutes, seconds = divmod(duration, 60) - hours, minutes = divmod(minutes, 60) - suf = "m" - res = "{:02d}:{:02d}".format(minutes, seconds) - if hours > 0: - if shorten: - res = "{:02d}:{:02d}".format(hours, minutes) - else: - res = "{:02d}:{}".format(hours, res) - suf = "h" - - return "{}{}".format(res, suf if suffix else "") - -def which(program): - import os - def is_exe(fpath): - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - - fpath, fname = os.path.split(program) - if fpath: - if is_exe(program): - return program - else: - localPATH = os.environ["PATH"].split(os.pathsep) - localPATH += ["/sbin", "/usr/sbin/", "/usr/local/sbin"] - for path in localPATH: - exe_file = os.path.join(path, program) - if is_exe(exe_file): - return exe_file - - return None - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/core/output.py b/core/output.py new file mode 100644 index 0000000..c0355c9 --- /dev/null +++ b/core/output.py @@ -0,0 +1,16 @@ +import json + +class i3(object): + def start(self): + return '{}\n'.format(json.dumps({ 'version': 1, 'click_events': True })) + + def stop(self): + return ']\n' + + def begin_status_line(self): + return '[' + + def end_status_line(self): + return '],\n' + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/runlint.sh b/runlint.sh deleted file mode 100755 index 6902ce9..0000000 --- a/runlint.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -find . -name "*.py"|xargs pylint --disable=R0903,R0201,C0330 diff --git a/runtests.sh b/runtests.sh deleted file mode 100755 index 549eb4a..0000000 --- a/runtests.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -echo "testing with $(python2 -V 2>&1)" -python2 $(which nosetests) -v --with-coverage --cover-erase tests/ - -if test $? -eq 0 ; then - echo - - echo "testing with $(python3 -V 2>&1)" - python3 $(which nosetests) -v --with-coverage --cover-erase tests/ -fi diff --git a/screenshots/amixer.png b/screenshots/amixer.png deleted file mode 100644 index 0da57c509dc21c9491caabf7481508b4e9cb9adb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1645 zcmV-z29o)SP)X0ssI2|AFK?00003b3#c}2nYz< z;ZNWI000nlMObuGZ)S9NVRB^vQ)qQ`bY*g5g3t*700s$3L_t(o!{u06OjBtT{_pL+ z(UrDzgO){FimZx)EW$7Yii|R1fH)JQW12#wep= za6wsQk)0L_2*@T3Sm{ES+uq)`^H9WEYD?y6kn{Ba=Y03v`~Cm<&pH2vK+dq6cg=>iP4lZ4q@JRTP#5NvglI2dIzbz#`TFr1s5WOF#TG*uXK%VhPaD*^!U z&Pav;*sDquaM)X#Dg=2Yk_I|$c8&SFaOpeZq=Lw>?np#D1VO&>x#|y(6lM?!`2P)6 zAQCn)BgWr^G~kOj;?#<0WwgvUHd?G~20f^|C?4zl$@2MS!z+^o2Sd8)#VmE~{#~k` zk>_rYXCq!Fzp9(;v)DE6Yn& zDMXrQb6zhGxzjRm>TrG(kL5GbDde5;vOjJ= z`R3R96|?p1u@7)K*!PN*1nI@gjZ_jTO&Np3!Q}<%Hy-zTz22~&$K?zP`0asbIAAUn zL}n(&hwvg1a1vy~o$=DT<0T9#c`bFraD8*X4RM}1T6pzdXHu+~&t(QY7#+n0UeMY% zhPtr+v6%(EF;=#g?X8I8`>XdS=9UbrwmAF9@HCFDI`L?Ob7sA(8vN##KN*6Shav*81{Mq0N@|JNz7+c$V8c#w_>uC z=A_(w+!vNAjXo!pG`vnGZ#(xtw|i8HbSfnP5pZtH^3nnTy(7lfwtZMQCb78DY};;X>bjg|6G zl;{3%?ZJ1K?p*r2rTRd&I_|A#|M{<`x(k;Z+6HT@izDb%f77A7)b@c1!>VmFY+V>_ zdZrS?Xkj850DxlHve6VR5a3`qXe2-C(KU8x9(C&!Qa+bS_eJu+_^i$0JX2ZNubX+H zHy+4IxzgB)VQ;NSW%2Hb*`@B`DFDFoikU_xspG^wBhURYZjWbrQLmJX2n5{pf_^>u ze_W99TYdAPywsY~9J!dMoqXZ(gi6b9H`!*3X0b*g$Q`Bv87jiceM}*f{K*_H%)Hm3 ziQ+NUani4Uxq*jq**oLI?l9uu&&*bMy|_>n$R{t2=8oa9&}Gd>xNmgY7hn&&hRs&{ zmnX`G#^;)QM>pc14o~e%+mV$V%Vy9rRf_$Y$}Y{6&y=ZB)K(SC#r#M%owu(jb2z$>$PtKa6mUSPM2 rL*4k^OW9s*q3!jDl|-X&AGZGj5@Bj4aW{yP00000NkvXXu0mjf(k>WM diff --git a/screenshots/battery.png b/screenshots/battery.png deleted file mode 100644 index bdbafee42fd9ce6df799d0cd95888d09fe707a75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1733 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 FV1f^&$CLm7 diff --git a/screenshots/brightness.png b/screenshots/brightness.png deleted file mode 100644 index 298abf3942f365bf6bf093fb568e2350274136a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1323 zcmV+`1=RY9P)Ag^4S000ocp%GHl)o>Uw*zE@Zk4iyK=C9+TqA03*q)AGk{_oRB#LhP_ zz}}}II|~>CU{tHKyWm{|(WulHDoXY(Cjr9R8kh1iJ=-fgET-lbO?D@)KY@^!nMNQG zAP7>Pk)M-Smlfs%0B97-@l@Xa;e;U2A~T8wF5I&^yKG!ATVjMrOrXB^`Z<4a!)S99 z=42rVqHL6x7v>AIGfg(<=hwUN9LXfo=~78!<*BX#)ueVV9@UFA6@siZw=d9fcK}7v zQX#*oru@_PP7K3#MXj%}NQ)e}ZE4YBoz)xS`DQ4Jsx|XB`UZXe0FTWq7ZnubWW})O zBp-QKsc^z>2#QyAEQKu*u}HT=og*cOh!_vkWZzMBOPIn_mlR{FMqn@ z4{pS%I?85x`~d&}?DigIvuaDkF1JUkU)hyYEO52T4YSBjzs3A}zIx*q^Yf=2#jwrLR`deFQLV1Q>D&|=g_4!Z8=9I?$f~~T z>fMzyH`DWK<5FbpY-e>=Z$MErc9ij1oyX^Q!fw6Q7S&)efCu~~l`OlJIM`Aj}6L?Uj56951;;zBx&7CVYB5WGD&VqIIuGjO;b2RAow z_Wu#jaf(LTcKAeGZtAse;bw@4i~6L& z))h$zgy;`|N$vd41H*c&?X@O36mjsAXbCNKQnhxzy}N%McJCFcLRR(rs5+I)5$9!p z{LPOig$k_V@$Wdhb14SPq55d&0@jOiSgkRMQ#&Krj?S5X9~E5%AjU^P;yLx%C@=N7LAO z-Ou21PYJ_n-F%Ed#re6yZOVzAm)Rvi~# zX$Bb9qZ*6TU9-fx^JliAetX73%1a+avC|v<8ym|JbA>`8HP=aJ7WHPk^TRjWcx=|t zgP9)Hm?$@!#Ynn8IlbR5{q+@A=8?^^LMH%#D3;blTJTs*Duo=0ilC^@V1hkf1VNY0 zs~J3Q$K8R@R(N&Ig*R{iTQOQ0{vf$b{@Arvd7+6`QM}Nc} hU^zsn(Bb(x`VRs*2Jffwcy<5)002ovPDHLkV1oRXbDIDF diff --git a/screenshots/caffeine.png b/screenshots/caffeine.png deleted file mode 100644 index 54447779afbf96b58344d131d7c390685fd770b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1143 zcmV--1c>{IP)6c$KL}; zVOS9n4u}XiRHV{rp@wQZjWsr|-I8V5VzMpEk|n!Xvb!bQUGI0hCEG66rA?D&YqQkV z8SU&O6q&1E6Qod)4>=q~D2RL&1*gPXn3J|W@cdEWD$^Lx+xf1c-s#yp12`r&c5$F^K?Bd7F~|bhY|ax191jKtT@J%lkn;Jw(Xz&2ojCeDG#c%K=2Rk; z{?}7J2nWDyvj@VFqt62Ya89j)8+cVV-jc)V3k1n#8PuNU*K^6Du|(49bfcj#^{X&z zY9Od|U;LTP{a2(j9ZD%IsG=pS%e#*FDP|ebYfo}spcruZeD95)XBKQv%0n$8;b@<( zMI@}N<&;N5yU`6~Yr;4miNz1X)72_#YnD_=4^BU7b$kBzHhA#+vSHcs(VMTmH8Qa5 z*vRFIR(HA~XC+@Fp zc&VSIlr(iIMO z-G_d4`<}y#y!YxQ2!e9C9I_J{H<+)DPrdWX#fqPC-=wfWr)ncj7=^z;WY=u7KNHDH z1bhI%UIHUJhkJV_jPp;UdqsH&$M4S033>H0v2fpM_xRSFp8l8AWb?zk`g3X(RSx`H zLT*n!pFbo_yR7+x*GF~g_LV2DI97xt7YiMm6(pL?WW zQkhH@Bmh7yn?2C2sbSEmk?zbGcfz|^CR6lFr|ll!!xbypUDLlUj`p1uHq;k?4d{)b z^Jpl%NhpIFrMyEXHIYvR0ElClVZ}mL$DooWjQKQNRrT@ z6zoEG*Oiy`_ve@F?#*}-lfaF`y*(T@`5G2FW>g`{Q<^HC|hD&|A7Ll;A ztpCX4`1aF?3qdDMkMr|ja=?=Imw2i(E}kJQ+p1P{sQQ|s$03vvpfI*002ov JPDHLkV1iwhDMA1M diff --git a/screenshots/cmus.png b/screenshots/cmus.png deleted file mode 100644 index 52c0b5159e2d2eb8dece597c177b523109319651..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5031 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 M{86v z$))REJztsgvIN$JCI9v$n22nCHtU2@-+zBJf*?t_vbgMeed!-Q z_*h@8L=a@j<@w>M=RWDVyX=NBRhDOqbOrg*uWql8mCi_(I&6+*)5J99b&Dk8^KZ1C zFzO{j{=Jd$-@JDb_WIOv>FdpnKYRI9CL?XfsB*O;91gel4SCo69RsFx2K}%`f$8Eh zGxb{KpW3cl?G6|0{iyS{(}Vrwm0IQO{NnAeAGp1~Yd8DC;f*p?QF1N;o4LB?2LQNX zuYk=m98|mDRm<#SOs*UOuS`*ptU|_pcep)+mI=)3D)MsPIDKq%#=5!UP!vR0*0jyO z9t!{b`==Q+T4G?SLOwV$u{BAn2`JFYrI;?IEZ4f=h|MH;AmA_;oUU&RJBm)s*`y+2 za;^oZJDbO)k;%D2{tCR>P*M8nok46O7E`ZOV6-Cfsd?6lAV~6wV!_!*Abw_3hTvDwzb2(1Boxl}?XsRnzIT(^wQ|Rq7z<;mmQJI}b3`vU9%-m3-8A|Ffj?gP3_;OzC+c5oZX^&0 z#RW1kpBop;pi$S?Lt+8%WNqbzwlD2Y7mY%}bkV3}U12^Eg5JCK#lY~`>n9s`tn6_x zS*ElFvLcBX0HBfQRTZm9TXH1f>iO6G^MAh7(LZF+soML7@VwFfQHw7S*xSyKN+#DH z)O4H7!BA+o7;S-Uv!3X+%BXQ-)+QHcK?FjfL>xQv$&xcRc0C*x@ij{TBf6t$$Qv>GiWrUPSf*^IlQrRyT)T?UTp7jxZJ;J zJri9rFlyyI%T^ad2eZWSIg0a5B=Dx5`=)m)Gy7 zQl37;g43lfkm8za!HP0+1G}MC+EU?e_-FhRR&1$MD&k^$Onny8R!&47K z;c#nH9haH;j5JqYtQ>q`iM?!nMd|N;{3@538Eed7&`%zz5^`ACeD1ju4T~$T$t|Ii zaLIE-LJq6XJO%&=ghG)>1orx%opeaVk{ccvo!D_}v-5`K+Z(0RsbsRo^QONCghoZ5`vFi7)he9GQCvL++X!+?clShut&aDN5kL-(4 z3yn-Z|K=-n8Z~*~@bse#pLG0xo_MIF5;~PC+iKf!2@L>n!f?0A9BVm1B9e$0N383i z5Q5`;;C~g|%*>-@x;-5HZ^pz~!W)s~RX7DF4C6f}P2oTJYd}hZ{rW*5^>>4P0f|Cq lq*U0C86EN4QY!2N{tbN4*5EiO503x<002ovPDHLkV1oAH?MDCr diff --git a/screenshots/currency.png b/screenshots/currency.png deleted file mode 100644 index ffcc549fa7fb2f085b15c1ee429a1bdfbb93c4a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2561 zcmV+c3jXzpP)^=LQY8bk6;WMB?ste^PBvUyzlqU`@BD%?|FaE^L`%) z5V{)JV8fdQMA28qJ2sxPxw{RoC3dV$LfTlc;mrg5Y?E9KmG!&WsIlSo^JWpa5?&$%wYj<9HTv-OP&l8{${RGJ;_k z?iu1;y;~`k%AUP!ds@<{K$u@%t8eijxnw2A2KrEDV#6*kix=;JuGeo{`Gr%S|r;;Lxruw`v-?1|FF>mj&xWAP{LRWv3yar8tvs+TNJ$PH%q_ zlW|~M#-~60TRl5#IDB->GXH8l5PttcM^dBPnLl zi7Z=}R4)I=)$6^FhNmRbpG&Lo_E;ZxH*1)et5iasrSgzpkcfwPy3K)oQ>Chq(|OBZ6op_nP%B7&~;ZpLcaj`;#PfG5G-i&}g+vmD*hM znDl`9wl3ZGoU|kWz{(Z=dc*X4pH{2w85;JZxEqe$nvziVphY+>vEB@eVFA4>s9wA= zetX}5Myr{e5?{RiOF>25GSO=YJ2VOjAr+6qlQqoGdw)b|eOuR}zCm_&iVdL~mv9$b zJ!ds$Z>#TRzD06&dM9(;@gw_)1iaB}*0PxGeFNh{kzP`N{fFP9VED^}zx(cJ?srFX zzd8K=@})Z-B;tfn1OSLz8A|i=xKdK-?mC|hi)Um62b|%pg~ws_KSTU{NJOW~rnXm@ z09imU0HA+l+~3oE_2c-beJS@Lk$~U5VQufw@R&eoCG+#j%FWe>OsZ?`SX@&3`67|V z&lBNeiM=1*3K^5ynYEZRg~chWB0~JBCodNmy#Q~IjR~Wl$jjHajPfVX7T$?l8S=a# zOA8Ddtx&4G-AS2=F()qNLr5eRjn;KN=-?jSy)}sH)iX5A2=HSw!}^EEbX{l^Dmyjt zOu=o91^d_1!2)^#0E46B7jBhpU7LWzVxmHVM3Nc7lz3U~+`la&#Lq{rRG!Go*FI*< zGUoM&VFYsrc>?kDqO;euccw;1IAF0x+}atd{w%HQ!&@O!3iAS|U`p)roInBqSQ8!b zaCmHRbX?!!?n>N~o{B^ujvv{l+x!0d4IXz#xa5L^V1jq^iIyPZ%>;d zsjbA=q%bEfsiw7~sjCM7;Oa!+8-%W!*3SC2E;}?zrcnN7W2!EyArXkAsBpDE#D+bLrJXJv*mVt8Y}+V=;E`W~_VA*{xbwH^S=g%cUrt9o*nr~xzE&1`697+S{lO$t~Eh9S&0_zLvOO%jjDQm!LFlkfwglQjZC3L!4RG5b?#=V zZUgz`hx1Po$K-@r&0Il6-766!7SM~evs0*4dNI()8-*bVhM{G0YZMHrXXobYeOL?{ zhAn1ZkLcjQu0ft$X~we~B8jw$)3zoe+)!sf`Nkkmf0Wp3O7RjxBG<)4G6MaQqgNi@ zwS~=Oc(@YTOolH1gCQt6D!in=(dgK#rBz!~;@M0F-X81WN=#?75LVZL(^!$8yuZhl zu;9$UmeV1YN^{cIxDlPx*(?&#Nne3+bii#&jwchH{X9MPXJ?E~iu5j`uQ3V564mUi zQl&OLN_aW2nBL5!n7vtVyAhoL0C;=*?P*D!4~Jw5<#Ny)6&#qGlSTC)lbi{gljA8Q zmu7CCHO%Xl79K=v?&>wy!U>VMw!KSh5PcRr>J;ZA%4H3ltY4&ttp$KWay?y8JT4UZ zkV&jCdR9_w&k(P8Qf!)R1A z44bY%48h~`8@X^aeTD9)k=F?FIFt#g-JM>NWh{jF`5+;vy|*9O@E8^xo?Aojp

U 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 LNkvXXu0mjfW)Onx diff --git a/screenshots/datetimetz.gif b/screenshots/datetimetz.gif deleted file mode 100644 index ae926ebac904faceb8c55bb1237927c4c93f3b6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111595 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 diff --git a/screenshots/disk.png b/screenshots/disk.png deleted file mode 100644 index 7a659b8fcdfeb1468d05e49f29f1c98fe3098c53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3721 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 diff --git a/screenshots/dnf.png b/screenshots/dnf.png deleted file mode 100644 index 49924168f31d1a081be9a94ab4063993d73d31ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1113 zcmV-f1g86mP)*HlGt&bp1w4y`s&)?Z*BoZ%eLrdRf6Mp1HxTG@04|M= z%mP7l`s~YB?n&6=U>RYzCI!DZ#pa_#ObuUniDhBICQgYM8z5?EaHZXt5j;Blc!VHu z(G}|MNU_5e0-Ahe^1eV3d1dhQdbcI@uY6NF10!`6nUvryRjZF~N7#8X#tHwjV0hxy z0RRfylG~H*x({$o!#3MTXJvP$&7p|GXvklFd-bTYNEgH*wX-L7 z#u;W|z8$x2B#<50-kIFFdTgm2E&~8OoHsmP)K^mk34J$VZPDgG{b;F0o4OUvk~uK3RV#Lvel0G_WlIC}Q29#{r`7HGUN{FJv;P2=&5BPd`6 zbrzT2{IT=;kWYb>nSimW);ZQyWAv*x0019f_P4d{t372hGMYrDo-})0A-x(8hL6ak zqlT#GKxTk19POlk}MX3SPBG4{iIXIDo@X5T*_VA=wGeq(lYWcGlG0013| z-@SkAcpGN7B>@0f7DiS7R29XNcxFlGIu+q9_1bk$B#zv=+d;6Ve7zc{eczDEMAJ~h)EiPby*PK6Z$dc3H2^=6NvYPy32xzMF%@UB%+SDVO~G_({DzKg07tl z>=thGK5Zmc?->YI_YF@dCQZ-0at~rZe{?${N0r6C6Y&Gv!3JfKb~jxipos^q&Xb!4 zhKWzB<&cUbq0X8o(SJ{IonW fP%Zzt1aaywgMS}|9}1IZ00000NkvXXu0mjfah(r0 diff --git a/screenshots/dunst.png b/screenshots/dunst.png deleted file mode 100644 index 1932b4d7dd3744cef3bdc34b3345bc3132292938..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 657 zcmV;C0&e|@P)X0ssI2%)0wB00003b3#c}2nYz< z;ZNWI000?uMObuGZ)S9NVRB^vXKrt8Wi4}Ka%E+1b7*gL?*qR+0006cNklJYVM>SB27E7hHwo(Bg>g~yeB2^N? z&*ytx!HC#Qq$3po4n#eKnvZf$;1*RI<46U7{(fpr0|R|_Fd{Y*xnHB0niwKf`b_VD z8Dn%Wq{fA_vxHhbL#h8@fu9L{*E<6MW~WP)qZ3rZOzGInm>moNKIe{XA^f$K1#8;9 z+Y4bfz3umIug`fl@U0aetlk(O9kiy64i(pHOQ}-I`nczXB4{R(@Y^@1AL`}^Ic zk)lCtou7Im{`}>8Ya9VzZ)~PkXzSGmk!J6XwdLy~8(gkd7-KJ9zN^Y>78Dvnjej2vsHJis3+EU>POdmbzj~4)xGcC_by5T+*m+> z0D)%=5J3R}0z~8!AV7eKd;$ar{1O4fWFqnj5O~H`dkESZ&LtwB0D)%+BmfdZgIyf# ziO45F;2A~qy(RS@V4Q%esNJOmDSvAX%KcAKVwCwv23hGv*L|bmlwc! zE-BX8fi*EJz9?wy9v;QIQli3lY}sthp!bi8N^9$9=cQPepQl@XdWy3HYiNA3__APH zEHOt42NW1ii3;BtA6;5gf2*^{9FdP+JLu-*VB9i3J$v-qVtfu?*_{Ey@Ym<79wCP~ zCgafS<-*QS-|Fe&JSUY^2pWWaLuM^3B+{duyEWlQ%nhPFWQ-;Ykt003;P7-4=srz&gbq_T{-%}4UGjusRx zA_xG0X>E06-|ou#8?C*A>2Xo-9mx61SEsdlJ-!qU9PMo1JCHLYk&;N3>3`}{OpvhKD}B3G2w*6Vco7;cbBDI7>-@{bFOK0A9ssa~*1f304x)@ZOxqt!ir z4slGzp?8z3Gm4`1E$v#JuDQEkG&|?(>1Og>PAt0wUg#HPmxe`CVyV2ax(>@PV#9)U zI^D(k8*-(p>UtB?#yT!M_~EU4;(n;sYQ<8y;o`Wts1`JB{iY)yM+(P#v8g;OIj-;S zxS7g(xv*n`mjy3uzU6wo9=W$LzLb?7%MAhmg!ywa;-dioe?0MxLZvqJvhD41QWFAw zyvPtVG&WH#XfRBT4+aE9r;Jh^nNmfgtf4lUOom7h3Q)tXiz5qLolq3*9vS7hyIybY zz?Z^TV@1$DB=9)|07M4*W~aonY?)1+y~RKNjF`$w%)|H*b6Lm8KR?7q&TryV$c5LRZeP=0<)*pdT;L_e4>-;r4W8?U|~dJ~*`RW=FT6 zxg7w2DeYj#eE(N@t-XW)JXxyK>5?~xy|O#wuU{9U=#s1PlVVe&dB;wFzvk8jA2cc@ zBEa|TWxw`WO>AUh>~gE)L5e(2%sw8@42ZF#jtqpg<0 z_uf#>Cbk3X)TNpipWANM_MYPdL6BvU|LzMp2!hb46e@*s>FUiP(Ihqxp(skF!V2}m zis>ygK6+wKQuJe;;kH81c(u75qZ6ewaT; zt<@ecJdZUtb@u-D;GQ6F&z_O7aDShq2=2hB$fVRp;N`50m-suDR?&Ic(SjnA92$-T zzAURm5M)X$Y3UvG^>jCm{0HL@-{dvvotT|dYcyOxA3LVCnGwREQ^Wl^4Z^O^ODlZb zU0&a_>ytBOqf;{g02>BfqtSZ0u=k`T9xpscf*=Ns_AuN&RelHnhz}3x85x_E$}Adi zwG@u`QYn;u=_%io|A3&Vnd4t;?@W)0v|%tTi2Q;J)f2N~2!edwUGsJ%i>0!g9rw?) zsW7b5uRNaNc(~o}>RYx$mM`0bUA}SW@{K!|(oKcoH*@(AnfyiRMRd8%UzSx;DU?KB z$UFJjAAegcQz!udL!!yj8o?WT`49w6&52uj24EOADYX$eUw5r*=omh4c&moSGl#o{dnOy(X5yk=zBPOr$VL17VveeLL{hXWUQn{ z005YrllZu~CP#7$tLp#&8lBFa?Z{7#EvXTV&&>IGxodTgU<37(_#s3hMFsm`__@KN z0oRnm@!s8AH-Kyc590C9kWO<{d6@~SV8`m;?2QqgHtqpU&93*apMo)PcQNNs5AYU=C-065w*XUwao;^Qgt!$5CO zYdX!$!ly~8HN6)iky4|1I-Nc-l53cT0E$Ki`+xZ9fALcQgulc` zss#<5N+BC<7$69uQz)yCKqCYF$Yk;(WIStY9MjtBKj*4e(~VgI0I;Id7MCxoi;Ig& z^#V*L2m5U5GAdK!OFd4k$>Bx-$K>_srNA)B=yNY32-OT#`$s2&yuC0#aj>-!-#d73 z=^X$7VC!2%pkK$p$OduvnEem{5EBy6Ju;@&Xe=5NM{2eAA}IQ&uTK4~pzvrx(b0mU zj{f1+zQLmfMdoDw61k#Y*oGyNkjdGUMx`)lG+z(5-0jINeS^l;*U33?awOM-?HC#4 zmzfY_X#BD9junIU_P*T#UhZD5?Cg|;!#SD8WA2QP{_|TeuSj2U;Kp`Li-|Z>SxX`T z8kIt$Qmp7po5SdkfZyb2bKG5>Say5U64?$c;k~+ut&R1bZAq?<_8!hoho8@!oD=ul zi+miZ2h-4Ge#oHFu!}}r=A&zJXcCrch+{Giy|+4gk|MdW+#q|VO>{^=YBcXgd$&n7 zT6+hyI^7}u4kwmf2xn75L?~8sZV+}#6^a*jraC*=^OIt|T-jG!+Ra>SDKHtkR>boY z_d~h&6}&YZu7GGJOFz24q!$k|P#L+>BTGt52n z3T1P5f7SISe%U006@>kSg?=Ro4I@n43ADtpC~Gq$d*_@5yaM- z@w2N8w04n1&`RPs`S#@rh5)l;Lw|5|(O+V=@lCO`W}DGC3nI>Of}N z+`O!$roOSQ%cL5M2=ZC+h5b8IKYHzeL?$n+t{WVmxOb?wc;alui@Ub{;nn>kqNx+b z<*Eg+j7@RX?V)fzq}eh2^v^Yf;{@Or8Kdhc3K3degN5@Rznw|n52;6M`bfASC@ z@T`H$@g^dl0D)%_#J|-eK!AvR0t5&UkxzgC0V47V5Fqf&!T$j?lBE`-x#f`n0000< KMNUMnLSTZBH{Rj^ diff --git a/screenshots/git.png b/screenshots/git.png deleted file mode 100644 index 69f786b0cc481affb1984aeda19d8348fab64503..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6044 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 BY_R|U diff --git a/screenshots/kernel.png b/screenshots/kernel.png deleted file mode 100644 index 9d20cdc9aed5a357b288e68226ae9d794fe36d40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3125 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 P00000NkvXXu0mjfu95r# diff --git a/screenshots/layout.png b/screenshots/layout.png deleted file mode 100644 index d09250e9638fc5df618c340e912420423ff36e72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1198 zcmV;f1X25mP)X0ssI23CLli00003b3#c}2nYz< z;ZNWI000nlMObuGZ)S9NVRB^vQ)qQ`bY*g5g3t*700c@&L_t(o!|hmIOj~6b{?0i) zr>FFX_LTmtg~4c9S2|$8V91b(f@pSuS(YqDO^n_d6K^n^H(r?C7`-tu(HLV4I=SRx z1C-6ND40<@p`}27p$nx@T25){?>X%`UUY7wEg-N2h<=yP_rBkG-uJxU^PcxWz|ahi z?1BbA-NE8w-{B1p21j{uw%JS2$moLfNV2Ch(Di#&a(?PCvoBmZ3=4feu9;dEt6De ztCTB2@*vswThxPg`8Ga0F1NML&{}7(I2Vd=?B_pB59XZvAyI^L#R9%>_hIL| z;;V^@ZLGV_H4Kd#1EFvZt|}o^CB&=eo5=767ekFR_Fa+S^YG<8)!P&H<`vOrV;Xkb ze+N zZr^{9-FoX{XR|>E0648Z(bduj0Ql(3@1pU0DvEde!Ve#xA)D<#u?jh zlU4u#!!UbM%xmo~rC?`umrKNJ>k*2g$EU1Wgi5EaZqKC`+h!LQ3%aBzdSG7~`6Y1UC)dHf%JV;P3QFitd)WEf^w)Hmf`!lSQu*?Nbo*OTZr4oiVGIU%7l8AD}- z)$IWQm}cxO3vT{?U&JqZxuv0CE4a{DTYpNcluBR-hGE$La8sNZheM|``&GOg#j(;;SluS|iE|FMtOvv_W1mVXgYx)pqEASaf+sMYUBUO@W00%I?{hcj&|<<21H-Oxm^CSv;Y7A M07*qoM6N<$g3G@`@Bjb+ diff --git a/screenshots/load.png b/screenshots/load.png deleted file mode 100644 index e136e7e9e1f0e88f5d84e7f4fc2a1b98686f19d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2005 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 diff --git a/screenshots/mpd.png b/screenshots/mpd.png deleted file mode 100644 index a332b001958226a5b94e2ecd73ef5eaac2651c87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6310 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 Y2#kgTapC}kKs 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 diff --git a/screenshots/nic.png b/screenshots/nic.png deleted file mode 100644 index d1512c0772b4b3bc2f09f274af9a215af3b366ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2474 zcmV;b303xqP)X0ssI2ND$sx00003b3#c}2nYz< z;ZNWI000nlMObuGZ)S9NVRB^vQ)qQ`bY*g5g3t*700~A(L_t(|+U;9wP*Z0b{?5t$ zN-pF=Aaad>fFK}(h+suPTM^wZXx(*f-R*Q{-Py6T({*=$bY}l_W@oxH+t$^+^kTPL zt1zn~R76w+xkT;+!X-dRVnRqDAtVqIAZLHXVv2GSLN}#5=b7{4e3$o}=X>&=?|aYt zoj@QV17N_giGjx?z_8hHe1EQI5ylk3fX$;AF!=0_cwB@)fcYqfO$r!>bCMF~796YV zF=;St20#F0ZHp6d*c)2wAqWx+cspXFVZ9RyN#w!o3_gc7F=eW1XdN8a_&ACag?Uk-A>%szmv_@Q1e5iJ(I zeey>l9v1*$G+Sz=op)Q>Yo%TC(Q&iYCg8A1L_$P}AT?gnJ3O}Fbo%~lX^qo0&25kS z2lu5X*LU>T9X{E{h(k}kl3!8RSk=&qAjnS+zSOT&&D!SCt#B~>tK%gj8tvukIu4V5 zcyDHNkKBbI0d`Ps5c4;uUZs*rECx+Jrq&s!H^?xMRw&BTsnnO!lYf8V(|eD*+{cJU z=W@Bc|C*tq6iM_-(QLJ`8T9q}=oTUWY-urxNN~Aa*B>-}UR|fuPR!WmTrStdw0TJN zq_I;*r%<*C`BX9~F($HWXvDF&zS)#QCaudyXRJ1vVqB@yFE|&+b(3L29yf?Npj4w< zWhTXof;pF~>YR&`*qV}ckyW6Zb2~T zQbiRyLln3WWUYwEKZb#{LQ$SdA~_t+bv>>EHY@Af4*KGRl-C};3gyM}Te>OJp5%nW ztTY&gJ(C6DA%gG_!GT?=U)9#X|F3cYfPTv4X|C2AQsX7=c;(#5WAA->b<$*}QOHKq zjMrOdZBO{?wVS#r6OSGA%hF;BnN(5NxT+6{NJy4MUAx=hE(*EqH;M}{T&q-QbSwt_ z)%={)_?U)H832IKWgE;h0D!FR+a%#(<#i2*_hxQzQuKwP@9>XdAgxf8r;?3)*-}~e%yJK~7OEYN{a##dBJ|ctS51j7J=+zZoZm9;Aq8=XRS zIGkYuUP<2W53XE?V3CYKRty`p`gw8+ai68Jf2i;E&2_t2h<^124C zaXMNoa{3ZCLy+GL*Bz?*#W0XoXv(Xe{OR&F_j!+E>`|vIH#G^}M;DyV`Yu_1dJ36H zTr(#X7qN~UzEV4(o-mv{d2IXEC@Pr*0AMlb*(nKUN{Sl0`^;7V@IQLaHB0 z!}wfLFvm1&^(CP8^bZ^i0RUF4UQiK3-vAoh#Tm(U`KWO^Hh0p4wT_S$)?_NKi z8Ygi#O&Cq32XbX2h zqRC<%)#&!7CTR^*zz6RLW<48Z%$Csk1;?~y)?Iu#XBUG;{m1p&tA@v*QQaeNl?_}h zza{M(+_NLm-PAoi3IM2l*y1iGZ;kFBRe2Y2et}7&t`t2fp)>RY{xJ-s6^imO40}4I zFqmWUSY%LOw%QtdWhl?~y7xj=6O&)uzMmQ|Ie)s0&177zx@+;4m{jYH$y=k8Iz0eD zKBnH85Uce_xS;Jl#7WZPB`gL#Enf0U&aV4y-R_kogvZTGPyYAU_aF$6hy)Um;GWk3 z0LS)azfoKmAreqXr0@_yWN65UTI+6V>6Ke-wz7g;K8JlMJ3}nwJ?ifB%0V`wOo@#N zW(5HN(&8m?5n^8$`UkHJ3o_IG_|_>mneW94Mfv?ZQ_hwZ$3=+gR7zUBWKT+BV^^P# z12>p0&3%I}ly2FD`1wtVv~N%>r3Z4^OuErD^JUHb z&OwFmV}D{ab`0UuAet(A#K@jr4+@yKuRB6}n=-t*1uvxMB8UEmq5E)J* zlUMD9`v2V%67XF_4FC7&SE|Nz`i;h;fdjPZ6@@UT`tC7hu{~lqpVE_OC07*qoM6N<$f`yF3^Z)<= diff --git a/screenshots/pacman.png b/screenshots/pacman.png deleted file mode 100644 index 8f4169e3f1b7eeb12ff9a5947b4386ff071d929a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 860 zcmV-i1Ec(jP)q%SB`UCnqYCUSL&8;=pY7SZ=CAy(9LFNyT zLj(CG#|;eR^z4B*5{?pF4{np!v+sRBuif_E_w&8)_gIgfzW{}J|J4l6F%T$Ihzk0n zgM&jf6e{PE14)t=#|odzIp?|L^wALjR@Z7MhL!s$Czr)F_jV}qe_v237IF|aldV(w z$hqm;_~|d+l!%0n?-`owE~A0qyQyz6JW;T0d`Q0&jeVS+WvoR<)!ay}7q>s)^rFRw z0z=T#2g6&z@XJ?kw}RnkVJB3Sn`pYqiw&>r%8@ z9h5b8N5?t>npSyUjZ`e^>}WC0FEQ52qgryYUrA*LIlU-JlBMQSC6$D+H~^quqik!e zpD@kT%A|QUw|lit4|?+J#xyH`Tahc=}E?y8g diff --git a/screenshots/pasink.png b/screenshots/pasink.png deleted file mode 100644 index 2fd6359522b9984ea4e8df7aed4d5465f718163f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1486 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 Hu0mjfCfMGn diff --git a/screenshots/ping.png b/screenshots/ping.png deleted file mode 100644 index 1c8bf2bec8065882dc2306424b8bafe161b1f309..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1655 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 diff --git a/screenshots/sensors.png b/screenshots/sensors.png deleted file mode 100644 index 69d2554251d763a53b5154d8521e8a797c7952f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1054 zcmV+(1mXLMP)AdQqrU8-ZorRk6Fwl#pHS}4A^}2GKEmz3oA%c_agQbU}=$48=f=}iIHyt83thkksO$#V2rHuekv({&` zT5^ILf~0CK_g3Mfu+F(*jl0xV2x%S5(+i@MRjskEa%X+je92pS=<=Oc=_ojV;_&>T zJ1;vQyy}MI2C565mKnmZu@`iec}g6R+>Y(pilXSJk;t?6ed?Oc?fq@^=kzm<<()j* zL=S#VcS!S@|`KG+^-Cl|h*yys_Xh?8RWSm}}Ph{=rF5VzaC+UUBheb4Ehq7=h_e z9c?PI7R096hi`h?KYmd+QjCACv))7C@4cY297Q!>^rn2Wv940nl+Eir;gLvmn$1WA zNuG$$TzmL-Y&;%tmmJwupA;l|@SBQ}b*5Gn$m%azK&G{~kL5V6^e6(a4!Yp=Q4|gN zoo&6J=L^Flk<*v%j*dk|S?&stwsZ_^4f)mWw8VC?vRMN?UJ#l)XPd7TuTZe`001nmDU+@ui zD6(=MAN$GzhD~HOH7l8)M)!&e21^eB5T9jRdT2#aVAi^S$EMi?N8&gDpuWnRJuTGf z)_YfX^pE@|-?k}m{NQ%K%WgCfp@6e~gYRW~7-j)6vFQ2KZUyKxq|_5}=5_BtZG|5} z)K4a0$u~x0v*R&F6lJr?m~}M)PYIT%U+kGr-t_?h_V1{%n8?Z4%+>p?ANs#QdeFE~ z+7fGOu-*nBwJ5b*y54g;R)u`t;jtesd+|~_)j`*x#@eJH3X*j8%7fTU0{ka2$_a15geLPe*ocesCe%wjS|CgY+kga3!YM5O_pqx8&zkOoC+x#i?E zlN2SD-2(s!qQtOsipH$vZ(u?! zcT`(fp2zP~7ivfdiQYuF5Y+~Q3)mE6<6dGr-Z=40;!N4iY<5mI$?Vz9obewydrtOD z;?3DfVkfh4x8vThX$Fh|(@Zs8bOaJYy_fyt8ATC10mcPs-{v%6ciK`l$QYJbx!~V1qB7gZ9#Jk3JMAe%F6=HF(@b~C@3!rEa3RR?l~-X ze7ulJE^LZx}#6M3b$nST*;3x4FJGmFlqdlEo)Y;O^gfml@;uT9{K#M0EbXsJS}*6LpOq+SK?=ij+= z3l5N|*!wk&`?A+{4U7mPLNO3Vrc}M|*)0iFnk(YQ(f|NhEH;3{R;V=r zT=s#yEIb|;?927zu*(}-Fc^r#^i*lIuX~O!7x(ioZcZzo7n~5qv$xc=cCL<(U7N_q zV1BWLgTY|l$XVai+3V0lsGraNO<7)aT5GTHTw$5PWC8#{5VR>hWlj7F0v_MmCp>?z z++Z}q?Fo2%Zd%fYm5Ikro{`FxPG*e3VE*gFqn~|q%67!l1+go5A>UoS^XH>)U4QVX zvFq7nK03Uwy5(tQQ|m$x##}I{9uk>6IwUB>m)qVqsMPAXUQ9kO#9%ZE$3(Vf5Cnbs z$KOB@WV3wo-`}e=S|q!Z5rcg=Z{~0CVlvu#2hJ9j>J85h5f+2_hb`F=LH^Y(9k(iL zZSC2fjNKd8@B)1GMpI>D%l(>0RN4}y0RU2BBlFUdSq$3ngycd|`M7w>`G~F}Z8>|5 zOeA{JsegEL*C)rnFK=jJdonVU1vo7Bi_@3YTHU7fm4O`g$?JtU9L^RuY+~mOEKbBC ziJH^?FNgMo_;CRMdV_IXBE4Bz(>*u>0QmhI+oOU5003r-MJ$!wt7)k3=ynpI7IF~W z&%bl$n3%9&`%R@rd**Hl0gp|Jj+ot;LMF|&ytjMvPsNqpgCl#h*X_<;d#`Y(;LTkki4=!jPz?eWgUL!3Tz&8e002SI##M=; zscHBvIGNdS*nA%ByagjusxB2*XmomeBZ)vT7)_sjd&*{kFYS@+NDy$i_jYZ*U(?vs z-S^g(?46kz=kGs&-^yK`tkvuP<8ME{yK8e|bU2*X$wcA@2Y23oRR7)8yIu_1uXf~H z0aR4efJ$46w3V?@+cVQo-zgpvO{T@gytgy|*vWHdDLDzK4~`? zjeK56V`tBm(uaIr@V)AWN$E5IASpV0ctZT~JNt(x#HVf-+wY4MSdhdbiE<+LO6kMq zo_+!zpA;2-boZ8HC(ceQlmLJ`RgY`iI&oNRR8YX7%^M{$`H)ELBtXrN+xd6ym`=pw z?KgXeMgagF{X_7k^Xm)%KvGn=aCD;KX^&E)y;Axxl^;ze5C8x=z2Sdv6%9{_l^Shz zOGiYIAKYH9)_nfM`EM@YwmR?Ejx<3m+-H1bn3pGg!TY@ceA%pjfA`SFRf+aSGKru! z7*raq?aykpI5XIBz4dsn33YGe1d39=BG?DPk7s0;V=I(xz+1%D64DsQ>#;lb1 z{*m#@Cv6ssMLaD(S6DVQK7mSGqBNaBf3C2!b6{Ab)0fsgArT0E-YoZ!wrss7kt@D9 zeK{h?pGqdD@}nRKvRbWrgHdNN004r0IKFIFN^E3PS1_~(K_}LEBnnXSXK|wy8o}p0y!04nTJ~FItWSk!nTHpR`8IaElx%=p` zZTG5O5(lS#LMkXa3mS;|CGLb;IR#t`7k9}Z45{o3tiCB9yS{(sDN95G#^f=rz`=SDr zb6nlObLW_v)~ERyDTJB-tDd3J+_aR7Mdc$Ck|~AKY_Z&}suM(n!FT-}{viPX8l9fQ z^0aMED^wKH&-c?X5ESUcS)USrtrYP@nU~_)ruTMl8Iw%uji$a)k?rHdoD^2omDW9h zUmwcfs5cs)JHY?|e7sl(^EW(fYAc!P697Oa5-4QSCr1vj=(LfE$%{o5;}WS8I}$89 zwQFz$0FW3R4gfSHnq<*w&s03BJG?db*l*vi>*%WM=z@)G7DFt1el4i9c_naJOe&c; z>U2HEg`_Q8ugR3^A8!|pOQaf|9*@IirzXC?cPkEqIgpn%JR$z-{MCW62?&C?UQDE$ zma_v3l2{~BPQ=1M5EL2Y9~R(yp{U$pbbk&zA(D5tuoT{0RDg1htNVBE9HY`|KmGa# z4D{?yn%-cD4GoM4^6wB1ie-w`g4niR;YVMcfB*#MTJvb`>Hp)QJyrg`!ZA^ZFV~mN zGFvRRcmL_YPF{d-d;efduW(UwwMR519i5aMF334?t&nMV9vm*nv5iG065tmGlL?+= zM#2hTHtX{r&Oi6K0Dx(g`chGaWHwJqWoPe|N#%;%wB+CJ-TIgB&KOK4Bs)^) zcpOe|FfwV>4Jir7FI<0rPXS@(labM6K5_M~Lwgb)?{K3Qm9}(g_H7E*rIk0fI-iLP zNn5sFvsf&BBjYnYo1O}X5~9NW*et8n+Bq;hHaP_Vuv)E7N?2zH79_DqqMV4`zbSKX z?m8R>3xM@HRrWdSB!(uQHfXeEhwDu0bRZ&O*>N#+A|IVFb004S}F)1oM zJ1q$SK*ZzeR7z}EP(w$LL?(ZGYi@qVN~Kz((;H6RDbea3hZISqmx?Ri-?Ige!%a+0 z8%!p(_UFQezn;C~K_c$ju;y?<&dKYAi<+)I#kGyw)}?h1j-pL8?%O^S(#2@PuQ z=|{374QDc&$t2>z{0$e2%MC^&36D28eF95wFnSOP3YB`{ico3uO0zXc7sLi}y&YDV zXX8TBmaW&knT(GPzcDc-9hwl63558_u(JAQ90qFb9f%GMTE&m9Ywz}9c?SA;cMpx8 zzEd*0huMJzNi32mCt`0^)HL_>Z&;Z?^B`9>ci09ks%@-o>$F&`a+TU)_iGyL z>;LA37vRee5A6^R(J3AjG6{>pR5rHm%F3{%`Z=*$t&>ujC!M-LyFJqiWmdA_sc>jm zBnAL5XcVL)3?r7wgJzcF76mG8UI|gb0oyavzrAwXp;C4>E+@^o-JVw_O*}0>e*PK` zhg+M#_vf&WojiN9qQ+=44~$QIaq6ei$4$9uN&JY=s^*T%#g#~%I0-EFVv$5S5eu*R z7uPh#g@<}EeqO_e*ZgHl)$FD9IRlh)+|s}E)C%9NsmKEG}`0m zuQ^}vu~@BIgCQcwU#`~Jz6$_9bFkpG9L%gjy+yI`E0Q;I*UOaZqS}R)>cb>OM>Kc! zM+Ew3CJT50zPmQ8DXMKWTPzX5jt{P4Ajq4^fL}up#9?{L6e=fnB!r_A7Aq*LZw3JP zvRM=|X-G8bqVoU~=@H9D2@ ztL>YB)vDI&s-Cpl7q42a*7C+yrCKvECdyl#ygns<_U>~A79_DqqUIGVQ>mM}`Zlgg zScoF9m;eRimj0dpd6bg6CzpyV;4HmmZE99Zyh5${^8B@tNeR*}R1fmzj8zP(2ZzPn zlfABFCbGwd1|8kKh09{nsg#{r8GR$;ujYGbR>#Hi!$U8XRFVh;GLc9o5*K*=6QYkyKG9I^e>h#V|)3W+z292^mcRhpZ5x`-8a9~G9!U|N{5~Z;iwBPJ0 zxLHvx91{@NZcgQ4SQ+FQ#AU=}k=k1lfDv^N0S*_Nl zu0F9;cI@QYr*;#DK?*ELVv$5GL~QBfrnI;i>JsY2oZy!JUC>92ipEx>(R^T2W=d@2 zv{Logvsc8wykqEq#bRO6sUN?yA6P+Iee>h?XEh1^VBs?_rKq;?MtSwTJC2E`;-h#!-YSj_3#O1sg^%hzX;dFJYfS8zU#nX> zC?w*c&Dk`H$H18A>x(yyCX*9841s`mD2mk^jNe|ldoV9^M`n6Q|KJtm8ci6T-tgtw ztGhR@{kLBqGMP<}nmZmew9KmumF8SpDnFV|rR>kk+MkyNxBT%|v8}YmrKK%fuNh6| zw!Q(H2l;YIr9!0+@#DU=Wuwt#;(D4z~9XTrI28=?xqf z^Ke17RIXe~slqY=mF9}Lu`~yc?%LclJho~@)Waw3>yrg%?v@OUE$JyDm%uIkyNDd~ z{}T?2`F2500GF-N={-oqx{mG(_a3}}uc%oDpwe6sH^Fe82kzuwG{>NzprD|>^%Sg diff --git a/screenshots/shortcut.png b/screenshots/shortcut.png deleted file mode 100644 index 96bce8c45045c29b4d45454a368e07fdfbd8b686..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1110 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 Hu0mjfsQ>H; diff --git a/screenshots/stock.png b/screenshots/stock.png deleted file mode 100644 index 427f7cf8ae02a243b30e2b8b1bea334fcd07a757..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2087 zcmV+?2-x?DP)T(kyL4OVW@)_%b9g#|A?UHrS5A#$VXRHqU-G_WpQ;m=x>>nKTk{bpN>b-H+dS z_uO;NJ?A|F5LJTJ=|saWut^(<&fV->szaOG@%R#H)eaIdm(AYXpp8SJ(1PA#i7z|| zf~3VFpFgmrBfum_TT%74MV?6B0;jo$2C@ z0k>x!0I&89u}xiQvLoABTeMjOPwXKX1EprBfb>L{2r=j%cRb zJ|r%0+G&$QJGs=Pr%;_9?+Rv<%Vt*O3)XZz*=e*FkJW$OqcE85pSRr(h8CKoiz>yW zr|c{dyno@Q-ehyQy`QwoY)%i>h4@j2+l!UlOeTV$L*vG{SXD)QG`P^xb=Twbw{|PH zBvGnL_*hpm8TJQ)Uc|ptf}s%pw|M<0hmw=XII-5p;lz~n?T@djv__Z5*M4VIr7>2D zx5Zu1Xt52dH8BP(3Xe@stuD-8t8FRB-D2i=fd?nnJmVl@DgD%O#1B$&2Rte0B^kiZ+`%dfwevkmneZqJbiSx(c(~y#qkt7iwbWHD!d31UwD2Ly>)kZZ-s;m z!)ubCa{S(icIvg~o7s%i*IzjJW&7Z*|EK`~5CKx_XU>1q4*-~PxN|v7X_@eoR(o_F zprRn}%Z@>uKz=lo#bPu`OD=qKd)n&cbJ^&9Q#qzL|M8z+VRglrp03Sn;~;_}R61pwgoF4nO<#gB!b zLkNOSAKeWAc<)>riqAt+DU@B3;sg>gzVNWvpnGT-|0O$#3@>>*0D!?{CqWRA2mt^z z21|Y}2diTNkYmmlJ$HwQr=FZf{3uQ=g<1~)xZEMfn$d292oa)B1}B~7_WB;h=f%KU z_pIhRNlG&LgNyBzeVCYB9wM=?S0gqEtBIr|&N1J|$FkAkue#K=QYs7E<{QFG$GVSSAYTVxW zXZKgL8MFh^((J8iUGl}tSVNWQm1nG}TS7bgeykaM@xbu!fg zYGh$$K6c?YE)yKVYKR9=$Nl@qdjDbY&!0kgbQbq5!y`*jN=%T~)=+Kbu9JC@ZUhxW z6o4G}|3A~i$Nt~HOXAl>XXU%n{fQUMo7PII>& zy_m3M2c{NWX$}~npT2&-eEYGYv?!)7Tp}RHadABVz(6=PAg4KH8S&ntC`?1Kra2J- z9$=zupSKxPJ$?~r_4VSF?d#VTaHXuQpYh}j{3c?`nrJB{`rH2b_YWuqj9(UDS@8Go zitWdbU%G>-7$^d-zMvUGLhSJ0-`wv$=wek35hOd!UAgnPY1RgaAu?h@$$|EhmhF7@ z`U4)jAX12;L`rJspTC@+KC9r;gSDJHcH!2NZO7nRu%@{mzkdIC`w^ms3tSkze*YPm zNN`yM7e_dbk@3Hr%;tL!L%)5K!Kw{wIcKJ)VyK~jFb;1yht*g-g6K`Ae}CEUJc#`F zmxGFF4v+O%rEoTxn3#Vm%B^8z{e)E;odvNpnOIrB$jWY{vpcX_hsAL~b*wC3v8txC kAa=*m*%7#`8`O>i06)gu*!nCHy#N3J07*qoM6N<$f&yGt0{{R3 diff --git a/screenshots/themes/default.png b/screenshots/themes/default.png deleted file mode 100644 index c2680bd1f60b488549f993deafbb36dcc51ee007..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7251 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 EA1Hc$F#rGn diff --git a/screenshots/themes/gruvbox-light.png b/screenshots/themes/gruvbox-light.png deleted file mode 100644 index 272fd9d7e000eab21cc01dc40cbd249378b3dbf8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6262 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 z 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 diff --git a/screenshots/themes/gruvbox.png b/screenshots/themes/gruvbox.png deleted file mode 100644 index 2f2c38598bd5e3f2a895cf7cd1836976e04fb77e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10310 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 diff --git a/screenshots/themes/iceberg-dark-powerline.png b/screenshots/themes/iceberg-dark-powerline.png deleted file mode 100644 index fa64e75db72f5e6589045e504338f5b9a352640e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13363 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�p7czR1L_C7c%uZo4(1`l%|>fZ(2Z`-SE@$m;cd$kpM2>FgAvEV838%D{ikY?qs#7vE>K0;y(JwML1 z(~;BN(J3^q@LNEE!%Nv{6hv+r2VhQu46(X~UWv^LCvp8}Bm-KNL}HAgiCoaN0Z~+w zke=?s@9%q@euCl@2{ubF1NTzp5DR%qBfOA&m7^iLLvh0c9h@iFQeisPUZV}}XWY0j zx?=0!18NBu0a9E-zke#xOdO&_$Xe}I1U#TtR_WVrOwp7V(3a~DC7iC_4TLXmLlXm$ zec36*Ush>3r7@9no2r_8ZMO2B_uw43B?yymt5fHQ;iIXi31pN(&1V^Z#`dt2QCL7G zdXJVr*7it<4`&G@`LMo9`fU&_SlB|A{hMb+tQzU z-*wz1d2^1J@u?Nx*I`Krp-3*>{qSCU6Zr#rF_ES2W5wv8XyN;-qbubq#n3cbS6*hq zQ4+6<$3}hHsW1S53;)me0vI6%3cTp~DS>Aikk7^~@O@wM1*Isrj_!z7-UfYt9wGRJFms5C; z&(6+aze`E7dVHv`QfixCj8n@#gBiv}gwFK$&WV#aTa9&M#(~n_3-f{~lSW;&eZqcE zR%q3PI`BsI)B(XiCdltCfL-(q8ozH?VlSJDC%Ybko>Gqr!z9E`1-v-^R|hPqR>dLMdp{`}?#PJZ2Gjt3?_FF6)-e-)8gT)dla zz6b6#U+=MeQtsMx<%bFkSFYt7o%?cXCx61`o^Ri`FgY|Hi`bv{o2#w0+vSkcWWmIn z-=;P=POw=}ntXy;YWA}wSC)6?vTVpc8`RKl(;a$hg0D)6d1`9!V=t z#b`|pT7Eg{#g|u}0#i;u{#d#Ewc-MaupB*+?tIfF-ReQpJiW8p7EV6>lx=U{;hAAG zL|Er(uD|^@ap`wIm!(hN3#d-+tk99^cP)?+=Wume7@;z4u|~G_#)zXl2b$9#ZVtH> z3+(IaUJ$eA5CZmOdfk{`>ulcf-cbM)*~@E_E^liNn)zs7<+=!ylE1HJZaTH=(4teK z`**kio9nzfv$rW}h#Zr@=BN0fVN<4=S9#kFmyJ2MCV8j}tKa|S8h^X>`M$5`XYHOX zwuNPn-s{!&KmUDeo}YQ^C&y~u2*c!~XDlr*0e6TzQ#}x|MkFhHm5R{GxeG2uw09hB zZeu@tC>?lG1MkF$xCOdZ0`o$&rb_Tsee2y^At2D*_P#FivNbR@hHuOLmix<5$q;lP zf{}*c@o%C&wj28G9i`4smX0($!L|SRx+@alS9O;LbL#mY*e3Hwdf;g+z{ z1m*Y+=$RT4z&)VYPWMP)Ir3B@HN6pwS>0mdK II;Vst01A?582|tP diff --git a/screenshots/themes/iceberg-rainbow.png b/screenshots/themes/iceberg-rainbow.png deleted file mode 100644 index 763739b0188f6c6f352b095a08ed38f34d3baf51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19947 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.Hwr|tlrd|)1 z?h>xX(oHT7S}eviGxyKM9vfop^+@&gz%+@BxVF?20=r)Y)Z)Y~Xe38M;aqeSv5{iM ziUhspcwsYiMRLa2>Z?t!bWU$p!9^VDfjRB=zNfgf3j=9QdL%$l$Vk;ELf}`Pu~$^c z2n_IcwR40RnbM8BY23xZh+Qyi9f&p+myMDjskgv6$H#Cj{qiX~Ft0OeWcyFy zfYDm>Q8!f?X|DDJB4KD08fuqU>)LBxPFDe1K)Og-c|lccaSH>JNag=}k=bsHZ2FBf z^8z|B!`m8%A6x&QtaFyXuAniOuneXrxU&F~cELTHDrI+d`b++&E97=tsOEQxKS_ z6biYQ!LujA;#zPaR=;s>8b^_gQ@ZSjVTN)J3>Td)#N|D9zy4wPS|QF)iH&+WqHf;L zhNJoDQzz%ub%wm<%1HSMX}ymKqWvfz-GlqV*Fov*HdSlSFfEIXV9xqp!kcCv|ACz{ zN3EVZE_jf1EYi^3na~&eo)@r*!u6Phpwf8TREPDy5ZOv4rca^%&@5BN}Q z?ZuCGrV5kfcCZq2`>tZzOnOl1E*z$!><+h3A!y9a*@(NT@|7hH8+jNzR=V-9sgI*z z*&*r(o9Q=d0N)aSUn6s2oZYFn4aJ?^lskGq(~=+{#`QbXzs) zOQ$TG5875K2H&}mxfv%5aePWv3SerkykG)@vyHdg8KOs=RqxT}F5BRt$x80*9nspI z`&&N!=l|!#C(qt%bekAKgag3=_~v?Wqb4Fs;sp-Zr!pV@uG=M!6Lvsy3^bsFa@-b* z&75W4&QWb>eicPCpc+4;BafY|gf6h}0*&<>*t7 zZ+&Vy_30cDfXl1;MtbaLwq9>S2MJ>)4cnrcinBxM0@Y5}#`9(mLVrAH(li3RtLo*_ zwqXy1v4$5D=9L%G`GD{%;E_%t}BgsyF0<6fQ;8(nh9uw4psY)oG+U+I*YwK$3 zbMn8=NGZ=_cl?P@LVRydL~%%xoi2@xnJY1!HM(w@znCTBqFuk{Z*KMobkHzy^L+Zj z%jt2d_r^@oXIkGvTx6DFk+5@R8qB~FrZ}I|y{A&97@wIapzPN+Q@%L>L#u8!%t*Lg z%KxV=Vt#SN>n>xMsALU7iLu4`jZ}&-FPdB+K>t{lf9z)&UQ+RW-@3Ac?cHd38@v;Q zLujbvb<#QYBU@_xUHv<=DZMWFMJ1NVa00%|1N-+{5u=^;`i}&+l43?Ck&d>S_1pVV(L;Rrbg}UkElzDfpPi|NUe4)0-M@kuH854adP(Ysu zF=xiijKy5CX4#y=9;2ftgrOG#6U#Ml@^?W3CNR@Sw46@hSuq-=EL}9qggMY8AR%pE z=d6S%{=E%-6D%rXqo=qL=(u{fb1 zJP3x4GFM+R18QkQLbszKI8n($N_Y9XB)6dc5uNPR;DA{IeYZb<(%OhszaW>BHEujb zU3osE*+!doJ+_AGrU7z%rAx>Z!!d2chWm$I-A~fm_LM1B)3fgmahsM{vds`b^f;tfQp>>N*MZj z_hffNXwF`--&H(!lGXW=Hlji%&78nAP@-v-0kLwLoUsr+#JIU?&%nGyP1o5oryHZ>_53Mj}t0m;8{C?lCE ze}Qj9$!o%8DYYe=qxiYP=3=7L;G)umcU)~pyLyfRut z))(pqjx#1w#koTVO#1!SSD&Scr=4^|0(~#)FEJ&Q8zzyW}3O!7L@izY5|Lxo~vOg!VJ>2SkIg`5a_q?et@W+KP zidpbw2Ka7s4P$Mxr$Zhbu8rBMi@3@^Kf?Qd9cYLUD6c1jVy~gfzHU7|66VZv;OM~2 zg*|2aLPe}z{t|dol7!98G`s{hAjXyM5wov=B zUiz=Z_Pz~bM;^QJs1Rd^aZW=ega?I~OY@o0NQ{82hQG*_da-@|!!_TRzL06t zq5?ng&&iGtzA5OIpc-PfyIgc=v^36zP4GE-YN?*xN|^JHVT3=_e7t$cPmF+kVSGm- zx{_>ZsELQ=IVa77Kq1?vc|SL%?Z<98KpUHgf1a0vuQj`5-u#oCHGIH@vC@XO16BYe z#;i@KJ%&sC7BUJ3aaB)Ji6<8I6Hm;nr!-S?>@O|&VI!em@%n-eZBR^k0xNY`u7WFiO+cMCj z`@YgiXHAiC(orxU&eSLckP_pC&9&gz)J(+;pNmvISWIvUQ0Lcl`$7up)S;f+!Tm4B ztSO4}2*|%9Vwr2~jEY}^t%5{Z)hiRJB34^@c!M(QBd6hJr(Yvj>^r1Tz07#K80-w-u@;~=EFwTm-sS`R} zuy2YTr7$>w722RNY&2v=yjf;a_=~R1BtkWnzPX>VV)FOYCuwf{ibKoVK$#O&B*IHb zEE)y?$u3gR_e1RJKBz|YW43SSmQmCnfY0W1RldKD@G_{*bTXW+4~9S2K!5@2!_7uY zXV>kUZH#7jUmm)}ptM&<1BBUIET+vgUP;e*J&%Ux^9VJUKhY2QOF)&Zt&^I9o|A$; zSv>bw0-D#7)|jn|2l>>-Zl%*>@9Sc-C)0xZ$I9BUDbiT2m)&QGIn~qiJZ%-dyv?g1 zfqhg6<$vfbnX<&zTE*SE?5?E=2<(himR%vsrkb?5gA~isBj%uPyVYlh+U>nWAI8YX ziBgnQtQ4`LQ{%knRYQLKPruvZK8SsxbHoPN`DoQDxXpQpZLI{>eZ?vB{v4KmT0v~g zn`|x$yVqSRJ?~li@wq5b5dj&ATR)QJr_F?t+@)D3G++k$H(>-3P?+m%rC_Q zUf@|w_Ww&znd)4=$wzN*b{~jPlEVOc9XXv|(bm0~1F)YO?#;f}eo>sLy(K0nquvzd zR4$WAS#WK@lu5H^tXgUUD>JLV?TqeKGhyXzwu_S*j{yKI@qcCkFkwG~x0Z(b_2w1? zAM_;r*B@#X(ik+g6^M{3ORJ?cU2Bm+&*5c@a=VpKZ<4zHcv%+Rd2Ry#tVSF~+A03U z!}C7GB8cz@sC$bhx^*_-0GETA8nijg=yLH!EY&tAzU=og{iYn{@F3g=J)e5~+vE9i z{}Z)Pv!{xzl)Z=PxDWe5XHiAbG@u6_Msod|b|Y zX3pF5Tqgkd54=LKyP2P4mRQN_Uh6b|@B4nE3IM!aZucr-g=y!lnHs=hjN`bUt%J|) z@^(kjo2BR2!KG~!1(erx?5{0gdJ&Vs91kQPhKDthl9xx_wl~qf%(AgcE2n9 zSe{a{7-S<+t^EEpopxV6`DQ$Qz8@RoAHX=hI}f3SJdVDF8r5EqZKK7+_12lg1m!CX z;gs4VdaVsl><0VZ`E(cg^3Pb4t8euR5QT|}ZGTU)o3r}(F%mIj=0sI$;oSSFN3pCT zR<|qU@(6yfHpQz}nP||2oK@z0OzYK+0UxLnfNbg)kBXrzk6@gGSeDS7YcFLs*kO+enOKNpG_)k3hOrU<+!JrKU_pN@dw%8uv-h^Y`Kg&2Hys zt=S|Zt?uXDIMo6O>0A9o6DqG%^aIo-^mt?ZFh_*q?K%EXqig!AvpQQ1LOGdBYN?jR ziYPfil4r~$wH(ji~r_*{P%o8*HO6DN{vy_xv0+5Yd!(R^ebr( z>}h$JzZP0BiW}q6LK&Fg_KQE=g`e1+#DL$&D)=M{fShs^IeuWujwnl`=K1pQ?u-cw z(jp-MX6A(-w`c8!soEC^9qcaZuE+&JeKQ-Pwj!Y|T%5x1gez927tJ0G__x~k4oQs3 zFt1k)VOsb9@&;KP?cEC#jn|qHq4P_^Jv%E|_ut~=@v%(Y`6lr2G2A81*~n1{_j2$& z@i%+jov?tdltALS<2x-w8HHb7KJkC7^e<~dO#5D#IK=;RV7=&3&|k{)yuACZ?c#4J zHA$nniI)}w-dZ*fMyT3}pIld0E9^4&jK4gmseQx@yt_urGnFs*BE)A!4IlOtR z8sNpiqAtYO5m(N4);|bwSmy_^!zP)XFK=_-h(Xf5U7l5n1<$k>0!?npD0MbkAV}ZZ zhneu-UdFf&5$W2~%S9zRKkwCjnOyJZw1O`e@8?@GBU3E%8?qm~PFv~_BCez)u`#kn z=3_jrvr*tQ$|s3dN&OkCJP2aT;X-Jn>^6S|Zhb%m@_F%l+MU)1V%@F5w1gSAZ5peS z8CS<|XKqMOSzRa3B~A@c!Kk~X)Edoh$ih4uKR!4*!^t5I<%tNU!s~|p#LMmG<6?Kc}dLU zgMzmBAmrJs9c;X6=s2O?46 f4*Gvd%&REey2YZelV<^BYzdH)QkJX{Hwpef>6*Dg diff --git a/screenshots/time.png b/screenshots/time.png deleted file mode 100644 index 39d5210fde31641aef160d774e958f9220bd2ab3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2456 zcmV;J31{|+P)X0ssI2x3c@Y00003b3#c}2nYz< z;ZNWI000nlMObuGZ)S9NVRB^vQ)qQ`bY*g5g3t*700}fnL_t(&-tAgzP*eF8|KHr3 z7kQBcNWvq8R}vlpqESRaR1k`&v)a}5(OO48wB4@L&TgGf+v&C+cKW4LcRRbS)9JKR zcW~Fb)|L8L7I#HKdC8lA;gtj-2_bKikZ_aS+YeC;5RwAQ_7XELaKLmkAJYHN!FCqN?fYxY?WIqCdP$?^P zdza(Ndf9+sSo_E*jY0+hgpZ#P0ObWz9)}%Q*2@MQfX*?M(Q1nnKQ~BnGkD1nsBl>H zO1VUomP{ohR)WMXmksWw>qpL+l2h2!?k5XKF92EHMRB)O+5Za z@|-zRYje69Z+5KAK~iGu#w(NM1#{WhJ@6rAz@A;GnHfh5G#cDy`0B%Mr#}@zSZ3z1!vEGxMw6OQO-4?mQf` zIo+SNbfbQ3Q(;b2QhTf9I-|9zwQm{qyS>X7Z{F`#s$l{&Z+7sxObCKt0wfU$#6ms* zAc@T|TI?^(p~s6_9IoIcN=x>5(BX;MsHA3%re4)#m@;$a7muA-ULcK3rLJ5?rI5aS zFs#v;p88j|kjKR^?CPC|R)=egEO-3_q$m(|3_bbpH|jP+5GcvZ2o6owMJkz?C*%ho z9Uh0NGg@P`-|M?KxAu(L7TuwfJPuQDdXn3599OAzS?MWJNo|md+6N|9h5-z}-8Hg| z`px!52!gmQnkX%qgus%lQ~*E{o4H_Fd|?hlQ7IR94o^j`8;mx4=a?$|G2vO-%MuX? zQI7KkIcWfZ_JQ&BOQ4*jgeMXQp0m=}AlB1edIf?xwo~ zZyv5`s4RWwM8mzl@fFVqqM=g$ySI<>IIQ)Y@VLyoeQTxiEc@6jT8JeuW&$~~P$VME zpi$=L!}v`A!_9UlkDDkJ33^5~Qjq{AKx87~T=GUW-t#zIK{}5u>>8ZCxl zQSA3XIp*?sA`{{;Y0f3@qRRsSAi^-2gusMn*+p-%006iw`noVe_M*NFKKDTN#y?;A zT4%CRpVmGA0KLiPSw@R;(-Y|l>*JEo??Zil498Y$964OO<>IZ5^-}dH)x~0=R3rcZ z6z64BY>*%@94+}#005}ZA9mGXw)2x%Vv(S^W0=FFWu>KLN1<hnYeOj!2`{ro zXO>Ha={(Ntyyb(&<~6I5Og;cWy*dB_0Q4sN;kvSpp~;7%Y5)K?k-qS>eyiSG*fl(* zU$6lHh9(}j^{T2iNJMhbCu1 zY@k!ARi&~!{ewP#AX5L+=5)0WkL)Nd4x0-HR9d-Ic&vWw?=Rf+uZ|448NBk6Jcn~h zGq=`$gDP#NfZNpC9~_oSGDoN9p2QghfgtFaDlv>iLZWt$etrIGXpP_x1g_ob3yoBM z4D)-Ii7=cm5?r_u{b)5j@p$K^{2rwm$8i7v1VPDcM$pL&W;+pvRT={TU|c)@+D)C+X~E{`P^yBGtU=!+O}|Q;Dd3(x*^rZw z0uvwr0F^?jE-QTNNG$|G7jE45qP{hEJs6sFxR#FAm-9HRhDy0mz+DNs-OA|-xui^% z!)DMF1=%|l`CY@)q4$1kYw>$;9ZF_1LMK=1Z;QjVjQW>R-%8(rNl!SqOTp)|(s}Gt z2dWGfhf1T5s+t@32kBJG_x4qAS@c8(?f9PZA0Da>N@uhXP+~f`oK{rvQPjo$Q!Pg<6q`rX4(H3AcA%Vo#v%guJz zKU%swhbC8_{UIU(Lxfo0bKy9~q*LEMQI7@i!HJpAW9yPEx;*C^n-A{V_>0r~0RSUY zv*#MW2$uC;)HkNq1q)6L!&F*>*}e$=GZf~flZZ&{!E;sBnw-2*;_p>WJnd9J|NVPm zRwj)?$xh|3Uy|jfZz{?b^0-V!g3f50F_><5k2;)7@&4rh2BVr8x7VxI&qI;FpGab{ zY80h$eZ7P*I~-s24ichnifiiSL=uYyp7;JQj#mY7`@PzL;@=Ox(m=>GdRz;yF8&SL W#66KzXm6kZ00004?6j#Kk}#>A4aY(I-q# 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 LYI3y@i+BG6%kyNw diff --git a/screenshots/todo.png b/screenshots/todo.png deleted file mode 100644 index 87b6bfc35e8b28835e75274a93fbc460a2ac861a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 733 zcmV<30wVp1P)2_#2F z1tumYy1J5wljG;lUozsN0S;Dk)@^(C<{c+HJAN%kuUz}`{X1P<`SbU0DN*5+0FTvs z4n2MSmYt3D*YDr>wfy+?>%jTTKYsn9vMbqGS=rfGSy-@?XwP523v;!zaPAC`S+i{RIA6G%nSn^te}x~@fw=!ger6?@L1o$`XKUz6?-itTyF5bF}CVTwSwU1xEguB^`2?=^x zo5@Ou9=ULZNRwZ^|9I@mjsO4uQzyoS`1uN=18+Qha^t~cG^yWz{!Ur7nV*NdJRwX) zR%*)1jo*I!B;MpFuV0_Nd7FfOPknl<2tOZIsn6fO?>%{L-DLTm P00000NkvXXu0mjfb4z8( diff --git a/screenshots/traffic.png b/screenshots/traffic.png deleted file mode 100644 index bcb27dad9a899d08ae2a682f3a2ec7411196dc03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3811 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 Z_&<{MbgwrU3^@P*002ovPDHLkV1hU?S~>s# diff --git a/screenshots/uptime.png b/screenshots/uptime.png deleted file mode 100644 index 489b212b425857331e325cc940088ac491bfced9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 980 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 diff --git a/screenshots/xrandr.png b/screenshots/xrandr.png deleted file mode 100644 index ec1c6de10d2188415b099a5fa5d80d0dd35e93cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2693 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 diff --git a/screenshots/zpool.png b/screenshots/zpool.png deleted file mode 100644 index c88bc0f7f571cacb4ada342ac71d178677101d24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4934 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 Ef+abn;s5{u diff --git a/testjson.sh b/testjson.sh deleted file mode 100755 index 28e65a9..0000000 --- a/testjson.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -find themes/ -name "*.json"|grep -v invalid| -while read f; do - cat "$f" | json_verify 2>&1 > /dev/null | sed "1 s@\(.*error\)@\nError in $f\n\1@g" -done diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/mocks.py b/tests/mocks.py deleted file mode 100644 index 38f1583..0000000 --- a/tests/mocks.py +++ /dev/null @@ -1,141 +0,0 @@ -# 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 = poll_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 poll_mock(module=""): - if len(module) > 0: module = "{}.".format(module) - - stdin = mock.patch("{}sys.stdin".format(module)) - select = mock.patch("{}select".format(module)) - poll = mock.Mock() - - stdin_mock = stdin.start() - select_mock = select.start() - - stdin_mock.fileno.return_value = 1 - select_mock.poll.return_value = poll - poll.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/tests/modules/__init__.py b/tests/modules/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/modules/test_battery.py b/tests/modules/test_battery.py deleted file mode 100644 index 6d0ec0a..0000000 --- a/tests/modules/test_battery.py +++ /dev/null @@ -1,109 +0,0 @@ -# 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.file.read.return_value = "120" - self.open.return_value = self.file - - self.exists.return_value = True - self.engine = mock.Mock() - self.config = Config() - self.config.set("battery.showremaining", "false") - 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" - - self.module.widgets()[0] - - 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.module.widgets()[0])) - - def test_warning(self): - self.file.read.return_value = self.warningValue - self.module.update_all() - self.assertTrue("warning" in self.module.state(self.module.widgets()[0])) - - def test_normal(self): - self.file.read.return_value = self.normalValue - self.module.update_all() - self.assertTrue(not "warning" in self.module.state(self.module.widgets()[0])) - self.assertTrue(not "critical" in self.module.state(self.module.widgets()[0])) - - def test_overload(self): - self.file.read.return_value = "120" - self.module.update_all() - self.assertTrue(not "warning" in self.module.state(self.module.widgets()[0])) - self.assertTrue(not "critical" in self.module.state(self.module.widgets()[0])) - self.assertEquals(self.module.capacity(self.module.widgets()[0]), "100%") - - def test_ac(self): - self.exists.return_value = False - self.file.read.return_value = "120" - self.module.update_all() - self.assertEquals(self.module.capacity(self.module.widgets()[0]), "ac") - self.assertTrue("AC" in self.module.state(self.module.widgets()[0])) - - def test_error(self): - self.file.read.side_effect = IOError("failed to read") - self.module.update_all() - self.assertEquals(self.module.capacity(self.module.widgets()[0]), "n/a") - self.assertTrue("critical" in self.module.state(self.module.widgets()[0])) - self.assertTrue("unknown" in self.module.state(self.module.widgets()[0])) - - def test_charging(self): - self.file.read.return_value = self.chargedValue - self.module.update_all() - self.assertTrue("charged" in self.module.state(self.module.widgets()[0])) - self.file.read.return_value = self.normalValue - self.module.update_all() - self.assertTrue("charging" in self.module.state(self.module.widgets()[0])) - - 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.module.widgets()[0])) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_brightness.py b/tests/modules/test_brightness.py deleted file mode 100644 index 54cc253..0000000 --- a/tests/modules/test_brightness.py +++ /dev/null @@ -1,69 +0,0 @@ -# pylint: disable=C0103,C0111 - -import mock -import unittest - -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - -try: - FileNotFoundError -except NameError: - FileNotFoundError = IOError - -import tests.mocks as mocks - -import bumblebee.util -from bumblebee.config import Config -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) - self.tool = "" - self.up = "" - self.down = "" - if bumblebee.util.which("light"): - self.tool = "light" - self.up = "-A {}%" - self.down = "-U {}%" - elif bumblebee.util.which("brightnessctl"): - self.tool = "brightnessctl" - self.up = "s {}%+" - self.down = "s {}%-" - else: - self.tool = "xbacklight" - self.up = "+{}%" - self.down = "-{}%" - - 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("{} {}".format(self.tool, self.up.format(2))) - - def test_wheel_down(self): - mocks.mouseEvent(stdin=self.stdin, button=WHEEL_DOWN, inp=self.input, module=self.module) - self.popen.assert_call("{} {}".format(self.tool, self.down.format(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("{} {}".format(self.tool, self.down.format(10))) - - @mock.patch('bumblebee.modules.brightness.open', create=True) - def test_error(self,mock_open): - mock_open.side_effect = FileNotFoundError - self.module.update_all() - self.assertEquals(self.module.brightness(self.anyWidget), "n/a") - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_caffeine.py b/tests/modules/test_caffeine.py deleted file mode 100644 index ad0cf00..0000000 --- a/tests/modules/test_caffeine.py +++ /dev/null @@ -1,50 +0,0 @@ -# pylint: disable=C0103,C0111 - -import unittest -from mock import patch -import tests.mocks as mocks - -from bumblebee.input import LEFT_MOUSE -from bumblebee.modules.caffeine import Module - -class TestCaffeineModule(unittest.TestCase): - def setUp(self): - mocks.setup_test(self, Module) - - def tearDown(self): - mocks.teardown_test(self) - - def test_check_requirements(self): - with patch('bumblebee.util.which', side_effect=['', 'xprop', 'xdg-screensaver']): - self.assertTrue(['xdotool'] == self.module._check_requirements()) - - def test_get_i3bar_xid_returns_digit(self): - self.popen.mock.communicate.return_value = ("8388614", None) - self.assertTrue(self.module._get_i3bar_xid().isdigit()) - - def test_get_i3bar_xid_returns_error_string(self): - self.popen.mock.communicate.return_value = ("Some error message", None) - self.assertTrue(self.module._get_i3bar_xid() is None) - - def test_get_i3bar_xid_returns_empty_string(self): - self.popen.mock.communicate.return_value = ("", None) - self.assertTrue(self.module._get_i3bar_xid() is None) - - def test_suspend_screensaver_success(self): - with patch.object(self.module, '_get_i3bar_xid', return_value=8388614): - mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) - self.assertTrue(self.module._suspend_screensaver() is True) - - def test_suspend_screensaver_fail(self): - with patch.object(self.module, '_get_i3bar_xid', return_value=None): - self.module._active = False - mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) - self.assertTrue(self.module._suspend_screensaver() is False) - - def test_resume_screensaver(self): - with patch.object(self.module, '_check_requirements', return_value=[]): - self.module._active = True - mocks.mouseEvent(stdin=self.stdin, button=LEFT_MOUSE, inp=self.input, module=self.module) - self.assertTrue(self.module._resume_screensaver() is True) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_cmus.py b/tests/modules/test_cmus.py deleted file mode 100644 index ce1f77c..0000000 --- a/tests/modules/test_cmus.py +++ /dev/null @@ -1,114 +0,0 @@ -# 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/tests/modules/test_cpu.py b/tests/modules/test_cpu.py deleted file mode 100644 index af9d28d..0000000 --- a/tests/modules/test_cpu.py +++ /dev/null @@ -1,45 +0,0 @@ -# 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("99.0%")) - - 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/tests/modules/test_disk.py b/tests/modules/test_disk.py deleted file mode 100644 index bc40b2a..0000000 --- a/tests/modules/test_disk.py +++ /dev/null @@ -1,48 +0,0 @@ -# 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") - self.config.set("disk.open", "nautilus") - - 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/tests/modules/test_hddtemp.py b/tests/modules/test_hddtemp.py deleted file mode 100644 index e8eb9ca..0000000 --- a/tests/modules/test_hddtemp.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- - -import mock -import unittest - -from bumblebee.config import Config -import bumblebee.modules.hddtemp - - -class TestHddtempModule(unittest.TestCase): - def setUp(self): - config = Config() - self.module = bumblebee.modules.hddtemp.Module( - engine=mock.Mock(), config={"config": config}) - self.data_line = "|/dev/sda|TOSHIBA DT01ACA100 �|35|C||/dev/sdb|TOSHIBA DT01ACA100 �|37|C|" - self.expected_parts = [ - "/dev/sda", - "TOSHIBA DT01ACA100 �", - "35", - "C", - "", - "/dev/sdb", - "TOSHIBA DT01ACA100 �", - "37", - "C", - ""] - self.expected_per_disk = [ - ["/dev/sda", - "TOSHIBA DT01ACA100 �", - "35", - "C", - ""], - ["/dev/sdb", - "TOSHIBA DT01ACA100 �", - "37", - "C", - ""]] - self.device_record = self.expected_per_disk[0] - self.expected_name_and_temp = ("sda", "35") - self.expected_hddtemp = "sda+35°C" - - def test_get_parts(self): - self.assertEqual( - self.expected_parts, self.module._get_parts(self.data_line)) - - def test_partition_parts(self): - self.assertEqual( - self.expected_per_disk, - self.module._partition_parts(self.expected_parts)) - - def test_get_name_and_temp(self): - self.assertEqual( - self.expected_name_and_temp, - self.module._get_name_and_temp(self.device_record)) - - def test_get_hddtemp(self): - self.assertEqual( - self.expected_hddtemp, - self.module._get_hddtemp(self.expected_name_and_temp)) diff --git a/tests/modules/test_http_status.py b/tests/modules/test_http_status.py deleted file mode 100644 index 463884d..0000000 --- a/tests/modules/test_http_status.py +++ /dev/null @@ -1,49 +0,0 @@ -# pylint: disable=C0103,C0111 - -import mock -import unittest - -from bumblebee.modules.http_status import Module -from bumblebee.config import Config - -class TestHttpStatusModule(unittest.TestCase): - def test_status_success(self): - config = Config() - config.set("http_status.target", "http://example.org") - self.module = Module(engine=mock.Mock(), config={"config":config}) - - self.assertTrue(not "warning" in self.module.state(self.module.widgets()[0])) - self.assertTrue(not "critical" in self.module.state(self.module.widgets()[0])) - self.assertEqual(self.module.getStatus(), "200") - self.assertEqual(self.module.getOutput(), "200") - - def test_status_error(self): - config = Config() - config.set("http_status.expect", "not a 200") - config.set("http_status.target", "http://example.org") - self.module = Module(engine=mock.Mock(), config={"config":config}) - - self.assertTrue(not "warning" in self.module.state(self.module.widgets()[0])) - self.assertTrue("critical" in self.module.state(self.module.widgets()[0])) - self.assertEqual(self.module.getStatus(), "200") - self.assertEqual(self.module.getOutput(), "200 != not a 200") - - def test_label(self): - config = Config() - config.set("http_status.label", "example") - config.set("http_status.target", "http://example.org") - self.module = Module(engine=mock.Mock(), config={"config":config}) - - self.assertEqual(self.module.getOutput(), "example: 200") - - def test_unknow(self): - config = Config() - config.set("http_status.target", "invalid target") - self.module = Module(engine=mock.Mock(), config={"config":config}) - - self.assertTrue("warning" in self.module.state(self.module.widgets()[0])) - self.assertEqual(self.module.getStatus(), "UNK") - self.assertEqual(self.module.getOutput(), "UNK != 200") - - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/modules/test_load.py b/tests/modules/test_load.py deleted file mode 100644 index e57bc2b..0000000 --- a/tests/modules/test_load.py +++ /dev/null @@ -1,57 +0,0 @@ -# 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/tests/modules/test_pulseaudio.py b/tests/modules/test_pulseaudio.py deleted file mode 100644 index f8754c6..0000000 --- a/tests/modules/test_pulseaudio.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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/tests/test_config.py b/tests/test_config.py deleted file mode 100644 index 9277a62..0000000 --- a/tests/test_config.py +++ /dev/null @@ -1,70 +0,0 @@ -# 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_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/tests/test_engine.py b/tests/test_engine.py deleted file mode 100644 index 946f604..0000000 --- a/tests/test_engine.py +++ /dev/null @@ -1,90 +0,0 @@ -# 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/tests/test_i3barinput.py b/tests/test_i3barinput.py deleted file mode 100644 index 23ac566..0000000 --- a/tests/test_i3barinput.py +++ /dev/null @@ -1,110 +0,0 @@ -# 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 - poll = mock.Mock() - self.select.poll.return_value = poll - - poll.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/tests/test_i3baroutput.py b/tests/test_i3baroutput.py deleted file mode 100644 index a350ceb..0000000 --- a/tests/test_i3baroutput.py +++ /dev/null @@ -1,138 +0,0 @@ -# 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.config = mock.Mock() - self.config.markup.return_value = "" - self.config.unused_keys.return_value = [] - - self.output = I3BarOutput(self.theme, self.config) - - 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/tests/test_module.py b/tests/test_module.py deleted file mode 100644 index cf663dc..0000000 --- a/tests/test_module.py +++ /dev/null @@ -1,89 +0,0 @@ -# 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/tests/test_output.py b/tests/test_output.py index 6d094c9..b073d4c 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -1,62 +1,27 @@ import unittest +import json -import bumblebee.output +import core.output - -class TestHBar(unittest.TestCase): - """tests for bumblebee.output.HBar""" +class i3(unittest.TestCase): def setUp(self): - self.value = 1 - self.values = [10, 20, 30, 40, 55, 65, 80, 90] - self.hbar = bumblebee.output.HBar(self.value) + self.i3 = core.output.i3() - def test___init__(self): - """bumblebee.output.HBar.__init__()""" - self.assertEqual( - self.hbar.step, bumblebee.output.MAX_PERCENTS / bumblebee.output.CHARS) + def tearDown(self): + pass - def test_get_char(self): - """bumblebee.output.HBar.get_char()""" - for i in range(bumblebee.output.CHARS): - hbar = bumblebee.output.HBar(self.values[i]) - self.assertEqual(hbar.get_char(), bumblebee.output.HBARS[i]) - # edge case for 100% - hbar = bumblebee.output.HBar(100) - self.assertEqual(hbar.get_char(), bumblebee.output.HBARS[-1]) + def test_start(self): + data = json.loads(self.i3.start()) + self.assertEquals(1, data['version'], 'i3bar protocol version 1 expected') + self.assertTrue(data['click_events'], 'click events should be enabled') + def test_begin_status_line(self): + self.assertEquals('[', self.i3.begin_status_line(), 'each line must be a JSON array') -class TestVBar(unittest.TestCase): - """tests for bumblebee.output.VBar""" - def setUp(self): - self.value = 1 - self.values = [10, 20, 30, 40, 55, 65, 80, 90] - self.vbar = bumblebee.output.VBar(self.value) + def test_end_status_line(self): + self.assertEquals('],\n', self.i3.end_status_line(), 'each line must terminate properly') - def test___init__(self): - """bumblebee.output.VBar.__init__()""" - self.assertEqual( - self.vbar.step, bumblebee.output.MAX_PERCENTS / bumblebee.output.CHARS) + def test_stop(self): + self.assertEquals(']\n', self.i3.stop()) - def test_get_chars(self): - """bumblebee.output.VBar.get_char()""" - for i in range(bumblebee.output.CHARS): - vbar = bumblebee.output.VBar(self.values[i]) - self.assertEqual(vbar.get_chars(), bumblebee.output.VBARS[i]) - # edge case for 100% - vbar = bumblebee.output.VBar(100) - self.assertEqual(vbar.get_chars(), bumblebee.output.VBARS[-1]) - # 0.x chars filled - value = 0.1 - vbar = bumblebee.output.VBar(value, 3) - expected_chars = vbar.bars[0] + " " - self.assertEqual(vbar.get_chars(), expected_chars) - # 1.x chars filled - value = 35 - vbar = bumblebee.output.VBar(value, 3) - expected_chars = vbar.bars[-1] + vbar.bars[0] + " " - self.assertEqual(vbar.get_chars(), expected_chars) - # 2.x chars filled - value = 67 - vbar = bumblebee.output.VBar(value, 3) - expected_chars = vbar.bars[-1] * 2 + vbar.bars[0] - self.assertEqual(vbar.get_chars(), expected_chars) +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/test_store.py b/tests/test_store.py deleted file mode 100644 index faa1efb..0000000 --- a/tests/test_store.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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/tests/test_theme.py b/tests/test_theme.py deleted file mode 100644 index ebfe055..0000000 --- a/tests/test_theme.py +++ /dev/null @@ -1,142 +0,0 @@ -# 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_empty_state(self): - theme = self.theme - data = theme.data() - - self.anyModule.state.return_value = "" - self.assertEquals(theme.fg(self.anyWidget), data["defaults"]["fg"]) - self.assertEquals(theme.bg(self.anyWidget), data["defaults"]["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/tests/test_util.py b/tests/test_util.py deleted file mode 100644 index 1a7ee07..0000000 --- a/tests/test_util.py +++ /dev/null @@ -1,90 +0,0 @@ -# pylint: disable=C0103,C0111 - -import unittest -import re - -import tests.mocks as mocks - -import bumblebee.util as bu - - -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(bu.bytefmt(10), "10.00B") - self.assertEquals(bu.bytefmt(15 * 1024), "15.00KiB") - self.assertEquals(bu.bytefmt(20 * 1024 * 1024), "20.00MiB") - self.assertEquals(bu.bytefmt(22 * 1024 * 1024 * 1024), "22.00GiB") - self.assertEquals(bu.bytefmt(35 * 1024 * 1024 * 1024 * 1024), "35840.00GiB") - - fmt = "{:.0f}" - self.assertEquals(bu.bytefmt(10, fmt), "10B") - self.assertEquals(bu.bytefmt(15 * 1024, fmt), "15KiB") - self.assertEquals(bu.bytefmt(20 * 1024 * 1024, fmt), "20MiB") - self.assertEquals(bu.bytefmt(22 * 1024 * 1024 * 1024, fmt), "22GiB") - self.assertEquals(bu.bytefmt(35 * 1024 * 1024 * 1024 * 1024, fmt), "35840GiB") - - def test_durationfmt(self): - self.assertEquals(bu.durationfmt(00), "00:00") - self.assertEquals(bu.durationfmt(25), "00:25") - self.assertEquals(bu.durationfmt(60), "01:00") - self.assertEquals(bu.durationfmt(119), "01:59") - self.assertEquals(bu.durationfmt(3600), "01:00:00") - self.assertEquals(bu.durationfmt(7265), "02:01:05") - - def test_execute(self): - bu.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): - bu.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): - bu.execute(self.some_command_with_args) - - def test_which(self): - # test for a binary that has to be somewhere - print(bu.which("ls")) - self.assertTrue(re.search('/(ls)$', bu.which("ls"))) - - # test for a binary that is not necessarily there - program = "iwgetid" - self.assertTrue( - bu.which(program) is None or - re.search('/(' + program + ')$', bu.which(program)) - ) - - # test if which also works with garbage input - self.assertTrue(bu.which("qwertygarbage") is None) - - def test_asbool(self): - for val in ("t", "true", "y", "yes", "on", "1", 1, True): - self.assertTrue(bu.asbool(val)) - if isinstance(val, str): - self.assertTrue(bu.asbool(val.upper())) - - for val in ("f", "false", "n", "no", "off", "0", 0, False): - self.assertFalse(bu.asbool(val)) - if isinstance(val, str): - self.assertFalse(bu.asbool(val.upper())) - -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/themes/default.json b/themes/default.json deleted file mode 100644 index ddda5fe..0000000 --- a/themes/default.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "icons": [ "ascii" ], - "defaults": { - "urgent": true, - "fg": "#aabbcc" - } -} diff --git a/themes/dracula-powerline.json b/themes/dracula-powerline.json deleted file mode 100644 index 311f46e..0000000 --- a/themes/dracula-powerline.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "icons": [ "awesome-fonts" ], - "defaults": { - "separator-block-width": 0, - "warning": { - "fg": "#282A36", - "bg": "#E5C07B" - }, - "critical": { - "fg": "#282A36", - "bg": "#FF5555" - } - }, - "cycle": [ - {"fg": "#F8F8F2", "bg": "#6272A4"}, - {"fg": "#F8F8F2", "bg": "#282A36"} - ], - "dnf": { - "good": { - "fg": "#282A36", - "bg": "#50FA7B" - } - }, - "pacman": { - "good": { - "fg": "#282A36", - "bg": "#50FA7B" - } - }, - "battery": { - "charged": { - "fg": "#282A36", - "bg": "#50FA7B" - }, - "AC": { - "fg": "#282A36", - "bg": "#50FA7B" - } - }, - "pomodoro": { - "paused": { - "fg": "#282A36", - "bg": "#F1FA8C" - }, - "work": { - "fg": "#50FA7B", - "bg": "#282A36" - }, - "break": { - "fg": "#282A36", - "bg": "#50FA7B" - } - } - -} diff --git a/themes/firefox-dark-powerline.json b/themes/firefox-dark-powerline.json deleted file mode 100644 index 00e7d95..0000000 --- a/themes/firefox-dark-powerline.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "icons": [ "awesome-fonts" ], - "defaults": { - "separator-block-width": 0, - "warning": { - "fg": "#002b36", - "bg": "#b58900" - }, - "critical": { - "fg": "#002b36", - "bg": "#dc322f" - } - }, - "cycle": [ - { "fg": "#B1B1B3", "bg": "#474749" }, - { "fg": "#BEBEBE", "bg": "#323234" } - ], - "dnf": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "pacman": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "pomodoro": { - "paused": { - "fg": "#002b36", - "bg": "#b58900" - }, - "break": { - "fg": "#002b36", - "bg": "#859900" - } - } - - -} diff --git a/themes/greyish-powerline.json b/themes/greyish-powerline.json deleted file mode 100644 index 2e9d9b8..0000000 --- a/themes/greyish-powerline.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "icons": [ "awesome-fonts" ], - "defaults": { - "separator-block-width": 0, - "warning": { - "fg": "#002b36", - "bg": "#b58900" - }, - "critical": { - "fg": "#002b36", - "bg": "#dc322f" - } - }, - "cycle": [ - { "fg": "#EFEFEF", "bg": "#0D0D0D" }, - { "fg": "#EFEFEF", "bg": "#4F4F4F" } - ], - "dnf": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "apt": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "pacman": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "battery": { - "charged": { - "fg": "#002b36", - "bg": "#859900" - }, - "AC": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "pomodoro": { - "paused": { - "fg": "#002b36", - "bg": "#b58900" - }, - "work": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "break": { - "fg": "#002b36", - "bg": "#859900" - } - } - -} diff --git a/themes/gruvbox-light.json b/themes/gruvbox-light.json deleted file mode 100644 index ee4d69e..0000000 --- a/themes/gruvbox-light.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "icons": [ "paxy97", "ascii" ], - "defaults": { - "warning": { - "fg": "#1d2021", - "bg": "#d79921" - }, - "critical": { - "fg": "#fbf1c7", - "bg": "#cc241d" - }, - "default-separators": false, - "separator-block-width": 0 - }, - "cycle": [ - { - "fg": "#1d2021", - "bg": "#ebdbb2" - }, - { - "fg": "#282828", - "bg": "#fbf1c7" - } - ], - "dnf": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "apt": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "battery": { - "charged": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "AC": { - "fg": "#1d2021", - "bg": "#b8bb26" - } - }, - "bluetooth": { - "ON": { - "fg": "#1d2021", - "bg": "#b8bb26" - } - }, - "git": { - "modified": { "bg": "#458588" }, - "deleted": { "bg": "#9d0006" }, - "new": { "bg": "#b16286" } - }, - "pomodoro": { - "paused": { - "fg": "#1d2021", - "bg": "#d79921" - }, - "work": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "break": { - "fg": "#002b36", - "bg": "#859900" - } - } - -} diff --git a/themes/gruvbox-powerline-light.json b/themes/gruvbox-powerline-light.json deleted file mode 100644 index d515df0..0000000 --- a/themes/gruvbox-powerline-light.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "icons": [ "paxy97", "awesome-fonts" ], - "defaults": { - "warning": { - "fg": "#1d2021", - "bg": "#d79921" - }, - "critical": { - "fg": "#fbf1c7", - "bg": "#cc241d" - }, - "default-separators": false, - "separator-block-width": 0 - }, - "cycle": [ - { - "fg": "#1d2021", - "bg": "#ebdbb2" - }, - { - "fg": "#282828", - "bg": "#fbf1c7" - } - ], - "dnf": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "apt": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "battery": { - "charged": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "AC": { - "fg": "#1d2021", - "bg": "#b8bb26" - } - }, - "bluetooth": { - "ON": { - "fg": "#1d2021", - "bg": "#b8bb26" - } - }, - "git": { - "modified": { "bg": "#458588" }, - "deleted": { "bg": "#9d0006" }, - "new": { "bg": "#b16286" } - }, - "pomodoro": { - "paused": { - "fg": "#1d2021", - "bg": "#d79921" - }, - "work": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "break": { - "fg": "#002b36", - "bg": "#859900" - } - } -} diff --git a/themes/gruvbox-powerline.json b/themes/gruvbox-powerline.json deleted file mode 100644 index 3481dbc..0000000 --- a/themes/gruvbox-powerline.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "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" - } - }, - "apt": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "battery": { - "charged": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "AC": { - "fg": "#1d2021", - "bg": "#b8bb26" - } - }, - "bluetooth": { - "ON": { - "fg": "#1d2021", - "bg": "#b8bb26" - } - }, - "git": { - "modified": { "bg": "#458588" }, - "deleted": { "bg": "#9d0006" }, - "new": { "bg": "#b16286" } - }, - "pomodoro": { - "paused": { - "fg": "#1d2021", - "bg": "#d79921" - }, - "work": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "break": { - "fg": "#002b36", - "bg": "#859900" - } - } -} diff --git a/themes/gruvbox.json b/themes/gruvbox.json deleted file mode 100644 index ab71814..0000000 --- a/themes/gruvbox.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "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" - } - }, - "apt": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "battery": { - "charged": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "AC": { - "fg": "#1d2021", - "bg": "#b8bb26" - } - }, - "bluetooth": { - "ON": { - "fg": "#1d2021", - "bg": "#b8bb26" - } - }, - "git": { - "modified": { "bg": "#458588" }, - "deleted": { "bg": "#9d0006" }, - "new": { "bg": "#b16286" } - }, - "pomodoro": { - "paused": { - "fg": "#1d2021", - "bg": "#d79921" - }, - "work": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "break": { - "fg": "#002b36", - "bg": "#859900" - } - } - -} diff --git a/themes/iceberg-dark-powerline.json b/themes/iceberg-dark-powerline.json deleted file mode 100644 index 1b13b84..0000000 --- a/themes/iceberg-dark-powerline.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "icons": [ "awesome-fonts" ], - "defaults": { - "separator-block-width": 0, - "warning": { - "fg": "#0f1117", - "bg": "#e2a478" - }, - "critical": { - "fg": "#0f1117", - "bg": "#e27878" - } - }, - "cycle": [ - { "fg": "#d2d4de", "bg": "#161821" }, - { "fg": "#d2d4de", "bg": "#262939" }, - { "fg": "#d2d4de", "bg": "#353a50" }, - { "fg": "#d2d4de", "bg": "#454b68" }, - { "fg": "#d2d4de", "bg": "#353a50" }, - { "fg": "#d2d4de", "bg": "#262939" }, - { "fg": "#d2d4de", "bg": "#1e212d" } - ], - "dnf": { - "good": { - "fg": "#b4be82", - "bg": "#161821" - } - }, - "apt": { - "good": { - "fg": "#b4be82", - "bg": "#161821" - } - }, - "pacman": { - "good": { - "fg": "#b4be82", - "bg": "#161821" - } - }, - "battery": { - "charged": { - "fg": "#0f1117", - "bg": "#b4be82" - }, - "charging": { - "fg": "#0f1117", - "bg": "#84a0c6" - } - }, - "pomodoro": { - "paused": { - "fg": "#0f1117", - "bg": "#e2a478" - }, - "work": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "break": { - "fg": "#b4be82", - "bg": "#161821" - } - } - -} diff --git a/themes/iceberg-powerline.json b/themes/iceberg-powerline.json deleted file mode 100644 index c7dca23..0000000 --- a/themes/iceberg-powerline.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "icons": [ "awesome-fonts" ], - "defaults": { - "separator-block-width": 0, - "warning": { - "fg": "#0f1117", - "bg": "#e2a478" - }, - "critical": { - "fg": "#0f1117", - "bg": "#e27878" - } - }, - "cycle": [ - { "fg": "#696d80", "bg": "#34394e" }, - { "fg": "#17171b", "bg": "#5a5f72" }, - { "fg": "#17171b", "bg": "#818596" }, - { "fg": "#161821", "bg": "#c6c8d1" }, - { "fg": "#17171b", "bg": "#818596" }, - { "fg": "#17171b", "bg": "#5a5f72" } - ], - "dnf": { - "good": { - "fg": "#b4be82", - "bg": "#161821" - } - }, - "apt": { - "good": { - "fg": "#b4be82", - "bg": "#161821" - } - }, - "pacman": { - "good": { - "fg": "b4be82", - "bg": "#161821" - } - }, - "battery": { - "charged": { - "fg": "#0f1117", - "bg": "#b4be82" - }, - "charging": { - "fg": "#0f1117", - "bg": "#84a0c6" - } - }, - "pomodoro": { - "paused": { - "fg": "#0f1117", - "bg": "#e2a478" - }, - "work": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "break": { - "fg": "#89b8c2", - "bg": "#161821" - } - } -} diff --git a/themes/iceberg-rainbow.json b/themes/iceberg-rainbow.json deleted file mode 100644 index 9c56af6..0000000 --- a/themes/iceberg-rainbow.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "icons": [ "awesome-fonts" ], - "defaults": { - "separator-block-width": 0, - "warning": { - "fg": "#e2a478", - "bg": "#c6c8d1" - }, - "critical": { - "fg": "#e27878", - "bg": "#c6c8d1" - } - }, - "cycle": [ - { "fg": "#161821", "bg": "#e2a478" }, - { "fg": "#161821", "bg": "#e27878" }, - { "fg": "#161821", "bg": "#b4be82" }, - { "fg": "#161821", "bg": "#89b8c2" }, - { "fg": "#161821", "bg": "#84a0c6" }, - { "fg": "#161821", "bg": "#a093c7" } - ], - "dnf": { - "good": { - "fg": "#b4be82", - "bg": "#161821" - } - }, - "apt": { - "good": { - "fg": "#b4be82", - "bg": "#161821" - } - }, - "pacman": { - "good": { - "fg": "b4be82", - "bg": "#161821" - } - }, - "battery": { - "charged": { - "fg": "#b4be82", - "bg": "#161821" - }, - "charging": { - "fg": "#89b8c2", - "bg": "#161821" - } - }, - "pomodoro": { - "paused": { - "fg": "#e2a478", - "bg": "#c6c8d1" - }, - "work": { - "fg": "#89b8c2", - "bg": "#161821" - }, - "break": { - "fg": "#b4be82", - "bg": "#161821" - } - } - -} diff --git a/themes/iceberg.json b/themes/iceberg.json deleted file mode 100644 index 9943eb4..0000000 --- a/themes/iceberg.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "icons": [ "awesome-fonts" ], - "defaults": { - "separator-block-width": 0, - "separator": "", - "warning": { - "fg": "#0f1117", - "bg": "#e2a478" - }, - "critical": { - "fg": "#0f1117", - "bg": "#e27878" - }, - "fg": "#c6c8d1", "bg": "#161821" - }, - "dnf": { - "good": { - "fg": "#0f1117", - "bg": "#b4be82" - } - }, - "apt": { - "good": { - "fg": "#0f1117", - "bg": "#b4be82" - } - }, - "pacman": { - "good": { - "fg": "#0f1117", - "bg": "#b4be82" - } - }, - "battery": { - "charged": { - "fg": "#b4be82", - "bg": "#161821" - }, - "charging": { - "fg": "#89b8c2", - "bg": "#161821" - } - }, - "pomodoro": { - "paused": { - "fg": "#0f1117", - "bg": "#e2a478" - }, - "work": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "break": { - "fg": "#89b8c2", - "bg": "#161821" - } - } - -} diff --git a/themes/icons/ascii.json b/themes/icons/ascii.json deleted file mode 100644 index 01aff79..0000000 --- a/themes/icons/ascii.json +++ /dev/null @@ -1,363 +0,0 @@ -{ - "defaults": { - "padding": " " - }, - "memory": { - "prefix": "ram" - }, - "cpu": { - "prefix": "cpu" - }, - "cpu2": { - "freq": { "prefix": "freq" }, - "load": { "prefix": "load" }, - "loads": { "prefix": "" }, - "temp": { "prefix": "temp" }, - "fan": { "prefix": "fan" } - }, - "disk": { - "prefix": "hdd" - }, - "dnf": { - "prefix": "dnf" - }, - "apt": { - "prefix": "apt" - }, - "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-upower": { - "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" - }, - "unknown-25": { - "suffix": "?" - }, - "unknown-50": { - "suffix": "?" - }, - "unknown-80": { - "suffix": "?" - }, - "unknown-100": { - "suffix": "?" - } - }, - "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" - }, - "unknown-25": { - "suffix": "?" - }, - "unknown-50": { - "suffix": "?" - }, - "unknown-80": { - "suffix": "?" - }, - "unknown-100": { - "suffix": "?" - } - }, - "battery_all": { - "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" - }, - "unknown-25": { - "suffix": "?" - }, - "unknown-50": { - "suffix": "?" - }, - "unknown-80": { - "suffix": "?" - }, - "unknown-100": { - "suffix": "?" - } - }, - "caffeine": { - "activated": { - "prefix": "caf-on" - }, - "deactivated": { - "prefix": "caf-off " - } - }, - "xrandr": { - "on": { - "prefix": " off " - }, - "off": { - "prefix": " on " - }, - "refresh": { - "prefix": " refresh " - } - }, - "redshift": { - "day": { - "prefix": "day" - }, - "night": { - "prefix": "night" - }, - "transition": { - "prefix": "trans" - } - }, - "docker_ps": { - "prefix": "containers" - }, - "sensors": { - "prefix": "sensors" - }, - "traffic": { - "rx": { - "prefix": "down" - }, - "tx": { - "prefix": "up" - } - }, - "mpd": { - "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]" - } - }, - "github": { - "prefix": "github" - }, - "deezer": { - "prefix": "" - }, - "spotify": { - "prefix": "" - }, - "uptime": { - "prefix": "uptime" - }, - "zpool": { - "poolread": { - "prefix": "pool read " - }, - "poolwrite": { - "prefix": "pool write " - }, - "ONLINE": { - "prefix": "pool" - }, - "FAULTED": { - "prefix": "pool (!)" - }, - "DEGRADED": { - "prefix": "pool (!)" - } - }, - "git": { - "main": { - "prefix": "" - }, - "new": { - "prefix": "[n]" - }, - "modified": { - "prefix": "[m]" - }, - "deleted": { - "prefix": "[d]" - } - }, - "dunst": { - "muted": { - "prefix": "dunst(muted)" - }, - "unmuted": { - "prefix": "dunst" - } - }, - "twmn": { - "muted": { - "prefix": "twmn" - }, - "unmuted": { - "prefix": "twmn(muted)" - } - }, - "system": { - "prefix": "system" - }, - "pomodoro": { - "off": { "prefix": "pom" }, - "paused": { "prefix": "||" }, - "on": { "prefix": " >" } - }, - "hddtemp": { - "prefix": "hddtemp" - } -} diff --git a/themes/icons/awesome-fonts.json b/themes/icons/awesome-fonts.json deleted file mode 100644 index 8c6688c..0000000 --- a/themes/icons/awesome-fonts.json +++ /dev/null @@ -1,264 +0,0 @@ -{ - "defaults": { - "separator": "", - "padding": " ", - "unknown": { "prefix": "" } - }, - "date": { "prefix": "" }, - "time": { "prefix": "" }, - "datetime": { "prefix": "" }, - "datetz": { "prefix": "" }, - "timetz": { "prefix": "" }, - "datetimetz": { "prefix": "" }, - "memory": { "prefix": "" }, - "cpu": { "prefix": "" }, - "cpu2": { - "freq": { "prefix": "" }, - "load": { "prefix": "" }, - "loads": { "prefix": "" }, - "temp": { "prefix": "" }, - "fan": { "prefix": "" } - }, - "disk": { "prefix": "" }, - "dnf": { "prefix": "" }, - "apt": { "prefix": "" }, - "pacman": { "prefix": "" }, - "brightness": { "prefix": "" }, - "load": { "prefix": "" }, - "layout": { "prefix": "" }, - "layout-xkb": { "prefix": "" }, - "layout-xkbswitch": { "prefix": "" }, - "notmuch_count": { - "empty": { "prefix": "\uf0e0" }, - "items": { "prefix": "\uf0e0" } - }, - "todo": { - "empty": { "prefix": "" }, - "items": { "prefix": "" }, - "uptime": { "prefix": "" } - }, - "zpool": { - "poolread": { "prefix": "→ " }, - "poolwrite": { "prefix": "← " }, - "ONLINE": { "prefix": "" }, - "FAULTED": { "prefix": "!" }, - "DEGRADED": { "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": "" } - }, - "bluetooth": { - "ON": { "prefix": "" }, - "OFF": { "prefix": "" }, - "?": { "prefix": "" } - }, - "battery-upower": { - "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": "" }, - "unlimited": { "prefix": "", "suffix": "" }, - "estimate": { "prefix": "" }, - "unknown-10": { "prefix": "", "suffix": "" }, - "unknown-25": { "prefix": "", "suffix": "" }, - "unknown-50": { "prefix": "", "suffix": "" }, - "unknown-80": { "prefix": "", "suffix": "" }, - "unknown-100": { "prefix": "", "suffix": "" } - }, - "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": "" }, - "unlimited": { "prefix": "", "suffix": "" }, - "estimate": { "prefix": "" }, - "unknown-10": { "prefix": "", "suffix": "" }, - "unknown-25": { "prefix": "", "suffix": "" }, - "unknown-50": { "prefix": "", "suffix": "" }, - "unknown-80": { "prefix": "", "suffix": "" }, - "unknown-100": { "prefix": "", "suffix": "" } - }, - "battery_all": { - "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": "" }, - "unlimited": { "prefix": "", "suffix": "" }, - "estimate": { "prefix": "" }, - "unknown-10": { "prefix": "", "suffix": "" }, - "unknown-25": { "prefix": "", "suffix": "" }, - "unknown-50": { "prefix": "", "suffix": "" }, - "unknown-80": { "prefix": "", "suffix": "" }, - "unknown-100": { "prefix": "", "suffix": "" } - }, - "caffeine": { - "activated": { "prefix": " " }, - "deactivated": { "prefix": " " } - }, - "xrandr": { - "on": { "prefix": " " }, - "off": { "prefix": " " }, - "refresh": { "prefix": "" } - }, - "redshift": { - "day": { "prefix": "" }, - "night": { "prefix": "" }, - "transition": { "prefix": "" } - }, - "docker_ps": { - "prefix": "" - }, - "sensors": { - "prefix": "" - }, - "sensors2": { - "temp": { "prefix": "" }, - "fan": { "prefix": "" }, - "cpu": { "prefix": "" } - }, - "traffic": { - "rx": { "prefix": "" }, - "tx": { "prefix": "" } - }, - "network_traffic": { - "rx": { "prefix": "" }, - "tx": { "prefix": "" } - }, - "mpd": { - "playing": { "prefix": "" }, - "paused": { "prefix": "" }, - "stopped": { "prefix": "" }, - "prev": { "prefix": "" }, - "next": { "prefix": "" }, - "shuffle-on": { "prefix": "" }, - "shuffle-off": { "prefix": "" }, - "repeat-on": { "prefix": "" }, - "repeat-off": { "prefix": "" } - }, - "arch-update": { - "prefix": " " - }, - "github": { - "prefix": "  " - }, - "deezer": { - "prefix": "  " - }, - "spotify": { - "prefix": "  " - }, - "publicip": { - "prefix": "  " - }, - "weather": { - "clouds": { "prefix": "" }, - "rain": { "prefix": "" }, - "snow": { "prefix": "" }, - "clear": { "prefix": "" }, - "thunder": { "prefix": "" } - }, - "taskwarrior": { - "prefix": "  " - }, - "progress": { - "copying": { - "prefix": "" - } - }, - "git": { - "main": { "prefix": "" }, - "new": { "prefix": "" }, - "modified": { "prefix": "" }, - "deleted": { "prefix": "" } - }, - "dunst": { - "muted": { "prefix": "" }, - "unmuted": { "prefix": "" } - }, - "twmn": { - "muted": { "prefix": "" }, - "unmuted": { "prefix": "" } - }, - "pihole": { - "enabled": { "prefix": "" }, - "disabled": { "prefix": "" } - }, - "vpn": { - "prefix": "" - }, - "system": { - "prefix": "  " - }, - "sun": { - "prefix": "" - }, - "rss": { - "prefix": "" - }, - "pomodoro": { - "off": { "prefix": "" }, - "paused": { "prefix": "" }, - "work": { "prefix": "" }, - "break": { "prefix": "" } - }, - "hddtemp": { "prefix": "" } -} diff --git a/themes/icons/ionicons.json b/themes/icons/ionicons.json deleted file mode 100644 index cf7813a..0000000 --- a/themes/icons/ionicons.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "defaults": { - "separator": "\ue0b2", - "padding": "\u2800", - "unknown": { "prefix": "\uf100" } - }, - "date": { "prefix": "\uf2d1" }, - "time": { "prefix": "\uf3b3" }, - "datetime": { "prefix": "\uf3b3" }, - "memory": { "prefix": "\uf389" }, - "cpu": { "prefix": "\uf4b0" }, - "disk": { "prefix": "\u26c1" }, - "dnf": { "prefix": "\uf2be" }, - "apt": { "prefix": "\uf2be" }, - "pacman": { "prefix": "\uf2be" }, - "brightness": { "prefix": "\u263c" }, - "load": { "prefix": "\uf13d" }, - "layout": { "prefix": "\uf38c" }, - "layout-xkb": { "prefix": "\uf38c" }, - "todo": { - "empty": { "prefix": "\uf453" }, - "items": { "prefix": "\uf454" }, - "uptime": { "prefix": "\uf4c1" } - }, - "zpool": { - "poolread": { "prefix": "\u26c1\uf3d6" }, - "poolwrite": { "prefix": "\u26c1\uf3d5" }, - "ONLINE": { "prefix": "\u26c1" }, - "FAULTED": { "prefix": "\u26c1\uf3bc" }, - "DEGRADED": { "prefix": "\u26c1\uf3bc" } - }, - "cmus": { - "playing": { "prefix": "\uf488" }, - "paused": { "prefix": "\uf210" }, - "stopped": { "prefix": "\uf24f" }, - "prev": { "prefix": "\uf4ab" }, - "next": { "prefix": "\uf4ad" }, - "shuffle-on": { "prefix": "\uf4a8" }, - "shuffle-off": { "prefix": "\uf453" }, - "repeat-on": { "prefix": "\uf459" }, - "repeat-off": { "prefix": "\uf30f" } - }, - "gpmdp": { - "playing": { "prefix": "\uf488" }, - "paused": { "prefix": "\uf210" }, - "stopped": { "prefix": "\uf24f" }, - "prev": { "prefix": "\uf4ab" }, - "next": { "prefix": "\uf4ad" } - }, - "pasink": { - "muted": { "prefix": "\uf3b9" }, - "unmuted": { "prefix": "\uf3ba" } - }, - "amixer": { - "muted": { "prefix": "\uf3b9" }, - "unmuted": { "prefix": "\uf3ba" } - }, - "pasource": { - "muted": { "prefix": "\uf395" }, - "unmuted": { "prefix": "\uf2ec" } - }, - "kernel": { - "prefix": "\uf17c" - }, - "nic": { - "wireless-up": { "prefix": "\uf25c" }, - "wireless-down": { "prefix": "\uf3d0" }, - "wired-up": { "prefix": "\uf270" }, - "wired-down": { "prefix": "\uf271" }, - "tunnel-up": { "prefix": "\uf133" }, - "tunnel-down": { "prefix": "\uf306" } - }, - "bluetooth": { - "ON": { "prefix": "\uf116" }, - "OFF": { "prefix": "\uf116" }, - "?": { "prefix": "\uf116" } - }, - "battery": { - "charged": { "prefix": "\uf113", "suffix": "\uf493" }, - "AC": { "suffix": "\uf493" }, - "charging": { - "prefix": ["\uf112", "\uf115", "\uf114", "", "\uf111"], - "suffix": "\uf493" - }, - "discharging-10": { "prefix": "\uf112", "suffix": "\uf3bc" }, - "discharging-25": { "prefix": "\uf115", "suffix": "\uf3e6" }, - "discharging-50": { "prefix": "\uf115", "suffix": "\uf3e6" }, - "discharging-80": { "prefix": "\uf114", "suffix": "\uf3e6" }, - "discharging-100": { "prefix": "\uf113", "suffix": "\uf3e6" }, - "unknown-10": { "prefix": "\uf112", "suffix": "\uf3bc" }, - "unknown-25": { "prefix": "\uf115", "suffix": "\uf142" }, - "unknown-50": { "prefix": "\uf115", "suffix": "\uf142" }, - "unknown-80": { "prefix": "\uf114", "suffix": "\uf142" }, - "unknown-100": { "prefix": "\uf113", "suffix": "\uf142" }, - "unlimited": { "prefix": "\uf402", "suffix": "\uf493" }, - "estimate": { "prefix": "\uf402" } - }, - "battery_all": { - "charged": { "prefix": "\uf113", "suffix": "\uf493" }, - "AC": { "suffix": "\uf493" }, - "charging": { - "prefix": ["\uf112", "\uf115", "\uf114", "", "\uf111"], - "suffix": "\uf493" - }, - "discharging-10": { "prefix": "\uf112", "suffix": "\uf3bc" }, - "discharging-25": { "prefix": "\uf115", "suffix": "\uf3e6" }, - "discharging-50": { "prefix": "\uf115", "suffix": "\uf3e6" }, - "discharging-80": { "prefix": "\uf114", "suffix": "\uf3e6" }, - "discharging-100": { "prefix": "\uf113", "suffix": "\uf3e6" }, - "unknown-10": { "prefix": "\uf112", "suffix": "\uf3bc" }, - "unknown-25": { "prefix": "\uf115", "suffix": "\uf142" }, - "unknown-50": { "prefix": "\uf115", "suffix": "\uf142" }, - "unknown-80": { "prefix": "\uf114", "suffix": "\uf142" }, - "unknown-100": { "prefix": "\uf113", "suffix": "\uf142" }, - "unlimited": { "prefix": "\uf402", "suffix": "\uf493" }, - "estimate": { "prefix": "\uf402" } - }, - "caffeine": { - "activated": { "prefix": "\uf272\u3000\uf354" }, - "deactivated": { "prefix": "\uf272\u3000\uf355" } - }, - "xrandr": { - "on": { "prefix": "\uf465\u3000\uf354" }, - "off": { "prefix": "\uf465\u3000\uf355" } - }, - "redshift": { - "day": { "prefix": "\uf4b6" }, - "night": { "prefix": "\uf467" }, - "transition": { "prefix": "\uf475" } - }, - "sensors": { - "prefix": "\uf3b6" - }, - "traffic": { - "rx": { "prefix": "\uf365" }, - "tx": { "prefix": "\uf35f" } - }, - "network_traffic": { - "rx": { "prefix": "\uf365" }, - "tx": { "prefix": "\uf35f" } - }, - "mpd": { - "playing": { "prefix": "\uf488" }, - "paused": { "prefix": "\uf210" }, - "stopped": { "prefix": "\uf24f" }, - "prev": { "prefix": "\uf4ab" }, - "next": { "prefix": "\uf4ad" }, - "shuffle-on": { "prefix": "\uf4a8" }, - "shuffle-off": { "prefix": "\uf453" }, - "repeat-on": { "prefix": "\uf459" }, - "repeat-off": { "prefix": "\uf30f" } - }, - "github": { - "prefix": "\uf233" - }, - "deezer": { - "prefix": "\uf305" - }, - "spotify": { - "prefix": "\uf305" - }, - "publicip": { - "prefix": "\uf268" - }, - "weather": { - "clouds": { "prefix": "\uf12b" }, - "rain": { "prefix": "\uf495" }, - "snow": { "prefix": "\uf4ae" }, - "clear": { "prefix": "\uf3b0" }, - "thunder": { "prefix": "\uf4bd" } - }, - "taskwarrior": { - "prefix": "\uf454" - }, - "dunst": { - "muted": { "prefix": "\uf39a" }, - "unmuted": { "prefix": "\uf39b" } - }, - "twmn": { - "muted": { "prefix": "\uf1f6" }, - "unmuted": { "prefix": "\uf0f3" } - }, - "system": { - "prefix": " \uf2a9 " - }, - "sun": { - "prefix": "\uf3b0" - }, - "rss": { - "prefix": "\uf1ea" - }, - "pomodoro": { - "off": { "prefix": "\uf24f" }, - "paused": { "prefix": "\uf210" }, - "on": { "prefix": "\uf488" } - } - -} diff --git a/themes/icons/paxy97.json b/themes/icons/paxy97.json deleted file mode 100644 index 9a889ab..0000000 --- a/themes/icons/paxy97.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "memory": { - "prefix": "  " - } -} diff --git a/themes/icons/test.json b/themes/icons/test.json deleted file mode 100644 index ad17178..0000000 --- a/themes/icons/test.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "test-widget": { - "prefix": "widget-prefix", - "suffix": "widget-suffix" - } -} diff --git a/themes/onedark-powerline.json b/themes/onedark-powerline.json deleted file mode 100644 index d0ca586..0000000 --- a/themes/onedark-powerline.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "icons": [ "awesome-fonts" ], - "defaults": { - "separator-block-width": 0, - "warning": { - "fg": "#282C34", - "bg": "#E5C07B" - }, - "critical": { - "fg": "#282C34", - "bg": "#E06C75" - } - }, - "cycle": [ - {"fg": "#ABB2BF", "bg": "#4B5263"}, - {"fg": "#ABB2BF", "bg": "#282C34"} - ], - "dnf": { - "good": { - "fg": "#282C34", - "bg": "#98C379" - } - }, - "pacman": { - "good": { - "fg": "#282C34", - "bg": "#98C379" - } - }, - "battery": { - "charged": { - "fg": "#282C34", - "bg": "#98C379" - }, - "AC": { - "fg": "#282C34", - "bg": "#98C379" - } - }, - "pomodoro": { - "paused": { - "fg": "#282C34", - "bg": "#E5C07B" - }, - "work": { - "fg": "#98C379", - "bg": "#282C34" - }, - "break": { - "fg": "#282C34", - "bg": "#98C379" - } - } - -} diff --git a/themes/powerline.json b/themes/powerline.json deleted file mode 100644 index b2b603d..0000000 --- a/themes/powerline.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "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" - } - }, - "apt": { - "good": { - "fg": "#494949", - "bg": "#41db00" - } - }, - "battery": { - "charged": { - "fg": "#494949", - "bg": "#41db00" - }, - "AC": { - "fg": "#494949", - "bg": "#41db00" - } - }, - "pomodoro": { - "paused": { - "fg": "#d75f00", - "bg": "#ffd700" - }, - "work": { - "fg": "#ffd700", - "bg": "#d75f00" - }, - "break": { - "fg": "#494949", - "bg": "#41db00" - } - } - -} diff --git a/themes/sac_red.json b/themes/sac_red.json deleted file mode 100644 index 7f6be68..0000000 --- a/themes/sac_red.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "icons": [ "paxy97", "awesome-fonts" ], - "defaults": { - "warning": { - "fg": "#FDFFFC", - "bg": "#B91372" - }, - "critical": { - "fg": "#FDFFFC", - "bg": "#FF0022" - }, - "default-separators": false, - "separator-block-width": 0 - }, - "cycle": [ - { - "fg": "#FDFFFC", - "bg": "#990014" - }, - { - "fg": "#FDFFFC", - "bg": "#011627" - } - ], - "battery": { - "charged": { - "fg": "#FDFFFC", - "bg": "#41EAD4" - }, - "AC": { - "fg": "#FDFFFC", - "bg": "#41EAD4" - } - }, - "bluetooth": { - "ON": { - "fg": "#FDFFFC", - "bg": "#41EAD4" - } - }, - "cmus": { - "bg": "#C42021" - }, - "pomodoro": { - "paused": { - "fg": "#FDFFFC", - "bg": "#B91372" - }, - "work": { - "fg": "#FDFFFC", - "bg": "#41EAD4" - }, - "break": { - "fg": "#FDFFFC", - "bg": "#011627" - } - } - -} diff --git a/themes/solarized-dark-awesome.json b/themes/solarized-dark-awesome.json deleted file mode 100644 index 7274dc7..0000000 --- a/themes/solarized-dark-awesome.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "icons": [ "awesome-fonts" ], - "defaults": { - "separator-block-width": 0, - "separator": "", - "warning": { - "fg": "#002b36", - "bg": "#b58900" - }, - "critical": { - "fg": "#002b36", - "bg": "#dc322f" - }, - "fg": "#93a1a1", "bg": "#002b36" - }, - "dnf": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "apt": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "pacman": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "battery": { - "charged": { - "fg": "#002b36", - "bg": "#859900" - }, - "AC": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "git": { - "modified": { "bg": "#2aa198" }, - "deleted": { "bg": "#d33682" }, - "new": { "bg": "#859900" } - }, - "pomodoro": { - "paused": { - "fg": "#002b36", - "bg": "#b58900" - }, - "work": { - "fg": "#eee8d5", - "bg": "#586e75" - }, - "break": { - "fg": "#002b36", - "bg": "#859900" - } - } -} diff --git a/themes/solarized-powerline.json b/themes/solarized-powerline.json deleted file mode 100644 index 25854f5..0000000 --- a/themes/solarized-powerline.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "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" - } - }, - "apt": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "battery": { - "charged": { - "fg": "#002b36", - "bg": "#859900" - }, - "AC": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "git": { - "modified": { "bg": "#2aa198" }, - "deleted": { "bg": "#d33682" }, - "new": { "bg": "#859900" } - }, - "pomodoro": { - "paused": { - "fg": "#002b36", - "bg": "#b58900" - }, - "work": { - "fg": "#eee8d5", - "bg": "#586e75" - }, - "break": { - "fg": "#002b36", - "bg": "#859900" - } - } -} diff --git a/themes/solarized.json b/themes/solarized.json deleted file mode 100644 index e6f0082..0000000 --- a/themes/solarized.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "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" - } - }, - "apt": { - "good": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "battery": { - "charged": { - "fg": "#002b36", - "bg": "#859900" - }, - "AC": { - "fg": "#002b36", - "bg": "#859900" - } - }, - "git": { - "modified": { "bg": "#2aa198" }, - "deleted": { "bg": "#d33682" }, - "new": { "bg": "#859900" } - }, - "pomodoro": { - "paused": { - "fg": "#002b36", - "bg": "#b58900" - }, - "work": { - "fg": "#eee8d5", - "bg": "#586e75" - }, - "break": { - "fg": "#002b36", - "bg": "#859900" - } - } - -} diff --git a/themes/test.json b/themes/test.json deleted file mode 100644 index 1d457a7..0000000 --- a/themes/test.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "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/themes/test_cycle.json b/themes/test_cycle.json deleted file mode 100644 index 5fd7e1a..0000000 --- a/themes/test_cycle.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "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/themes/test_invalid.json b/themes/test_invalid.json deleted file mode 100644 index d510f27..0000000 --- a/themes/test_invalid.json +++ /dev/null @@ -1 +0,0 @@ -this is really not json diff --git a/themes/wal-powerline.json b/themes/wal-powerline.json deleted file mode 100644 index 8b28d3a..0000000 --- a/themes/wal-powerline.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "icons": [ "awesome-fonts" ], - "colors": [ "wal" ], - "defaults": { - "separator-block-width": 0, - "critical": { - "fg": "cursor", - "bg": "color5" - }, - "warning": { - "fg": "cursor", - "bg": "color6" - }, - "default_separators": false - }, - "cycle": [ - { - "fg": "foreground", - "bg": "background" - }, - { - "fg": "background", - "bg": "foreground" - } - ], - "dnf": { - "good": { - "fg": "background", - "bg": "color3" - } - }, - "apt": { - "good": { - "fg": "background", - "bg": "color3" - } - }, - "battery": { - "charged": { - "fg": "background", - "bg": "color3" - }, - "AC": { - "fg": "background", - "bg": "color3" - } - }, - "pomodoro": { - "paused": { - "fg": "cursor", - "bg": "color6" - }, - "work": { - "fg": "background", - "bg": "foreground" - }, - "break": { - "fg": "background", - "bg": "color3" - } - } - -}