feature: Add battery widget (#120)

This commit is contained in:
Clement Tsang 2020-04-16 20:06:50 -04:00 committed by GitHub
parent 45e9ba1234
commit 163f6823a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 759 additions and 232 deletions

View file

@ -11,16 +11,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#58](https://github.com/ClementTsang/bottom/issues/58): I/O stats per process
- [#55](https://github.com/ClementTsang/bottom/issues/55): Battery monitoring widget
- [#114](https://github.com/ClementTsang/bottom/pull/114): Process state per process
### Changes
- Changed default colours for highlighted borders and table headers to cyan - this is mostly to deal with Powershell colour conflicts.
- Updated the widget type keyword list to accept the following keywords as existing types:
- `"memory"`
- `"network"`
- `"process"`
- `"processes"`
- `"temperature"`
### Bug Fixes
- Fixed `dd` not working on non-first entries.
- Fixed bug where a single empty row as a layout would crash without a proper warning.
The behaviour now errors out with a more helpful message.
## [0.3.0] - 2020-04-07
### Features

98
Cargo.lock generated
View file

@ -83,6 +83,22 @@ name = "base64"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "battery"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
"mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"uom 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.2.1"
@ -104,9 +120,10 @@ version = "0.3.0"
dependencies = [
"assert_cmd 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"backtrace 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)",
"battery 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"fern 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -178,6 +195,15 @@ name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "core-foundation"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "core-foundation"
version = "0.7.0"
@ -187,6 +213,11 @@ dependencies = [
"libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "core-foundation-sys"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "core-foundation-sys"
version = "0.7.0"
@ -245,14 +276,14 @@ dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"signal-hook 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossterm"
version = "0.17.2"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -260,7 +291,7 @@ dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"signal-hook 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -670,6 +701,11 @@ name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazycell"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.68"
@ -677,7 +713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lock_api"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -696,6 +732,14 @@ name = "macaddr"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "mach"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "mach"
version = "0.3.2"
@ -761,6 +805,18 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nix"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nix"
version = "0.17.0"
@ -837,16 +893,16 @@ dependencies = [
[[package]]
name = "parking_lot"
version = "0.10.0"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lock_api 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot_core 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parking_lot_core"
version = "0.7.0"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1192,6 +1248,15 @@ name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "uom"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "uom"
version = "0.27.0"
@ -1280,6 +1345,7 @@ dependencies = [
"checksum backtrace 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)" = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e"
"checksum backtrace-sys 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118"
"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
"checksum battery 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "36a698e449024a5d18994a815998bf5e2e4bc1883e35a7d7ba95b6b69ee45907"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
"checksum cassowary 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
@ -1289,14 +1355,16 @@ dependencies = [
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d"
"checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b"
"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
"checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
"checksum crossterm 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5750773d74a7dc612eac2ded3f55e9cdeeaa072210cd17c0192aedb48adb3618"
"checksum crossterm 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d2cfea8393092f9ffcfa5f1f88e2fa27b3cf5e47cb175e6b8c41ec914680b8e"
"checksum crossterm 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ccdd8ef63a44e821956c6a276eca0faaa889d6a067dfcdbd5bfe85dce3a1d250"
"checksum crossterm_winapi 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8777c700901e2d5b50c406f736ed6b8f9e43645c7e104ddb74f8bc42b8ae62f6"
"checksum crossterm_winapi 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "057b7146d02fb50175fd7dbe5158f6097f33d02831f43b4ee8ae4ddf67b68f5c"
"checksum darwin-libproc 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9fb90051930c9a0f09e585762152048e23ac74d20c10590ef7cf01c0343c3046"
@ -1338,10 +1406,12 @@ dependencies = [
"checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
"checksum libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0"
"checksum lock_api 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b"
"checksum lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum macaddr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "baee0bbc17ce759db233beb01648088061bf678383130602a298e6998eedb2d8"
"checksum mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1"
"checksum mach 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
@ -1349,6 +1419,7 @@ dependencies = [
"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
"checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229"
"checksum nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
"checksum normalize-line-endings 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
"checksum ntapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f26e041cd983acbc087e30fcba770380cfa352d0e392e175b2344ebaf7ea0602"
@ -1358,8 +1429,8 @@ dependencies = [
"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
"checksum once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b"
"checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518"
"checksum parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc"
"checksum parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1"
"checksum parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
"checksum parking_lot_core 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e136c1904604defe99ce5fd71a28d473fa60a12255d511aa78a9ddf11237aeb"
"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
"checksum platforms 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "feb3b2b1033b8a60b4da6ee470325f887758c95d5320f52f9ce0df055a55940e"
"checksum predicates 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "347a1b6f0b21e636bc9872fb60b83b8e185f6f5516298b8238699f7f9a531030"
@ -1403,6 +1474,7 @@ dependencies = [
"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum uom 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4cec796ec5f7ac557631709079168286056205c51c60aac33f51764bdc7b8dc4"
"checksum uom 0.27.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51fc04fb44bcb7806da71885872cb15d123b681e459a476ef8a0bab287bee0cd"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

View file

@ -22,6 +22,7 @@ lto = "fat"
codegen-units = 1
[dependencies]
battery = "0.7.5"
crossterm = "0.17"
chrono = "0.4.11"
clap = "2.33.0"

101
README.md
View file

@ -29,6 +29,7 @@ A cross-platform graphical process/system monitor with a customizable interface
- [CPU bindings](#cpu-bindings)
- [Process bindings](#process-bindings)
- [Process search bindings](#process-search-bindings)
- [Battery bindings](#battery-bindings)
- [Features](#features)
- [Process filtering](#process-filtering)
- [Zoom](#zoom)
@ -38,6 +39,7 @@ A cross-platform graphical process/system monitor with a customizable interface
- [Config flags](#config-flags)
- [Theming](#theming)
- [Layout](#layout)
- [Battery](#battery)
- [Compatibility](#compatibility)
- [Contribution](#contribution)
- [Bug reports and feature requests](#bug-reports-and-feature-requests)
@ -202,6 +204,15 @@ Run using `btm`.
| `Alt-c`/`F1` | Toggle matching case |
| `Alt-w`/`F2` | Toggle matching the entire word |
| `Alt-r`/`F3` | Toggle using regex |
| `Left` | Move cursor left |
| `Right` | Move cursor right |
#### Battery bindings
| | |
| ------- | -------------------------- |
| `Left` | Go to the next battery |
| `Right` | Go to the previous battery |
## Features
@ -289,23 +300,24 @@ The config file can be used to set custom colours for parts of the application u
Supported named colours are one of the following strings: `Reset, Black, Red, Green, Yellow, Blue, Magenta, Cyan, Gray, DarkGray, LightRed, LightGreen, LightYellow, LightBlue, LightMagenta, LightCyan, White`.
| Labels | Details | Example |
| ------------------------------- | ---------------------------------------------- | ------------------------------------------------------- |
| Table header colours | Colour of table headers | `table_header_color="255, 255, 255"` |
| CPU colour per core | Colour of each core. Read in order. | `cpu_core_colors=["#ffffff", "white", "255, 255, 255"]` |
| Average CPU colour | The average CPU color | `avg_cpu_color="White"` |
| RAM | The colour RAM will use | `ram_color="#ffffff"` |
| SWAP | The colour SWAP will use | `swap_color="#ffffff"` |
| RX | The colour rx will use | `rx_color="#ffffff"` |
| TX | The colour tx will use | `tx_color="#ffffff"` |
| Widget title colour | The colour of the label each widget has | `widget_title_color="#ffffff"` |
| Border colour | The colour of the border of unselected widgets | `border_color="#ffffff"` |
| Selected border colour | The colour of the border of selected widgets | `highlighted_border_color="#ffffff"` |
| Text colour | The colour of most text | `text_color="#ffffff"` |
| Graph colour | The colour of the lines and text of the graph | `graph_color="#ffffff"` |
| Cursor colour | The cursor's colour | `cursor_color="#ffffff"` |
| Selected text colour | The colour of text that is selected | `scroll_entry_text_color="#ffffff"` |
| Selected text background colour | The background colour of text that is selected | `scroll_entry_bg_color="#ffffff"` |
| Labels | Details | Example |
| ------------------------------- | ----------------------------------------------------- | ------------------------------------------------------- |
| Table header colours | Colour of table headers | `table_header_color="255, 255, 255"` |
| CPU colour per core | Colour of each core. Read in order. | `cpu_core_colors=["#ffffff", "white", "255, 255, 255"]` |
| Average CPU colour | The average CPU color | `avg_cpu_color="White"` |
| RAM | The colour RAM will use | `ram_color="#ffffff"` |
| SWAP | The colour SWAP will use | `swap_color="#ffffff"` |
| RX | The colour rx will use | `rx_color="#ffffff"` |
| TX | The colour tx will use | `tx_color="#ffffff"` |
| Widget title colour | The colour of the label each widget has | `widget_title_color="#ffffff"` |
| Border colour | The colour of the border of unselected widgets | `border_color="#ffffff"` |
| Selected border colour | The colour of the border of selected widgets | `highlighted_border_color="#ffffff"` |
| Text colour | The colour of most text | `text_color="#ffffff"` |
| Graph colour | The colour of the lines and text of the graph | `graph_color="#ffffff"` |
| Cursor colour | The cursor's colour | `cursor_color="#ffffff"` |
| Selected text colour | The colour of text that is selected | `scroll_entry_text_color="#ffffff"` |
| Selected text background colour | The background colour of text that is selected | `scroll_entry_bg_color="#ffffff"` |
| Battery bar colours | Colour used is based on percentage and no. of colours | `battery_colours=["green", "yellow", "red"]` |
#### Layout
@ -345,13 +357,14 @@ represents a _widget_. A widget is represented by having a `type` field set to a
The following `type` values are supported:
| | |
|---------|--------------------------|
| `cpu` | CPU chart and legend |
| `mem` | Memory chart |
| `proc` | Process table and search |
| `net` | Network chart and legend |
| `temp` | Temperature table |
| `disk` | Disk table |
| `empty` | An empty space |
| `"cpu"` | CPU chart and legend |
| `"mem", "memory"` | Memory chart |
| `"net", "network"` | Network chart and legend |
| `"proc", "process", "processes"` | Process table and search |
| `"temp", "temperature"` | Temperature table |
| `"disk"` | Disk table |
| `"empty"` | An empty space |
| `"batt", "battery"` | Battery statistics |
Each component of the layout accepts a `ratio` value. If this is not set, it defaults to 1.
@ -389,15 +402,45 @@ For an example, look at the [default config](./sample_configs/default_config.tom
and get the following CPU donut:
![CPU donut](./assets/cpu_layout.png)
### Battery
You can get battery statistics (charge, time to fill/discharge, and consumption in watts) via the battery widget.
Since this is only useful for devices like laptops, it is off by default. Currently, the only way to use it is to set it
as a widget via [layouts](#layout).
So with this slightly silly layout:
```toml
[[row]]
ratio=1
[[row.child]]
type="batt"
[[row]]
ratio=2
[[row.child]]
ratio=4
type="batt"
[[row.child]]
ratio=3
[[row.child.child]]
type="cpu"
[[row.child.child]]
type="batt"
```
You get this:
![Battery example](assets/battery.png)
### Compatibility
The current compatibility of widgets with operating systems from personal testing:
| OS | CPU | Memory | Disks | Temperature | Processes/Search | Networks |
| ------- | --- | ------ | ----- | ----------- | ---------------- | -------- |
| Linux | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Windows | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ |
| macOS | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| OS | CPU | Memory | Disks | Temperature | Processes/Search | Networks | Battery |
| ------- | --- | ------ | ----- | ----------- | ---------------- | -------- | -------------------------------------------- |
| Linux | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Windows | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ (seems to have issues with dual batteries) |
| macOS | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
## Contribution

BIN
assets/battery.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View file

@ -452,7 +452,6 @@ impl DiskState {
DiskState { widget_states }
}
}
pub struct BasicTableWidgetState {
// Since this is intended (currently) to only be used for ONE widget, that's
// how it's going to be written. If we want to allow for multiple of these,
@ -462,6 +461,21 @@ pub struct BasicTableWidgetState {
pub widget_id: i64,
}
#[derive(Default)]
pub struct BatteryWidgetState {
pub currently_selected_battery_index: usize,
}
pub struct BatteryState {
pub widget_states: HashMap<u64, BatteryWidgetState>,
}
impl BatteryState {
pub fn init(widget_states: HashMap<u64, BatteryWidgetState>) -> Self {
BatteryState { widget_states }
}
}
#[derive(TypedBuilder)]
pub struct App {
#[builder(default = false, setter(skip))]
@ -506,6 +520,7 @@ pub struct App {
pub proc_state: ProcState,
pub temp_state: TempState,
pub disk_state: DiskState,
pub battery_state: BatteryState,
pub basic_table_widget_state: Option<BasicTableWidgetState>,
@ -1089,33 +1104,50 @@ impl App {
pub fn on_left_key(&mut self) {
if !self.is_in_dialog() {
if let BottomWidgetType::ProcSearch = self.current_widget.widget_type {
let is_in_search_widget = self.is_in_search_widget();
if let Some(proc_widget_state) = self
.proc_state
.widget_states
.get_mut(&(self.current_widget.widget_id - 1))
{
if is_in_search_widget {
let prev_cursor = proc_widget_state.get_cursor_position();
proc_widget_state.search_walk_back(proc_widget_state.get_cursor_position());
if proc_widget_state.get_cursor_position() < prev_cursor {
let str_slice = &proc_widget_state
.process_search_state
.search_state
.current_search_query
[proc_widget_state.get_cursor_position()..prev_cursor];
match self.current_widget.widget_type {
BottomWidgetType::ProcSearch => {
let is_in_search_widget = self.is_in_search_widget();
if let Some(proc_widget_state) = self
.proc_state
.widget_states
.get_mut(&(self.current_widget.widget_id - 1))
{
if is_in_search_widget {
let prev_cursor = proc_widget_state.get_cursor_position();
proc_widget_state
.process_search_state
.search_state
.char_cursor_position -= UnicodeWidthStr::width(str_slice);
proc_widget_state
.process_search_state
.search_state
.cursor_direction = CursorDirection::LEFT;
.search_walk_back(proc_widget_state.get_cursor_position());
if proc_widget_state.get_cursor_position() < prev_cursor {
let str_slice = &proc_widget_state
.process_search_state
.search_state
.current_search_query
[proc_widget_state.get_cursor_position()..prev_cursor];
proc_widget_state
.process_search_state
.search_state
.char_cursor_position -= UnicodeWidthStr::width(str_slice);
proc_widget_state
.process_search_state
.search_state
.cursor_direction = CursorDirection::LEFT;
}
}
}
}
BottomWidgetType::Battery => {
if !self.canvas_data.battery_data.is_empty() {
if let Some(battery_widget_state) = self
.battery_state
.widget_states
.get_mut(&self.current_widget.widget_id)
{
if battery_widget_state.currently_selected_battery_index > 0 {
battery_widget_state.currently_selected_battery_index -= 1;
}
}
}
}
_ => {}
}
} else if self.delete_dialog_state.is_showing_dd && !self.delete_dialog_state.is_on_yes {
self.delete_dialog_state.is_on_yes = true;
@ -1124,34 +1156,53 @@ impl App {
pub fn on_right_key(&mut self) {
if !self.is_in_dialog() {
if let BottomWidgetType::ProcSearch = self.current_widget.widget_type {
let is_in_search_widget = self.is_in_search_widget();
if let Some(proc_widget_state) = self
.proc_state
.widget_states
.get_mut(&(self.current_widget.widget_id - 1))
{
if is_in_search_widget {
let prev_cursor = proc_widget_state.get_cursor_position();
proc_widget_state
.search_walk_forward(proc_widget_state.get_cursor_position());
if proc_widget_state.get_cursor_position() > prev_cursor {
let str_slice = &proc_widget_state
.process_search_state
.search_state
.current_search_query
[prev_cursor..proc_widget_state.get_cursor_position()];
match self.current_widget.widget_type {
BottomWidgetType::ProcSearch => {
let is_in_search_widget = self.is_in_search_widget();
if let Some(proc_widget_state) = self
.proc_state
.widget_states
.get_mut(&(self.current_widget.widget_id - 1))
{
if is_in_search_widget {
let prev_cursor = proc_widget_state.get_cursor_position();
proc_widget_state
.process_search_state
.search_state
.char_cursor_position += UnicodeWidthStr::width(str_slice);
proc_widget_state
.process_search_state
.search_state
.cursor_direction = CursorDirection::RIGHT;
.search_walk_forward(proc_widget_state.get_cursor_position());
if proc_widget_state.get_cursor_position() > prev_cursor {
let str_slice = &proc_widget_state
.process_search_state
.search_state
.current_search_query
[prev_cursor..proc_widget_state.get_cursor_position()];
proc_widget_state
.process_search_state
.search_state
.char_cursor_position += UnicodeWidthStr::width(str_slice);
proc_widget_state
.process_search_state
.search_state
.cursor_direction = CursorDirection::RIGHT;
}
}
}
}
BottomWidgetType::Battery => {
if !self.canvas_data.battery_data.is_empty() {
let battery_count = self.canvas_data.battery_data.len();
if let Some(battery_widget_state) = self
.battery_state
.widget_states
.get_mut(&self.current_widget.widget_id)
{
if battery_widget_state.currently_selected_battery_index
< battery_count - 1
{
battery_widget_state.currently_selected_battery_index += 1;
}
}
}
}
_ => {}
}
} else if self.delete_dialog_state.is_showing_dd && self.delete_dialog_state.is_on_yes {
self.delete_dialog_state.is_on_yes = false;

View file

@ -15,7 +15,9 @@
use std::time::Instant;
use std::vec::Vec;
use crate::data_harvester::{cpu, disks, mem, network, processes, temperature, Data};
use crate::data_harvester::{
battery_harvester, cpu, disks, mem, network, processes, temperature, Data,
};
pub type TimeOffset = f64;
pub type Value = f64;
@ -56,6 +58,7 @@ pub struct DataCollection {
pub io_harvest: disks::IOHarvest,
pub io_labels_and_prev: Vec<((u64, u64), (u64, u64))>,
pub temp_harvest: Vec<temperature::TempHarvest>,
pub battery_harvest: Vec<battery_harvester::BatteryHarvest>,
}
impl Default for DataCollection {
@ -73,6 +76,7 @@ impl Default for DataCollection {
io_harvest: disks::IOHarvest::default(),
io_labels_and_prev: Vec::default(),
temp_harvest: Vec::default(),
battery_harvest: Vec::default(),
}
}
}
@ -89,6 +93,7 @@ impl DataCollection {
self.io_harvest = disks::IOHarvest::default();
self.io_labels_and_prev = Vec::default();
self.temp_harvest = Vec::default();
self.battery_harvest = Vec::default();
}
pub fn set_frozen_time(&mut self) {
@ -115,22 +120,43 @@ impl DataCollection {
let mut new_entry = TimedData::default();
// Network
self.eat_network(&harvested_data, harvested_time, &mut new_entry);
if let Some(network) = &harvested_data.network {
self.eat_network(network, harvested_time, &mut new_entry);
}
// Memory and Swap
self.eat_memory_and_swap(&harvested_data, harvested_time, &mut new_entry);
if let Some(memory) = &harvested_data.memory {
if let Some(swap) = &harvested_data.swap {
self.eat_memory_and_swap(memory, swap, harvested_time, &mut new_entry);
}
}
// CPU
self.eat_cpu(&harvested_data, harvested_time, &mut new_entry);
if let Some(cpu) = &harvested_data.cpu {
self.eat_cpu(cpu, harvested_time, &mut new_entry);
}
// Temp
self.eat_temp(&harvested_data);
if let Some(temperature_sensors) = &harvested_data.temperature_sensors {
self.eat_temp(temperature_sensors);
}
// Disks
self.eat_disks(&harvested_data, harvested_time);
if let Some(disks) = &harvested_data.disks {
if let Some(io) = &harvested_data.io {
self.eat_disks(disks, io, harvested_time);
}
}
// Processes
self.eat_proc(&harvested_data);
if let Some(list_of_processes) = &harvested_data.list_of_processes {
self.eat_proc(list_of_processes);
}
// Battery
if let Some(list_of_batteries) = &harvested_data.list_of_batteries {
self.eat_battery(list_of_batteries);
}
// And we're done eating. Update time and push the new entry!
self.current_instant = harvested_time;
@ -138,12 +164,13 @@ impl DataCollection {
}
fn eat_memory_and_swap(
&mut self, harvested_data: &Data, harvested_time: Instant, new_entry: &mut TimedData,
&mut self, memory: &mem::MemHarvest, swap: &mem::MemHarvest, harvested_time: Instant,
new_entry: &mut TimedData,
) {
// Memory
let mem_percent = match harvested_data.memory.mem_total_in_mb {
let mem_percent = match memory.mem_total_in_mb {
0 => 0f64,
total => (harvested_data.memory.mem_used_in_mb as f64) / (total as f64) * 100.0,
total => (memory.mem_used_in_mb as f64) / (total as f64) * 100.0,
};
let mem_joining_pts = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(*time, last_pt.mem_data.0, harvested_time, mem_percent)
@ -154,10 +181,10 @@ impl DataCollection {
new_entry.mem_data = mem_pt;
// Swap
if harvested_data.swap.mem_total_in_mb > 0 {
let swap_percent = match harvested_data.swap.mem_total_in_mb {
if swap.mem_total_in_mb > 0 {
let swap_percent = match swap.mem_total_in_mb {
0 => 0f64,
total => (harvested_data.swap.mem_used_in_mb as f64) / (total as f64) * 100.0,
total => (swap.mem_used_in_mb as f64) / (total as f64) * 100.0,
};
let swap_joining_pt = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(*time, last_pt.swap_data.0, harvested_time, swap_percent)
@ -169,16 +196,17 @@ impl DataCollection {
}
// In addition copy over latest data for easy reference
self.memory_harvest = harvested_data.memory.clone();
self.swap_harvest = harvested_data.swap.clone();
self.memory_harvest = memory.clone();
self.swap_harvest = swap.clone();
}
fn eat_network(
&mut self, harvested_data: &Data, harvested_time: Instant, new_entry: &mut TimedData,
&mut self, network: &network::NetworkHarvest, harvested_time: Instant,
new_entry: &mut TimedData,
) {
// RX
let logged_rx_val = if harvested_data.network.rx as f64 > 0.0 {
(harvested_data.network.rx as f64).log(2.0)
let logged_rx_val = if network.rx as f64 > 0.0 {
(network.rx as f64).log(2.0)
} else {
0.0
};
@ -192,8 +220,8 @@ impl DataCollection {
new_entry.rx_data = rx_pt;
// TX
let logged_tx_val = if harvested_data.network.tx as f64 > 0.0 {
(harvested_data.network.tx as f64).log(2.0)
let logged_tx_val = if network.tx as f64 > 0.0 {
(network.tx as f64).log(2.0)
} else {
0.0
};
@ -207,47 +235,49 @@ impl DataCollection {
new_entry.tx_data = tx_pt;
// In addition copy over latest data for easy reference
self.network_harvest = harvested_data.network.clone();
self.network_harvest = network.clone();
}
fn eat_cpu(
&mut self, harvested_data: &Data, harvested_time: Instant, new_entry: &mut TimedData,
&mut self, cpu: &[cpu::CPUData], harvested_time: Instant, new_entry: &mut TimedData,
) {
// Note this only pre-calculates the data points - the names will be
// within the local copy of cpu_harvest. Since it's all sequential
// it probably doesn't matter anyways.
if let Some((time, last_pt)) = self.timed_data_vec.last() {
for (cpu, last_pt_data) in harvested_data.cpu.iter().zip(&last_pt.cpu_data) {
for (cpu, last_pt_data) in cpu.iter().zip(&last_pt.cpu_data) {
let cpu_joining_pts =
generate_joining_points(*time, last_pt_data.0, harvested_time, cpu.cpu_usage);
let cpu_pt = (cpu.cpu_usage, cpu_joining_pts);
new_entry.cpu_data.push(cpu_pt);
}
} else {
for cpu in harvested_data.cpu.iter() {
for cpu in cpu.iter() {
let cpu_pt = (cpu.cpu_usage, Vec::new());
new_entry.cpu_data.push(cpu_pt);
}
}
self.cpu_harvest = harvested_data.cpu.clone();
self.cpu_harvest = cpu.to_vec();
}
fn eat_temp(&mut self, harvested_data: &Data) {
fn eat_temp(&mut self, temperature_sensors: &[temperature::TempHarvest]) {
// TODO: [PO] To implement
self.temp_harvest = harvested_data.temperature_sensors.clone();
self.temp_harvest = temperature_sensors.to_vec();
}
fn eat_disks(&mut self, harvested_data: &Data, harvested_time: Instant) {
fn eat_disks(
&mut self, disks: &[disks::DiskHarvest], io: &disks::IOHarvest, harvested_time: Instant,
) {
// TODO: [PO] To implement
let time_since_last_harvest = harvested_time
.duration_since(self.current_instant)
.as_secs_f64();
for (itx, device) in harvested_data.disks.iter().enumerate() {
for (itx, device) in disks.iter().enumerate() {
if let Some(trim) = device.name.split('/').last() {
let io_device = harvested_data.io.get(trim);
let io_device = io.get(trim);
if let Some(io) = io_device {
let io_r_pt = io.read_bytes;
let io_w_pt = io.write_bytes;
@ -267,12 +297,16 @@ impl DataCollection {
}
}
self.disk_harvest = harvested_data.disks.clone();
self.io_harvest = harvested_data.io.clone();
self.disk_harvest = disks.to_vec();
self.io_harvest = io.clone();
}
fn eat_proc(&mut self, harvested_data: &Data) {
self.process_harvest = harvested_data.list_of_processes.clone();
fn eat_proc(&mut self, list_of_processes: &[processes::ProcessHarvest]) {
self.process_harvest = list_of_processes.to_vec();
}
fn eat_battery(&mut self, list_of_batteries: &[battery_harvester::BatteryHarvest]) {
self.battery_harvest = list_of_batteries.to_vec();
}
}

View file

@ -7,10 +7,13 @@ use std::collections::HashMap;
use sysinfo::{System, SystemExt};
use battery::{Battery, Manager};
use crate::app::layout_manager::UsedWidgets;
use futures::join;
pub mod battery_harvester;
pub mod cpu;
pub mod disks;
pub mod mem;
@ -20,44 +23,48 @@ pub mod temperature;
#[derive(Clone, Debug)]
pub struct Data {
pub cpu: cpu::CPUHarvest,
pub memory: mem::MemHarvest,
pub swap: mem::MemHarvest,
pub temperature_sensors: Vec<temperature::TempHarvest>,
pub network: network::NetworkHarvest,
pub list_of_processes: Vec<processes::ProcessHarvest>,
pub disks: Vec<disks::DiskHarvest>,
pub io: disks::IOHarvest,
pub last_collection_time: Instant,
pub cpu: Option<cpu::CPUHarvest>,
pub memory: Option<mem::MemHarvest>,
pub swap: Option<mem::MemHarvest>,
pub temperature_sensors: Option<Vec<temperature::TempHarvest>>,
pub network: Option<network::NetworkHarvest>,
pub list_of_processes: Option<Vec<processes::ProcessHarvest>>,
pub disks: Option<Vec<disks::DiskHarvest>>,
pub io: Option<disks::IOHarvest>,
pub list_of_batteries: Option<Vec<battery_harvester::BatteryHarvest>>,
}
impl Default for Data {
fn default() -> Self {
Data {
cpu: cpu::CPUHarvest::default(),
memory: mem::MemHarvest::default(),
swap: mem::MemHarvest::default(),
temperature_sensors: Vec::default(),
list_of_processes: Vec::default(),
disks: Vec::default(),
io: disks::IOHarvest::default(),
network: network::NetworkHarvest::default(),
last_collection_time: Instant::now(),
cpu: None,
memory: None,
swap: None,
temperature_sensors: None,
list_of_processes: None,
disks: None,
io: None,
network: None,
list_of_batteries: None,
}
}
}
impl Data {
pub fn first_run_cleanup(&mut self) {
self.io = disks::IOHarvest::default();
self.temperature_sensors = Vec::new();
self.list_of_processes = Vec::new();
self.disks = Vec::new();
self.io = None;
self.temperature_sensors = None;
self.list_of_processes = None;
self.disks = None;
self.memory = None;
self.swap = None;
self.cpu = None;
self.network.first_run_cleanup();
self.memory = mem::MemHarvest::default();
self.swap = mem::MemHarvest::default();
self.cpu = cpu::CPUHarvest::default();
if let Some(network) = &mut self.network {
network.first_run_cleanup();
}
}
}
@ -78,6 +85,8 @@ pub struct DataCollector {
total_tx: u64,
show_average_cpu: bool,
widgets_to_harvest: UsedWidgets,
battery_manager: Option<Manager>,
battery_list: Option<Vec<Battery>>,
}
impl Default for DataCollector {
@ -99,6 +108,8 @@ impl Default for DataCollector {
total_tx: 0,
show_average_cpu: false,
widgets_to_harvest: UsedWidgets::default(),
battery_manager: None,
battery_list: None,
}
}
}
@ -106,6 +117,19 @@ impl Default for DataCollector {
impl DataCollector {
pub fn init(&mut self) {
self.mem_total_kb = self.sys.get_total_memory();
if self.widgets_to_harvest.use_battery {
if let Ok(battery_manager) = Manager::new() {
if let Ok(batteries) = battery_manager.batteries() {
let battery_list: Vec<Battery> = batteries.filter_map(Result::ok).collect();
if !battery_list.is_empty() {
self.battery_list = Some(battery_list);
self.battery_manager = Some(battery_manager);
}
}
}
}
futures::executor::block_on(self.update_data());
std::thread::sleep(std::time::Duration::from_millis(250));
self.data.first_run_cleanup();
@ -148,7 +172,17 @@ impl DataCollector {
// CPU
if self.widgets_to_harvest.use_cpu {
self.data.cpu = cpu::get_cpu_data_list(&self.sys, self.show_average_cpu);
self.data.cpu = Some(cpu::get_cpu_data_list(&self.sys, self.show_average_cpu));
}
// Batteries
if let Some(battery_manager) = &self.battery_manager {
if let Some(battery_list) = &mut self.battery_list {
self.data.list_of_batteries = Some(battery_harvester::refresh_batteries(
&battery_manager,
battery_list,
));
}
}
if self.widgets_to_harvest.use_proc {
@ -186,7 +220,7 @@ impl DataCollector {
Ok(Vec::new())
}
} {
self.data.list_of_processes = process_list;
self.data.list_of_processes = Some(process_list);
}
}
@ -221,39 +255,29 @@ impl DataCollector {
// After async
if let Some(net_data) = net_data {
self.data.network = net_data;
self.total_rx = self.data.network.total_rx;
self.total_tx = self.data.network.total_tx;
self.total_rx = net_data.total_rx;
self.total_tx = net_data.total_tx;
self.data.network = Some(net_data);
}
if let Ok(memory) = mem_res {
if let Some(memory) = memory {
self.data.memory = memory;
}
self.data.memory = memory;
}
if let Ok(swap) = swap_res {
if let Some(swap) = swap {
self.data.swap = swap;
}
self.data.swap = swap;
}
if let Ok(disks) = disk_res {
if let Some(disks) = disks {
self.data.disks = disks;
}
self.data.disks = disks;
}
if let Ok(io) = io_res {
if let Some(io) = io {
self.data.io = io;
}
self.data.io = io;
}
if let Ok(temp) = temp_res {
if let Some(temp) = temp {
self.data.temperature_sensors = temp;
}
self.data.temperature_sensors = temp;
}
// Update time

View file

@ -0,0 +1,42 @@
use battery::{
units::{power::watt, ratio::percent, time::second, Time},
Battery, Manager,
};
#[derive(Debug, Clone)]
pub struct BatteryHarvest {
pub charge_percent: f64,
pub secs_until_full: Option<i64>,
pub secs_until_empty: Option<i64>,
pub power_consumption_rate_watts: f64,
}
fn convert_optional_time_to_optional_seconds(optional_time: Option<Time>) -> Option<i64> {
if let Some(time) = optional_time {
Some(f64::from(time.get::<second>()) as i64)
} else {
None
}
}
pub fn refresh_batteries(manager: &Manager, batteries: &mut [Battery]) -> Vec<BatteryHarvest> {
batteries
.iter_mut()
.filter_map(|battery| {
if manager.refresh(battery).is_ok() {
Some(BatteryHarvest {
secs_until_full: convert_optional_time_to_optional_seconds(
battery.time_to_full(),
),
secs_until_empty: convert_optional_time_to_optional_seconds(
battery.time_to_empty(),
),
charge_percent: f64::from(battery.state_of_charge().get::<percent>()),
power_consumption_rate_watts: f64::from(battery.energy_rate().get::<watt>()),
})
} else {
None
}
})
.collect::<Vec<_>>()
}

View file

@ -878,6 +878,7 @@ pub enum BottomWidgetType {
BasicMem,
BasicNet,
BasicTables,
Battery,
}
impl BottomWidgetType {
@ -924,12 +925,13 @@ impl std::str::FromStr for BottomWidgetType {
let lower_case = s.to_lowercase();
match lower_case.as_str() {
"cpu" => Ok(BottomWidgetType::Cpu),
"mem" => Ok(BottomWidgetType::Mem),
"net" => Ok(BottomWidgetType::Net),
"proc" => Ok(BottomWidgetType::Proc),
"temp" => Ok(BottomWidgetType::Temp),
"disk" => Ok(BottomWidgetType::Disk),
"mem" | "memory" => Ok(BottomWidgetType::Mem),
"net" | "network" => Ok(BottomWidgetType::Net),
"proc" | "process" | "processes" => Ok(BottomWidgetType::Proc),
"temp" | "temperature" => Ok(BottomWidgetType::Temp),
"disk" => Ok(BottomWidgetType::Disk),
"empty" => Ok(BottomWidgetType::Empty),
"battery" | "batt" => Ok(BottomWidgetType::Battery),
_ => Err(BottomError::ConfigError(format!(
"Invalid widget type: {}",
s
@ -946,4 +948,5 @@ pub struct UsedWidgets {
pub use_proc: bool,
pub use_disk: bool,
pub use_temp: bool,
pub use_battery: bool,
}

View file

@ -21,7 +21,7 @@ use crate::{
App,
},
constants::*,
data_conversion::{ConvertedCpuData, ConvertedProcessData},
data_conversion::{ConvertedBatteryData, ConvertedCpuData, ConvertedProcessData},
utils::error,
};
@ -51,6 +51,7 @@ pub struct DisplayableData {
pub mem_data: Vec<(f64, f64)>,
pub swap_data: Vec<(f64, f64)>,
pub cpu_data: Vec<ConvertedCpuData>,
pub battery_data: Vec<ConvertedBatteryData>,
}
/// Handles the canvas' state. TODO: [OPT] implement this.
@ -201,20 +202,6 @@ impl Painter {
}
}
// pub fn draw_specific_table<B: Backend>(
// &self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
// widget_selected: WidgetPosition,
// ) {
// match widget_selected {
// WidgetPosition::Process | WidgetPosition::ProcessSearch => {
// self.draw_process_and_search(f, app_state, draw_loc, draw_border)
// }
// WidgetPosition::Temp => self.draw_temp_table(f, app_state, draw_loc, draw_border),
// WidgetPosition::Disk => self.draw_disk_table(f, app_state, draw_loc, draw_border),
// _ => {}
// }
// }
// TODO: [FEATURE] Auto-resizing dialog sizes.
pub fn draw_data<B: Backend>(
&mut self, terminal: &mut Terminal<B>, app_state: &mut app::App,
@ -375,6 +362,12 @@ impl Painter {
true,
app_state.current_widget.widget_id - 1,
),
Battery => self.draw_battery_display(
&mut f,
app_state,
rect[0],
app_state.current_widget.widget_id,
),
_ => {}
}
} else if app_state.app_config_fields.use_basic_mode {
@ -564,6 +557,9 @@ impl Painter {
true,
widget.widget_id,
),
Battery => {
self.draw_battery_display(f, app_state, *widget_draw_loc, widget.widget_id)
}
_ => {}
}
}

View file

@ -24,6 +24,8 @@ pub struct CanvasColours {
pub text_style: Style,
pub widget_title_style: Style,
pub graph_style: Style,
// Full, Medium, Low
pub battery_bar_styles: Vec<Style>,
}
impl Default for CanvasColours {
@ -48,6 +50,14 @@ impl Default for CanvasColours {
text_style: Style::default().fg(text_colour),
widget_title_style: Style::default().fg(text_colour),
graph_style: Style::default().fg(text_colour),
battery_bar_styles: vec![
Style::default().fg(Color::Red),
Style::default().fg(Color::Yellow),
Style::default().fg(Color::Yellow),
Style::default().fg(Color::Green),
Style::default().fg(Color::Green),
Style::default().fg(Color::Green),
],
}
}
}
@ -150,4 +160,20 @@ impl CanvasColours {
self.graph_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_battery_colours(&mut self, colours: &[String]) -> error::Result<()> {
if colours.is_empty() {
Err(error::BottomError::ConfigError(
"Battery colour list must have at least one colour!".to_string(),
))
} else {
let generated_colours: Result<Vec<_>, _> = colours
.iter()
.map(|colour| get_style_from_config(colour))
.collect();
self.battery_bar_styles = generated_colours?;
Ok(())
}
}
}

View file

@ -186,7 +186,7 @@ fn convert_name_to_color(color_name: &str) -> error::Result<Color> {
Err(error::BottomError::GenericError(format!(
"Color {} is not a supported config colour. bottom supports the following named colours as strings: \
Reset, Black, Red, Green, Yellow, Blue, Magenta, Cyan, Gray, DarkGray, LightRed, LightGreen, \
LightYellow, LightBlue, LightMagenta, LightCyan, White",
LightYellow, LightBlue, LightMagenta, LightCyan, White",
color_name
)))
}

View file

@ -1,4 +1,5 @@
pub mod basic_table_arrows;
pub mod battery_display;
pub mod cpu_basic;
pub mod cpu_graph;
pub mod disk_table;
@ -10,6 +11,7 @@ pub mod process_table;
pub mod temp_table;
pub use basic_table_arrows::BasicTableArrows;
pub use battery_display::BatteryDisplayWidget;
pub use cpu_basic::CpuBasicWidget;
pub use cpu_graph::CpuGraphWidget;
pub use disk_table::DiskTableWidget;

View file

@ -0,0 +1,145 @@
use std::cmp::max;
use crate::{
app::App,
canvas::{drawing_utils::calculate_basic_use_bars, Painter},
};
use tui::{
backend::Backend,
layout::{Constraint, Rect},
terminal::Frame,
widgets::{Block, Borders, Paragraph, Row, Table, Tabs, Text, Widget},
};
pub trait BatteryDisplayWidget {
fn draw_battery_display<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
);
}
impl BatteryDisplayWidget for Painter {
fn draw_battery_display<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
if let Some(battery_widget_state) =
app_state.battery_state.widget_states.get_mut(&widget_id)
{
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Battery ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title = format!(
" Battery ─{}─ Esc to go back ",
"".repeat(repeat_num as usize)
);
result_title
} else {
" Battery ".to_string()
};
let border_and_title_style = if app_state.current_widget.widget_id == widget_id {
self.colours.highlighted_border_style
} else {
self.colours.border_style
};
let battery_block = Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
border_and_title_style
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(border_and_title_style);
if let Some(battery_details) = app_state
.canvas_data
.battery_data
.get(battery_widget_state.currently_selected_battery_index)
{
// Assuming a 50/50 split in width
let bar_length = max(0, (draw_loc.width as i64 - 2) / 2 - 8) as usize;
let charge_percentage = battery_details.charge_percentage;
let num_bars = calculate_basic_use_bars(charge_percentage, bar_length);
let bars = format!(
"[{}{}{:3.0}%]",
"|".repeat(num_bars),
" ".repeat(bar_length - num_bars),
charge_percentage,
);
let battery_items = vec![
["Charge %", &bars],
["Consumption", &battery_details.watt_consumption],
if let Some(duration_until_full) = &battery_details.duration_until_full {
["Time to full", duration_until_full]
} else if let Some(duration_until_empty) = &battery_details.duration_until_empty
{
["Time to empty", duration_until_empty]
} else {
["Time to full/empty", "N/A"]
},
];
let battery_rows = battery_items.iter().enumerate().map(|(itx, item)| {
Row::StyledData(
item.iter(),
if itx == 0 {
let colour_index = ((charge_percentage
* self.colours.battery_bar_styles.len() as f64
- 1.0)
/ 100.0)
.floor() as usize;
*self
.colours
.battery_bar_styles
.get(colour_index)
.unwrap_or(&self.colours.text_style)
} else {
self.colours.text_style
},
)
});
// Draw
Table::new([""].iter(), battery_rows)
.block(battery_block)
.header_style(self.colours.table_header_style)
.widths([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.render(f, draw_loc);
} else {
Paragraph::new(
[Text::Styled(
"No data found for this battery".into(),
self.colours.text_style,
)]
.iter(),
)
.block(battery_block)
.render(f, draw_loc);
}
// if app_state.canvas_data.battery_data.len() > 1 {
Tabs::default()
.block(battery_block)
.titles(
(app_state
.canvas_data
.battery_data
.iter()
.map(|battery| &battery.battery_name))
.collect::<Vec<_>>()
.as_ref(),
)
.divider(tui::symbols::line::VERTICAL)
.style(self.colours.text_style)
.highlight_style(self.colours.currently_selected_text_style)
.select(battery_widget_state.currently_selected_battery_index)
.render(f, draw_loc);
// }
}
}
}

View file

@ -8,7 +8,7 @@ use tui::{
};
use crate::{
app::{self},
app,
canvas::{
drawing_utils::{get_start_position, get_variable_intrinsic_widths},
Painter,

View file

@ -199,6 +199,9 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##"
# Represents the colour of the lines and text of the graph.
#graph_color="Gray"
# Represents the colours of the battery based on charge
#battery_colors = ["red", "yellow", "yellow", "green", "green", "green"]
##########################################################
# Layout - layouts follow a pattern like this:

View file

@ -14,6 +14,15 @@ use crate::{
type Point = (f64, f64);
#[derive(Default, Debug)]
pub struct ConvertedBatteryData {
pub battery_name: String,
pub charge_percentage: f64,
pub watt_consumption: String,
pub duration_until_full: Option<String>,
pub duration_until_empty: Option<String>,
}
#[derive(Default, Debug)]
pub struct ConvertedNetworkData {
pub rx: Vec<Point>,
@ -413,3 +422,50 @@ pub fn convert_process_data(
(single_list, grouped_list)
}
pub fn convert_battery_harvest(
current_data: &data_farmer::DataCollection,
) -> Vec<ConvertedBatteryData> {
current_data
.battery_harvest
.iter()
.enumerate()
.map(|(itx, battery_harvest)| ConvertedBatteryData {
battery_name: format!("Battery {}", itx),
charge_percentage: battery_harvest.charge_percent,
watt_consumption: format!("{:.2}W", battery_harvest.power_consumption_rate_watts),
duration_until_empty: if let Some(secs_till_empty) = battery_harvest.secs_until_empty {
let time = chrono::Duration::seconds(secs_till_empty);
let num_minutes = time.num_minutes() - time.num_hours() * 60;
let num_seconds = time.num_seconds() - time.num_minutes() * 60;
Some(format!(
"{} hour{}, {} minute{}, {} second{}",
time.num_hours(),
if time.num_hours() == 1 { "" } else { "s" },
num_minutes,
if num_minutes == 1 { "" } else { "s" },
num_seconds,
if num_seconds == 1 { "" } else { "s" },
))
} else {
None
},
duration_until_full: if let Some(secs_till_full) = battery_harvest.secs_until_full {
let time = chrono::Duration::seconds(secs_till_full);
let num_minutes = time.num_minutes() - time.num_hours() * 60;
let num_seconds = time.num_seconds() - time.num_minutes() * 60;
Some(format!(
"{} hour{}, {} minute{}, {} second{}",
time.num_hours(),
if time.num_hours() == 1 { "" } else { "s" },
num_minutes,
if num_minutes == 1 { "" } else { "s" },
num_seconds,
if num_seconds == 1 { "" } else { "s" },
))
} else {
None
},
})
.collect()
}

View file

@ -228,6 +228,12 @@ fn main() -> error::Result<()> {
app.canvas_data.grouped_process_data = grouped;
update_all_process_lists(&mut app);
}
// Battery
if app.used_widgets.use_battery {
app.canvas_data.battery_data =
convert_battery_harvest(&app.data_collection);
}
}
}
BottomEvent::Clean => {
@ -491,6 +497,10 @@ fn generate_config_colours(config: &Config, painter: &mut canvas::Painter) -> er
if let Some(graph_color) = &colours.graph_color {
painter.colours.set_graph_colour(graph_color)?;
}
if let Some(battery_colors) = &colours.battery_colors {
painter.colours.set_battery_colours(battery_colors)?;
}
}
Ok(())

View file

@ -3,11 +3,7 @@ use std::collections::{HashMap, HashSet};
use std::time::Instant;
use crate::{
app::{
data_harvester, layout_manager::*, App, AppConfigFields, BasicTableWidgetState, CpuState,
CpuWidgetState, DiskState, DiskWidgetState, MemState, MemWidgetState, NetState,
NetWidgetState, ProcState, ProcWidgetState, TempState, TempWidgetState,
},
app::{layout_manager::*, *},
constants::*,
utils::error::{self, BottomError},
};
@ -65,12 +61,14 @@ pub struct ConfigColours {
pub selected_bg_color: Option<String>,
pub widget_title_color: Option<String>,
pub graph_color: Option<String>,
pub battery_colors: Option<Vec<String>>,
}
pub fn build_app(
matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout,
default_widget_id: u64,
) -> error::Result<App> {
use BottomWidgetType::*;
let autohide_time = get_autohide_time(&matches, &config);
let default_time_value = get_default_time_value(&matches, &config)?;
let use_basic_mode = get_use_basic_mode(&matches, &config);
@ -88,6 +86,7 @@ pub fn build_app(
let mut proc_state_map: HashMap<u64, ProcWidgetState> = HashMap::new();
let mut temp_state_map: HashMap<u64, TempWidgetState> = HashMap::new();
let mut disk_state_map: HashMap<u64, DiskWidgetState> = HashMap::new();
let mut battery_state_map: HashMap<u64, BatteryWidgetState> = HashMap::new();
let autohide_timer = if autohide_time {
Some(Instant::now())
@ -97,9 +96,8 @@ pub fn build_app(
let (default_widget_type_option, _) = get_default_widget_and_count(matches, config)?;
let mut initial_widget_id: u64 = default_widget_id;
let mut initial_widget_type = BottomWidgetType::Proc;
let mut initial_widget_type = Proc;
let is_custom_layout = config.row.is_some();
let mut used_widget_set = HashSet::new();
for row in &widget_layout.rows {
@ -110,22 +108,22 @@ pub fn build_app(
if let Some(default_widget_type) = &default_widget_type_option {
if !is_custom_layout || use_basic_mode {
match widget.widget_type {
BottomWidgetType::BasicCpu => {
if let BottomWidgetType::Cpu = *default_widget_type {
BasicCpu => {
if let Cpu = *default_widget_type {
initial_widget_id = widget.widget_id;
initial_widget_type = BottomWidgetType::Cpu;
initial_widget_type = Cpu;
}
}
BottomWidgetType::BasicMem => {
if let BottomWidgetType::Mem = *default_widget_type {
BasicMem => {
if let Mem = *default_widget_type {
initial_widget_id = widget.widget_id;
initial_widget_type = BottomWidgetType::Cpu;
initial_widget_type = Cpu;
}
}
BottomWidgetType::BasicNet => {
if let BottomWidgetType::Net = *default_widget_type {
BasicNet => {
if let Net = *default_widget_type {
initial_widget_id = widget.widget_id;
initial_widget_type = BottomWidgetType::Cpu;
initial_widget_type = Cpu;
}
}
_ => {
@ -141,25 +139,25 @@ pub fn build_app(
used_widget_set.insert(widget.widget_type.clone());
match widget.widget_type {
BottomWidgetType::Cpu => {
Cpu => {
cpu_state_map.insert(
widget.widget_id,
CpuWidgetState::init(default_time_value, autohide_timer),
);
}
BottomWidgetType::Mem => {
Mem => {
mem_state_map.insert(
widget.widget_id,
MemWidgetState::init(default_time_value, autohide_timer),
);
}
BottomWidgetType::Net => {
Net => {
net_state_map.insert(
widget.widget_id,
NetWidgetState::init(default_time_value, autohide_timer),
);
}
BottomWidgetType::Proc => {
Proc => {
proc_state_map.insert(
widget.widget_id,
ProcWidgetState::init(
@ -170,13 +168,18 @@ pub fn build_app(
),
);
}
BottomWidgetType::Disk => {
Disk => {
disk_state_map.insert(widget.widget_id, DiskWidgetState::init());
}
BottomWidgetType::Temp => {
Temp => {
temp_state_map.insert(widget.widget_id, TempWidgetState::init());
}
_ => {}
Battery => {
battery_state_map
.insert(widget.widget_id, BatteryWidgetState::default());
}
Empty | BasicCpu | BasicMem | BasicNet | BasicTables | ProcSearch
| CpuLegend => {}
}
}
}
@ -185,15 +188,13 @@ pub fn build_app(
let basic_table_widget_state = if use_basic_mode {
Some(match initial_widget_type {
BottomWidgetType::Proc | BottomWidgetType::Disk | BottomWidgetType::Temp => {
BasicTableWidgetState {
currently_displayed_widget_type: initial_widget_type,
currently_displayed_widget_id: initial_widget_id,
widget_id: 100,
}
}
Proc | Disk | Temp => BasicTableWidgetState {
currently_displayed_widget_type: initial_widget_type,
currently_displayed_widget_id: initial_widget_id,
widget_id: 100,
},
_ => BasicTableWidgetState {
currently_displayed_widget_type: BottomWidgetType::Proc,
currently_displayed_widget_type: Proc,
currently_displayed_widget_id: DEFAULT_WIDGET_ID,
widget_id: 100,
},
@ -218,15 +219,13 @@ pub fn build_app(
};
let used_widgets = UsedWidgets {
use_cpu: used_widget_set.get(&BottomWidgetType::Cpu).is_some()
|| used_widget_set.get(&BottomWidgetType::BasicCpu).is_some(),
use_mem: used_widget_set.get(&BottomWidgetType::Mem).is_some()
|| used_widget_set.get(&BottomWidgetType::BasicMem).is_some(),
use_net: used_widget_set.get(&BottomWidgetType::Net).is_some()
|| used_widget_set.get(&BottomWidgetType::BasicNet).is_some(),
use_proc: used_widget_set.get(&BottomWidgetType::Proc).is_some(),
use_disk: used_widget_set.get(&BottomWidgetType::Disk).is_some(),
use_temp: used_widget_set.get(&BottomWidgetType::Temp).is_some(),
use_cpu: used_widget_set.get(&Cpu).is_some() || used_widget_set.get(&BasicCpu).is_some(),
use_mem: used_widget_set.get(&Mem).is_some() || used_widget_set.get(&BasicMem).is_some(),
use_net: used_widget_set.get(&Net).is_some() || used_widget_set.get(&BasicNet).is_some(),
use_proc: used_widget_set.get(&Proc).is_some(),
use_disk: used_widget_set.get(&Disk).is_some(),
use_temp: used_widget_set.get(&Temp).is_some(),
use_battery: used_widget_set.get(&Battery).is_some(),
};
Ok(App::builder()
@ -237,6 +236,7 @@ pub fn build_app(
.proc_state(ProcState::init(proc_state_map))
.disk_state(DiskState::init(disk_state_map))
.temp_state(TempState::init(temp_state_map))
.battery_state(BatteryState::init(battery_state_map))
.basic_table_widget_state(basic_table_widget_state)
.current_widget(widget_map.get(&initial_widget_id).unwrap().clone()) // I think the unwrap is fine here
.widget_map(widget_map)
@ -275,9 +275,16 @@ pub fn get_widget_layout(
.collect::<error::Result<Vec<_>>>()?,
total_row_height_ratio: total_height_ratio,
};
ret_bottom_layout.get_movement_mappings();
ret_bottom_layout
// Confirm that we have at least ONE widget - if we don't, go back to default!
if iter_id > 0 {
ret_bottom_layout.get_movement_mappings();
ret_bottom_layout
} else {
return Err(error::BottomError::ConfigError(
"Invalid layout - please have at least one widget.".to_string(),
));
}
} else {
default_widget_id = DEFAULT_WIDGET_ID;
BottomLayout::init_default(left_legend)