├── .gitignore ├── include ├── sketchybar.c └── sketchybar.h ├── assets └── stats_provider.png ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── homebrew.yml │ └── release.yml ├── Cargo.toml ├── src ├── stats │ ├── mod.rs │ ├── network.rs │ ├── system.rs │ ├── disk.rs │ ├── battery.rs │ ├── uptime.rs │ ├── cpu.rs │ └── memory.rs ├── sketchybar.rs ├── cli.rs └── main.rs ├── AGENTS.md ├── README.md ├── Cargo.lock └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /include/sketchybar.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "sketchybar.h" 3 | -------------------------------------------------------------------------------- /assets/stats_provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joncrangle/sketchybar-system-stats/HEAD/assets/stats_provider.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "cargo" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stats_provider" 3 | version = "0.8.0" 4 | description = "A simple system stats event provider for Sketchybar." 5 | edition = "2021" 6 | build = "build.rs" 7 | 8 | [dependencies] 9 | anyhow = "1.0.100" 10 | clap = { version = "4.5.53", features = ["derive"] } 11 | starship-battery = "0.10.3" 12 | sysinfo = { version = "0.37.2", default-features = false, features = ["component", "disk", "network", "system"] } 13 | tokio = { version = "1", features = ["full"] } 14 | 15 | [build-dependencies] 16 | cc = "1.2.49" 17 | -------------------------------------------------------------------------------- /src/stats/mod.rs: -------------------------------------------------------------------------------- 1 | mod battery; 2 | mod cpu; 3 | mod disk; 4 | mod memory; 5 | mod network; 6 | mod system; 7 | mod uptime; 8 | 9 | use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind}; 10 | 11 | pub use battery::get_battery_stats; 12 | pub use cpu::get_cpu_stats; 13 | pub use disk::get_disk_stats; 14 | pub use memory::get_memory_stats; 15 | pub use network::get_network_stats; 16 | pub use system::get_system_stats; 17 | pub use uptime::get_uptime_stats; 18 | 19 | pub fn build_refresh_kind() -> RefreshKind { 20 | RefreshKind::nothing() 21 | .with_cpu(CpuRefreshKind::nothing().with_cpu_usage().with_frequency()) 22 | .with_memory(MemoryRefreshKind::nothing().with_ram().with_swap()) 23 | } 24 | -------------------------------------------------------------------------------- /src/stats/network.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | use sysinfo::Networks; 3 | 4 | pub fn get_network_stats( 5 | n: &Networks, 6 | interfaces: Option<&[String]>, 7 | interval: u32, 8 | no_units: bool, 9 | buf: &mut String, 10 | ) { 11 | if interval == 0 { 12 | return; 13 | } 14 | 15 | let interfaces_to_check: Vec<&str> = match interfaces { 16 | Some(ifaces) => ifaces.iter().map(String::as_str).collect(), 17 | None => n 18 | .keys() 19 | .map(|interface_name| interface_name.as_str()) 20 | .collect(), 21 | }; 22 | 23 | let unit = if no_units { "" } else { "KB/s" }; 24 | 25 | for interface in interfaces_to_check { 26 | if let Some(data) = n.get(interface) { 27 | let _ = write!( 28 | buf, 29 | "NETWORK_RX_{}=\"{}{unit}\" NETWORK_TX_{}=\"{}{unit}\" ", 30 | interface, 31 | (data.received() / 1024) / interval as u64, 32 | interface, 33 | (data.transmitted() / 1024) / interval as u64 34 | ); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/stats/system.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | use sysinfo::System; 3 | 4 | pub fn get_system_stats(flags: &[&str], buf: &mut String) { 5 | for &flag in flags { 6 | match flag { 7 | "arch" => { 8 | let _ = write!(buf, "ARCH=\"{}\" ", System::cpu_arch()); 9 | } 10 | "distro" => { 11 | let _ = write!(buf, "DISTRO=\"{}\" ", System::distribution_id()); 12 | } 13 | 14 | "host_name" => { 15 | let _ = write!(buf, "HOST_NAME=\"{}\" ", System::host_name().unwrap()); 16 | } 17 | "kernel_version" => { 18 | let _ = write!( 19 | buf, 20 | "KERNEL_VERSION=\"{}\" ", 21 | System::kernel_version().unwrap() 22 | ); 23 | } 24 | "name" => { 25 | let _ = write!(buf, "SYSTEM_NAME=\"{}\" ", System::name().unwrap()); 26 | } 27 | "os_version" => { 28 | let _ = write!(buf, "OS_VERSION=\"{}\" ", System::os_version().unwrap()); 29 | } 30 | "long_os_version" => { 31 | let _ = write!( 32 | buf, 33 | "LONG_OS_VERSION=\"{}\" ", 34 | System::long_os_version().unwrap() 35 | ); 36 | } 37 | _ => {} 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # Agent Development Guide 2 | 3 | ## Build/Test Commands 4 | - `cargo build` - Build the project 5 | - `cargo build --release` - Build optimized release version 6 | - `cargo test` - Run all tests 7 | - `cargo test ` - Run a single test (e.g., `cargo test test_validate_cli_with_all_flag`) 8 | - `cargo fmt` - Format code 9 | - `cargo clippy` - Run linting checks 10 | 11 | ## Code Style 12 | - Naming: `snake_case` for functions/variables/modules, `PascalCase` for structs/enums, `SCREAMING_SNAKE_CASE` for constants 13 | - Imports: External crates first, then local `mod` and `use` statements, group by visibility (pub before private) 14 | - Error handling: `anyhow::Result` for fallible functions, `anyhow::bail!()` for errors, `?` for propagation, `.context()` for error context 15 | - String formatting: `format!()` for dynamic, string literals for static, `String::with_capacity()` for buffers 16 | - Function signatures: Accept `&str` slices, return owned `String` only when necessary 17 | - Testing: Place unit tests in `#[cfg(test)] mod tests` at bottom of each file, use descriptive names like `test__` 18 | 19 | ## Architecture 20 | - Modular structure: `src/` contains `main.rs`, `cli.rs`, `sketchybar.rs`, and `stats/` module 21 | - Stats module: Individual stat files (`cpu.rs`, `disk.rs`, etc.) with public functions exported via `mod.rs` 22 | - CLI: `clap` with `Parser` derive, validation in separate `validate_cli()` function, constants for defaults/limits 23 | - Async runtime: `tokio::main` macro, `tokio::select!` for concurrent operations, `tokio::time::sleep` for intervals 24 | - Platform: macOS only - use `#[cfg(target_os = "macos")]` for platform-specific code 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | permissions: 13 | contents: read 14 | 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | build: 21 | name: Testing ${{ matrix.build_target }} 22 | runs-on: ${{ matrix.os }} 23 | strategy: 24 | matrix: 25 | include: 26 | - build_target: macos-aarch64 27 | os: macos-latest 28 | artifact_suffix: macos-aarch64 29 | target: aarch64-apple-darwin 30 | - build_target: macos-x86_64 31 | os: macos-latest 32 | artifact_suffix: macos-x86_64 33 | target: x86_64-apple-darwin 34 | steps: 35 | - uses: actions/checkout@v6 36 | 37 | - name: Install Rust 38 | uses: dtolnay/rust-toolchain@stable 39 | with: 40 | target: ${{ matrix.target }} 41 | 42 | - name: Running cargo build 43 | run: cargo build --verbose --locked --target ${{ matrix.target }} 44 | 45 | - name: Running cargo test 46 | run: cargo test --verbose --locked --target ${{ matrix.target }} 47 | 48 | # Check Rust code formatting. 49 | fmt: 50 | name: Running `cargo fmt` 51 | runs-on: macos-latest 52 | steps: 53 | - uses: actions/checkout@v6 54 | 55 | - name: Install Rust 56 | uses: dtolnay/rust-toolchain@stable 57 | with: 58 | components: rustfmt 59 | 60 | - name: cargo fmt 61 | run: cargo fmt --all -- --check 62 | 63 | # Run `cargo clippy` on all the targets in all workspace members with all 64 | # features enabled, and return an error if there are any clippy suggestions. 65 | clippy: 66 | name: Running `cargo clippy` 67 | runs-on: ${{ matrix.os }} 68 | strategy: 69 | matrix: 70 | include: 71 | - build_target: macos-aarch64 72 | os: macos-latest 73 | artifact_suffix: macos-aarch64 74 | target: aarch64-apple-darwin 75 | - build_target: macos-x86_64 76 | os: macos-latest 77 | artifact_suffix: macos-x86_64 78 | target: x86_64-apple-darwin 79 | steps: 80 | - uses: actions/checkout@v6 81 | 82 | - name: Install Rust 83 | uses: dtolnay/rust-toolchain@stable 84 | with: 85 | components: clippy 86 | targets: ${{ matrix.target }} 87 | 88 | - name: cargo clippy 89 | run: cargo clippy --locked --no-deps --workspace --all-targets --all-features --verbose -- -D warnings 90 | -------------------------------------------------------------------------------- /src/stats/disk.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | use sysinfo::Disks; 3 | 4 | const BYTES_PER_GB: f32 = 1_073_741_824.0; 5 | 6 | pub fn get_disk_stats(disks: &Disks, flags: &[&str], no_units: bool, buf: &mut String) { 7 | let (total_space, used_space) = disks.list().iter().fold((0, 0), |(total, used), disk| { 8 | ( 9 | total + disk.total_space(), 10 | used + disk.total_space() - disk.available_space(), 11 | ) 12 | }); 13 | let disk_usage_percentage = if total_space > 0 { 14 | ((used_space as f32 / total_space as f32) * 100.0).round() as u32 15 | } else { 16 | 0 17 | }; 18 | 19 | for &flag in flags { 20 | match flag { 21 | "count" => { 22 | let _ = write!(buf, "DISK_COUNT=\"{}\" ", disks.list().len()); 23 | } 24 | "free" => { 25 | let unit = if no_units { "" } else { "GB" }; 26 | let _ = write!( 27 | buf, 28 | "DISK_FREE=\"{:.1}{unit}\" ", 29 | (total_space as f32 - used_space as f32) / BYTES_PER_GB 30 | ); 31 | } 32 | "total" => { 33 | let unit = if no_units { "" } else { "GB" }; 34 | let _ = write!( 35 | buf, 36 | "DISK_TOTAL=\"{:.1}{unit}\" ", 37 | total_space as f32 / BYTES_PER_GB 38 | ); 39 | } 40 | "used" => { 41 | let unit = if no_units { "" } else { "GB" }; 42 | let _ = write!( 43 | buf, 44 | "DISK_USED=\"{:.1}{unit}\" ", 45 | used_space as f32 / BYTES_PER_GB 46 | ); 47 | } 48 | "usage" => { 49 | let unit = if no_units { "" } else { "%" }; 50 | let _ = write!(buf, "DISK_USAGE=\"{disk_usage_percentage}{unit}\" "); 51 | } 52 | _ => {} 53 | } 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | 61 | #[test] 62 | fn test_get_disk_stats_with_units() { 63 | let disks = Disks::new_with_refreshed_list(); 64 | let mut buf = String::new(); 65 | 66 | get_disk_stats(&disks, &["count", "total"], false, &mut buf); 67 | 68 | assert!(buf.contains("DISK_COUNT=")); 69 | } 70 | 71 | #[test] 72 | fn test_get_disk_stats_without_units() { 73 | let disks = Disks::new_with_refreshed_list(); 74 | let mut buf = String::new(); 75 | 76 | get_disk_stats(&disks, &["total"], true, &mut buf); 77 | 78 | if !buf.is_empty() { 79 | assert!(!buf.contains("GB")); 80 | } 81 | } 82 | 83 | #[test] 84 | fn test_get_disk_stats_empty_flags() { 85 | let disks = Disks::new_with_refreshed_list(); 86 | let mut buf = String::new(); 87 | 88 | get_disk_stats(&disks, &[], false, &mut buf); 89 | 90 | assert_eq!(buf, ""); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /.github/workflows/homebrew.yml: -------------------------------------------------------------------------------- 1 | name: Update Homebrew Formula 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | workflow_dispatch: 8 | inputs: 9 | version: 10 | description: "Version to test (without the v prefix)" 11 | required: true 12 | default: "0.8.0" 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | update-formula: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout main repository 22 | uses: actions/checkout@v6 23 | 24 | - name: Get release version 25 | id: get_version 26 | run: | 27 | if [ -n "${{ github.event.inputs.version }}" ]; then 28 | VERSION=${{ github.event.inputs.version }} 29 | else 30 | VERSION=${GITHUB_REF#refs/tags/v} 31 | fi 32 | echo "version=$VERSION" >> $GITHUB_OUTPUT 33 | 34 | - name: Download ARM binary SHA 35 | id: arm 36 | run: | 37 | VERSION=${{ github.event.inputs.version || github.ref_name }} 38 | curl -L -o arm.sha256 \ 39 | https://github.com/joncrangle/sketchybar-system-stats/releases/download/${VERSION}/stats_provider-${VERSION}-aarch64-apple-darwin.tar.gz.sha256 40 | ARM_SHA256=$(awk '{print $1}' arm.sha256) 41 | echo "arm_sha256=$ARM_SHA256" >> $GITHUB_OUTPUT 42 | 43 | - name: Download Intel binary SHA 44 | id: intel 45 | run: | 46 | VERSION=${{ github.event.inputs.version || github.ref_name }} 47 | curl -L -o intel.sha256 \ 48 | https://github.com/joncrangle/sketchybar-system-stats/releases/download/${VERSION}/stats_provider-${VERSION}-x86_64-apple-darwin.tar.gz.sha256 49 | INTEL_SHA256=$(awk '{print $1}' intel.sha256) 50 | echo "intel_sha256=$INTEL_SHA256" >> $GITHUB_OUTPUT 51 | 52 | - name: Clone Homebrew tap 53 | uses: actions/checkout@v6 54 | with: 55 | repository: joncrangle/homebrew-tap 56 | token: ${{ secrets.HOMEBREW_TAP_TOKEN }} 57 | path: tap 58 | 59 | - name: Update formula 60 | run: | 61 | FORMULA=tap/Formula/sketchybar-system-stats.rb 62 | VERSION=${{ steps.get_version.outputs.version }} 63 | ARM_SHA256=${{ steps.arm.outputs.arm_sha256 }} 64 | INTEL_SHA256=${{ steps.intel.outputs.intel_sha256 }} 65 | sed -i "s|stats_provider-[0-9.]\+-aarch64-apple-darwin.tar.gz|stats_provider-${VERSION}-aarch64-apple-darwin.tar.gz|g" $FORMULA 66 | sed -i "s|stats_provider-[0-9.]\+-x86_64-apple-darwin.tar.gz|stats_provider-${VERSION}-x86_64-apple-darwin.tar.gz|g" $FORMULA 67 | sed -i "/aarch64-apple-darwin.tar.gz/,/sha256/ s|sha256 \".*\"|sha256 \"${ARM_SHA256}\"|" $FORMULA 68 | sed -i "/x86_64-apple-darwin.tar.gz/,/sha256/ s|sha256 \".*\"|sha256 \"${INTEL_SHA256}\"|" $FORMULA 69 | 70 | - name: Commit and push 71 | run: | 72 | cd tap 73 | git config user.name "github-actions[bot]" 74 | git config user.email "github-actions[bot]@users.noreply.github.com" 75 | git add Formula/sketchybar-system-stats.rb 76 | git commit -m "Update sketchybar-system-stats to v${{ steps.get_version.outputs.version }}" 77 | git push 78 | -------------------------------------------------------------------------------- /src/stats/battery.rs: -------------------------------------------------------------------------------- 1 | use starship_battery::{Manager, State}; 2 | use std::fmt::Write; 3 | 4 | pub fn get_battery_stats(flags: &[&str], no_units: bool, buf: &mut String) { 5 | let manager = match Manager::new() { 6 | Ok(m) => m, 7 | Err(_) => return, 8 | }; 9 | 10 | let batteries: Vec<_> = match manager.batteries() { 11 | Ok(batteries) => batteries.filter_map(Result::ok).collect(), 12 | Err(_) => return, 13 | }; 14 | 15 | if batteries.is_empty() { 16 | return; 17 | } 18 | 19 | let battery = &batteries[0]; 20 | 21 | for &flag in flags { 22 | match flag { 23 | "percentage" => { 24 | let percentage = (battery.state_of_charge().value * 100.0).round() as u32; 25 | let unit = if no_units { "" } else { "%" }; 26 | let _ = write!(buf, "BATTERY_PERCENTAGE=\"{percentage}{unit}\" "); 27 | } 28 | "state" => { 29 | let state_str = match battery.state() { 30 | State::Charging => "charging", 31 | State::Discharging => "discharging", 32 | State::Full => "full", 33 | State::Empty => "empty", 34 | _ => "unknown", 35 | }; 36 | let _ = write!(buf, "BATTERY_STATE=\"{state_str}\" "); 37 | } 38 | "remaining" => { 39 | if let Some(time) = battery.time_to_empty() { 40 | let mins = time.value as u32 / 60; 41 | let unit = if no_units { "" } else { "min" }; 42 | let _ = write!(buf, "BATTERY_REMAINING=\"{mins}{unit}\" "); 43 | } 44 | } 45 | "time_to_full" => { 46 | if let Some(time) = battery.time_to_full() { 47 | let mins = time.value as u32 / 60; 48 | let unit = if no_units { "" } else { "min" }; 49 | let _ = write!(buf, "BATTERY_TIME_TO_FULL=\"{mins}{unit}\" "); 50 | } 51 | } 52 | _ => {} 53 | } 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | 61 | #[test] 62 | fn test_get_battery_stats_with_units() { 63 | let mut buf = String::new(); 64 | get_battery_stats(&["percentage", "state"], false, &mut buf); 65 | 66 | if !buf.is_empty() { 67 | assert!(buf.contains("BATTERY_PERCENTAGE=") || buf.contains("BATTERY_STATE=")); 68 | } 69 | } 70 | 71 | #[test] 72 | fn test_get_battery_stats_without_units() { 73 | let mut buf = String::new(); 74 | get_battery_stats(&["percentage"], true, &mut buf); 75 | 76 | if buf.contains("BATTERY_PERCENTAGE=") { 77 | assert!(!buf.contains("%")); 78 | } 79 | } 80 | 81 | #[test] 82 | fn test_get_battery_stats_empty_flags() { 83 | let mut buf = String::new(); 84 | get_battery_stats(&[], false, &mut buf); 85 | 86 | assert_eq!(buf, ""); 87 | } 88 | 89 | #[test] 90 | fn test_get_battery_stats_handles_no_battery() { 91 | let mut buf = String::new(); 92 | get_battery_stats(&["percentage"], false, &mut buf); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/stats/uptime.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | use sysinfo::System; 3 | 4 | struct TimeUnit { 5 | name: &'static str, 6 | seconds: u64, 7 | suffix: &'static str, 8 | } 9 | 10 | const TIME_UNITS: &[TimeUnit] = &[ 11 | TimeUnit { 12 | name: "week", 13 | seconds: 7 * 24 * 3600, 14 | suffix: "w", 15 | }, 16 | TimeUnit { 17 | name: "day", 18 | seconds: 24 * 3600, 19 | suffix: "d", 20 | }, 21 | TimeUnit { 22 | name: "hour", 23 | seconds: 3600, 24 | suffix: "h", 25 | }, 26 | TimeUnit { 27 | name: "min", 28 | seconds: 60, 29 | suffix: "m", 30 | }, 31 | TimeUnit { 32 | name: "sec", 33 | seconds: 1, 34 | suffix: "s", 35 | }, 36 | ]; 37 | 38 | pub fn get_uptime_stats(flags: &[&str], buf: &mut String) { 39 | let mut uptime_secs = System::uptime(); 40 | 41 | let sorted_flags: Vec<&str> = if flags.is_empty() { 42 | TIME_UNITS.iter().map(|u| u.name).collect() 43 | } else { 44 | let mut flags_vec: Vec<&str> = flags 45 | .iter() 46 | .copied() 47 | .filter(|&flag| TIME_UNITS.iter().any(|u| u.name == flag)) 48 | .collect(); 49 | 50 | flags_vec.sort_by_key(|&flag| TIME_UNITS.iter().position(|u| u.name == flag).unwrap()); 51 | flags_vec 52 | }; 53 | 54 | let _ = write!(buf, "UPTIME=\""); 55 | let mut has_value = false; 56 | 57 | for &flag in &sorted_flags { 58 | if let Some(unit) = TIME_UNITS.iter().find(|u| u.name == flag) { 59 | if uptime_secs >= unit.seconds { 60 | let value = uptime_secs / unit.seconds; 61 | uptime_secs %= unit.seconds; 62 | if has_value { 63 | let _ = write!(buf, " "); 64 | } 65 | let _ = write!(buf, "{}{}", value, unit.suffix); 66 | has_value = true; 67 | } 68 | } 69 | } 70 | 71 | if !has_value { 72 | let min_suffix = sorted_flags 73 | .last() 74 | .and_then(|flag| TIME_UNITS.iter().find(|u| u.name == *flag)) 75 | .map(|unit| unit.suffix) 76 | .unwrap_or("s"); 77 | let _ = write!(buf, "0{}", min_suffix); 78 | } 79 | 80 | let _ = write!(buf, "\" "); 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use super::*; 86 | 87 | #[test] 88 | fn test_get_uptime_stats_all_units() { 89 | let mut buf = String::new(); 90 | get_uptime_stats(&["week", "day", "hour", "min", "sec"], &mut buf); 91 | 92 | assert!(buf.starts_with("UPTIME=\"")); 93 | assert!(buf.ends_with("\" ")); 94 | } 95 | 96 | #[test] 97 | fn test_get_uptime_stats_single_unit() { 98 | let mut buf = String::new(); 99 | get_uptime_stats(&["min"], &mut buf); 100 | 101 | assert!(buf.contains("UPTIME=\"")); 102 | assert!(buf.contains("m")); 103 | } 104 | 105 | #[test] 106 | fn test_get_uptime_stats_empty_flags() { 107 | let mut buf = String::new(); 108 | get_uptime_stats(&[], &mut buf); 109 | 110 | assert!(buf.contains("UPTIME=\"")); 111 | } 112 | 113 | #[test] 114 | fn test_get_uptime_stats_invalid_flag() { 115 | let mut buf = String::new(); 116 | get_uptime_stats(&["invalid"], &mut buf); 117 | 118 | assert_eq!(buf, "UPTIME=\"0s\" "); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/sketchybar.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use std::ffi::{CStr, CString}; 3 | use std::os::raw::c_char; 4 | use std::sync::Once; 5 | use tokio::sync::Mutex; 6 | use tokio::time::{Duration, Instant}; 7 | 8 | // Modified from sketchybar-rs (https://github.com/johnallen3d/sketchybar-rs) 9 | const PORT_REFRESH_INTERVAL_SECS: u64 = 300; 10 | 11 | #[link(name = "sketchybar", kind = "static")] 12 | extern "C" { 13 | fn sketchybar(message: *const c_char, bar_name: *const c_char) -> *mut c_char; 14 | fn free_sketchybar_response(response: *mut c_char); 15 | fn cleanup_sketchybar(); 16 | fn refresh_sketchybar_port(bar_name: *const c_char) -> bool; 17 | } 18 | 19 | static CLEANUP: Once = Once::new(); 20 | 21 | struct PortState { 22 | last_refresh: Instant, 23 | refresh_interval: Duration, 24 | } 25 | 26 | pub struct Sketchybar { 27 | bar_name: CString, 28 | port_state: Mutex, 29 | } 30 | 31 | impl Sketchybar { 32 | pub fn new(bar_name: Option<&str>) -> Result { 33 | let name = bar_name.unwrap_or("sketchybar"); 34 | let c_string = CString::new(name).context("Failed to create CString for bar_name")?; 35 | Ok(Self { 36 | bar_name: c_string, 37 | port_state: Mutex::new(PortState { 38 | last_refresh: Instant::now(), 39 | refresh_interval: Duration::from_secs(PORT_REFRESH_INTERVAL_SECS), 40 | }), 41 | }) 42 | } 43 | 44 | async fn maybe_refresh_port(&self) -> Result<()> { 45 | let mut state = self.port_state.lock().await; 46 | if state.last_refresh.elapsed() >= state.refresh_interval { 47 | let refreshed = unsafe { refresh_sketchybar_port(self.bar_name.as_ptr()) }; 48 | if !refreshed { 49 | anyhow::bail!("Failed to refresh sketchybar port"); 50 | } 51 | state.last_refresh = Instant::now(); 52 | } 53 | Ok(()) 54 | } 55 | 56 | pub async fn send_message( 57 | &self, 58 | flag: &str, 59 | event: &str, 60 | payload: Option<&str>, 61 | verbose: bool, 62 | ) -> Result { 63 | self.maybe_refresh_port().await?; 64 | 65 | let message = format!("--{} {} {}", flag, event, payload.unwrap_or_default()); 66 | let c_message = CString::new(message).context("Failed to create CString for message")?; 67 | 68 | let response_ptr = unsafe { sketchybar(c_message.as_ptr(), self.bar_name.as_ptr()) }; 69 | 70 | if response_ptr.is_null() { 71 | anyhow::bail!("Failed to get response from sketchybar"); 72 | } 73 | 74 | let response = unsafe { 75 | let response = CStr::from_ptr(response_ptr) 76 | .to_str() 77 | .context("Failed to convert C string to Rust string")? 78 | .to_owned(); 79 | free_sketchybar_response(response_ptr); 80 | response 81 | }; 82 | 83 | if verbose { 84 | println!( 85 | "Successfully sent to SketchyBar: (Bar: {}): --{} {} {}", 86 | self.bar_name.to_str().unwrap(), 87 | flag, 88 | event, 89 | payload.unwrap_or_default() 90 | ); 91 | } 92 | 93 | Ok(response) 94 | } 95 | } 96 | 97 | impl Drop for Sketchybar { 98 | fn drop(&mut self) { 99 | CLEANUP.call_once(|| unsafe { 100 | cleanup_sketchybar(); 101 | }); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/stats/cpu.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | use sysinfo::{Components, System}; 3 | 4 | pub fn get_cpu_stats( 5 | s: &System, 6 | components: &Components, 7 | flags: &[&str], 8 | no_units: bool, 9 | buf: &mut String, 10 | ) { 11 | let cpu_count = s.cpus().len(); 12 | 13 | if cpu_count == 0 { 14 | return; 15 | } 16 | 17 | for &flag in flags { 18 | match flag { 19 | "count" => { 20 | let _ = write!(buf, "CPU_COUNT=\"{cpu_count}\" "); 21 | } 22 | "frequency" => { 23 | let total_frequency: u64 = s.cpus().iter().map(|cpu| cpu.frequency()).sum(); 24 | let avg_freq = total_frequency / cpu_count as u64; 25 | let unit = if no_units { "" } else { "MHz" }; 26 | let _ = write!(buf, "CPU_FREQUENCY=\"{avg_freq}{unit}\" "); 27 | } 28 | "temperature" => { 29 | let mut total_temp: f32 = 0.0; 30 | let mut count: u32 = 0; 31 | 32 | let cpu_labels = ["CPU", "PMU", "SOC"]; 33 | 34 | for component in components { 35 | if cpu_labels 36 | .iter() 37 | .any(|&label| component.label().contains(label)) 38 | { 39 | if let Some(temperature) = component.temperature() { 40 | total_temp += temperature; 41 | count += 1; 42 | } 43 | } 44 | } 45 | 46 | let average_temp = if count > 0 { 47 | total_temp / count as f32 48 | } else { 49 | -1.0 50 | }; 51 | 52 | let unit = if no_units { "" } else { "°C" }; 53 | if average_temp != -1.0 { 54 | let _ = write!(buf, "CPU_TEMP=\"{average_temp:.1}{unit}\" "); 55 | } else { 56 | let _ = write!(buf, "CPU_TEMP=\"N/A{unit}\" "); 57 | } 58 | } 59 | "usage" => { 60 | let unit = if no_units { "" } else { "%" }; 61 | let _ = write!( 62 | buf, 63 | "CPU_USAGE=\"{:.0}{unit}\" ", 64 | s.global_cpu_usage().round() 65 | ); 66 | } 67 | _ => {} 68 | } 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use super::*; 75 | 76 | #[test] 77 | fn test_get_cpu_stats_with_units() { 78 | let mut s = System::new_all(); 79 | s.refresh_all(); 80 | let components = Components::new_with_refreshed_list(); 81 | let mut buf = String::new(); 82 | 83 | get_cpu_stats(&s, &components, &["count", "usage"], false, &mut buf); 84 | 85 | assert!(buf.contains("CPU_COUNT=")); 86 | assert!(buf.contains("CPU_USAGE=")); 87 | assert!(buf.contains("%")); 88 | } 89 | 90 | #[test] 91 | fn test_get_cpu_stats_without_units() { 92 | let mut s = System::new_all(); 93 | s.refresh_all(); 94 | let components = Components::new_with_refreshed_list(); 95 | let mut buf = String::new(); 96 | 97 | get_cpu_stats(&s, &components, &["usage"], true, &mut buf); 98 | 99 | assert!(buf.contains("CPU_USAGE=")); 100 | assert!(!buf.contains("%")); 101 | } 102 | 103 | #[test] 104 | fn test_get_cpu_stats_empty_flags() { 105 | let mut s = System::new_all(); 106 | s.refresh_all(); 107 | let components = Components::new_with_refreshed_list(); 108 | let mut buf = String::new(); 109 | 110 | get_cpu_stats(&s, &components, &[], false, &mut buf); 111 | 112 | assert_eq!(buf, ""); 113 | } 114 | 115 | #[test] 116 | fn test_get_cpu_stats_invalid_flag() { 117 | let mut s = System::new_all(); 118 | s.refresh_all(); 119 | let components = Components::new_with_refreshed_list(); 120 | let mut buf = String::new(); 121 | 122 | get_cpu_stats(&s, &components, &["invalid_flag"], false, &mut buf); 123 | 124 | assert_eq!(buf, ""); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | # Only do the release on x.y.z tags. 4 | on: 5 | push: 6 | tags: 7 | - "[0-9]+.[0-9]+.[0-9]+" 8 | 9 | # We need this to be able to create releases. 10 | permissions: 11 | contents: write 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | # The create-release job runs purely to initialize the GitHub release itself, 19 | # and names the release after the `x.y.z` tag that was pushed. It's separate 20 | # from building the release so that we only create the release once. 21 | create-release: 22 | name: create-release 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v6 26 | - name: Get the release version from the tag 27 | if: env.VERSION == '' 28 | run: echo "VERSION=${{ github.ref_name }}" >> $GITHUB_ENV 29 | - name: Show the version 30 | run: | 31 | echo "version is: $VERSION" 32 | - name: Check that tag version and Cargo.toml version are the same 33 | shell: bash 34 | run: | 35 | if ! grep -q "version = \"$VERSION\"" Cargo.toml; then 36 | echo "version does not match Cargo.toml" >&2 37 | exit 1 38 | fi 39 | - name: Create GitHub release 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | run: gh release create $VERSION --draft --verify-tag --title $VERSION 43 | outputs: 44 | version: ${{ env.VERSION }} 45 | 46 | build-release: 47 | name: build-release 48 | needs: ["create-release"] 49 | runs-on: ${{ matrix.os }} 50 | env: 51 | CARGO_TERM_COLOR: always 52 | strategy: 53 | fail-fast: false 54 | matrix: 55 | include: 56 | - build: macos-aarch64 57 | os: macos-latest 58 | target: aarch64-apple-darwin 59 | artifact_suffix: macos-aarch64 60 | - build: macos-x86_64 61 | os: macos-latest 62 | target: x86_64-apple-darwin 63 | artifact_suffix: macos-x86_64 64 | 65 | steps: 66 | - name: Checkout repository 67 | uses: actions/checkout@v6 68 | 69 | - name: Install Rust 70 | uses: dtolnay/rust-toolchain@stable 71 | with: 72 | target: ${{ matrix.target }} 73 | 74 | - name: Set target variables 75 | shell: bash 76 | run: | 77 | echo "TARGET_FLAGS=--target ${{ matrix.target }}" >> $GITHUB_ENV 78 | echo "TARGET_DIR=./target/${{ matrix.target }}/release" >> $GITHUB_ENV 79 | 80 | - name: Show command used for Cargo 81 | shell: bash 82 | run: | 83 | echo "target flag is: ${{ env.TARGET_FLAGS }}" 84 | echo "target dir is: ${{ env.TARGET_DIR }}" 85 | 86 | - name: Build release binary 87 | shell: bash 88 | run: | 89 | cargo build --verbose --release ${{ env.TARGET_FLAGS }} 90 | bin="${{ env.TARGET_DIR }}/stats_provider" 91 | echo "BIN=$bin" >> $GITHUB_ENV 92 | 93 | - name: Strip release binary (macos) 94 | if: matrix.os == 'macos-latest' 95 | shell: bash 96 | run: strip "$BIN" 97 | 98 | - name: Determine archive name 99 | shell: bash 100 | run: | 101 | version="${{ needs.create-release.outputs.version }}" 102 | echo "ARCHIVE=stats_provider-$version-${{ matrix.target }}" >> $GITHUB_ENV 103 | 104 | - name: Creating directory for archive 105 | shell: bash 106 | run: | 107 | mkdir -p "$ARCHIVE" 108 | cp "$BIN" "$ARCHIVE"/ 109 | cp {README.md,LICENSE} "$ARCHIVE"/ 110 | 111 | - name: Build archive 112 | shell: bash 113 | run: | 114 | tar czf "$ARCHIVE.tar.gz" "$ARCHIVE" 115 | shasum -a 256 "$ARCHIVE.tar.gz" > "$ARCHIVE.tar.gz.sha256" 116 | echo "ASSET=$ARCHIVE.tar.gz" >> $GITHUB_ENV 117 | echo "ASSET_SUM=$ARCHIVE.tar.gz.sha256" >> $GITHUB_ENV 118 | 119 | - name: Upload release archive 120 | env: 121 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 122 | shell: bash 123 | run: | 124 | version="${{ needs.create-release.outputs.version }}" 125 | gh release upload "$version" ${{ env.ASSET }} ${{ env.ASSET_SUM }} 126 | -------------------------------------------------------------------------------- /src/stats/memory.rs: -------------------------------------------------------------------------------- 1 | use crate::cli; 2 | use std::fmt::Write; 3 | use sysinfo::System; 4 | 5 | const BYTES_PER_GB: f32 = 1_073_741_824.0; 6 | 7 | pub fn get_memory_stats(s: &System, flags: &[&str], no_units: bool, buf: &mut String) { 8 | let ram_flag_present = flags 9 | .iter() 10 | .any(|&flag| cli::all_ram_flags().contains(&flag)); 11 | let swp_flag_present = flags 12 | .iter() 13 | .any(|&flag| cli::all_swp_flags().contains(&flag)); 14 | 15 | let (ram_total, ram_used, ram_usage_percentage) = if ram_flag_present { 16 | let ram_total = s.total_memory(); 17 | let ram_used = s.used_memory(); 18 | let ram_usage_percentage = if ram_total > 0 { 19 | ((ram_used as f32 / ram_total as f32) * 100.0).round() as u32 20 | } else { 21 | 0 22 | }; 23 | (ram_total, ram_used, ram_usage_percentage) 24 | } else { 25 | (0, 0, 0) 26 | }; 27 | let (swp_total, swp_used, swp_usage_percentage) = if swp_flag_present { 28 | let swp_total = s.total_swap(); 29 | let swp_used = s.used_swap(); 30 | let swp_usage_percentage = if swp_total > 0 { 31 | ((swp_used as f32 / swp_total as f32) * 100.0).round() as u32 32 | } else { 33 | 0 34 | }; 35 | (swp_total, swp_used, swp_usage_percentage) 36 | } else { 37 | (0, 0, 0) 38 | }; 39 | 40 | for &flag in flags { 41 | match flag { 42 | "ram_available" => { 43 | let unit = if no_units { "" } else { "GB" }; 44 | let _ = write!( 45 | buf, 46 | "RAM_AVAILABLE=\"{:.1}{unit}\" ", 47 | s.available_memory() as f32 / BYTES_PER_GB 48 | ); 49 | } 50 | "ram_total" => { 51 | let unit = if no_units { "" } else { "GB" }; 52 | let _ = write!( 53 | buf, 54 | "RAM_TOTAL=\"{:.1}{unit}\" ", 55 | ram_total as f32 / BYTES_PER_GB 56 | ); 57 | } 58 | "ram_used" => { 59 | let unit = if no_units { "" } else { "GB" }; 60 | let _ = write!( 61 | buf, 62 | "RAM_USED=\"{:.1}{unit}\" ", 63 | ram_used as f32 / BYTES_PER_GB 64 | ); 65 | } 66 | "ram_usage" => { 67 | let unit = if no_units { "" } else { "%" }; 68 | let _ = write!(buf, "RAM_USAGE=\"{ram_usage_percentage}{unit}\" "); 69 | } 70 | "swp_free" => { 71 | let unit = if no_units { "" } else { "GB" }; 72 | let _ = write!( 73 | buf, 74 | "SWP_FREE=\"{:.1}{unit}\" ", 75 | s.free_swap() as f32 / BYTES_PER_GB 76 | ); 77 | } 78 | "swp_total" => { 79 | let unit = if no_units { "" } else { "GB" }; 80 | let _ = write!( 81 | buf, 82 | "SWP_TOTAL=\"{:.1}{unit}\" ", 83 | swp_total as f32 / BYTES_PER_GB 84 | ); 85 | } 86 | "swp_used" => { 87 | let unit = if no_units { "" } else { "GB" }; 88 | let _ = write!( 89 | buf, 90 | "SWP_USED=\"{:.1}{unit}\" ", 91 | swp_used as f32 / BYTES_PER_GB 92 | ); 93 | } 94 | "swp_usage" => { 95 | let unit = if no_units { "" } else { "%" }; 96 | let _ = write!(buf, "SWP_USAGE=\"{swp_usage_percentage}{unit}\" "); 97 | } 98 | _ => {} 99 | } 100 | } 101 | } 102 | 103 | #[cfg(test)] 104 | mod tests { 105 | use super::*; 106 | 107 | #[test] 108 | fn test_get_memory_stats_with_units() { 109 | let mut s = System::new_all(); 110 | s.refresh_all(); 111 | let mut buf = String::new(); 112 | 113 | get_memory_stats(&s, &["ram_total", "ram_usage"], false, &mut buf); 114 | 115 | assert!(buf.contains("RAM_TOTAL=")); 116 | assert!(buf.contains("RAM_USAGE=")); 117 | assert!(buf.contains("GB") || buf.contains("%")); 118 | } 119 | 120 | #[test] 121 | fn test_get_memory_stats_without_units() { 122 | let mut s = System::new_all(); 123 | s.refresh_all(); 124 | let mut buf = String::new(); 125 | 126 | get_memory_stats(&s, &["ram_usage"], true, &mut buf); 127 | 128 | assert!(buf.contains("RAM_USAGE=")); 129 | assert!(!buf.contains("%")); 130 | } 131 | 132 | #[test] 133 | fn test_get_memory_stats_swap() { 134 | let mut s = System::new_all(); 135 | s.refresh_all(); 136 | let mut buf = String::new(); 137 | 138 | get_memory_stats(&s, &["swp_total", "swp_usage"], false, &mut buf); 139 | 140 | assert!(buf.contains("SWP_TOTAL=")); 141 | assert!(buf.contains("SWP_USAGE=")); 142 | } 143 | 144 | #[test] 145 | fn test_get_memory_stats_empty_flags() { 146 | let s = System::new_all(); 147 | let mut buf = String::new(); 148 | 149 | get_memory_stats(&s, &[], false, &mut buf); 150 | 151 | assert_eq!(buf, ""); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /include/sketchybar.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct mach_message { 11 | mach_msg_header_t header; 12 | mach_msg_size_t msgh_descriptor_count; 13 | mach_msg_ool_descriptor_t descriptor; 14 | }; 15 | 16 | struct mach_buffer { 17 | struct mach_message message; 18 | mach_msg_trailer_t trailer; 19 | }; 20 | 21 | static mach_port_t g_mach_port = MACH_PORT_NULL; 22 | static pthread_mutex_t g_port_mutex = PTHREAD_MUTEX_INITIALIZER; 23 | 24 | mach_port_t mach_get_bs_port(char *bar_name) { 25 | mach_port_name_t task = mach_task_self(); 26 | 27 | mach_port_t bs_port; 28 | if (task_get_special_port(task, TASK_BOOTSTRAP_PORT, &bs_port) != 29 | KERN_SUCCESS) { 30 | return MACH_PORT_NULL; 31 | } 32 | 33 | char service_name[256]; // Assuming the service name will not exceed 255 chars 34 | snprintf(service_name, sizeof(service_name), "git.felix.%s", bar_name); 35 | 36 | mach_port_t port; 37 | kern_return_t result = bootstrap_look_up(bs_port, service_name, &port); 38 | mach_port_deallocate(task, bs_port); 39 | 40 | return (result == KERN_SUCCESS) ? port : MACH_PORT_NULL; 41 | } 42 | 43 | void mach_receive_message(mach_port_t port, struct mach_buffer *buffer, 44 | bool timeout) { 45 | *buffer = (struct mach_buffer){0}; 46 | mach_msg_return_t msg_return; 47 | if (timeout) 48 | msg_return = 49 | mach_msg(&buffer->message.header, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, 50 | sizeof(struct mach_buffer), port, 100, MACH_PORT_NULL); 51 | else 52 | msg_return = mach_msg(&buffer->message.header, MACH_RCV_MSG, 0, 53 | sizeof(struct mach_buffer), port, 54 | MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 55 | 56 | if (msg_return != MACH_MSG_SUCCESS) { 57 | buffer->message.descriptor.address = NULL; 58 | } 59 | } 60 | 61 | char *mach_send_message(mach_port_t port, const char *message, uint32_t len) { 62 | if (!message || !port) { 63 | return NULL; 64 | } 65 | 66 | mach_port_t response_port; 67 | mach_port_name_t task = mach_task_self(); 68 | if (mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &response_port) != 69 | KERN_SUCCESS) { 70 | return NULL; 71 | } 72 | 73 | if (mach_port_insert_right(task, response_port, response_port, 74 | MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS) { 75 | mach_port_deallocate(task, response_port); 76 | return NULL; 77 | } 78 | 79 | struct mach_message msg = {0}; 80 | msg.header.msgh_remote_port = port; 81 | msg.header.msgh_local_port = response_port; 82 | msg.header.msgh_id = response_port; 83 | msg.header.msgh_bits = 84 | MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND, 0, 85 | MACH_MSGH_BITS_COMPLEX); 86 | 87 | msg.header.msgh_size = sizeof(struct mach_message); 88 | msg.msgh_descriptor_count = 1; 89 | msg.descriptor.address = (void *)message; 90 | msg.descriptor.size = len * sizeof(char); 91 | msg.descriptor.copy = MACH_MSG_VIRTUAL_COPY; 92 | msg.descriptor.deallocate = false; 93 | msg.descriptor.type = MACH_MSG_OOL_DESCRIPTOR; 94 | 95 | mach_msg_return_t send_result = 96 | mach_msg(&msg.header, MACH_SEND_MSG, sizeof(struct mach_message), 0, 97 | MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 98 | 99 | if (send_result != MACH_MSG_SUCCESS) { 100 | mach_port_mod_refs(task, response_port, MACH_PORT_RIGHT_RECEIVE, -1); 101 | mach_port_deallocate(task, response_port); 102 | return NULL; 103 | } 104 | 105 | struct mach_buffer buffer = {0}; 106 | mach_receive_message(response_port, &buffer, true); 107 | 108 | char *result = NULL; 109 | if (buffer.message.descriptor.address) { 110 | size_t result_len = buffer.message.descriptor.size; 111 | result = (char *)malloc(result_len + 1); 112 | if (result) { 113 | memcpy(result, buffer.message.descriptor.address, result_len); 114 | result[result_len] = '\0'; 115 | } 116 | } 117 | 118 | mach_msg_destroy(&buffer.message.header); 119 | mach_port_mod_refs(task, response_port, MACH_PORT_RIGHT_RECEIVE, -1); 120 | mach_port_deallocate(task, response_port); 121 | 122 | return result; 123 | } 124 | 125 | char *sketchybar(const char *message, const char *bar_name) { 126 | if (!message || !bar_name) { 127 | return strdup(""); 128 | } 129 | 130 | uint32_t message_length = strlen(message) + 1; 131 | char *formatted_message = (char *)malloc(message_length + 1); 132 | if (!formatted_message) { 133 | return strdup(""); 134 | } 135 | 136 | char quote = '\0'; 137 | uint32_t caret = 0; 138 | for (uint32_t i = 0; i < message_length; ++i) { 139 | if (message[i] == '"' || message[i] == '\'') { 140 | if (quote == message[i]) 141 | quote = '\0'; 142 | else 143 | quote = message[i]; 144 | continue; 145 | } 146 | formatted_message[caret] = message[i]; 147 | if (message[i] == ' ' && !quote) 148 | formatted_message[caret] = '\0'; 149 | caret++; 150 | } 151 | 152 | if (caret > 0 && formatted_message[caret] == '\0' && 153 | formatted_message[caret - 1] == '\0') { 154 | caret--; 155 | } 156 | 157 | formatted_message[caret] = '\0'; 158 | 159 | pthread_mutex_lock(&g_port_mutex); 160 | if (g_mach_port == MACH_PORT_NULL) { 161 | g_mach_port = mach_get_bs_port((char *)bar_name); 162 | } 163 | 164 | char *response = mach_send_message(g_mach_port, formatted_message, caret + 1); 165 | pthread_mutex_unlock(&g_port_mutex); 166 | 167 | free(formatted_message); 168 | 169 | return response ? response : strdup(""); 170 | } 171 | 172 | bool refresh_sketchybar_port(const char *bar_name) { 173 | pthread_mutex_lock(&g_port_mutex); 174 | if (g_mach_port != MACH_PORT_NULL) { 175 | mach_port_deallocate(mach_task_self(), g_mach_port); 176 | } 177 | g_mach_port = mach_get_bs_port((char *)bar_name); 178 | bool success = (g_mach_port != MACH_PORT_NULL); 179 | pthread_mutex_unlock(&g_port_mutex); 180 | return success; 181 | } 182 | 183 | void free_sketchybar_response(char *response) { free(response); } 184 | 185 | void cleanup_sketchybar() { 186 | pthread_mutex_lock(&g_port_mutex); 187 | if (g_mach_port != MACH_PORT_NULL) { 188 | mach_port_deallocate(mach_task_self(), g_mach_port); 189 | g_mach_port = MACH_PORT_NULL; 190 | } 191 | pthread_mutex_unlock(&g_port_mutex); 192 | } 193 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use clap::Parser; 3 | 4 | // Default values as constants 5 | pub const DEFAULT_INTERVAL: u32 = 5; 6 | pub const DEFAULT_NETWORK_REFRESH_RATE: u32 = 5; 7 | pub const MIN_INTERVAL: u32 = 1; 8 | pub const MAX_INTERVAL: u32 = 3600; // 1 hour max 9 | pub const MIN_NETWORK_REFRESH_RATE: u32 = 1; 10 | pub const MAX_NETWORK_REFRESH_RATE: u32 = 100; 11 | 12 | #[derive(Parser, Debug)] 13 | #[command(name = "stats_provider", version, about, long_about = None, arg_required_else_help = true)] 14 | pub struct Cli { 15 | #[arg(short = 'a', long, num_args = 0, help = "Get all stats")] 16 | pub all: bool, 17 | 18 | #[arg(short = 'b', long, num_args = 1.., value_parser = all_battery_flags(), help = "Get battery stats")] 19 | pub battery: Option>, 20 | 21 | #[arg(short = 'c', long, num_args = 1.., value_parser = all_cpu_flags(), help = "Get CPU stats")] 22 | pub cpu: Option>, 23 | 24 | #[arg(short = 'd', long, num_args = 1.., value_parser = all_disk_flags(), help = "Get disk stats")] 25 | pub disk: Option>, 26 | 27 | #[arg(short = 'm', long, num_args = 1.., value_parser = all_memory_flags(), help = "Get memory stats")] 28 | pub memory: Option>, 29 | 30 | #[arg(short = 'n', long, num_args = 1.., help = "Network rx/tx in KB/s. Specify network interfaces (e.g., -n eth0 en0 lo0). At least one is required.")] 31 | pub network: Option>, 32 | 33 | #[arg(short = 's', long, num_args = 1.., value_parser = all_system_flags(), help = "Get system stats")] 34 | pub system: Option>, 35 | 36 | #[arg(short = 'u', long, num_args = 1.., value_parser = all_uptime_flags(), help = "Get uptime stats")] 37 | pub uptime: Option>, 38 | 39 | #[arg( 40 | short = 'i', 41 | long, 42 | default_value_t = DEFAULT_INTERVAL, 43 | help = "Refresh interval in seconds" 44 | )] 45 | pub interval: u32, 46 | 47 | #[arg( 48 | long, 49 | default_value_t = DEFAULT_NETWORK_REFRESH_RATE, 50 | help = "Network refresh rate (how often to refresh network interface list, in stat intervals)" 51 | )] 52 | pub network_refresh_rate: u32, 53 | 54 | #[arg(long, help = "Bar name (optional)")] 55 | pub bar: Option, 56 | 57 | #[arg(long, default_value_t = false, help = "Enable verbose output")] 58 | pub verbose: bool, 59 | 60 | #[arg(long, default_value_t = false, help = "Output values without units")] 61 | pub no_units: bool, 62 | } 63 | 64 | pub fn parse_args() -> Cli { 65 | Cli::parse() 66 | } 67 | 68 | pub fn validate_cli(cli: &Cli) -> Result<()> { 69 | // Validate interval 70 | if cli.interval < MIN_INTERVAL || cli.interval > MAX_INTERVAL { 71 | bail!( 72 | "Interval must be between {} and {} seconds, got {}", 73 | MIN_INTERVAL, 74 | MAX_INTERVAL, 75 | cli.interval 76 | ); 77 | } 78 | 79 | // Validate network refresh rate 80 | if cli.network_refresh_rate < MIN_NETWORK_REFRESH_RATE 81 | || cli.network_refresh_rate > MAX_NETWORK_REFRESH_RATE 82 | { 83 | bail!( 84 | "Network refresh rate must be between {} and {}, got {}", 85 | MIN_NETWORK_REFRESH_RATE, 86 | MAX_NETWORK_REFRESH_RATE, 87 | cli.network_refresh_rate 88 | ); 89 | } 90 | 91 | // Validate that at least one stat type is requested if not using --all 92 | if !cli.all 93 | && cli.battery.is_none() 94 | && cli.cpu.is_none() 95 | && cli.disk.is_none() 96 | && cli.memory.is_none() 97 | && cli.network.is_none() 98 | && cli.system.is_none() 99 | && cli.uptime.is_none() 100 | { 101 | bail!("At least one stat type must be specified, or use --all"); 102 | } 103 | 104 | Ok(()) 105 | } 106 | 107 | pub fn all_battery_flags() -> Vec<&'static str> { 108 | vec!["percentage", "remaining", "state", "time_to_full"] 109 | } 110 | 111 | pub fn all_cpu_flags() -> Vec<&'static str> { 112 | vec!["count", "frequency", "temperature", "usage"] 113 | } 114 | 115 | pub fn all_disk_flags() -> Vec<&'static str> { 116 | vec!["count", "free", "total", "usage", "used"] 117 | } 118 | 119 | pub fn all_ram_flags() -> Vec<&'static str> { 120 | vec!["ram_available", "ram_total", "ram_usage", "ram_used"] 121 | } 122 | 123 | pub fn all_swp_flags() -> Vec<&'static str> { 124 | vec!["swp_free", "swp_total", "swp_usage", "swp_used"] 125 | } 126 | 127 | pub fn all_memory_flags() -> Vec<&'static str> { 128 | let mut flags = Vec::new(); 129 | flags.extend(all_ram_flags()); 130 | flags.extend(all_swp_flags()); 131 | flags 132 | } 133 | 134 | pub fn all_system_flags() -> Vec<&'static str> { 135 | vec![ 136 | "arch", 137 | "distro", 138 | "host_name", 139 | "kernel_version", 140 | "name", 141 | "os_version", 142 | "long_os_version", 143 | ] 144 | } 145 | 146 | pub fn all_uptime_flags() -> Vec<&'static str> { 147 | vec!["week", "day", "hour", "min", "sec"] 148 | } 149 | 150 | #[cfg(test)] 151 | mod tests { 152 | use super::*; 153 | 154 | #[test] 155 | fn test_validate_cli_with_all_flag() { 156 | let cli = Cli { 157 | all: true, 158 | battery: None, 159 | cpu: None, 160 | disk: None, 161 | memory: None, 162 | network: None, 163 | system: None, 164 | uptime: None, 165 | interval: DEFAULT_INTERVAL, 166 | network_refresh_rate: DEFAULT_NETWORK_REFRESH_RATE, 167 | bar: None, 168 | verbose: false, 169 | no_units: false, 170 | }; 171 | assert!(validate_cli(&cli).is_ok()); 172 | } 173 | 174 | #[test] 175 | fn test_validate_cli_with_cpu_flag() { 176 | let cli = Cli { 177 | all: false, 178 | battery: None, 179 | cpu: Some(vec!["usage".to_string()]), 180 | disk: None, 181 | memory: None, 182 | network: None, 183 | system: None, 184 | uptime: None, 185 | interval: DEFAULT_INTERVAL, 186 | network_refresh_rate: DEFAULT_NETWORK_REFRESH_RATE, 187 | bar: None, 188 | verbose: false, 189 | no_units: false, 190 | }; 191 | assert!(validate_cli(&cli).is_ok()); 192 | } 193 | 194 | #[test] 195 | fn test_validate_cli_no_flags() { 196 | let cli = Cli { 197 | all: false, 198 | battery: None, 199 | cpu: None, 200 | disk: None, 201 | memory: None, 202 | network: None, 203 | system: None, 204 | uptime: None, 205 | interval: DEFAULT_INTERVAL, 206 | network_refresh_rate: DEFAULT_NETWORK_REFRESH_RATE, 207 | bar: None, 208 | verbose: false, 209 | no_units: false, 210 | }; 211 | assert!(validate_cli(&cli).is_err()); 212 | } 213 | 214 | #[test] 215 | fn test_validate_cli_interval_too_low() { 216 | let cli = Cli { 217 | all: true, 218 | battery: None, 219 | cpu: None, 220 | disk: None, 221 | memory: None, 222 | network: None, 223 | system: None, 224 | uptime: None, 225 | interval: 0, 226 | network_refresh_rate: DEFAULT_NETWORK_REFRESH_RATE, 227 | bar: None, 228 | verbose: false, 229 | no_units: false, 230 | }; 231 | assert!(validate_cli(&cli).is_err()); 232 | } 233 | 234 | #[test] 235 | fn test_validate_cli_interval_too_high() { 236 | let cli = Cli { 237 | all: true, 238 | battery: None, 239 | cpu: None, 240 | disk: None, 241 | memory: None, 242 | network: None, 243 | system: None, 244 | uptime: None, 245 | interval: MAX_INTERVAL + 1, 246 | network_refresh_rate: DEFAULT_NETWORK_REFRESH_RATE, 247 | bar: None, 248 | verbose: false, 249 | no_units: false, 250 | }; 251 | assert!(validate_cli(&cli).is_err()); 252 | } 253 | 254 | #[test] 255 | fn test_validate_cli_network_refresh_rate_too_low() { 256 | let cli = Cli { 257 | all: true, 258 | battery: None, 259 | cpu: None, 260 | disk: None, 261 | memory: None, 262 | network: None, 263 | system: None, 264 | uptime: None, 265 | interval: DEFAULT_INTERVAL, 266 | network_refresh_rate: 0, 267 | bar: None, 268 | verbose: false, 269 | no_units: false, 270 | }; 271 | assert!(validate_cli(&cli).is_err()); 272 | } 273 | 274 | #[test] 275 | fn test_validate_cli_network_refresh_rate_too_high() { 276 | let cli = Cli { 277 | all: true, 278 | battery: None, 279 | cpu: None, 280 | disk: None, 281 | memory: None, 282 | network: None, 283 | system: None, 284 | uptime: None, 285 | interval: DEFAULT_INTERVAL, 286 | network_refresh_rate: MAX_NETWORK_REFRESH_RATE + 1, 287 | bar: None, 288 | verbose: false, 289 | no_units: false, 290 | }; 291 | assert!(validate_cli(&cli).is_err()); 292 | } 293 | 294 | #[test] 295 | fn test_all_cpu_flags_returns_correct_flags() { 296 | let flags = all_cpu_flags(); 297 | assert_eq!(flags, vec!["count", "frequency", "temperature", "usage"]); 298 | } 299 | 300 | #[test] 301 | fn test_all_disk_flags_returns_correct_flags() { 302 | let flags = all_disk_flags(); 303 | assert_eq!(flags, vec!["count", "free", "total", "usage", "used"]); 304 | } 305 | 306 | #[test] 307 | fn test_all_memory_flags_contains_ram_and_swap() { 308 | let flags = all_memory_flags(); 309 | assert!(flags.contains(&"ram_available")); 310 | assert!(flags.contains(&"swp_total")); 311 | assert_eq!(flags.len(), 8); 312 | } 313 | 314 | #[test] 315 | fn test_all_system_flags_returns_correct_flags() { 316 | let flags = all_system_flags(); 317 | assert!(flags.contains(&"arch")); 318 | assert!(flags.contains(&"distro")); 319 | assert_eq!(flags.len(), 7); 320 | } 321 | 322 | #[test] 323 | fn test_all_uptime_flags_returns_correct_flags() { 324 | let flags = all_uptime_flags(); 325 | assert_eq!(flags, vec!["week", "day", "hour", "min", "sec"]); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Sketchybar System Stats 2 | 3 | ![stats_provider](assets/stats_provider.png) 4 | 5 | This is a simple event provider for [Sketchybar](https://github.com/FelixKratz/SketchyBar?tab=readme-ov-file) that sends system stats to Sketchybar via the event trigger `system_stats`. 6 | 7 | ## Installation 8 | 9 | ### Homebrew 10 | 11 | ```bash 12 | brew tap joncrangle/tap 13 | brew install sketchybar-system-stats 14 | ``` 15 | 16 | ### Prebuilt binaries 17 | 18 | You can download a prebuilt binary for Apple Silicon (aarch64) and Intel Macs (x86_64) from the [latest release](https://github.com/joncrangle/sketchybar-system-stats/releases). 19 | 20 | ### Build locally 21 | 22 | 1. [Install the Rust toolchain](https://rustup.rs/). 23 | 24 | ```bash 25 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 26 | ``` 27 | 28 | 2. Use `cargo` to build the binary: 29 | 30 | ```bash 31 | git clone https://github.com/joncrangle/sketchybar-system-stats.git 32 | cd sketchybar-system-stats 33 | cargo build --release 34 | ``` 35 | 36 | ## CLI usage 37 | 38 | Use the `help` command to get usage information: 39 | ```console 40 | $ stats_provider --help 41 | A simple system stats event provider for Sketchybar. 42 | 43 | Usage: stats_provider [OPTIONS] 44 | 45 | Options: 46 | -a, --all Get all stats 47 | -b, --battery ... Get battery stats [possible values: percentage, remaining, state, time_to_full] 48 | -c, --cpu ... Get CPU stats [possible values: count, frequency, temperature, usage] 49 | -d, --disk ... Get disk stats [possible values: count, free, total, usage, used] 50 | -m, --memory ... Get memory stats [possible values: ram_available, ram_total, ram_usage, ram_used, swp_free, swp_total, swp_usage, swp_used] 51 | -n, --network ... Network rx/tx in KB/s. Specify network interfaces (e.g., -n eth0 en0 lo0). At least one is required. 52 | -s, --system ... Get system stats [possible values: arch, distro, host_name, kernel_version, name, os_version, long_os_version] 53 | -u, --uptime ... Get uptime stats [possible values: week, day, hour, min, sec] 54 | -i, --interval Refresh interval in seconds [default: 5] 55 | --network-refresh-rate Network refresh rate (how often to refresh network interface list, in stat intervals) [default: 5] 56 | --bar Bar name (optional) 57 | --verbose Enable verbose output 58 | --no-units Output values without units 59 | -h, --help Print help 60 | -V, --version Print version 61 | ``` 62 | 63 | Example: trigger event with cpu, disk and ram usage percentages at a refresh interval of 2 seconds: 64 | ```bash 65 | stats_provider --cpu usage --disk usage --memory ram_usage --interval 2 66 | ``` 67 | 68 | Example: network monitoring with optimized refresh rate: 69 | ```bash 70 | # Monitor network with interface refresh every 8 stat intervals 71 | stats_provider --network en0 --interval 3 --network-refresh-rate 8 72 | ``` 73 | 74 | ### Uptime Usage 75 | 76 | The uptime system supports customizable time units. You can specify which units to display: 77 | 78 | ```bash 79 | # Show all available units (week, day, hour, min, sec) 80 | stats_provider --uptime 81 | 82 | # Show specific units only 83 | stats_provider --uptime day min # Shows "5d 42m" 84 | stats_provider --uptime week hour # Shows "2w 5h" 85 | ``` 86 | 87 | Available uptime units: 88 | - `week` (w) - weeks 89 | - `day` (d) - days 90 | - `hour` (h) - hours 91 | - `min` (m) - minutes 92 | - `sec` (s) - seconds 93 | 94 | Units are automatically sorted from largest to smallest, with intelligent carry-over (e.g., excess hours carry into days). 95 | 96 | ### Output Format 97 | 98 | By default, all numeric values include their units (MHz, °C, %, GB, KB/s). You can output raw numeric values without units using the `--no-units` flag: 99 | 100 | ```bash 101 | # With units (default) 102 | stats_provider --cpu usage --memory ram_usage 103 | # Output: CPU_USAGE="45%" RAM_USAGE="60%" 104 | 105 | # Without units 106 | stats_provider --cpu usage --memory ram_usage --no-units 107 | # Output: CPU_USAGE="45" RAM_USAGE="60" 108 | ``` 109 | 110 | This is useful when you want to process the values programmatically or apply custom formatting in your Sketchybar configuration. 111 | 112 | ### Network Optimization 113 | 114 | The `--network-refresh-rate` parameter controls how frequently the network interface list is refreshed: 115 | 116 | ```bash 117 | # Default: refresh network interfaces every 5 stat intervals 118 | stats_provider --network en0 --interval 2 --network-refresh-rate 5 119 | 120 | # More frequent refresh (every 2 intervals) for dynamic environments 121 | stats_provider --network en0 wlan0 --network-refresh-rate 2 122 | 123 | # Less frequent refresh (every 10 intervals) for stable setups to reduce overhead 124 | stats_provider --network en0 --network-refresh-rate 10 125 | ``` 126 | 127 | **Recommendation:** Use higher values (8-15) for stable network setups, lower values (2-5) for environments where interfaces frequently change. 128 | 129 | ### Verbose Output 130 | 131 | Add the `--verbose` flag to see more detailed output: 132 | 133 | ```console 134 | $ stats_provider --cpu usage --disk usage --memory ram_usage --interval 2 --verbose 135 | SketchyBar Stats Provider is running. 136 | Stats Provider CLI: Cli { all: false, cpu: Some(["usage"]), disk: Some(["usage"]), memory: Some(["ram_usage"]), network: None, system: None, interval: 2, bar: None, verbose: true } 137 | Successfully sent to SketchyBar: --add event system_stats 138 | Current message: CPU_USAGE="4%" DISK_USAGE="65%" RAM_USAGE="54%" 139 | Successfully sent to SketchyBar: --trigger system_stats CPU_USAGE="4%" DISK_USAGE="65%" RAM_USAGE="54%" 140 | Current message: CPU_USAGE="6%" DISK_USAGE="65%" RAM_USAGE="54%" 141 | Successfully sent to SketchyBar: --trigger system_stats CPU_USAGE="6%" DISK_USAGE="65%" RAM_USAGE="54%" 142 | Current message: CPU_USAGE="5%" DISK_USAGE="65%" RAM_USAGE="54%" 143 | Successfully sent to SketchyBar: --trigger system_stats CPU_USAGE="5%" DISK_USAGE="65%" RAM_USAGE="54%" 144 | ``` 145 | 146 | ## Usage with Sketchybar 147 | 148 | Environment variables that can be provided by the `system_stats` event 149 | 150 | | Variable | Description | 151 | | ------------------------ | ----------------------------------------- | 152 | | `ARCH` | System architecture | 153 | | `BATTERY_PERCENTAGE` | Battery charge level % | 154 | | `BATTERY_REMAINING` | Time remaining until empty (min) | 155 | | `BATTERY_STATE` | Battery charging state | 156 | | `BATTERY_TIME_TO_FULL` | Time until fully charged (min) | 157 | | `CPU_COUNT` | Number of CPU cores | 158 | | `CPU_FREQUENCY` | CPU frequency MHz | 159 | | `CPU_TEMP` | CPU temperature °C | 160 | | `CPU_USAGE` | CPU usage % | 161 | | `DISK_COUNT` | Number of disks | 162 | | `DISK_FREE` | Free disk space GB | 163 | | `DISK_TOTAL` | Total disk space GB | 164 | | `DISK_USAGE` | Disk usage % | 165 | | `DISK_USED` | Used disk space GB | 166 | | `DISTRO` | System distribution | 167 | | `HOST_NAME` | System host name | 168 | | `KERNEL_VERSION` | System kernel version | 169 | | `NETWORK_RX_{INTERFACE}` | Received KB/s from specified interface | 170 | | `NETWORK_TX_{INTERFACE}` | Transmitted KB/s from specified interface | 171 | | `OS_VERSION` | System OS version | 172 | | `LONG_OS_VERSION` | System long OS version | 173 | | `RAM_TOTAL` | Total memory GB | 174 | | `RAM_AVAILABLE` | Available memory GB | 175 | | `RAM_TOTAL` | Total memory GB | 176 | | `RAM_USAGE` | Memory usage % | 177 | | `RAM_USED` | Used memory GB | 178 | | `SWP_FREE` | Free swap GB | 179 | | `SWP_TOTAL` | Total swap GB | 180 | | `SWP_USAGE` | Swap usage % | 181 | | `SWP_USED` | Used swap GB | 182 | | `SYSTEM_NAME` | System name (i.e. Darwin) | 183 | | `UPTIME` | System uptime (customizable units) | 184 | 185 | > [!NOTE] 186 | > System stats that are not expected to change between system restarts (e.g. `NAME`, `OS_VERSION`, etc.) are sent when the app binary starts, but are not refreshed. 187 | 188 | ### `sketchybarrc` file 189 | 190 | Run `stats_provider` with desired options by including it in your `sketchybarrc` config: 191 | 192 | ```bash 193 | killall stats_provider 194 | # Update with path to stats_provider 195 | $CONFIG_DIR/sketchybar-system-stats/target/release/stats_provider --cpu usage --disk usage --memory ram_usage & 196 | ``` 197 | 198 | Example: use `stats_provider` to add an item `disk_usage`, subscribe to the `system_stats` event and update the `disk_usage` item. 199 | 200 | ```bash 201 | # Ensure that `stats_provider` is running by invoking it earlier in your `sketchybarrc` file 202 | sketchybar --add item disk_usage right \ 203 | --set disk_usage script="sketchybar --set disk_usage label=\$DISK_USAGE" \ 204 | --subscribe disk_usage system_stats 205 | ``` 206 | 207 | ### `SbarLua` module 208 | 209 | ```lua 210 | -- Update with path to stats_provider 211 | sbar.exec('killall stats_provider >/dev/null; $CONFIG_DIR/sketchybar-system-stats/target/release/stats_provider --cpu usage --disk usage --memory ram_usage') 212 | 213 | -- Subscribe and use the `DISK_USAGE` var 214 | local disk = sbar.add('item', 'disk', { 215 | position = 'right', 216 | }) 217 | disk:subscribe('system_stats', function(env) 218 | disk:set { label = env.DISK_USAGE } 219 | end) 220 | ``` 221 | 222 | ## Why? 223 | 224 | I wanted a single simple, lightweight binary to provide stats to Sketchybar. I also wanted to learn how to code in Rust, and learning by doing is a great way to learn. 225 | 226 | ## Thanks 227 | 228 | * [Sketchybar](https://github.com/FelixKratz/SketchyBar) and [SbarLua](https://github.com/FelixKratz/SbarLua) 229 | * [sketchybar-rs](https://github.com/johnallen3d/sketchybar-rs) 230 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod cli; 2 | mod sketchybar; 3 | mod stats; 4 | 5 | use anyhow::{Context, Result}; 6 | use sketchybar::Sketchybar; 7 | use stats::{ 8 | get_battery_stats, get_cpu_stats, get_disk_stats, get_memory_stats, get_network_stats, 9 | get_system_stats, get_uptime_stats, 10 | }; 11 | use sysinfo::{Components, Disks, Networks, System}; 12 | 13 | struct ProcessedFlags<'a> { 14 | battery_flags: Option<&'a [String]>, 15 | cpu_flags: Option<&'a [String]>, 16 | disk_flags: Option<&'a [String]>, 17 | memory_flags: Option<&'a [String]>, 18 | network_flags: Option<&'a [String]>, 19 | uptime_flags: Option<&'a [String]>, 20 | } 21 | 22 | macro_rules! flag_refs_method { 23 | ($method_name:ident, $field:ident) => { 24 | fn $method_name(&self) -> Option> { 25 | self.$field 26 | .map(|flags| flags.iter().map(String::as_str).collect()) 27 | } 28 | }; 29 | } 30 | 31 | impl<'a> ProcessedFlags<'a> { 32 | flag_refs_method!(battery_flag_refs, battery_flags); 33 | flag_refs_method!(cpu_flag_refs, cpu_flags); 34 | flag_refs_method!(disk_flag_refs, disk_flags); 35 | flag_refs_method!(memory_flag_refs, memory_flags); 36 | flag_refs_method!(uptime_flag_refs, uptime_flags); 37 | } 38 | 39 | struct StatsContext<'a> { 40 | system: &'a mut System, 41 | disks: &'a mut Disks, 42 | networks: &'a mut Networks, 43 | components: &'a Components, 44 | } 45 | 46 | struct StatsConfig<'a> { 47 | flags: ProcessedFlags<'a>, 48 | refresh_kind: sysinfo::RefreshKind, 49 | } 50 | 51 | fn process_cli_flags(cli: &cli::Cli) -> ProcessedFlags<'_> { 52 | ProcessedFlags { 53 | battery_flags: cli.battery.as_deref(), 54 | cpu_flags: cli.cpu.as_deref(), 55 | disk_flags: cli.disk.as_deref(), 56 | memory_flags: cli.memory.as_deref(), 57 | network_flags: cli.network.as_deref(), 58 | uptime_flags: cli.uptime.as_deref(), 59 | } 60 | } 61 | 62 | fn validate_network_interfaces( 63 | networks: &Networks, 64 | requested_interfaces: &[String], 65 | verbose: bool, 66 | ) -> Result<()> { 67 | let available_interfaces: Vec = networks.keys().map(|name| name.to_string()).collect(); 68 | 69 | if available_interfaces.is_empty() { 70 | anyhow::bail!("No network interfaces available on this system"); 71 | } 72 | 73 | for interface in requested_interfaces { 74 | if !available_interfaces.contains(interface) { 75 | let msg = format!( 76 | "Network interface '{}' not found. Available interfaces: {}", 77 | interface, 78 | available_interfaces.join(", ") 79 | ); 80 | if verbose { 81 | eprintln!("Warning: {}", msg); 82 | } 83 | anyhow::bail!("{}", msg); 84 | } 85 | } 86 | 87 | Ok(()) 88 | } 89 | 90 | async fn send_initial_system_stats( 91 | cli: &cli::Cli, 92 | sketchybar: &Sketchybar, 93 | system: &mut System, 94 | refresh_kind: &sysinfo::RefreshKind, 95 | buf: &mut String, 96 | ) -> Result<()> { 97 | if cli.all || cli.system.is_some() { 98 | system.refresh_specifics(*refresh_kind); 99 | let system_flags = match &cli.system { 100 | Some(flags) => flags.iter().map(|s| s.as_str()).collect::>(), 101 | None => cli::all_system_flags(), 102 | }; 103 | buf.clear(); 104 | get_system_stats(&system_flags, buf); 105 | sketchybar 106 | .send_message("trigger", "system_stats", Some(buf), cli.verbose) 107 | .await?; 108 | } 109 | 110 | Ok(()) 111 | } 112 | 113 | async fn get_stats(cli: &cli::Cli, sketchybar: &Sketchybar) -> Result<()> { 114 | let refresh_kind = stats::build_refresh_kind(); 115 | let mut system = System::new_with_specifics(refresh_kind); 116 | let mut disks = Disks::new_with_refreshed_list(); 117 | let mut networks = Networks::new_with_refreshed_list(); 118 | let components = Components::new_with_refreshed_list(); 119 | 120 | if let Some(network_flags) = &cli.network { 121 | validate_network_interfaces(&networks, network_flags, cli.verbose)?; 122 | } 123 | 124 | let flags = process_cli_flags(cli); 125 | let mut message_buffer = String::with_capacity(512); 126 | 127 | send_initial_system_stats( 128 | cli, 129 | sketchybar, 130 | &mut system, 131 | &refresh_kind, 132 | &mut message_buffer, 133 | ) 134 | .await?; 135 | 136 | let config = StatsConfig { 137 | flags, 138 | refresh_kind, 139 | }; 140 | 141 | let mut context = StatsContext { 142 | system: &mut system, 143 | disks: &mut disks, 144 | networks: &mut networks, 145 | components: &components, 146 | }; 147 | 148 | run_stats_loop(cli, sketchybar, &config, &mut context, &mut message_buffer).await 149 | } 150 | 151 | async fn run_stats_loop( 152 | cli: &cli::Cli, 153 | sketchybar: &Sketchybar, 154 | config: &StatsConfig<'_>, 155 | context: &mut StatsContext<'_>, 156 | message_buffer: &mut String, 157 | ) -> Result<()> { 158 | let mut network_refresh_tick = 0; 159 | 160 | loop { 161 | tokio::select! { 162 | result = collect_stats_commands(cli, config, context, network_refresh_tick, message_buffer) => { 163 | network_refresh_tick = result?; 164 | 165 | if cli.verbose { 166 | println!("Current message: {}", message_buffer); 167 | } 168 | sketchybar 169 | .send_message("trigger", "system_stats", Some(message_buffer), cli.verbose) 170 | .await?; 171 | } 172 | _ = tokio::signal::ctrl_c() => { 173 | if cli.verbose { 174 | println!("Received shutdown signal, cleaning up..."); 175 | } 176 | println!("SketchyBar Stats Provider is shutting down."); 177 | return Ok(()); 178 | } 179 | } 180 | } 181 | } 182 | 183 | async fn collect_stats_commands( 184 | cli: &cli::Cli, 185 | config: &StatsConfig<'_>, 186 | context: &mut StatsContext<'_>, 187 | network_refresh_tick: u32, 188 | buf: &mut String, 189 | ) -> Result { 190 | buf.clear(); 191 | 192 | tokio::time::sleep(tokio::time::Duration::from_secs(cli.interval.into())).await; 193 | context.system.refresh_specifics(config.refresh_kind); 194 | context.disks.refresh(true); 195 | 196 | let mut updated_tick = network_refresh_tick + 1; 197 | if updated_tick >= cli.network_refresh_rate { 198 | *context.networks = Networks::new_with_refreshed_list(); 199 | updated_tick = 0; 200 | } else { 201 | context.networks.refresh(true); 202 | } 203 | 204 | if cli.all { 205 | get_battery_stats(&cli::all_battery_flags(), cli.no_units, buf); 206 | get_cpu_stats( 207 | context.system, 208 | context.components, 209 | &cli::all_cpu_flags(), 210 | cli.no_units, 211 | buf, 212 | ); 213 | get_disk_stats(context.disks, &cli::all_disk_flags(), cli.no_units, buf); 214 | get_memory_stats(context.system, &cli::all_memory_flags(), cli.no_units, buf); 215 | get_network_stats(context.networks, None, cli.interval, cli.no_units, buf); 216 | get_uptime_stats(&cli::all_uptime_flags(), buf); 217 | } else { 218 | if let Some(battery_flag_refs) = config.flags.battery_flag_refs() { 219 | get_battery_stats(&battery_flag_refs, cli.no_units, buf); 220 | } 221 | 222 | if let Some(cpu_flag_refs) = config.flags.cpu_flag_refs() { 223 | get_cpu_stats( 224 | context.system, 225 | context.components, 226 | &cpu_flag_refs, 227 | cli.no_units, 228 | buf, 229 | ); 230 | } 231 | 232 | if let Some(disk_flag_refs) = config.flags.disk_flag_refs() { 233 | get_disk_stats(context.disks, &disk_flag_refs, cli.no_units, buf); 234 | } 235 | 236 | if let Some(memory_flag_refs) = config.flags.memory_flag_refs() { 237 | get_memory_stats(context.system, &memory_flag_refs, cli.no_units, buf); 238 | } 239 | 240 | if let Some(network_flags) = config.flags.network_flags { 241 | get_network_stats( 242 | context.networks, 243 | Some(network_flags), 244 | cli.interval, 245 | cli.no_units, 246 | buf, 247 | ); 248 | } 249 | 250 | if let Some(uptime_flag_refs) = config.flags.uptime_flag_refs() { 251 | get_uptime_stats(&uptime_flag_refs, buf); 252 | } 253 | } 254 | 255 | Ok(updated_tick) 256 | } 257 | 258 | #[cfg(target_os = "macos")] 259 | #[tokio::main] 260 | async fn main() -> Result<()> { 261 | let cli = cli::parse_args(); 262 | 263 | cli::validate_cli(&cli).context("Invalid CLI arguments")?; 264 | 265 | println!("SketchyBar Stats Provider is running."); 266 | 267 | if cli.verbose { 268 | println!("Stats Provider CLI: {cli:?}"); 269 | } 270 | let sketchybar = 271 | Sketchybar::new(cli.bar.as_deref()).context("Failed to create Sketchybar instance")?; 272 | 273 | sketchybar 274 | .send_message("add event", "system_stats", None, cli.verbose) 275 | .await?; 276 | 277 | get_stats(&cli, &sketchybar).await?; 278 | 279 | Ok(()) 280 | } 281 | 282 | #[cfg(test)] 283 | mod tests { 284 | use super::*; 285 | 286 | #[test] 287 | fn test_process_cli_flags() { 288 | let cli = cli::Cli { 289 | all: false, 290 | battery: None, 291 | cpu: Some(vec!["usage".to_string()]), 292 | disk: None, 293 | memory: Some(vec!["ram_total".to_string()]), 294 | network: None, 295 | system: None, 296 | uptime: None, 297 | interval: 5, 298 | network_refresh_rate: 5, 299 | bar: None, 300 | verbose: false, 301 | no_units: false, 302 | }; 303 | 304 | let flags = process_cli_flags(&cli); 305 | 306 | assert!(flags.cpu_flags.is_some()); 307 | assert!(flags.disk_flags.is_none()); 308 | assert!(flags.memory_flags.is_some()); 309 | assert!(flags.network_flags.is_none()); 310 | } 311 | 312 | #[test] 313 | fn test_processed_flags_cpu_flag_refs() { 314 | let cpu_flags = vec!["usage".to_string(), "count".to_string()]; 315 | let flags = ProcessedFlags { 316 | battery_flags: None, 317 | cpu_flags: Some(&cpu_flags), 318 | disk_flags: None, 319 | memory_flags: None, 320 | network_flags: None, 321 | uptime_flags: None, 322 | }; 323 | 324 | let refs = flags.cpu_flag_refs(); 325 | assert!(refs.is_some()); 326 | let refs_vec = refs.unwrap(); 327 | assert_eq!(refs_vec.len(), 2); 328 | assert_eq!(refs_vec[0], "usage"); 329 | assert_eq!(refs_vec[1], "count"); 330 | } 331 | 332 | #[test] 333 | fn test_processed_flags_returns_none_when_empty() { 334 | let flags = ProcessedFlags { 335 | battery_flags: None, 336 | cpu_flags: None, 337 | disk_flags: None, 338 | memory_flags: None, 339 | network_flags: None, 340 | uptime_flags: None, 341 | }; 342 | 343 | assert!(flags.cpu_flag_refs().is_none()); 344 | assert!(flags.disk_flag_refs().is_none()); 345 | assert!(flags.memory_flag_refs().is_none()); 346 | assert!(flags.uptime_flag_refs().is_none()); 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.20" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.7" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.4" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 40 | dependencies = [ 41 | "windows-sys 0.60.2", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 49 | dependencies = [ 50 | "anstyle", 51 | "once_cell_polyfill", 52 | "windows-sys 0.60.2", 53 | ] 54 | 55 | [[package]] 56 | name = "anyhow" 57 | version = "1.0.100" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 60 | 61 | [[package]] 62 | name = "autocfg" 63 | version = "1.5.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 66 | 67 | [[package]] 68 | name = "base64" 69 | version = "0.22.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 72 | 73 | [[package]] 74 | name = "bitflags" 75 | version = "2.9.4" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" 78 | 79 | [[package]] 80 | name = "bytes" 81 | version = "1.10.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 84 | 85 | [[package]] 86 | name = "cc" 87 | version = "1.2.49" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" 90 | dependencies = [ 91 | "find-msvc-tools", 92 | "shlex", 93 | ] 94 | 95 | [[package]] 96 | name = "cfg-if" 97 | version = "1.0.3" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" 100 | 101 | [[package]] 102 | name = "cfg_aliases" 103 | version = "0.2.1" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 106 | 107 | [[package]] 108 | name = "clap" 109 | version = "4.5.53" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" 112 | dependencies = [ 113 | "clap_builder", 114 | "clap_derive", 115 | ] 116 | 117 | [[package]] 118 | name = "clap_builder" 119 | version = "4.5.53" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" 122 | dependencies = [ 123 | "anstream", 124 | "anstyle", 125 | "clap_lex", 126 | "strsim", 127 | ] 128 | 129 | [[package]] 130 | name = "clap_derive" 131 | version = "4.5.49" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" 134 | dependencies = [ 135 | "heck", 136 | "proc-macro2", 137 | "quote", 138 | "syn", 139 | ] 140 | 141 | [[package]] 142 | name = "clap_lex" 143 | version = "0.7.5" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 146 | 147 | [[package]] 148 | name = "colorchoice" 149 | version = "1.0.4" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 152 | 153 | [[package]] 154 | name = "core-foundation" 155 | version = "0.10.1" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 158 | dependencies = [ 159 | "core-foundation-sys", 160 | "libc", 161 | ] 162 | 163 | [[package]] 164 | name = "core-foundation-sys" 165 | version = "0.8.7" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 168 | 169 | [[package]] 170 | name = "deranged" 171 | version = "0.5.4" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" 174 | dependencies = [ 175 | "powerfmt", 176 | ] 177 | 178 | [[package]] 179 | name = "equivalent" 180 | version = "1.0.2" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 183 | 184 | [[package]] 185 | name = "find-msvc-tools" 186 | version = "0.1.5" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 189 | 190 | [[package]] 191 | name = "hashbrown" 192 | version = "0.16.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 195 | 196 | [[package]] 197 | name = "heck" 198 | version = "0.5.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 201 | 202 | [[package]] 203 | name = "indexmap" 204 | version = "2.11.4" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" 207 | dependencies = [ 208 | "equivalent", 209 | "hashbrown", 210 | ] 211 | 212 | [[package]] 213 | name = "is_terminal_polyfill" 214 | version = "1.70.1" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 217 | 218 | [[package]] 219 | name = "itoa" 220 | version = "1.0.15" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 223 | 224 | [[package]] 225 | name = "lazycell" 226 | version = "1.3.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 229 | 230 | [[package]] 231 | name = "libc" 232 | version = "0.2.175" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" 235 | 236 | [[package]] 237 | name = "lock_api" 238 | version = "0.4.13" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 241 | dependencies = [ 242 | "autocfg", 243 | "scopeguard", 244 | ] 245 | 246 | [[package]] 247 | name = "mach2" 248 | version = "0.5.0" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" 251 | dependencies = [ 252 | "libc", 253 | ] 254 | 255 | [[package]] 256 | name = "memchr" 257 | version = "2.7.5" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 260 | 261 | [[package]] 262 | name = "mio" 263 | version = "1.0.4" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 266 | dependencies = [ 267 | "libc", 268 | "wasi", 269 | "windows-sys 0.59.0", 270 | ] 271 | 272 | [[package]] 273 | name = "nix" 274 | version = "0.30.1" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" 277 | dependencies = [ 278 | "bitflags", 279 | "cfg-if", 280 | "cfg_aliases", 281 | "libc", 282 | ] 283 | 284 | [[package]] 285 | name = "ntapi" 286 | version = "0.4.1" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" 289 | dependencies = [ 290 | "winapi", 291 | ] 292 | 293 | [[package]] 294 | name = "num-conv" 295 | version = "0.1.0" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 298 | 299 | [[package]] 300 | name = "num-traits" 301 | version = "0.2.19" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 304 | dependencies = [ 305 | "autocfg", 306 | ] 307 | 308 | [[package]] 309 | name = "objc2-core-foundation" 310 | version = "0.3.1" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" 313 | dependencies = [ 314 | "bitflags", 315 | ] 316 | 317 | [[package]] 318 | name = "objc2-io-kit" 319 | version = "0.3.1" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" 322 | dependencies = [ 323 | "libc", 324 | "objc2-core-foundation", 325 | ] 326 | 327 | [[package]] 328 | name = "once_cell_polyfill" 329 | version = "1.70.1" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 332 | 333 | [[package]] 334 | name = "parking_lot" 335 | version = "0.12.4" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 338 | dependencies = [ 339 | "lock_api", 340 | "parking_lot_core", 341 | ] 342 | 343 | [[package]] 344 | name = "parking_lot_core" 345 | version = "0.9.11" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 348 | dependencies = [ 349 | "cfg-if", 350 | "libc", 351 | "redox_syscall", 352 | "smallvec", 353 | "windows-targets 0.52.6", 354 | ] 355 | 356 | [[package]] 357 | name = "pin-project-lite" 358 | version = "0.2.16" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 361 | 362 | [[package]] 363 | name = "plist" 364 | version = "1.8.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" 367 | dependencies = [ 368 | "base64", 369 | "indexmap", 370 | "quick-xml", 371 | "serde", 372 | "time", 373 | ] 374 | 375 | [[package]] 376 | name = "powerfmt" 377 | version = "0.2.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 380 | 381 | [[package]] 382 | name = "proc-macro2" 383 | version = "1.0.101" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 386 | dependencies = [ 387 | "unicode-ident", 388 | ] 389 | 390 | [[package]] 391 | name = "quick-xml" 392 | version = "0.38.3" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" 395 | dependencies = [ 396 | "memchr", 397 | ] 398 | 399 | [[package]] 400 | name = "quote" 401 | version = "1.0.40" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 404 | dependencies = [ 405 | "proc-macro2", 406 | ] 407 | 408 | [[package]] 409 | name = "redox_syscall" 410 | version = "0.5.17" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" 413 | dependencies = [ 414 | "bitflags", 415 | ] 416 | 417 | [[package]] 418 | name = "scopeguard" 419 | version = "1.2.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 422 | 423 | [[package]] 424 | name = "serde" 425 | version = "1.0.228" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 428 | dependencies = [ 429 | "serde_core", 430 | ] 431 | 432 | [[package]] 433 | name = "serde_core" 434 | version = "1.0.228" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 437 | dependencies = [ 438 | "serde_derive", 439 | ] 440 | 441 | [[package]] 442 | name = "serde_derive" 443 | version = "1.0.228" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 446 | dependencies = [ 447 | "proc-macro2", 448 | "quote", 449 | "syn", 450 | ] 451 | 452 | [[package]] 453 | name = "shlex" 454 | version = "1.3.0" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 457 | 458 | [[package]] 459 | name = "signal-hook-registry" 460 | version = "1.4.6" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" 463 | dependencies = [ 464 | "libc", 465 | ] 466 | 467 | [[package]] 468 | name = "smallvec" 469 | version = "1.15.1" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 472 | 473 | [[package]] 474 | name = "socket2" 475 | version = "0.6.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" 478 | dependencies = [ 479 | "libc", 480 | "windows-sys 0.59.0", 481 | ] 482 | 483 | [[package]] 484 | name = "starship-battery" 485 | version = "0.10.3" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "fd0efc2c44c92705be724265a0c758e3b7c120ea63817d2d684bab86fbeced9a" 488 | dependencies = [ 489 | "cfg-if", 490 | "core-foundation", 491 | "lazycell", 492 | "libc", 493 | "mach2", 494 | "nix", 495 | "num-traits", 496 | "plist", 497 | "uom", 498 | "windows-sys 0.61.2", 499 | ] 500 | 501 | [[package]] 502 | name = "stats_provider" 503 | version = "0.8.0" 504 | dependencies = [ 505 | "anyhow", 506 | "cc", 507 | "clap", 508 | "starship-battery", 509 | "sysinfo", 510 | "tokio", 511 | ] 512 | 513 | [[package]] 514 | name = "strsim" 515 | version = "0.11.1" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 518 | 519 | [[package]] 520 | name = "syn" 521 | version = "2.0.106" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 524 | dependencies = [ 525 | "proc-macro2", 526 | "quote", 527 | "unicode-ident", 528 | ] 529 | 530 | [[package]] 531 | name = "sysinfo" 532 | version = "0.37.2" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" 535 | dependencies = [ 536 | "libc", 537 | "memchr", 538 | "ntapi", 539 | "objc2-core-foundation", 540 | "objc2-io-kit", 541 | "windows", 542 | ] 543 | 544 | [[package]] 545 | name = "time" 546 | version = "0.3.44" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 549 | dependencies = [ 550 | "deranged", 551 | "itoa", 552 | "num-conv", 553 | "powerfmt", 554 | "serde", 555 | "time-core", 556 | "time-macros", 557 | ] 558 | 559 | [[package]] 560 | name = "time-core" 561 | version = "0.1.6" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 564 | 565 | [[package]] 566 | name = "time-macros" 567 | version = "0.2.24" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" 570 | dependencies = [ 571 | "num-conv", 572 | "time-core", 573 | ] 574 | 575 | [[package]] 576 | name = "tokio" 577 | version = "1.48.0" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" 580 | dependencies = [ 581 | "bytes", 582 | "libc", 583 | "mio", 584 | "parking_lot", 585 | "pin-project-lite", 586 | "signal-hook-registry", 587 | "socket2", 588 | "tokio-macros", 589 | "windows-sys 0.61.2", 590 | ] 591 | 592 | [[package]] 593 | name = "tokio-macros" 594 | version = "2.6.0" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 597 | dependencies = [ 598 | "proc-macro2", 599 | "quote", 600 | "syn", 601 | ] 602 | 603 | [[package]] 604 | name = "typenum" 605 | version = "1.19.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 608 | 609 | [[package]] 610 | name = "unicode-ident" 611 | version = "1.0.18" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 614 | 615 | [[package]] 616 | name = "uom" 617 | version = "0.37.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "cd5cfe7d84f6774726717f358a37f5bca8fca273bed4de40604ad129d1107b49" 620 | dependencies = [ 621 | "num-traits", 622 | "typenum", 623 | ] 624 | 625 | [[package]] 626 | name = "utf8parse" 627 | version = "0.2.2" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 630 | 631 | [[package]] 632 | name = "wasi" 633 | version = "0.11.1+wasi-snapshot-preview1" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 636 | 637 | [[package]] 638 | name = "winapi" 639 | version = "0.3.9" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 642 | dependencies = [ 643 | "winapi-i686-pc-windows-gnu", 644 | "winapi-x86_64-pc-windows-gnu", 645 | ] 646 | 647 | [[package]] 648 | name = "winapi-i686-pc-windows-gnu" 649 | version = "0.4.0" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 652 | 653 | [[package]] 654 | name = "winapi-x86_64-pc-windows-gnu" 655 | version = "0.4.0" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 658 | 659 | [[package]] 660 | name = "windows" 661 | version = "0.61.3" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" 664 | dependencies = [ 665 | "windows-collections", 666 | "windows-core", 667 | "windows-future", 668 | "windows-link 0.1.3", 669 | "windows-numerics", 670 | ] 671 | 672 | [[package]] 673 | name = "windows-collections" 674 | version = "0.2.0" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" 677 | dependencies = [ 678 | "windows-core", 679 | ] 680 | 681 | [[package]] 682 | name = "windows-core" 683 | version = "0.61.2" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 686 | dependencies = [ 687 | "windows-implement", 688 | "windows-interface", 689 | "windows-link 0.1.3", 690 | "windows-result", 691 | "windows-strings", 692 | ] 693 | 694 | [[package]] 695 | name = "windows-future" 696 | version = "0.2.1" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" 699 | dependencies = [ 700 | "windows-core", 701 | "windows-link 0.1.3", 702 | "windows-threading", 703 | ] 704 | 705 | [[package]] 706 | name = "windows-implement" 707 | version = "0.60.0" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 710 | dependencies = [ 711 | "proc-macro2", 712 | "quote", 713 | "syn", 714 | ] 715 | 716 | [[package]] 717 | name = "windows-interface" 718 | version = "0.59.1" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 721 | dependencies = [ 722 | "proc-macro2", 723 | "quote", 724 | "syn", 725 | ] 726 | 727 | [[package]] 728 | name = "windows-link" 729 | version = "0.1.3" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 732 | 733 | [[package]] 734 | name = "windows-link" 735 | version = "0.2.1" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 738 | 739 | [[package]] 740 | name = "windows-numerics" 741 | version = "0.2.0" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" 744 | dependencies = [ 745 | "windows-core", 746 | "windows-link 0.1.3", 747 | ] 748 | 749 | [[package]] 750 | name = "windows-result" 751 | version = "0.3.4" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 754 | dependencies = [ 755 | "windows-link 0.1.3", 756 | ] 757 | 758 | [[package]] 759 | name = "windows-strings" 760 | version = "0.4.2" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 763 | dependencies = [ 764 | "windows-link 0.1.3", 765 | ] 766 | 767 | [[package]] 768 | name = "windows-sys" 769 | version = "0.59.0" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 772 | dependencies = [ 773 | "windows-targets 0.52.6", 774 | ] 775 | 776 | [[package]] 777 | name = "windows-sys" 778 | version = "0.60.2" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 781 | dependencies = [ 782 | "windows-targets 0.53.3", 783 | ] 784 | 785 | [[package]] 786 | name = "windows-sys" 787 | version = "0.61.2" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 790 | dependencies = [ 791 | "windows-link 0.2.1", 792 | ] 793 | 794 | [[package]] 795 | name = "windows-targets" 796 | version = "0.52.6" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 799 | dependencies = [ 800 | "windows_aarch64_gnullvm 0.52.6", 801 | "windows_aarch64_msvc 0.52.6", 802 | "windows_i686_gnu 0.52.6", 803 | "windows_i686_gnullvm 0.52.6", 804 | "windows_i686_msvc 0.52.6", 805 | "windows_x86_64_gnu 0.52.6", 806 | "windows_x86_64_gnullvm 0.52.6", 807 | "windows_x86_64_msvc 0.52.6", 808 | ] 809 | 810 | [[package]] 811 | name = "windows-targets" 812 | version = "0.53.3" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" 815 | dependencies = [ 816 | "windows-link 0.1.3", 817 | "windows_aarch64_gnullvm 0.53.0", 818 | "windows_aarch64_msvc 0.53.0", 819 | "windows_i686_gnu 0.53.0", 820 | "windows_i686_gnullvm 0.53.0", 821 | "windows_i686_msvc 0.53.0", 822 | "windows_x86_64_gnu 0.53.0", 823 | "windows_x86_64_gnullvm 0.53.0", 824 | "windows_x86_64_msvc 0.53.0", 825 | ] 826 | 827 | [[package]] 828 | name = "windows-threading" 829 | version = "0.1.0" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" 832 | dependencies = [ 833 | "windows-link 0.1.3", 834 | ] 835 | 836 | [[package]] 837 | name = "windows_aarch64_gnullvm" 838 | version = "0.52.6" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 841 | 842 | [[package]] 843 | name = "windows_aarch64_gnullvm" 844 | version = "0.53.0" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 847 | 848 | [[package]] 849 | name = "windows_aarch64_msvc" 850 | version = "0.52.6" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 853 | 854 | [[package]] 855 | name = "windows_aarch64_msvc" 856 | version = "0.53.0" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 859 | 860 | [[package]] 861 | name = "windows_i686_gnu" 862 | version = "0.52.6" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 865 | 866 | [[package]] 867 | name = "windows_i686_gnu" 868 | version = "0.53.0" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 871 | 872 | [[package]] 873 | name = "windows_i686_gnullvm" 874 | version = "0.52.6" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 877 | 878 | [[package]] 879 | name = "windows_i686_gnullvm" 880 | version = "0.53.0" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 883 | 884 | [[package]] 885 | name = "windows_i686_msvc" 886 | version = "0.52.6" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 889 | 890 | [[package]] 891 | name = "windows_i686_msvc" 892 | version = "0.53.0" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 895 | 896 | [[package]] 897 | name = "windows_x86_64_gnu" 898 | version = "0.52.6" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 901 | 902 | [[package]] 903 | name = "windows_x86_64_gnu" 904 | version = "0.53.0" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 907 | 908 | [[package]] 909 | name = "windows_x86_64_gnullvm" 910 | version = "0.52.6" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 913 | 914 | [[package]] 915 | name = "windows_x86_64_gnullvm" 916 | version = "0.53.0" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 919 | 920 | [[package]] 921 | name = "windows_x86_64_msvc" 922 | version = "0.52.6" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 925 | 926 | [[package]] 927 | name = "windows_x86_64_msvc" 928 | version = "0.53.0" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 931 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------