Compare commits

...

637 commits
v2.0.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
tobi-wan-kenobi
80493d3bea
Merge pull request #848 from logan-connolly/feature/add_rose_pine_theme
feat(theme): add rose pine theme
2022-01-25 18:29:01 +01:00
Logan Connolly
c40a174463 feat(theme): add rose pine theme 2022-01-25 17:50:25 +01:00
Dhananjay Tanpure
8867f4f188
added module for blugon 2022-01-20 21:06:17 +00:00
tobi-wan-kenobi
08b5386140 [util/popup] fix endless loop on "close on leave"
When closing a popup window when the mouse leave the area (default
behaviour, unfortunately), the main "show()" got stuck in an infinite
loop.

Fix that by setting running to False when exiting.

fixes #844
2022-01-14 13:39:04 +01:00
tobi-wan-kenobi
8bde6378d4 [modules/arandr] handle case of "no layouts exist
To ensure that arandr works also if no layouts are available, add some
(very simplistic) exception handling.

see #844
2022-01-14 13:29:29 +01:00
tobi-wan-kenobi
1089792bc6
Merge pull request #845 from donfranio/apt-view-update-on-click
add updating view on apt-cache on click
2022-01-13 11:37:36 +01:00
Frank Scherrer
30dd0f2efb add updating view on apt-cache on click 2022-01-13 11:05:01 +01:00
tobi-wan-kenobi
c019c4f382
Merge pull request #843 from fedeliallalinea/readme-gentoo-ebuild
Added ebuild link on README.md
2022-01-06 10:30:55 +01:00
Marco Genasci
5d1059ba63 Added ebuild link on README.md
Signed-off-by: Marco Genasci <fedeliallalinea@gmail.com>
2022-01-06 10:13:40 +01:00
tobi-wan-kenobi
a89bc096ef
Merge pull request #842 from fedeliallalinea/emerge-status-module
New emerge status module
2022-01-06 09:03:52 +01:00
Marco Genasci
f4ca5eaa3b Added documentation and screenshot for emerge_status module
Signed-off-by: Marco Genasci <fedeliallalinea@gmail.com>
2022-01-06 08:07:49 +01:00
Marco Genasci
8a50eb6f81 New module emerge_status
Display information about the currently running emerge process.

Signed-off-by: Marco Genasci <fedeliallalinea@gmail.com>
2022-01-06 08:06:57 +01:00
tobi-wan-kenobi
441d4f0275
Merge pull request #841 from fedeliallalinea/fixes-some-qa
Fixes some QA
2022-01-05 13:12:47 +01:00
Marco Genasci
51c3805f7f Change deprecated dash-separated with underscore in setup.cfg
Signed-off-by: Marco Genasci <fedeliallalinea@gmail.com>
2022-01-05 09:34:38 +01:00
Marco Genasci
d430f90434 Excluding the tests folder from the installation
Signed-off-by: Marco Genasci <fedeliallalinea@gmail.com>
2022-01-05 09:34:27 +01:00
tobi-wan-kenobi
4b7a6a18d5
Merge pull request #840 from LokiLuciferase/feature/silence-spotify-exceptions
Silence exceptions in the spotify module
2021-12-28 11:33:43 +01:00
Lukas Lüftinger
8991bba90e Silence exceptions in the spotify module which may write large amounts of logs to ~/.xsession-errors 2021-12-28 00:34:10 +01:00
tobi-wan-kenobi
e590a3cf3f
Merge pull request #838 from ouuan/fix-playerctl-log
[contrib/playerctl]: don't log when no player is found
2021-12-17 11:18:27 +01:00
Yufan You
973dd6117e
[contrib/playerctl]: don't log when no player is found
`playerctl status` returns 1 when no player is found, which caused
contrib/playerctl to log many times when there's no player.
2021-12-17 18:07:45 +08:00
tobi-wan-kenobi
6ce761695a [doc] remove "master" branch 2021-11-07 13:50:44 +01:00
tobi-wan-kenobi
a84b4f9a65 [docs] fix docs build
pin docutils to < 0.18 as per https://github.com/readthedocs/readthedocs.org/issues/8616#issuecomment-952034858
2021-11-07 13:46:52 +01:00
tobi-wan-kenobi
26e4bdd7eb [modules/progress] improved autohide functionality
Simplify the previous autohide functionality by adding a flag that lets
a module (e.g. progress) indicate that the current state should be
"revealed" (not auto-hidden).

This vastly simplifies the implementation.

see #835
2021-11-06 08:21:08 +01:00
tobi-wan-kenobi
5ad211f862 Revert "[contrib/progress] allow hiding of inactive state"
This reverts commit cbd989309d.
2021-11-06 08:17:18 +01:00
tobi-wan-kenobi
6a3e4761bf Revert "[core/output] fix logic error when using "autohide""
This reverts commit 74ecbb6ca8.
2021-11-06 08:17:11 +01:00
tobi-wan-kenobi
74ecbb6ca8 [core/output] fix logic error when using "autohide"
- when state is critical or warning -> *show* the module
- when state is mayhide -> *hide* the module

see #835
2021-11-05 19:11:45 +01:00
tobi-wan-kenobi
cbd989309d [contrib/progress] allow hiding of inactive state
Add a new "hide-able" state "mayhide" that can be utilized by modules
without warning state. This state indicates that the module *may* be
hidden by autohide, if the user configures it like this.

see #835
2021-11-05 14:00:34 +01:00
tobi-wan-kenobi
f0ab3ef03a [core/config] fix autohide parameter from configuration file
Need to parse the config parameter "autohide" into a list and actually
check whether the current module is in the list.

see #835
2021-11-05 13:57:05 +01:00
tobi-wan-kenobi
c7f58ae2a4 [doc] fix typo (autohid vs. autohide)
see #835
2021-11-05 08:43:33 +01:00
tobi-wan-kenobi
4bbe25d195
Merge pull request #830 from kushagraa-j/nord-theme-colorful
added new theme nord-colorful
2021-11-03 20:36:14 +01:00
tobi-wan-kenobi
d94d12897d
Merge pull request #832 from cambid/solaar-module
new module solaar.py for logitech's unifying devices
2021-10-26 22:16:19 +02:00
Jan Fader
2cb72fcc30
add tests for solaar.py 2021-10-26 21:52:15 +02:00
Jan Fader
dced20bf89
refactor code to decrease cognitive complexity in update 2021-10-26 19:52:42 +02:00
Jan Fader
fdc9b78967
add new solaar.py for logitech's unifying devices 2021-10-26 19:27:02 +02:00
Kushagra Jain
d8216a5e2c added new theme 2021-10-26 13:54:35 +05:30
tobi-wan-kenobi
0fe89e13a0
Merge pull request #829 from izn/fix/codeclimate-coverage-report
fix: generate coverage xml before cc report
2021-10-23 16:10:50 +02:00
Thaynã Moretti
7542a47dbc fix: generate coverage xml before cc report 2021-10-23 11:02:46 -03:00
tobi-wan-kenobi
d89c6b1bc1
Merge pull request #828 from izn/docs/fix-travis-ci-url
docs: update travisci url
2021-10-23 15:30:29 +02:00
Thaynã Moretti
99bb5e99aa Fix Travis CI build URL 2021-10-23 10:24:02 -03:00
tobi-wan-kenobi
876774ce40 [travis] removing python 3.4 and 3.5
Since 3.4 and 3.5 are both not supported anymore, do not do unit tests
for them.
2021-10-23 13:43:08 +02:00
tobi-wan-kenobi
e6bb787e01
Merge pull request #826 from alexcoder04/main
[modules/sensors] use util.format.asbool()
2021-10-22 18:26:21 +02:00
alexcoder04
6b31cdb698 [modules/sensors] use util.format.asbool() + auto-check only if no path is specified 2021-10-21 14:43:15 +02:00
tobi-wan-kenobi
9106ec9c8f
Merge pull request #825 from alexcoder04/main
[modules/sensors] auto-determine the correct thermal zone
2021-10-21 14:07:42 +02:00
alexcoder04
0dc6a95ac2 [modules/sensors] auto-determine the correct thermal zone 2021-10-21 13:45:40 +02:00
tobi-wan-kenobi
618a22c122
Merge pull request #824 from izn/chore/add-libvirtvms-tests
Create libvirtvms tests
2021-10-12 17:06:04 +02:00
Thaynã Moretti
4007517e45 chore: create libvirtvms tests 2021-10-12 11:34:20 -03:00
tobi-wan-kenobi
d67232d8cf
Merge pull request #823 from izn/chore/add-dunst-tests
Create dunst tests
2021-10-12 03:22:32 +02:00
Thaynã Moretti
1580951474 chore: create missing dunstctl tests 2021-10-11 19:17:55 -03:00
Thaynã Moretti
40de07ba2e chore: create dunst tests 2021-10-11 19:16:37 -03:00
Thaynã Moretti
b5395fe764 chore: public toggle method 2021-10-11 19:16:14 -03:00
tobi-wan-kenobi
c96d119b0e [core/config] add autohide to configuration file
make it possible to configure a list of automatically hidden modules in
the configuration file (+ add documentation for it).

fixes #822
2021-10-07 15:39:29 +02:00
tobi-wan-kenobi
ed5a4e61e4 [modules/bluetooth] Add more error checking
Do not kill the bar when the dbus-send command fails.

see #818
2021-09-10 12:45:11 +02:00
tobi-wan-kenobi
9c463fc2b7
Merge pull request #813 from tomsaleeba/tomsaleeba/nvidiagpu-additions-2
fix: correct mem usage to be mem *io* usage
2021-08-16 07:36:36 +02:00
Tom Saleeba
d4339f6e43 fix: correct mem usage to be mem *io* usage 2021-08-15 22:27:44 -06:00
tobi-wan-kenobi
27194a92c8
Merge pull request #812 from soykan/patch-1
Small bug fix on todo module.
2021-08-14 19:28:08 +02:00
Soykan Ertürk
05f76c0d9a
Update todo.py 2021-08-14 20:16:45 +03:00
Soykan Ertürk
5a1addec7f
Fixing a small bug on todo module
todo counts new lines (blank lines) as todo and increments todo count. After my fix todo doesn't counts blank lines.
2021-08-14 20:14:54 +03:00
tobi-wan-kenobi
439b140916
Merge pull request #811 from soykan/patch-2
system module requirement
2021-08-12 22:16:42 +02:00
Soykan Ertürk
8be9f1a05c
system module requirement
added tkinter as requirement
2021-08-12 23:15:09 +03:00
tobi-wan-kenobi
b00c0ae47c
Merge pull request #810 from soykan/patch-1
Improving docs
2021-08-12 22:01:24 +02:00
Soykan Ertürk
473d2fbd14
Improving docs
I added tkinter as dependency in requirements.
2021-08-12 22:54:34 +03:00
tobi-wan-kenobi
5c65a8fef4
Merge pull request #807 from soykan/patch-2
Dependency for powerline themes
2021-08-10 19:28:22 +02:00
Soykan Ertürk
f98053371e
Dependency for powerline themes 2021-08-10 20:19:30 +03:00
tobi-wan-kenobi
f6dd17b383
Merge pull request #806 from tomsaleeba/tomsaleeba/nvidiagpu-additions
feat: add GPU usage % and GPU memory usage % to nvidiagpu
2021-07-25 06:54:52 +02:00
Tom Saleeba
98c92bb78f feat: add GPU usage % and GPU memory usage % to nvidiagpu 2021-07-24 15:18:04 -06:00
tobi-wan-kenobi
4d422ffc84
Merge pull request #801 from nepoz/NetworkTestFix
Removed dependency on pytest-socket for the Network module's unit tests.
2021-07-09 08:28:16 +02:00
nepoz
5a2dfc226b Removed dependency on pytest-socket for the Network module's unit tests. 2021-07-09 00:58:09 -05:00
tobi-wan-kenobi
a678241a70
Merge pull request #800 from nepoz/network
Network
2021-07-09 07:32:59 +02:00
nepoz
5d80a5a1a0 Slight refactoring to try and break apart networkmethod 2021-07-09 00:28:00 -05:00
nepoz
48501fa534 Updated docstring 2021-07-08 23:00:57 -05:00
nepoz
f9017c3a38 Added more tests and exception handling 2021-07-08 22:55:23 -05:00
nepoz
2100a7cfdb Set up initial testing framework for network module 2021-07-08 12:10:46 -05:00
nepoz
3f524ab371 Refactoring, making use of netifaces 2021-07-08 09:04:40 -05:00
nepoz
911230c659 first complete implementation of the network module 2021-07-05 13:54:28 -05:00
nepoz
c7df1926dc Formatting fixes, fixed state management and added some icons 2021-07-05 13:09:17 -05:00
nepoz
448ab6de83 Functional display for wireless connection 2021-07-05 12:34:42 -05:00
nepoz
4987c7d3e2 added stateful behavior 2021-07-05 11:26:46 -05:00
nepoz
f141b95d8f Basic functionaly for dealingn with signal strength 2021-07-05 10:29:37 -05:00
nepoz
1232c4d960 Initial commit -- give basic message about interface being used 2021-07-05 07:55:47 -05:00
tobi-wan-kenobi
80663bdbc8
Merge pull request #798 from sayansil/main
Use the existing util.cli module in contrib/optman
2021-06-30 08:09:26 +02:00
Sayan Sil
4485b65722
Use the existing util.cli module 2021-06-30 11:31:42 +05:30
tobi-wan-kenobi
3ff2e49e5f
Merge pull request #796 from ouuan/playerctl
[modules/playerctl]: support the stopped status
2021-06-26 13:30:01 +02:00
Yufan You
37ccbd7f4a
[modules/playerctl]: support the stopped status 2021-06-26 18:19:24 +08:00
tobi-wan-kenobi
447d094fe2
Merge pull request #795 from sayansil/main
Add active gpu module using optimus-manager
2021-06-25 15:49:59 +02:00
Sayan
e5007a5729 Add active gpu module using optimus-manager 2021-06-24 23:17:35 +05:30
tobi-wan-kenobi
ec71d7fbbe
Merge pull request #793 from ouuan/playerctl
[modules/playerctl]: use `playerctl -f` and add `playerctl.args`
2021-06-11 12:56:35 +02:00
Yufan You
c4046d0cd2
[doc]: link to the README instead of manpage 2021-06-11 18:12:13 +08:00
Yufan You
51f68addcd
[modules/playerctl]: BREAKING: use playerctl -f and add playerctl.args
1. Use `playerctl -f` to format, which is more powerful. This also fixes
   #767, which is caused by missing a few fields of the metadata.
2. Add `playerctl.args`, so that users can choose a specific player,
   etc.
3. Display nothing when there's no running player.

This is a breaking change. Users need to change `{title}` to
`{{title}}`.
2021-06-11 17:38:46 +08:00
tobi-wan-kenobi
4b6b4b9052 [core] add custom minimizer capability
Add a new set of parameters to allow modules to be customly minimized.

It works like this: If a module has the parameter "minimize" set to a
true value, it will *not* use the built-in minimizer, and instead look
for "minimized" parameters (e.g. if date has the "format" parameter, it
would look for "minimized.format" when in minimized state). This allows
the user to have different parametrization for different states.

Also, using the "start-minimized" parameter allows for modules to start
minimized.

Note: This is hinging off the *module*, not the *widget* (the current,
hard-coded hiding is per-widget). This means that modules using this
method will only show a single widget - the first one - when in
minimized state. The module author has to account for that.

see #791
2021-05-24 12:56:02 +02:00
tobi-wan-kenobi
dfd23a44de [modules/layout] add a new - generic - layout module
Add a new module "layout" that will eventually evolve into the only
keyboard layout module.

Right now, it uses an external binary (get-kbd-layout) to determine the
layout of a keyboard device (because I did not manage to call libX11
with ctypes correctly).

see #788
see #790
2021-05-16 21:09:58 +02:00
tobi-wan-kenobi
902288f30d [modules/sensors] do not truncate temperature
use strip() instead of a sub-list to get the value for the temperature.

fixes #787
2021-05-11 11:23:06 +02:00
tobi-wan-kenobi
7f03c9ce2d [doc] update module documentation 2021-05-10 17:48:23 +00:00
tobi-wan-kenobi
9e20b48cee
Merge pull request #789 from fredj/sun_param_fix
Fix parameters name for the sun module
2021-05-10 17:46:24 +00:00
Frederic Junod
046b950b8a Fix parameters name for the sun module 2021-05-10 14:35:34 +02:00
tobi-wan-kenobi
afeb30e40e
Merge pull request #786 from solar-core/AddManualParam
Update modules.rst
2021-04-28 20:11:11 +02:00
Edward
9553bec7db
Update modules.rst 2021-04-28 20:48:49 +03:00
tobi-wan-kenobi
1e13798c95 [core/input] add pseudo-event "update" to selectively update modules
to trigger an update of a module (without actually triggering a mouse
interaction), use the special event "update":

bumblebee-ctl -m <module> -b update

see #784
2021-04-28 12:41:04 +02:00
tobi-wan-kenobi
028932a560 [tests/cpu] adapt tests and add per-cpu tests
fixes #785
2021-04-27 17:17:55 +02:00
tobi-wan-kenobi
fb6be007e5 [core/output] fix minimum width with padding
when calculating the minimum width of a widget, also take the padding
into consideration.

see #785
2021-04-27 17:17:28 +02:00
tobi-wan-kenobi
10c169af8a [modules/core/cpu] optionally add per-cpu widget
if the parameter "percpu" is set to true, create one widget per cpu, and
also handle warning/error state on a per-widget basis.

see #785
2021-04-27 17:17:13 +02:00
tobi-wan-kenobi
8001ed3ada [modules/cpu] Add per-cpu mode
By explicitly setting "cpu.percpu=True" as parameter, it is now possible
to get the CPU utilization on a per-cpu basis.

Future extension: One widget per CPU (add ID of CPU and add
warning/error state on a per CPU basis)

see #785
2021-04-27 17:16:54 +02:00
tobi-wan-kenobi
4a6be622a8 [modules/rotation] fix widget creation
each iteration of the rotation module created new/duplicate widgets,
causing a status bar of infinite length.

fixes #782
2021-04-03 19:29:40 +00:00
tobi-wan-kenobi
0410ac9c6b [doc/shortcut] better example for shortcut module 2021-04-03 19:24:01 +00:00
tobi-wan-kenobi
527d1706c2 [core/module] add fallback for module loading
Looks like some Python versions work with find_spec(), others with
spec_from_file_location(), so add find_spec() as fallback.

fixes #779
2021-04-02 03:30:09 +00:00
tobi-wan-kenobi
7f2ce7d76e
Merge pull request #778 from c05m4r/main
[themes] add albiceleste-powerline and rastafari-powerline
2021-03-27 20:30:55 +00:00
Marcos Gabriel Miller
abcf861fcb [themes] add rastafari-powerline 2021-03-27 17:22:35 -03:00
Marcos Gabriel Miller
10c9321c24 [themes] add albiceleste-powerline 2021-03-27 17:22:10 -03:00
tobi-wan-kenobi
3d809eb590
Merge pull request #777 from JaroslawSlabik/main
Adding the ability to change the editor to module todo
2021-03-20 11:38:01 +01:00
jslabik
7756eaaa31 Adding the ability to change the editor to module todo 2021-03-20 01:18:46 +01:00
tobi-wan-kenobi
e560649531 [modules/shell] remove obsolete event handlers
modules are now automatically updated when clicked.

fixes #776
2021-03-18 15:30:03 +01:00
tobi-wan-kenobi
4187bddad6 [modules/shell] do not default to "makewide"
to avoid unnecessarily wide shell modules, set "makewide" to false, if
it is not set at all.

fixes #775
2021-03-18 15:29:06 +01:00
tobi-wan-kenobi
65da1e2246 [doc] migrate to travis.com 2021-03-13 20:45:56 +01:00
tobi-wan-kenobi
38613495f2 [tests] Adjust for widget hiding 2021-03-13 20:44:36 +01:00
tobi-wan-kenobi
9f89e3a657 [core] make bumblebee more reactive
- set default delay to 0
- split input reading into 2 threads
- get rid of polling
2021-03-13 14:10:30 +01:00
tobi-wan-kenobi
868502d62e [modules/keys] add missing modules
forgot to add in the previous commit
2021-03-13 14:04:42 +01:00
tobi-wan-kenobi
8d88b23947 [modules] add a module "keys" that shows whether a key is pressed
also, add backend functionality to hide individual widgets of a module.
2021-03-13 13:17:20 +01:00
tobi-wan-kenobi
7d0d1455c8 [core/module] Add fallback for user module loading
If importlib.machinery is not present, fall back to importlib.util to
load the module by its absolute name.

hopefully fixes #763
2021-03-09 19:12:59 +01:00
tobi-wan-kenobi
6d1536ca80
Merge pull request #773 from fredj/stock_doc
[doc] Remove requests dependency in stock module
2021-03-02 18:31:40 +01:00
Frederic Junod
0ff49ac7d5 [doc] Remove requests dependency in stock module
The module is using `urllib.request`
2021-03-02 17:00:14 +01:00
tobi-wan-kenobi
32eef6b204 [doc] fix typos/wrong grammar
fixes #769
2021-02-27 13:09:32 +01:00
tobi-wan-kenobi
da7734d81f
Merge pull request #766 from michalroxorpl/enable_scroll_in_shell
Add code to enable scrolling of shell module output
2021-02-26 18:12:18 +01:00
Michal Cieslicki
6d7934f0fe Add code to enable scrolling of shell module output 2021-02-26 18:02:47 +01:00
tobi-wan-kenobi
d4425039b9
Merge pull request #764 from michalroxorpl/external_configuration_file
Add parameter to specify a configuration file
2021-02-20 14:49:45 +01:00
Michal Cieslicki
618ebbeccc Add parameter to specify a configuration file 2021-02-20 13:50:31 +01:00
tobi-wan-kenobi
31f1f99102 [doc] regenerate to fix typos 2021-02-12 09:31:32 +01:00
tobi-wan-kenobi
6e3caa6f14 [modules/shortcut] fix typo
fixes #760
2021-02-12 09:31:09 +01:00
tobi-wan-kenobi
0734c970b0 [modules/hddtemp] fix typo
fixes #761
2021-02-12 09:30:24 +01:00
tobi-wan-kenobi
406eadeac7 [modules/time] update once per second
fixes #676
2021-01-17 15:35:39 +01:00
tobi-wan-kenobi
a27c284869 [core/module] fix failing unit test
wrong error handling again
2021-01-17 15:29:44 +01:00
tobi-wan-kenobi
beca26c2bf [core/config] Allow modules to be hidden when in critical/error state
When a module is in critical state, the user can now hide the module
(e.g. if pulseaudio fails to load).

fixes #746
2021-01-17 15:21:40 +01:00
tobi-wan-kenobi
45c0a382c9 [core/module] fix load error when no user module exists 2021-01-17 15:17:14 +01:00
tobi-wan-kenobi
21ded8f640 [core] Allow module loading from user directory
If a module fails to load from both core and contrib, fall back to
loading by file name from "~/.config/bumblebee-status/modules/<name>.py"

fixes #757
2021-01-17 14:18:58 +01:00
tobi-wan-kenobi
413abdcae7 [doc] add license badge 2020-12-31 13:22:40 +01:00
tobi-wan-kenobi
5e790b7496 [doc] Add reference to slackbuild
fixes #755
2020-12-31 13:21:35 +01:00
tobi-wan-kenobi
0e37d6cbf2
Merge pull request #754 from gkeep/playerctl-improvements
[modules/playerctl] Add format and layout parameters
2020-12-27 17:58:33 +01:00
gkeep
b74ebce702 [modules/playerctl] Small fix 2020-12-27 19:12:36 +03:00
gkeep
436cea8f37 [modules/playerctl] Add format and layout parameters 2020-12-27 19:02:45 +03:00
tobi-wan-kenobi
af95b2e276 [tests] fix syntax error 2020-12-27 14:52:45 +01:00
tobi-wan-kenobi
960792b2e5 [tests] fix module load test for python 3.6 and further 2020-12-27 14:49:12 +01:00
tobi-wan-kenobi
73b071edb0 [doc] clarify interval handling further
fixes #751
2020-12-20 15:11:35 +01:00
tobi-wan-kenobi
7b1659a1b5 [core/theme] add /usr/share as theme directory
add a theme directory /usr/share/bumblebee-status/themes for system-wide
theme installation.

fixes #753
2020-12-20 10:23:06 +01:00
tobi-wan-kenobi
7fd4f710a1 [doc] improve interval documentation
Add information about global vs. per-module intervals.

see #751
2020-12-19 13:17:05 +01:00
tobi-wan-kenobi
a94114dd94 [core/module] better error reporting for failed module loads
if a module fails to load, explicitly log errors for core and contrib in
the error log, but be a bit less verbose (and less confusing) in the
module error message itself.

fixes #747
2020-12-19 13:07:29 +01:00
tobi-wan-kenobi
64355e5314
Merge pull request #752 from gkeep/spotifyd-support
[modules/spotify] Add initial spotifyd compatibility
2020-12-18 12:49:48 +01:00
gkeep
601b2115ce Add initial spotifyd compatibility 2020-12-18 13:56:26 +03:00
tobi-wan-kenobi
3644acce76 [pip] adjust removed dependencies 2020-12-07 10:00:20 +01:00
tobi-wan-kenobi
fa66873582 [core/theme] add xresources support
add support for reading foreground and background, and colors, from
xresources.py

many thanks to @Aliuakbar for the addition!

fixes #731
2020-12-07 09:01:08 +01:00
tobi-wan-kenobi
13b06793da
Merge pull request #745 from jayvdb/libvirt
.travis.yml setup
2020-12-05 13:43:04 +01:00
John Vandenberg
9e9dff7658 rss.txt: Unpin feedparser
And install feedparser<6 on Travis for Python 3.4-5.
2020-12-05 17:11:30 +07:00
John Vandenberg
e872897346 .travis.yml: Pre-install libvirt-dev
Also install libvirt-python<6.3 for Python 3.4 support.
2020-12-05 15:01:07 +07:00
John Vandenberg
a018343af6 .travis.yml: Add libgit2-dev
And pre-install pygit2<1 for Python 3.4 and 3.5 support.
2020-12-05 14:59:46 +07:00
John Vandenberg
d0e309ad0f test_format: Allow 2.00TiB 2020-12-05 14:57:52 +07:00
John Vandenberg
b598869450 .travis.yml: Add taskwarrior
Taskwarrior package requires task to be installed.
2020-12-05 14:57:50 +07:00
tobi-wan-kenobi
5ff6263986 [travis] forgotten in previous commit 2020-12-04 18:55:21 +01:00
tobi-wan-kenobi
3937e73e7d [travis] hopefully fixed test build
locally tested with a xenial image, and this should work.

Unfortunately, cannot get the pygit2 to build against the provided
libgit2-dev, so using the packaged version instead.
2020-12-04 18:55:03 +01:00
tobi-wan-kenobi
e0fc98bfb0 [travis] temporarily exclude dbus and libvirt from requirements
since they cause issues in the automated tests, do not install the dbus
and libvirt python libraries.
2020-12-03 20:48:06 +01:00
tobi-wan-kenobi
47a640e610
Merge pull request #744 from jayvdb/travis-pip-install
.travis.yml: Install declared PyPI dependencies
2020-12-03 07:15:49 +01:00
John Vandenberg
a58454de97 .travis.yml: Allow building dbus-python 2020-12-03 12:54:44 +07:00
John Vandenberg
21268d7d86 Use dbus-python PyPI package 2020-12-03 10:55:23 +07:00
John Vandenberg
071a69b2d8 .travis.yml: Install declared PyPI dependencies 2020-12-03 08:02:41 +07:00
John Vandenberg
659a0147da system.txt: Replace tk with Pillow
Related to https://github.com/tobi-wan-kenobi/bumblebee-status/issues/741
2020-12-03 08:02:12 +07:00
John Vandenberg
38c4b46351 libvirtvms.txt: Fix PyPI dependency name
Related to https://github.com/tobi-wan-kenobi/bumblebee-status/issues/741
2020-12-03 07:56:36 +07:00
tobi-wan-kenobi
cd851340e2 [pip] updated/fixed dependencies
many thanks to @jayvdb for pointing those out!

fixes #741
2020-12-02 21:21:34 +01:00
tobi-wan-kenobi
1d8b261057
Merge pull request #742 from cdbrkfxrpt/main
Add TiB to disk units, add SI unit option for disk space
2020-12-02 21:14:18 +01:00
tobi-wan-kenobi
efebc8d049 [travis] add sudo (accidentially removed) 2020-12-02 20:53:43 +01:00
Florian Eich
3c08eafa4a Add TiB to disk units, add SI unit option for disk space 2020-12-02 19:08:45 +01:00
tobi-wan-kenobi
2de39be731 [travis/code climate] update to new reporter + small fixes 2020-12-02 11:07:19 +01:00
tobi-wan-kenobi
a8d1254e06 [modules/nic] Make regex for SSID a raw string 2020-12-01 15:58:55 +01:00
tobi-wan-kenobi
5fb19b66da
Merge pull request #740 from cdbrkfxrpt/main
Change iw call in module nic from link to info
2020-12-01 03:32:10 +01:00
Florian Eich
de01d96b91 Change iw call in module nic from link to info 2020-12-01 00:19:17 +01:00
tobi-wan-kenobi
f5e6bc12db [doc] update doc 2020-11-30 17:22:42 +01:00
tobi-wan-kenobi
d0ee1b06e4 [modules/nic] make exclude list regular expression capable
Allow the usage of regexps in the exclude list, but keep the "this is a
prefix" logic for backwards compatibility.

should address (see #738)
2020-11-30 17:21:17 +01:00
tobi-wan-kenobi
a8b28cd0bf
Merge pull request #739 from eumiro/py39
add python 3.9 support
2020-11-30 07:06:27 +01:00
Miroslav Šedivý
681bba4f12 add python 3.9 support 2020-11-29 22:14:33 +01:00
tobi-wan-kenobi
6c08336154
Merge pull request #736 from jebaum/main
add rofication module
2020-11-24 07:03:56 +01:00
James Baumgarten
02465ea0c2 add rofication module 2020-11-23 20:44:17 -08:00
Tobias Witek
08ef42834e [modules/nic] update documentation to include iwgetid
fixes #734
2020-11-13 14:56:31 +01:00
tobi-wan-kenobi
0fc1782e0b
Merge pull request #733 from martindoublem/main
[module] Improved smartstatus with combined_singles
2020-11-06 12:36:33 +01:00
Martin Morlot
a811c9c886 [module] Improved smartstatus with combined_singles
Added combined_singles as way to detect the drives that are permanently inside your machine and not plugged via USB.

As USB flash drives without smartstatus sometime caused the module to crash.
2020-11-06 12:14:56 +01:00
tobi-wan-kenobi
c5f13746b3
Merge pull request #730 from donfranio/fix-for-missing-python2
update python name in shebang to respect PEP-0394
2020-10-26 09:03:51 +01:00
Frank Scherrer
5e96b63c60
update python name in shebang to respect PEP-0394 2020-10-26 08:28:55 +01:00
tobi-wan-kenobi
d1e9e7e8bb
Merge pull request #729 from spxtr/patch-1
Fix arch-updates off-by-one.
2020-10-26 03:08:30 +01:00
Joe Finney
9b82e736a0
Fix arch-updates off-by-one.
There's a newline in the output so this overcounts by one.
2020-10-25 17:29:00 -07:00
tobi-wan-kenobi
45125f39af
Merge pull request #728 from joachimmathes/module_dunstctl
Provide alternative dunstctl implementation
2020-10-18 22:06:35 +02:00
Joachim Mathes
3c0499ba56 Provide alternative dunstctl implementation 2020-10-18 21:53:47 +02:00
tobi-wan-kenobi
68bd6f8ef8
Merge pull request #727 from w1kl4s/master
Fix Python 3.9 compatibility
2020-10-14 18:30:56 +02:00
w1kl4s
1a7ae9ecc6 Fix Python 3.9 compatibility
Replaced threading.Thread.isAlive() with threading.Thread.is_alive()
2020-10-14 18:07:29 +02:00
tobi-wan-kenobi
49edf97b89
Merge pull request #723 from martindoublem/main
[Bluetooth2] fixed the execution of the toggle state
2020-10-09 11:14:59 +02:00
Martin Morlot
1912f3053d [Bluetooth2] fixed the execution of the toggle state 2020-10-09 10:59:59 +02:00
tobi-wan-kenobi
bfbd94232d
Merge pull request #722 from izn/add-layout-xkbswitch-tests
Create layout-xkbswitch tests
2020-10-08 04:58:16 +02:00
Thaynã Moretti
04a2ea438b Create layout-xkbswitch tests 2020-10-07 17:37:04 -03:00
tobi-wan-kenobi
270b3e13bf
Merge pull request #721 from izn/add-dunstctl-tests
Create dunstctl tests
2020-10-06 06:37:26 +02:00
Thaynã Moretti
180a87e0c3 Create dunstctl tests 2020-10-05 20:10:01 -03:00
tobi-wan-kenobi
f7feb3e674
Merge pull request #720 from izn/add-arch-update-tests
Add arch-update tests
2020-10-04 21:06:18 +02:00
Thaynã Moretti
0a7a4150e0 Create arch-update tests 2020-10-04 15:35:13 -03:00
tobi-wan-kenobi
1751e4afa2
Merge pull request #718 from joshbarrass/spotify
Add "concise controls" to Spotify module
2020-10-04 16:54:42 +02:00
tobi-wan-kenobi
284ef6b260
Merge pull request #717 from joshbarrass/main
Add org-mode TODO module
2020-10-04 16:52:11 +02:00
Joshua Barrass
fbe5764313
Add "concise controls" to spotify module 2020-10-04 15:41:15 +01:00
Joshua Barrass
823a57d261
Add org-mode TODO module 2020-10-04 14:44:27 +01:00
tobi-wan-kenobi
5ec4b04a64
Merge pull request #716 from Niladri29/main
Some minor
2020-10-02 21:27:44 +02:00
Niladri Bhattacharjee
130bfc0f0b
Some minor 2020-10-03 00:53:04 +05:30
tobi-wan-kenobi
f2153b95a5 [doc] add scrolling parameters
see #710
2020-10-02 09:34:53 +02:00
tobi-wan-kenobi
57f669a5e7
Merge pull request #714 from izn/create-symbolic-links
Create symbolic links
2020-10-02 08:09:05 +02:00
Thaynã Moretti
a253e70328 Update documentation 2020-10-01 19:18:49 -03:00
Thaynã Moretti
4df495601a Create symbolic links 2020-10-01 19:10:46 -03:00
tobi-wan-kenobi
96f8e92822
Merge pull request #712 from izn/add-amixer-tests
Add amixer tests
2020-09-29 05:07:19 +02:00
tobi-wan-kenobi
1bdaedb481
Merge pull request #711 from izn/fix/force-feedparser-package-version
Force feedparser to 6.0.0b1
2020-09-29 05:06:24 +02:00
Thaynã Moretti
7d85ba87d5 Force feedparser to 6.0.0b1 2020-09-28 19:45:34 -03:00
Thaynã Moretti
9e80d4d907 Add amixer tests 2020-09-28 19:24:45 -03:00
tobi-wan-kenobi
1759c33cf3 [bumblebee] make input delay configurable
make the delay before updating the bar after an input event
configurable as engine.input_delay.

see #702
2020-09-16 09:13:48 +02:00
tobi-wan-kenobi
806b97895e [bumblebee] add small sleep before update
in some situations/modules (spotify, playerctl, etc.), it is possible
that a state change (e.g. play/pause) takes a small time to actually
propagate into the whole system (e.g. until the next "update" retrieves
the correct value).

To alleviate that, add a very small delay to the update.
2020-09-16 09:09:49 +02:00
tobi-wan-kenobi
8a4fc40947 Revert "[modules/spotify] properly initialize widgets"
This reverts commit aa6238a5c6.
2020-09-15 20:27:50 +02:00
Tobias Witek
e42f09b27e [bumblebee] Make update lock on input non-blocking
for consistent lock behaviour, make the input loop lock non-blocking.

see #702
2020-09-13 11:21:39 +02:00
Tobias Witek
aa6238a5c6 [modules/spotify] properly initialize widgets
see #702
2020-09-13 11:19:05 +02:00
Tobias Witek
fcbb89db90 [modules/spotify] make global dbus object
instead of creating a new dbus instance during each update interval,
reuse one dbus instance.

see #702
2020-09-13 11:17:21 +02:00
tobi-wan-kenobi
f37eb31f94
Merge pull request #709 from cristianmiranda/missing_screenshots_for_dunstctl_and_thunderbird
Docs; Missing screenshots for dunstctl and thunderbird
2020-09-11 06:20:11 +02:00
Cristian Miranda
4621de95b5 docs[thunderbird]: Added missing screenshot 2020-09-10 17:42:41 -03:00
Cristian Miranda
2390dfa946 docs[dunstctl]: Added missing screenshot 2020-09-10 17:42:19 -03:00
tobi-wan-kenobi
070fe865dd [modules/spotify] update in the background
to rule out issues in the dbus communication, update the spotify module
in the background.

see #702
2020-09-09 14:15:16 +02:00
tobi-wan-kenobi
b79c19b616 [modules/spotify] fix song not shown issue
the previous commit accidentially removed the song display. re-add that
and also add a bit of logging for troubleshooting.
2020-09-06 14:27:17 +02:00
tobi-wan-kenobi
3a5365a2c3 [doc] add testing guidelines, general improvements 2020-09-06 14:09:18 +02:00
tobi-wan-kenobi
965e7b2453 [modules/spotify] improve update mechanism
instead of updating the widget list during each update, create the list
of widgets during initialization, and later only update the widget
states.

see #702
2020-09-06 14:00:32 +02:00
tobi-wan-kenobi
7a4d4d5ab6
Merge pull request #706 from cristianmiranda/thunderbird
[modules/thunderbird]: Thunderbird's unread email counts by inbox
2020-09-06 07:13:46 +02:00
tobi-wan-kenobi
f180c7cc58
Merge pull request #705 from cristianmiranda/dunstctl
[modules/dunstctl]: Toggle dunst v1.5.0+ notifications using dunstctl
2020-09-06 07:10:58 +02:00
Cristian Miranda
100568206a [modules/thunderbird]: Thunderbird's unread email counts by inbox 2020-09-05 19:22:40 -03:00
Cristian Miranda
d568ef3622 [modules/dunstctl]: Toggle dunst v1.5.0+ notifications using dunstctl 2020-09-04 17:30:46 -03:00
tobi-wan-kenobi
ecf1825c00
Merge pull request #704 from izn/add-uptime-and-public-ip-tests
Add uptime and public ip tests
2020-09-03 03:42:38 +02:00
Thaynã Moretti
e7b853c667 Remove useless mock side effect 2020-09-02 22:16:58 -03:00
Thaynã Moretti
710a2cef98 Create uptime and public ip tests 2020-09-02 22:10:09 -03:00
tobi-wan-kenobi
7e7327c374
Merge pull request #703 from izn/add-load-module-tests
Add load module tests
2020-09-02 19:38:06 +02:00
Thaynã Moretti
6c0930cfae Add load module tests 2020-09-02 14:33:07 -03:00
tobi-wan-kenobi
1638180b6d
Merge pull request #700 from izn/add-date-and-time-module-tests
Add date and time module tests
2020-09-02 07:15:56 +02:00
tobi-wan-kenobi
7b57299cdb
Merge pull request #701 from izn/improve-network-traffic-tests
Improve network traffic module tests
2020-09-02 07:14:53 +02:00
Thaynã Moretti
04f6a6a08f Add time module tests 2020-09-01 22:16:57 -03:00
Thaynã Moretti
213c28992c Add date tests 2020-09-01 22:16:57 -03:00
Thaynã Moretti
032a651efa Improve network traffic module tests 2020-09-01 22:12:54 -03:00
tobi-wan-kenobi
1b66e17780
Merge pull request #699 from izn/add-cpu-module-tests
Add CPU module tests
2020-09-02 02:20:47 +02:00
tobi-wan-kenobi
6db8470c2b
Merge pull request #698 from izn/add-datetime-tests
Add datetime tests + freezegun dependency
2020-09-02 02:17:11 +02:00
Thaynã Moretti
07f0b7e34a Add CPU module tests 2020-09-01 20:54:10 -03:00
Thaynã Moretti
b1501a8159 Remove useless import 2020-09-01 20:29:04 -03:00
Thaynã Moretti
0776592da6 Add freezegun to Travis dependencies 2020-09-01 20:12:31 -03:00
Thaynã Moretti
f22095e978 Add datetime module tests 2020-08-31 17:48:19 -03:00
tobi-wan-kenobi
ebeb084da0
Merge pull request #696 from izn/fix-memory-module-tests
Fix memory module tests
2020-08-30 18:06:14 +02:00
Thaynã Moretti
0c8d682d62 Add unit tests 2020-08-30 12:49:33 -03:00
Thaynã Moretti
49de0e520b Reduce code cognitive complexity 2020-08-30 12:37:58 -03:00
Thaynã Moretti
6f6f3cedd9 Improve meminfo parse logic 2020-08-30 12:28:48 -03:00
Thaynã Moretti
820598b1b8 Fix memory module tests 2020-08-30 12:11:48 -03:00
tobi-wan-kenobi
780c5bf3d0
Merge pull request #695 from izn/add-memory-module-tests
Add memory module tests
2020-08-30 16:25:08 +02:00
Thaynã Moretti
945838dc74 Create memory module tests 2020-08-30 11:15:23 -03:00
tobi-wan-kenobi
9d5035fa10
Merge pull request #694 from bbernhard/octoprint_long_error
small improvements in octoprint plugin
2020-08-30 09:21:04 +02:00
Bernhard B
c339a16365 small improvements in octoprint plugin
* octoprint sometimes returns additional information a the 3d printer is offline.
  so, it's better to check if the octoprint job state starts with "Offline".

* in case the returned job state is really long, truncate it.
2020-08-30 09:03:34 +02:00
tobi-wan-kenobi
58bbcf2ac5
Merge pull request #693 from izn/tests/network-traffic-module
Add Network Traffic module tests
2020-08-30 04:55:59 +02:00
Thaynã Moretti
22ddcf42bd Improve tests 2020-08-29 21:49:41 -03:00
Thaynã Moretti
dff187252a Fix RECV/SENT start values 2020-08-29 21:09:59 -03:00
Thaynã Moretti
2c8dafec70 Add Network Traffic module tests 2020-08-29 20:17:39 -03:00
tobi-wan-kenobi
905f71fa52 [core] fix broken "sparse" updates
c77f3aa accidentially broke "sparse" updates (i.e. updates that do not
trigger during each update interval).

Introduce a new update parameter, "force", to model the use case "update
everything on SIGUSR1".

fixes #692
2020-08-28 17:14:05 +02:00
tobi-wan-kenobi
362d1a5f6f [modules/contrib/dnf] fix undefined "widget" error
while refactoring, i overlooked that the variable "widget" doesn't exist
anymore.

see #692
2020-08-28 14:03:34 +02:00
tobi-wan-kenobi
db41792afb [dnf] simplify threading
use framework threading to simplify the dnf module

see #692
2020-08-27 20:07:10 +02:00
tobi-wan-kenobi
38e54a7c81
Merge pull request #691 from cristianmiranda/github_remove_error_print_on_exception
[github]: Remove error print on exception catch
2020-08-21 16:50:53 +02:00
Cristian Miranda
9b5477675c [github]: Remove error printing if exception caught
I experienced that when an exception is caught and we print it
I get an ugly error on the whole bar making it unusable. This
fixes that problem.
2020-08-20 10:47:56 -03:00
tobi-wan-kenobi
738a846853 Merge branch 'master' into main 2020-08-11 20:54:23 +02:00
tobi-wan-kenobi
d759ed5051
Merge pull request #690 from joshbarrass/deadbeef-patch
Quotes around the deadbeef string to prevent parsing errors by deadbeef
2020-08-11 06:16:58 +02:00
tobi-wan-kenobi
ad91b1c025
Merge pull request #689 from joshbarrass/usr1
Force status bar to update early using USR1 signal
2020-08-11 06:16:04 +02:00
Joshua Barrass
9e04e0a27b
Quotes around the deadbeef string to prevent parsing errors by deadbeef 2020-08-11 00:54:35 +01:00
Joshua Barrass
c77f3aa3bc
Force update using USR1 signal 2020-08-11 00:45:13 +01:00
tobi-wan-kenobi
b368cb49da
Merge pull request #688 from alexsr/master
Fix wrong usage of asbool in layout-xkb
2020-08-06 16:40:37 +02:00
Alexander Scheid-Rehder
5959d73cde Fix wrong usage of asbool in layout-xkb 2020-08-06 14:49:41 +02:00
tobi-wan-kenobi
f8d035c079
Merge pull request #687 from anlif/main
[modules/contrib/stock] handle urllib request exception
2020-08-01 10:36:23 +02:00
Andreas Lindahl Flåten
5de616ff89 [modules/contrib/stock] handle urllib request exception
Handle exception that is raised when e.g. your network connection is
down.
2020-07-31 15:26:49 +02:00
tobi-wan-kenobi
72d255f0ae [doc] document additional config file parameters
see #678
2020-07-27 07:04:47 +02:00
tobi-wan-kenobi
969378846f
Merge pull request #686 from nayaverdier/main
[xrandr] Add autotoggle behavior to xrandr
2020-07-27 06:55:15 +02:00
Naya Verdier
13a851a636
[xrandr-tests] Test disconnecting excluded active display 2020-07-26 15:52:47 -07:00
Naya Verdier
06afb03807
[xrandr] add tests for dis/connecting without autotoggle 2020-07-26 13:29:14 -07:00
Naya Verdier
06d6739da4
[xrandr] Add autotoggle behavior to xrandr
Also makes i3 subscription events and widget click live-update
2020-07-26 13:21:49 -07:00
tobi-wan-kenobi
3fd8c5aaa0
Merge pull request #684 from nayaverdier/main
[xrandr] unit tests, exclude parameter, and final display safeguard
2020-07-26 11:25:07 +02:00
Naya Verdier
b1adc382aa
[vault] Turns out, <Leave> is triggered when going into a submenu 2020-07-25 12:38:28 -07:00
Naya Verdier
647ad44b31
ModuleNotFoundError incompatible with python 3.4, 3.5 2020-07-25 11:50:13 -07:00
Naya Verdier
22d85d6d1d
[xrandr-test] Catch all exceptions if i3 Subscription fails 2020-07-25 11:33:42 -07:00
Naya Verdier
d05ff39f02
[xrandr] Add unit tests 2020-07-25 10:53:44 -07:00
Naya Verdier
b14eae4d6c
[xrandr] add safeguard to prevent turning off the only display 2020-07-25 10:52:57 -07:00
Naya Verdier
5874850bd5
[xrandr] Fix neighbor functionality when some displays are excluded 2020-07-25 10:52:10 -07:00
Naya Verdier
8f57bb952d
[xrandr] add exclude parameter to ignore certain display prefixes 2020-07-25 08:47:31 -07:00
Naya Verdier
5f2857ad9a
[vault] add leave_menu parameter to auto-close the menu 2020-07-25 08:46:57 -07:00
Naya Verdier
4aff0499f0
[util.popup] Deduplicate code, "close" button only if leave=False 2020-07-25 08:43:26 -07:00
tobi-wan-kenobi
d358790c6a [travis] make unit tests work again 2020-07-21 09:04:50 +02:00
tobi-wan-kenobi
b39dba8867 [travis] remove webbrowser 2020-07-21 09:02:51 +02:00
tobi-wan-kenobi
c692a776b6 [travis] remove tkinter 2020-07-21 09:00:59 +02:00
tobi-wan-kenobi
bee2586ed4 [travis] remove tempfile 2020-07-21 08:58:36 +02:00
tobi-wan-kenobi
632eb0c450 [travis] remove speedtest 2020-07-21 08:55:54 +02:00
tobi-wan-kenobi
6870c3ba84 [travis] removed a couple more modules 2020-07-21 08:53:41 +02:00
tobi-wan-kenobi
993be61eec [travis] remove pygit2 2020-07-21 08:43:08 +02:00
tobi-wan-kenobi
84f2fdd419 [travis] removed random 2020-07-21 08:27:23 +02:00
tobi-wan-kenobi
b7083aacce [travis] remove multiprocessing
doesn't install :(
2020-07-21 08:25:18 +02:00
tobi-wan-kenobi
6c7737cdd5 [travis] remove libvirt-python
requires at least python3.5
2020-07-21 08:23:17 +02:00
tobi-wan-kenobi
550b594c86 [tests] fix some test prerequisites 2020-07-21 08:20:55 +02:00
tobi-wan-kenobi
4fe56fc00d [travis] remove math module 2020-07-21 08:19:51 +02:00
tobi-wan-kenobi
6c9e89627a [travis] fix package name 2020-07-21 08:17:25 +02:00
tobi-wan-kenobi
bf1cae2399 [travis] forgot sudo 2020-07-21 08:15:47 +02:00
tobi-wan-kenobi
463bc9665b [travis] fix python-dbus 2020-07-21 08:14:21 +02:00
tobi-wan-kenobi
a008ce3e58 [travis] add all dependencies
to make all unit tests run, and none skipped (hopefully), add a list of
dependencies
2020-07-21 08:08:22 +02:00
tobi-wan-kenobi
54a2fc3a41 [generate-tests] black 2020-07-20 13:58:46 +02:00
tobi-wan-kenobi
548ccc5e94 [tests] add somewhat experimental import-time tests
add auto-generated tests that check that a given module can be imported,
if all prerequisites are followed.

see #641
2020-07-20 13:58:24 +02:00
tobi-wan-kenobi
9be1331e1b [tests] add placeholder for core tests 2020-07-20 13:17:35 +02:00
tobi-wan-kenobi
dc70527797 [util] add a script to generate basic autotests
Add a small utility that can generate a basic "import this unless you
are missing dependencies" test for all modules.
2020-07-20 13:17:15 +02:00
tobi-wan-kenobi
6025fcd2da [doc] update as per PR #683 2020-07-18 08:23:44 +02:00
tobi-wan-kenobi
7c8ddc9c87 [doc] update docstrings as per PR #683 2020-07-18 08:23:28 +02:00
tobi-wan-kenobi
72966ee37d [modules/{cpu,load,memory}] Add gnome-system-monitor dependency to doc 2020-07-18 08:16:57 +02:00
tobi-wan-kenobi
df29627983 [core/doc] add autogenerated warning to modules.rst 2020-07-18 08:14:23 +02:00
tobi-wan-kenobi
83a4be3bc0 [doc] add module portage_status (plus attribution) 2020-07-12 19:31:28 +02:00
tobi-wan-kenobi
088309df23
Merge pull request #682 from andrewreisner/main
Add simple Gentoo portage status module.
2020-07-12 19:29:48 +02:00
Andrew Reisner
b1bb0fe690
Run formatter. 2020-07-12 11:01:00 -06:00
Andrew Reisner
0785202860
Add simple portage status module.
This adds a simple module to display the status of Gentoo portage
operations by reading its logfile.
2020-07-12 10:52:05 -06:00
tobi-wan-kenobi
5ea2708f45
Merge pull request #681 from bbernhard/messagereceiver
reworked messagereceiver module
2020-07-11 19:33:42 +02:00
Bernhard B
a4a622252b reworked messagereceiver module
* use bumblebee's internal threading capabilities
* various small code improvements (pylint)
2020-07-11 18:07:57 +02:00
tobi-wan-kenobi
2b888325a6 [doc] add messagereceiver (+ attribution) 2020-07-10 20:19:43 +02:00
tobi-wan-kenobi
2d6cedd8ac
Merge pull request #680 from bbernhard/messagereceiver
Messagereceiver
2020-07-10 20:13:24 +02:00
Bernhard B
01cde70e14 improved documentation of messagereceiver module 2020-07-10 17:35:40 +02:00
Bernhard B
adbba9bf9a fixed small bug in messagereceiver
* wrong logging syntax
2020-07-10 17:17:16 +02:00
Bernhard B
0067ce83f0 added new module messagereceiver
* binds to unix sockets and listens for incoming messages. The message
  will then be displayed in the status bar.
2020-07-10 17:11:50 +02:00
tobi-wan-kenobi
72045b2318 [core/config] make configurable module list work
configparser doesn't seem to have direct array support,
so use format.aslist() to get a list of modules

fixes #678
2020-07-09 07:04:45 +02:00
tobi-wan-kenobi
a9f50f1b51 [core/config] add "core" config section
move theme and modules into a "core" config section
2020-07-09 07:04:22 +02:00
tobi-wan-kenobi
057f894d52 [core/config] change preferred theme source
now, it works like this:

- if present, use what's on the CLI
- if not, use what's present in the config
- fallback is "default"

see #679
2020-07-09 06:56:43 +02:00
tobi-wan-kenobi
5c207df6ae
Merge pull request #679 from fenilgandhi/main
Updating config parser
2020-07-09 06:52:53 +02:00
ColdFire
aac2316d74
Updating config parser 2020-07-06 14:20:41 +05:30
tobi-wan-kenobi
6b63b87c42
Merge pull request #677 from andrewreisner/main
Small fix to xrandr module.
2020-07-06 07:00:52 +02:00
Andrew Reisner
edb8eaf410
Small fix to xrandr module. 2020-07-05 12:35:44 -06:00
tobi-wan-kenobi
b4fc88b57e
Merge pull request #675 from xsteadfastx/iw
[modules/nic] Using `iw` to find out whats the SSID name
2020-06-30 20:20:17 +02:00
tobi-wan-kenobi
8f0fdd6b39
Merge pull request #674 from gkeep/media-states-to-actions
[modules/media] Convert icon states to actions
2020-06-30 20:19:32 +02:00
Marvin Steadfast
6d9d325eca [modules/nic] Using iw to find out whats the SSID name 2020-06-30 14:49:43 +02:00
gkeep
7579a08615 Use icon actions instead of states 2020-06-30 13:58:10 +03:00
tobi-wan-kenobi
f75f1a9f26 [modules/spotify] fix layout parameter 2020-06-30 07:20:54 +02:00
286 changed files with 8610 additions and 972 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

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
*.o
# Vim swap files
*swp
*~

9
.readthedocs.yaml Normal file
View file

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

View file

@ -1,18 +0,0 @@
sudo: false
language: python
python:
- "3.4"
- "3.5"
- "3.6"
- "3.7"
before_install:
- sudo apt-get -qq update
install:
- pip install -U coverage==4.3 pytest pytest-mock
- pip install codeclimate-test-reporter
script:
- coverage run --source=. -m pytest tests -v
- CODECLIMATE_REPO_TOKEN=40cb00907f7a10e04868e856570bb997ab9c42fd3b63d980f2b2269433195fdf codeclimate-test-reporter
addons:
code_climate:
repo_token: 40cb00907f7a10e04868e856570bb997ab9c42fd3b63d980f2b2269433195fdf

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,21 @@
# 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://travis-ci.org/tobi-wan-kenobi/bumblebee-status.svg?branch=main)](https://travis-ci.org/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.**
@ -28,16 +36,14 @@ Thanks a lot!
Required i3wm version: 4.12+ (in earlier versions, blocks won't have background colors)
Supported Python versions: 3.4, 3.5, 3.6, 3.7, 3.8
Supported Python versions: 3.4, 3.5, 3.6, 3.7, 3.8, 3.9
Supported FontAwesome version: 4 (free version of 5 doesn't include some of the icons)
---
**NOTE**
***NOTE***
The default branch for this project is `main` - I'm keeping `master` around for backwards compatibility (I do not want to break anybody's setup), but the default branch is now `main`!
If you are curious why: [ZDNet:github-master-alternative](https://www.zdnet.com/article/github-to-replace-master-with-alternative-term-to-avoid-slavery-references/)
The default branch for this project is `main`. If you are curious why: [ZDNet:github-master-alternative](https://www.zdnet.com/article/github-to-replace-master-with-alternative-term-to-avoid-slavery-references/)
---
@ -76,10 +82,16 @@ makepkg -sicr
pip install --user bumblebee-status
```
There is also a SlackBuild available here: [slackbuilds:bumblebee-status](http://slackbuilds.org/repository/14.2/desktop/bumblebee-status/) - many thanks to [@Tonus1](https://github.com/Tonus1)!
An ebuild, for Gentoo Linux, is available on [gallifrey overlay](https://github.com/fedeliallalinea/gallifrey/tree/master/x11-misc/bumblebee-status). Instructions for adding the overlay can be found [here](https://github.com/fedeliallalinea/gallifrey/blob/master/README.md).
# Dependencies
[Available modules](https://bumblebee-status.readthedocs.io/en/main/modules.html) lists the dependencies (Python modules and external executables)
for each module. If you are not using a module, you don't need the dependencies.
Some themes (e.g. all powerline themes) require Font Awesome http://fontawesome.io/ and a powerline-compatible font (powerline-fonts) https://github.com/powerline/fonts
# Usage
## Normal usage
In your i3wm configuration, modify the *status_command* for your i3bar like this:

View file

@ -12,6 +12,7 @@ button = {
"right-mouse": 3,
"wheel-up": 4,
"wheel-down": 5,
"update": -1,
}
@ -20,7 +21,7 @@ def main():
parser.add_argument(
"-b",
"--button",
choices=["left-mouse", "right-mouse", "middle-mouse", "wheel-up", "wheel-down"],
choices=["left-mouse", "right-mouse", "middle-mouse", "wheel-up", "wheel-down", "update"],
help="button to emulate",
default="left-mouse",
)

View file

@ -1,11 +1,11 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import os
import sys
import json
import time
import signal
import socket
import select
import logging
import threading
@ -38,44 +38,48 @@ class CommandSocket(object):
self.__socket.close()
os.unlink(self.__name)
def process_event(event_line, config, update_lock):
modules = {}
try:
event = json.loads(event_line)
core.input.trigger(event)
if "name" in event:
modules[event["name"]] = True
except ValueError:
pass
def handle_input(output, update_lock):
delay = float(config.get("engine.input_delay", 0.0))
if delay > 0:
time.sleep(delay)
if update_lock.acquire(blocking=False) == True:
core.event.trigger("update", modules.keys(), force=True)
core.event.trigger("draw")
update_lock.release()
def handle_commands(config, update_lock):
with CommandSocket() as cmdsocket:
poll = select.poll()
poll.register(sys.stdin.fileno(), select.POLLIN)
poll.register(cmdsocket, select.POLLIN)
while True:
events = poll.poll()
tmp, _ = cmdsocket.accept()
line = tmp.recv(4096).decode()
tmp.close()
logging.debug("socket event {}".format(line))
process_event(line, config, update_lock)
modules = {}
for fileno, event in events:
if fileno == cmdsocket.fileno():
tmp, _ = cmdsocket.accept()
line = tmp.recv(4096).decode()
tmp.close()
logging.debug("socket event {}".format(line))
else:
line = "["
while line.startswith("["):
line = sys.stdin.readline().strip(",").strip()
logging.info("input event: {}".format(line))
try:
event = json.loads(line)
core.input.trigger(event)
if "name" in event:
modules[event["name"]] = True
except ValueError:
pass
update_lock.acquire()
core.event.trigger("update", modules.keys())
core.event.trigger("draw")
update_lock.release()
poll.unregister(sys.stdin.fileno())
def handle_events(config, update_lock):
while True:
try:
line = sys.stdin.readline().strip(",").strip()
if line == "[": continue
logging.info("input event: {}".format(line))
process_event(line, config, update_lock)
except Exception as e:
logging.error(e)
def main():
global started
config = core.config.Config(sys.argv[1:])
level = logging.DEBUG if config.debug() else logging.ERROR
if config.logfile():
@ -98,10 +102,23 @@ 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()
input_thread = threading.Thread(target=handle_input, args=(output, update_lock, ))
input_thread.daemon = True
input_thread.start()
event_thread = threading.Thread(target=handle_events, args=(config, update_lock, ))
event_thread.daemon = True
event_thread.start()
cmd_thread = threading.Thread(target=handle_commands, args=(config, update_lock, ))
cmd_thread.daemon = True
cmd_thread.start()
def sig_USR1_handler(signum,stack):
if update_lock.acquire(blocking=False) == True:
core.event.trigger("update", force=True)
core.event.trigger("draw")
update_lock.release()
if config.debug():
modules.append(core.module.load("debug", config, theme))
@ -118,8 +135,7 @@ 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:
core.event.trigger("update")
@ -136,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

@ -71,6 +71,11 @@ class print_usage(argparse.Action):
)
rst = {}
if self._format == "rst":
print(".. THIS DOCUMENT IS AUTO-GENERATED, DO NOT MODIFY")
print(".. To change this document, please update the docstrings in the individual modules")
for m in all_modules():
try:
module_type = "core"
@ -142,6 +147,13 @@ class Config(util.store.Store):
parser = argparse.ArgumentParser(
description="bumblebee-status is a modular, theme-able status line generator for the i3 window manager. https://github.com/tobi-wan-kenobi/bumblebee-status/wiki"
)
parser.add_argument(
"-c",
"--config-file",
action="store",
default=None,
help="Specify a configuration file to use"
)
parser.add_argument(
"-m", "--modules", nargs="+", action="append", default=[], help=MODULE_HELP
)
@ -153,7 +165,7 @@ class Config(util.store.Store):
default=[],
help=PARAMETER_HELP,
)
parser.add_argument("-t", "--theme", default="default", help=THEME_HELP)
parser.add_argument("-t", "--theme", default=None, help=THEME_HELP)
parser.add_argument(
"-i",
"--iconset",
@ -167,6 +179,13 @@ class Config(util.store.Store):
default=[],
help="Specify a list of modules to hide when not in warning/error state",
)
parser.add_argument(
"-e",
"--errorhide",
nargs="+",
default=[],
help="Specify a list of modules that are hidden when in state error"
)
parser.add_argument(
"-d", "--debug", action="store_true", help="Add debug fields to i3 output"
)
@ -191,13 +210,18 @@ class Config(util.store.Store):
self.__args = parser.parse_args(args)
for cfg in [
"~/.bumblebee-status.conf",
"~/.config/bumblebee-status.conf",
"~/.config/bumblebee-status/config",
]:
if self.__args.config_file:
cfg = self.__args.config_file
cfg = os.path.expanduser(cfg)
self.load_config(cfg)
else:
for cfg in [
"~/.bumblebee-status.conf",
"~/.config/bumblebee-status.conf",
"~/.config/bumblebee-status/config",
]:
cfg = os.path.expanduser(cfg)
self.load_config(cfg)
parameters = [item for sub in self.__args.parameters for item in sub]
for param in parameters:
@ -216,15 +240,24 @@ 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"):
self.set(key, value)
if tmp.has_section("core"):
for key, value in tmp.items("core"):
self.set(key, value)
"""Returns a list of configured modules
@ -233,7 +266,11 @@ class Config(util.store.Store):
"""
def modules(self):
return [item for sub in self.__args.modules for item in sub]
list_of_modules = [item for sub in self.__args.modules for item in sub]
if list_of_modules == []:
list_of_modules = util.format.aslist(self.get('modules', []))
return list_of_modules
"""Returns the global update interval
@ -244,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
@ -278,7 +324,7 @@ class Config(util.store.Store):
"""
def theme(self):
return self.__args.theme
return self.__args.theme or self.get("theme") or "default"
"""Returns the configured iconset name
@ -289,14 +335,21 @@ class Config(util.store.Store):
def iconset(self):
return self.__args.iconset
"""Returns which modules should be hidden if their state is not warning/critical
"""Returns whether a module should be hidden if their state is not warning/critical
:return: list of modules to hide automatically
:rtype: list of strings
:return: True if module should be hidden automatically, False otherwise
:rtype: bool
"""
def autohide(self, name):
return name in self.__args.autohide
return name in self.__args.autohide or name in util.format.aslist(self.get("autohide", []))
"""Returns which modules should be hidden if they are in state error
:return: returns True if name should be hidden, False otherwise
:rtype: bool
"""
def errorhide(self, name):
return name in self.__args.errorhide
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -8,6 +8,13 @@ def register(event, callback, *args, **kwargs):
__callbacks.setdefault(event, []).append(cb)
def register_exclusive(event, callback, *args, **kwargs):
cb = callback
if args or kwargs:
cb = lambda: callback(*args, **kwargs)
__callbacks[event] = [cb]
def unregister(event):
if event in __callbacks:
del __callbacks[event]

View file

@ -10,6 +10,7 @@ MIDDLE_MOUSE = 2
RIGHT_MOUSE = 3
WHEEL_UP = 4
WHEEL_DOWN = 5
UPDATE = -1
def button_name(button):
@ -23,6 +24,8 @@ def button_name(button):
return "wheel-up"
if button == WHEEL_DOWN:
return "wheel-down"
if button == UPDATE:
return "update"
return "n/a"
@ -51,10 +54,13 @@ 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(event_id, 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(event_id, lambda event: __execute(event, cmd, wait))
core.event.register_exclusive(event_id, lambda event: __execute(event, cmd, wait))
def trigger(event):

View file

@ -1,5 +1,6 @@
import os
import importlib
import importlib.util
import logging
import threading
@ -17,6 +18,27 @@ except Exception as e:
log = logging.getLogger(__name__)
def import_user(module_short, config, theme):
usermod = os.path.expanduser("~/.config/bumblebee-status/modules/{}.py".format(module_short))
if os.path.exists(usermod):
if hasattr(importlib, "machinery"):
log.debug("importing {} from user via machinery".format(module_short))
mod = importlib.machinery.SourceFileLoader("modules.{}".format(module_short),
os.path.expanduser(usermod)).load_module()
return getattr(mod, "Module")(config, theme)
else:
log.debug("importing {} from user via importlib.util".format(module_short))
try:
spec = importlib.util.spec_from_file_location("modules.{}".format(module_short), usermod)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod.Module(config, theme)
except Exception as e:
spec = importlib.util.find_spec("modules.{}".format(module_short), usermod)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod.Module(config, theme)
raise ImportError("not found")
"""Loads a module by name
@ -33,20 +55,25 @@ def load(module_name, config=core.config.Config([]), theme=None):
error = None
module_short, alias = (module_name.split(":") + [module_name])[0:2]
config.set("__alias__", alias)
for namespace in ["core", "contrib"]:
try:
mod = importlib.import_module("modules.core.{}".format(module_short))
log.debug("importing {} from core".format(module_short))
return getattr(mod, "Module")(config, theme)
except ImportError as e:
try:
mod = importlib.import_module(
"modules.{}.{}".format(namespace, module_short)
)
log.debug(
"importing {} from {}.{}".format(module_short, namespace, module_short)
)
log.warning("failed to import {} from core: {}".format(module_short, e))
mod = importlib.import_module("modules.contrib.{}".format(module_short))
log.debug("importing {} from contrib".format(module_short))
return getattr(mod, "Module")(config, theme)
except ImportError as e:
log.debug("failed to import {}: {}".format(module_name, e))
error = e
log.fatal("failed to import {}: {}".format(module_name, error))
return Error(config=config, module=module_name, error=error)
try:
log.warning("failed to import {} from system: {}".format(module_short, e))
return import_user(module_short, config, theme)
except ImportError as e:
log.fatal("import failed: {}".format(e))
log.fatal("failed to import {}".format(module_short))
return Error(config=config, module=module_name, error="unable to load module")
class Module(core.input.Object):
@ -69,6 +96,8 @@ class Module(core.input.Object):
self.alias = self.__config.get("__alias__", None)
self.id = self.alias if self.alias else self.name
self.next_update = None
self.minimized = False
self.minimized = self.parameter("start-minimized", False)
self.theme = theme
@ -84,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.
@ -100,6 +138,8 @@ class Module(core.input.Object):
for prefix in [self.name, self.module_name, self.alias]:
value = self.__config.get("{}.{}".format(prefix, key), value)
if self.minimized:
value = self.__config.get("{}.minimized.{}".format(prefix, key), value)
return value
"""Set a parameter for this module
@ -123,7 +163,7 @@ class Module(core.input.Object):
def update_wrapper(self):
if self.background == True:
if self.__thread and self.__thread.isAlive():
if self.__thread and self.__thread.is_alive():
return # skip this update interval
self.__thread = threading.Thread(target=self.internal_update, args=(True,))
self.__thread.start()
@ -170,9 +210,9 @@ class Module(core.input.Object):
:rtype: bumblebee_status.widget.Widget
"""
def add_widget(self, full_text="", name=None):
def add_widget(self, full_text="", name=None, hidden=False):
widget_id = "{}::{}".format(self.name, len(self.widgets()))
widget = core.widget.Widget(full_text=full_text, name=name, widget_id=widget_id)
widget = core.widget.Widget(full_text=full_text, name=name, widget_id=widget_id, hidden=hidden)
self.widgets().append(widget)
widget.module = self
return widget
@ -265,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
@ -57,6 +58,9 @@ class block(object):
def set(self, key, value):
self.__attributes[key] = value
def get(self, key, default=None):
return self.__attributes.get(key, default)
def is_pango(self, attr):
if isinstance(attr, dict) and "pango" in attr:
return True
@ -91,9 +95,17 @@ class block(object):
assign(self.__attributes, result, "background", "bg")
if "full_text" in self.__attributes:
prefix = self.__pad(self.pangoize(self.__attributes.get("prefix")))
suffix = self.__pad(self.pangoize(self.__attributes.get("suffix")))
self.set("_prefix", prefix)
self.set("_suffix", suffix)
self.set("_raw", self.get("full_text"))
result["full_text"] = self.pangoize(result["full_text"])
result["full_text"] = self.__format(self.__attributes["full_text"])
if "min-width" in self.__attributes and "padding" in self.__attributes:
self.set("min-width", self.__format(self.get("min-width")))
for k in [
"name",
"instance",
@ -123,11 +135,8 @@ class block(object):
def __format(self, text):
if text is None:
return None
prefix = self.__pad(self.pangoize(self.__attributes.get("prefix")))
suffix = self.__pad(self.pangoize(self.__attributes.get("suffix")))
self.set("_prefix", prefix)
self.set("_suffix", suffix)
self.set("_raw", text)
prefix = self.get("_prefix")
suffix = self.get("_suffix")
return "{}{}{}".format(prefix, text, suffix)
@ -137,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
@ -158,18 +171,25 @@ class i3(object):
def toggle_minimize(self, event):
widget_id = event["instance"]
for module in self.__modules:
if module.widget(widget_id=widget_id) and util.format.asbool(module.parameter("minimize", False)) == True:
# this module can customly minimize
module.minimized = not module.minimized
return
if widget_id in self.__content:
self.__content[widget_id]["minimized"] = not self.__content[widget_id]["minimized"]
def draw(self, what, args=None):
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 {
@ -206,29 +226,57 @@ 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"]
state in widget.state() for state in ["warning", "critical", "no-autohide"]
):
continue
if module.hidden():
continue
if widget.hidden:
continue
if "critical" in widget.state() and self.__config.errorhide(widget.module.name):
continue
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
# TODO: only updates full text, not the state!?
def update(self, affected_modules=None, redraw_only=False):
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:
continue
if not affected_modules and module.next_update:
if now < module.next_update:
if now < module.next_update and not force:
continue
if not redraw_only:
@ -246,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

@ -7,16 +7,27 @@ import glob
import core.event
import util.algorithm
import util.xresources
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():
@ -89,13 +100,21 @@ class Theme(object):
try:
if isinstance(name, dict):
return name
result = {}
if name.lower() == "wal":
wal = self.__load_json("~/.cache/wal/colors.json")
result = {}
for field in ["special", "colors"]:
for key in wal.get(field, {}):
result[key] = wal[field][key]
return result
if name.lower() == "xresources":
for key in ("background", "foreground"):
result[key] = xresources.query(key)
for i in range(16):
key = color + str(i)
result[key] = xresources.query(key)
return result
except Exception as e:
log.error("failed to load colors: {}", e)

View file

@ -10,12 +10,13 @@ log = logging.getLogger(__name__)
class Widget(util.store.Store, core.input.Object):
def __init__(self, full_text="", name=None, widget_id=None):
def __init__(self, full_text="", name=None, widget_id=None, hidden=False):
super(Widget, self).__init__()
self.__full_text = full_text
self.module = None
self.name = name
self.id = widget_id or self.id
self.hidden = hidden
@property
def module(self):

View file

@ -1,12 +1,18 @@
"""get volume level or control it
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
@ -23,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
@ -59,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":
@ -76,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

@ -14,6 +14,7 @@ import threading
import core.module
import core.widget
import core.decorators
import core.input
import util.cli
@ -56,6 +57,8 @@ class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.updates))
self.__thread = None
core.input.register(self, button=core.input.RIGHT_MOUSE,
cmd=self.updates)
def updates(self, widget):
if widget.get("error"):
@ -65,7 +68,7 @@ class Module(core.module.Module):
)
def update(self):
if self.__thread and self.__thread.isAlive():
if self.__thread and self.__thread.is_alive():
return
self.__thread = threading.Thread(target=get_apt_check_info, args=(self,))

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
@ -54,7 +57,7 @@ class Module(core.module.Module):
def activate_layout(layout_path):
log.debug("activating layout")
log.debug(layout_path)
execute(layout_path)
execute(layout_path, ignore_errors=True)
def popup(self, widget):
"""Create Popup that allows the user to control their displays in one
@ -64,7 +67,7 @@ class Module(core.module.Module):
menu = popup.menu()
menu.add_menuitem(
"arandr",
callback=partial(execute, self.manager)
callback=partial(execute, self.manager, ignore_errors=True)
)
menu.add_separator()
@ -105,11 +108,12 @@ class Module(core.module.Module):
if count_on == 1:
log.info("attempted to turn off last display")
return
execute("{} --output {} --off".format(self.toggle_cmd, display))
execute("{} --output {} --off".format(self.toggle_cmd, display), ignore_errors=True)
else:
log.debug("toggling on {}".format(display))
execute(
"{} --output {} --auto".format(self.toggle_cmd, display)
"{} --output {} --auto".format(self.toggle_cmd, display),
ignore_errors=True
)
@staticmethod
@ -120,7 +124,7 @@ class Module(core.module.Module):
connected).
"""
displays = {}
for line in execute("xrandr -q").split("\n"):
for line in execute("xrandr -q", ignore_errors=True).split("\n"):
if "connected" not in line:
continue
is_on = bool(re.search(r"\d+x\d+\+(\d+)\+\d+", line))
@ -136,16 +140,19 @@ class Module(core.module.Module):
def _get_layouts():
"""Loads and parses the arandr screen layout scripts."""
layouts = {}
for filename in os.listdir(__screenlayout_dir__):
if fnmatch.fnmatch(filename, '*.sh'):
fullpath = os.path.join(__screenlayout_dir__, filename)
with open(fullpath, "r") as file:
for line in file:
s_line = line.strip()
if "xrandr" not in s_line:
continue
displays_in_file = Module._parse_layout(line)
layouts[filename] = displays_in_file
try:
for filename in os.listdir(__screenlayout_dir__):
if fnmatch.fnmatch(filename, '*.sh'):
fullpath = os.path.join(__screenlayout_dir__, filename)
with open(fullpath, "r") as file:
for line in file:
s_line = line.strip()
if "xrandr" not in s_line:
continue
displays_in_file = Module._parse_layout(line)
layouts[filename] = displays_in_file
except Exception as e:
log.error(str(e))
return layouts
@staticmethod

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,12 +36,13 @@ 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
)
if code == 0:
self.__packages = len(result.split("\n"))
self.__packages = len(result.strip().split("\n"))
elif code == 2:
self.__packages = 0
else:

View file

@ -0,0 +1 @@
arch-update.py

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

@ -0,0 +1 @@
battery-upower.py

View file

@ -1,4 +1,4 @@
"""Displays bluetooth status (Bluez). Left mouse click launches manager app,
"""Displays bluetooth status (Bluez). Left mouse click launches manager app `blueman-manager`,
right click toggles bluetooth. Needs dbus-send to toggle bluetooth state.
Parameters:
@ -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."""
@ -106,7 +102,7 @@ class Module(core.module.Module):
)
logging.debug("bt: toggling bluetooth")
util.cli.execute(cmd)
util.cli.execute(cmd, ignore_errors=True)
def state(self, widget):
"""Get current state."""

View file

@ -1,4 +1,4 @@
"""Displays bluetooth status. Left mouse click launches manager app,
"""Displays bluetooth status. Left mouse click launches manager app `blueman-manager`,
right click toggles bluetooth. Needs dbus-send to toggle bluetooth state and
python-dbus to count the number of connections
@ -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")
core.util.execute(cmd)
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

@ -2,6 +2,11 @@
"""Displays the brightness of a display
The following executables can be used if `use_acpi` is not enabled:
* brightnessctl
* light
* xbacklight
Parameters:
* brightness.step: The amount of increase/decrease on scroll in % (defaults to 2)
* brightness.device_path: The device path (defaults to /sys/class/backlight/intel_backlight), can contain wildcards (in this case, the first matching path will be used); This is only used when brightness.use_acpi is set to true

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

@ -2,6 +2,10 @@
"""Displays the current date and time with timezone options.
Requires the following python packages:
* tzlocal
* pytz
Parameters:
* datetimetz.format : strftime()-compatible formatting string
* datetimetz.timezone : IANA timezone name

View file

@ -5,8 +5,6 @@ some media control bindings.
Left click toggles pause, scroll up skips the current song, scroll
down returns to the previous song.
Requires the following library:
* subprocess
Parameters:
* deadbeef.format: Format string (defaults to '{artist} - {title}')
Available values are: {artist}, {title}, {album}, {length},
@ -114,7 +112,7 @@ class Module(core.module.Module):
self._song = ""
return
## perform the actual query -- these can be much more sophisticated
data = util.cli.execute(self.now_playing_tf + self._tf_format)
data = util.cli.execute(self.now_playing_tf + '"'+self._tf_format+'"')
self._song = data
def update_standard(self, widgets):

View file

@ -5,13 +5,8 @@
Requires the following executable:
* dnf
Parameters:
* dnf.interval: Time in minutes between two consecutive update checks (defaults to 30 minutes)
"""
import threading
import core.event
import core.module
import core.widget
@ -20,46 +15,13 @@ import core.decorators
import util.cli
def get_dnf_info(widget):
res = util.cli.execute("dnf updateinfo", ignore_errors=True)
security = 0
bugfixes = 0
enhancements = 0
other = 0
for line in res.split("\n"):
if not line.startswith(" "):
continue
elif "ecurity" in line:
for s in line.split():
if s.isdigit():
security += int(s)
elif "ugfix" in line:
for s in line.split():
if s.isdigit():
bugfixes += int(s)
elif "hancement" in line:
for s in line.split():
if s.isdigit():
enhancements += int(s)
else:
for s in line.split():
if s.isdigit():
other += int(s)
widget.set("security", security)
widget.set("bugfixes", bugfixes)
widget.set("enhancements", enhancements)
widget.set("other", other)
core.event.trigger("update", [widget.module.id], redraw_only=True)
class Module(core.module.Module):
@core.decorators.every(minutes=30)
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.updates))
self.background = True
def updates(self, widget):
result = []
for t in ["security", "bugfixes", "enhancements", "other"]:
@ -67,8 +29,38 @@ class Module(core.module.Module):
return "/".join(result)
def update(self):
thread = threading.Thread(target=get_dnf_info, args=(self.widget(),))
thread.start()
widget = self.widget()
res = util.cli.execute("dnf updateinfo", ignore_errors=True)
security = 0
bugfixes = 0
enhancements = 0
other = 0
for line in res.split("\n"):
if not line.startswith(" "):
continue
elif "ecurity" in line:
for s in line.split():
if s.isdigit():
security += int(s)
elif "ugfix" in line:
for s in line.split():
if s.isdigit():
bugfixes += int(s)
elif "hancement" in line:
for s in line.split():
if s.isdigit():
enhancements += int(s)
else:
for s in line.split():
if s.isdigit():
other += int(s)
widget.set("security", security)
widget.set("bugfixes", bugfixes)
widget.set("enhancements", enhancements)
widget.set("other", other)
def state(self, widget):
cnt = 0

View file

@ -0,0 +1,44 @@
# pylint: disable=C0111,R0903
"""Toggle dunst notifications using dunstctl.
When notifications are paused using this module dunst doesn't get killed and
you'll keep getting notifications on the background that will be displayed when
unpausing. This is specially useful if you're using dunst's scripting
(https://wiki.archlinux.org/index.php/Dunst#Scripting), which requires dunst to
be running. Scripts will be executed when dunst gets unpaused.
Requires:
* dunst v1.5.0+
contributed by `cristianmiranda <https://github.com/cristianmiranda>`_ - many thanks!
contributed by `joachimmathes <https://github.com/joachimmathes>`_ - many thanks!
"""
import core.module
import core.widget
import core.input
import util.cli
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(""))
core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.toggle_state)
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)
def state(self, widget):
return self.__states[self.__is_dunst_paused()]
def __is_dunst_paused(self):
result = util.cli.execute("dunstctl is-paused",
return_exitcode=True,
ignore_errors=True)
return result[1].rstrip() if result[0] == 0 else "unknown"

View file

@ -0,0 +1,113 @@
"""Display information about the currently running emerge process.
Requires the following executable:
* emerge
Parameters:
* emerge_status.format: Format string (defaults to '{current}/{total} {action} {category}/{pkg}')
This code is based on emerge_status module from p3status [1] original created by AnwariasEu.
[1] https://github.com/ultrabug/py3status/blob/master/py3status/modules/emerge_status.py
"""
import re
import copy
import core.module
import core.widget
import core.decorators
import util.cli
import util.format
class Module(core.module.Module):
@core.decorators.every(seconds=10)
def __init__(self, config, theme):
super().__init__(config, theme, [])
self.__format = self.parameter(
"format", "{current}/{total} {action} {category}/{pkg}"
)
self.__ret_default = {
"action": "",
"category": "",
"current": 0,
"pkg": "",
"total": 0,
}
def update(self):
response = {}
ret = copy.deepcopy(self.__ret_default)
if self.__emerge_running():
ret = self.__get_progress()
widget = self.widget("status")
if not widget:
widget = self.add_widget(name="status")
if ret["total"] == 0:
widget.full_text("emrg calculating...")
else:
widget.full_text(
" ".join(
self.__format.format(
current=ret["current"],
total=ret["total"],
action=ret["action"],
category=ret["category"],
pkg=ret["pkg"],
).split()
)
)
else:
self.clear_widgets()
def __emerge_running(self):
"""
Check if emerge is running.
Returns true if at least one instance of emerge is running.
"""
try:
util.cli.execute("pgrep emerge")
return True
except Exception:
return False
def __get_progress(self):
"""
Get current progress of emerge.
Returns a dict containing current and total value.
"""
input_data = []
ret = {}
# traverse emerge.log from bottom up to get latest information
last_lines = util.cli.execute("tail -50 /var/log/emerge.log")
input_data = last_lines.split("\n")
input_data.reverse()
for line in input_data:
if "*** terminating." in line:
# copy content of ret_default, not only the references
ret = copy.deepcopy(self.__ret_default)
break
else:
status_re = re.compile(
r"\((?P<cu>[\d]+) of (?P<t>[\d]+)\) "
r"(?P<a>[a-zA-Z/]+( [a-zA-Z]+)?) "
r"\((?P<ca>[\w\-]+)/(?P<p>[\w.]+)"
)
res = status_re.search(line)
if res is not None:
ret["action"] = res.group("a").lower()
ret["category"] = res.group("ca")
ret["current"] = res.group("cu")
ret["pkg"] = res.group("p")
ret["total"] = res.group("t")
break
return ret
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

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

@ -5,6 +5,8 @@ Displays the unread GitHub notifications count for a GitHub user using the follo
* https://developer.github.com/v3/activity/notifications/#notification-reasons
Uses `xdg-open` or `x-www-browser` to open web-pages.
Requires the following library:
* requests
@ -81,7 +83,6 @@ class Module(core.module.Module):
self.__label += "/".join(counts)
except Exception as err:
print(err)
self.__label = "n/a"
def __getUnreadNotificationsCountByReason(self, notifications, reason):

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

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Fetch hard drive temeperature data from a hddtemp daemon
"""Fetch hard drive temperature data from a hddtemp daemon
that runs on localhost and default port (7634)
contributed by `somospocos <https://github.com/somospocos>`_ - many thanks!

View file

@ -2,6 +2,9 @@
"""Displays the indicator status, for numlock, scrolllock and capslock
Requires the following executable:
* xset
Parameters:
* indicator.include: Comma-separated list of interface prefixes to include (defaults to 'numlock,capslock')
* indicator.signalstype: If you want the signali type color to be 'critical' or 'warning' (defaults to 'warning')

View file

@ -19,13 +19,13 @@ class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.current_layout))
core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.__next_keymap)
core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.next_keymap)
self.__current_layout = self.__get_current_layout()
def current_layout(self, _):
return self.__current_layout
def __next_keymap(self, event):
def next_keymap(self, event):
util.cli.execute("xkb-switch -n", ignore_errors=True)
def __get_current_layout(self):

View file

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

View file

@ -0,0 +1,85 @@
# pylint: disable=C0111,R0903
"""
Displays the message that's received via unix socket.
Parameters:
* messagereceiver : Unix socket address (e.g: /tmp/bumblebee_messagereceiver.sock)
Example:
The following examples assume that /tmp/bumblebee_messagereceiver.sock is used as unix socket address.
In order to send the string "I  bumblebee-status" to your status bar, use the following command:
echo -e '{"message":"I  bumblebee-status", "state": ""}' | socat unix-connect:/tmp/bumblebee_messagereceiver.sock STDIO
In order to highlight the text, the state variable can be used:
echo -e '{"message":"I  bumblebee-status", "state": "warning"}' | socat unix-connect:/tmp/bumblebee_messagereceiver.sock STDIO
contributed by `bbernhard <https://github.com/bbernhard>`_ - many thanks!
"""
import socket
import logging
import os
import json
import core.module
import core.widget
import core.input
class Module(core.module.Module):
@core.decorators.never
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.message))
self.background = True
self.__unix_socket_address = self.parameter("address", "")
self.__message = ""
self.__state = []
def message(self, widget):
return self.__message
def __read_data_from_socket(self):
while True:
try:
os.unlink(self.__unix_socket_address)
except OSError:
if os.path.exists(self.__unix_socket_address):
logging.exception(
"Couldn't bind to unix socket %s", self.__unix_socket_address
)
raise
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.bind(self.__unix_socket_address)
s.listen()
conn, _ = s.accept()
with conn:
while True:
data = conn.recv(1024)
if not data:
break
yield data.decode("utf-8")
def update(self):
try:
for received_data in self.__read_data_from_socket():
parsed_data = json.loads(received_data)
self.__message = parsed_data["message"]
self.__state = parsed_data["state"]
core.event.trigger("update", [self.id], redraw_only=True)
except json.JSONDecodeError:
logging.exception("Couldn't parse message")
except Exception:
logging.exception("Unexpected exception while reading from socket")
def state(self, widget):
return self.__state
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

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

@ -0,0 +1,128 @@
"""
A module to show the currently active network connection (ethernet or wifi) and connection strength if the connection is wireless.
Requires the Python netifaces package and iw installed on Linux.
A simpler take on nic and network_traffic. No extra config necessary!
"""
import util.cli
import util.format
import core.module
import core.widget
import core.input
import netifaces
import socket
class Module(core.module.Module):
@core.decorators.every(seconds=5)
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.network))
self.__is_wireless = False
self.__is_connected = False
self.__interface = None
self.__message = None
self.__signal = -110
# Get network information to display to the user
def network(self, widgets):
# Determine whether there is an internet connection
self.__is_connected = self.__attempt_connection()
# Attempt to extract a valid network interface device
try:
self.__interface = netifaces.gateways()["default"][netifaces.AF_INET][1]
except Exception:
self.__interface = None
# Check to see if the interface (if connected to the internet) is wireless
if self.__is_connected and self.__interface:
self.__is_wireless = self.__interface_is_wireless(self.__interface)
# setup message to send to the user
if not self.__is_connected or not self.__interface:
self.__message = "No connection"
elif not self.__is_wireless:
# Assuming that if user is connected via non-wireless means that it will be ethernet
self.__signal = -30
self.__message = "Ethernet"
else:
# We have a wireless connection
iw_dat = util.cli.execute("iwgetid")
has_ssid = "ESSID" in iw_dat
signal = self.__compute_signal(self.__interface)
# If signal is None, that means that we can't compute the default interface's signal strength
self.__signal = (
util.format.asint(signal, minimum=-110, maximum=-30) if signal else None
)
ssid = (
iw_dat[iw_dat.index(":") + 1 :].replace('"', "").strip()
if has_ssid
else "Unknown"
)
self.__message = self.__generate_wireles_message(ssid, self.__signal)
return self.__message
# State determined by signal strength
def state(self, widget):
if self.__compute_strength(self.__signal) < 50:
return "critical"
if self.__compute_strength(self.__signal) < 75:
return "warning"
return None
# manually done for better granularity / ease of parsing strength data
def __generate_wireles_message(self, ssid, signal):
computed_strength = self.__compute_strength(signal)
strength_str = str(computed_strength) if computed_strength else "?"
return "{} {}%".format(ssid, strength_str)
def __compute_strength(self, signal):
return int(100 * ((signal + 100) / 70.0)) if signal else None
# get signal strength in decibels/milliwat
def __compute_signal(self, interface):
# Get connection strength
cmd = "iwconfig {}".format(interface)
config_dat = " ".join(util.cli.execute(cmd).split())
config_tokens = config_dat.replace("=", " ").split()
# handle weird output
try:
signal = config_tokens[config_tokens.index("level") + 1]
except Exception:
signal = None
return signal
def __attempt_connection(self):
can_connect = False
try:
socket.create_connection(("1.1.1.1", 53))
can_connect = True
except Exception:
can_connect = False
return can_connect
def __interface_is_wireless(self, interface):
is_wireless = False
try:
with open("/proc/net/wireless", "r") as f:
is_wireless = interface in f.read()
f.close()
except Exception:
is_wireless = False
return is_wireless

View file

@ -38,8 +38,8 @@ class Module(core.module.Module):
try:
self._bandwidth = BandwidthInfo()
self._rate_recv = "?"
self._rate_sent = "?"
self._rate_recv = 0
self._rate_sent = 0
self._bytes_recv = self._bandwidth.bytes_recv()
self._bytes_sent = self._bandwidth.bytes_sent()
except Exception:
@ -97,9 +97,6 @@ class BandwidthInfo(object):
"""Return default active network adapter"""
gateway = netifaces.gateways()["default"]
if not gateway:
raise "No default gateway found"
return gateway[netifaces.AF_INET][1]
@classmethod

View file

@ -4,11 +4,15 @@
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.
"""
import core.module
@ -41,6 +45,9 @@ class Module(core.module.Module):
clockMem = ""
clockGpu = ""
fanspeed = ""
gpuUsagePct = ""
memIoPct = ""
memUsage = "not found"
for item in sp.split("\n"):
try:
key, val = item.split(":")
@ -61,10 +68,18 @@ class Module(core.module.Module):
name = val
elif key == "Fan Speed":
fanspeed = val.split(" ")[0]
elif title == "Utilization":
if key == "Gpu":
gpuUsagePct = val.split(" ")[0]
elif key == "Memory":
memIoPct = val.split(" ")[0]
except:
title = item.strip()
if totalMem and usedMem:
memUsage = int(int(usedMem) / int(totalMem) * 100)
str_format = self.parameter(
"format", "{name}: {temp}°C {mem_used}/{mem_total} MiB"
)
@ -76,6 +91,9 @@ class Module(core.module.Module):
clock_gpu=clockGpu,
clock_mem=clockMem,
fanspeed=fanspeed,
gpu_usage_pct=gpuUsagePct,
mem_io_pct=memIoPct,
mem_usage_pct=memUsage,
)

View file

@ -1,9 +1,12 @@
# pylint: disable=C0111,R0903
"""Displays the Octorpint status and the printer's bed/tools temperature in the status bar.
"""Displays the Octorrint status and the printer's bed/tools temperature in the status bar.
Left click opens a popup which shows the bed & tools temperatures and additionally a livestream of the webcam (if enabled).
Prerequisites:
* tk python library (usually python-tk or python3-tk, depending on your distribution)
Parameters:
* octoprint.address : Octoprint address (e.q: http://192.168.1.3)
* octoprint.apitoken : Octorpint API Token (can be obtained from the Octoprint Webinterface)
@ -82,8 +85,15 @@ class Module(core.module.Module):
core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.__show_popup)
def octoprint_status(self, widget):
if self.__octoprint_state == "Offline" or self.__octoprint_state == "Unknown":
return self.__octoprint_state
if (
self.__octoprint_state.startswith("Offline")
or self.__octoprint_state == "Unknown"
):
return (
(self.__octoprint_state[:25] + "...")
if len(self.__octoprint_state) > 25
else self.__octoprint_state
)
return (
self.__octoprint_state
+ " | B: "

View file

@ -0,0 +1,30 @@
"""Displays currently active gpu by optimus-manager
Requires the following packages:
* optimus-manager
"""
import core.module
import core.widget
import util.cli
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.output))
self.__gpumode = ""
def output(self, _):
return "GPU: {}".format(self.__gpumode)
def update(self):
cmd = "optimus-manager --print-mode"
output = util.cli.execute(cmd).strip()
if "intel" in output:
self.__gpumode = "Intel"
elif "nvidia" in output:
self.__gpumode = "Nvidia"
elif "amd" in output:
self.__gpumode = "AMD"

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

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

@ -5,57 +5,131 @@
Requires the following executable:
* playerctl
contributed by `smitajit <https://github.com/smitajit>`_ - many thanks!
Parameters:
* playerctl.format: Format string (defaults to '{{artist}} - {{title}} {{duration(position)}}/{{duration(mpris:length)}}').
The format string is passed to 'playerctl -f' as an argument. Read `the README <https://github.com/altdesktop/playerctl#printing-properties-and-metadata>`_ for more information.
* 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'.
* 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!
contributed by `smitajit <https://github.com/smitajit>`_ - many thanks!
"""
import core.module
import core.widget
import core.input
import util.cli
import util.format
import logging
class Module(core.module.Module):
def __init__(self,config , theme):
widgets = [
core.widget.Widget(name="playerctl.prev"),
core.widget.Widget(name="playerctl.main", full_text=self.description),
core.widget.Widget(name="playerctl.next"),
]
super(Module, self).__init__(config, theme , widgets)
def __init__(self, config, theme):
super(Module, self).__init__(config, theme, [])
core.input.register(widgets[0], button=core.input.LEFT_MOUSE,
cmd="playerctl previous")
core.input.register(widgets[1], button=core.input.LEFT_MOUSE,
cmd="playerctl play-pause")
core.input.register(widgets[2], button=core.input.LEFT_MOUSE,
cmd="playerctl next")
self.background = True
self._status = None
self._tags = None
self.__hide = util.format.asbool(self.parameter("hide", "false"));
self.__hidden = self.__hide
def description(self, widget):
return self._tags if self._tags else "..."
self.__layout = util.format.aslist(
self.parameter(
"layout", "playerctl.prev, playerctl.song, playerctl.pause, playerctl.next"
)
)
self.__cmd = "playerctl " + self.parameter("args", "") + " "
self.__format = self.parameter("format", "{{artist}} - {{title}} {{duration(position)}}/{{duration(mpris:length)}}")
widget_map = {}
for widget_name in self.__layout:
widget = self.add_widget(name=widget_name)
if widget_name == "playerctl.prev":
widget_map[widget] = {
"button": core.input.LEFT_MOUSE,
"cmd": self.__cmd + "previous",
}
elif widget_name == "playerctl.pause":
widget_map[widget] = {
"button": core.input.LEFT_MOUSE,
"cmd": self.__cmd + "play-pause",
}
elif widget_name == "playerctl.next":
widget_map[widget] = {
"button": core.input.LEFT_MOUSE,
"cmd": self.__cmd + "next",
}
elif widget_name == "playerctl.song":
widget_map[widget] = [
{
"button": core.input.LEFT_MOUSE,
"cmd": self.__cmd + "play-pause",
}, {
"button": core.input.WHEEL_UP,
"cmd": self.__cmd + "next",
}, {
"button": core.input.WHEEL_DOWN,
"cmd": self.__cmd + "previous",
}
]
else:
raise KeyError(
"The playerctl module does not have a {widget_name!r} widget".format(
widget_name=widget_name
)
)
for widget, callback_options in widget_map.items():
if isinstance(callback_options, dict):
core.input.register(widget, **callback_options)
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":
return None
return playback_status
except Exception as e:
logging.exception(e)
return None
def update(self):
self._load_song()
playback_status = self.status()
if not playback_status:
self.__hidden = self.__hide
else:
self.__hidden = False
for widget in self.widgets():
if playback_status:
if widget.name == "playerctl.pause":
if playback_status == "Playing":
widget.set("state", "playing")
elif playback_status == "Paused":
widget.set("state", "paused")
elif playback_status == "Stopped":
widget.set("state", "stopped")
else:
widget.set("state", "")
elif widget.name == "playerctl.next":
widget.set("state", "next")
elif widget.name == "playerctl.prev":
widget.set("state", "prev")
elif widget.name == "playerctl.song":
widget.full_text(self.__get_song())
else:
widget.set("state", "")
widget.full_text(" ")
def state(self, widget):
if widget.name == "playerctl.prev":
return "prev"
if widget.name == "playerctl.next":
return "next"
return self._status
def _load_song(self):
info = ""
def __get_song(self):
try:
status = util.cli.execute("playerctl status").lower()
info = util.cli.execute("playerctl metadata xesam:title")
except :
self._status = None
self._tags = None
return
self._status = status.split("\n")[0].lower()
self._tags = info.split("\n")[0][:20]
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
return str(util.cli.execute(self.__cmd + "metadata -f '" + self.__format + "'")).strip()
except Exception as e:
logging.exception(e)
return " "

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,72 @@
"""Displays the status of Gentoo portage operations.
Parameters:
* portage_status.logfile: logfile for portage (default is /var/log/emerge.log)
contributed by `andrewreisner <https://github.com/andrewreisner>`_ - many thanks!
"""
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.__logfile = self.parameter("logfile", "/var/log/emerge.log")
self.clear()
def clear(self):
self.__action = ""
self.__package = ""
self.__status = ""
def output(self, widget):
return " ".join(
[
atom
for atom in (self.__action, self.__package, self.__status)
if atom != ""
]
)
def state(self, widgets):
if self.__action == "":
return "idle"
return "active"
def update(self):
try:
with open(self.__logfile, "rb") as f:
f.seek(-2, os.SEEK_END)
while f.read(1) != b"\n":
f.seek(-2, os.SEEK_CUR)
last_line = f.readline().decode()
if "===" in last_line:
if "Unmerging..." in last_line:
self.__action = "Unmerging"
package_beg = last_line.find("(") + 1
package_end = last_line.find("-", last_line.find("/")) - 1
self.__package = last_line[package_beg : package_end + 1]
else: # merging
status_beg = last_line.find("(")
status_end = last_line.find(")")
self.__status = last_line[status_beg : status_end + 1]
package_beg = last_line.find("(", status_end) + 1
package_end = (
package_beg
+ last_line[package_beg:].find(
"-", last_line[package_beg:].find("/")
)
- 1
)
self.__package = last_line[package_beg : package_end + 1]
action_beg = status_end + 2
action_end = package_beg - 3
self.__action = last_line[action_beg : action_end + 1]
else:
self.clear()
except Exception:
self.clear()

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

@ -20,7 +20,8 @@ Parameters:
* prime.nvidiastring: String to use when nvidia is selected (defaults to 'intel')
* prime.intelstring: String to use when intel is selected (defaults to 'intel')
Requires the following executable:
Requires the following executables:
* sudo
* prime-select
contributed by `jeffeb3 <https://github.com/jeffeb3>`_ - many thanks!

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)
@ -101,7 +104,7 @@ class Module(core.module.Module):
def state(self, widget):
if self.__active:
return "copying"
return ["copying", "no-autohide"]
return "pending"

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

@ -0,0 +1,59 @@
"""Rofication indicator
https://github.com/DaveDavenport/Rofication
simple module to show an icon + the number of notifications stored in rofication
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
import core.widget
import core.decorators
import sys
import socket
class Module(core.module.Module):
@core.decorators.every(seconds=5)
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.full_text))
self.__critical = False
self.__numnotifications = 0
self.__regolith = self.parameter("regolith", False)
def full_text(self, widgets):
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client:
client.connect("/tmp/rofi_notification_daemon")
# below code will fetch two numbers in a list, e.g. ['22', '1']
# first is total number of notifications, second is number of critical notifications
if self.__regolith:
client.sendall(bytes("num\n", "utf-8"))
else:
client.sendall(bytes("num", "utf-8"))
val = client.recv(512)
val = val.decode("utf-8")
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
def state(self, widget):
# rofication doesn't really support the idea of seen vs unseen notifications
# marking a message as "seen" actually just sets its urgency to normal
# so, doing highlighting if any notifications are present
if self.__critical:
return ["critical"]
elif self.__numnotifications:
return ["warning"]
return []
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -31,14 +31,13 @@ class Module(core.module.Module):
orientation = curr_orient
break
widget = self.widget(display)
widget = self.widget(name=display)
if not widget:
widget = self.add_widget(full_text=display, name=display)
core.input.register(
widget, button=core.input.LEFT_MOUSE, cmd=self.__toggle
)
widget.set("orientation", orientation)
widgets.append(widget)
def state(self, widget):
return widget.get("orientation", "normal")

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

@ -4,6 +4,7 @@
"""Displays sensor temperature
Parameters:
* sensors.use_sensors: whether to use the sensors command
* sensors.path: path to temperature file (default /sys/class/thermal/thermal_zone0/temp).
* sensors.json: if set to 'true', interpret sensors.path as JSON 'path' in the output
of 'sensors -j' (i.e. <key1>/<key2>/.../<value>), for example, path could
@ -18,6 +19,7 @@ contributed by `mijoharas <https://github.com/mijoharas>`_ - many thanks!
"""
import re
import os
import json
import logging
@ -46,22 +48,25 @@ class Module(core.module.Module):
self._json = util.format.asbool(self.parameter("json", False))
self._freq = util.format.asbool(self.parameter("show_freq", True))
core.input.register(self, button=core.input.LEFT_MOUSE, cmd="xsensors")
self.determine_method()
self.use_sensors = self.determine_method()
def determine_method(self):
if util.format.asbool(self.parameter("use_sensors")) == True:
return True
if util.format.asbool(self.parameter("use_sensors")) == False:
return False
if self.parameter("path") != None and self._json == False:
self.use_sensors = False # use thermal zone
else:
# try to use output of sensors -u
try:
output = util.cli.execute("sensors -u")
self.use_sensors = True
log.debug("Sensors command available")
except FileNotFoundError as e:
log.info(
"Sensors command not available, using /sys/class/thermal/thermal_zone*/"
)
self.use_sensors = False
return False
# try to use output of sensors -u
try:
_ = util.cli.execute("sensors -u")
log.debug("Sensors command available")
return True
except FileNotFoundError as e:
log.info(
"Sensors command not available, using /sys/class/thermal/thermal_zone*/"
)
return False
def _get_temp_from_sensors(self):
if self._json == True:
@ -92,22 +97,31 @@ class Module(core.module.Module):
def get_temp(self):
if self.use_sensors:
temperature = self._get_temp_from_sensors()
log.debug("Retrieve temperature from sensors -u")
else:
try:
temperature = open(
self.parameter("path", "/sys/class/thermal/thermal_zone0/temp")
).read()[:2]
log.debug("retrieved temperature from /sys/class/")
# TODO: Iterate through all thermal zones to determine the correct one and use its value
# https://unix.stackexchange.com/questions/304845/discrepancy-between-number-of-cores-and-thermal-zones-in-sys-class-thermal
except IOError:
temperature = "unknown"
log.info("Can not determine temperature, please install lm-sensors")
return temperature
return self._get_temp_from_sensors()
try:
path = None
# use path provided by the user
if self.parameter("path") is not None:
path = self.parameter("path")
# find the thermal zone that provides cpu temperature
else:
for zone in os.listdir("/sys/class/thermal"):
if not zone.startswith("thermal_zone"):
continue
if open(f"/sys/class/thermal/{zone}/type").read().strip() != "x86_pkg_temp":
continue
path = f"/sys/class/thermal/{zone}/temp"
# use zone 0 as fallback
if path is None:
log.info("Can not determine temperature path, using thermal_zone0")
path = "/sys/class/thermal/thermal_zone0/temp"
log.debug(f"retrieving temperature from {path}")
# the values are t°C * 1000, so divide by 1000
return str(int(open(path).read()) / 1000)
except IOError:
log.info("Can not determine temperature, please install lm-sensors")
return "unknown"
def get_mhz(self):
mhz = None

View file

@ -41,19 +41,21 @@ 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:
self.__output = "please wait..."
self.__current_thread = threading.Thread()
# LMB and RMB will update output regardless of timer
core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.update)
core.input.register(self, button=core.input.RIGHT_MOUSE, cmd=self.update)
if self.parameter("scrolling.makewide") is None:
self.set("scrolling.makewide", False)
def set_output(self, value):
self.__output = value
core.event.trigger("update", [self.id], redraw_only=True)
@core.decorators.scrollable
def get_output(self, _):
return self.__output

View file

@ -4,12 +4,12 @@
when clicking on it.
For more than one shortcut, the commands and labels are strings separated by
a demiliter (; semicolon by default).
a delimiter (; semicolon by default).
For example in order to create two shortcuts labeled A and B with commands
cmdA and cmdB you could do:
./bumblebee-status -m shortcut -p shortcut.cmd='ls;ps' shortcut.label='A;B'
./bumblebee-status -m shortcut -p shortcut.cmd='firefox https://www.google.com;google-chrome https://google.com' shortcut.label='Google (Firefox);Google (Chrome)'
Parameters:
* shortcut.cmds : List of commands to execute

View file

@ -5,8 +5,12 @@
"""Displays HDD smart status of different drives or all drives
Requires the following executables:
* sudo
* smartctl
Parameters:
* smartstatus.display: how to display (defaults to 'combined', other choices: '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.
"""
@ -34,7 +38,7 @@ class Module(core.module.Module):
self.create_widgets()
def create_widgets(self):
if self.display == "combined":
if self.display == "combined" or self.display == "combined_singles":
widget = self.add_widget()
widget.set("device", "combined")
widget.set("assessment", self.combined())
@ -77,6 +81,8 @@ class Module(core.module.Module):
def combined(self):
for device in self.devices:
if self.display == "combined_singles" and device not in self.drives:
continue
result = self.smart(device)
if result == "Fail":
return "Fail"

View file

@ -0,0 +1,58 @@
"""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!
"""
import logging
import core.module
import core.widget
import core.decorators
import util.cli
class Module(core.module.Module):
@core.decorators.every(seconds=30)
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.utilization))
self.__battery = self.parameter("device", "")
self.background = True
self.__battery_status = ""
self.__error = False
if self.__battery != "":
self.__cmd = f"solaar show '{self.__battery}'"
else:
self.__cmd = "solaar show"
@property
def __format(self):
return self.parameter("format", "{}")
def utilization(self, widget):
return self.__format.format(self.__battery_status)
def update(self):
self.__error = False
code, result = util.cli.execute(
self.__cmd, ignore_errors=True, return_exitcode=True
)
if code == 0:
for line in result.split('\n'):
if line.count('Battery') > 0:
self.__battery_status = line.split(':')[1].strip()
else:
self.__error = True
logging.error(f"solaar exited with {code}: {result}")
def state(self, widget):
if self.__error:
return "warning"
return "okay"
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -9,7 +9,6 @@ an example.
Requires the following libraries:
* requests
* regex
Parameters:
* spaceapi.url: String representation of the api endpoint
@ -17,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

@ -8,10 +8,16 @@ Parameters:
Available values are: {album}, {title}, {artist}, {trackNumber}
* spotify.layout: Comma-separated list to change order of widgets (defaults to song, previous, pause, next)
Widget names are: spotify.song, spotify.prev, spotify.pause, spotify.next
* spotify.concise_controls: When enabled, allows spotify to be controlled from just the spotify.song widget.
Concise controls are: Left Click: Toggle Pause; Wheel Up: Next; Wheel Down; Previous.
* spotify.bus_name: String (defaults to `spotify`)
Available values: spotify, spotifyd
contributed by `yvesh <https://github.com/yvesh>`_ - many thanks!
added controls by `LtPeriwinkle <https://github.com/LtPeriwinkle>`_ - many thanks!
fixed icons and layout parameter by `gkeep <https://github.com/gkeep>`_ - many thanks!
"""
import sys
@ -23,31 +29,98 @@ import core.input
import core.decorators
import util.format
import logging
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, [])
self.__layout = self.parameter(
"layout",
util.format.aslist("spotify.song,spotify.prev,spotify.pause,spotify.next"),
self.background = True
self.__bus_name = self.parameter("bus_name", "spotify")
self.__layout = util.format.aslist(
self.parameter(
"layout", "spotify.song,spotify.prev,spotify.pause,spotify.next",
)
)
self.__bus = dbus.SessionBus()
self.__song = ""
self.__pause = ""
self.__format = self.parameter("format", "{artist} - {title}")
self.__cmd = "dbus-send --session --type=method_call --dest=org.mpris.MediaPlayer2.spotify \
if self.__bus_name == "spotifyd":
self.__cmd = "dbus-send --session --type=method_call --dest=org.mpris.MediaPlayer2.spotifyd \
/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player."
else:
self.__cmd = "dbus-send --session --type=method_call --dest=org.mpris.MediaPlayer2.spotify \
/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player."
widget_map = {}
for widget_name in self.__layout:
widget = self.add_widget(name=widget_name)
if widget_name == "spotify.prev":
widget_map[widget] = {
"button": core.input.LEFT_MOUSE,
"cmd": self.__cmd + "Previous",
}
widget.set("state", "prev")
elif widget_name == "spotify.pause":
widget_map[widget] = {
"button": core.input.LEFT_MOUSE,
"cmd": self.__cmd + "PlayPause",
}
elif widget_name == "spotify.next":
widget_map[widget] = {
"button": core.input.LEFT_MOUSE,
"cmd": self.__cmd + "Next",
}
widget.set("state", "next")
elif widget_name == "spotify.song":
if util.format.asbool(self.parameter("concise_controls", "false")):
widget_map[widget] = [
{
"button": core.input.LEFT_MOUSE,
"cmd": self.__cmd + "PlayPause",
}, {
"button": core.input.WHEEL_UP,
"cmd": self.__cmd + "Next",
}, {
"button": core.input.WHEEL_DOWN,
"cmd": self.__cmd + "Previous",
}
]
else:
raise KeyError(
"The spotify module does not have a {widget_name!r} widget".format(
widget_name=widget_name
)
)
# is there any reason the inputs can't be directly registered above?
for widget, callback_options in widget_map.items():
if isinstance(callback_options, dict):
core.input.register(widget, **callback_options)
elif isinstance(callback_options, list): # used by concise_controls
for opts in callback_options:
core.input.register(widget, **opts)
def hidden(self):
return self.string_song == ""
def __get_song(self):
bus = dbus.SessionBus()
spotify = bus.get_object(
"org.mpris.MediaPlayer2.spotify", "/org/mpris/MediaPlayer2"
)
@core.decorators.scrollable
def __get_song(self, widget):
bus = self.__bus
if self.__bus_name == "spotifyd":
spotify = bus.get_object(
"org.mpris.MediaPlayer2.spotifyd", "/org/mpris/MediaPlayer2"
)
else:
spotify = bus.get_object(
"org.mpris.MediaPlayer2.spotify", "/org/mpris/MediaPlayer2"
)
spotify_iface = dbus.Interface(spotify, "org.freedesktop.DBus.Properties")
props = spotify_iface.Get("org.mpris.MediaPlayer2.Player", "Metadata")
self.__song = self.__format.format(
@ -56,54 +129,36 @@ 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.clear_widgets()
self.__get_song()
if self.__bus_name == "spotifyd":
bus = self.__bus.get_object(
"org.mpris.MediaPlayer2.spotifyd", "/org/mpris/MediaPlayer2"
)
else:
bus = self.__bus.get_object(
"org.mpris.MediaPlayer2.spotify", "/org/mpris/MediaPlayer2"
)
widget_map = {}
for widget_name in self.__layout:
widget = self.add_widget(name=widget_name)
if widget_name == "spotify.prev":
widget_map[widget] = {
"button": core.input.LEFT_MOUSE,
"cmd": self.__cmd + "Previous",
}
widget.set("state", "prev")
elif widget_name == "spotify.pause":
widget_map[widget] = {
"button": core.input.LEFT_MOUSE,
"cmd": self.__cmd + "PlayPause",
}
for widget in self.widgets():
if widget.name == "spotify.pause":
playback_status = str(
dbus.Interface(dbus.SessionBus().get_object(
"org.mpris.MediaPlayer2.spotify", "/org/mpris/MediaPlayer2"), "org.freedesktop.DBus.Properties")
.Get("org.mpris.MediaPlayer2.Player", "PlaybackStatus")
dbus.Interface(
bus,
"org.freedesktop.DBus.Properties",
).Get("org.mpris.MediaPlayer2.Player", "PlaybackStatus")
)
if playback_status == "Playing":
widget.set("state", "playing")
else:
widget.set("state", "paused")
elif widget_name == "spotify.next":
widget_map[widget] = {
"button": core.input.LEFT_MOUSE,
"cmd": self.__cmd + "Next",
}
widget.set("state", "next")
elif widget_name == "spotify.song":
elif widget.name == "spotify.song":
widget.set("state", "song")
widget.full_text(self.__song)
else:
raise KeyError(
"The spotify module does not have a {widget_name!r} widget".format(
widget_name=widget_name
)
)
for widget, callback_options in widget_map.items():
core.input.register(widget, **callback_options)
widget.full_text(self.__get_song(widget))
except Exception:
except Exception as e:
self.__song = ""
@property

View file

@ -1,14 +1,13 @@
# -*- coding: UTF-8 -*-
# pylint: disable=C0111,R0903
"""Display a stock quote from worldtradingdata.com
Requires the following python packages:
* requests
"""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!
@ -25,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)
@ -32,37 +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"
)
return urllib.request.urlopen(url).read().strip()
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

@ -5,10 +5,11 @@
Requires the following python packages:
* requests
* suntime
* python-dateutil
Parameters:
* cpu.lat : Latitude of your location
* cpu.lon : Longitude of your location
* sun.lat : Latitude of your location
* sun.lon : Longitude of your location
(if none of those are set, location is determined automatically via location APIs)
@ -38,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))
@ -54,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')
@ -21,6 +21,9 @@ Parameters:
* system.suspend: specify a command for suspending (defaults to 'i3exit suspend')
* system.hibernate: specify a command for hibernating (defaults to 'i3exit hibernate')
Requirements:
tkinter (python3-tk package on debian based systems either you can install it as python package)
contributed by `bbernhard <https://github.com/bbernhard>`_ - many thanks!
"""
@ -69,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")
@ -93,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

@ -0,0 +1,89 @@
# pylint: disable=C0111,R0903
"""
Displays the unread emails count for one or more Thunderbird inboxes
Parameters:
* thunderbird.home: Absolute path of your .thunderbird directory (e.g.: /home/pi/.thunderbird)
* thunderbird.inboxes: Comma separated values for all MSF inboxes and their parent directory (account) (e.g.: imap.gmail.com/INBOX.msf,outlook.office365.com/Work.msf)
Tips:
* You can run the following command in order to list all your Thunderbird inboxes
find ~/.thunderbird -name '*.msf' | awk -F '/' '{print $(NF-1)"/"$(NF)}'
contributed by `cristianmiranda <https://github.com/cristianmiranda>`_ - many thanks!
"""
import core.module
import core.widget
import core.decorators
import core.input
import util.cli
class Module(core.module.Module):
@core.decorators.every(minutes=1)
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.thunderbird))
self.__total = 0
self.__label = ""
self.__inboxes = []
self.__home = self.parameter("home", "")
inboxes = self.parameter("inboxes", "")
if inboxes:
self.__inboxes = util.format.aslist(inboxes)
def thunderbird(self, _):
return str(self.__label)
def update(self):
try:
self.__total = 0
self.__label = ""
stream = self.__getThunderbirdStream()
unread = self.__getUnreadMessagesByInbox(stream)
counts = []
for inbox in self.__inboxes:
count = unread[inbox]
self.__total += int(count)
counts.append(count)
self.__label = "/".join(counts)
except Exception as err:
self.__label = err
def __getThunderbirdStream(self):
cmd = (
"find "
+ self.__home
+ " -name '*.msf' -exec grep -REo 'A2=[0-9]' {} + | grep"
)
for inbox in self.__inboxes:
cmd += " -e {}".format(inbox)
cmd += "| awk -F / '{print $(NF-1)\"/\"$(NF)}'"
return util.cli.execute(cmd, shell=True).strip().split("\n")
def __getUnreadMessagesByInbox(self, stream):
unread = {}
for line in stream:
entry = line.split(":A2=")
inbox = entry[0]
count = entry[1]
unread[inbox] = count
return unread
def state(self, widget):
if self.__total > 0:
return ["warning"]
# 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

@ -21,9 +21,10 @@ class Module(core.module.Module):
super().__init__(config, theme, core.widget.Widget(self.output))
self.__doc = os.path.expanduser(self.parameter("file", "~/Documents/todo.txt"))
self.__editor = self.parameter("editor", "xdg-open")
self.__todos = self.count_items()
core.input.register(
self, button=core.input.LEFT_MOUSE, cmd="xdg-open {}".format(self.__doc)
self, button=core.input.LEFT_MOUSE, cmd="{} {}".format(self.__editor, self.__doc)
)
def output(self, widget):
@ -39,11 +40,12 @@ class Module(core.module.Module):
def count_items(self):
try:
i = -1
i = 0
with open(self.__doc) as f:
for i, l in enumerate(f):
pass
return i + 1
for l in f.readlines():
if l.strip() != '':
i += 1
return i
except Exception:
return 0

View file

@ -0,0 +1,57 @@
# pylint: disable=C0111,R0903
"""Displays the number of todo items from an org-mode file
Parameters:
* todo_org.file: File to read TODOs from (defaults to ~/org/todo.org)
* 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>`
"""
import re
import os.path
import core.module
import core.widget
import core.input
from util.format import asbool
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.output))
self.__todo_regex = re.compile("^\\s*\\*+\\s*TODO")
self.__done_regex = re.compile("^\\s*\\*+\\s*DONE")
self.__doc = os.path.expanduser(
self.parameter("file", "~/org/todo.org")
)
self.__remaining = asbool(self.parameter("remaining", "False"))
self.__todo, self.__total = self.count_items()
core.input.register(
self,
button=core.input.LEFT_MOUSE,
cmd="emacs {}".format(self.__doc)
)
def output(self, widget):
if self.__remaining:
return "TODO: {}/{}".format(self.__todo, self.__total)
return "TODO: {}/{}".format(self.__total-self.__todo, self.__total)
def update(self):
self.__todo, self.__total = self.count_items()
def count_items(self):
todo, total = 0, 0
try:
with open(self.__doc, "r") as f:
for line in f:
if self.__todo_regex.match(line.upper()) is not None:
todo += 1
total += 1
elif self.__done_regex.match(line.upper()) is not None:
total += 1
return todo, total
except OSError:
return -1, -1
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

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

@ -2,6 +2,9 @@
"""Toggle twmn notifications.
Requires the following executable:
* systemctl
contributed by `Pseudonick47 <https://github.com/Pseudonick47>`_ - many thanks!
"""

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

@ -1,5 +1,8 @@
"""Displays info about zpools present on the system
Requires the following executable:
* sudo (if `zpool.sudo` is explicitly set to `true`)
Parameters:
* zpool.list: Comma-separated list of zpools to display info for. If empty, info for all zpools
is displayed. (Default: '')

View file

@ -2,13 +2,17 @@
"""Displays CPU utilization across all CPUs.
By default, opens `gnome-system-monitor` on left mouse click.
Requirements:
* the psutil Python module for the first three items from the list above
* gnome-system-monitor for default mouse click action
Parameters:
* cpu.warning : Warning threshold in % of CPU usage (defaults to 70%)
* cpu.critical: Critical threshold in % of CPU usage (defaults to 80%)
* cpu.format : Format string (defaults to '{:.01f}%')
* cpu.percpu : If set to true, show each individual cpu (defaults to false)
"""
import psutil
@ -17,12 +21,19 @@ import core.module
import core.widget
import core.input
import util.format
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.utilization))
self.widget().set("theme.minwidth", self._format.format(100.0 - 10e-20))
self._utilization = psutil.cpu_percent(percpu=False)
super().__init__(config, theme, [])
self._percpu = util.format.asbool(self.parameter("percpu", False))
for idx, cpu_perc in enumerate(self.cpu_utilization()):
widget = self.add_widget(name="cpu#{}".format(idx), full_text=self.utilization)
widget.set("utilization", cpu_perc)
widget.set("theme.minwidth", self._format.format(100.0 - 10e-20))
core.input.register(
self, button=core.input.LEFT_MOUSE, cmd="gnome-system-monitor"
)
@ -31,14 +42,19 @@ class Module(core.module.Module):
def _format(self):
return self.parameter("format", "{:.01f}%")
def utilization(self, _):
return self._format.format(self._utilization)
def utilization(self, widget):
return self._format.format(widget.get("utilization", 0.0))
def cpu_utilization(self):
tmp = psutil.cpu_percent(percpu=self._percpu)
return tmp if self._percpu else [tmp]
def update(self):
self._utilization = psutil.cpu_percent(percpu=False)
for idx, cpu_perc in enumerate(self.cpu_utilization()):
self.widgets()[idx].set("utilization", cpu_perc)
def state(self, _):
return self.threshold_state(self._utilization, 70, 80)
def state(self, widget):
return self.threshold_state(widget.get("utilization", 0.0), 70, 80)
# 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")
self._fmt = self.parameter("format", self.default_format())
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()
retval = datetime.datetime.now().strftime(self._fmt)
fmt = self.parameter("format", self.default_format())
retval = self.dtlibrary.datetime.now().strftime(fmt)
if hasattr(retval, "decode"):
return retval.decode(enc)
return retval

View file

@ -4,10 +4,11 @@
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}%)')
* disk.system: Unit system to use - SI (KB, MB, ...) or IEC (KiB, MiB, ...) (defaults to 'IEC')
"""
import os
@ -25,6 +26,7 @@ class Module(core.module.Module):
self._path = self.parameter("path", "/")
self._format = self.parameter("format", "{used}/{size} ({percent:05.02f}%)")
self._system = self.parameter("system", "IEC")
self._used = 0
self._left = 0
@ -38,9 +40,9 @@ class Module(core.module.Module):
)
def diskspace(self, widget):
used_str = util.format.byte(self._used)
size_str = util.format.byte(self._size)
left_str = util.format.byte(self._left)
used_str = util.format.byte(self._used, sys=self._system)
size_str = util.format.byte(self._size, sys=self._system)
left_str = util.format.byte(self._left, sys=self._system)
percent_str = self._percent
return self._format.format(

View file

@ -0,0 +1,56 @@
# pylint: disable=C0111,R0903
"""Shows when a key is pressed
Parameters:
* keys.keys: Comma-separated list of keys to monitor (defaults to "")
"""
import core.module
import core.widget
import core.decorators
import core.event
import util.format
from pynput.keyboard import Listener
NAMES = {
"Key.cmd": "cmd",
"Key.ctrl": "ctrl",
"Key.shift": "shift",
"Key.alt": "alt",
}
class Module(core.module.Module):
@core.decorators.never
def __init__(self, config, theme):
super().__init__(config, theme, [])
self._listener = Listener(on_press=self._key_press, on_release=self._key_release)
self._keys = util.format.aslist(self.parameter("keys", "Key.cmd,Key.ctrl,Key.alt,Key.shift"))
for k in self._keys:
self.add_widget(name=k, full_text=self._display_name(k), hidden=True)
self._listener.start()
def _display_name(self, key):
return NAMES.get(key, key)
def _key_press(self, key):
key = str(key)
if not key in self._keys: return
self.widget(key).hidden = False
core.event.trigger("update", [self.id], redraw_only=False)
def _key_release(self, key):
key = str(key)
if not key in self._keys: return
self.widget(key).hidden = True
core.event.trigger("update", [self.id], redraw_only=False)
def state(self, widget):
return widget.name
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -53,7 +53,7 @@ class Module(core.module.Module):
log.debug("group num: {}".format(xkb.group_num))
name = (
xkb.group_name
if util.format.asbool(self.parameter("showname"), False)
if util.format.asbool(self.parameter("showname", False))
else xkb.group_symbol
)
if self.__show_variant:

View file

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

View file

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

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