1
0
Fork 0
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:
Jake Stanger 2025-02-15 23:10:51 +00:00 committed by GitHub
commit 7c7b6c7323
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 1633 additions and 589 deletions

105
Cargo.lock generated
View file

@ -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",
]

View file

@ -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

View file

@ -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.
![Screenshot showing sys-info module with widgets for all of the types of formatting tokens](https://user-images.githubusercontent.com/5057870/196059090-4056d083-69f0-4e6f-9673-9e35dc29d9f0.png)
@ -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

View file

@ -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}"
]
}

View file

@ -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}"
]
},

View file

@ -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}",
]

View file

@ -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}%'

View file

@ -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
View 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
}

View file

@ -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
View 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,
})
}
}

View 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"));
}
}

View 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()),
}
}
}

View 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,
}