mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-08-16 22:31:03 +02:00
Merge pull request #831 from JakeStanger/feat/sysinfo-overhaul
feat(sysinfo): overhaul to add aggregate/unit/formatting support
This commit is contained in:
commit
7c7b6c7323
14 changed files with 1633 additions and 589 deletions
105
Cargo.lock
generated
105
Cargo.lock
generated
|
@ -472,9 +472,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.4"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
|
@ -1392,15 +1392,6 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
|
@ -1565,7 +1556,7 @@ dependencies = [
|
|||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
"windows-core 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1664,7 +1655,7 @@ version = "1.0.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
@ -1964,7 +1955,7 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
|
@ -2168,16 +2159,6 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||
dependencies = [
|
||||
"hermit-abi 0.2.6",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.30.3"
|
||||
|
@ -2519,9 +2500,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.7.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
|
@ -2529,14 +2510,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.11.0"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3162,17 +3141,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.29.11"
|
||||
version = "0.33.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666"
|
||||
checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"once_cell",
|
||||
"rayon",
|
||||
"winapi",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3965,6 +3943,16 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
|
||||
dependencies = [
|
||||
"windows-core 0.57.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
|
@ -3974,17 +3962,60 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-result 0.1.2",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.38",
|
||||
"syn 2.0.92",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.38",
|
||||
"syn 2.0.92",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-result 0.2.0",
|
||||
"windows-strings",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.2.0"
|
||||
|
@ -4000,7 +4031,7 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-result 0.2.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -63,7 +63,7 @@ keyboard = ["dep:input", "dep:evdev-rs", "dep:libc", "dep:nix"]
|
|||
|
||||
launcher = []
|
||||
|
||||
music = ["regex"]
|
||||
music = ["dep:regex"]
|
||||
"music+all" = ["music", "music+mpris", "music+mpd"]
|
||||
"music+mpris" = ["music", "mpris"]
|
||||
"music+mpd" = ["music", "mpd-utils"]
|
||||
|
@ -72,7 +72,7 @@ network_manager = ["futures-lite", "futures-signals", "zbus"]
|
|||
|
||||
notifications = ["zbus"]
|
||||
|
||||
sys_info = ["sysinfo", "regex"]
|
||||
sys_info = ["dep:sysinfo"]
|
||||
|
||||
tray = ["system-tray"]
|
||||
|
||||
|
@ -148,12 +148,15 @@ libc = { version = "0.2.164", optional = true }
|
|||
# music
|
||||
mpd-utils = { version = "0.2.1", optional = true }
|
||||
mpris = { version = "2.0.1", optional = true }
|
||||
regex = { version = "1.11.1", default-features = false, features = [
|
||||
"std",
|
||||
], optional = true }
|
||||
|
||||
# network_manager
|
||||
futures-signals = { version = "0.3.34", optional = true }
|
||||
|
||||
# sys_info
|
||||
sysinfo = { version = "0.29.11", optional = true }
|
||||
sysinfo = { version = "0.33.1", optional = true }
|
||||
|
||||
# tray
|
||||
system-tray = { version = "0.7.0", features = ["dbusmenu-gtk3"], optional = true }
|
||||
|
@ -164,9 +167,6 @@ libpulse-binding = { version = "2.28.2", optional = true }
|
|||
# shared
|
||||
futures-lite = { version = "2.6.0", optional = true } # network_manager, upower, workspaces
|
||||
nix = { version = "0.29.0", optional = true, features = ["event", "fs", "poll"] } # clipboard, input
|
||||
regex = { version = "1.11.1", default-features = false, features = [
|
||||
"std",
|
||||
], optional = true } # music, sys_info
|
||||
zbus = { version = "5.5.0", default-features = false, features = ["tokio"], optional = true } # network_manager, notifications, upower
|
||||
swayipc-async = { version = "2.0.1", optional = true } # workspaces, keyboard
|
||||
hyprland = { version = "0.4.0-alpha.3", features = ["silent"], optional = true } # workspaces, keyboard
|
||||
|
|
|
@ -3,6 +3,8 @@ Displays one or more labels containing system information.
|
|||
Separating information across several labels allows for styling each one independently.
|
||||
Pango markup is supported.
|
||||
|
||||
Options can be provided in a token to specify operations, units and formatting.
|
||||
|
||||

|
||||
|
||||
|
||||
|
@ -11,14 +13,14 @@ Pango markup is supported.
|
|||
> Type: `sys_info`
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|--------------------|--------------------|---------|--------------------------------------------------------------------------------------------------------------------------------|
|
||||
|--------------------|------------------------------------------------------------|----------------|--------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `format` | `string[]` | `null` | Array of strings including formatting tokens. For available tokens see below. |
|
||||
| `interval` | `integer` or `Map` | `5` | Seconds between refreshing. Can be a single value for all data or a map of individual refresh values for different data types. |
|
||||
| `interval.memory` | `integer` | `5` | Seconds between refreshing memory data |
|
||||
| `interval.cpu` | `integer` | `5` | Seconds between refreshing cpu data |
|
||||
| `interval.temps` | `integer` | `5` | Seconds between refreshing temperature data |
|
||||
| `interval.disks` | `integer` | `5` | Seconds between refreshing disk data |
|
||||
| `interval.network` | `integer` | `5` | Seconds between refreshing network data |
|
||||
| `interval.memory` | `integer` | `5` | Seconds between refreshing memory data. |
|
||||
| `interval.cpu` | `integer` | `5` | Seconds between refreshing cpu data. |
|
||||
| `interval.temps` | `integer` | `5` | Seconds between refreshing temperature data. |
|
||||
| `interval.disks` | `integer` | `5` | Seconds between refreshing disk data. |
|
||||
| `interval.network` | `integer` | `5` | Seconds between refreshing network data. |
|
||||
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the labels. |
|
||||
| `direction` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | How the labels are laid out (not the rotation of an individual label). |
|
||||
|
||||
|
@ -30,12 +32,11 @@ Pango markup is supported.
|
|||
"end": [
|
||||
{
|
||||
"format": [
|
||||
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C",
|
||||
" {memory_used} / {memory_total} GB ({memory_percent}%)",
|
||||
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
|
||||
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
|
||||
" {net_down:enp39s0} / {net_up:enp39s0} Mbps",
|
||||
" {load_average:1} | {load_average:5} | {load_average:15}",
|
||||
" {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C",
|
||||
" {memory_used} / {memory_total} GB ({memory_available} | {memory_percent}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)",
|
||||
" {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s",
|
||||
" {net_down@enp39s0} / {net_up@enp39s0} Mbps",
|
||||
" {load_average_1} | {load_average_5} | {load_average_15}",
|
||||
" {uptime}"
|
||||
],
|
||||
"interval": {
|
||||
|
@ -60,13 +61,12 @@ Pango markup is supported.
|
|||
[[end]]
|
||||
type = 'sys_info'
|
||||
format = [
|
||||
' {cpu_percent}% | {temp_c:k10temp-Tccd1}°C',
|
||||
' {memory_used} / {memory_total} GB ({memory_percent}%)',
|
||||
'| {swap_used} / {swap_total} GB ({swap_percent}%)',
|
||||
' {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)',
|
||||
' {net_down:enp39s0} / {net_up:enp39s0} Mbps',
|
||||
' {load_average:1} | {load_average:5} | {load_average:15}',
|
||||
' {uptime}',
|
||||
" {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C",
|
||||
" {memory_used} / {memory_total} GB ({memory_available} | {memory_percent}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)",
|
||||
" {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s",
|
||||
" {net_down@enp39s0} / {net_up@enp39s0} Mbps",
|
||||
" {load_average_1} | {load_average_5} | {load_average_15}",
|
||||
" {uptime}"
|
||||
]
|
||||
|
||||
[end.interval]
|
||||
|
@ -87,13 +87,12 @@ temps = 5
|
|||
```yaml
|
||||
end:
|
||||
- format:
|
||||
- ' {cpu_percent}% | {temp_c:k10temp-Tccd1}°C'
|
||||
- ' {memory_used} / {memory_total} GB ({memory_percent}%)'
|
||||
- '| {swap_used} / {swap_total} GB ({swap_percent}%)'
|
||||
- ' {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)'
|
||||
- ' {net_down:enp39s0} / {net_up:enp39s0} Mbps'
|
||||
- ' {load_average:1} | {load_average:5} | {load_average:15}'
|
||||
- ' {uptime}'
|
||||
- " {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C"
|
||||
- " {memory_used} / {memory_total} GB ({memory_available} | {memory_percent2}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)"
|
||||
- " {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s"
|
||||
- " {net_down@enp39s0} / {net_up@enp39s0} Mbps"
|
||||
- " {load_average_1} | {load_average_5} | {load_average_15}"
|
||||
- " {uptime}"
|
||||
interval:
|
||||
cpu: 1
|
||||
disks: 300
|
||||
|
@ -121,12 +120,11 @@ end:
|
|||
interval.networks = 3
|
||||
|
||||
format = [
|
||||
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C"
|
||||
" {memory_used} / {memory_total} GB ({memory_percent}%)"
|
||||
"| {swap_used} / {swap_total} GB ({swap_percent}%)"
|
||||
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)"
|
||||
" {net_down:enp39s0} / {net_up:enp39s0} Mbps"
|
||||
" {load_average:1} | {load_average:5} | {load_average:15}"
|
||||
" {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C"
|
||||
" {memory_used} / {memory_total} GB ({memory_available} | {memory_percent2}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)"
|
||||
" {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s"
|
||||
" {net_down@enp39s0} / {net_up@enp39s0} Mbps"
|
||||
" {load_average_1} | {load_average_5} | {load_average_15}"
|
||||
" {uptime}"
|
||||
]
|
||||
}
|
||||
|
@ -138,39 +136,179 @@ end:
|
|||
|
||||
### Formatting Tokens
|
||||
|
||||
The following tokens can be used in the `format` configuration option:
|
||||
The below table lists the tokens which can be used in the `format` configuration option.
|
||||
More information about each of these and the additional options can be found further below.
|
||||
|
||||
| Token | Description |
|
||||
|--------------------------|------------------------------------------------------------------------------------|
|
||||
| **CPU** | |
|
||||
| `{cpu_percent}` | Total CPU utilisation percentage |
|
||||
| **Memory** | |
|
||||
| `{memory_free}` | Memory free in GB. |
|
||||
| `{memory_used}` | Memory used in GB. |
|
||||
| `{memory_total}` | Memory total in GB. |
|
||||
| `{memory_percent}` | Memory utilisation percentage. |
|
||||
| `{swap_free}` | Swap free in GB. |
|
||||
| `{swap_used}` | Swap used in GB. |
|
||||
| `{swap_total}` | Swap total in GB. |
|
||||
| `{swap_percent}` | Swap utilisation percentage. |
|
||||
| **Temperature** | |
|
||||
| `{temp_c:[sensor]}` | Temperature in degrees C. Replace `[sensor]` with the sensor label. |
|
||||
| `{temp_f:[sensor]}` | Temperature in degrees F. Replace `[sensor]` with the sensor label. |
|
||||
| **Disk** | |
|
||||
| `{disk_free:[mount]}` | Disk free space in GB. Replace `[mount]` with the disk mountpoint. |
|
||||
| `{disk_used:[mount]}` | Disk used space in GB. Replace `[mount]` with the disk mountpoint. |
|
||||
| `{disk_total:[mount]}` | Disk total space in GB. Replace `[mount]` with the disk mountpoint. |
|
||||
| `{disk_percent:[mount]}` | Disk utilisation percentage. Replace `[mount]` with the disk mountpoint. |
|
||||
| **Network** | |
|
||||
| `{net_down:[adapter]}` | Average network download speed in Mbps. Replace `[adapter]` with the adapter name. |
|
||||
| `{net_up:[adapter]}` | Average network upload speed in Mbps. Replace `[adapter]` with the adapter name. |
|
||||
| **System** | |
|
||||
| `{load_average:1}` | 1-minute load average. |
|
||||
| `{load_average:5}` | 5-minute load average. |
|
||||
| `{load_average:15}` | 15-minute load average. |
|
||||
| `{uptime}` | System uptime formatted as `HH:mm`. |
|
||||
| Token | Default Function | Default Unit | Default Formatting |
|
||||
|--------------------------|------------------|--------------|--------------------|
|
||||
| **CPU** | | | |
|
||||
| `{cpu_frequency[#core]}` | `mean` | MHz | `.2` |
|
||||
| `{cpu_percent[#core]}` | `mean` | % | `0<2` |
|
||||
| **Memory** | | | |
|
||||
| `{memory_free}` | N/A | GB | `0<4.1` |
|
||||
| `{memory_available}` | N/A | GB | `0<4.1` |
|
||||
| `{memory_used}` | N/A | GB | `0<4.1` |
|
||||
| `{memory_total}` | N/A | GB | `0<4.1` |
|
||||
| `{memory_percent}` | N/A | GB | `0<4.1` |
|
||||
| `{swap_free}` | N/A | GB | `0<4.1` |
|
||||
| `{swap_used}` | N/A | GB | `0<4.1` |
|
||||
| `{swap_total}` | N/A | GB | `0<4.1` |
|
||||
| `{swap_percent}` | N/A | GB | `0<4.1` |
|
||||
| **Temperature** | | | |
|
||||
| `{temp_c[#sensor]}` | `max` | °C | |
|
||||
| `{temp_f[#sensor]}` | `max` | °F | |
|
||||
| **Disk** | | | |
|
||||
| `{disk_free[#mount]}` | `sum` | GB | |
|
||||
| `{disk_used[#mount]}` | `sum` | GB | |
|
||||
| `{disk_total[#mount]}` | `sum` | GB | |
|
||||
| `{disk_percent[#mount]}` | `sum` | % | |
|
||||
| `{disk_read[#mount]}` | `sum` | MB/s | |
|
||||
| `{disk_write[#mount]}` | `sum` | MB/s | |
|
||||
| **Network** | | | |
|
||||
| `{net_down[#adapter]}` | `sum` | Mb/s | |
|
||||
| `{net_up[#adapter]}` | `sum` | Mb/s | |
|
||||
| **System** | | | |
|
||||
| `{load_average_1}` | N/A | - | `.2` |
|
||||
| `{load_average_5}` | N/A | - | `.2` |
|
||||
| `{load_average_15}` | N/A | - | `.2` |
|
||||
| `{uptime}` | N/A | ??? | ??? |
|
||||
|
||||
For Intel CPUs, you can typically use `coretemp-Package-id-0` for the temperature sensor. For AMD, you can use `k10temp-Tccd1`.
|
||||
#### Functions and names
|
||||
|
||||
Many of the tokens operate on a value set, as opposed to an individual value:
|
||||
|
||||
- CPU tokens operate on each physical thread.
|
||||
- Temperature tokens operate on each sensor.
|
||||
- Disk tokens operate on each mount.
|
||||
- Network tokens operate on each adapter.
|
||||
|
||||
By default, these will apply a function to the full set to reduce them down to a single value.
|
||||
The list of available functions is shown below:
|
||||
|
||||
| Function | Description |
|
||||
|----------|-----------------------------------------|
|
||||
| `sum` | Adds each value in the set. |
|
||||
| `min` | Gets the smallest value in the set. |
|
||||
| `max` | Gets the largest value in the set. |
|
||||
| `mean` | Gets the mean average value of the set. |
|
||||
|
||||
It is also possible to get only a single value from the set by specifying a name instead of a function.
|
||||
|
||||
| Token category | Valid name |
|
||||
|----------------|-------------------------------------------------------------------------|
|
||||
| CPU | A CPU thread, eg `cpu0`, `cpu1`, ... |
|
||||
| Temperature | A sensor name, eg `CPUTIN`. These line up with the output of `sensors`. |
|
||||
| Disk | A disk mountpoint, eg `/`, `/home`, ... |
|
||||
| Network | An adapter name, eg `eth0` or `enp30s0`. |
|
||||
|
||||
|
||||
To specify a name or function, use a `@`. For example, to show disk percent for `/home`:
|
||||
|
||||
```json
|
||||
"{disk_percent@/home}%"
|
||||
```
|
||||
|
||||
To show total CPU utilization where each core represents 100% (like `htop` etc):
|
||||
|
||||
```json
|
||||
"{cpu_percent@sum}%"
|
||||
```
|
||||
|
||||
#### Prefixes and units
|
||||
|
||||
For tokens which return an appropriate unit, you can specify the SI prefix (or unit in some special cases).
|
||||
The following options can be supplied:
|
||||
|
||||
| Name | Value |
|
||||
|---------|-------|
|
||||
| Kilo | `k` |
|
||||
| Mega | `M` |
|
||||
| Giga | `G` |
|
||||
| Tera | `T` |
|
||||
| Peta | `P` |
|
||||
| | |
|
||||
| Kibi | `ki` |
|
||||
| Mebi | `Mi` |
|
||||
| Gibi | `Gi` |
|
||||
| Tebi | `Ti` |
|
||||
| Pebi | `Pi` |
|
||||
| | |
|
||||
| Kilobit | `kb` |
|
||||
| Megabit | `Mb` |
|
||||
| Gigabit | `Gb` |
|
||||
|
||||
To specify a prefix or unit, use a `#`. For example, to show free total disk space in terabytes:
|
||||
|
||||
```json
|
||||
"{disk_free#T} TB"
|
||||
```
|
||||
|
||||
#### Formatting
|
||||
|
||||
To control the formatting of the resultant number,
|
||||
a subset of Rust's string formatting is implemented. This includes:
|
||||
|
||||
- Width
|
||||
- Fill/Alignment
|
||||
- Precision
|
||||
|
||||
Formatting is specified with a `:` and MUST be the last part of a token.
|
||||
|
||||
##### Width
|
||||
|
||||
The width controls the minimum string length of the value.
|
||||
Specifying just a width will left-pad the value with `0` until the value reaches the target length.
|
||||
|
||||
The width can be any value from `1-9`. Larger values are not supported.
|
||||
|
||||
For example, to render CPU usage as `045%`:
|
||||
|
||||
```json
|
||||
"{cpu_usage:3}%"
|
||||
```
|
||||
|
||||
##### Fill/Alignment
|
||||
|
||||
These options can be used to control the `width` property.
|
||||
|
||||
To specify the fill and alignment, prefix the width with a character and a direction.
|
||||
Fill characters can be any single UTF-8 character EXCEPT 1-9. Alignment must be one of:
|
||||
|
||||
- `<` - Left fill
|
||||
- `^` - Center fill
|
||||
- `>` - Right fill
|
||||
|
||||
For example, to render CPU usage as ` 45%`:
|
||||
|
||||
```json
|
||||
"{cpu_usage: <3}%"
|
||||
```
|
||||
|
||||
##### Precision
|
||||
|
||||
The number of decimal places a value is shown to can be controlled using precision.
|
||||
Any value is supported.
|
||||
|
||||
To specify precision, include a `.` followed by the value. If other options are supplied, this MUST come after.
|
||||
|
||||
For example, to render used disk space to 2dp:
|
||||
|
||||
```json
|
||||
"{disk_used:.2} GB"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Combining Options
|
||||
|
||||
Each of the token options can be combined to create more complex solutions.
|
||||
|
||||
Putting it all together, you could show the free disk space on your `/home` partition in terabytes,
|
||||
left-padded with spaces to a min width of 5, and shown to 2dp as follows:
|
||||
|
||||
```json
|
||||
"{disk_used@/home#T: <5.2} TB"
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
|
|
|
@ -55,12 +55,11 @@ let {
|
|||
interval.networks = 3
|
||||
|
||||
format = [
|
||||
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C"
|
||||
" {memory_used} / {memory_total} GB ({memory_percent}%)"
|
||||
"| {swap_used} / {swap_total} GB ({swap_percent}%)"
|
||||
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)"
|
||||
" {net_down:enp39s0} / {net_up:enp39s0} Mbps"
|
||||
" {load_average:1} | {load_average:5} | {load_average:15}"
|
||||
" {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C"
|
||||
" {memory_used} / {memory_total} GB ({memory_available} | {memory_percent2}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)"
|
||||
" {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s"
|
||||
" {net_down@enp39s0} / {net_up@enp39s0} Mbps"
|
||||
" {load_average1} | {load_average5} | {load_average15}"
|
||||
" {uptime}"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "https://f.jstanger.dev/github/ironbar/schema.json",
|
||||
"anchor_to_edges": true,
|
||||
"position": "bottom",
|
||||
"icon_theme": "Paper",
|
||||
|
@ -64,12 +63,11 @@
|
|||
"networks": 3
|
||||
},
|
||||
"format": [
|
||||
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C",
|
||||
" {memory_used} / {memory_total} GB ({memory_percent}%)",
|
||||
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
|
||||
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
|
||||
" {net_down:enp39s0} / {net_up:enp39s0} Mbps",
|
||||
" {load_average:1} | {load_average:5} | {load_average:15}",
|
||||
" {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C",
|
||||
" {memory_used} / {memory_total} GB ({memory_available} | {memory_percent2}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)",
|
||||
" {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s",
|
||||
" {net_down@enp39s0} / {net_up@enp39s0} Mbps",
|
||||
" {load_average1} | {load_average5} | {load_average15}",
|
||||
" {uptime}"
|
||||
]
|
||||
},
|
||||
|
|
|
@ -53,12 +53,11 @@ interval = 500
|
|||
[[end]]
|
||||
type = "sys_info"
|
||||
format = [
|
||||
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C",
|
||||
" {memory_used} / {memory_total} GB ({memory_percent}%)",
|
||||
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
|
||||
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
|
||||
" {net_down:enp39s0} / {net_up:enp39s0} Mbps",
|
||||
" {load_average:1} | {load_average:5} | {load_average:15}",
|
||||
" {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C",
|
||||
" {memory_used} / {memory_total} GB ({memory_available} | {memory_percent2}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)",
|
||||
" {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s",
|
||||
" {net_down@enp39s0} / {net_up@enp39s0} Mbps",
|
||||
" {load_average1} | {load_average5} | {load_average15}",
|
||||
" {uptime}",
|
||||
]
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
$schema: https://f.jstanger.dev/github/ironbar/schema.json
|
||||
anchor_to_edges: true
|
||||
position: bottom
|
||||
icon_theme: Paper
|
||||
|
@ -44,12 +43,11 @@ end:
|
|||
disks: 300
|
||||
networks: 3
|
||||
format:
|
||||
- {cpu_percent}% | {temp_c:k10temp-Tccd1}°C
|
||||
- {memory_used} / {memory_total} GB ({memory_percent}%)
|
||||
- '| {swap_used} / {swap_total} GB ({swap_percent}%)'
|
||||
- {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)
|
||||
- {net_down:enp39s0} / {net_up:enp39s0} Mbps
|
||||
- {load_average:1} | {load_average:5} | {load_average:15}
|
||||
- {cpu_percent}% | {cpu_frequency} GHz | {temp_c@CPUTIN}°C
|
||||
- {memory_used} / {memory_total} GB ({memory_available} | {memory_percent2}%) | {swap_used} / {swap_total} GB ({swap_free} | {swap_percent}%)
|
||||
- {disk_used#T@/:.1} / {disk_total#T@/:.1} TB ({disk_percent@/}%) | {disk_read} / {disk_write} MB/s
|
||||
- {net_down@enp39s0} / {net_up@enp39s0} Mbps
|
||||
- {load_average1} | {load_average5} | {load_average15}
|
||||
- {uptime}
|
||||
- type: volume
|
||||
format: '{icon} {percentage}%'
|
||||
|
|
|
@ -21,6 +21,8 @@ pub mod networkmanager;
|
|||
pub mod sway;
|
||||
#[cfg(feature = "notifications")]
|
||||
pub mod swaync;
|
||||
#[cfg(feature = "sys_info")]
|
||||
pub mod sysinfo;
|
||||
#[cfg(feature = "tray")]
|
||||
pub mod tray;
|
||||
#[cfg(feature = "upower")]
|
||||
|
@ -54,6 +56,8 @@ pub struct Clients {
|
|||
network_manager: Option<Arc<networkmanager::Client>>,
|
||||
#[cfg(feature = "notifications")]
|
||||
notifications: Option<Arc<swaync::Client>>,
|
||||
#[cfg(feature = "sys_info")]
|
||||
sys_info: Option<Arc<sysinfo::Client>>,
|
||||
#[cfg(feature = "tray")]
|
||||
tray: Option<Arc<tray::Client>>,
|
||||
#[cfg(feature = "upower")]
|
||||
|
@ -185,6 +189,13 @@ impl Clients {
|
|||
Ok(client)
|
||||
}
|
||||
|
||||
#[cfg(feature = "sys_info")]
|
||||
pub fn sys_info(&mut self) -> Arc<sysinfo::Client> {
|
||||
self.sys_info
|
||||
.get_or_insert_with(|| Arc::new(sysinfo::Client::new()))
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "tray")]
|
||||
pub fn tray(&mut self) -> ClientResult<tray::Client> {
|
||||
let client = if let Some(client) = &self.tray {
|
||||
|
|
390
src/clients/sysinfo.rs
Normal file
390
src/clients/sysinfo.rs
Normal file
|
@ -0,0 +1,390 @@
|
|||
use crate::modules::sysinfo::Interval;
|
||||
use crate::{lock, register_client};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Mutex;
|
||||
use sysinfo::{Components, Disks, LoadAvg, Networks, RefreshKind, System};
|
||||
|
||||
#[repr(u64)]
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Prefix {
|
||||
#[default]
|
||||
None = 1,
|
||||
|
||||
Kilo = 1000,
|
||||
Mega = Prefix::Kilo as u64 * 1000,
|
||||
Giga = Prefix::Mega as u64 * 1000,
|
||||
Tera = Prefix::Giga as u64 * 1000,
|
||||
Peta = Prefix::Tera as u64 * 1000,
|
||||
|
||||
Kibi = 1024,
|
||||
Mebi = Prefix::Kibi as u64 * 1024,
|
||||
Gibi = Prefix::Mebi as u64 * 1024,
|
||||
Tebi = Prefix::Gibi as u64 * 1024,
|
||||
Pebi = Prefix::Tebi as u64 * 1024,
|
||||
|
||||
// # Units
|
||||
// These are special cases
|
||||
// where you'd actually want to do slightly more than a prefix alone.
|
||||
// Included as part of the prefix system for simplicity.
|
||||
KiloBit = 128,
|
||||
MegaBit = Prefix::KiloBit as u64 * 1024,
|
||||
GigaBit = Prefix::MegaBit as u64 * 1024,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Function {
|
||||
None,
|
||||
Sum,
|
||||
Min,
|
||||
Max,
|
||||
Mean,
|
||||
Name(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ValueSet {
|
||||
values: HashMap<Box<str>, Value>,
|
||||
}
|
||||
|
||||
impl FromIterator<(Box<str>, Value)> for ValueSet {
|
||||
fn from_iter<T: IntoIterator<Item = (Box<str>, Value)>>(iter: T) -> Self {
|
||||
Self {
|
||||
values: iter.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueSet {
|
||||
fn values(&self, prefix: Prefix) -> impl Iterator<Item = f64> + use<'_> {
|
||||
self.values
|
||||
.values()
|
||||
.map(move |v| v.get(prefix))
|
||||
.filter(|v| !v.is_nan())
|
||||
}
|
||||
|
||||
pub fn apply(&self, function: &Function, prefix: Prefix) -> f64 {
|
||||
match function {
|
||||
Function::None => 0.0,
|
||||
Function::Sum => self.sum(prefix),
|
||||
Function::Min => self.min(prefix),
|
||||
Function::Max => self.max(prefix),
|
||||
Function::Mean => self.mean(prefix),
|
||||
Function::Name(name) => self
|
||||
.values
|
||||
.get(&Box::from(name.as_str()))
|
||||
.map(|v| v.get(prefix))
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn sum(&self, prefix: Prefix) -> f64 {
|
||||
self.values(prefix).sum()
|
||||
}
|
||||
|
||||
fn min(&self, prefix: Prefix) -> f64 {
|
||||
self.values(prefix)
|
||||
.min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn max(&self, prefix: Prefix) -> f64 {
|
||||
self.values(prefix)
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn mean(&self, prefix: Prefix) -> f64 {
|
||||
self.sum(prefix) / self.values.len() as f64
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct Value {
|
||||
value: f64,
|
||||
prefix: Prefix,
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn new(value: f64) -> Self {
|
||||
Self::new_with_prefix(value, Prefix::None)
|
||||
}
|
||||
|
||||
pub fn new_with_prefix(value: f64, prefix: Prefix) -> Self {
|
||||
Self { value, prefix }
|
||||
}
|
||||
|
||||
pub fn get(self, prefix: Prefix) -> f64 {
|
||||
if prefix == self.prefix {
|
||||
self.value
|
||||
} else {
|
||||
let scale = self.prefix as u64 as f64 / prefix as u64 as f64;
|
||||
self.value * scale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
system: Mutex<System>,
|
||||
disks: Mutex<Disks>,
|
||||
components: Mutex<Components>,
|
||||
networks: Mutex<Networks>,
|
||||
load_average: Mutex<LoadAvg>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new() -> Self {
|
||||
let refresh_kind = RefreshKind::everything().without_processes();
|
||||
|
||||
let system = System::new_with_specifics(refresh_kind);
|
||||
let disks = Disks::new_with_refreshed_list();
|
||||
let components = Components::new_with_refreshed_list();
|
||||
let networks = Networks::new_with_refreshed_list();
|
||||
let load_average = System::load_average();
|
||||
|
||||
Self {
|
||||
system: Mutex::new(system),
|
||||
disks: Mutex::new(disks),
|
||||
components: Mutex::new(components),
|
||||
networks: Mutex::new(networks),
|
||||
load_average: Mutex::new(load_average),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_cpu(&self) {
|
||||
lock!(self.system).refresh_cpu_all();
|
||||
}
|
||||
|
||||
pub fn refresh_memory(&self) {
|
||||
lock!(self.system).refresh_memory();
|
||||
}
|
||||
|
||||
pub fn refresh_network(&self) {
|
||||
lock!(self.networks).refresh(true);
|
||||
}
|
||||
|
||||
pub fn refresh_temps(&self) {
|
||||
lock!(self.components).refresh(true);
|
||||
}
|
||||
|
||||
pub fn refresh_disks(&self) {
|
||||
lock!(self.disks).refresh(true);
|
||||
}
|
||||
|
||||
pub fn refresh_load_average(&self) {
|
||||
*lock!(self.load_average) = System::load_average();
|
||||
}
|
||||
|
||||
pub fn cpu_frequency(&self) -> ValueSet {
|
||||
lock!(self.system)
|
||||
.cpus()
|
||||
.iter()
|
||||
.map(|cpu| {
|
||||
(
|
||||
cpu.name().into(),
|
||||
Value::new_with_prefix(cpu.frequency() as f64, Prefix::Mega),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn cpu_percent(&self) -> ValueSet {
|
||||
lock!(self.system)
|
||||
.cpus()
|
||||
.iter()
|
||||
.map(|cpu| (cpu.name().into(), Value::new(cpu.cpu_usage() as f64)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn memory_free(&self) -> Value {
|
||||
Value::new(lock!(self.system).free_memory() as f64)
|
||||
}
|
||||
|
||||
pub fn memory_available(&self) -> Value {
|
||||
Value::new(lock!(self.system).available_memory() as f64)
|
||||
}
|
||||
|
||||
pub fn memory_total(&self) -> Value {
|
||||
Value::new(lock!(self.system).total_memory() as f64)
|
||||
}
|
||||
|
||||
pub fn memory_used(&self) -> Value {
|
||||
Value::new(lock!(self.system).used_memory() as f64)
|
||||
}
|
||||
|
||||
pub fn memory_percent(&self) -> Value {
|
||||
let total = lock!(self.system).total_memory() as f64;
|
||||
let used = lock!(self.system).used_memory() as f64;
|
||||
|
||||
Value::new(used / total * 100.0)
|
||||
}
|
||||
|
||||
pub fn swap_free(&self) -> Value {
|
||||
Value::new(lock!(self.system).free_swap() as f64)
|
||||
}
|
||||
|
||||
pub fn swap_total(&self) -> Value {
|
||||
Value::new(lock!(self.system).total_swap() as f64)
|
||||
}
|
||||
|
||||
pub fn swap_used(&self) -> Value {
|
||||
Value::new(lock!(self.system).used_swap() as f64)
|
||||
}
|
||||
pub fn swap_percent(&self) -> Value {
|
||||
let total = lock!(self.system).total_swap() as f64;
|
||||
let used = lock!(self.system).used_swap() as f64;
|
||||
|
||||
Value::new(used / total * 100.0)
|
||||
}
|
||||
|
||||
pub fn temp_c(&self) -> ValueSet {
|
||||
lock!(self.components)
|
||||
.iter()
|
||||
.map(|comp| {
|
||||
(
|
||||
comp.label().into(),
|
||||
Value::new(comp.temperature().unwrap_or_default() as f64),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn temp_f(&self) -> ValueSet {
|
||||
lock!(self.components)
|
||||
.iter()
|
||||
.map(|comp| {
|
||||
(
|
||||
comp.label().into(),
|
||||
Value::new(c_to_f(comp.temperature().unwrap_or_default() as f64)),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn disk_free(&self) -> ValueSet {
|
||||
lock!(self.disks)
|
||||
.iter()
|
||||
.map(|disk| {
|
||||
(
|
||||
disk.mount_point().to_string_lossy().into(),
|
||||
Value::new(disk.available_space() as f64),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn disk_total(&self) -> ValueSet {
|
||||
lock!(self.disks)
|
||||
.iter()
|
||||
.map(|disk| {
|
||||
(
|
||||
disk.mount_point().to_string_lossy().into(),
|
||||
Value::new(disk.total_space() as f64),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn disk_used(&self) -> ValueSet {
|
||||
lock!(self.disks)
|
||||
.iter()
|
||||
.map(|disk| {
|
||||
(
|
||||
disk.mount_point().to_string_lossy().into(),
|
||||
Value::new((disk.total_space() - disk.available_space()) as f64),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn disk_percent(&self) -> ValueSet {
|
||||
lock!(self.disks)
|
||||
.iter()
|
||||
.map(|disk| {
|
||||
(
|
||||
disk.mount_point().to_string_lossy().into(),
|
||||
Value::new(
|
||||
(disk.total_space() - disk.available_space()) as f64
|
||||
/ disk.total_space() as f64
|
||||
* 100.0,
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn disk_read(&self, interval: Interval) -> ValueSet {
|
||||
lock!(self.disks)
|
||||
.iter()
|
||||
.map(|disk| {
|
||||
(
|
||||
disk.mount_point().to_string_lossy().into(),
|
||||
Value::new(disk.usage().read_bytes as f64 / interval.disks() as f64),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn disk_write(&self, interval: Interval) -> ValueSet {
|
||||
lock!(self.disks)
|
||||
.iter()
|
||||
.map(|disk| {
|
||||
(
|
||||
disk.mount_point().to_string_lossy().into(),
|
||||
Value::new(disk.usage().written_bytes as f64 / interval.disks() as f64),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn net_down(&self, interval: Interval) -> ValueSet {
|
||||
lock!(self.networks)
|
||||
.iter()
|
||||
.map(|(name, net)| {
|
||||
(
|
||||
name.as_str().into(),
|
||||
Value::new(net.received() as f64 / interval.networks() as f64),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn net_up(&self, interval: Interval) -> ValueSet {
|
||||
lock!(self.networks)
|
||||
.iter()
|
||||
.map(|(name, net)| {
|
||||
(
|
||||
name.as_str().into(),
|
||||
Value::new(net.transmitted() as f64 / interval.networks() as f64),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn load_average_1(&self) -> Value {
|
||||
Value::new(lock!(self.load_average).one)
|
||||
}
|
||||
|
||||
pub fn load_average_5(&self) -> Value {
|
||||
Value::new(lock!(self.load_average).five)
|
||||
}
|
||||
|
||||
pub fn load_average_15(&self) -> Value {
|
||||
Value::new(lock!(self.load_average).fifteen)
|
||||
}
|
||||
|
||||
/// Gets system uptime formatted as `HH:mm`.
|
||||
pub fn uptime(&self) -> String {
|
||||
let uptime = System::uptime();
|
||||
let hours = uptime / 3600;
|
||||
format!("{:0>2}:{:0>2}", hours, (uptime % 3600) / 60)
|
||||
}
|
||||
}
|
||||
|
||||
register_client!(Client, sys_info);
|
||||
|
||||
const fn c_to_f(c: f64) -> f64 {
|
||||
c / 5.0 * 9.0 + 32.0
|
||||
}
|
|
@ -1,451 +0,0 @@
|
|||
use crate::config::{CommonConfig, ModuleOrientation};
|
||||
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::{glib_recv, module_impl, send_async, spawn};
|
||||
use color_eyre::Result;
|
||||
use gtk::prelude::*;
|
||||
use gtk::Label;
|
||||
use regex::{Captures, Regex};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use sysinfo::{ComponentExt, CpuExt, DiskExt, NetworkExt, RefreshKind, System, SystemExt};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
pub struct SysInfoModule {
|
||||
/// List of strings including formatting tokens.
|
||||
/// For available tokens, see [below](#formatting-tokens).
|
||||
///
|
||||
/// **Required**
|
||||
format: Vec<String>,
|
||||
|
||||
/// Number of seconds between refresh.
|
||||
///
|
||||
/// This can be set as a global interval,
|
||||
/// or passed as an object to customize the interval per-system.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "Interval::default")]
|
||||
interval: Interval,
|
||||
|
||||
/// The orientation of text for the labels.
|
||||
///
|
||||
/// **Valid options**: `horizontal`, `vertical`, `h`, `v`
|
||||
/// <br>
|
||||
/// **Default** : `horizontal`
|
||||
#[serde(default)]
|
||||
orientation: ModuleOrientation,
|
||||
|
||||
/// The orientation by which the labels are laid out.
|
||||
///
|
||||
/// **Valid options**: `horizontal`, `vertical`, `h`, `v`
|
||||
/// <br>
|
||||
/// **Default** : `horizontal`
|
||||
direction: Option<ModuleOrientation>,
|
||||
|
||||
/// See [common options](module-level-options#common-options).
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Copy, Clone)]
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
pub struct Intervals {
|
||||
/// The number of seconds between refreshing memory data.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "default_interval")]
|
||||
memory: u64,
|
||||
|
||||
/// The number of seconds between refreshing CPU data.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "default_interval")]
|
||||
cpu: u64,
|
||||
|
||||
/// The number of seconds between refreshing temperature data.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "default_interval")]
|
||||
temps: u64,
|
||||
|
||||
/// The number of seconds between refreshing disk data.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "default_interval")]
|
||||
disks: u64,
|
||||
|
||||
/// The number of seconds between refreshing network data.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "default_interval")]
|
||||
networks: u64,
|
||||
|
||||
/// The number of seconds between refreshing system data.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "default_interval")]
|
||||
system: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Copy, Clone)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
pub enum Interval {
|
||||
All(u64),
|
||||
Individual(Intervals),
|
||||
}
|
||||
|
||||
impl Default for Interval {
|
||||
fn default() -> Self {
|
||||
Self::All(default_interval())
|
||||
}
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
const fn memory(self) -> u64 {
|
||||
match self {
|
||||
Self::All(n) => n,
|
||||
Self::Individual(intervals) => intervals.memory,
|
||||
}
|
||||
}
|
||||
|
||||
const fn cpu(self) -> u64 {
|
||||
match self {
|
||||
Self::All(n) => n,
|
||||
Self::Individual(intervals) => intervals.cpu,
|
||||
}
|
||||
}
|
||||
|
||||
const fn temps(self) -> u64 {
|
||||
match self {
|
||||
Self::All(n) => n,
|
||||
Self::Individual(intervals) => intervals.temps,
|
||||
}
|
||||
}
|
||||
|
||||
const fn disks(self) -> u64 {
|
||||
match self {
|
||||
Self::All(n) => n,
|
||||
Self::Individual(intervals) => intervals.disks,
|
||||
}
|
||||
}
|
||||
|
||||
const fn networks(self) -> u64 {
|
||||
match self {
|
||||
Self::All(n) => n,
|
||||
Self::Individual(intervals) => intervals.networks,
|
||||
}
|
||||
}
|
||||
|
||||
const fn system(self) -> u64 {
|
||||
match self {
|
||||
Self::All(n) => n,
|
||||
Self::Individual(intervals) => intervals.system,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn default_interval() -> u64 {
|
||||
5
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum RefreshType {
|
||||
Memory,
|
||||
Cpu,
|
||||
Temps,
|
||||
Disks,
|
||||
Network,
|
||||
System,
|
||||
}
|
||||
|
||||
impl Module<gtk::Box> for SysInfoModule {
|
||||
type SendMessage = HashMap<String, String>;
|
||||
type ReceiveMessage = ();
|
||||
|
||||
module_impl!("sysinfo");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let interval = self.interval;
|
||||
|
||||
let refresh_kind = RefreshKind::everything()
|
||||
.without_processes()
|
||||
.without_users_list();
|
||||
|
||||
let mut sys = System::new_with_specifics(refresh_kind);
|
||||
sys.refresh_components_list();
|
||||
sys.refresh_disks_list();
|
||||
sys.refresh_networks_list();
|
||||
|
||||
let (refresh_tx, mut refresh_rx) = mpsc::channel(16);
|
||||
|
||||
macro_rules! spawn_refresh {
|
||||
($refresh_type:expr, $func:ident) => {{
|
||||
let tx = refresh_tx.clone();
|
||||
spawn(async move {
|
||||
loop {
|
||||
send_async!(tx, $refresh_type);
|
||||
sleep(Duration::from_secs(interval.$func())).await;
|
||||
}
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
spawn_refresh!(RefreshType::Memory, memory);
|
||||
spawn_refresh!(RefreshType::Cpu, cpu);
|
||||
spawn_refresh!(RefreshType::Temps, temps);
|
||||
spawn_refresh!(RefreshType::Disks, disks);
|
||||
spawn_refresh!(RefreshType::Network, networks);
|
||||
spawn_refresh!(RefreshType::System, system);
|
||||
|
||||
let tx = context.tx.clone();
|
||||
spawn(async move {
|
||||
let mut format_info = HashMap::new();
|
||||
|
||||
while let Some(refresh) = refresh_rx.recv().await {
|
||||
match refresh {
|
||||
RefreshType::Memory => refresh_memory_tokens(&mut format_info, &mut sys),
|
||||
RefreshType::Cpu => refresh_cpu_tokens(&mut format_info, &mut sys),
|
||||
RefreshType::Temps => refresh_temp_tokens(&mut format_info, &mut sys),
|
||||
RefreshType::Disks => refresh_disk_tokens(&mut format_info, &mut sys),
|
||||
RefreshType::Network => {
|
||||
refresh_network_tokens(&mut format_info, &mut sys, interval.networks());
|
||||
}
|
||||
RefreshType::System => refresh_system_tokens(&mut format_info, &sys),
|
||||
};
|
||||
|
||||
send_async!(tx, ModuleUpdateEvent::Update(format_info.clone()));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn into_widget(
|
||||
self,
|
||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_info: &ModuleInfo,
|
||||
) -> Result<ModuleParts<gtk::Box>> {
|
||||
let re = Regex::new(r"\{([^}]+)}")?;
|
||||
|
||||
let layout = match self.direction {
|
||||
Some(orientation) => orientation,
|
||||
None => self.orientation,
|
||||
};
|
||||
|
||||
let container = gtk::Box::new(layout.into(), 10);
|
||||
|
||||
let mut labels = Vec::new();
|
||||
|
||||
for format in &self.format {
|
||||
let label = Label::builder().label(format).use_markup(true).build();
|
||||
|
||||
label.add_class("item");
|
||||
label.set_angle(self.orientation.to_angle());
|
||||
|
||||
container.add(&label);
|
||||
labels.push(label);
|
||||
}
|
||||
|
||||
{
|
||||
let formats = self.format;
|
||||
glib_recv!(context.subscribe(), info => {
|
||||
for (format, label) in formats.iter().zip(labels.clone()) {
|
||||
let format_compiled = re.replace_all(format, |caps: &Captures| {
|
||||
info.get(&caps[1])
|
||||
.unwrap_or(&caps[0].to_string())
|
||||
.to_string()
|
||||
});
|
||||
|
||||
label.set_label_escaped(format_compiled.as_ref());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(ModuleParts {
|
||||
widget: container,
|
||||
popup: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_memory_tokens(format_info: &mut HashMap<String, String>, sys: &mut System) {
|
||||
sys.refresh_memory();
|
||||
|
||||
let total_memory = sys.total_memory();
|
||||
let available_memory = sys.available_memory();
|
||||
|
||||
let actual_used_memory = total_memory - available_memory;
|
||||
let memory_percent = actual_used_memory as f64 / total_memory as f64 * 100.0;
|
||||
|
||||
format_info.insert(
|
||||
String::from("memory_free"),
|
||||
(bytes_to_gigabytes(available_memory)).to_string(),
|
||||
);
|
||||
format_info.insert(
|
||||
String::from("memory_used"),
|
||||
(bytes_to_gigabytes(actual_used_memory)).to_string(),
|
||||
);
|
||||
format_info.insert(
|
||||
String::from("memory_total"),
|
||||
(bytes_to_gigabytes(total_memory)).to_string(),
|
||||
);
|
||||
format_info.insert(
|
||||
String::from("memory_percent"),
|
||||
format!("{memory_percent:0>2.0}"),
|
||||
);
|
||||
|
||||
let used_swap = sys.used_swap();
|
||||
let total_swap = sys.total_swap();
|
||||
|
||||
format_info.insert(
|
||||
String::from("swap_free"),
|
||||
(bytes_to_gigabytes(sys.free_swap())).to_string(),
|
||||
);
|
||||
format_info.insert(
|
||||
String::from("swap_used"),
|
||||
(bytes_to_gigabytes(used_swap)).to_string(),
|
||||
);
|
||||
format_info.insert(
|
||||
String::from("swap_total"),
|
||||
(bytes_to_gigabytes(total_swap)).to_string(),
|
||||
);
|
||||
format_info.insert(
|
||||
String::from("swap_percent"),
|
||||
format!("{:0>2.0}", used_swap as f64 / total_swap as f64 * 100.0),
|
||||
);
|
||||
}
|
||||
|
||||
fn refresh_cpu_tokens(format_info: &mut HashMap<String, String>, sys: &mut System) {
|
||||
sys.refresh_cpu();
|
||||
|
||||
let cpu_info = sys.global_cpu_info();
|
||||
let cpu_percent = cpu_info.cpu_usage();
|
||||
|
||||
format_info.insert(String::from("cpu_percent"), format!("{cpu_percent:0>2.0}"));
|
||||
}
|
||||
|
||||
fn refresh_temp_tokens(format_info: &mut HashMap<String, String>, sys: &mut System) {
|
||||
sys.refresh_components();
|
||||
|
||||
let components = sys.components();
|
||||
for component in components {
|
||||
let key = component.label().replace(' ', "-");
|
||||
let temp = component.temperature();
|
||||
|
||||
format_info.insert(format!("temp_c:{key}"), format!("{temp:.0}"));
|
||||
format_info.insert(format!("temp_f:{key}"), format!("{:.0}", c_to_f(temp)));
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_disk_tokens(format_info: &mut HashMap<String, String>, sys: &mut System) {
|
||||
sys.refresh_disks();
|
||||
|
||||
for disk in sys.disks() {
|
||||
// replace braces to avoid conflict with regex
|
||||
let key = disk
|
||||
.mount_point()
|
||||
.to_str()
|
||||
.map(|s| s.replace(['{', '}'], ""));
|
||||
|
||||
if let Some(key) = key {
|
||||
let total = disk.total_space();
|
||||
let available = disk.available_space();
|
||||
let used = total - available;
|
||||
|
||||
format_info.insert(
|
||||
format!("disk_free:{key}"),
|
||||
bytes_to_gigabytes(available).to_string(),
|
||||
);
|
||||
|
||||
format_info.insert(
|
||||
format!("disk_used:{key}"),
|
||||
bytes_to_gigabytes(used).to_string(),
|
||||
);
|
||||
|
||||
format_info.insert(
|
||||
format!("disk_total:{key}"),
|
||||
bytes_to_gigabytes(total).to_string(),
|
||||
);
|
||||
|
||||
format_info.insert(
|
||||
format!("disk_percent:{key}"),
|
||||
format!("{:0>2.0}", used as f64 / total as f64 * 100.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_network_tokens(
|
||||
format_info: &mut HashMap<String, String>,
|
||||
sys: &mut System,
|
||||
interval: u64,
|
||||
) {
|
||||
sys.refresh_networks();
|
||||
|
||||
for (iface, network) in sys.networks() {
|
||||
format_info.insert(
|
||||
format!("net_down:{iface}"),
|
||||
format!("{:0>2.0}", bytes_to_megabits(network.received()) / interval),
|
||||
);
|
||||
|
||||
format_info.insert(
|
||||
format!("net_up:{iface}"),
|
||||
format!(
|
||||
"{:0>2.0}",
|
||||
bytes_to_megabits(network.transmitted()) / interval
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_system_tokens(format_info: &mut HashMap<String, String>, sys: &System) {
|
||||
// no refresh required for these tokens
|
||||
|
||||
let load_average = sys.load_average();
|
||||
format_info.insert(
|
||||
String::from("load_average:1"),
|
||||
format!("{:.2}", load_average.one),
|
||||
);
|
||||
|
||||
format_info.insert(
|
||||
String::from("load_average:5"),
|
||||
format!("{:.2}", load_average.five),
|
||||
);
|
||||
|
||||
format_info.insert(
|
||||
String::from("load_average:15"),
|
||||
format!("{:.2}", load_average.fifteen),
|
||||
);
|
||||
|
||||
let uptime = Duration::from_secs(sys.uptime()).as_secs();
|
||||
let hours = uptime / 3600;
|
||||
format_info.insert(
|
||||
String::from("uptime"),
|
||||
format!("{:0>2}:{:0>2}", hours, (uptime % 3600) / 60),
|
||||
);
|
||||
}
|
||||
|
||||
/// Converts celsius to fahrenheit.
|
||||
fn c_to_f(c: f32) -> f32 {
|
||||
c * 9.0 / 5.0 + 32.0
|
||||
}
|
||||
|
||||
const fn bytes_to_gigabytes(b: u64) -> u64 {
|
||||
const BYTES_IN_GIGABYTE: u64 = 1_000_000_000;
|
||||
b / BYTES_IN_GIGABYTE
|
||||
}
|
||||
|
||||
const fn bytes_to_megabits(b: u64) -> u64 {
|
||||
const BYTES_IN_MEGABIT: u64 = 125_000;
|
||||
b / BYTES_IN_MEGABIT
|
||||
}
|
314
src/modules/sysinfo/mod.rs
Normal file
314
src/modules/sysinfo/mod.rs
Normal file
|
@ -0,0 +1,314 @@
|
|||
mod parser;
|
||||
mod renderer;
|
||||
mod token;
|
||||
|
||||
use crate::config::{CommonConfig, ModuleOrientation};
|
||||
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
|
||||
use crate::modules::sysinfo::token::{Part, TokenType};
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::{clients, glib_recv, module_impl, send_async, spawn, try_send};
|
||||
use color_eyre::Result;
|
||||
use gtk::prelude::*;
|
||||
use gtk::Label;
|
||||
use serde::Deserialize;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
pub struct SysInfoModule {
|
||||
/// List of strings including formatting tokens.
|
||||
/// For available tokens, see [below](#formatting-tokens).
|
||||
///
|
||||
/// **Required**
|
||||
format: Vec<String>,
|
||||
|
||||
/// Number of seconds between refresh.
|
||||
///
|
||||
/// This can be set as a global interval,
|
||||
/// or passed as an object to customize the interval per-system.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "Interval::default")]
|
||||
interval: Interval,
|
||||
|
||||
/// The orientation of text for the labels.
|
||||
///
|
||||
/// **Valid options**: `horizontal`, `vertical`, `h`, `v`
|
||||
/// <br>
|
||||
/// **Default** : `horizontal`
|
||||
#[serde(default)]
|
||||
orientation: ModuleOrientation,
|
||||
|
||||
/// The orientation by which the labels are laid out.
|
||||
///
|
||||
/// **Valid options**: `horizontal`, `vertical`, `h`, `v`
|
||||
/// <br>
|
||||
/// **Default** : `horizontal`
|
||||
direction: Option<ModuleOrientation>,
|
||||
|
||||
/// See [common options](module-level-options#common-options).
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Copy, Clone)]
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
pub struct Intervals {
|
||||
/// The number of seconds between refreshing memory data.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "default_interval")]
|
||||
memory: u64,
|
||||
|
||||
/// The number of seconds between refreshing CPU data.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "default_interval")]
|
||||
cpu: u64,
|
||||
|
||||
/// The number of seconds between refreshing temperature data.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "default_interval")]
|
||||
temps: u64,
|
||||
|
||||
/// The number of seconds between refreshing disk data.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "default_interval")]
|
||||
disks: u64,
|
||||
|
||||
/// The number of seconds between refreshing network data.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "default_interval")]
|
||||
networks: u64,
|
||||
|
||||
/// The number of seconds between refreshing system data.
|
||||
///
|
||||
/// **Default**: `5`
|
||||
#[serde(default = "default_interval")]
|
||||
system: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Copy, Clone)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
pub enum Interval {
|
||||
All(u64),
|
||||
Individual(Intervals),
|
||||
}
|
||||
|
||||
impl Default for Interval {
|
||||
fn default() -> Self {
|
||||
Self::All(default_interval())
|
||||
}
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
const fn memory(self) -> u64 {
|
||||
match self {
|
||||
Self::All(n) => n,
|
||||
Self::Individual(intervals) => intervals.memory,
|
||||
}
|
||||
}
|
||||
|
||||
const fn cpu(self) -> u64 {
|
||||
match self {
|
||||
Self::All(n) => n,
|
||||
Self::Individual(intervals) => intervals.cpu,
|
||||
}
|
||||
}
|
||||
|
||||
const fn temps(self) -> u64 {
|
||||
match self {
|
||||
Self::All(n) => n,
|
||||
Self::Individual(intervals) => intervals.temps,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn disks(self) -> u64 {
|
||||
match self {
|
||||
Self::All(n) => n,
|
||||
Self::Individual(intervals) => intervals.disks,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn networks(self) -> u64 {
|
||||
match self {
|
||||
Self::All(n) => n,
|
||||
Self::Individual(intervals) => intervals.networks,
|
||||
}
|
||||
}
|
||||
|
||||
const fn system(self) -> u64 {
|
||||
match self {
|
||||
Self::All(n) => n,
|
||||
Self::Individual(intervals) => intervals.system,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn default_interval() -> u64 {
|
||||
5
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
enum RefreshType {
|
||||
Memory,
|
||||
Cpu,
|
||||
Temps,
|
||||
Disks,
|
||||
Network,
|
||||
System,
|
||||
}
|
||||
|
||||
impl TokenType {
|
||||
fn is_affected_by(self, refresh_type: RefreshType) -> bool {
|
||||
match self {
|
||||
Self::CpuFrequency | Self::CpuPercent => refresh_type == RefreshType::Cpu,
|
||||
Self::MemoryFree
|
||||
| Self::MemoryAvailable
|
||||
| Self::MemoryTotal
|
||||
| Self::MemoryUsed
|
||||
| Self::MemoryPercent
|
||||
| Self::SwapFree
|
||||
| Self::SwapTotal
|
||||
| Self::SwapUsed
|
||||
| Self::SwapPercent => refresh_type == RefreshType::Memory,
|
||||
Self::TempC | Self::TempF => refresh_type == RefreshType::Temps,
|
||||
Self::DiskFree
|
||||
| Self::DiskTotal
|
||||
| Self::DiskUsed
|
||||
| Self::DiskPercent
|
||||
| Self::DiskRead
|
||||
| Self::DiskWrite => refresh_type == RefreshType::Disks,
|
||||
Self::NetDown | Self::NetUp => refresh_type == RefreshType::Network,
|
||||
Self::LoadAverage1 | Self::LoadAverage5 | Self::LoadAverage15 => {
|
||||
refresh_type == RefreshType::System
|
||||
}
|
||||
Self::Uptime => refresh_type == RefreshType::System,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Module<gtk::Box> for SysInfoModule {
|
||||
type SendMessage = (usize, String);
|
||||
type ReceiveMessage = ();
|
||||
|
||||
module_impl!("sysinfo");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let interval = self.interval;
|
||||
|
||||
let client = context.client::<clients::sysinfo::Client>();
|
||||
|
||||
let format_tokens = self
|
||||
.format
|
||||
.iter()
|
||||
.map(|format| parser::parse_input(format.as_str()))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
for (i, token_set) in format_tokens.iter().enumerate() {
|
||||
let rendered = Part::render_all(token_set, &client, interval);
|
||||
try_send!(context.tx, ModuleUpdateEvent::Update((i, rendered)));
|
||||
}
|
||||
|
||||
let (refresh_tx, mut refresh_rx) = mpsc::channel(16);
|
||||
|
||||
macro_rules! spawn_refresh {
|
||||
($refresh_type:expr, $func:ident) => {{
|
||||
let tx = refresh_tx.clone();
|
||||
spawn(async move {
|
||||
loop {
|
||||
send_async!(tx, $refresh_type);
|
||||
sleep(Duration::from_secs(interval.$func())).await;
|
||||
}
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
spawn_refresh!(RefreshType::Memory, memory);
|
||||
spawn_refresh!(RefreshType::Cpu, cpu);
|
||||
spawn_refresh!(RefreshType::Temps, temps);
|
||||
spawn_refresh!(RefreshType::Disks, disks);
|
||||
spawn_refresh!(RefreshType::Network, networks);
|
||||
spawn_refresh!(RefreshType::System, system);
|
||||
|
||||
let tx = context.tx.clone();
|
||||
spawn(async move {
|
||||
while let Some(refresh) = refresh_rx.recv().await {
|
||||
match refresh {
|
||||
RefreshType::Memory => client.refresh_memory(),
|
||||
RefreshType::Cpu => client.refresh_cpu(),
|
||||
RefreshType::Temps => client.refresh_temps(),
|
||||
RefreshType::Disks => client.refresh_disks(),
|
||||
RefreshType::Network => client.refresh_network(),
|
||||
RefreshType::System => client.refresh_load_average(),
|
||||
};
|
||||
|
||||
for (i, token_set) in format_tokens.iter().enumerate() {
|
||||
let is_affected = token_set
|
||||
.iter()
|
||||
.filter_map(|part| {
|
||||
if let Part::Token(token) = part {
|
||||
Some(token)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.any(|t| t.token.is_affected_by(refresh));
|
||||
|
||||
if is_affected {
|
||||
let rendered = Part::render_all(token_set, &client, interval);
|
||||
send_async!(tx, ModuleUpdateEvent::Update((i, rendered)));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn into_widget(
|
||||
self,
|
||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_info: &ModuleInfo,
|
||||
) -> Result<ModuleParts<gtk::Box>> {
|
||||
let layout = match self.direction {
|
||||
Some(orientation) => orientation,
|
||||
None => self.orientation,
|
||||
};
|
||||
|
||||
let container = gtk::Box::new(layout.into(), 10);
|
||||
|
||||
let mut labels = Vec::new();
|
||||
|
||||
for _ in &self.format {
|
||||
let label = Label::builder().use_markup(true).build();
|
||||
|
||||
label.add_class("item");
|
||||
label.set_angle(self.orientation.to_angle());
|
||||
|
||||
container.add(&label);
|
||||
labels.push(label);
|
||||
}
|
||||
|
||||
glib_recv!(context.subscribe(), data => {
|
||||
let label = &labels[data.0];
|
||||
label.set_label_escaped(&data.1);
|
||||
});
|
||||
|
||||
Ok(ModuleParts {
|
||||
widget: container,
|
||||
popup: None,
|
||||
})
|
||||
}
|
||||
}
|
460
src/modules/sysinfo/parser.rs
Normal file
460
src/modules/sysinfo/parser.rs
Normal file
|
@ -0,0 +1,460 @@
|
|||
use crate::clients::sysinfo::{Function, Prefix};
|
||||
use crate::modules::sysinfo::token::{Alignment, Formatting, Part, Token, TokenType};
|
||||
use color_eyre::{Report, Result};
|
||||
use std::iter::Peekable;
|
||||
use std::str::{Chars, FromStr};
|
||||
|
||||
impl FromStr for TokenType {
|
||||
type Err = Report;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
"cpu_frequency" => Ok(Self::CpuFrequency),
|
||||
"cpu_percent" => Ok(Self::CpuPercent),
|
||||
|
||||
"memory_free" => Ok(Self::MemoryFree),
|
||||
"memory_available" => Ok(Self::MemoryAvailable),
|
||||
"memory_total" => Ok(Self::MemoryTotal),
|
||||
"memory_used" => Ok(Self::MemoryUsed),
|
||||
"memory_percent" => Ok(Self::MemoryPercent),
|
||||
|
||||
"swap_free" => Ok(Self::SwapFree),
|
||||
"swap_total" => Ok(Self::SwapTotal),
|
||||
"swap_used" => Ok(Self::SwapUsed),
|
||||
"swap_percent" => Ok(Self::SwapPercent),
|
||||
|
||||
"temp_c" => Ok(Self::TempC),
|
||||
"temp_f" => Ok(Self::TempF),
|
||||
|
||||
"disk_free" => Ok(Self::DiskFree),
|
||||
"disk_total" => Ok(Self::DiskTotal),
|
||||
"disk_used" => Ok(Self::DiskUsed),
|
||||
"disk_percent" => Ok(Self::DiskPercent),
|
||||
"disk_read" => Ok(Self::DiskRead),
|
||||
"disk_write" => Ok(Self::DiskWrite),
|
||||
|
||||
"net_down" => Ok(Self::NetDown),
|
||||
"net_up" => Ok(Self::NetUp),
|
||||
|
||||
"load_average_1" => Ok(Self::LoadAverage1),
|
||||
"load_average_5" => Ok(Self::LoadAverage5),
|
||||
"load_average_15" => Ok(Self::LoadAverage15),
|
||||
"uptime" => Ok(Self::Uptime),
|
||||
_ => Err(Report::msg(format!("invalid token type: '{s}'"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Function {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"sum" => Ok(Self::Sum),
|
||||
"min" => Ok(Self::Min),
|
||||
"max" => Ok(Self::Max),
|
||||
"mean" => Ok(Self::Mean),
|
||||
"" => Err(()),
|
||||
_ => Ok(Self::Name(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub(crate) fn default_for(token_type: TokenType) -> Self {
|
||||
match token_type {
|
||||
TokenType::CpuFrequency
|
||||
| TokenType::CpuPercent
|
||||
| TokenType::TempC
|
||||
| TokenType::DiskPercent => Self::Mean,
|
||||
TokenType::DiskFree
|
||||
| TokenType::DiskTotal
|
||||
| TokenType::DiskUsed
|
||||
| TokenType::DiskRead
|
||||
| TokenType::DiskWrite
|
||||
| TokenType::NetDown
|
||||
| TokenType::NetUp => Self::Sum,
|
||||
_ => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Prefix {
|
||||
pub(crate) fn default_for(token_type: TokenType) -> Self {
|
||||
match token_type {
|
||||
TokenType::CpuFrequency
|
||||
| TokenType::MemoryFree
|
||||
| TokenType::MemoryAvailable
|
||||
| TokenType::MemoryTotal
|
||||
| TokenType::MemoryUsed
|
||||
| TokenType::SwapFree
|
||||
| TokenType::SwapTotal
|
||||
| TokenType::SwapUsed
|
||||
| TokenType::DiskFree
|
||||
| TokenType::DiskTotal
|
||||
| TokenType::DiskUsed => Self::Giga,
|
||||
TokenType::DiskRead | TokenType::DiskWrite => Self::Mega,
|
||||
TokenType::NetDown | TokenType::NetUp => Self::MegaBit,
|
||||
_ => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Prefix {
|
||||
type Err = Report;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
"k" => Ok(Prefix::Kilo),
|
||||
"M" => Ok(Prefix::Mega),
|
||||
"G" => Ok(Prefix::Giga),
|
||||
"T" => Ok(Prefix::Tera),
|
||||
"P" => Ok(Prefix::Peta),
|
||||
|
||||
"ki" => Ok(Prefix::Kibi),
|
||||
"Mi" => Ok(Prefix::Mebi),
|
||||
"Gi" => Ok(Prefix::Gibi),
|
||||
"Ti" => Ok(Prefix::Tebi),
|
||||
"Pi" => Ok(Prefix::Pebi),
|
||||
|
||||
"kb" => Ok(Prefix::KiloBit),
|
||||
"Mb" => Ok(Prefix::MegaBit),
|
||||
"Gb" => Ok(Prefix::GigaBit),
|
||||
|
||||
_ => Err(Report::msg(format!("invalid prefix: {s}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<char> for Alignment {
|
||||
type Error = Report;
|
||||
|
||||
fn try_from(value: char) -> Result<Self> {
|
||||
match value {
|
||||
'<' => Ok(Self::Left),
|
||||
'^' => Ok(Self::Center),
|
||||
'>' => Ok(Self::Right),
|
||||
_ => Err(Report::msg(format!("Unknown alignment: {value}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Formatting {
|
||||
fn default_for(token_type: TokenType) -> Self {
|
||||
match token_type {
|
||||
TokenType::CpuFrequency
|
||||
| TokenType::LoadAverage1
|
||||
| TokenType::LoadAverage5
|
||||
| TokenType::LoadAverage15 => Self {
|
||||
width: 0,
|
||||
fill: '0',
|
||||
align: Alignment::default(),
|
||||
precision: 2,
|
||||
},
|
||||
TokenType::CpuPercent => Self {
|
||||
width: 2,
|
||||
fill: '0',
|
||||
align: Alignment::default(),
|
||||
precision: 0,
|
||||
},
|
||||
TokenType::MemoryFree
|
||||
| TokenType::MemoryAvailable
|
||||
| TokenType::MemoryTotal
|
||||
| TokenType::MemoryUsed
|
||||
| TokenType::MemoryPercent
|
||||
| TokenType::SwapFree
|
||||
| TokenType::SwapTotal
|
||||
| TokenType::SwapUsed
|
||||
| TokenType::SwapPercent => Self {
|
||||
width: 4,
|
||||
fill: '0',
|
||||
align: Alignment::default(),
|
||||
precision: 1,
|
||||
},
|
||||
_ => Self {
|
||||
width: 0,
|
||||
fill: '0',
|
||||
align: Alignment::default(),
|
||||
precision: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_input(input: &str) -> Result<Vec<Part>> {
|
||||
let mut tokens = vec![];
|
||||
|
||||
let mut chars = input.chars().peekable();
|
||||
|
||||
let mut next_char = chars.peek().copied();
|
||||
while let Some(char) = next_char {
|
||||
let token = if char == '{' {
|
||||
chars.next();
|
||||
parse_dynamic(&mut chars)?
|
||||
} else {
|
||||
parse_static(&mut chars)
|
||||
};
|
||||
|
||||
tokens.push(token);
|
||||
next_char = chars.peek().copied();
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
fn parse_static(chars: &mut Peekable<Chars>) -> Part {
|
||||
let mut str = String::new();
|
||||
|
||||
let mut next_char = chars.next_if(|&c| c != '{');
|
||||
while let Some(char) = next_char {
|
||||
if char == '{' {
|
||||
break;
|
||||
}
|
||||
|
||||
str.push(char);
|
||||
next_char = chars.next_if(|&c| c != '{');
|
||||
}
|
||||
|
||||
Part::Static(str)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
enum DynamicMode {
|
||||
Token,
|
||||
Name,
|
||||
Prefix,
|
||||
}
|
||||
|
||||
fn parse_dynamic(chars: &mut Peekable<Chars>) -> Result<Part> {
|
||||
let mut mode = DynamicMode::Token;
|
||||
|
||||
let mut token_str = String::new();
|
||||
let mut func_str = String::new();
|
||||
let mut prefix_str = String::new();
|
||||
|
||||
// we don't want to peek here as that would be the same char as the outer loop
|
||||
let mut next_char = chars.next();
|
||||
while let Some(char) = next_char {
|
||||
match char {
|
||||
'}' | ':' => break,
|
||||
'@' => mode = DynamicMode::Name,
|
||||
'#' => mode = DynamicMode::Prefix,
|
||||
_ => match mode {
|
||||
DynamicMode::Token => token_str.push(char),
|
||||
DynamicMode::Name => func_str.push(char),
|
||||
DynamicMode::Prefix => prefix_str.push(char),
|
||||
},
|
||||
}
|
||||
|
||||
next_char = chars.next();
|
||||
}
|
||||
|
||||
let token_type = token_str.parse()?;
|
||||
let mut formatting = Formatting::default_for(token_type);
|
||||
|
||||
if next_char == Some(':') {
|
||||
formatting = parse_formatting(chars, formatting)?;
|
||||
}
|
||||
|
||||
let token = Token {
|
||||
token: token_type,
|
||||
function: func_str
|
||||
.parse()
|
||||
.unwrap_or_else(|()| Function::default_for(token_type)),
|
||||
prefix: prefix_str
|
||||
.parse()
|
||||
.unwrap_or_else(|_| Prefix::default_for(token_type)),
|
||||
formatting,
|
||||
};
|
||||
|
||||
Ok(Part::Token(token))
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
enum FormattingMode {
|
||||
WidthFillAlign,
|
||||
Precision,
|
||||
}
|
||||
|
||||
fn parse_formatting(chars: &mut Peekable<Chars>, mut formatting: Formatting) -> Result<Formatting> {
|
||||
let mut width_string = String::new();
|
||||
let mut precision_string = String::new();
|
||||
|
||||
let mut mode = FormattingMode::WidthFillAlign;
|
||||
|
||||
let mut next_char = chars.next();
|
||||
while let Some(char) = next_char {
|
||||
match (char, mode) {
|
||||
('}', _) => break,
|
||||
('.', _) => mode = FormattingMode::Precision,
|
||||
(_, FormattingMode::Precision) => precision_string.push(char),
|
||||
('1'..='9', FormattingMode::WidthFillAlign) => width_string.push(char),
|
||||
('<' | '^' | '>', FormattingMode::WidthFillAlign) => {
|
||||
formatting.align = Alignment::try_from(char)?;
|
||||
}
|
||||
(_, FormattingMode::WidthFillAlign) => formatting.fill = char,
|
||||
};
|
||||
|
||||
next_char = chars.next();
|
||||
}
|
||||
|
||||
if !width_string.is_empty() {
|
||||
formatting.width = width_string.parse()?;
|
||||
}
|
||||
|
||||
if !precision_string.is_empty() {
|
||||
formatting.precision = precision_string.parse()?;
|
||||
}
|
||||
|
||||
Ok(formatting)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn static_only() {
|
||||
let tokens = parse_input("hello world").unwrap();
|
||||
println!("{tokens:?}");
|
||||
|
||||
assert_eq!(tokens.len(), 1);
|
||||
assert!(matches!(&tokens[0], Part::Static(str) if str == "hello world"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let tokens = parse_input("{cpu_frequency}").unwrap();
|
||||
println!("{tokens:?}");
|
||||
|
||||
assert_eq!(tokens.len(), 1);
|
||||
|
||||
assert!(matches!(&tokens[0], Part::Token(_)));
|
||||
let Part::Token(token) = tokens.get(0).unwrap() else {
|
||||
return;
|
||||
};
|
||||
|
||||
assert_eq!(token.token, TokenType::CpuFrequency);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named() {
|
||||
let tokens = parse_input("{cpu_frequency@cpu0}").unwrap();
|
||||
println!("{tokens:?}");
|
||||
|
||||
assert_eq!(tokens.len(), 1);
|
||||
|
||||
assert!(matches!(&tokens[0], Part::Token(_)));
|
||||
let Part::Token(token) = tokens.get(0).unwrap() else {
|
||||
return;
|
||||
};
|
||||
|
||||
assert_eq!(token.token, TokenType::CpuFrequency);
|
||||
assert!(matches!(&token.function, Function::Name(n) if n == "cpu0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversion() {
|
||||
let tokens = parse_input("{cpu_frequency#G}").unwrap();
|
||||
println!("{tokens:?}");
|
||||
|
||||
assert_eq!(tokens.len(), 1);
|
||||
|
||||
assert!(matches!(&tokens[0], Part::Token(_)));
|
||||
let Part::Token(token) = tokens.get(0).unwrap() else {
|
||||
return;
|
||||
};
|
||||
|
||||
assert_eq!(token.token, TokenType::CpuFrequency);
|
||||
assert_eq!(token.prefix, Prefix::Giga);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatting_basic() {
|
||||
let tokens = parse_input("{cpu_frequency:.2}").unwrap();
|
||||
println!("{tokens:?}");
|
||||
|
||||
assert_eq!(tokens.len(), 1);
|
||||
|
||||
assert!(matches!(&tokens[0], Part::Token(_)));
|
||||
let Part::Token(token) = tokens.get(0).unwrap() else {
|
||||
return;
|
||||
};
|
||||
|
||||
assert_eq!(token.token, TokenType::CpuFrequency);
|
||||
assert_eq!(token.formatting.precision, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatting_complex() {
|
||||
let tokens = parse_input("{cpu_frequency:0<5.2}").unwrap();
|
||||
println!("{tokens:?}");
|
||||
|
||||
assert_eq!(tokens.len(), 1);
|
||||
|
||||
assert!(matches!(&tokens[0], Part::Token(_)));
|
||||
let Part::Token(token) = tokens.get(0).unwrap() else {
|
||||
return;
|
||||
};
|
||||
|
||||
assert_eq!(token.token, TokenType::CpuFrequency);
|
||||
assert_eq!(token.formatting.fill, '0');
|
||||
assert_eq!(token.formatting.align, Alignment::Left);
|
||||
assert_eq!(token.formatting.width, 5);
|
||||
assert_eq!(token.formatting.precision, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex() {
|
||||
let tokens = parse_input("{cpu_frequency@cpu0#G:.2}").unwrap();
|
||||
println!("{tokens:?}");
|
||||
|
||||
assert_eq!(tokens.len(), 1);
|
||||
|
||||
assert!(matches!(&tokens[0], Part::Token(_)));
|
||||
let Part::Token(token) = tokens.get(0).unwrap() else {
|
||||
return;
|
||||
};
|
||||
|
||||
assert_eq!(token.token, TokenType::CpuFrequency);
|
||||
assert!(matches!(&token.function, Function::Name(n) if n == "cpu0"));
|
||||
assert_eq!(token.prefix, Prefix::Giga);
|
||||
assert_eq!(token.formatting.precision, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_then_token() {
|
||||
let tokens = parse_input("Freq: {cpu_frequency#G:.2}").unwrap();
|
||||
println!("{tokens:?}");
|
||||
|
||||
assert_eq!(tokens.len(), 2);
|
||||
|
||||
assert!(matches!(&tokens[0], Part::Static(str) if str == "Freq: "));
|
||||
|
||||
assert!(matches!(&tokens[1], Part::Token(_)));
|
||||
let Part::Token(token) = tokens.get(1).unwrap() else {
|
||||
return;
|
||||
};
|
||||
|
||||
assert_eq!(token.token, TokenType::CpuFrequency);
|
||||
assert_eq!(token.formatting.precision, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_then_static() {
|
||||
let tokens = parse_input("{cpu_frequency#G:.2} GHz").unwrap();
|
||||
println!("{tokens:?}");
|
||||
|
||||
assert_eq!(tokens.len(), 2);
|
||||
|
||||
assert!(matches!(&tokens[0], Part::Token(_)));
|
||||
let Part::Token(token) = tokens.get(0).unwrap() else {
|
||||
return;
|
||||
};
|
||||
|
||||
assert_eq!(token.token, TokenType::CpuFrequency);
|
||||
assert_eq!(token.formatting.precision, 2);
|
||||
|
||||
assert!(matches!(&tokens[1], Part::Static(str) if str == " GHz"));
|
||||
}
|
||||
}
|
91
src/modules/sysinfo/renderer.rs
Normal file
91
src/modules/sysinfo/renderer.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
use super::token::{Alignment, Part, Token, TokenType};
|
||||
use super::Interval;
|
||||
use crate::clients;
|
||||
use crate::clients::sysinfo::{Value, ValueSet};
|
||||
|
||||
pub enum TokenValue {
|
||||
Number(f64),
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl Part {
|
||||
pub fn render_all(
|
||||
tokens: &[Self],
|
||||
client: &clients::sysinfo::Client,
|
||||
interval: Interval,
|
||||
) -> String {
|
||||
tokens
|
||||
.iter()
|
||||
.map(|part| part.render(client, interval))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn render(&self, client: &clients::sysinfo::Client, interval: Interval) -> String {
|
||||
match self {
|
||||
Part::Static(str) => str.clone(),
|
||||
Part::Token(token) => {
|
||||
match token.get(client, interval) {
|
||||
TokenValue::Number(value) => {
|
||||
let fmt = token.formatting;
|
||||
let mut str = format!("{value:.precision$}", precision = fmt.precision);
|
||||
|
||||
// fill/align doesn't support parameterization so we need our own impl
|
||||
let mut add_to_end = fmt.align == Alignment::Right;
|
||||
while str.len() < fmt.width {
|
||||
if add_to_end {
|
||||
str.push(fmt.fill);
|
||||
} else {
|
||||
str.insert(0, fmt.fill);
|
||||
}
|
||||
|
||||
if fmt.align == Alignment::Center {
|
||||
add_to_end = !add_to_end;
|
||||
}
|
||||
}
|
||||
|
||||
str
|
||||
}
|
||||
TokenValue::String(value) => value,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn get(&self, client: &clients::sysinfo::Client, interval: Interval) -> TokenValue {
|
||||
let get = |value: Value| TokenValue::Number(value.get(self.prefix));
|
||||
let apply = |set: ValueSet| TokenValue::Number(set.apply(&self.function, self.prefix));
|
||||
|
||||
match self.token {
|
||||
// Number tokens
|
||||
TokenType::CpuFrequency => apply(client.cpu_frequency()),
|
||||
TokenType::CpuPercent => apply(client.cpu_percent()),
|
||||
TokenType::MemoryFree => get(client.memory_free()),
|
||||
TokenType::MemoryAvailable => get(client.memory_available()),
|
||||
TokenType::MemoryTotal => get(client.memory_total()),
|
||||
TokenType::MemoryUsed => get(client.memory_used()),
|
||||
TokenType::MemoryPercent => get(client.memory_percent()),
|
||||
TokenType::SwapFree => get(client.swap_free()),
|
||||
TokenType::SwapTotal => get(client.swap_total()),
|
||||
TokenType::SwapUsed => get(client.swap_used()),
|
||||
TokenType::SwapPercent => get(client.swap_percent()),
|
||||
TokenType::TempC => apply(client.temp_c()),
|
||||
TokenType::TempF => apply(client.temp_f()),
|
||||
TokenType::DiskFree => apply(client.disk_free()),
|
||||
TokenType::DiskTotal => apply(client.disk_total()),
|
||||
TokenType::DiskUsed => apply(client.disk_used()),
|
||||
TokenType::DiskPercent => apply(client.disk_percent()),
|
||||
TokenType::DiskRead => apply(client.disk_read(interval)),
|
||||
TokenType::DiskWrite => apply(client.disk_write(interval)),
|
||||
TokenType::NetDown => apply(client.net_down(interval)),
|
||||
TokenType::NetUp => apply(client.net_up(interval)),
|
||||
TokenType::LoadAverage1 => get(client.load_average_1()),
|
||||
TokenType::LoadAverage5 => get(client.load_average_5()),
|
||||
TokenType::LoadAverage15 => get(client.load_average_15()),
|
||||
|
||||
// String tokens
|
||||
TokenType::Uptime => TokenValue::String(client.uptime()),
|
||||
}
|
||||
}
|
||||
}
|
66
src/modules/sysinfo/token.rs
Normal file
66
src/modules/sysinfo/token.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use crate::clients::sysinfo::{Function, Prefix};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TokenType {
|
||||
CpuFrequency,
|
||||
CpuPercent,
|
||||
|
||||
MemoryFree,
|
||||
MemoryAvailable,
|
||||
MemoryTotal,
|
||||
MemoryUsed,
|
||||
MemoryPercent,
|
||||
|
||||
SwapFree,
|
||||
SwapTotal,
|
||||
SwapUsed,
|
||||
SwapPercent,
|
||||
|
||||
TempC,
|
||||
TempF,
|
||||
|
||||
DiskFree,
|
||||
DiskTotal,
|
||||
DiskUsed,
|
||||
DiskPercent,
|
||||
DiskRead,
|
||||
DiskWrite,
|
||||
|
||||
NetDown,
|
||||
NetUp,
|
||||
|
||||
LoadAverage1,
|
||||
LoadAverage5,
|
||||
LoadAverage15,
|
||||
Uptime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Token {
|
||||
pub token: TokenType,
|
||||
pub function: Function,
|
||||
pub prefix: Prefix,
|
||||
pub formatting: Formatting,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Part {
|
||||
Static(String),
|
||||
Token(Token),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Formatting {
|
||||
pub width: usize,
|
||||
pub fill: char,
|
||||
pub align: Alignment,
|
||||
pub precision: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub enum Alignment {
|
||||
#[default]
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue