Compare commits

...

308 commits
v2.1.5 ... main

Author SHA1 Message Date
676bbebf4c
bluetooth2: states and styling 2024-04-17 23:05:16 +02:00
617a12f96d
Merge branch 'fix/bluetooth' 2024-04-17 22:53:03 +02:00
5053bb0f1b
themes/awesome-fonts: fix bluetooth icons 2024-04-17 22:47:45 +02:00
21060a10a0
bluetooth2: only show connection count in cases of excessive numbers of connections 2024-04-17 22:46:18 +02:00
a56b86100a
fix updated nerdfonts 2023-12-06 22:17:49 +01:00
fd4b940d58
nic.py: adapting wifi-menu path 2023-12-06 22:01:20 +01:00
2bbac991db
battery: updating nerd font icons 2023-11-10 21:03:43 +01:00
255794cd1c
vpn: updating nerd font icon 2023-11-10 20:51:31 +01:00
2e81eed830
pulsein: replacing muted nerd font icon with slash in the other direction, just to align it with bluetooth2 2023-11-10 20:47:35 +01:00
60bfb19378
pulseout: adding nerd font icons for low, mid, heigh, muted 2023-11-10 20:44:16 +01:00
d7d4603855
wlrotation: adding awesome-fonts icons 2023-11-10 20:03:44 +01:00
cbd0c58b4a
wlrotation: refactored the entire module and integrated external services 2023-11-10 19:44:33 +01:00
bbc26c263c
wlrotation: fix nerd font icon 2023-11-10 16:51:07 +01:00
53de1b524a
bluetooth2: using dbus api, shortening output, some refactoring 2023-11-10 16:50:16 +01:00
c3d4fce74c
nic: fix syntax 2023-11-10 16:48:05 +01:00
8cc7c9de9b
icons: fix nerd font battery/ac 2023-11-10 16:46:38 +01:00
3ed26c62a5
vpn: shorter default label 2023-11-08 22:59:56 +01:00
900a0710c5
nic: add click handler 2023-11-08 22:56:23 +01:00
2e18d71284
battery: better support for pen battery and some formatting 2023-11-08 22:46:58 +01:00
61123eb7a0
fix wlrotation: migrated to new api 2023-11-08 21:52:08 +01:00
27be30263f
Merge branch 'feature/wlrotation' into merge 2023-11-08 20:51:27 +01:00
559517f345
update golor theme and icons 2023-11-08 20:28:49 +01:00
b45ff330c9
wlrotation: init 2023-11-08 00:38:39 +01:00
tobi-wan-kenobi
bfafd93643 fix: remove power from tests installation as a quickfix 2023-10-26 09:19:46 +02:00
tobi-wan-kenobi
c14ed1166d fix: autotest - try to pin down pip versions 2023-10-26 09:11:39 +02:00
tobi-wan-kenobi
9f6c9cc7d2 doc: update readthedocs.yaml 2023-10-26 09:08:43 +02:00
tobi-wan-kenobi
025d3fcb51 fix(module/shell): synchonous invocation was broken
For some (unknown) reason, redrawing while in the update loop breaks the
update (this probably warrants a closer look).

As a quickfix to restore functionality, remove the unnecessary redraw
call and move it into the async codepath, where it is actually needed.

fixes #1001
2023-10-26 09:00:09 +02:00
tobi-wan-kenobi
05622f985a fix(module/shell): expand user directory in command spec
Make sure that ~/ is expanded to the user's home directory in the
command specified for the shell module.

fixes #1002
2023-10-26 08:59:14 +02:00
tobi-wan-kenobi
c4a3f488aa
Merge pull request #1000 from Sigggii/main
Add power-profile module
2023-10-04 06:25:02 +02:00
siggi
68de299763 Merge remote-tracking branch 'origin/main' 2023-10-03 22:36:23 +02:00
siggi
85760926d7 Add Power-Profile module 2023-10-03 22:28:28 +02:00
tobi-wan-kenobi
3bc3c75ff4
Merge pull request #997 from zetxx/patch-2
Update modules.rst
2023-10-02 14:38:16 +02:00
Elin Angelov
d30e5c694e
Update modules.rst 2023-10-02 12:31:15 +03:00
tobi-wan-kenobi
b217ac9c9e docs: update module documentation 2023-10-01 10:07:03 +02:00
tobi-wan-kenobi
fcda13cac0
Merge pull request #994 from zetxx/patch-1
feat: add `disabled` parameter
2023-09-20 14:28:53 +02:00
Elin Angelov
0c2123cfe4
fix: identation 2023-09-20 15:11:24 +03:00
Elin Angelov
9251217bb3
feat: add disabled parameter
it pauses dunst notification on startup
2023-09-20 14:59:24 +03:00
tobi-wan-kenobi
9170785ed5 fix(module-shell): fix syntax error 2023-09-15 15:21:38 +02:00
tobi-wan-kenobi
2e7e75a27c feat(shell): add timeout warning and faster update
see #990
2023-09-15 15:15:31 +02:00
tobi-wan-kenobi
d303b794f3 feat(docs): clarify line continuation
fixes #987
2023-09-15 15:11:23 +02:00
tobi-wan-kenobi
b42323013d fix(config): make config file keys case sensitive
Since the configparser library by default parses keys case insensitive
(all lowercase), certain mappings, especially in the pulseaudio modules,
could fail ("internal" pulseaudio device names are matched against
entries in the configuration).

fixes #992
2023-09-15 15:06:57 +02:00
tobi-wan-kenobi
8583b5123e
Merge pull request #991 from LokiLuciferase/feature/title-change-on-workspace-switch
poll window title also on workspace change (not only window events)
2023-09-13 21:25:16 +02:00
Lukas Lüftinger
b762132037 poll window title also on workspace change (not only window events) 2023-09-13 18:02:26 +02:00
tobi-wan-kenobi
fded39fa81 [modules/pulsectl] make device names case sensitive
A previous change accidentially changed the "pretty" device name mapping
to be required to be in lowercase (rather than the exact name of the
devices. Restore previous functionality.

fixes #989
2023-09-11 09:36:47 +02:00
tobi-wan-kenobi
9c5e30ac61
Merge pull request #988 from TheEdgeOfRage/pulsectl-filter
Add device filter support to pulsectl popup menu
2023-09-07 10:32:54 +02:00
Pavle Portic
9e6e656fa8
Add device filter support to pulsectl popup menu 2023-09-06 22:34:38 +02:00
tobi-wan-kenobi
b9e45ca994
Merge pull request #986 from TheEdgeOfRage/pulsectl-font-size
Allow adjusting the fontsize of the pulsectl default device popup
2023-09-06 16:00:46 +02:00
Pavle Portic
37b5646d65
Allow adjusting the font size of tk popups 2023-09-06 12:11:57 +02:00
tobi-wan-kenobi
d03e6307f5 [contrib/stock] change API to alphavantage.co
Changed the stock module to work again (with an API key), and make it
easier to change the data provider by the user.

fixes #971
2023-07-21 14:18:17 +02:00
tobi-wan-kenobi
1471d8824b
Merge pull request #980 from sazk07/main
Fix: pipewire.py module
2023-07-20 00:35:40 +02:00
Shahan Arshad
04a222f3c8
Fix: pipewire.py module
mousewheel up and down events were initially not working on my bumblebee status bar. After verifying on the command line, it seems that wpctl set-volume command requires sink ID first and percentage change as the second arg.

steps to verify, assuming wireplumber is installed and sink ID is 32

```sh
wpctl set-volume 32 50%
```
will set the volume to 50%

```sh
wpctl set-volume 50% 32
```
will result in `Object '52' not found
2023-07-20 02:16:24 +05:00
tobi-wan-kenobi
868fdbedd3
Merge pull request #979 from sazk07/patch-1
Add: missing mention of pipewire module on website
2023-07-19 20:05:50 +02:00
Shahan Arshad
e1f50f4782
Add: missing mention of pipewire module on website
I was reading the docs on the website(https://bumblebee-status.readthedocs.io/en/latest/modules.html#contrib) and there was no mention of the pipewire module which exists among the contrib modules. This edit will inform users about the pipewire module
2023-07-19 22:10:05 +05:00
tobi-wan-kenobi
f855d5c235 [modules/aur-update] hide if no packages
fixes #978
2023-07-17 12:58:48 +02:00
tobi-wan-kenobi
2546dbae2e [tests/amixer] fix tests 2023-07-17 12:55:06 +02:00
tobi-wan-kenobi
d27986a316 [requirements] remove json requirement - which is a builtin 2023-07-17 12:43:06 +02:00
tobi-wan-kenobi
4df5272164 [workflows] update python versions 2023-07-17 12:36:32 +02:00
tobi-wan-kenobi
839d79e68f
Merge pull request #977 from jebaum/main
allow setting sink ID in pipewire module
2023-07-17 01:41:01 +02:00
James Baumgarten
ee796f6589 allow setting sink ID in pipewire module 2023-07-16 15:06:31 -06:00
tobi-wan-kenobi
9b4944c53f
Merge pull request #976 from lasnikr/main
[modules/usage] A module for "ActivityWatch"
2023-07-10 22:03:02 +02:00
Lasnik
8178919e2c add description 2023-07-10 16:52:22 +02:00
Lasnik
305f9cf491 formatting guidelines 2023-07-10 15:51:00 +02:00
Lasnik
b866ab25b6 create module 2023-07-10 15:42:11 +02:00
tobi-wan-kenobi
775210db08
Merge pull request #974 from hugoeustaquio/main
Adding suport for multiple sound cards, not only devices.
2023-06-30 06:15:10 +02:00
Hugo Eustáquio
2ef6f84df3 Adding suport for multiple sound cards, not only devices. 2023-06-29 14:38:54 -03:00
tobi-wan-kenobi
a97e6f2f7d
Merge pull request #973 from SuperQ/cpu3
[modules/cpu3] Add new CPU module
2023-06-19 14:46:32 +02:00
SuperQ
0b4ff04be5
[modules/cpu3] Add new CPU module
Based on cpu2 module, but use `sensors -j` and some json path walking to
better parse CPU temp and fan speeds. The output of `sensors -u` can
contain many duplicates of the same sensor name, but from different
sensor device paths.

Signed-off-by: SuperQ <superq@gmail.com>
2023-06-19 14:08:16 +02:00
tobi-wan-kenobi
7cda35c1df
Merge pull request #972 from bbernhard/fix_pihole
use API token instead of password hash in pihole module
2023-05-28 18:08:38 +02:00
Bernhard B
b3007dd042 use API token instead of password hash in pihole module
* with newer versions of pi-hole, it is not possible
  anymore to use the password hash for the API authentication.
  Instead, one needs to use the dedicated API token for that.
  In order to stay backwards compatible, and not break existing
  bumblebee_status setups, the 'pwhash' parameter is still supported
  (in case someone runs an outdated pi-hole version).

* the new pi-hole API endpoints do not allow to access the summary
  endpoint without an API token. So, therefore '&auth=<api token>' was
  added.
2023-05-28 17:13:52 +02:00
tobi-wan-kenobi
8967eec44b [module/watson] Add formatting string
Make it possible to customize the watson message in the widget

see #963
2023-05-11 14:28:32 +02:00
tobi-wan-kenobi
14f19c897a [modules/pulsectl] add default device selection
re-enable functionality to add a popup that allows the user to select
the default source/sink.

fixes #965
2023-05-11 08:45:03 +02:00
tobi-wan-kenobi
e9696b2150 [readthedocs] explicitly specify build OS
fixes #970
2023-05-11 08:30:59 +02:00
tobi-wan-kenobi
c0526f2775
Merge pull request #969 from Duarte-Figueiredo/fix-build
Fixed typo in 'today' that is currently breaking the tests
2023-05-06 12:45:19 +02:00
Duarte Figueiredo
6b4898017f Fixed typo in 'today' that is currently breaking the tests 2023-05-06 11:20:45 +01:00
tobi-wan-kenobi
bdfc4fdab4
Merge pull request #968 from Duarte-Figueiredo/main
Updated gitlab module to have state of warning when there is at least 1 notification, just like the github module
2023-05-06 12:01:24 +02:00
Duarte Figueiredo
a6de61b751 Updated gitlab module to have state of warning when there is at least 1 notification, just like the github module 2023-05-06 10:56:17 +01:00
tobi-wan-kenobi
1dd39a4e43
Merge pull request #967 from Duarte-Figueiredo/main
[modules/todoist] - New module that connects to https://api.todoist.com
2023-04-19 14:20:59 +02:00
Duarte Figueiredo
592d08c082 removed Final import because of python3.7 backwards compatibility 2023-04-19 12:01:07 +01:00
Duarte Figueiredo
cad45ecd2c [modules/todoist] - New module that connects to https://api.todoist.com and displays number of tasks due 2023-04-19 11:50:25 +01:00
tobi-wan-kenobi
79081ebb4f
Merge pull request #966 from Duarte-Figueiredo/main
[modules/wakatime] - New module that connects to https://wakatime.com api
2023-04-16 15:24:50 +02:00
Duarte Figueiredo
1b0478edd4 changed icon from normal w to font-awesome clock 2023-04-16 11:38:13 +01:00
Duarte Figueiredo
2b4e2b2c82 rename mock_summaries_api_response test function 2023-04-16 11:29:31 +01:00
Duarte Figueiredo
42e041ce03 [modules/wakatime] - New module that connects to https://wakatime.com and displays coding duration stats 2023-04-16 11:24:51 +01:00
tobi-wan-kenobi
e58afff48a
Merge pull request #964 from dmturner/weather
Change OpenWeatherMap request url from HTTP to HTTPS
2023-04-13 14:26:12 +02:00
dmturner
f34e02d824 Change OpenWeatherMap request url from HTTP to HTTPS 2023-04-13 12:49:39 +01:00
tobi-wan-kenobi
2e1289f778 [core] fix importlib.util error
add explicit import of importlib.util

fixes #962
2023-04-11 12:42:55 +02:00
tobi-wan-kenobi
b750d96a72
Merge pull request #959 from chedge/pipx_compatibility
Added path for themes directory when installed via pipx
2023-03-26 01:30:32 +01:00
C H
61e38c6094 Added path for themes directory when installed via pipx 2023-03-25 15:27:42 -07:00
tobi-wan-kenobi
ad8b1802f5
Merge pull request #957 from LokiLuciferase/fix/playerctl-calls
remove unnecessary `playerctl` subprocess call to determine whether widget should be hidden
2023-03-15 20:03:14 +01:00
Lukas Lüftinger
99bd2a81b6 remove unnecessary playerctl calls to determine whether widgets should be hidden 2023-03-15 19:01:48 +01:00
tobi-wan-kenobi
93f3da1e08
Merge pull request #951 from beckcl/gitlab-module
Add GitLab module
2023-02-19 08:04:37 +01:00
Clemens Beck
7161ef211c [modules/gitlab] add module 2023-02-19 03:43:14 +01:00
tobi-wan-kenobi
f77f5552ae
Merge pull request #950 from jebaum/main
fix bug in pipewire module
2023-02-11 08:22:02 +01:00
James Baumgarten
be332005fa fix bug in pipewire module 2023-02-10 08:52:59 -07:00
tobi-wan-kenobi
cc883d1723
Merge pull request #949 from jebaum/main
add pipewire module
2023-02-04 07:58:45 +01:00
James Baumgarten
30362cb124 add pipewire module 2023-02-03 21:23:34 -07:00
tobi-wan-kenobi
098f03ac52
Merge pull request #947 from arivarton/gcalendar_fixes
Added a max_chars parameter to be able to control the widget width.
2023-01-29 17:04:24 +01:00
arivarton
ae29c1b79f Divided date/time and summary into two widgets and made the summary
widget scrollable.
2023-01-29 13:05:13 +01:00
arivarton
b327162f3b Added a max_chars parameter to be able to control the widget width.
Also moved the try block a bit further up to catch network errors.
2023-01-04 21:34:21 +01:00
tobi-wan-kenobi
8eb2545eed
Merge pull request #943 from pvutov/main
Documentation: Fix the default format string for nvidiagpu
2022-11-30 20:07:58 +01:00
pvutov
f0ce6a1f7f
Documentation: Fix the default format string for nvidiagpu 2022-11-30 21:04:33 +02:00
tobi-wan-kenobi
a6f2e6fc5e [modules/mpd] make mpd port configurable
fixes #941
2022-11-27 17:41:48 +01:00
tobi-wan-kenobi
87a2890b48 [modules/pulsectl] fix case when no devices are available
no devices lead to an exception that completely stopped bumblebee-status
from processing data.

handle this case more gracefully by defaulting to a volume of 0%. if
this proves to be an issue, we can still add error indicators later.

see #940
2022-11-27 12:03:35 +01:00
tobi-wan-kenobi
6a93238bda [core] log exceptions
to enable error investigation, log exceptions.

see #940
2022-11-27 09:46:55 +01:00
tobi-wan-kenobi
79ce2167b0 [autotest] update codeclimate action 2022-11-26 12:09:04 +01:00
tobi-wan-kenobi
1fef60b32c [tests] fix location tests 2022-11-26 12:05:42 +01:00
tobi-wan-kenobi
0bc2c6b8e1 [tests] remove unsupported python version 2022-11-26 10:17:09 +01:00
tobi-wan-kenobi
07e2364f78 [main] fix i3 protocol buf on error messages ("could not parse JSON")
Errors during startup currently cause bumblebee-status to mistakenly
output the first line of output (the "version" line) of the i3 protocol
twice, causing an error message that says "could not parse JSON")

see #940
2022-11-26 10:11:51 +01:00
tobi-wan-kenobi
5412591a0e
Merge pull request #934 from tfwiii/main
publicip - Bug Fix
2022-10-12 09:03:14 +02:00
tfwiii
697c3310a0 publicip - Bug Fix - IP address changes wer being missed if an interface was present but did not have an IPv4 address associated with it. Added exception handling to mitigate this. 2022-10-12 13:52:55 +07:00
tobi-wan-kenobi
1682a47554
Merge pull request #933 from tfwiii/main
publicip module - Fixed bug and minor improvements in output
2022-10-08 06:08:41 +02:00
tfwiii
cace02909e Bug fix improvements to publicip and util.location
Fixed publicip bug arising from last PR review
Simplified ip change detection code
Added pause after location.reset() call to allow completion before query
util.location - change order of information providers as default was not returning geo coords
2022-10-08 10:42:12 +07:00
tfwiii
605b749e22 Removed debugging prints 2022-10-06 14:21:43 +07:00
tfwiii
61fe7f6d3e Handled fail where core.location does not provide values for latitude and longitude. Added handling for coordinates N, S, E, W. 2022-10-06 13:49:37 +07:00
tobi-wan-kenobi
e70402e92c
Merge pull request #932 from ramonsaraiva/add-moonlight-theme
Add moonlight theme (powerline)
2022-09-27 17:05:00 +02:00
Ramon Saraiva
88f24100ff [themes] add moonlight theme (powerline) 2022-09-27 11:10:33 -03:00
tobi-wan-kenobi
a7979e7d66
Merge pull request #930 from benthetechguy/man-fix
Don't install manpages to /usr/usr
2022-09-21 06:32:43 +02:00
Ben Westover
7ae95ad6b6
Don't install manpages to /usr/usr
`data_files` shouldn't have `usr/` in it; this causes the manpages to be installed to `/usr/usr/share/man/man1` instead of `/usr/share/man/man1`.
2022-09-20 23:15:38 -04:00
tobi-wan-kenobi
ccf2fb3fd0
Merge pull request #929 from alonsomoya/docs/network_trafic_dependency
contrib/network_traffic dependency in docs
2022-09-20 15:10:12 +02:00
Jose Javier ALONSO MOYA
7ec3adfa47 contrib/network_traffic dependency in docs 2022-09-20 15:00:14 +02:00
tobi-wan-kenobi
1c19250fe5 [core/output] fix broken output 2022-09-18 16:50:43 +02:00
tobi-wan-kenobi
38d3a6d4c4 [doc] rearrange badges 2022-09-18 09:04:39 +02:00
tobi-wan-kenobi
0151d20451 [doc] update badges 2022-09-18 09:04:08 +02:00
tobi-wan-kenobi
acb387a685 Merge branch '921-scrolling-status-bar' 2022-09-17 17:04:12 +02:00
tobi-wan-kenobi
e5f36053af [workflow] add experimental AUR release workflow
OK, so I don't know how to test this, but *theoretically*, this should
automatically push releases to AUR.
2022-09-17 16:12:29 +02:00
tobi-wan-kenobi
d94ee9416a [doc] various updates 2022-09-17 16:07:05 +02:00
tobi-wan-kenobi
0807bfb5c3
Merge pull request #927 from kellya/main
Correct battery modules handling of critical/warning states
2022-09-17 02:05:52 +02:00
Alex Kelly
c637392bd0 Correct battery modules handling of critical/warning states
The logic for the critical/warning handling on the battery modules was
applied BEFORE the discharging-<pct> logic.  This made it  possible for
those discharging states to get applied over a critical/warning and
allow a theme to override the critical/warning colors with a state of
"discharging-10", for example.

This change moves that logic after the discharging state, so that it
will always "win" if critical or warning states are set.

This also adds the "discharging" battery state to the critical/warning
check so the state will apply if the battery is not on AC power, but
would return normal otherwise.  Meaning, if the battery is "critical"
from a percentage check, but is plugged into power, the critical state
is removed.
2022-09-16 15:36:06 -04:00
tobi-wan-kenobi
4337575557
Merge pull request #926 from kellya/main
[modules/taskwarrior] add state handling
2022-09-15 17:57:36 +02:00
Alex Kelly
a5e0b01e3b [modules/taskwarrior] add state handling
Sets one of two states:
  "stopped" - Default, no running task
  "active"  - When an active task is running.
2022-09-15 11:48:43 -04:00
tobi-wan-kenobi
97a022e452 [modules/pulsectl] add friendly name support
thanks to @anopheles for figuring out this is still missing
2022-09-14 21:12:01 +02:00
tobi-wan-kenobi
7a1022de46 [docs] add logo attribution 2022-09-13 19:02:49 +02:00
tobi-wan-kenobi
01cf02c560 [docs] transparent logo 2022-09-13 19:02:12 +02:00
tobi-wan-kenobi
71d65fafc6 [doc] attempt to use relative paths 2022-09-13 18:56:39 +02:00
tobi-wan-kenobi
a6388aea49
Merge pull request #924 from kellya/main
added logo and favicon to the conf
2022-09-13 18:36:15 +02:00
Alex Kelly
46b379815b added logo and favicon to the conf 2022-09-13 11:58:03 -04:00
tobi-wan-kenobi
7e6ae7c7be [themes] add plain zengarden theme 2022-09-13 12:59:59 +02:00
tobi-wan-kenobi
5ddc0d84a3 [docs] need to figure out how to include into readthedocs 2022-09-13 09:26:08 +02:00
tobi-wan-kenobi
245cacac42 [doc] add logo to readthedocs 2022-09-13 09:19:23 +02:00
tobi-wan-kenobi
d158fbccba [doc] reduce logo size a bit 2022-09-13 09:18:04 +02:00
tobi-wan-kenobi
aa86ac931f [doc] another attempt 2022-09-13 09:17:27 +02:00
tobi-wan-kenobi
700f977c87 [doc] play with formatting 2022-09-13 09:13:52 +02:00
tobi-wan-kenobi
84a3ebf47c [doc] test 2022-09-13 09:13:04 +02:00
tobi-wan-kenobi
9fe4e6c347 [doc] minor reformatting 2022-09-13 09:12:04 +02:00
tobi-wan-kenobi
a07f40d051 [doc] logo as img tag 2022-09-13 09:11:26 +02:00
tobi-wan-kenobi
578f806504 [doc] image not working? 2022-09-13 09:09:45 +02:00
tobi-wan-kenobi
5b864e4924 [doc] add logo - thanks to kellya! 2022-09-13 09:07:49 +02:00
tobi-wan-kenobi
96c9989ad5
Merge pull request #923 from kellya/main
Added task detail display for taskwarrior
2022-09-12 19:50:40 +02:00
Alex Kelly
54d5e83909 [modules/taskwarrior] Update docstring with show.active stuff 2022-09-12 13:43:10 -04:00
Alex Kelly
e76a6e0ba3 [doc] update document to reflec the show_active param for taskwarrior 2022-09-12 13:36:05 -04:00
Alex Kelly
754707379a Add active-task display and scrolling
This adds an option allowing you to specify
"taskwarrior.show_active=true" in your bar configuration and will
display the current, active task id and description on the status bar, but will show the
number of pending tasks if one isn't active.

This also adds the scrolling decorator, since task descriptions can be
quite long.
2022-09-12 13:29:19 -04:00
tobi-wan-kenobi
c40f59f7be [modules/scroll] edge case error 2022-09-11 16:00:35 +02:00
tobi-wan-kenobi
3f97ea6a39 [doc] add scroll menu
see #921
2022-09-11 13:16:06 +02:00
tobi-wan-kenobi
21cbbe685d [modules/scroll] add preliminary version of scrolling module
add a scrolling module that can be used to scroll the whole bar to an
arbitrary number of widgets.

its parameter is "width", which determines the number of widgets to
display.

see #921
2022-09-11 13:12:51 +02:00
tobi-wan-kenobi
910b9a8963 [doc] module doc update 2022-09-10 09:30:43 +02:00
tobi-wan-kenobi
cd46b9c6a6
Merge pull request #920 from tobi-wan-kenobi/917-event-based-pulseaudio
917 event based pulseaudio
2022-09-10 09:17:43 +02:00
tobi-wan-kenobi
2287dcab48 [docs] add note for pulsectl being preferred over pulseaudio 2022-09-10 09:13:55 +02:00
tobi-wan-kenobi
a97f46c087 [modules/pulsectl] add documentation 2022-09-10 09:12:03 +02:00
tobi-wan-kenobi
eb11c279f6 [modules/pulsectl] add device name mapping and display 2022-09-10 09:09:16 +02:00
tobi-wan-kenobi
025b1ec2f2 [modules/pulsectl] add optional bar representation 2022-09-10 09:04:52 +02:00
tobi-wan-kenobi
20bc4b3fa6 [modules/pulsectl] add parameter to set an upper limit 2022-09-10 09:02:13 +02:00
tobi-wan-kenobi
ca6bf2e189 [modules/pulsectl] add option to automatically start pulseaudio 2022-09-10 08:57:00 +02:00
tobi-wan-kenobi
003a6efc8e [modules/pulsectl] figure out default devices
make sure the modules always refer to the default devices
2022-09-09 21:40:03 +02:00
tobi-wan-kenobi
a1d94d4355 [modules/pulsectl] add mouse actions
add toggle mute on click and volume up/down on scroll
2022-09-09 21:34:32 +02:00
tobi-wan-kenobi
cc910f1198 [modules/pulsectl] add preliminary version of event-based pulseaudio
add a new module based on pulsectl, with pulsein for microphone and
pulseout for speakers.

should eventually become a drop-in replacement for pasink and pasource.

see #917
2022-09-09 21:21:09 +02:00
tobi-wan-kenobi
f4bd0fba0b [core] fix concurrency issues
* initialize first line of output earlier (before modules are
  initialized, so that module/thread output cannot interfere)
* make sure that update and draw are protected against concurrent access
2022-09-09 20:58:59 +02:00
tobi-wan-kenobi
28601cf2b7 Revert "Merge branch '917-event-based-pulseaudio'"
This reverts commit 72a888748e, reversing
changes made to d57ef9364a.

This merge causes really high CPU load if using both pasink and
pasource, because those two modules trigger each other, and there's not
a terrible lot I can do about that, unfortunately.
2022-09-09 08:39:05 +02:00
tobi-wan-kenobi
72a888748e Merge branch '917-event-based-pulseaudio'
fixes #917
2022-09-09 08:25:23 +02:00
tobi-wan-kenobi
d57ef9364a
Merge pull request #919 from kellya/main
[doc] fix missing bullet on scrolling.bounce
2022-09-08 18:06:29 +02:00
Alex Kelly
87764dccf0 [doc] fix missing bullet on scrolling.bounce 2022-09-08 11:06:06 -04:00
tobi-wan-kenobi
3c37b666f5
Merge pull request #918 from FraSharp/main
[modules/pamixer] use -t flag to toggle mute instead of setting volume level to 0
2022-09-08 12:55:36 +02:00
FraSharp
8d0f8a4177 [modules/pamixer] use -t flag to toggle mute instead of setting volume level to 0
Signed-off-by: FraSharp <s23265@iisve.it>
2022-09-08 12:11:27 +02:00
tobi-wan-kenobi
ee9885a601 [core] fix concurrency issues
* initialize first line of output earlier (before modules are
  initialized, so that module/thread output cannot interfere)
* make sure that update and draw are protected against concurrent access
2022-09-04 16:22:29 +02:00
tobi-wan-kenobi
ee81f6198e [modules/pulseaudio] rate-limit pulseaudio events
attempting a different tack: Reduce the amount of draws that are being
emitted by the pulseaudio module to max. 2/s. That hopefully increases
reactivity and at the same time keeps flickering to a minimum.

see #917
2022-09-04 09:55:33 +02:00
tobi-wan-kenobi
40041d6080 Revert "[core/output] rate-limit output (see #917)"
(did not fix the issue)

This reverts commit b90346424b.
2022-09-04 09:41:20 +02:00
tobi-wan-kenobi
b90346424b [core/output] rate-limit output (see #917)
according to research (Jakob Nielsen '93), roughly 0.1s is what is
required for the user to feel "instantaneous".

based on this, rate-limit updates to only once per ~0.03s (0.1 felt
really laggy for me, so let's be conservative)
2022-09-01 21:15:14 +02:00
tobi-wan-kenobi
978519e130 [modules/pulseaudio] redraw only
make sure that we only ever redraw see #917
2022-09-01 19:00:06 +02:00
tobi-wan-kenobi
1983408e58 [util] fix location timeouts 2022-08-31 19:12:05 +02:00
tobi-wan-kenobi
0f74b690ca [modules/publicip] add nicer logging 2022-08-31 19:09:04 +02:00
tobi-wan-kenobi
05f0e08493
Merge pull request #916 from tfwiii/main
More robust identification of changes to public IPs
2022-08-31 19:08:34 +02:00
tobi-wan-kenobi
8d2cdebbaf [modules/pulseaudio] fix pactl subscribe thread
calling "draw" too early causes status line to be messed up.

see #917
2022-08-31 19:05:08 +02:00
tobi-wan-kenobi
ae04cc9897 [module/pulseaudio] somewhat experimental immediate update
try to immediately update pulseaudio, if pactl subscribe exists & allows
us to monitor update.

see #913
2022-08-31 07:51:36 +02:00
tobi-wan-kenobi
b1fd18b9af Revert "[module/pulseaudio] somewhat experimental immediate update"
This reverts commit d5d0d6a56c.
2022-08-30 21:50:56 +02:00
tobi-wan-kenobi
d5d0d6a56c [module/pulseaudio] somewhat experimental immediate update
try to immediately update pulseaudio, if pactl subscribe exists & allows
us to monitor update.

see #913
2022-08-30 21:38:48 +02:00
tfwiii
7ee9645437 Added secondary check for potential changes in public IP and a small bug fix arising from reliance on util.location
Added a second check for indications of possible change of public IP address since netifaces does not properly handle all Linux routing tables (ref: http://linux-ip.net/html/routing-tables.html) which can lead to changes being missed if only looking at defauilt route. Consequently an additional check for changes to the network interface names/numbers was added to further help in identifying potentially notable changes. Note that netifaces is no longer being maintained so the underlying issue with its handling of gateways is unlikely to be resolved (https://github.com/al45tair/netifaces). An alternative would be to use direct calls to the OS like 'ip route list table all' (note the difference to 'ip route list all') but this might lead to unpredicatble results between distributions/flavours so probably best to stick with a library for now. all') but this might lead to unpredicatble results between distributions/flavours so probably best to stick with a library for now.

Introduced time.sleep(2) following calls to util.location.reset() since it can take util.location a while to update following a call to .reset() which can cause calls to .location_info() to return unpredicatable/unuseful results.
2022-08-30 02:01:25 +07:00
tobi-wan-kenobi
82e55ec517 [modules/pulseaudio] remove "warning" if "too loud"
Falls in the "meant well, but doesn't really make sense" category: When
the volume exceeds 100%, the widget was shown in "critical" state. Some
headsets, audio cards, etc. do require a high volume setting, however.
And anyhow, it's really up to the user.

fixes #913
2022-08-26 21:14:28 +02:00
tobi-wan-kenobi
fed7a067ba
Merge pull request #911 from diesphink/main
Fix on gcalendar module
2022-08-11 17:37:01 +02:00
Diego Pereyra
2b3b9c0ca0 Undo serialization using picke, added comment on minimum version for google-api-python-client 2022-08-11 12:26:42 -03:00
tobi-wan-kenobi
bb36f98aaa [doc] add redshift.adjust parameter 2022-08-11 16:54:15 +02:00
Diego Pereyra
80efa64614 Support for locale on gcalendar 2022-08-10 12:02:35 -03:00
tobi-wan-kenobi
84dfd85396
Merge pull request #912 from diesphink/regolith-rofication
Added support for regolith fork of rofication
2022-08-09 06:16:39 +02:00
Diego Pereyra
f9cfede0d0 Added support for regolith fork of rofication (differs on the comm protocol: \n after command, receive values on csv) 2022-08-08 23:13:01 -03:00
Diego Pereyra
55f3085c90 Added support for regolith fork of rofication (differs on the comm protocol: \n after command, receive values on csv) 2022-08-08 23:11:05 -03:00
Diego Pereyra
124f13075d Fix for multiple calendars 2022-08-08 21:59:39 -03:00
Diego Pereyra
2ab14d9cd3 Fix save token (using pickle) 2022-08-08 21:59:27 -03:00
tobi-wan-kenobi
470f05150d [module/redshift] allow adjusting the color temperature
add a boolean flag ("adjust") to redshift that allows the user to have
bumblebee-status actually perform the color adjustment (by invoking
redshift in "one-shot" node).

Note that this only updates the color value each time the redshift
module is updated (every 10s, by default), and likely will collide with
any running redshift process.

fixes #908
2022-08-07 10:32:28 +02:00
tobi-wan-kenobi
6af47dc506 [doc] update module documentation
fixes #910
2022-08-06 10:55:04 +02:00
tobi-wan-kenobi
7a47e9e13d [module/publicip] handle netifaces errors
make sure the monitoring thread doesn't terminate when there are
netifaces errors.

see #909
2022-08-06 09:17:49 +02:00
tobi-wan-kenobi
a7dba79664 [modules/publicip] add default route monitor
(re)add a separate thread that monitors the default route and updates
the module immediately, if the default route changes.

fixes #909
2022-08-05 14:30:50 +02:00
tobi-wan-kenobi
062506f467 [doc] fix code climage badge 2022-07-29 14:08:37 +02:00
tobi-wan-kenobi
b752eb8934 [doc] fix badge 2022-07-29 14:08:02 +02:00
tobi-wan-kenobi
38a5f38b43 [doc] add test badge 2022-07-29 14:05:15 +02:00
tobi-wan-kenobi
f59da4c2d8 [autotests] add more python versions 2022-07-29 14:04:04 +02:00
tobi-wan-kenobi
42ef6a3f02 Revert "[doc] remove code climate for now"
This reverts commit 7635fec36f.
2022-07-29 14:03:12 +02:00
tobi-wan-kenobi
921ddb64f0 Revert "[autotest] remove code climate dependency"
This reverts commit 61ebc3aea6.
2022-07-29 14:02:54 +02:00
tobi-wan-kenobi
a78403d3e8 [publicip] fix tests and bugs 2022-07-29 14:00:28 +02:00
tobi-wan-kenobi
61ebc3aea6 [autotest] remove code climate dependency 2022-07-29 13:44:40 +02:00
tobi-wan-kenobi
6bac2b6e34 [autotest] remove environment variable setting 2022-07-29 13:39:06 +02:00
tobi-wan-kenobi
6980d5e5d0 [autotest] use coverage3 explicitly? 2022-07-29 13:35:58 +02:00
tobi-wan-kenobi
db13bd94a1 [autotest] use separate action for code climate 2022-07-29 13:29:50 +02:00
tobi-wan-kenobi
e8c493607f [autotest] add debug flag to code climate tool 2022-07-29 13:27:03 +02:00
tobi-wan-kenobi
00b7929df5 [autotest] fix ubuntu package installation 2022-07-29 13:19:32 +02:00
tobi-wan-kenobi
184762ac57 [autotest] first attempt at a github action that runs tests 2022-07-29 13:18:07 +02:00
tobi-wan-kenobi
7635fec36f [doc] remove code climate for now
since those were triggered by the travis builds, remove code climate as
well (might reintroduce them later)

see #906
2022-07-29 11:41:34 +02:00
tobi-wan-kenobi
a8a4a86350 [all] remove Travis CI
As far as I can tell, Travis CI is *not* free anymore, even for OSS
projects. I assumed the 10k credits were monthly or yearly, but
seemingly, they are a one-time thing.

So, remove Travis CI and start searching for a better replacement.

see #906
2022-07-29 11:29:18 +02:00
tobi-wan-kenobi
a1c4b3c65e [modules/spotify] enable scrolling
this change should enable scrolling for the spotify module

(unfortunately, i am unable to fully test this, as i am not using
spotify)

fixes #903
2022-07-29 11:18:07 +02:00
tobi-wan-kenobi
b5535fcdc1
Merge pull request #905 from br0xpl/main
Pulseaudio bug when chaning sink/source (between mono and stereo devices).
2022-07-25 09:47:19 +02:00
Adamczyk Błażej
8e85eaa018 Fixed a bug in pulseaudio when changing source/sink, e.g. (dis)connecting bluetooth headset. 2022-07-25 09:12:42 +02:00
tobi-wan-kenobi
e2aa039276
Merge pull request #904 from ishaanbhimwal/fix_typos
fix multiple typos
2022-07-21 08:14:13 +02:00
Ishaan Bhimwal
a5fbc73c44 fix typo 2022-07-21 11:37:33 +05:30
tobi-wan-kenobi
39f7ad9a75
Merge pull request #902 from tfwiii/main
Fixed runaway thread creation (faulty indenting)
2022-07-11 19:43:01 +02:00
Tom Watson
2fa7931783 Fixed runaway thread creation (faulty indenting) 2022-07-12 00:19:19 +07:00
tobi-wan-kenobi
b594fd4263
Merge pull request #899 from tfwiii/publicip-update_on_default_route_change
Updated to automatically update on detecting a change to default route
2022-07-10 13:52:28 +02:00
Tom Watson
284662a0ba Ran publicip.py through black 2022-07-07 18:38:43 +07:00
Tom Watson
9a6e61173f Updated publicip to automatically update on detecting a change to default route 2022-07-07 18:08:20 +07:00
tobi-wan-kenobi
05c28c52c7
Merge pull request #897 from tfwiii/updated_publicip_module
Updated contrib/publicip module and util/location
2022-07-06 14:57:08 +02:00
Tom Watson
a97a7fe507 Updates addressing PR comments
Added location_info() to util/location API to return a dict of all location information. Updated modules/contrib/publicip to use that API. Changed modules/contrib/publicip refresh period back to 60 minutes. Changed /util/location API from 'country_name' back to 'name'
2022-07-06 19:37:29 +07:00
Tom Watson
6f137c4927 Update following PR review
Moved to format string handling of parameters. Minor refactoring.
2022-07-06 17:51:19 +07:00
Tom Watson
218bfa2235 Updated contrib/publicip module and util/location
Added another API endpoint, Added options to display country name, country code, city name and lat/long coordinates, attempt to handle failure to fetch info from API endpoints cleanly
2022-07-06 01:05:25 +07:00
tobi-wan-kenobi
6f4f163a7d [core/layout] use python-only layout detection
remove precompiled binary and make the layout module
fall back to the python implementation.

fixes #883
2022-07-03 14:02:38 +02:00
tobi-wan-kenobi
326e2f9318 [core/theme] add support for XDG_DATA_DIRS
read theme information from
$XDG_DATA_DIRS/bumblebee-status/themes

see #821
2022-07-03 13:56:02 +02:00
tobi-wan-kenobi
f462102439
Merge pull request #892 from SamTebbs33/main
Hide battery module if there are no batteries
2022-06-22 11:29:58 +02:00
Sam Tebbs
eabf167c1f Hide battery module if there are no batteries 2022-06-22 09:12:14 +01:00
tobi-wan-kenobi
df9890690a
Merge pull request #890 from anarcat/upower-no-bat
handle missing battery case
2022-06-20 17:37:04 +02:00
Antoine Beaupré
16c4ce2ee6
handle missing battery case
I run the same bumblebee-status configuration on my laptop and my
workstation. On my laptop, the upower module works fine: it says "ac"
when plugged in, charging, all that stuff is great.

But on my workstation, it's completely broken: it thinks there's a
battery (which is a mistake: there is no battery at all, apart maybe
from the CMOS battery, but that's not covered by upower), and it
thinks it's discharged, which makes a very noisy warning in the bar.

Now maybe there's something wrong with dbus, Debian, the kernel,
Linux, or some thing else in the stack. All I know is that
`self.power.get_display_device()` returns something like a valid
dbus object here and from there it confuses the heck out of the
module.

So this just adds a function to check if the actual device we're
talking about is actually present, and bails earlier otherwise.

Before: battery logo and "0% 00:00m!", all marked as critical ("red")

After: "ac" with the plugged in logo, not marked critical ("black")
2022-06-20 11:34:12 -04:00
tobi-wan-kenobi
e6f1939857
Merge pull request #889 from anarcat/thin-space
reduce spacing in awesome fonts iconset
2022-06-15 18:28:25 +02:00
Antoine Beaupré
2d6041be5d
reduce spacing in awesome fonts iconset
This replaces the previous normal spacing character (which is the
usual ASCII 0x20 SPACE) by a *narrower* spacer (which is unicode
U+2009 THIN SPACE).

I found that space thanks to

https://en.wikipedia.org/wiki/Space_(punctuation)#Types_of_spaces

... and specifically:

https://en.wikipedia.org/wiki/Thin_space

... and this actually works, amazingly. Probably because it is pretty
standard as it's part of the SI specification (thousands separator),
Tex (`\thinspace` or `\,`), and HTML (`&thinsp;`)

Closes: #888
2022-06-15 11:50:23 -04:00
tobi-wan-kenobi
b46c295827
Merge pull request #886 from anarcat/patch-1
expand FAQ about fonts
2022-06-08 18:30:13 +02:00
anarcat
9072249d5c
expand FAQ about fonts
This adds a little more information about the font stuff based on the things I learned in https://github.com/tobi-wan-kenobi/bumblebee-status/issues/881#issuecomment-1150034722
2022-06-08 11:51:56 -04:00
tobi-wan-kenobi
3764e758a2
Merge pull request #885 from anarcat/srcery
add srcery theme (closes: #881)
2022-06-08 17:25:49 +02:00
Antoine Beaupré
7b63efee36
add srcery theme (closes: #881) 2022-06-08 11:16:13 -04:00
tobi-wan-kenobi
4afb8d8636 [doc] add man pages
Many thanks to https://github.com/benthetechguy, who wrote man pages for
bumblebee-status and bumblebee-ctl

fixes #882
2022-06-08 08:34:05 +02:00
tobi-wan-kenobi
7dd5914e3f
Merge pull request #884 from benthetechguy/faq
Change nonexistent image to link
2022-06-08 02:52:47 +02:00
benthetechguy
c001f031a1
Change nonexistent image to link 2022-06-07 19:14:51 -04:00
tobi-wan-kenobi
7fc712862c
Merge pull request #877 from FraSharp/main
[modules]: introduce pamixer module
2022-05-26 13:43:02 +02:00
FraSharp
5c166beebf [modules]: introduce pamixer module
Signed-off-by: FraSharp <s23265@iisve.it>
2022-05-26 13:00:40 +02:00
tobi-wan-kenobi
6b2b5217ed
Merge pull request #876 from SamTebbs33/main
Add popup command parameter to the system module
2022-05-20 10:26:10 +02:00
Sam Tebbs
91b1b5e037 Add popup command parameter to the system module 2022-05-20 09:21:26 +01:00
tobi-wan-kenobi
2e8495d5ff
Merge pull request #875 from SamTebbs33/main
Hide progress module if it's inactive
2022-05-13 13:50:08 +02:00
Sam Tebbs
f01179290b Hide progress module if it's inactive 2022-05-13 10:21:31 +01:00
tobi-wan-kenobi
221ea0d22f
Merge pull request #874 from Timoses/patch-1
Fix logout item using hardcoded command
2022-05-11 20:04:18 +02:00
Timoses
a6d2ccc666
Fix logout item using hardcoded command 2022-05-11 19:58:34 +02:00
tobi-wan-kenobi
b7dd47c834
Merge pull request #873 from alexcoder04/main
arch-update: sleep 1 sec before checking
2022-05-01 12:50:52 +02:00
alexcoder04
3da0f08fcb
arch-update: sleep 1 sec before checking
When waking up from suspend, there is sometimes a delay connecting to
the network, so arch-update gives an error
2022-05-01 12:15:29 +02:00
tobi-wan-kenobi
c011cfb6c1
Merge pull request #872 from SamTebbs33/main
Fix missing playback_status
2022-04-24 17:13:50 +02:00
Samuel Tebbs
d20dacb2dc Fix missing playback_status 2022-04-24 16:09:54 +01:00
tobi-wan-kenobi
d3de79e6b4
Merge pull request #871 from SamTebbs33/main
Add hide parameter to playerctl
2022-04-23 16:32:15 +02:00
Samuel Tebbs
771e7482d7 [modules/playerctl] add 'hide' parameter 2022-04-23 14:44:35 +01:00
tobi-wan-kenobi
d1ae8f277f
Merge pull request #870 from arivarton/main
Changed __time_format to self.__time_format.
2022-04-11 20:34:21 +02:00
arivarton
83d910a7ef Changed __time_format to self.__time_format. 2022-04-08 17:09:28 +02:00
tobi-wan-kenobi
f64f71b6e2
Merge pull request #869 from arivarton/main
Google calendar module
2022-04-08 14:27:47 +02:00
arivarton
a48ddbb2c8 Ran black -t py34 2022-04-08 14:23:22 +02:00
arivarton
8501c406af Google calendar module. 2022-04-08 14:15:28 +02:00
tobi-wan-kenobi
2383cadadc
Merge pull request #868 from bbernhard/pulseaudio_extension
Pulseaudio extension
2022-03-25 20:43:57 +01:00
Bernhard B
4d3de3be04 handle util.popup ImportError gracefully
* util.popup requires tkinter to be installed (in order to display
  popups). As most people will probably use the default configuration
  of the pulseaudio module (where the popup is disabled) and in order
  to avoid breaking existing setups, we catch import errors and just log
  them.
2022-03-25 19:41:10 +01:00
Bernhard B
82fa347f2c extended pulseaudio module
* added possibility to show currently selected default device in the
  statusbar (default: off)
* allows to override the left mouse button click with a different
  action (e.g open popup menu to change the current default device)
2022-03-24 21:23:59 +01:00
Bernhard B
7932af712e Merge branch 'main' into pulseaudio_extension 2022-03-22 18:21:33 +01:00
tobi-wan-kenobi
d446a44f06
Merge pull request #866 from ishaanbhimwal/aur-update
Add documentation and screenshot for aur-update module
2022-03-15 13:50:04 +01:00
ishaan
9b61eee725 add better screenshots 2022-03-15 17:32:45 +05:30
ishaan
1fc4139b7c add documentation and screenshot for aur-update 2022-03-15 15:56:42 +05:30
tobi-wan-kenobi
69edfc42ae
Merge pull request #865 from ishaanbhimwal/aur-update
Fix aur-update showing wrong update number
2022-03-14 21:20:53 +01:00
ishaan
f513582d44 fix aur-update showing wrong update number 2022-03-15 01:17:57 +05:30
tobi-wan-kenobi
2d99bce987
Merge pull request #864 from ishaanbhimwal/aur-update
Add module aur-update
2022-03-14 18:41:20 +01:00
ishaan
879744e19c add aur-update 2022-03-14 22:28:00 +05:30
tobi-wan-kenobi
a8fabce14e
Merge pull request #863 from Hame-daani/main
refactor contrib.persian_date module
2022-03-10 09:51:47 +01:00
Sadegh Hamedani
c228ca3b12
[contrib/persian_date] refactor using core.datetime module as parent 2022-03-10 11:53:19 +03:30
Sadegh Hamedani
82ca97c65f
[core/datetime] added 'dtlibrary' attribute and 'set_locale' method 2022-03-10 11:52:08 +03:30
tobi-wan-kenobi
fd1eb6e790
Merge pull request #846 from DTan13/main
added module
2022-03-10 02:52:51 +01:00
tobi-wan-kenobi
b31dea19cc
Merge pull request #861 from bbernhard/pactl_revert
Pactl revert
2022-03-07 21:23:26 +01:00
Bernhard B
d52f713063 Revert "Merge pull request #857 from bbernhard/pactl"
This reverts commit eb51a3c1c7, reversing
changes made to c57daf65ce.

Instead of creating a separate module, the changes will be integrated
into the pulseaudio module.
2022-03-07 20:53:11 +01:00
Bernhard B
0bf91c2f15 Merge branch 'main' into pactl_revert 2022-03-07 20:52:31 +01:00
Dhananjay Tanpure
3de6f9f4b9
Merge branch 'tobi-wan-kenobi:main' into main 2022-03-07 08:58:13 +05:30
Dhananjay Tanpure
e5cdabcc0f
mpd and title 2022-03-07 08:50:30 +05:30
tobi-wan-kenobi
fbe70607be
Merge pull request #860 from Hame-daani/main
[contrib] added module persian_date
2022-03-04 17:22:57 +01:00
Sadegh Hamedani
9cbc39e462
[contrib] added module persian_date 2022-03-04 18:28:13 +03:30
tobi-wan-kenobi
bf7aac4e6e
Merge pull request #859 from tobi-wan-kenobi/858
[core/input] methods can be event callbacks
2022-03-04 09:37:01 +01:00
tobi-wan-kenobi
7d33171749 [core/input] methods can be event callbacks
When registering an event (especially mouse events), if the parameter
is a valid method in the Module, execute that with the event as
parameter.

Add this in the core.spacer module as an example.

fixes #858
see #857
2022-03-04 09:35:43 +01:00
tobi-wan-kenobi
eb51a3c1c7
Merge pull request #857 from bbernhard/pactl
Pactl
2022-03-03 20:31:51 +01:00
tobi-wan-kenobi
c57daf65ce [themes/zengarden] add key colors 2022-03-03 14:31:47 +01:00
tobi-wan-kenobi
9a2e7637c9 [themes/zengarden] lighter accents 2022-03-02 16:16:24 +01:00
tobi-wan-kenobi
07200c466b [themes] add zengarden light (powerline) 2022-03-02 12:33:21 +01:00
Bernhard B
33d22c2637 removed debug log from 'pactl' module 2022-02-28 19:20:19 +01:00
Bernhard B
950931e1b9 added new module 'pactl'
* added new module 'pactl' which displays the current default sink and allows to
  select a different default sink from the popup menu.
2022-02-28 19:14:21 +01:00
tobi-wan-kenobi
a17356ee9d
Merge pull request #856 from LokiLuciferase/bugfix/redshift-spelling
fix case of Kelvin SI unit in redshift widget
2022-02-20 12:57:42 +01:00
Lukas Lüftinger
928f8258aa fix case of Kelvin SI unit in redshift widget 2022-02-20 12:29:49 +01:00
tobi-wan-kenobi
03731136b6 [modules/nic] fix missing check for None 2022-02-15 16:34:02 +01:00
tobi-wan-kenobi
8897c1bde5
Merge pull request #854 from mihaimorariu/fix/publicip-exception
Fix publicip
2022-02-14 15:19:58 +01:00
Mihai Morariu
283d47ff65 Merge branch 'main' into fix/publicip-exception 2022-02-14 16:19:43 +02:00
tobi-wan-kenobi
3aadab5628 [modules/publicip] handle missing public ip more gracefully
If location does not throw, but reports an empty public IP, return
"n/a".

Since this caused a bug, also add a test for it.

fixes #853
2022-02-14 14:58:01 +01:00
Mihai Morariu
2a77e3a85c Fix exception in location.py. 2022-02-14 15:36:24 +02:00
tobi-wan-kenobi
b1f49f6a1e
Merge pull request #852 from piyueh/patch-1
typo in nic.py: minium -> minimum
2022-02-12 20:07:46 +01:00
Pi-Yueh Chuang
4784be4076
typo in nic.py: minium -> minimum 2022-02-12 12:54:31 -05:00
tobi-wan-kenobi
a1ae6d4f34
Merge pull request #851 from deadbeef2000/nic_strength
[modules/nic] Added strength indicator for wifi signals
2022-02-12 11:23:49 +01:00
Christopher Kepes
5c390be25c [modules/nic] Added strength indicator for wifi signals 2022-02-12 11:06:10 +01:00
tobi-wan-kenobi
4f9553f7ea [modules/rss] fix insecure use of tempfile
fixes #850
2022-02-11 13:44:10 +01:00
tobi-wan-kenobi
8458eef1e6 [doc] add codeql badge 2022-02-09 21:24:35 +01:00
tobi-wan-kenobi
4c08cd812e
Create codeql-analysis.yml
Trying out CodeQL
2022-02-09 21:15:08 +01:00
Dhananjay Tanpure
8867f4f188
added module for blugon 2022-01-20 21:06:17 +00:00
118 changed files with 4103 additions and 573 deletions

29
.github/workflows/aurpublish.yml vendored Normal file
View file

@ -0,0 +1,29 @@
---
name: Upload AUR Package
on:
release:
types: [created]
jobs:
aur-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests
- name: Create PKGBUILD
run: |
python ./create-pkgbuild.py > ./PKGBUILD
- name: Publish AUR package
uses: KSXGitHub/github-actions-deploy-aur@v2.5.0
with:
pkgname: bumblebee-status
pkgbuild: ./PKGBUILD
commit_username: ${{ secrets.AUR_USERNAME }}
commit_email: ${{ secrets.AUR_EMAIL }}
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
commit_message: Update AUR package
ssh_keyscan_types: rsa,dsa,ecdsa,ed25519

45
.github/workflows/autotest.yml vendored Normal file
View file

@ -0,0 +1,45 @@
name: Tests
on:
pull_request:
types: [ opened, reopened, edited ]
push:
env:
CC_TEST_REPORTER_ID: 40cb00907f7a10e04868e856570bb997ab9c42fd3b63d980f2b2269433195fdf
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11']
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install Ubuntu dependencies
run: sudo apt-get install -y libdbus-1-dev libgit2-dev libvirt-dev taskwarrior
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -U coverage pytest pytest-mock freezegun
pip install 'pygit2<1' 'libvirt-python<6.3' 'feedparser<6' || true
pip install $(cat requirements/modules/*.txt | grep -v power | cut -d ' ' -f 1 | sort -u)
- name: Install Code Climate dependency
run: |
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
chmod +x ./cc-test-reporter
./cc-test-reporter before-build
- name: Run tests
run: |
coverage run --source=. -m pytest tests -v
- name: Report coverage
uses: paambaati/codeclimate-action@v3.2.0
with:
coverageCommand: coverage3 xml
debug: true

70
.github/workflows/codeql-analysis.yml vendored Normal file
View file

@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '31 0 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View file

@ -3,4 +3,7 @@ version: 2
python:
install:
- requirements: docs/requirements.txt
build:
os: ubuntu-22.04
tools:
python: "3"

View file

@ -1,30 +0,0 @@
os: linux
language: python
env:
global:
- CC_TEST_REPORTER_ID=40cb00907f7a10e04868e856570bb997ab9c42fd3b63d980f2b2269433195fdf
python:
- "3.6"
- "3.7"
- "3.8"
- "3.9"
before_script:
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
- chmod +x ./cc-test-reporter
- ./cc-test-reporter before-build
addons:
apt:
packages:
libdbus-1-dev
libgit2-dev
libvirt-dev
taskwarrior
install:
- pip install -U coverage pytest pytest-mock freezegun
- pip install 'pygit2<1' 'libvirt-python<6.3' 'feedparser<6' || true
- pip install $(cat requirements/modules/*.txt | cut -d ' ' -f 1 | sort -u)
script:
- coverage run --source=. -m pytest tests -v
after_script:
- coverage xml
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT

View file

@ -12,6 +12,5 @@ But even if you can't provide those, any indicator that something is not working
### Adding a new module or theme
If you want to add a new module, please have a look at [how to write a new module](docs/development/module.rst) and [how to write a new theme](docs/development/theme.rst). Then simply create a Pull Request and I will review the changes as soon as possible.
If you want to do me a *big* favour, check the Travis status for any failing unit tests. Oh - and if you happen to add unit tests, that's also something I am very grateful for!
Thanks for reading until here! :)

45
PKGBUILD.template Normal file
View file

@ -0,0 +1,45 @@
# Maintainer: Tobias Witek <tobi@tobi-wan-kenobi.at>
# Contributor: Daniel M. Capella <polycitizen@gmail.com>
# Contributor: spookykidmm <https://github.com/spookykidmm>
pkgname=bumblebee-status
pkgver=<PKGVERSION>
pkgrel=1
pkgdesc='Modular, theme-able status line generator for the i3 window manager'
arch=('any')
url=https://github.com/tobi-wan-kenobi/bumblebee-status
license=('MIT')
depends=('python' 'python-netifaces' 'python-psutil' 'python-requests')
optdepends=('xorg-xbacklight: to display a displays brightness'
'xorg-xset: enable/disable automatic screen locking'
'libnotify: enable/disable automatic screen locking'
'dnf: display DNF package update information'
'xorg-setxkbmap: display/change the current keyboard layout'
'redshift: display the redshifts current color'
'pulseaudio: control pulseaudio sink/sources'
'xorg-xrandr: enable/disable screen outputs'
'pacman: display current status of pacman'
'iputils: display a ping'
'python-i3ipc: display titlebar'
'fakeroot: dependency of the pacman module'
'python-pytz: timezone conversion for datetimetz module'
'python-tzlocal: retrieve system timezone for datetimetz module'
)
source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
sha512sums=('<SHA512SUM>')
package() {
install -d "$pkgdir"/usr/bin \
"$pkgdir"/usr/share/$pkgname/bumblebee_status/{core,util} \
"$pkgdir"/usr/share/$pkgname/bumblebee_status/modules/{core,contrib} \
"$pkgdir"/usr/share/$pkgname/themes/icons
ln -s /usr/share/$pkgname/$pkgname "$pkgdir"/usr/bin/$pkgname
ln -s /usr/share/$pkgname/bumblebee-ctl "$pkgdir"/usr/bin/bumblebee-ctl
cd $pkgname-$pkgver
cp -a --parents $pkgname bumblebee_status/{,core/,util/,modules/core/,modules/contrib/}*.py \
themes/{,icons/}*.json $pkgdir/usr/share/$pkgname
cp -r bin $pkgdir/usr/share/$pkgname/
install -Dm644 LICENSE "$pkgdir"/usr/share/licenses/$pkgname/LICENSE
}

View file

@ -1,13 +1,20 @@
# bumblebee-status
<img src="https://github.com/kellya/bumblebee-status-icon/blob/main/img/bumblebee_status_rtl.svg" width="50" style="display:inline-block">bumblebee-status
=====================================================
logo courtesy of [kellya](https://github.com/kellya) - thank you!
[![Build Status](https://app.travis-ci.com/tobi-wan-kenobi/bumblebee-status.svg?branch=main)](https://app.travis-ci.com/tobi-wan-kenobi/bumblebee-status)
[![Documentation Status](https://readthedocs.org/projects/bumblebee-status/badge/?version=main)](https://bumblebee-status.readthedocs.io/en/main/?badge=main)
![Commits since release](https://img.shields.io/github/commits-since/tobi-wan-kenobi/bumblebee-status/latest)
![AUR version (release)](https://img.shields.io/aur/version/bumblebee-status)
![AUR version (git)](https://img.shields.io/aur/version/bumblebee-status-git)
[![PyPI version](https://badge.fury.io/py/bumblebee-status.svg)](https://badge.fury.io/py/bumblebee-status)
![PyPI version](https://img.shields.io/pypi/v/bumblebee-status)
![Contributors](https://img.shields.io/github/contributors-anon/tobi-wan-kenobi/bumblebee-status)
[![Tests](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/autotest.yml/badge.svg?branch=main)](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/autotest.yml)
[![Code Climate](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/gpa.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status)
[![Test Coverage](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/coverage.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage)
[![Issue Count](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/issue_count.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status)
[![CodeQL](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/codeql-analysis.yml)
![License](https://img.shields.io/github/license/tobi-wan-kenobi/bumblebee-status)
**Many, many thanks to all contributors! I am still amazed by and deeply grateful for how many PRs this project gets.**

Binary file not shown.

View file

@ -68,13 +68,18 @@ def handle_commands(config, update_lock):
def handle_events(config, update_lock):
while True:
line = sys.stdin.readline().strip(",").strip()
if line == "[": continue
logging.info("input event: {}".format(line))
process_event(line, config, update_lock)
try:
line = sys.stdin.readline().strip(",").strip()
if line == "[": continue
logging.info("input event: {}".format(line))
process_event(line, config, update_lock)
except Exception as e:
logging.error(e)
def main():
global started
config = core.config.Config(sys.argv[1:])
level = logging.DEBUG if config.debug() else logging.ERROR
if config.logfile():
@ -97,6 +102,9 @@ def main():
core.input.register(None, core.input.WHEEL_UP, "i3-msg workspace prev_on_output")
core.input.register(None, core.input.WHEEL_DOWN, "i3-msg workspace next_on_output")
core.event.trigger("start")
started = True
update_lock = threading.Lock()
event_thread = threading.Thread(target=handle_events, args=(config, update_lock, ))
event_thread.daemon = True
@ -127,8 +135,6 @@ def main():
if util.format.asbool(config.get("engine.collapsible", True)) == True:
core.input.register(None, core.input.MIDDLE_MOUSE, output.toggle_minimize)
core.event.trigger("start")
started = True
signal.signal(10, sig_USR1_handler)
while True:
if update_lock.acquire(blocking=False) == True:
@ -146,6 +152,7 @@ if __name__ == "__main__":
main()
except Exception as e:
# really basic errors -> make sure these are shown in the status bar by minimal config
logging.exception(e)
if not started:
print("{\"version\":1}")
print("[")

View file

@ -292,7 +292,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
# TAG-NUM-gHEX
mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
if not mo:
# unparseable. Maybe git-describe is misbehaving?
# unparsable. Maybe git-describe is misbehaving?
pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
return pieces

View file

@ -240,11 +240,16 @@ class Config(util.store.Store):
:param filename: path to the file to load
"""
def load_config(self, filename):
if os.path.exists(filename):
def load_config(self, filename, content=None):
if os.path.exists(filename) or content != None:
log.info("loading {}".format(filename))
tmp = RawConfigParser()
tmp.read(u"{}".format(filename))
tmp.optionxform = str
if content:
tmp.read_string(content)
else:
tmp.read(u"{}".format(filename))
if tmp.has_section("module-parameters"):
for key, value in tmp.items("module-parameters"):
@ -276,6 +281,15 @@ class Config(util.store.Store):
def interval(self, default=1):
return util.format.seconds(self.get("interval", default))
"""Returns the global popup menu font size
:return: popup menu font size
:rtype: int
"""
def popup_font_size(self, default=12):
return util.format.asint(self.get("popup_font_size", default))
"""Returns whether debug mode is enabled
:return: True if debug is enabled, False otherwise

View file

@ -54,8 +54,11 @@ def register(obj, button=None, cmd=None, wait=False):
event_id = __event_id(obj.id if obj is not None else "", button)
logging.debug("registering callback {}".format(event_id))
core.event.unregister(event_id) # make sure there's always only one input event
if callable(cmd):
core.event.register_exclusive(event_id, cmd)
elif obj and hasattr(obj, cmd) and callable(getattr(obj, cmd)):
core.event.register_exclusive(event_id, lambda event: getattr(obj, cmd)(event))
else:
core.event.register_exclusive(event_id, lambda event: __execute(event, cmd, wait))

View file

@ -1,5 +1,6 @@
import os
import importlib
import importlib.util
import logging
import threading
@ -112,6 +113,15 @@ class Module(core.input.Object):
def hidden(self):
return False
"""Override this to show the module even if it normally would be scrolled away
:return: True if the module should be hidden, False otherwise
:rtype: boolean
"""
def scroll(self):
return True
"""Retrieve CLI/configuration parameters for this module. For example, if
the module is called "test" and the user specifies "-p test.x=123" on the
commandline, using self.parameter("x") retrieves the value 123.
@ -295,7 +305,7 @@ class Error(Module):
def full_text(self, widget):
return "{}: {}".format(self.__module, self.__error)
"""Overriden state, always returns critical (it *is* an error, after all"""
"""Overridden state, always returns critical (it *is* an error, after all"""
def state(self, widget):
return ["critical"]

View file

@ -1,6 +1,7 @@
import sys
import json
import time
import threading
import core.theme
import core.event
@ -145,10 +146,14 @@ class i3(object):
self.__content = {}
self.__theme = theme
self.__config = config
self.__offset = 0
self.__lock = threading.Lock()
core.event.register("update", self.update)
core.event.register("start", self.draw, "start")
core.event.register("draw", self.draw, "statusline")
core.event.register("stop", self.draw, "stop")
core.event.register("output.scroll-left", self.scroll_left)
core.event.register("output.scroll-right", self.scroll_right)
def content(self):
return self.__content
@ -176,14 +181,15 @@ class i3(object):
self.__content[widget_id]["minimized"] = not self.__content[widget_id]["minimized"]
def draw(self, what, args=None):
cb = getattr(self, what)
data = cb(args) if args else cb()
if "blocks" in data:
sys.stdout.write(json.dumps(data["blocks"], default=dump_json))
if "suffix" in data:
sys.stdout.write(data["suffix"])
sys.stdout.write("\n")
sys.stdout.flush()
with self.__lock:
cb = getattr(self, what)
data = cb(args) if args else cb()
if "blocks" in data:
sys.stdout.write(json.dumps(data["blocks"], default=dump_json))
if "suffix" in data:
sys.stdout.write(data["suffix"])
sys.stdout.write("\n")
sys.stdout.flush()
def start(self):
return {
@ -220,13 +226,29 @@ class i3(object):
blk.set("__state", state)
return blk
def scroll_left(self):
if self.__offset > 0:
self.__offset -= 1
def scroll_right(self):
self.__offset += 1
def blocks(self, module):
blocks = []
if module.minimized:
blocks.extend(self.separator_block(module, module.widgets()[0]))
blocks.append(self.__content_block(module, module.widgets()[0]))
self.__widgetcount += 1
return blocks
width = self.__config.get("output.width", 0)
for widget in module.widgets():
if module.scroll() == True and width > 0:
self.__widgetcount += 1
if self.__widgetcount-1 < self.__offset:
continue
if self.__widgetcount-1 >= self.__offset + width:
continue
if widget.module and self.__config.autohide(widget.module.name):
if not any(
state in widget.state() for state in ["warning", "critical", "no-autohide"]
@ -241,9 +263,14 @@ class i3(object):
blocks.extend(self.separator_block(module, widget))
blocks.append(self.__content_block(module, widget))
core.event.trigger("next-widget")
core.event.trigger("output.done", self.__offset, self.__widgetcount)
return blocks
def update(self, affected_modules=None, redraw_only=False, force=False):
with self.__lock:
self.update2(affected_modules, redraw_only, force)
def update2(self, affected_modules=None, redraw_only=False, force=False):
now = time.time()
for module in self.__modules:
if affected_modules and not module.id in affected_modules:
@ -267,6 +294,7 @@ class i3(object):
def statusline(self):
blocks = []
self.__widgetcount = 0
for module in self.__modules:
blocks.extend(self.blocks(module))
return {"blocks": blocks, "suffix": ","}

View file

@ -14,11 +14,20 @@ log = logging.getLogger(__name__)
THEME_BASE_DIR = os.path.dirname(os.path.realpath(__file__))
PATHS = [
".",
os.path.join(THEME_BASE_DIR, "../../themes"),
os.path.join(THEME_BASE_DIR, "../../themes")
]
if os.environ.get("XDG_DATA_DIRS"):
PATHS.extend([
os.path.join(p, "bumblebee-status/themes") for p in os.environ["XDG_DATA_DIRS"].split(":")
])
PATHS.extend([
os.path.expanduser("~/.config/bumblebee-status/themes"),
os.path.expanduser("~/.local/share/bumblebee-status/themes"), # PIP
os.path.expanduser("~/.local/pipx/venvs/bumblebee-status/share/bumblebee-status/themes"), # PIPX
"/usr/share/bumblebee-status/themes",
]
])
def themes():

View file

@ -4,12 +4,15 @@ Requires the following executable:
* amixer
Parameters:
* amixer.card: Sound Card to use (default is 0)
* amixer.device: Device to use (default is Master,0)
* amixer.percent_change: How much to change volume by when scrolling on the module (default is 4%)
contributed by `zetxx <https://github.com/zetxx>`_ - many thanks!
input handling contributed by `ardadem <https://github.com/ardadem>`_ - many thanks!
multiple audio cards contributed by `hugoeustaquio <https://github.com/hugoeustaquio>`_ - many thanks!
"""
import re
@ -26,6 +29,7 @@ class Module(core.module.Module):
self.__level = "n/a"
self.__muted = True
self.__card = self.parameter("card", "0")
self.__device = self.parameter("device", "Master,0")
self.__change = util.format.asint(
self.parameter("percent_change", "4%").strip("%"), 0, 100
@ -62,7 +66,7 @@ class Module(core.module.Module):
self.set_parameter("{}%-".format(self.__change))
def set_parameter(self, parameter):
util.cli.execute("amixer -q set {} {}".format(self.__device, parameter))
util.cli.execute("amixer -c {} -q set {} {}".format(self.__card, self.__device, parameter))
def volume(self, widget):
if self.__level == "n/a":
@ -79,7 +83,7 @@ class Module(core.module.Module):
def update(self):
try:
self.__level = util.cli.execute(
"amixer get {}".format(self.__device)
"amixer -c {} get {}".format(self.__card, self.__device)
)
except Exception as e:
self.__level = "n/a"

View file

@ -8,6 +8,9 @@ saved screen layout as well as toggle on/off individual connected displays.
Parameters:
* No configuration parameters
Requires the following python modules:
* tkinter
Requires the following executable:
* arandr
* xrandr

View file

@ -7,6 +7,7 @@ contributed by `lucassouto <https://github.com/lucassouto>`_ - many thanks!
"""
import logging
from time import sleep
import core.module
import core.widget
@ -35,6 +36,7 @@ class Module(core.module.Module):
def update(self):
self.__error = False
sleep(1)
code, result = util.cli.execute(
"checkupdates", ignore_errors=True, return_exitcode=True
)

View file

@ -0,0 +1,57 @@
"""Check updates for AUR.
Requires the following executable:
* yay (https://github.com/Jguer/yay)
contributed by `ishaanbhimwal <https://github.com/ishaanbhimwal>`_ - many thanks!
"""
import logging
import core.module
import core.widget
import core.decorators
import util.cli
class Module(core.module.Module):
@core.decorators.every(minutes=60)
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.utilization))
self.background = True
self.__packages = 0
self.__error = False
@property
def __format(self):
return self.parameter("format", "Update AUR: {}")
def utilization(self, widget):
return self.__format.format(self.__packages)
def hidden(self):
return self.__packages == 0
def update(self):
self.__error = False
code, result = util.cli.execute(
"yay -Qum", ignore_errors=True, return_exitcode=True
)
if code == 0:
if result == "":
self.__packages = 0
else:
self.__packages = len(result.strip().split("\n"))
else:
self.__error = True
logging.error("aur-update exited with {}: {}".format(code, result))
def state(self, widget):
if self.__error:
return "warning"
return self.threshold_state(self.__packages, 1, 100)
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -68,7 +68,7 @@ class UPowerManager:
isPresent = battery_proxy_interface.Get(
self.UPOWER_NAME + ".Device", "IsPresent"
)
isRechargable = battery_proxy_interface.Get(
isRechargeable = battery_proxy_interface.Get(
self.UPOWER_NAME + ".Device", "IsRechargeable"
)
online = battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "Online")
@ -128,7 +128,7 @@ class UPowerManager:
"HasHistory": hasHistory,
"HasStatistics": hasStatistics,
"IsPresent": isPresent,
"IsRechargeable": isRechargable,
"IsRechargeable": isRechargeable,
"Online": online,
"PowerSupply": powersupply,
"Capacity": capacity,
@ -207,6 +207,14 @@ class UPowerManager:
data = upower_interface.GetTotal()
return data
def is_battery_present(self, battery):
battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery)
battery_proxy_interface = dbus.Interface(battery_proxy, self.DBUS_PROPERTIES)
return bool(
battery_proxy_interface.Get(self.UPOWER_NAME + ".Device", "IsPresent")
)
def is_loading(self, battery):
battery_proxy = self.bus.get_object(self.UPOWER_NAME, battery)
battery_proxy_interface = dbus.Interface(battery_proxy, self.DBUS_PROPERTIES)
@ -259,6 +267,11 @@ class Module(core.module.Module):
widget.set("capacity", -1)
widget.set("ac", False)
output = "n/a"
if not self.power.is_battery_present(self.device):
widget.set("ac", True)
widget.set("capacity", 100)
output = "ac"
return output
try:
capacity = int(self.power.get_device_percentage(self.device))
capacity = capacity if capacity < 100 else 100
@ -298,11 +311,6 @@ class Module(core.module.Module):
if capacity < 0:
return ["critical", "unknown"]
if capacity < int(self.parameter("critical", 10)):
state.append("critical")
elif capacity < int(self.parameter("warning", 20)):
state.append("warning")
if widget.get("ac"):
state.append("AC")
else:
@ -328,6 +336,16 @@ class Module(core.module.Module):
state.append("charged")
else:
state.append("charging")
if (
capacity < int(self.parameter("critical", 10))
and self.power.get_state(self.device) == "Discharging"
):
state.append("critical")
elif (
capacity < int(self.parameter("warning", 20))
and self.power.get_state(self.device) == "Discharging"
):
state.append("warning")
return state

View file

@ -130,10 +130,19 @@ class Module(core.module.Module):
log.debug("adding new widget for {}".format(battery))
widget = self.add_widget(full_text=self.capacity, name=battery)
for w in self.widgets():
if util.format.asbool(self.parameter("decorate", True)) == False:
try:
with open("/sys/class/power_supply/{}/model_name".format(battery)) as f:
widget.set("pen", ("Pen" in f.read().strip()))
except Exception:
pass
if util.format.asbool(self.parameter("decorate", True)) == False:
for widget in self.widgets():
widget.set("theme.exclude", "suffix")
def hidden(self):
return len(self._batteries) == 0
def ac(self, widget):
return "ac"
@ -144,15 +153,16 @@ class Module(core.module.Module):
capacity = self.__manager.capacity(widget.name)
widget.set("capacity", capacity)
widget.set("ac", self.__manager.isac_any(self._batteries))
widget.set("theme.minwidth", "100%")
# Read power conumption
if util.format.asbool(self.parameter("showpowerconsumption", False)):
output = "{}% ({})".format(
capacity, self.__manager.consumption(widget.name)
)
else:
elif capacity < 100:
output = "{}%".format(capacity)
else:
output = ""
if (
util.format.asbool(self.parameter("showremaining", True))
@ -164,6 +174,16 @@ class Module(core.module.Module):
output, util.format.duration(remaining, compact=True, unit=True)
)
# if bumblebee.util.asbool(self.parameter("rate", True)):
# try:
# with open("{}/power_now".format(widget.name)) as f:
# rate = (float(f.read())/1000000)
# if rate > 0:
# output = "{} {:.2f}w".format(output, rate)
# except Exception:
# pass
if util.format.asbool(self.parameter("showdevice", False)):
output = "{} ({})".format(output, widget.name)
@ -173,15 +193,13 @@ class Module(core.module.Module):
state = []
capacity = widget.get("capacity")
if widget.get("pen"):
state.append("PEN")
if capacity < 0:
log.debug("battery state: {}".format(state))
return ["critical", "unknown"]
if capacity < int(self.parameter("critical", 10)):
state.append("critical")
elif capacity < int(self.parameter("warning", 20)):
state.append("warning")
if widget.get("ac"):
state.append("AC")
else:
@ -189,16 +207,10 @@ class Module(core.module.Module):
charge = self.__manager.charge_any(self._batteries)
else:
charge = self.__manager.charge(widget.name)
if charge == "Discharging":
if charge in ["Discharging", "Unknown"]:
state.append(
"discharging-{}".format(
min([10, 25, 50, 80, 100], key=lambda i: abs(i - capacity))
)
)
elif charge == "Unknown":
state.append(
"unknown-{}".format(
min([10, 25, 50, 80, 100], key=lambda i: abs(i - capacity))
min([5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], key=lambda i: abs(i - capacity))
)
)
else:
@ -206,6 +218,18 @@ class Module(core.module.Module):
state.append("charged")
else:
state.append("charging")
if (
capacity < int(self.parameter("critical", 10))
and self.__manager.charge_any(self._batteries) == "Discharging"
):
state.append("critical")
elif (
capacity < int(self.parameter("warning", 20))
and self.__manager.charge_any(self._batteries) == "Discharging"
):
state.append("warning")
return state

View file

@ -75,19 +75,15 @@ class Module(core.module.Module):
def popup(self, widget):
"""Show a popup menu."""
menu = util.popup.PopupMenu()
menu = util.popup.menu(self.__config)
if self._status == "On":
menu.add_menuitem("Disable Bluetooth")
menu.add_menuitem("Disable Bluetooth", callback=self._toggle)
elif self._status == "Off":
menu.add_menuitem("Enable Bluetooth")
menu.add_menuitem("Enable Bluetooth", callback=self._toggle)
else:
return
# show menu and get return code
ret = menu.show(widget)
if ret == 0:
# first (and only) item selected.
self._toggle()
menu.show(widget)
def _toggle(self, widget=None):
"""Toggle bluetooth state."""

View file

@ -8,7 +8,6 @@ Parameters:
contributed by `martindoublem <https://github.com/martindoublem>`_ - many thanks!
"""
import os
import re
import subprocess
@ -22,7 +21,6 @@ import core.input
import util.cli
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.status))
@ -37,7 +35,7 @@ class Module(core.module.Module):
def status(self, widget):
"""Get status."""
return self._status
return self._status if self._status.isdigit() and int(self._status) > 1 else ""
def update(self):
"""Update current state."""
@ -46,7 +44,7 @@ class Module(core.module.Module):
)
if state > 0:
connected_devices = self.get_connected_devices()
self._status = "On - {}".format(connected_devices)
self._status = "{}".format(connected_devices)
else:
self._status = "Off"
adapters_cmd = "rfkill list | grep Bluetooth"
@ -58,31 +56,23 @@ class Module(core.module.Module):
def _toggle(self, widget=None):
"""Toggle bluetooth state."""
if "On" in self._status:
state = "false"
else:
state = "true"
cmd = (
"dbus-send --system --print-reply --dest=org.blueman.Mechanism /org/blueman/mechanism org.blueman.Mechanism.SetRfkillState boolean:%s"
% state
)
logging.debug("bt: toggling bluetooth")
util.cli.execute(cmd, ignore_errors=True)
SetRfkillState = self._bus.get_object("org.blueman.Mechanism", "/org/blueman/mechanism").get_dbus_method("SetRfkillState", dbus_interface="org.blueman.Mechanism")
SetRfkillState(self._status == "Off")
def state(self, widget):
"""Get current state."""
state = []
if self._status == "No Adapter Found":
if self._status in [ "No Adapter Found", "Off" ]:
state.append("critical")
elif self._status == "On - 0":
state.append("warning")
elif "On" in self._status and not (self._status == "On - 0"):
state.append("ON")
elif self._status == "0":
state.append("enabled")
else:
state.append("critical")
state.append("connected")
state.append("good")
return state
def get_connected_devices(self):
@ -92,12 +82,8 @@ class Module(core.module.Module):
).GetManagedObjects()
for path, interfaces in objects.items():
if "org.bluez.Device1" in interfaces:
if dbus.Interface(
self._bus.get_object("org.bluez", path),
"org.freedesktop.DBus.Properties",
).Get("org.bluez.Device1", "Connected"):
if dbus.Interface(self._bus.get_object("org.bluez", path), "org.freedesktop.DBus.Properties", ).Get("org.bluez.Device1", "Connected"):
devices += 1
return devices
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -0,0 +1,97 @@
"""Displays temperature of blugon and Controls it.
Use wheel up and down to change temperature, middle click to toggle and right click to reset temperature.
Default Values:
* Minimum temperature: 1000 (red)
* Maximum temperature: 20000 (blue)
* Default temperature: 6600
Requires the following executable:
* blugon
Parameters:
* blugon.step: The amount of increase/decrease on scroll (default: 200)
contributed by `DTan13 <https://github.com/DTan13>`
"""
import core.module
import core.widget
import util.cli
import util.format
import os
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.full_text))
self.__state = True
self.__default = 6600
self.__step = (
util.format.asint(self.parameter("step")) if self.parameter("step") else 200
)
self.__max, self.__min = 20000, 1000
file = open(os.path.expanduser("~/.config/blugon/current"))
self.__current = int(float(file.read()))
events = [
{
"type": "toggle",
"action": self.toggle,
"button": core.input.MIDDLE_MOUSE,
},
{
"type": "blue",
"action": self.blue,
"button": core.input.WHEEL_UP,
},
{
"type": "red",
"action": self.red,
"button": core.input.WHEEL_DOWN,
},
{
"type": "reset",
"action": self.reset,
"button": core.input.RIGHT_MOUSE,
},
]
for event in events:
core.input.register(self, button=event["button"], cmd=event["action"])
def set_temp(self):
temp = self.__current if self.__state else self.__default
util.cli.execute("blugon --setcurrent={}".format(temp))
def full_text(self, widget):
return self.__current if self.__state else self.__default
def state(self, widget):
if not self.__state:
return ["critical"]
def toggle(self, event):
self.__state = not self.__state
self.set_temp()
def reset(self, event):
self.__current = 6600
self.set_temp()
def blue(self, event):
if self.__state and (self.__current < self.__max):
self.__current += self.__step
self.set_temp()
def red(self, event):
if self.__state and (self.__current > self.__min):
self.__current -= self.__step
self.set_temp()
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -24,9 +24,9 @@ Parameters:
* cpu2.fanspeed
* cpu2.colored: 1 for colored per core load graph, 0 for mono (default)
* cpu2.temp_pattern: pattern to look for in the output of 'sensors -u';
required if cpu2.temp widged is used
required if cpu2.temp widget is used
* cpu2.fan_pattern: pattern to look for in the output of 'sensors -u';
required if cpu2.fanspeed widged is used
required if cpu2.fanspeed widget 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.

View file

@ -0,0 +1,158 @@
"""Multiwidget CPU module
Can display any combination of:
* max CPU frequency
* total CPU load in percents (integer value)
* per-core CPU load as graph - either mono or colored
* CPU temperature (in Celsius degrees)
* CPU fan speed
Requirements:
* the psutil Python module for the first three items from the list above
* sensors executable for the rest
Parameters:
* cpu3.layout: Space-separated list of widgets to add.
Possible widgets are:
* cpu3.maxfreq
* cpu3.cpuload
* cpu3.coresload
* cpu3.temp
* cpu3.fanspeed
* cpu3.colored: 1 for colored per core load graph, 0 for mono (default)
* cpu3.temp_json: json path to look for in the output of 'sensors -j';
required if cpu3.temp widget is used
* cpu3.fan_json: json path to look for in the output of 'sensors -j';
required if cpu3.fanspeed widget is used
Note: if you are getting 'n/a' for CPU temperature / fan speed, then you're
lacking the aforementioned json path settings or they have wrong values.
Example json paths:
* `cpu3.temp_json="coretemp-isa-0000.Package id 0.temp1_input"`
* `cpu3.fan_json="thinkpad-isa-0000.fan1.fan1_input"`
contributed by `SuperQ <https://github.com/SuperQ>`
based on cpu2 by `<somospocos <https://github.com/somospocos>`
"""
import json
import psutil
import core.module
import util.cli
import util.graph
import util.format
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, [])
self.__layout = self.parameter(
"layout", "cpu3.maxfreq cpu3.cpuload cpu3.coresload cpu3.temp cpu3.fanspeed"
)
self.__widget_names = self.__layout.split()
self.__colored = util.format.asbool(self.parameter("colored", False))
for widget_name in self.__widget_names:
if widget_name == "cpu3.maxfreq":
widget = self.add_widget(name=widget_name, full_text=self.maxfreq)
widget.set("type", "freq")
elif widget_name == "cpu3.cpuload":
widget = self.add_widget(name=widget_name, full_text=self.cpuload)
widget.set("type", "load")
elif widget_name == "cpu3.coresload":
widget = self.add_widget(name=widget_name, full_text=self.coresload)
widget.set("type", "loads")
elif widget_name == "cpu3.temp":
widget = self.add_widget(name=widget_name, full_text=self.temp)
widget.set("type", "temp")
elif widget_name == "cpu3.fanspeed":
widget = self.add_widget(name=widget_name, full_text=self.fanspeed)
widget.set("type", "fan")
if self.__colored:
widget.set("pango", True)
self.__temp_json = self.parameter("temp_json")
if self.__temp_json is None:
self.__temp = "n/a"
self.__fan_json = self.parameter("fan_json")
if self.__fan_json is None:
self.__fan = "n/a"
# maxfreq is loaded only once at startup
if "cpu3.maxfreq" in self.__widget_names:
self.__maxfreq = psutil.cpu_freq().max / 1000
def maxfreq(self, _):
return "{:.2f}GHz".format(self.__maxfreq)
def cpuload(self, _):
return "{:>3}%".format(self.__cpuload)
def add_color(self, bar):
"""add color as pango markup to a bar"""
if bar in ["", ""]:
color = self.theme.color("green", "green")
elif bar in ["", ""]:
color = self.theme.color("yellow", "yellow")
elif bar in ["", ""]:
color = self.theme.color("orange", "orange")
elif bar in ["", ""]:
color = self.theme.color("red", "red")
colored_bar = '<span foreground="{}">{}</span>'.format(color, bar)
return colored_bar
def coresload(self, _):
mono_bars = [util.graph.hbar(x) for x in self.__coresload]
if not self.__colored:
return "".join(mono_bars)
colored_bars = [self.add_color(x) for x in mono_bars]
return "".join(colored_bars)
def temp(self, _):
if self.__temp == "n/a" or self.__temp == 0:
return "n/a"
return "{}°C".format(self.__temp)
def fanspeed(self, _):
if self.__fanspeed == "n/a":
return "n/a"
return "{}RPM".format(self.__fanspeed)
def _parse_sensors_output(self):
output = util.cli.execute("sensors -j")
json_data = json.loads(output)
temp = "n/a"
fan = "n/a"
temp_json = json_data
fan_json = json_data
for path in self.__temp_json.split('.'):
temp_json = temp_json[path]
for path in self.__fan_json.split('.'):
fan_json = fan_json[path]
if temp_json is not None:
temp = float(temp_json)
if fan_json is not None:
fan = int(fan_json)
return temp, fan
def update(self):
if "cpu3.maxfreq" in self.__widget_names:
self.__maxfreq = psutil.cpu_freq().max / 1000
if "cpu3.cpuload" in self.__widget_names:
self.__cpuload = round(psutil.cpu_percent(percpu=False))
if "cpu3.coresload" in self.__widget_names:
self.__coresload = psutil.cpu_percent(percpu=True)
if "cpu3.temp" in self.__widget_names or "cpu3.fanspeed" in self.__widget_names:
self.__temp, self.__fanspeed = self._parse_sensors_output()
def state(self, widget):
"""for having per-widget icons"""
return [widget.get("type", "")]
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -28,6 +28,8 @@ class Module(core.module.Module):
self.__states = {"unknown": ["unknown", "critical"],
"true": ["muted", "warning"],
"false": ["unmuted"]}
if util.format.asbool(self.parameter("disabled", False)):
util.cli.execute("dunstctl set-paused true", ignore_errors=True)
def toggle_state(self, event):
util.cli.execute("dunstctl set-paused toggle", ignore_errors=True)

View file

@ -0,0 +1,171 @@
"""Displays first upcoming event in google calendar.
Events that are set as 'all-day' will not be shown.
Requires credentials.json from a google api application where the google calendar api is installed.
On first time run the browser will open and google will ask for permission for this app to access
the google calendar and then save a .gcalendar_token.json file to the credentials_path directory
which stores this permission.
A refresh is done every 15 minutes.
Parameters:
* gcalendar.time_format: Format time output. Defaults to "%H:%M".
* gcalendar.date_format: Format date output. Defaults to "%d.%m.%y".
* gcalendar.credentials_path: Path to credentials.json. Defaults to "~/".
* gcalendar.locale: locale to use rather than the system default.
Requires these pip packages:
* google-api-python-client >= 1.8.0
* google-auth-httplib2
* google-auth-oauthlib
"""
# This import belongs to the google code
from __future__ import print_function
from dateutil.parser import parse as dtparse
import core.module
import core.widget
import core.decorators
import util.format
import datetime
import os.path
import locale
import time
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
# Minutes
update_every = 15
class Module(core.module.Module):
@core.decorators.every(minutes=update_every)
def __init__(self, config, theme):
super().__init__(config, theme, [core.widget.Widget(self.__datetime), core.widget.Widget(self.__summary)])
self.__error = False
self.__time_format = self.parameter("time_format", "%H:%M")
self.__date_format = self.parameter("date_format", "%d.%m.%y")
self.__credentials_path = os.path.expanduser(
self.parameter("credentials_path", "~/")
)
self.__credentials = os.path.join(self.__credentials_path, "credentials.json")
self.__token = os.path.join(self.__credentials_path, ".gcalendar_token.json")
l = locale.getdefaultlocale()
if not l or l == (None, None):
l = ("en_US", "UTF-8")
lcl = self.parameter("locale", ".".join(l))
try:
locale.setlocale(locale.LC_TIME, lcl.split("."))
except Exception:
locale.setlocale(locale.LC_TIME, ("en_US", "UTF-8"))
self.__last_update = time.time()
self.__gcalendar_date, self.__gcalendar_summary = self.__fetch_from_calendar()
def hidden(self):
return self.__error
def __datetime(self, _):
return self.__gcalendar_date
@core.decorators.scrollable
def __summary(self, _):
return self.__gcalendar_summary
def __fetch_from_calendar(self):
SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]
creds = None
try:
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists(self.__token):
creds = Credentials.from_authorized_user_file(self.__token, SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
self.__credentials, SCOPES
)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open(self.__token, "w") as token:
token.write(creds.to_json())
service = build("calendar", "v3", credentials=creds)
# Call the Calendar API
now = datetime.datetime.utcnow().isoformat() + "Z" # 'Z' indicates UTC time
end = (
datetime.datetime.utcnow() + datetime.timedelta(days=7)
).isoformat() + "Z" # 'Z' indicates UTC time
# Get all calendars
calendar_list = service.calendarList().list().execute()
event_list = []
for calendar_list_entry in calendar_list["items"]:
calendar_id = calendar_list_entry["id"]
events_result = (
service.events()
.list(
calendarId=calendar_id,
timeMin=now,
timeMax=end,
singleEvents=True,
orderBy="startTime",
)
.execute()
)
events = events_result.get("items", [])
for event in events:
start = dtparse(
event["start"].get("dateTime", event["start"].get("date"))
)
# Only add to list if not an whole day event
if start.tzinfo:
event_list.append(
{
"date": start,
"summary": event["summary"],
"type": event["eventType"],
}
)
sorted_list = sorted(event_list, key=lambda t: t["date"])
next_event = sorted_list[0]
if next_event["date"] >= datetime.datetime.now(datetime.timezone.utc):
if next_event["date"].date() == datetime.datetime.utcnow().date():
dt = next_event["date"].astimezone()\
.strftime(f"{self.__time_format}")
else:
dt = next_event["date"].astimezone()\
.strftime(f"{self.__date_format} {self.__time_format}")
return (dt, next_event["summary"])
return (None, "No upcoming events.")
except:
self.__error = True
def update(self):
# Since scrolling runs the update command and therefore negates the
# every decorator, this need to be stopped
# to not break the API rules of google.
if self.__last_update+(update_every*60) < time.time():
self.__last_update = time.time()
self.__gcalendar_date, self.__gcalendar_summary = self.__fetch_from_calendar()
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -0,0 +1,87 @@
# pylint: disable=C0111,R0903
"""
Displays the GitLab todo count:
* https://docs.gitlab.com/ee/user/todos.html
* https://docs.gitlab.com/ee/api/todos.html
Uses `xdg-open` or `x-www-browser` to open web-pages.
Requires the following library:
* requests
Errors:
if the GitLab todo query failed, the shown value is `n/a`
Parameters:
* gitlab.token: GitLab personal access token, the token needs to have the "read_api" scope.
* gitlab.host: Host of the GitLab instance, default is "gitlab.com".
* gitlab.actions: Comma separated actions to be parsed (e.g.: gitlab.actions=assigned,approval_required)
"""
import shutil
import requests
import core.decorators
import core.input
import core.module
import core.widget
import util
class Module(core.module.Module):
@core.decorators.every(minutes=5)
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.gitlab))
self.background = True
self.__label = ""
self.__host = self.parameter("host", "gitlab.com")
self.__actions = []
actions = self.parameter("actions", "")
if actions:
self.__actions = util.format.aslist(actions)
self.__requests = requests.Session()
self.__requests.headers.update({"PRIVATE-TOKEN": self.parameter("token", "")})
cmd = "xdg-open"
if not shutil.which(cmd):
cmd = "x-www-browser"
core.input.register(
self,
button=core.input.LEFT_MOUSE,
cmd="{cmd} https:/{host}//dashboard/todos".format(
cmd=cmd, host=self.__host
),
)
def gitlab(self, _):
return self.__label
def update(self):
try:
url = "https://{host}/api/v4/todos".format(host=self.__host)
response = self.__requests.get(url)
todos = response.json()
if self.__actions:
todos = [t for t in todos if t["action_name"] in self.__actions]
self.__label = str(len(todos))
except Exception as e:
self.__label = "n/a"
def state(self, widget):
state = []
try:
if int(self.__label) > 0:
state.append("warning")
except ValueError:
pass
return state
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -3,7 +3,7 @@
"""
Displays the message that's received via unix socket.
Parameteres:
Parameters:
* messagereceiver : Unix socket address (e.g: /tmp/bumblebee_messagereceiver.sock)
Example:

View file

@ -42,6 +42,7 @@ Parameters:
if {file} = '/foo/bar.baz', then {file2} = 'bar'
* mpd.host: MPD host to connect to. (mpc behaviour by default)
* mpd.port: MPD port to connect to. (mpc behaviour by default)
* mpd.layout: Space-separated list of widgets to add. Possible widgets are the buttons/toggles mpd.prev, mpd.next, mpd.shuffle and mpd.repeat, and the main display with play/pause function mpd.main.
contributed by `alrayyes <https://github.com/alrayyes>`_ - many thanks!
@ -73,10 +74,12 @@ class Module(core.module.Module):
self._repeat = False
self._tags = defaultdict(lambda: "")
if not self.parameter("host"):
self._hostcmd = ""
else:
self._hostcmd = " -h " + self.parameter("host")
self._hostcmd = ""
if self.parameter("host"):
self._hostcmd = " -h {}".format(self.parameter("host"))
if self.parameter("port"):
self._hostcmd += " -p {}".format(self.parameter("port"))
# Create widgets
widget_map = {}
@ -94,6 +97,12 @@ class Module(core.module.Module):
"cmd": "mpc toggle" + self._hostcmd,
}
widget.full_text(self.description)
elif widget_name == "mpd.toggle":
widget_map[widget] = {
"button": core.input.LEFT_MOUSE,
"cmd": "mpc toggle" + self._hostcmd,
}
widget.full_text(self.toggle)
elif widget_name == "mpd.next":
widget_map[widget] = {
"button": core.input.LEFT_MOUSE,
@ -127,6 +136,9 @@ class Module(core.module.Module):
def description(self, widget):
return string.Formatter().vformat(self._fmt, (), self._tags)
def toggle(self, widget):
return str(util.cli.execute("mpc status %currenttime%/%totaltime%", ignore_errors=True)).strip()
def update(self):
self._load_song()

View file

@ -3,7 +3,7 @@
"""Displays update information per repository for pacman.
Parameters:
* pacman.sum: If you prefere displaying updates with a single digit (defaults to 'False')
* pacman.sum: If you prefer displaying updates with a single digit (defaults to 'False')
Requires the following executables:
* fakeroot

View file

@ -0,0 +1,88 @@
"""get volume level or control it
Requires the following executable:
* pamixer
Parameters:
* pamixer.percent_change: How much to change volume by when scrolling on the module (default is 4%)
heavily based on amixer module
"""
import re
import core.module
import core.widget
import core.input
import util.cli
import util.format
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.volume))
self.__level = "volume 0%"
self.__muted = True
self.__change = util.format.asint(
self.parameter("percent_change", "4%").strip("%"), 0, 200
)
events = [
{
"type": "mute",
"action": self.toggle,
"button": core.input.LEFT_MOUSE,
},
{
"type": "volume",
"action": self.increase_volume,
"button": core.input.WHEEL_UP,
},
{
"type": "volume",
"action": self.decrease_volume,
"button": core.input.WHEEL_DOWN,
},
]
for event in events:
core.input.register(self, button=event["button"], cmd=event["action"])
def toggle(self, event):
self.set_parameter("--toggle-mute")
def increase_volume(self, event):
self.set_parameter("--increase {}".format(self.__change))
def decrease_volume(self, event):
self.set_parameter("--decrease {}".format(self.__change))
def set_parameter(self, parameter):
util.cli.execute("pamixer {}".format(parameter))
def volume(self, widget):
if self.__level == "volume 0%":
self.__muted = True
return self.__level
m = re.search(r"([\d]+)\%", self.__level)
if m:
if m.group(1) != "0%" in self.__level:
self.__muted = False
return "volume {}%".format(m.group(1))
else:
return "volume 0%"
def update(self):
try:
volume = util.cli.execute("pamixer --get-volume-human".format())
self.__level = volume
self.__muted = False
except Exception as e:
self.__level = "volume 0%"
def state(self, widget):
if self.__muted:
return ["warning", "muted"]
return ["unmuted"]
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -0,0 +1,31 @@
# pylint: disable=C0111,R0903
"""Displays the current date and time in Persian(Jalali) Calendar.
Requires the following python packages:
* jdatetime
Parameters:
* datetime.format: strftime()-compatible formatting string. default: "%A %d %B" e.g., "جمعه ۱۳ اسفند"
* datetime.locale: locale to use. default: "fa_IR"
"""
import jdatetime
import core.decorators
from modules.core.datetime import Module as dtmodule
class Module(dtmodule):
@core.decorators.every(minutes=1)
def __init__(self, config, theme):
super().__init__(config, theme, dtlibrary=jdatetime)
def default_format(self):
return "%A %d %B"
def default_locale(self):
return ("fa_IR", "UTF-8")
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -4,13 +4,20 @@
Parameters:
* pihole.address : pi-hole address (e.q: http://192.168.1.3)
* pihole.pwhash : pi-hole webinterface password hash (can be obtained from the /etc/pihole/SetupVars.conf file)
* pihole.apitoken : pi-hole API token (can be obtained in the pi-hole webinterface (Settings -> API)
OR (deprecated!)
* pihole.pwhash : pi-hole webinterface password hash (can be obtained from the /etc/pihole/SetupVars.conf file)
contributed by `bbernhard <https://github.com/bbernhard>`_ - many thanks!
"""
import requests
import logging
import core.module
import core.widget
import core.input
@ -22,7 +29,18 @@ class Module(core.module.Module):
super().__init__(config, theme, core.widget.Widget(self.pihole_status))
self._pihole_address = self.parameter("address", "")
self._pihole_pw_hash = self.parameter("pwhash", "")
pihole_pw_hash = self.parameter("pwhash", "")
pihole_api_token = self.parameter("apitoken", "")
self._pihole_secret = (
pihole_api_token if pihole_api_token != "" else pihole_pw_hash
)
if pihole_pw_hash != "":
logging.warn(
"pihole: The 'pwhash' parameter is deprecated - consider using the 'apitoken' parameter instead!"
)
self._pihole_status = None
self._ads_blocked_today = "-"
self.update_pihole_status()
@ -42,7 +60,11 @@ class Module(core.module.Module):
def update_pihole_status(self):
try:
data = requests.get(self._pihole_address + "/admin/api.php?summary").json()
data = requests.get(
self._pihole_address
+ "/admin/api.php?summary&auth="
+ self._pihole_secret
).json()
self._pihole_status = True if data["status"] == "enabled" else False
self._ads_blocked_today = data["ads_blocked_today"]
except Exception as e:
@ -56,13 +78,13 @@ class Module(core.module.Module):
req = requests.get(
self._pihole_address
+ "/admin/api.php?disable&auth="
+ self._pihole_pw_hash
+ self._pihole_secret
)
else:
req = requests.get(
self._pihole_address
+ "/admin/api.php?enable&auth="
+ self._pihole_pw_hash
+ self._pihole_secret
)
if req is not None:
if req.status_code == 200:

View file

@ -0,0 +1,90 @@
"""get volume level or control it
Requires the following executable:
* wpctl
Parameters:
* wpctl.percent_change: How much to change volume by when scrolling on the module (default is 4%)
heavily based on amixer module
"""
import re
import core.module
import core.widget
import core.input
import util.cli
import util.format
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.volume))
self.__level = "N/A"
self.__muted = True
self.__change = (
util.format.asint(self.parameter("percent_change", "4%").strip("%"), 0, 200)
/ 100.0
) # divide by 100 because wpctl represents 100% volume as 1.00, 50% as 0.50, etc
self.__id = self.parameter("sink_id") or "@DEFAULT_AUDIO_SINK@"
events = [
{
"type": "mute",
"action": self.toggle,
"button": core.input.LEFT_MOUSE,
},
{
"type": "volume",
"action": self.increase_volume,
"button": core.input.WHEEL_UP,
},
{
"type": "volume",
"action": self.decrease_volume,
"button": core.input.WHEEL_DOWN,
},
]
for event in events:
core.input.register(self, button=event["button"], cmd=event["action"])
def toggle(self, event):
util.cli.execute("wpctl set-mute {} toggle".format(self.__id))
def increase_volume(self, event):
util.cli.execute(
"wpctl set-volume --limit 1.0 {} {}+".format(self.__id, self.__change)
)
def decrease_volume(self, event):
util.cli.execute(
"wpctl set-volume --limit 1.0 {} {}-".format(self.__id, self.__change)
)
def volume(self, widget):
if self.__level == "N/A":
return self.__level
return "{}%".format(int(float(self.__level) * 100))
def update(self):
try:
# `wpctl get-volume` will return a string like "Volume: n.nn" or "Volume: n.nn [MUTED]"
volume = util.cli.execute("wpctl get-volume {}".format(self.__id))
v = re.search("\d\.\d+", volume)
m = re.search("MUTED", volume)
self.__level = v.group()
self.__muted = True if m else False
except Exception:
self.__level = "N/A"
def state(self, widget):
if self.__muted:
return ["warning", "muted"]
return ["unmuted"]
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

21
bumblebee_status/modules/contrib/playerctl.py Executable file → Normal file
View file

@ -12,6 +12,7 @@ Parameters:
Widget names are: playerctl.song, playerctl.prev, playerctl.pause, playerctl.next
* playerctl.args: The arguments added to playerctl.
You can check 'playerctl --help' or `its README <https://github.com/altdesktop/playerctl#using-the-cli>`_. For example, it could be '-p vlc,%any'.
* playerctl.hide: Hide the widgets when no players are found. Defaults to "false".
Parameters are inspired by the `spotify` module, many thanks to its developers!
@ -32,6 +33,9 @@ class Module(core.module.Module):
self.background = True
self.__hide = util.format.asbool(self.parameter("hide", "false"));
self.__hidden = self.__hide
self.__layout = util.format.aslist(
self.parameter(
"layout", "playerctl.prev, playerctl.song, playerctl.pause, playerctl.next"
@ -83,14 +87,25 @@ class Module(core.module.Module):
if isinstance(callback_options, dict):
core.input.register(widget, **callback_options)
def update(self):
def hidden(self):
return self.__hidden
def status(self):
try:
playback_status = str(util.cli.execute(self.__cmd + "status 2>&1 || true", shell = True)).strip()
if playback_status == "No players found":
playback_status = None
return None
return playback_status
except Exception as e:
logging.exception(e)
playback_status = None
return None
def update(self):
playback_status = self.status()
if not playback_status:
self.__hidden = self.__hide
else:
self.__hidden = False
for widget in self.widgets():
if playback_status:
if widget.name == "playerctl.pause":

View file

@ -13,7 +13,7 @@ Parameters:
Example: 'notify-send 'Time up!''. If you want to chain multiple commands,
please use an external wrapper script and invoke that. The module itself does
not support command chaining (see https://github.com/tobi-wan-kenobi/bumblebee-status/issues/532
for a detailled explanation)
for a detailed explanation)
contributed by `martindoublem <https://github.com/martindoublem>`_, inspired by `karthink <https://github.com/karthink>`_ - many thanks!
"""

View file

@ -0,0 +1,99 @@
# pylint: disable=C0111,R0903
"""
Displays the current Power-Profile active
Left-Click or Right-Click as well as Scrolling up / down changes the active Power-Profile
Prerequisites:
* dbus-python
* power-profiles-daemon
"""
import dbus
import core.module
import core.widget
import core.input
class PowerProfileManager:
def __init__(self):
self.POWER_PROFILES_NAME = "net.hadess.PowerProfiles"
self.POWER_PROFILES_PATH = "/net/hadess/PowerProfiles"
self.PP_PROPERTIES_CURRENT_POWER_PROFILE = "ActiveProfile"
self.PP_PROPERTIES_ALL_POWER_PROFILES = "Profiles"
self.DBUS_PROPERTIES = "org.freedesktop.DBus.Properties"
bus = dbus.SystemBus()
pp_proxy = bus.get_object(self.POWER_PROFILES_NAME, self.POWER_PROFILES_PATH)
self.pp_interface = dbus.Interface(pp_proxy, self.DBUS_PROPERTIES)
def get_current_power_profile(self):
return self.pp_interface.Get(
self.POWER_PROFILES_NAME, self.PP_PROPERTIES_CURRENT_POWER_PROFILE
)
def __get_all_power_profile_names(self):
power_profiles = self.pp_interface.Get(
self.POWER_PROFILES_NAME, self.PP_PROPERTIES_ALL_POWER_PROFILES
)
power_profiles_names = []
for pp in power_profiles:
power_profiles_names.append(pp["Profile"])
return power_profiles_names
def next_power_profile(self, event):
all_pp_names = self.__get_all_power_profile_names()
current_pp_index = self.__get_current_pp_index()
next_index = 0
if current_pp_index != (len(all_pp_names) - 1):
next_index = current_pp_index + 1
self.pp_interface.Set(
self.POWER_PROFILES_NAME,
self.PP_PROPERTIES_CURRENT_POWER_PROFILE,
all_pp_names[next_index],
)
def prev_power_profile(self, event):
all_pp_names = self.__get_all_power_profile_names()
current_pp_index = self.__get_current_pp_index()
last_index = len(all_pp_names) - 1
if current_pp_index is not 0:
last_index = current_pp_index - 1
self.pp_interface.Set(
self.POWER_PROFILES_NAME,
self.PP_PROPERTIES_CURRENT_POWER_PROFILE,
all_pp_names[last_index],
)
def __get_current_pp_index(self):
all_pp_names = self.__get_all_power_profile_names()
current_pp = self.get_current_power_profile()
return all_pp_names.index(current_pp)
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.full_text))
self.pp_manager = PowerProfileManager()
core.input.register(
self, button=core.input.WHEEL_UP, cmd=self.pp_manager.next_power_profile
)
core.input.register(
self, button=core.input.WHEEL_DOWN, cmd=self.pp_manager.prev_power_profile
)
core.input.register(
self, button=core.input.LEFT_MOUSE, cmd=self.pp_manager.next_power_profile
)
core.input.register(
self, button=core.input.RIGHT_MOUSE, cmd=self.pp_manager.prev_power_profile
)
def full_text(self, widgets):
return self.pp_manager.get_current_power_profile()
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -29,6 +29,9 @@ class Module(core.module.Module):
super().__init__(config, theme, core.widget.Widget(self.get_progress_text))
self.__active = False
def hidden(self):
return not self.__active
def get_progress_text(self, widget):
if self.update_progress_info(widget):
width = util.format.asint(self.parameter("barwidth", 8))
@ -53,7 +56,7 @@ class Module(core.module.Module):
return self.parameter("placeholder", "n/a")
def update_progress_info(self, widget):
"""Update widget's informations about the copy"""
"""Update widget's information about the copy"""
if not self.__active:
return
@ -61,8 +64,8 @@ class Module(core.module.Module):
# 1. pid
# 2. command
# 3. arguments
# 4. progress (xx.x formated)
# 5. quantity (.. unit / .. unit formated)
# 4. progress (xx.x formatted)
# 5. quantity (.. unit / .. unit formatted)
# 6. speed
# 7. time remaining
extract_nospeed = re.compile(
@ -77,7 +80,7 @@ class Module(core.module.Module):
result = extract_wtspeed.match(raw)
if not result:
# Abord speed measures
# Abort speed measures
raw = util.cli.execute("progress -q")
result = extract_nospeed.match(raw)

View file

@ -1,28 +1,155 @@
"""Displays public IP address
"""
Displays information about the public IP address associated with the default route:
* Public IP address
* Country Name
* Country Code
* City Name
* Geographic Coordinates
Left mouse click on the widget forces immediate update.
Any change to the default route will cause the widget to update.
Requirements:
* netifaces
Parameters:
* publicip.format: Format string (defaults to {ip} ({country_code}))
* Available format strings - ip, country_name, country_code, city_name, coordinates
Examples:
* bumblebee-status -m publicip -p publicip.format="{ip} ({country_code})"
* bumblebee-status -m publicip -p publicip.format="{ip} which is in {city_name}"
* bumblebee-status -m publicip -p publicip.format="Your packets are right here: {coordinates}"
contributed by `tfwiii <https://github.com/tfwiii>` - many thanks!
"""
import re
import threading
import netifaces
import time
import core.module
import core.widget
import core.input
import core.decorators
import util.format
import util.location
import logging
log = logging.getLogger(__name__)
class Module(core.module.Module):
@core.decorators.every(minutes=60)
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.public_ip))
super().__init__(config, theme, core.widget.Widget(self.publicip))
self.__ip = ""
self.__previous_default_route = None
self.__current_default_route = None
self.background = True
def public_ip(self, widget):
return self.__ip
# Immediate update (override default) when left click on widget
core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.__click_update)
# By default show: <ip> (<2 letter country code>)
self._format = self.parameter("format", "{ip} ({country_code})")
self.__monitor = threading.Thread(target=self.monitor, args=())
self.__monitor.start()
def monitor(self):
__previous_ips = set()
__current_ips = set()
# Initially set to True to force an info update on first pass
__information_changed = True
self.update()
while threading.main_thread().is_alive():
__current_ips.clear()
# Look for any changes to IP addresses
try:
for interface in netifaces.interfaces():
try:
__current_ips.add(netifaces.ifaddresses(interface)[2][0]['addr'])
except:
pass
except:
# If not ip address information found clear __current_ips
__current_ips.clear()
# If a change of any interfaces' IP then flag change
if __current_ips.symmetric_difference(__previous_ips):
__previous_ips = __current_ips.copy()
__information_changed = True
# Update if change is flagged
if __information_changed:
__information_changed = False
self.update()
# Throttle the calls to netifaces
time.sleep(1)
def publicip(self, widget):
if widget.get("public_ip") is None:
return "n/a"
return self._format.format(
ip = widget.get("public_ip", "-"),
country_name = widget.get("country_name", "-"),
country_code = widget.get("country_code", "-"),
city_name = widget.get("city_name", "-"),
coordinates = widget.get("coordinates", "-"),
)
def __click_update(self, event):
util.location.reset()
def update(self):
widget = self.widget()
try:
self.__ip = util.location.public_ip()
except Exception:
self.__ip = "n/a"
util.location.reset()
time.sleep(5) # wait for reset to complete before querying results
# Fetch fresh location information
__info = util.location.location_info()
__raw_lat = __info["latitude"]
__raw_lon = __info["longitude"]
# Contstruct coordinates string if util.location has provided required info
if isinstance(__raw_lat, float) and isinstance(__raw_lon, float):
__lat = float("{:.2f}".format(__raw_lat))
__lon = float("{:.2f}".format(__raw_lon))
if __lat < 0:
__coords = str(__lat) + "°S"
else:
__coords = str(__lat) + "°N"
__coords += ","
if __lon < 0:
__coords += str(__lon) + "°W"
else:
__coords += str(__lon) + "°E"
else:
__coords = "Unknown"
# Set widget values
widget.set("public_ip", __info["public_ip"])
widget.set("country_name", __info["country"])
widget.set("country_code", __info["country_code"])
widget.set("city_name", __info["city_name"])
widget.set("coordinates", __coords)
# Update widget values
core.event.trigger("update", [widget.module.id], redraw_only=True)
except Exception as ex:
widget.set("public_ip", None)
logging.error(str(ex))
def state(self, widget):
return widget.get("state", None)
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -5,6 +5,10 @@
module will have normal highlighting if there are zero notifications,
"warning" highlighting if there are nonzero notifications,
"critical" highlighting if there are any critical notifications
Parameters:
* rofication.regolith: Switch to regolith fork of rofication, see <https://github.com/regolith-linux/regolith-rofication>.
"""
import core.module
@ -20,6 +24,7 @@ class Module(core.module.Module):
super().__init__(config, theme, core.widget.Widget(self.full_text))
self.__critical = False
self.__numnotifications = 0
self.__regolith = self.parameter("regolith", False)
def full_text(self, widgets):
@ -27,10 +32,16 @@ class Module(core.module.Module):
client.connect("/tmp/rofi_notification_daemon")
# below code will fetch two numbers in a list, e.g. ['22', '1']
# first is total number of notifications, second is number of critical notifications
client.sendall(bytes("num", "utf-8"))
if self.__regolith:
client.sendall(bytes("num\n", "utf-8"))
else:
client.sendall(bytes("num", "utf-8"))
val = client.recv(512)
val = val.decode("utf-8")
l = val.split('\n',2)
if self.__regolith:
l = val.split(',',2)
else:
l = val.split('\n',2)
self.__numnotifications = int(l[0])
self.__critical = bool(int(l[1]))
return self.__numnotifications

View file

@ -55,7 +55,7 @@ class Module(core.module.Module):
self._state = []
self._newspaper_filename = tempfile.mktemp(".html")
self._newspaper_file = tempfile.NamedTemporaryFile(mode="w", suffix=".html")
self._last_refresh = 0
self._last_update = 0
@ -308,10 +308,11 @@ class Module(core.module.Module):
while newspaper_items:
content += self._create_news_section(newspaper_items)
open(self._newspaper_filename, "w").write(
self._newspaper_file.write(
HTML_TEMPLATE.replace("[[CONTENT]]", content)
)
webbrowser.open("file://" + self._newspaper_filename)
self._newspaper_file.flush()
webbrowser.open("file://" + self._newspaper_file.name)
self._update_history("newspaper")
self._save_history()

View file

@ -41,6 +41,7 @@ class Module(core.module.Module):
super().__init__(config, theme, core.widget.Widget(self.get_output))
self.__command = self.parameter("command", 'echo "no command configured"')
self.__command = os.path.expanduser(self.__command)
self.__async = util.format.asbool(self.parameter("async"))
if self.__async:
@ -52,6 +53,7 @@ class Module(core.module.Module):
def set_output(self, value):
self.__output = value
core.event.trigger("update", [self.id], redraw_only=True)
@core.decorators.scrollable
def get_output(self, _):

View file

@ -10,7 +10,7 @@ Requires the following executables:
* smartctl
Parameters:
* smartstatus.display: how to display (defaults to 'combined', other choices: 'combined_singles', 'seperate' or 'singles')
* smartstatus.display: how to display (defaults to 'combined', other choices: 'combined_singles', 'separate' or 'singles')
* smartstatus.drives: in the case of singles which drives to display, separated comma list value, multiple accepted (defaults to 'sda', example:'sda,sdc')
* smartstatus.show_names: boolean in the form of "True" or "False" to show the name of the drives in the form of sda, sbd, combined or none at all.
"""

View file

@ -16,7 +16,7 @@ Parameters:
Format Strings:
* Format strings are indicated by double %%
* They represent a leaf in the JSON tree, layers seperated by '.'
* They represent a leaf in the JSON tree, layers separated by '.'
* Boolean values can be overwritten by appending '%true%false'
in the format string
* Example: to reference 'open' in '{'state':{'open': true}}'

View file

@ -110,7 +110,8 @@ class Module(core.module.Module):
def hidden(self):
return self.string_song == ""
def __get_song(self):
@core.decorators.scrollable
def __get_song(self, widget):
bus = self.__bus
if self.__bus_name == "spotifyd":
spotify = bus.get_object(
@ -128,11 +129,10 @@ class Module(core.module.Module):
artist=",".join(props.get("xesam:artist")),
trackNumber=str(props.get("xesam:trackNumber")),
)
return self.__song
def update(self):
try:
self.__get_song()
if self.__bus_name == "spotifyd":
bus = self.__bus.get_object(
"org.mpris.MediaPlayer2.spotifyd", "/org/mpris/MediaPlayer2"
@ -156,7 +156,7 @@ class Module(core.module.Module):
widget.set("state", "paused")
elif widget.name == "spotify.song":
widget.set("state", "song")
widget.full_text(self.__song)
widget.full_text(self.__get_song(widget))
except Exception as e:
self.__song = ""

View file

@ -5,7 +5,9 @@
Parameters:
* stock.symbols : Comma-separated list of symbols to fetch
* stock.change : Should we fetch change in stock value (defaults to True)
* stock.apikey : API key created on https://alphavantage.co
* stock.url : URL to use, defaults to "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={symbol}&apikey={apikey}"
* stock.fields : Fields from the response to show, defaults to "01. symbol,05. price,10. change percent"
contributed by `msoulier <https://github.com/msoulier>`_ - many thanks!
@ -22,6 +24,12 @@ import core.decorators
import util.format
def flatten(d, result):
for k, v in d.items():
if type(v) is dict:
flatten(v, result)
else:
result[k] = v
class Module(core.module.Module):
@core.decorators.every(hours=1)
@ -29,41 +37,41 @@ class Module(core.module.Module):
super().__init__(config, theme, core.widget.Widget(self.value))
self.__symbols = self.parameter("symbols", "")
self.__apikey = self.parameter("apikey", None)
self.__fields = self.parameter("fields", "01. symbol,05. price,10. change percent").split(",")
self.__url = self.parameter("url", "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={symbol}&apikey={apikey}")
self.__change = util.format.asbool(self.parameter("change", True))
self.__value = None
self.__values = []
def value(self, widget):
results = []
if not self.__value:
return "n/a"
data = json.loads(self.__value)
result = ""
for symbol in data["quoteResponse"]["result"]:
valkey = "regularMarketChange" if self.__change else "regularMarketPrice"
sym = symbol.get("symbol", "n/a")
currency = symbol.get("currency", "USD")
val = "n/a" if not valkey in symbol else "{:.2f}".format(symbol[valkey])
results.append("{} {} {}".format(sym, val, currency))
return " ".join(results)
for value in self.__values:
res = {}
flatten(value, res)
for field in self.__fields:
result += res.get(field, "n/a") + " "
result = result[:-1]
return result
def fetch(self):
results = []
if self.__symbols:
url = "https://query1.finance.yahoo.com/v7/finance/quote?symbols="
url += (
self.__symbols
+ "&fields=regularMarketPrice,currency,regularMarketChange"
)
try:
return urllib.request.urlopen(url).read().strip()
except urllib.request.URLError:
logging.error("unable to open stock exchange url")
return None
for symbol in self.__symbols.split(","):
url = self.__url.format(symbol=symbol, apikey=self.__apikey)
try:
results.append(json.loads(urllib.request.urlopen(url).read().strip()))
except urllib.request.URLError:
logging.error("unable to open stock exchange url")
return []
else:
logging.error("unable to retrieve stock exchange rate")
return None
return []
return results
def update(self):
self.__value = self.fetch()
self.__values = self.fetch()
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -39,7 +39,11 @@ class Module(core.module.Module):
self.__sun = None
if not lat or not lon:
lat, lon = util.location.coordinates()
try:
lat, lon = util.location.coordinates()
except Exception:
pass
if lat and lon:
self.__sun = Sun(float(lat), float(lon))
@ -55,6 +59,10 @@ class Module(core.module.Module):
return "n/a"
def __calculate_times(self):
if not self.__sun:
self.__sunset = self.__sunrise = None
return
self.__isup = False
order_matters = True

View file

@ -8,11 +8,11 @@ adds the possibility to
* reboot
the system.
Per default a confirmation dialog is shown before the actual action is performed.
Parameters:
* system.confirm: show confirmation dialog before performing any action (default: true)
* system.confirm: show confirmation dialog before performing any action (default: true)
* system.reboot: specify a reboot command (defaults to 'reboot')
* system.shutdown: specify a shutdown command (defaults to 'shutdown -h now')
* system.logout: specify a logout command (defaults to 'i3exit logout')
@ -72,7 +72,12 @@ class Module(core.module.Module):
util.cli.execute(command)
def popup(self, widget):
menu = util.popup.menu()
popupcmd = self.parameter("popupcmd", "");
if (popupcmd != ""):
util.cli.execute(popupcmd)
return
menu = util.popup.menu(self.__config)
reboot_cmd = self.parameter("reboot", "reboot")
shutdown_cmd = self.parameter("shutdown", "shutdown -h now")
logout_cmd = self.parameter("logout", "i3exit logout")
@ -96,7 +101,7 @@ class Module(core.module.Module):
menu.add_menuitem(
"log out",
callback=functools.partial(
self.__on_command, "Log out", "Log out?", "i3exit logout"
self.__on_command, "Log out", "Log out?", logout_cmd
),
)
# don't ask for these

View file

@ -5,6 +5,7 @@ Requires the following library:
Parameters:
* taskwarrior.taskrc : path to the taskrc file (defaults to ~/.taskrc)
* taskwarrior.show_active: true/false(default) to show the active task ID and description when one is active, otherwise show the total number pending.
contributed by `chdorb <https://github.com/chdorb>`_ - many thanks!
@ -22,20 +23,45 @@ class Module(core.module.Module):
super().__init__(config, theme, core.widget.Widget(self.output))
self.__pending_tasks = "0"
self.__status = "stopped"
def update(self):
"""Return a string with the number of pending tasks from TaskWarrior."""
"""Return a string with the number of pending tasks from TaskWarrior
or the descripton of an active task.
if show.active is set in the config, show the description of the
current active task, otherwise the number of pending tasks will be displayed.
"""
try:
taskrc = self.parameter("taskrc", "~/.taskrc")
show_active = self.parameter("show_active", False)
w = TaskWarrior(config_filename=taskrc)
pending_tasks = w.filter_tasks({"status": "pending"})
self.__pending_tasks = str(len(pending_tasks))
active_tasks = (
w.filter_tasks({"start.any": "", "status": "pending"}) or None
)
if show_active and active_tasks:
# this is using the first element of the list, if there happen
# to be other active tasks, they won't be displayed.
reporting_tasks = (
f"{active_tasks[0]['id']} - {active_tasks[0]['description']}"
)
self.__status = "active"
else:
reporting_tasks = len(w.filter_tasks({"status": "pending"}))
self.__status = "stopped"
self.__pending_tasks = reporting_tasks
except:
self.__pending_tasks = "n/a"
self.__status = "stopped"
@core.decorators.scrollable
def output(self, _):
"""Format the task counter to output in bumblebee."""
return "{}".format(self.__pending_tasks)
def state(self, widget):
"""Return the set status to reflect state"""
return self.__status
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -9,6 +9,7 @@ Parameters:
* title.max : Maximum character length for title before truncating. Defaults to 64.
* title.placeholder : Placeholder text to be placed if title was truncated. Defaults to '...'.
* title.scroll : Boolean flag for scrolling title. Defaults to False
* title.short : Boolean flag for short title. Defaults to False
contributed by `UltimatePancake <https://github.com/UltimatePancake>`_ - many thanks!
@ -35,6 +36,7 @@ class Module(core.module.Module):
# parsing of parameters
self.__scroll = util.format.asbool(self.parameter("scroll", False))
self.__short = util.format.asbool(self.parameter("short", False))
self.__max = int(self.parameter("max", 64))
self.__placeholder = self.parameter("placeholder", "...")
self.__title = ""
@ -48,8 +50,9 @@ class Module(core.module.Module):
# create a connection with i3ipc
self.__i3 = i3ipc.Connection()
# event is called both on focus change and title change
# event is called both on focus change and title change, and on workspace change
self.__i3.on("window", lambda __p_i3, __p_e: self.__pollTitle())
self.__i3.on("workspace", lambda __p_i3, __p_e: self.__pollTitle())
# begin listening for events
threading.Thread(target=self.__i3.main).start()
@ -66,7 +69,9 @@ class Module(core.module.Module):
def __pollTitle(self):
"""Updating current title."""
try:
self.__full_title = self.__i3.get_tree().find_focused().name
focused = self.__i3.get_tree().find_focused().name
self.__full_title = focused.split(
"-")[-1].strip() if self.__short else focused
except:
self.__full_title = no_title
if self.__full_title is None:

View file

@ -0,0 +1,76 @@
# pylint: disable=C0111,R0903
"""
Displays the of Todoist tasks that are due:
* https://developer.todoist.com/rest/v2/#get-active-tasks
Uses `xdg-open` or `x-www-browser` to open web-pages.
Requires the following library:
* requests
Errors:
if the Todoist get active tasks query failed, the shown value is `n/a`
Parameters:
* todoist.token: Todoist api token, you can get it in https://todoist.com/app/settings/integrations/developer.
* todoist.filter: a filter statement defined by Todoist (https://todoist.com/help/articles/introduction-to-filters), eg: "!assigned to: others & (Overdue | due: today)"
"""
import shutil
import requests
import core.decorators
import core.input
import core.module
import core.widget
HOST_API = "https://api.todoist.com"
HOST_WEBSITE = "https://todoist.com/app/today"
TASKS_URL = f"{HOST_API}/rest/v2/tasks"
class Module(core.module.Module):
@core.decorators.every(minutes=5)
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.todoist))
self.__user_id = None
self.background = True
self.__label = ""
token = self.parameter("token", "")
self.__filter = self.parameter("filter", "")
self.__requests = requests.Session()
self.__requests.headers.update({"Authorization": f"Bearer {token}"})
cmd = "xdg-open"
if not shutil.which(cmd):
cmd = "x-www-browser"
core.input.register(
self,
button=core.input.LEFT_MOUSE,
cmd=f"{cmd} {HOST_WEBSITE}",
)
def todoist(self, _):
return self.__label
def update(self):
try:
self.__label = self.__get_pending_tasks()
except Exception:
self.__label = "n/a"
def __get_pending_tasks(self) -> str:
params = {"filter": self.__filter} if self.__filter else None
response = self.__requests.get(TASKS_URL, params=params)
data = response.json()
return str(len(data))

View file

@ -8,7 +8,7 @@ Parameters:
* traffic.showname: If set to False, hide network interface name (defaults to True)
* traffic.format: Format string for download/upload speeds.
Defaults to '{:.2f}'
* traffic.graphlen: Graph lenth in seconds. Positive even integer. Each
* traffic.graphlen: Graph length in seconds. Positive even integer. Each
char shows 2 seconds. If set, enables up/down traffic
graphs

View file

@ -0,0 +1,78 @@
# pylint: disable=C0111,R0903
"""
Module for ActivityWatch (https://activitywatch.net/)
Displays the amount of time the system was used actively.
Requirements:
* sqlite3 module for python
* ActivityWatch
Errors:
* when you get 'error: unable to open database file', modify the parameter 'database' to your ActivityWatch database file
-> often found by running 'locate aw-server/peewee-sqlite.v2.db'
Parameters:
* usage.database: path to your database file
* usage.format: Specify what gets printed to the bar
-> use 'HH', 'MM' or 'SS', they will get replaced by the number of hours, minutes and seconds, respectively
contributed by lasnikr (https://github.com/lasnikr)
"""
import sqlite3
import os
import core.module
import core.widget
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.output))
self.__usage = ""
def output(self, _):
return "{}".format(self.__usage)
def update(self):
database_loc = self.parameter(
"database", "~/.local/share/activitywatch/aw-server/peewee-sqlite.v2.db"
)
home = os.path.expanduser("~")
database = sqlite3.connect(database_loc.replace("~", home))
cursor = database.cursor()
cursor.execute("SELECT key, id FROM bucketmodel")
bucket_id = 1
for tuple in cursor.fetchall():
if "aw-watcher-afk" in tuple[1]:
bucket_id = tuple[0]
cursor.execute(
f"SELECT duration, datastr FROM eventmodel WHERE bucket_id = {bucket_id} "
+ 'AND strftime("%Y,%m,%d", timestamp) = strftime("%Y,%m,%d", "now")'
)
duration = 0
for tuple in cursor.fetchall():
if '{"status": "not-afk"}' in tuple[1]:
duration += tuple[0]
hours = "%.0f" % (duration // 3600)
minutes = "%.0f" % ((duration % 3600) // 60)
seconds = "%.0f" % (duration % 60)
formatting = self.parameter("format", "HHh, MMmin")
self.__usage = (
formatting.replace("HH", hours)
.replace("MM", minutes)
.replace("SS", seconds)
)
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -1,4 +1,5 @@
# pylint: disable=C0111,R0903
# -*- coding: utf-8 -*-
""" Displays the VPN profile that is currently in use.
@ -68,7 +69,7 @@ class Module(core.module.Module):
def vpn_status(self, widget):
if self.__connected_vpn_profile is None:
return "off"
return ""
return self.__connected_vpn_profile
def __on_vpndisconnect(self):
@ -93,7 +94,7 @@ class Module(core.module.Module):
self.__connected_vpn_profile = None
def popup(self, widget):
menu = util.popup.menu()
menu = util.popup.menu(self.__config)
if self.__connected_vpn_profile is not None:
menu.add_menuitem("Disconnect", callback=self.__on_vpndisconnect)

View file

@ -0,0 +1,94 @@
# pylint: disable=C0111,R0903
"""
Displays the WakaTime daily/weekly/monthly times:
* https://wakatime.com/developers#stats
Uses `xdg-open` or `x-www-browser` to open web-pages.
Requires the following library:
* requests
Errors:
if the Wakatime status query failed, the shown value is `n/a`
Parameters:
* wakatime.token: Wakatime secret api key, you can get it in https://wakatime.com/settings/account.
* wakatime.range: Range of the output, default is "Today". Can be one of Today, Yesterday, Last 7 Days, Last 7 Days from Yesterday, Last 14 Days, Last 30 Days, This Week, Last Week, This Month, or Last Month.
* wakatime.format: Format of the output, default is "digital"
Valid inputs are:
* "decimal" -> 1.37
* "digital" -> 1:22
* "seconds" -> 4931.29
* "text" -> 1 hr 22 mins
* "%H:%M:%S" -> 01:22:31 (or any other valid format)
"""
import base64
import shutil
import time
import requests
import core.decorators
import core.input
import core.module
import core.widget
HOST_API = "https://wakatime.com"
SUMMARIES_URL = f"{HOST_API}/api/v1/users/current/summaries"
UTF8 = "utf-8"
FORMAT_PARAMETERS = ["decimal", "digital", "seconds", "text"]
class Module(core.module.Module):
@core.decorators.every(minutes=5)
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.wakatime))
self.background = True
self.__label = ""
self.__output_format = self.parameter("format", "digital")
self.__range = self.parameter("range", "Today")
self.__requests = requests.Session()
token = self.__encode_to_base_64(self.parameter("token", ""))
self.__requests.headers.update({"Authorization": f"Basic {token}"})
cmd = "xdg-open"
if not shutil.which(cmd):
cmd = "x-www-browser"
core.input.register(
self,
button=core.input.LEFT_MOUSE,
cmd=f"{cmd} {HOST_API}/dashboard",
)
def wakatime(self, _):
return self.__label
def update(self):
try:
self.__label = self.__get_waka_time(self.__range)
except Exception:
self.__label = "n/a"
def __get_waka_time(self, since_date: str) -> str:
response = self.__requests.get(f"{SUMMARIES_URL}?range={since_date}")
data = response.json()
grand_total = data["cumulative_total"]
if self.__output_format in FORMAT_PARAMETERS:
return str(grand_total[self.__output_format])
else:
total_seconds = int(grand_total["seconds"])
return time.strftime(self.__output_format, time.gmtime(total_seconds))
@staticmethod
def __encode_to_base_64(s: str) -> str:
return base64.b64encode(s.encode(UTF8)).decode(UTF8)

View file

@ -5,6 +5,10 @@
Requires the following executable:
* watson
Parameters:
* watson.format: Output format, defaults to "{project} [{tags}]"
Supported fields are: {project}, {tags}, {relative_start}, {absolute_start}
contributed by `bendardenne <https://github.com/bendardenne>`_ - many thanks!
"""
@ -26,11 +30,11 @@ class Module(core.module.Module):
super().__init__(config, theme, core.widget.Widget(self.text))
self.__tracking = False
self.__project = ""
self.__info = {}
self.__format = self.parameter("format", "{project} [{tags}]")
core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.toggle)
def toggle(self, widget):
self.__project = "hit"
if self.__tracking:
util.cli.execute("watson stop")
else:
@ -39,20 +43,27 @@ class Module(core.module.Module):
def text(self, widget):
if self.__tracking:
return self.__project
return self.__format.format(**self.__info)
else:
return "Paused"
def update(self):
output = util.cli.execute("watson status")
if re.match(r"No project started", output):
m = re.search(r"Project ([^\[\]]+)(?: \[(.+)\])? started (.+) \((.+)\)", output)
if m:
self.__tracking = True
self.__info = {
"project": m.group(1),
"tags": m.group(2) or "",
"relative_start": m.group(3),
"absolute_start": m.group(4),
}
else:
self.__tracking = False
return
self.__tracking = True
m = re.search(r"Project (.+) started", output)
self.__project = m.group(1)
def state(self, widget):
return "on" if self.__tracking else "off"

View file

@ -13,7 +13,7 @@ Parameters:
* weather.unit: metric (default), kelvin, imperial
* weather.showcity: If set to true, show location information, otherwise hide it (defaults to true)
* weather.showminmax: If set to true, show the minimum and maximum temperature, otherwise hide it (defaults to false)
* weather.apikey: API key from http://api.openweathermap.org
* weather.apikey: API key from https://api.openweathermap.org
contributed by `TheEdgeOfRage <https://github.com/TheEdgeOfRage>`_ - many thanks!
@ -116,7 +116,7 @@ class Module(core.module.Module):
def update(self):
try:
weather_url = "http://api.openweathermap.org/data/2.5/weather?appid={}".format(
weather_url = "https://api.openweathermap.org/data/2.5/weather?appid={}".format(
self.__apikey
)
weather_url = "{}&units={}".format(weather_url, self.__unit)

View file

@ -0,0 +1,126 @@
# pylint: disable=C0111,R0903
# -*- coding: utf-8 -*-
"""Shows a widget for each connected screen and allows the user to loop through different orientations.
Parameters:
* wlrotation.display : Name of the output display that will be rotated
+ wlrotation.auto : Boolean value if the display should be rotatet automatic by default
Requires the following executable:
* swaymsg
"""
import core.module
import core.input
import util.cli
import iio
import json
from math import degrees, atan2, sqrt
from os import environ, path
possible_orientations = ["normal", "90", "180", "270"]
class iioValue:
def __init__(self, channel):
self.channel = channel
self.scale = self.read('scale')
self.offset = self.read('offset')
def read(self, attr):
return float(self.channel.attrs[attr].value)
def value(self):
return (self.read('raw') + self.offset) * self.scale
class iioAccelDevice:
def __init__(self):
self.ctx = iio.Context() # store ctx pointer
d = self.ctx.find_device('accel_3d')
self.x = iioValue(d.find_channel('accel_x'))
self.y = iioValue(d.find_channel('accel_y'))
self.z = iioValue(d.find_channel('accel_z'))
def orientation(self):
"""
returns tuple of `[success, value]` where `success` indicates, if an accurate value could be meassured and `value` the sway output api compatible value or `normal` if success is `False`
"""
x_deg, y_deg, z_deg = self._deg()
if abs(z_deg) < 70: # checks if device is angled too shallow
if x_deg >= 70: return True, "270"
if x_deg <= -70: return True, "90"
if abs(x_deg) <= 20:
if y_deg < 0: return True, "normal"
if y_deg > 0: return True, "180"
return False, "normal"
def _deg(self):
gravity = 9.81
x, y, z = self.x.value() / gravity, self.y.value() / gravity, self.z.value() / gravity
return degrees(atan2(x, sqrt(pow(y, 2) + pow(z, 2)))), degrees(atan2(y, sqrt(pow(z, 2) + pow(x, 2)))), degrees(atan2(z, sqrt(pow(x, 2) + pow(y, 2))))
class Display():
def __init__(self, name, widget, display_data, auto=False):
self.name = name
self.widget = widget
self.accelDevice = iioAccelDevice()
self._lock_auto_rotation(not auto)
self.widget.set("orientation", display_data['transform'])
core.input.register(widget, button=core.input.LEFT_MOUSE, cmd=self.rotate_90deg)
core.input.register(widget, button=core.input.RIGHT_MOUSE, cmd=self.toggle)
def rotate_90deg(self, event):
# compute new orientation based on current orientation
current = self.widget.get("orientation")
self._set_rotation(possible_orientations[(possible_orientations.index(current) + 1) % len(possible_orientations)])
# disable auto rotation
self._lock_auto_rotation(True)
def toggle(self, event):
self._lock_auto_rotation(not self.locked)
def auto_rotate(self):
# automagically rotate the display based on sensor values
# this is only called if rotation lock is disabled
success, value = self.accelDevice.orientation()
if success:
self._set_rotation(value)
def _set_rotation(self, new_orientation):
self.widget.set("orientation", new_orientation)
util.cli.execute("swaymsg 'output {} transform {}'".format(self.name, new_orientation))
def _lock_auto_rotation(self, locked):
self.locked = locked
self.widget.set("locked", self.locked)
class Module(core.module.Module):
@core.decorators.every(seconds=1)
def __init__(self, config, theme):
super().__init__(config, theme, [])
self.display = None
display_filter = self.parameter("display", None)
for display in json.loads(util.cli.execute("swaymsg -t get_outputs -r")):
name = display['name']
if display_filter == None or display_filter == name:
self.display = Display(name, self.add_widget(name=name), display, auto=util.format.asbool(self.parameter("auto", False)))
break # I assume that it makes only sense to rotate a single screen
def update(self):
if self.display == None:
return
if self.display.locked:
return
self.display.auto_rotate()
def state(self, widget):
state = []
state.append("locked" if widget.get("locked", True) else "auto")
return state
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -17,26 +17,33 @@ import core.input
class Module(core.module.Module):
def __init__(self, config, theme):
def __init__(self, config, theme, dtlibrary=None):
super().__init__(config, theme, core.widget.Widget(self.full_text))
core.input.register(self, button=core.input.LEFT_MOUSE, cmd="calendar")
l = locale.getdefaultlocale()
self.dtlibrary = dtlibrary or datetime
def set_locale(self):
l = self.default_locale()
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("."))
locale.setlocale(locale.LC_ALL, lcl.split("."))
except Exception as e:
locale.setlocale(locale.LC_TIME, ("en_US", "UTF-8"))
locale.setlocale(locale.LC_ALL, ("en_US", "UTF-8"))
def default_format(self):
return "%x %X"
def default_locale(self):
return locale.getdefaultlocale()
def full_text(self, widget):
self.set_locale()
enc = locale.getpreferredencoding()
fmt = self.parameter("format", self.default_format())
retval = datetime.datetime.now().strftime(fmt)
retval = self.dtlibrary.datetime.now().strftime(fmt)
if hasattr(retval, "decode"):
return retval.decode(enc)
return retval

View file

@ -4,7 +4,7 @@
Parameters:
* disk.warning: Warning threshold in % of disk space (defaults to 80%)
* disk.critical: Critical threshold in % of disk space (defaults ot 90%)
* disk.critical: Critical threshold in % of disk space (defaults to 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}%)')

View file

@ -1,39 +0,0 @@
# pylint: disable=C0111,R0903
"""Displays the current keyboard layout
Parameters:
* layout.device: The device ID of the keyboard (as reported by `xinput -list`), defaults to the core device
"""
import re
import core.widget
import core.module
import util.cli
from bumblebee_status.discover import utility
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config=config, theme=theme, widgets=core.widget.Widget(self.get_layout))
self._cmd = utility("get-kbd-layout")
keyboard = self.parameter("device", None)
if keyboard:
self._cmd += " {}".format(keyboard)
def get_layout(self, widget):
result = util.cli.execute(self._cmd, ignore_errors=True)
m = re.search("([a-zA-Z]+_)?([a-zA-Z]+)(\(([\w-]+)\))?", result)
if m:
layout = m.group(2)
variant = m.group(3)
return layout if not variant else "{} {}".format(layout, variant)
return "n/a"
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -0,0 +1 @@
layout-xkb.py

View file

@ -13,7 +13,9 @@ Parameters:
* nic.exclude: Comma-separated list of interface prefixes (supporting regular expressions) to exclude (defaults to 'lo,virbr,docker,vboxnet,veth,br,.*:avahi')
* nic.include: Comma-separated list of interfaces to include
* nic.states: Comma-separated list of states to show (prefix with '^' to invert - i.e. ^down -> show all devices that are not in state down)
* nic.format: Format string (defaults to '{intf} {state} {ip} {ssid}')
* nic.format: Format string (defaults to '{intf} {state} {ip} {ssid} {strength}')
* nic.strength_warning: Integer to set the threshold for warning state (defaults to 50)
* nic.strength_critical: Integer to set the threshold for critical state (defaults to 30)
"""
import re
@ -23,12 +25,13 @@ import subprocess
import core.module
import core.decorators
import core.input
import util.cli
import util.format
class Module(core.module.Module):
@core.decorators.every(seconds=10)
@core.decorators.every(seconds=5)
def __init__(self, config, theme):
widgets = []
super().__init__(config, theme, widgets)
@ -45,9 +48,19 @@ class Module(core.module.Module):
self._states["exclude"].append(state[1:])
else:
self._states["include"].append(state)
self._format = self.parameter("format", "{intf} {state} {ip} {ssid}")
self._format = self.parameter("format", "{intf} {state} {ip} {ssid} {strength}")
self._strength_threshold_critical = self.parameter("strength_critical", 30)
self._strength_threshold_warning = self.parameter("strength_warning", 50)
# Limits for the accepted dBm values of wifi strength
self.__strength_dbm_lower_bound = -110
self.__strength_dbm_upper_bound = -30
self.iw = shutil.which("iw")
self._update_widgets(widgets)
core.input.register(self, button=core.input.LEFT_MOUSE, cmd='wifi-menu')
core.input.register(self, button=core.input.RIGHT_MOUSE, cmd='nm-connection-editor')
def update(self):
self._update_widgets(self.widgets())
@ -64,15 +77,21 @@ class Module(core.module.Module):
iftype = "wireless" if self._iswlan(intf) else "wired"
iftype = "tunnel" if self._istunnel(intf) else iftype
# "strength" is none if interface type is not wlan
strength = widget.get("strength")
if self._iswlan(intf) and strength:
if strength < self._strength_threshold_critical:
states.append("critical")
elif strength < self._strength_threshold_warning:
states.append("warning")
states.append("{}-{}".format(iftype, widget.get("state")))
return states
def _iswlan(self, intf):
# wifi, wlan, wlp, seems to work for me
if intf.startswith("w"):
return True
return False
return intf.startswith("w") and not intf.startswith("wwan")
def _istunnel(self, intf):
return intf.startswith("tun") or intf.startswith("wg")
@ -116,6 +135,9 @@ class Module(core.module.Module):
):
continue
strength_dbm = self.get_strength_dbm(intf)
strength_percent = self.convert_strength_dbm_percent(strength_dbm)
widget = self.widget(intf)
if not widget:
widget = self.add_widget(name=intf)
@ -126,12 +148,14 @@ class Module(core.module.Module):
ip=", ".join(addr),
intf=intf,
state=state,
strength=str(strength_percent) + "%" if strength_percent else "",
ssid=self.get_ssid(intf),
).split()
)
)
widget.set("intf", intf)
widget.set("state", state)
widget.set("strength", strength_percent)
def get_ssid(self, intf):
if not self._iswlan(intf) or self._istunnel(intf) or not self.iw:
@ -145,5 +169,23 @@ class Module(core.module.Module):
return ""
def get_strength_dbm(self, intf):
if not self._iswlan(intf) or self._istunnel(intf) or not self.iw:
return None
with open("/proc/net/wireless", "r") as file:
for line in file:
if intf in line:
# Remove trailing . by slicing it off ;)
strength_dbm = line.split()[3][:-1]
return util.format.asint(strength_dbm,
minimum=self.__strength_dbm_lower_bound,
maximum=self.__strength_dbm_upper_bound)
return None
def convert_strength_dbm_percent(self, signal):
return int(100 * ((signal + 100) / 70.0)) if signal else None
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -2,6 +2,8 @@
"""Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol.
!!! This module will eventually be deprecated (since it has bad performance and high CPU load) and be replaced with "pulsectl", which is a much better drop-in replacement !!!
Aliases: pasink (use this to control output instead of input), pasource
Parameters:
@ -11,6 +13,20 @@ Parameters:
Note: If the left and right channels have different volumes, the limit might not be reached exactly.
* pulseaudio.showbars: 1 for showing volume bars, requires --markup=pango;
0 for not showing volume bars (default)
* pulseaudio.showdevicename: If set to 'true' (default is 'false'), the currently selected default device is shown.
Per default, the sink/source name returned by "pactl list sinks short" is used as display name.
As this name is usually not particularly nice (e.g "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo"),
its possible to map the name to more a user friendly name.
e.g to map "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" to the name "Headset", add the following
bumblebee-status config entry: pulseaudio.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=Headset
Furthermore its possible to specify individual (unicode) icons for all sinks/sources. e.g in order to use the icon 🎧 for the
"alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" sink, add the following bumblebee-status config entry:
pulseaudio.icon.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=🎧
* Per default a left mouse button click mutes/unmutes the device. In case you want to open a dropdown menu to change the current
default device add the following config entry to your bumblebee-status config: pulseaudio.left-click=select_default_device_popup
Requires the following executable:
* pulseaudio
@ -20,6 +36,7 @@ Requires the following executable:
import re
import logging
import functools
import core.module
import core.widget
@ -29,10 +46,14 @@ import util.cli
import util.graph
import util.format
try:
import util.popup
except ImportError as e:
logging.warning("Couldn't import util.popup: %s. Popups won't work!", e)
class Module(core.module.Module):
def __init__(self, config, theme, channel):
super().__init__(config, theme, core.widget.Widget(self.volume))
super().__init__(config, theme, core.widget.Widget(self.display))
if util.format.asbool(self.parameter("autostart", False)):
util.cli.execute("pulseaudio --start", ignore_errors=True)
@ -48,7 +69,11 @@ class Module(core.module.Module):
self._mute = False
self._failed = False
self._channel = channel
self.__selected_default_device = None
self._showbars = util.format.asbool(self.parameter("showbars", 0))
self.__show_device_name = util.format.asbool(
self.parameter("showdevicename", False)
)
self._patterns = [
{"expr": "Name:", "callback": (lambda line: False)},
@ -123,9 +148,12 @@ class Module(core.module.Module):
m = re.search(r"mono:.*\s*\/\s*(\d+)%", line)
if m:
self._mono = m.group(1)
self._left = 0
self._right = 0
else:
m = re.search(r"left:.*\s*\/\s*(\d+)%.*right:.*\s*\/\s*(\d+)%", line)
if m:
self._mono = 0
self._left = m.group(1)
self._right = m.group(2)
@ -138,19 +166,19 @@ class Module(core.module.Module):
logging.error("no pulseaudio device found")
return "n/a"
def volume(self, widget):
def display(self, widget):
if self._failed == True:
return "n/a"
vol = None
if int(self._mono) > 0:
vol = "{}%".format(self._mono)
if self._showbars:
vol = "{} {}".format(vol, util.graph.hbar(float(self._mono)))
return vol
elif self._left == self._right:
vol = "{}%".format(self._left)
if self._showbars:
vol = "{} {}".format(vol, util.graph.hbar(float(self._left)))
return vol
else:
vol = "{}%/{}%".format(self._left, self._right)
if self._showbars:
@ -159,19 +187,31 @@ class Module(core.module.Module):
util.graph.hbar(float(self._left)),
util.graph.hbar(float(self._right)),
)
return vol
output = vol
if self.__show_device_name:
friendly_name = self.parameter(
self.__selected_default_device, self.__selected_default_device
)
icon = self.parameter("icon." + self.__selected_default_device, "")
output = (
icon + " " + friendly_name + " | " + vol
if icon != ""
else friendly_name + " | " + vol
)
return output
def update(self):
try:
self._failed = False
channel = "sinks" if self._channel == "sink" else "sources"
device = self._default_device()
self.__selected_default_device = self._default_device()
result = util.cli.execute("pactl list {}".format(channel))
found = False
for line in result.split("\n"):
if "Name: {}".format(device) in line:
if "Name: {}".format(self.__selected_default_device) in line:
found = True
continue
if found is False:
@ -189,11 +229,32 @@ class Module(core.module.Module):
else:
raise e
def __on_sink_selected(self, sink_name):
util.cli.execute("pactl set-default-{} {}".format(self._channel, sink_name))
def select_default_device_popup(self, widget):
channel = "sinks" if self._channel == "sink" else "sources"
result = util.cli.execute("pactl list {} short".format(channel))
menu = util.popup.menu(self.__config)
lines = result.splitlines()
for line in lines:
info = line.split("\t")
try:
friendly_name = self.parameter(info[1], info[1])
menu.add_menuitem(
friendly_name,
callback=functools.partial(self.__on_sink_selected, info[1]),
)
except:
logging.exception("Couldn't parse {}".format(channel))
pass
menu.show(widget)
def state(self, widget):
if self._mute:
return ["warning", "muted"]
if int(self._left) > int(100):
return ["critical", "unmuted"]
return ["unmuted"]

View file

@ -0,0 +1,207 @@
# pylint: disable=C0111,R0903
"""Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol.
**Please prefer this module over the "pulseaudio" module, which will eventually be deprecated
Aliases: pulseout (for outputs, such as headsets, speakers), pulsein (for microphones)
NOTE: Do **not** use this module directly, but rather use either pulseout or pulsein!
NOTE2: For the parameter names below, please also use pulseout or pulsein, instead of pulsectl
Parameters:
* pulsectl.autostart: If set to 'true' (default is 'false'), automatically starts the pulsectl daemon if it is not running
* pulsectl.percent_change: How much to change volume by when scrolling on the module (default is 2%)
* pulsectl.limit: Upper limit for setting the volume (default is 0%, which means 'no limit')
* pulsectl.popup-filter: Comma-separated list of device strings (if the device name contains it) to exclude
from the default device popup menu (e.g. Monitor for sources)
* pulsectl.showbars: 'true' for showing volume bars, requires --markup=pango;
'false' for not showing volume bars (default)
* pulsectl.showdevicename: If set to 'true' (default is 'false'), the currently selected default device is shown.
Per default, the sink/source name returned by "pactl list sinks short" is used as display name.
As this name is usually not particularly nice (e.g "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo"),
its possible to map the name to more a user friendly name.
e.g to map "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" to the name "Headset", add the following
bumblebee-status config entry: pulsectl.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=Headset
Furthermore its possible to specify individual (unicode) icons for all sinks/sources. e.g in order to use the icon 🎧 for the
"alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" sink, add the following bumblebee-status config entry:
pulsectl.icon.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=🎧
* Per default a left mouse button click mutes/unmutes the device. In case you want to open a dropdown menu to change the current
default device add the following config entry to your bumblebee-status config: pulsectl.left-click=select_default_device_popup
Requires the following Python module:
* pulsectl
"""
import pulsectl
import logging
import functools
import core.module
import core.widget
import core.input
import core.event
import util.cli
import util.graph
import util.format
try:
import util.popup
except ImportError as e:
logging.warning("Couldn't import util.popup: %s. Popups won't work!", e)
class Module(core.module.Module):
def __init__(self, config, theme, type):
super().__init__(config, theme, core.widget.Widget(self.display))
self.background = True
self.__type = type
self.__volume = 0
self.__devicename = "n/a"
self.__muted = False
self.__showbars = util.format.asbool(self.parameter("showbars", False))
self.__show_device_name = util.format.asbool(
self.parameter("showdevicename", False)
)
self.__change = util.format.asint(
self.parameter("percent_change", "2%").strip("%"), 0, 100
)
self.__limit = util.format.asint(self.parameter("limit", "0%").strip("%"), 0)
popup_filter_param = self.parameter("popup-filter", [])
if popup_filter_param == '':
self.__popup_filter = []
else:
self.__popup_filter = util.format.aslist(popup_filter_param)
events = [
{
"type": "mute",
"action": self.toggle_mute,
"button": core.input.LEFT_MOUSE
},
{
"type": "volume",
"action": self.increase_volume,
"button": core.input.WHEEL_UP,
},
{
"type": "volume",
"action": self.decrease_volume,
"button": core.input.WHEEL_DOWN,
},
]
for event in events:
core.input.register(self, button=event["button"], cmd=event["action"])
if util.format.asbool(self.parameter("autostart", False)):
util.cli.execute("pulseaudio --start", ignore_errors=True)
self.process(None)
def display(self, _):
res = f"{int(self.__volume*100)}%"
if self.__showbars:
res = f"{res} {util.graph.hbar(self.__volume*100)}"
if self.__show_device_name:
friendly_name = self.parameter(self.__devicename, self.__devicename)
icon = self.parameter("icon." + self.__devicename, "")
res = (
icon + " " + friendly_name + " | " + res
if icon != ""
else friendly_name + " | " + res
)
return res
def toggle_mute(self, _):
with pulsectl.Pulse(self.id + "vol") as pulse:
dev = self.get_device(pulse)
if not dev:
return
pulse.mute(dev, not self.__muted)
def change_volume(self, amount):
with pulsectl.Pulse(self.id + "vol") as pulse:
dev = self.get_device(pulse)
if not dev:
return
vol = dev.volume
vol.value_flat += amount
if self.__limit > 0 and vol.value_flat > self.__limit/100:
vol.value_flat = self.__limit/100
pulse.volume_set(dev, vol)
def increase_volume(self, _):
self.change_volume(self.__change/100.0)
def decrease_volume(self, _):
self.change_volume(-self.__change/100.0)
def get_device(self, pulse):
devs = pulse.sink_list() if self.__type == "sink" else pulse.source_list()
default = pulse.server_info().default_sink_name if self.__type == "sink" else pulse.server_info().default_source_name
for dev in devs:
if dev.name == default:
return dev
if len(devs) == 0:
return None
return devs[0] # fallback
def process(self, _):
with pulsectl.Pulse(self.id + "proc") as pulse:
dev = self.get_device(pulse)
if not dev:
self.__volume = 0
self.__devicename = "n/a"
else:
self.__volume = dev.volume.value_flat
self.__muted = dev.mute
self.__devicename = dev.name
core.event.trigger("update", [self.id], redraw_only=True)
core.event.trigger("draw")
def update(self):
with pulsectl.Pulse(self.id) as pulse:
pulse.event_mask_set(self.__type)
pulse.event_callback_set(self.process)
pulse.event_listen()
def select_default_device_popup(self, widget):
with pulsectl.Pulse(self.id) as pulse:
if self.__type == "sink":
devs = pulse.sink_list()
else:
devs = pulse.source_list()
devs = filter(lambda dev: not any(filter in dev.description for filter in self.__popup_filter), devs)
menu = util.popup.menu(self.__config)
for dev in devs:
menu.add_menuitem(
dev.description,
callback=functools.partial(self.__on_default_changed, dev),
)
menu.show(widget)
def __on_default_changed(self, dev):
with pulsectl.Pulse(self.id) as pulse:
pulse.default_set(dev)
def state(self, _):
if self.__muted:
return ["warning", "muted"]
if self.__volume >= .5:
return ["unmuted", "unmuted-high"]
if self.__volume >= .1:
return ["unmuted", "unmuted-mid"]
return ["unmuted", "unmuted-low"]
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -0,0 +1,9 @@
from .pulsectl import Module
class Module(Module):
def __init__(self, config, theme):
super().__init__(config, theme, "source")
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -0,0 +1,9 @@
from .pulsectl import Module
class Module(Module):
def __init__(self, config, theme):
super().__init__(config, theme, "sink")
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -12,6 +12,7 @@ Parameters:
* redshift.lat : latitude if location is set to 'manual'
* redshift.lon : longitude if location is set to 'manual'
* redshift.show_transition: information about the transitions (x% day) defaults to True
* redshift.adjust: set this to 'true' (defaults to false) to let bumblebee-status adjust color temperature, instead of just showing the current settings
"""
import re
@ -38,7 +39,13 @@ def get_redshift_value(module):
if location == "manual" and (lat is None or lon is None):
location = "geoclue2"
command = ["redshift", "-p"]
command = ["redshift"]
if util.format.asbool(module.parameter("adjust", "false")) == True:
command.extend(["-o", "-v"])
else:
command.append("-p")
if location == "manual":
command.extend(["-l", "{}:{}".format(lat, lon)])
if location == "geoclue2":
@ -54,7 +61,7 @@ def get_redshift_value(module):
for line in res.split("\n"):
line = line.lower()
if "temperature" in line:
widget.set("temp", line.split(" ")[2])
widget.set("temp", line.split(" ")[2].upper())
if "period" in line:
state = line.split(" ")[1]
if "day" in state:

View file

@ -0,0 +1,53 @@
# pylint: disable=C0111,R0903
"""Displays two widgets that can be used to scroll the whole status bar
Parameters:
* scroll.width: Width (in number of widgets) to display
"""
import core.module
import core.widget
import core.input
import core.event
import util.format
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, [])
self.__offset = 0
self.__widgetcount = 0
w = self.add_widget(full_text = "<")
core.input.register(w, button=core.input.LEFT_MOUSE, cmd=self.scroll_left)
w = self.add_widget(full_text = ">")
core.input.register(w, button=core.input.LEFT_MOUSE, cmd=self.scroll_right)
self.__width = util.format.asint(self.parameter("width"))
config.set("output.width", self.__width)
core.event.register("output.done", self.update_done)
def scroll_left(self, _):
if self.__offset > 0:
core.event.trigger("output.scroll-left")
def scroll_right(self, _):
if self.__offset + self.__width < self.__widgetcount:
core.event.trigger("output.scroll-right")
def update_done(self, offset, widgetcount):
self.__offset = offset
self.__widgetcount = widgetcount
def scroll(self):
return False
def state(self, widget):
if widget.id == self.widgets()[0].id:
if self.__offset == 0:
return ["warning"]
elif self.__offset + self.__width >= self.__widgetcount:
return ["warning"]
return []
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -11,7 +11,7 @@ Parameters:
* sensors2.showother: Enable or display 'other' sensor readings (default: false)
* sensors2.showname: Enable or disable show of sensor name (default: false)
* sensors2.chip_include: Comma-separated list of chip to include (defaults to '' will include all by default, example: 'coretemp,bat')
* sensors2.chip_exclude:Comma separated list of chip to exclude (defaults to '' will exlude none by default)
* sensors2.chip_exclude:Comma separated list of chip to exclude (defaults to '' will exclude none by default)
* sensors2.field_include: Comma separated list of chip to include (defaults to '' will include all by default, example: 'temp,fan')
* sensors2.field_exclude: Comma separated list of chip to exclude (defaults to '' will exclude none by default)
* sensors2.chip_field_exclude: Comma separated list of chip field to exclude (defaults to '' will exclude none by default, example: 'coretemp-isa-0000.temp1,coretemp-isa-0000.fan1')

View file

@ -9,7 +9,7 @@ Parameters:
import core.module
import core.widget
import core.decorators
import core.input
class Module(core.module.Module):
@core.decorators.every(minutes=60)
@ -20,5 +20,8 @@ class Module(core.module.Module):
def text(self, _):
return self.__text
def update_text(self, event):
self.__text = core.input.button_name(event["button"])
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -52,7 +52,7 @@ def build_menu(parent, current_directory, callback):
)
else:
submenu = util.popup.menu(parent, leave=False)
submenu = util.popup.menu(self.__config, parent, leave=False)
build_menu(
submenu, os.path.join(current_directory, entry.name), callback
)
@ -73,7 +73,7 @@ class Module(core.module.Module):
core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.popup)
def popup(self, widget):
menu = util.popup.menu(leave=False)
menu = util.popup.menu(self.__config, leave=False)
build_menu(menu, self.__path, self.__callback)
menu.show(widget, offset_x=self.__offx, offset_y=self.__offy)

View file

@ -52,7 +52,19 @@ def execute(
raise RuntimeError("{} not found".format(cmd))
if wait:
out, _ = proc.communicate()
timeout = 60
try:
out, _ = proc.communicate(timeout=timeout)
except subprocess.TimeoutExpired as e:
logging.warning(
f"""
Communication with process pid={proc.pid} hangs for more
than {timeout} seconds.
If this is not expected, the process is stale, or
you might have run in stdout / stderr deadlock.
"""
)
out, _ = proc.communicate()
if proc.returncode != 0:
err = "{} exited with code {}".format(cmd, proc.returncode)
logging.warning(err)

View file

@ -3,8 +3,10 @@ service and caches it for 12h (retries are done every
30m in case of problems)
Right now, it uses (in order of preference):
- http://free.ipwhois.io/
- http://ipapi.co/
- http://free.ipwhois.io/ - 10k free requests/month
- http://ipapi.co/ - 30k free requests/month
- http://ip-api.com/ - ~2m free requests/month
"""
@ -16,21 +18,36 @@ __document = None
__data = {}
__next = 0
__sources = [
{
"url": "http://ipapi.co/json",
"mapping": {
"latitude": "latitude",
"longitude": "longitude",
"country_name": "country",
"ip": "public_ip",
},
},
{
"url": "http://free.ipwhois.io/json/",
"mapping": {
"latitude": "latitude",
"longitude": "longitude",
"country": "country",
"country": "country_name",
"country_code": "country_code",
"city": "city_name",
"ip": "public_ip",
},
},
{
"url": "http://ip-api.com/json",
"mapping": {
"lat": "latitude",
"lon": "longitude",
"country": "country_name",
"countryCode": "country_code",
"city": "city_name",
"query": "public_ip",
},
},
{
"url": "http://ipapi.co/json",
"mapping": {
"latitude": "latitude",
"longitude": "longitude",
"country_name": "country_name",
"country_code": "country_code",
"city": "city_name",
"ip": "public_ip",
},
}
@ -59,17 +76,22 @@ def __load():
__next = time.time() + 60 * 30 # error - try again every 30m
def __get(name, default=None):
def __get(name):
global __data
if not __data or __expired():
__load()
return __data.get(name, default)
if name in __data:
return __data[name]
else:
return None
def reset():
"""Resets the location library, ensuring that a new query will be started
"""
"""Resets the location library, ensuring that a new query will be started"""
global __next
global __data
__data = None
__next = 0
@ -88,7 +110,25 @@ def country():
:return: country name
:rtype: string
"""
return __get("country")
return __get("country_name")
def country_code():
"""Returns the current country code
:return: country code
:rtype: string
"""
return __get("country_code")
def city_name():
"""Returns the current city name
:return: city name
:rtype: string
"""
return __get("city_name")
def public_ip():
@ -100,4 +140,20 @@ def public_ip():
return __get("public_ip")
def location_info():
"""Returns the current location information
:return: public IP, country name, country code, city name & coordinates
:rtype: dictionary
"""
return {
"public_ip": __get("public_ip"),
"country": __get("country_name"),
"country_code": __get("country_code"),
"city_name": __get("city_name"),
"latitude": __get("latitude"),
"longitude": __get("longitude"),
}
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -3,6 +3,7 @@
import logging
import tkinter as tk
import tkinter.font as tkFont
import functools
@ -10,11 +11,12 @@ import functools
class menu(object):
"""Draws a hierarchical popup menu
:param config: Global config singleton, passed on from modules
:param parent: If given, this menu is a leave of the "parent" menu
:param leave: If set to True, close this menu when mouse leaves the area (defaults to True)
"""
def __init__(self, parent=None, leave=True):
def __init__(self, config, parent=None, leave=True):
self.running = True
self.parent = parent
@ -23,6 +25,7 @@ class menu(object):
self._root.withdraw()
self._menu = tk.Menu(self._root, tearoff=0)
self._menu.bind("<FocusOut>", self.__on_focus_out)
self._font_size = tkFont.Font(size=config.popup_font_size())
if leave:
self._menu.bind("<Leave>", self.__on_focus_out)
@ -68,7 +71,7 @@ class menu(object):
"""
def add_cascade(self, menuitem, submenu):
self._menu.add_cascade(label=menuitem, menu=submenu.menu())
self._menu.add_cascade(label=menuitem, menu=submenu.menu(), font=self._font_size)
"""Adds an item to the current menu
@ -78,7 +81,7 @@ class menu(object):
def add_menuitem(self, menuitem, callback):
self._menu.add_command(
label=menuitem, command=functools.partial(self.__on_click, callback)
label=menuitem, command=functools.partial(self.__on_click, callback), font=self._font_size,
)
"""Adds a separator to the menu in the current location"""

28
create-pkgbuild.py Normal file
View file

@ -0,0 +1,28 @@
#!/bin/bash
import sys
import json
import hashlib
import requests
rv = requests.request(
"GET",
"https://api.github.com/repos/tobi-wan-kenobi/bumblebee-status/releases/latest",
)
if rv.status_code != 200:
sys.exit(1)
release = json.loads(rv.text)
tar = requests.get(f"https://github.com/tobi-wan-kenobi/bumblebee-status/archive/{release['name']}.tar.gz")
checksum = hashlib.sha512(tar.content).hexdigest()
template = ""
with open("./PKGBUILD.template") as f:
template = f.read()
template = template.replace("<PKGVERSION>", release["name"].lstrip("v"))
template = template.replace("<SHA512SUM>", checksum)
print(template)

View file

@ -29,9 +29,9 @@ didnt have background color support for the status bar.
Some of the icons dont render correctly
----------------------------------------
Please check that you have |Font Awesome| installed (version 4).
Please check that you have `Font Awesome`_ installed (version 4).
.. note:: The |Font Awesome| is required for all themes that
.. note:: The `Font Awesome`_ is required for all themes that
contain icons (because that is the font that includes these icons).
Please refer to your distributions package management on how to install
them, or get them from their website directly. Also, please note that
@ -52,4 +52,15 @@ Please check that you have |Font Awesome| installed (version 4).
# Other
# see https://github.com/gabrielelana/awesome-terminal-fonts
.. |Font Awesome| image:: https://fontawesome.com/
You might also need to add it to the `font` directive in your i3 configuration, for example:
.. code-block::
bar {
font pango:FontAwesome, Fira mono 10
status_command bumblebee-status -m title pasink pasource cpu memory battery datetime --iconset awesome-fonts
}
If you are unsure about how the font is named, you can use the ``pango-list`` command line tool to look at the fonts installed on your computer. Also note how you can specify multiple fonts, separated by commas, in the above example.
.. _Font Awesome: https://fontawesome.com/

View file

@ -49,6 +49,8 @@ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# a list of builtin themes.
#
html_theme = "sphinx_rtd_theme"
html_logo = "logo.png"
html_favicon = "favicon.ico"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,

View file

@ -250,5 +250,5 @@ module using ``self.set()`` or via the CLI using the ``--parameter`` flag:
- ``scrolling.width``: Integer, defaults to 30, determines the minimum width of the widgets, if ``makewide`` is specified
- ``scrolling.makewide``: Boolean, defaults to true, determines whether the widgets should be expanded to their minwidth
``scrolling.bounce``: Boolean, defaults to true, determines whether the content should change directions when a scroll is completed, or just marquee through
- ``scrolling.bounce``: Boolean, defaults to true, determines whether the content should change directions when a scroll is completed, or just marquee through

BIN
docs/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -9,6 +9,8 @@ Welcome to bumblebee-status's documentation!
bumblebee-status is a modular, theme-able status line generator for the
`i3 window manager <https://i3wm.org/>`__.
Logo courtesy of [kellya](https://github.com/kellya) - thank you!
Focus is on:
- ease of use, sane defaults (no mandatory configuration file)

View file

@ -44,6 +44,14 @@ like this:
-t <theme>
}
Line continuations (breaking a single line into multiple lines) is allowed in
the i3 configuration, but please ensure that all lines except the final one need to have a trailing
"\".
This is explained in detail here:
[i3 user guide: line continuation](https://i3wm.org/docs/userguide.html#line_continuation)
You can retrieve a list of modules (and their parameters) and themes by
entering:

BIN
docs/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

@ -60,7 +60,7 @@ Shows free diskspace, total diskspace and the percentage of free disk space.
Parameters:
* disk.warning: Warning threshold in % of disk space (defaults to 80%)
* disk.critical: Critical threshold in % of disk space (defaults ot 90%)
* disk.critical: Critical threshold in % of disk space (defaults to 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}%)')
@ -93,6 +93,22 @@ Shows when a key is pressed
Parameters:
* keys.keys: Comma-separated list of keys to monitor (defaults to "")
layout
~~~~~~
Displays the current keyboard layout using libX11
Requires the following library:
* libX11.so.6
and python module:
* xkbgroup
Parameters:
* layout-xkb.showname: Boolean that indicate whether the full name should be displayed. Defaults to false (only the symbol will be displayed)
* layout-xkb.show_variant: Boolean that indecates whether the variant name should be displayed. Defaults to true.
.. image:: ../screenshots/layout.png
layout-xkb
~~~~~~~~~~
@ -171,7 +187,9 @@ Parameters:
* nic.exclude: Comma-separated list of interface prefixes (supporting regular expressions) to exclude (defaults to 'lo,virbr,docker,vboxnet,veth,br,.*:avahi')
* nic.include: Comma-separated list of interfaces to include
* nic.states: Comma-separated list of states to show (prefix with '^' to invert - i.e. ^down -> show all devices that are not in state down)
* nic.format: Format string (defaults to '{intf} {state} {ip} {ssid}')
* nic.format: Format string (defaults to '{intf} {state} {ip} {ssid} {strength}')
* nic.strength_warning: Integer to set the threshold for warning state (defaults to 50)
* nic.strength_critical: Integer to set the threshold for critical state (defaults to 30)
.. image:: ../screenshots/nic.png
@ -197,6 +215,8 @@ pulseaudio
Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol.
!!! This module will eventually be deprecated (since it has bad performance and high CPU load) and be replaced with "pulsectl", which is a much better drop-in replacement !!!
Aliases: pasink (use this to control output instead of input), pasource
Parameters:
@ -206,6 +226,20 @@ Parameters:
Note: If the left and right channels have different volumes, the limit might not be reached exactly.
* pulseaudio.showbars: 1 for showing volume bars, requires --markup=pango;
0 for not showing volume bars (default)
* pulseaudio.showdevicename: If set to 'true' (default is 'false'), the currently selected default device is shown.
Per default, the sink/source name returned by "pactl list sinks short" is used as display name.
As this name is usually not particularly nice (e.g "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo"),
its possible to map the name to more a user friendly name.
e.g to map "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" to the name "Headset", add the following
bumblebee-status config entry: pulseaudio.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=Headset
Furthermore its possible to specify individual (unicode) icons for all sinks/sources. e.g in order to use the icon 🎧 for the
"alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" sink, add the following bumblebee-status config entry:
pulseaudio.icon.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=🎧
* Per default a left mouse button click mutes/unmutes the device. In case you want to open a dropdown menu to change the current
default device add the following config entry to your bumblebee-status config: pulseaudio.left-click=select_default_device_popup
Requires the following executable:
* pulseaudio
@ -214,6 +248,44 @@ Requires the following executable:
.. image:: ../screenshots/pulseaudio.png
pulsectl
~~~~~~~~
Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol.
**Please prefer this module over the "pulseaudio" module, which will eventually be deprecated
Aliases: pulseout (for outputs, such as headsets, speakers), pulsein (for microphones)
NOTE: Do **not** use this module directly, but rather use either pulseout or pulsein!
NOTE2: For the parameter names below, please also use pulseout or pulsein, instead of pulsectl
Parameters:
* pulsectl.autostart: If set to 'true' (default is 'false'), automatically starts the pulsectl daemon if it is not running
* pulsectl.percent_change: How much to change volume by when scrolling on the module (default is 2%)
* pulsectl.limit: Upper limit for setting the volume (default is 0%, which means 'no limit')
* pulsectl.popup-filter: Comma-separated list of device strings (if the device name contains it) to exclude
from the default device popup menu (e.g. Monitor for sources)
* pulsectl.showbars: 'true' for showing volume bars, requires --markup=pango;
'false' for not showing volume bars (default)
* pulsectl.showdevicename: If set to 'true' (default is 'false'), the currently selected default device is shown.
Per default, the sink/source name returned by "pactl list sinks short" is used as display name.
As this name is usually not particularly nice (e.g "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo"),
its possible to map the name to more a user friendly name.
e.g to map "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" to the name "Headset", add the following
bumblebee-status config entry: pulsectl.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=Headset
Furthermore its possible to specify individual (unicode) icons for all sinks/sources. e.g in order to use the icon 🎧 for the
"alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" sink, add the following bumblebee-status config entry:
pulsectl.icon.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=🎧
* Per default a left mouse button click mutes/unmutes the device. In case you want to open a dropdown menu to change the current
default device add the following config entry to your bumblebee-status config: pulsectl.left-click=select_default_device_popup
Requires the following Python module:
* pulsectl
redshift
~~~~~~~~
@ -229,9 +301,18 @@ Parameters:
* redshift.lat : latitude if location is set to 'manual'
* redshift.lon : longitude if location is set to 'manual'
* redshift.show_transition: information about the transitions (x% day) defaults to True
* redshift.adjust: set this to 'true' (defaults to false) to let bumblebee-status adjust color temperature, instead of just showing the current settings
.. image:: ../screenshots/redshift.png
scroll
~~~~~~
Displays two widgets that can be used to scroll the whole status bar
Parameters:
* scroll.width: Width (in number of widgets) to display
sensors2
~~~~~~~~
@ -246,7 +327,7 @@ Parameters:
* sensors2.showother: Enable or display 'other' sensor readings (default: false)
* sensors2.showname: Enable or disable show of sensor name (default: false)
* sensors2.chip_include: Comma-separated list of chip to include (defaults to '' will include all by default, example: 'coretemp,bat')
* sensors2.chip_exclude:Comma separated list of chip to exclude (defaults to '' will exlude none by default)
* sensors2.chip_exclude:Comma separated list of chip to exclude (defaults to '' will exclude none by default)
* sensors2.field_include: Comma separated list of chip to include (defaults to '' will include all by default, example: 'temp,fan')
* sensors2.field_exclude: Comma separated list of chip to exclude (defaults to '' will exclude none by default)
* sensors2.chip_field_exclude: Comma separated list of chip field to exclude (defaults to '' will exclude none by default, example: 'coretemp-isa-0000.temp1,coretemp-isa-0000.fan1')
@ -345,6 +426,7 @@ Requires the following executable:
* amixer
Parameters:
* amixer.card: Sound Card to use (default is 0)
* amixer.device: Device to use (default is Master,0)
* amixer.percent_change: How much to change volume by when scrolling on the module (default is 4%)
@ -352,6 +434,8 @@ contributed by `zetxx <https://github.com/zetxx>`_ - many thanks!
input handling contributed by `ardadem <https://github.com/ardadem>`_ - many thanks!
multiple audio cards contributed by `hugoeustaquio <https://github.com/hugoeustaquio>`_ - many thanks!
.. image:: ../screenshots/amixer.png
apt
@ -375,6 +459,9 @@ saved screen layout as well as toggle on/off individual connected displays.
Parameters:
* No configuration parameters
Requires the following python modules:
* tkinter
Requires the following executable:
* arandr
* xrandr
@ -391,6 +478,8 @@ Requires the following executable:
contributed by `lucassouto <https://github.com/lucassouto>`_ - many thanks!
.. image:: ../screenshots/arch-update.png
arch_update
~~~~~~~~~~~
@ -401,6 +490,18 @@ Requires the following executable:
contributed by `lucassouto <https://github.com/lucassouto>`_ - many thanks!
aur-update
~~~~~~~~~~
Check updates for AUR.
Requires the following executable:
* yay (https://github.com/Jguer/yay)
contributed by `ishaanbhimwal <https://github.com/ishaanbhimwal>`_ - many thanks!
.. image:: ../screenshots/aur-update.png
battery
~~~~~~~
@ -472,6 +573,26 @@ Parameters:
contributed by `martindoublem <https://github.com/martindoublem>`_ - many thanks!
blugon
~~~~~~
Displays temperature of blugon and Controls it.
Use wheel up and down to change temperature, middle click to toggle and right click to reset temperature.
Default Values:
* Minimum temperature: 1000 (red)
* Maximum temperature: 20000 (blue)
* Default temperature: 6600
Requires the following executable:
* blugon
Parameters:
* blugon.step: The amount of increase/decrease on scroll (default: 200)
contributed by `DTan13 <https://github.com/DTan13>`
brightness
~~~~~~~~~~
@ -560,15 +681,58 @@ Parameters:
* cpu2.fanspeed
* cpu2.colored: 1 for colored per core load graph, 0 for mono (default)
* cpu2.temp_pattern: pattern to look for in the output of 'sensors -u';
required if cpu2.temp widged is used
required if cpu2.temp widget is used
* cpu2.fan_pattern: pattern to look for in the output of 'sensors -u';
required if cpu2.fanspeed widged is used
required if cpu2.fanspeed widget is used
Note: if you are getting 'n/a' for CPU temperature / fan speed, then you're
lacking the aforementioned pattern settings or they have wrong values.
contributed by `somospocos <https://github.com/somospocos>`_ - many thanks!
cpu3
~~~~
Multiwidget CPU module
Can display any combination of:
* max CPU frequency
* total CPU load in percents (integer value)
* per-core CPU load as graph - either mono or colored
* CPU temperature (in Celsius degrees)
* CPU fan speed
Requirements:
* the psutil Python module for the first three items from the list above
* sensors executable for the rest
Parameters:
* cpu3.layout: Space-separated list of widgets to add.
Possible widgets are:
* cpu3.maxfreq
* cpu3.cpuload
* cpu3.coresload
* cpu3.temp
* cpu3.fanspeed
* cpu3.colored: 1 for colored per core load graph, 0 for mono (default)
* cpu3.temp_json: json path to look for in the output of 'sensors -j';
required if cpu3.temp widget is used
* cpu3.fan_json: json path to look for in the output of 'sensors -j';
required if cpu3.fanspeed widget is used
Note: if you are getting 'n/a' for CPU temperature / fan speed, then you're
lacking the aforementioned json path settings or they have wrong values.
Example json paths:
* `cpu3.temp_json="coretemp-isa-0000.Package id 0.temp1_input"`
* `cpu3.fan_json="thinkpad-isa-0000.fan1.fan1_input"`
contributed by `SuperQ <https://github.com/SuperQ>`
based on cpu2 by `<somospocos <https://github.com/somospocos>`
currency
~~~~~~~~
@ -720,6 +884,9 @@ be running. Scripts will be executed when dunst gets unpaused.
Requires:
* dunst v1.5.0+
Parameters:
* dunstctl.disabled(Boolean): dunst state on start
contributed by `cristianmiranda <https://github.com/cristianmiranda>`_ - many thanks!
contributed by `joachimmathes <https://github.com/joachimmathes>`_ - many thanks!
@ -736,10 +903,37 @@ Requires the following executable:
Parameters:
* emerge_status.format: Format string (defaults to '{current}/{total} {action} {category}/{pkg}')
This code is based on `emerge_status module from p3status <https://github.com/ultrabug/py3status/blob/master/py3status/modules/emerge_status.py>`_ original created by `AnwariasEu <https://github.com/AnwariasEu>`_.
This code is based on emerge_status module from p3status [1] original created by AnwariasEu.
[1] https://github.com/ultrabug/py3status/blob/master/py3status/modules/emerge_status.py
.. image:: ../screenshots/emerge_status.png
gcalendar
~~~~~~~~~
Displays first upcoming event in google calendar.
Events that are set as 'all-day' will not be shown.
Requires credentials.json from a google api application where the google calendar api is installed.
On first time run the browser will open and google will ask for permission for this app to access
the google calendar and then save a .gcalendar_token.json file to the credentials_path directory
which stores this permission.
A refresh is done every 15 minutes.
Parameters:
* gcalendar.time_format: Format time output. Defaults to "%H:%M".
* gcalendar.date_format: Format date output. Defaults to "%d.%m.%y".
* gcalendar.credentials_path: Path to credentials.json. Defaults to "~/".
* gcalendar.locale: locale to use rather than the system default.
Requires these pip packages:
* google-api-python-client >= 1.8.0
* google-auth-httplib2
* google-auth-oauthlib
getcrypto
~~~~~~~~~
@ -782,6 +976,29 @@ contributed by:
.. image:: ../screenshots/github.png
gitlab
~~~~~~
Displays the GitLab todo count:
* https://docs.gitlab.com/ee/user/todos.html
* https://docs.gitlab.com/ee/api/todos.html
Uses `xdg-open` or `x-www-browser` to open web-pages.
Requires the following library:
* requests
Errors:
if the GitLab todo query failed, the shown value is `n/a`
Parameters:
* gitlab.token: GitLab personal access token, the token needs to have the "read_api" scope.
* gitlab.host: Host of the GitLab instance, default is "gitlab.com".
* gitlab.actions: Comma separated actions to be parsed (e.g.: gitlab.actions=assigned,approval_required)
.. image:: ../screenshots/gitlab.png
gpmdp
~~~~~
@ -846,18 +1063,6 @@ contributed by `pierre87 <https://github.com/pierre87>`_ - many thanks!
.. image:: ../screenshots/kernel.png
layout
~~~~~~
Displays and changes the current keyboard layout
Requires the following executable:
* setxkbmap
contributed by `Pseudonick47 <https://github.com/Pseudonick47>`_ - many thanks!
.. image:: ../screenshots/layout.png
layout-xkbswitch
~~~~~~~~~~~~~~~~
@ -893,7 +1098,7 @@ messagereceiver
Displays the message that's received via unix socket.
Parameteres:
Parameters:
* messagereceiver : Unix socket address (e.g: /tmp/bumblebee_messagereceiver.sock)
Example:
@ -978,12 +1183,22 @@ Parameters:
if {file} = '/foo/bar.baz', then {file2} = 'bar'
* mpd.host: MPD host to connect to. (mpc behaviour by default)
* mpd.port: MPD port to connect to. (mpc behaviour by default)
* mpd.layout: Space-separated list of widgets to add. Possible widgets are the buttons/toggles mpd.prev, mpd.next, mpd.shuffle and mpd.repeat, and the main display with play/pause function mpd.main.
contributed by `alrayyes <https://github.com/alrayyes>`_ - many thanks!
.. image:: ../screenshots/mpd.png
network
~~~~~~~
A module to show the currently active network connection (ethernet or wifi) and connection strength if the connection is wireless.
Requires the Python netifaces package and iw installed on Linux.
A simpler take on nic and network_traffic. No extra config necessary!
network_traffic
~~~~~~~~~~~~~~~
@ -1016,12 +1231,16 @@ 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}
Available values are: {name} {temp} {mem_used} {mem_total} {fanspeed} {clock_gpu} {clock_mem} {gpu_usage_pct} {mem_usage_pct} {mem_io_pct}
Requires nvidia-smi
contributed by `RileyRedpath <https://github.com/RileyRedpath>`_ - many thanks!
Note: mem_io_pct is (from `man nvidia-smi`):
> Percent of time over the past sample period during which global (device)
> memory was being read or written.
octoprint
~~~~~~~~~
@ -1039,13 +1258,21 @@ Parameters:
contributed by `bbernhard <https://github.com/bbernhard>`_ - many thanks!
optman
~~~~~~
Displays currently active gpu by optimus-manager
Requires the following packages:
* optimus-manager
pacman
~~~~~~
Displays update information per repository for pacman.
Parameters:
* pacman.sum: If you prefere displaying updates with a single digit (defaults to 'False')
* pacman.sum: If you prefer displaying updates with a single digit (defaults to 'False')
Requires the following executables:
* fakeroot
@ -1055,6 +1282,31 @@ contributed by `Pseudonick47 <https://github.com/Pseudonick47>`_ - many thanks!
.. image:: ../screenshots/pacman.png
pamixer
~~~~~~~
get volume level or control it
Requires the following executable:
* pamixer
Parameters:
* pamixer.percent_change: How much to change volume by when scrolling on the module (default is 4%)
heavily based on amixer module
persian_date
~~~~~~~~~~~~
Displays the current date and time in Persian(Jalali) Calendar.
Requires the following python packages:
* jdatetime
Parameters:
* datetime.format: strftime()-compatible formatting string. default: "%A %d %B" e.g., "جمعه ۱۳ اسفند"
* datetime.locale: locale to use. default: "fa_IR"
pihole
~~~~~~
@ -1062,10 +1314,30 @@ Displays the pi-hole status (up/down) together with the number of ads that were
Parameters:
* pihole.address : pi-hole address (e.q: http://192.168.1.3)
* pihole.pwhash : pi-hole webinterface password hash (can be obtained from the /etc/pihole/SetupVars.conf file)
* pihole.apitoken : pi-hole API token (can be obtained in the pi-hole webinterface (Settings -> API)
OR (deprecated!)
* pihole.pwhash : pi-hole webinterface password hash (can be obtained from the /etc/pihole/SetupVars.conf file)
contributed by `bbernhard <https://github.com/bbernhard>`_ - many thanks!
pipewire
~~~~~~~~
get volume level or control it
Requires the following executable:
* wpctl
Parameters:
* wpctl.percent_change: How much to change volume by when scrolling on the module (default is 4%)
heavily based on amixer module
playerctl
~~~~~~~~~
@ -1080,7 +1352,8 @@ Parameters:
* playerctl.layout: Comma-separated list to change order of widgets (defaults to song, previous, pause, next)
Widget names are: playerctl.song, playerctl.prev, playerctl.pause, playerctl.next
* playerctl.args: The arguments added to playerctl.
You can check 'playerctl --help' or `its readme <https://github.com/altdesktop/playerctl#using-the-cli>`_. For example, it could be '-p vlc,%any'.
You can check 'playerctl --help' or `its README <https://github.com/altdesktop/playerctl#using-the-cli>`_. For example, it could be '-p vlc,%any'.
* playerctl.hide: Hide the widgets when no players are found. Defaults to "false".
Parameters are inspired by the `spotify` module, many thanks to its developers!
@ -1104,7 +1377,7 @@ Parameters:
Example: 'notify-send 'Time up!''. If you want to chain multiple commands,
please use an external wrapper script and invoke that. The module itself does
not support command chaining (see https://github.com/tobi-wan-kenobi/bumblebee-status/issues/532
for a detailled explanation)
for a detailed explanation)
contributed by `martindoublem <https://github.com/martindoublem>`_, inspired by `karthink <https://github.com/karthink>`_ - many thanks!
@ -1168,7 +1441,29 @@ contributed by `remi-dupre <https://github.com/remi-dupre>`_ - many thanks!
publicip
~~~~~~~~
Displays public IP address
Displays information about the public IP address associated with the default route:
* Public IP address
* Country Name
* Country Code
* City Name
* Geographic Coordinates
Left mouse click on the widget forces immediate update.
Any change to the default route will cause the widget to update.
Requirements:
* netifaces
Parameters:
* publicip.format: Format string (defaults to {ip} ({country_code}))
* Available format strings - ip, country_name, country_code, city_name, coordinates
Examples:
* bumblebee-status -m publicip -p publicip.format="{ip} ({country_code})"
* bumblebee-status -m publicip -p publicip.format="{ip} which is in {city_name}"
* bumblebee-status -m publicip -p publicip.format="Your packets are right here: {coordinates}"
contributed by `tfwiii <https://github.com/tfwiii>` - many thanks!
rofication
~~~~~~~~~~
@ -1181,6 +1476,9 @@ module will have normal highlighting if there are zero notifications,
"warning" highlighting if there are nonzero notifications,
"critical" highlighting if there are any critical notifications
Parameters:
* rofication.regolith: Switch to regolith fork of rofication, see <https://github.com/regolith-linux/regolith-rofication>.
rotation
~~~~~~~~
@ -1210,9 +1508,7 @@ sensors
Displays sensor temperature
Parameters:
* sensors.use_sensors: whether to use the 'sensors' command.
If set to 'false', the sysfs-interface at '/sys/class/thermal' is used.
If not set, 'sensors' will be used if available.
* sensors.use_sensors: whether to use the sensors command
* sensors.path: path to temperature file (default /sys/class/thermal/thermal_zone0/temp).
* sensors.json: if set to 'true', interpret sensors.path as JSON 'path' in the output
of 'sensors -j' (i.e. <key1>/<key2>/.../<value>), for example, path could
@ -1288,10 +1584,20 @@ Requires the following executables:
* smartctl
Parameters:
* smartstatus.display: how to display (defaults to 'combined', other choices: 'combined_singles', 'seperate' or 'singles')
* smartstatus.display: how to display (defaults to 'combined', other choices: 'combined_singles', 'separate' or 'singles')
* smartstatus.drives: in the case of singles which drives to display, separated comma list value, multiple accepted (defaults to 'sda', example:'sda,sdc')
* smartstatus.show_names: boolean in the form of "True" or "False" to show the name of the drives in the form of sda, sbd, combined or none at all.
solaar
~~~~~~
Shows status and load percentage of logitech's unifying device
Requires the following executable:
* solaar (from community)
contributed by `cambid <https://github.com/cambid>`_ - many thanks!
spaceapi
~~~~~~~~
@ -1308,7 +1614,7 @@ Parameters:
Format Strings:
* Format strings are indicated by double %%
* They represent a leaf in the JSON tree, layers seperated by '.'
* They represent a leaf in the JSON tree, layers separated by '.'
* Boolean values can be overwritten by appending '%true%false'
in the format string
* Example: to reference 'open' in '{'state':{'open': true}}'
@ -1351,7 +1657,9 @@ Display a stock quote from finance.yahoo.com
Parameters:
* stock.symbols : Comma-separated list of symbols to fetch
* stock.change : Should we fetch change in stock value (defaults to True)
* stock.apikey : API key created on https://alphavantage.co
* stock.url : URL to use, defaults to "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={symbol}&apikey={apikey}"
* stock.fields : Fields from the response to show, defaults to "01. symbol,05. price,10. change percent"
contributed by `msoulier <https://github.com/msoulier>`_ - many thanks!
@ -1386,11 +1694,11 @@ adds the possibility to
* reboot
the system.
Per default a confirmation dialog is shown before the actual action is performed.
Parameters:
* system.confirm: show confirmation dialog before performing any action (default: true)
* system.confirm: show confirmation dialog before performing any action (default: true)
* system.reboot: specify a reboot command (defaults to 'reboot')
* system.shutdown: specify a shutdown command (defaults to 'shutdown -h now')
* system.logout: specify a logout command (defaults to 'i3exit logout')
@ -1398,7 +1706,7 @@ Parameters:
* 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')
Requirements:
tkinter (python3-tk package on debian based systems either you can install it as python package)
@ -1414,6 +1722,7 @@ Requires the following library:
Parameters:
* taskwarrior.taskrc : path to the taskrc file (defaults to ~/.taskrc)
* taskwarrior.show_active: true/false(default) to show the active task ID and description when one is active, otherwise show the total number pending.
contributed by `chdorb <https://github.com/chdorb>`_ - many thanks!
@ -1459,6 +1768,7 @@ Parameters:
* title.max : Maximum character length for title before truncating. Defaults to 64.
* title.placeholder : Placeholder text to be placed if title was truncated. Defaults to '...'.
* title.scroll : Boolean flag for scrolling title. Defaults to False
* title.short : Boolean flag for short title. Defaults to False
contributed by `UltimatePancake <https://github.com/UltimatePancake>`_ - many thanks!
@ -1487,6 +1797,27 @@ Parameters:
* todo_org.remaining: False by default. When true, will output the number of remaining todos instead of the number completed (i.e. 1/4 means 1 of 4 todos remaining, rather than 1 of 4 todos completed)
Based on the todo module by `codingo <https://github.com/codingo>`
todoist
~~~~~~~
Displays the nº of Todoist tasks that are due:
* https://developer.todoist.com/rest/v2/#get-active-tasks
Uses `xdg-open` or `x-www-browser` to open web-pages.
Requires the following library:
* requests
Errors:
if the Todoist get active tasks query failed, the shown value is `n/a`
Parameters:
* todoist.token: Todoist api token, you can get it in https://todoist.com/app/settings/integrations/developer.
* todoist.filter: a filter statement defined by Todoist (https://todoist.com/help/articles/introduction-to-filters), eg: "!assigned to: others & (Overdue | due: today)"
.. image:: ../screenshots/todoist.png
traffic
~~~~~~~
@ -1498,7 +1829,7 @@ Parameters:
* traffic.showname: If set to False, hide network interface name (defaults to True)
* traffic.format: Format string for download/upload speeds.
Defaults to '{:.2f}'
* traffic.graphlen: Graph lenth in seconds. Positive even integer. Each
* traffic.graphlen: Graph length in seconds. Positive even integer. Each
char shows 2 seconds. If set, enables up/down traffic
graphs
@ -1525,6 +1856,27 @@ contributed by `ccoors <https://github.com/ccoors>`_ - many thanks!
.. image:: ../screenshots/uptime.png
usage
~~~~~
Module for ActivityWatch (https://activitywatch.net/)
Displays the amount of time the system was used actively.
Requirements:
* sqlite3 module for python
* ActivityWatch
Errors:
* when you get 'error: unable to open database file', modify the parameter 'database' to your ActivityWatch database file
-> often found by running 'locate aw-server/peewee-sqlite.v2.db'
Parameters:
* usage.database: path to your database file
* usage.format: Specify what gets printed to the bar
-> use 'HH', 'MM' or 'SS', they will get replaced by the number of hours, minutes and seconds, respectively
contributed by lasnikr (https://github.com/lasnikr)
vpn
~~~
@ -1544,6 +1896,34 @@ Displays the VPN profile that is currently in use.
contributed by `bbernhard <https://github.com/bbernhard>`_ - many thanks!
wakatime
~~~~~~~~
Displays the WakaTime daily/weekly/monthly times:
* https://wakatime.com/developers#stats
Uses `xdg-open` or `x-www-browser` to open web-pages.
Requires the following library:
* requests
Errors:
if the Wakatime status query failed, the shown value is `n/a`
Parameters:
* wakatime.token: Wakatime secret api key, you can get it in https://wakatime.com/settings/account.
* wakatime.range: Range of the output, default is "Today". Can be one of “Today”, “Yesterday”, “Last 7 Days”, “Last 7 Days from Yesterday”, “Last 14 Days”, “Last 30 Days”, “This Week”, “Last Week”, “This Month”, or “Last Month”.
* wakatime.format: Format of the output, default is "digital"
Valid inputs are:
* "decimal" -> 1.37
* "digital" -> 1:22
* "seconds" -> 4931.29
* "text" -> 1 hr 22 mins
* "%H:%M:%S" -> 01:22:31 (or any other valid format)
.. image:: ../screenshots/wakatime.png
watson
~~~~~~
@ -1552,6 +1932,10 @@ Displays the status of watson (time-tracking tool)
Requires the following executable:
* watson
Parameters:
* watson.format: Output format, defaults to "{project} [{tags}]"
Supported fields are: {project}, {tags}, {relative_start}, {absolute_start}
contributed by `bendardenne <https://github.com/bendardenne>`_ - many thanks!
weather
@ -1569,7 +1953,7 @@ Parameters:
* weather.unit: metric (default), kelvin, imperial
* weather.showcity: If set to true, show location information, otherwise hide it (defaults to true)
* weather.showminmax: If set to true, show the minimum and maximum temperature, otherwise hide it (defaults to false)
* weather.apikey: API key from http://api.openweathermap.org
* weather.apikey: API key from https://api.openweathermap.org
contributed by `TheEdgeOfRage <https://github.com/TheEdgeOfRage>`_ - many thanks!

View file

@ -97,3 +97,8 @@ List of available themes
:alt: Default
Default (nothing or -t default)
.. figure:: ../screenshots/themes/moonlight-powerline.png
:alt: Moonlight Powerline
Moonlight Powerline (-t moonlight-powerline) (contributed by `Ramon Saraiva <https://github.com/ramonsaraiva>`__)

29
man/bumblebee-ctl.1 Normal file
View file

@ -0,0 +1,29 @@
.TH BUMBLEBEE-CTL "1" "June 2022" "bumblebee-status"
.SH NAME
bumblebee-ctl \- Send commands to bumblebee-status
.SH SYNOPSIS
.B bumblebee-ctl
[\fB\-h\fR] [\fB\-b\fR \fIbutton\fR] [\fB\-i\fR \fIID\fR] \fB-m\fR \fImodule\fR
.SH DESCRIPTION
.B bumblebee-ctl
can be used to send commands to bumblebee-status.
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-b\fR, \fB\-\-button\fR <button>
Button to emulate. Buttons can be any of left\-mouse, right\-mouse,
middle\-mouse, wheel\-up, wheel\-down, or update.
.TP
\fB\-i\fR, \fB\-\-id\fR <ID>
ID of widget to trigger
.TP
\fB\-m\fR, \fB\-\-module\fR <module>
name of module to trigger
.SH COPYRIGHT
Copyright \(co 2016\-2022 tobi\-wan\-kenobi.
.PP
.br
This is free software; see the source for copying conditions.
There is NO WARRANTY, to the extent permitted by law.

58
man/bumblebee-status.1 Normal file
View file

@ -0,0 +1,58 @@
.TH BUMBLEBEE-STATUS "1" "June 2022" "bumblebee-status"
.SH NAME
bumblebee-status \- status line generator for the i3 window manager
.SH SYNOPSIS
.B bumblebee-status
[\fIoptions\fR]
.SH DESCRIPTION
.B bumblebee-status
is a modular, themeable status line generator for the i3 window manager.
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-c\fR, \fB\-\-config\-file\fR <config\-file>
specify a configuration file to use
.TP
\fB\-m\fR, \fB\-\-modules\fR MODULES <modules>
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
<module>:<alias> to provide an alias in case you want to load the same
module multiple times, but specify different parameters.
.TP
\fB\-p\fR, \fB\-\-parameters\fR PARAMETERS <parameters>
provide configuration parameters in the form of <module>.<key>=<value>
.TP
\fB\-t\fR, \fB\-\-theme\fR <theme>
specify the theme to use for drawing modules
.TP
\fB\-i\fR, \fB\-\-iconset\fR <iconset>
specify the name of an iconset to use (overrides theme default)
.TP
\fB\-a\fR, \fB\-\-autohide\fR AUTOHIDE <modules>
specify a list of modules to hide when not in warning/error state
.TP
\fB\-e\fR, \fB\-\-errorhide\fR ERRORHIDE <modules>
specify a list of modules that are hidden when in state error
.TP
\fB\-d\fR, \fB\-\-debug\fR
add debug fields to i3 output
.TP
\fB\-f\fR, \fB\-\-logfile\fR <logfile>
destination for the debug log file, if \fB\-d\fR or \fB\-\-debug\fR
is specified; defaults to stderr
.TP
\fB\-r\fR, \fB\-\-right\-to\-left\fR
draw widgets from right to left, rather than left to right
(which is the default)
.TP
\fB\-l\fR, \fB\-\-list\fR <\fImodules\fR,\fIthemes\fR,\fImodules\-rst\fR>
display a list of available themes or available modules,
along with their parameters
.SH COPYRIGHT
Copyright \(co 2016\-2022 tobi\-wan\-kenobi.
.PP
.br
This is free software; see the source for copying conditions.
There is NO WARRANTY, to the extent permitted by law.

View file

@ -0,0 +1 @@
psutil

View file

@ -0,0 +1 @@
requests

View file

@ -0,0 +1,2 @@
dbus-python
power-profiles-daemon

BIN
screenshots/arch-update.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
screenshots/aur-update.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
screenshots/gitlab.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
screenshots/todoist.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

BIN
screenshots/wakatime.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -56,6 +56,7 @@ setup(
("share/bumblebee-status/themes", glob.glob("themes/*.json")),
("share/bumblebee-status/themes/icons", glob.glob("themes/icons/*.json")),
("share/bumblebee-status/utility", glob.glob("bin/*")),
("share/man/man1", glob.glob("man/*.1")),
],
packages=find_packages(exclude=["tests", "tests.*"])
)

Some files were not shown because too many files have changed in this diff Show more