Commit afc00b7

Karn Wong <[email protected]>
2025-07-28 06:03:23
feat(getSystemInfo): add battery cycle count
1 parent 7564a4d
internal/get/system_info.go
@@ -1,5 +1,11 @@
 package get
 
+/*
+#cgo LDFLAGS: ./lib/libsystem.dylib
+#include "../../lib/system.h"
+#include <stdlib.h>
+*/
+import "C"
 import (
 	"fmt"
 	"math"
@@ -38,6 +44,7 @@ type systemInfoStruct struct {
 	BatteryCurrent        float64
 	BatteryFull           float64
 	BatteryDesignCapacity float64
+	BatteryCycleCount     uint64
 }
 
 func convertKBtoGB(v uint64) float64 {
@@ -108,6 +115,23 @@ func getSystemInfo() systemInfoStruct {
 		batteryDesignCapacity = batteries[0].Design
 	}
 
+	//// cycle count
+	var batteryCycleCount uint64
+	result := C.battery_cycle_count()
+
+	switch result.error {
+	case C.BATTERY_SUCCESS:
+		batteryCycleCount = uint64(result.cycle_count)
+	case C.BATTERY_NO_BATTERY:
+		batteryCycleCount = 0
+	case C.BATTERY_NO_CYCLE_COUNT:
+		batteryCycleCount = 0
+	case C.BATTERY_MANAGER_ERROR:
+		log.Fatal().Msg("Battery manager error")
+	default:
+		log.Fatal().Msg("Unknown error occurred")
+	}
+
 	// return
 	return systemInfoStruct{
 		Username:              username.Username,
@@ -122,6 +146,7 @@ func getSystemInfo() systemInfoStruct {
 		BatteryCurrent:        batteryCurrent,
 		BatteryFull:           batteryFull,
 		BatteryDesignCapacity: batteryDesignCapacity,
+		BatteryCycleCount:     batteryCycleCount,
 	}
 }
 
@@ -161,7 +186,7 @@ func SystemInfo() {
 			batteryPercentStr = color.Red(batteryFormat)
 		}
 
-		batteryStdout := fmt.Sprintf("%s: %s (Health: %s)", color.Green("Battery"), batteryPercentStr, color.Blue(strconv.Itoa(batteryHealth)+"%"))
+		batteryStdout := fmt.Sprintf("%s: %s (Health: %s, Cycles: %s)", color.Green("Battery"), batteryPercentStr, color.Blue(strconv.Itoa(batteryHealth)+"%"), color.Blue(strconv.Itoa(int(systemInfo.BatteryCycleCount))))
 		fmt.Println(batteryStdout)
 	}
 }
lib/system/src/battery_cycle_count.rs
@@ -0,0 +1,113 @@
+use battery;
+use log::error;
+
+#[repr(C)]
+#[derive(Debug)]
+pub enum BatteryError {
+    Success = 0,
+    NoBattery = 1,
+    NoCycleCount = 2,
+    ManagerError = 3,
+}
+
+#[repr(C)]
+pub struct BatteryResult {
+    pub cycle_count: u32,
+    pub error: BatteryError,
+}
+
+#[no_mangle]
+pub extern "C" fn battery_cycle_count() -> BatteryResult {
+    match battery::Manager::new() {
+        Ok(manager) => {
+            match manager.batteries() {
+                Ok(batteries) => {
+                    // Get the first battery if available
+                    if let Some(battery) = batteries.into_iter().next() {
+                        match battery {
+                            Ok(bat) => {
+                                match bat.cycle_count() {
+                                    Some(count) => BatteryResult {
+                                        cycle_count: count,
+                                        error: BatteryError::Success,
+                                    },
+                                    None => {
+                                        error!("No cycle count available");
+                                        BatteryResult {
+                                            cycle_count: 0,
+                                            error: BatteryError::NoCycleCount,
+                                        }
+                                    }
+                                }
+                            }
+                            Err(e) => {
+                                error!("Battery error: {}", e);
+                                BatteryResult {
+                                    cycle_count: 0,
+                                    error: BatteryError::NoBattery,
+                                }
+                            }
+                        }
+                    } else {
+                        error!("No batteries found");
+                        BatteryResult {
+                            cycle_count: 0,
+                            error: BatteryError::NoBattery,
+                        }
+                    }
+                }
+                Err(e) => {
+                    error!("Failed to get batteries: {}", e);
+                    BatteryResult {
+                        cycle_count: 0,
+                        error: BatteryError::ManagerError,
+                    }
+                }
+            }
+        }
+        Err(e) => {
+            error!("Failed to create battery manager: {}", e);
+            BatteryResult {
+                cycle_count: 0,
+                error: BatteryError::ManagerError,
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_battery_cycle_count() {
+        let result = battery_cycle_count();
+
+        match result.error {
+            BatteryError::Success => {
+                println!("Battery cycle count: {}", result.cycle_count);
+                assert!(result.cycle_count >= 1);
+            }
+            BatteryError::NoBattery => {
+                println!("No battery found");
+                assert_eq!(result.cycle_count, 0);
+            }
+            BatteryError::NoCycleCount => {
+                println!("No cycle count available");
+                assert_eq!(result.cycle_count, 0);
+            }
+            BatteryError::ManagerError => {
+                println!("Battery manager error");
+                assert_eq!(result.cycle_count, 0);
+            }
+        }
+
+        assert!(matches!(
+            result.error,
+            BatteryError::Success
+                | BatteryError::NoBattery
+                | BatteryError::NoCycleCount
+                | BatteryError::ManagerError
+        ));
+    }
+}
lib/system/src/lib.rs
@@ -1,5 +1,9 @@
+extern crate battery;
 extern crate log;
 extern crate sysinfo;
 
 mod sensors;
+mod battery_cycle_count;
+
+pub use battery_cycle_count::{battery_cycle_count, BatteryError, BatteryResult};
 pub use sensors::{sensors, SensorError, SensorResult};
lib/system/Cargo.lock
@@ -2,12 +2,78 @@
 # It is not intended for manual editing.
 version = 4
 
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "battery"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4b624268937c0e0a3edb7c27843f9e547c320d730c610d3b8e6e8e95b2026e4"
+dependencies = [
+ "cfg-if",
+ "core-foundation",
+ "lazycell",
+ "libc",
+ "mach",
+ "nix",
+ "num-traits",
+ "uom",
+ "winapi",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
 [[package]]
 name = "bitflags"
 version = "2.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
 
+[[package]]
+name = "cc"
+version = "1.2.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+
+[[package]]
+name = "core-foundation"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
 [[package]]
 name = "libc"
 version = "0.2.174"
@@ -20,12 +86,33 @@ version = "0.4.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
 
+[[package]]
+name = "mach"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "memchr"
 version = "2.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
 
+[[package]]
+name = "nix"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2"
+dependencies = [
+ "bitflags 1.3.2",
+ "cc",
+ "cfg-if",
+ "libc",
+]
+
 [[package]]
 name = "ntapi"
 version = "0.4.1"
@@ -35,13 +122,22 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "objc2-core-foundation"
 version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
 dependencies = [
- "bitflags",
+ "bitflags 2.9.1",
 ]
 
 [[package]]
@@ -72,6 +168,12 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
 [[package]]
 name = "syn"
 version = "2.0.104"
@@ -101,17 +203,34 @@ dependencies = [
 name = "system"
 version = "0.1.0"
 dependencies = [
+ "battery",
  "libc",
  "log",
  "sysinfo",
 ]
 
+[[package]]
+name = "typenum"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
 
+[[package]]
+name = "uom"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e76503e636584f1e10b9b3b9498538279561adcef5412927ba00c2b32c4ce5ed"
+dependencies = [
+ "num-traits",
+ "typenum",
+]
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
lib/system/Cargo.toml
@@ -6,6 +6,7 @@ version = "0.1.0"
 crate-type = ["staticlib"]
 
 [dependencies]
+battery = "0.7.8"
 libc = "0.2.174"
 log = "0.4.27"
 sysinfo = "0.36.1"
lib/system.h
@@ -1,3 +1,20 @@
+// battery
+enum BatteryError {
+    BATTERY_SUCCESS = 0,
+    BATTERY_NO_BATTERY = 1,
+    BATTERY_NO_CYCLE_COUNT = 2,
+    BATTERY_MANAGER_ERROR = 3
+};
+
+struct BatteryResult {
+    unsigned int cycle_count;
+    enum BatteryError error;
+};
+
+struct BatteryResult battery_cycle_count(void);
+
+
+// sensors
 enum SensorError {
     SENSOR_SUCCESS = 0,
     SENSOR_NO_COMPONENTS = 1,
.pre-commit-config.yaml
@@ -17,7 +17,6 @@ repos:
   - repo: https://github.com/kahnwong/pre-commit
     rev: "791fd68"
     hooks:
-      - id: go-build
       - id: go-fmt
       - id: go-imports
       - id: go-mod-tidy
@@ -25,3 +24,12 @@ repos:
       - id: go-vet
       - id: golangci-lint
       - id: yamlfmt
+  - repo: local
+    hooks:
+      - id: go-build
+        name: go-build
+        entry: "make build-dynamic"
+        language: system
+        types: [go]
+        pass_filenames: false
+        require_serial: true
\ No newline at end of file