Commit 998aafe

Karn Wong <[email protected]>
2025-09-20 12:37:47
feat(system-info): add battery time remaining
1 parent ba3c891
Changed files (4)
internal/get/system_info.go
@@ -47,6 +47,7 @@ type systemInfoStruct struct {
 	BatteryFull           float64
 	BatteryDesignCapacity float64
 	BatteryCycleCount     uint64
+	BatteryTimeToEmpty    uint64
 }
 
 func convertKBtoGB(v uint64) float64 {
@@ -134,6 +135,23 @@ func getSystemInfo() systemInfoStruct {
 		log.Fatal().Msg("Unknown error occurred")
 	}
 
+	//// time to empty
+	var batteryTimeToEmpty uint64
+	resultTimeToEmpty := C.battery_time_to_empty()
+
+	switch result.error {
+	case C.BATTERY_TIME_TO_EMPTY_SUCCESS:
+		batteryTimeToEmpty = uint64(resultTimeToEmpty.time_to_empty_seconds)
+	case C.BATTERY_TIME_TO_EMPTY_NO_BATTERY:
+		batteryTimeToEmpty = 0
+	case C.BATTERY_TIME_TO_EMPTY_NO_TIME_TO_EMPTY:
+		batteryTimeToEmpty = 0
+	case C.BATTERY_TIME_TO_EMPTY_MANAGER_ERROR:
+		log.Fatal().Msg("Battery manager error")
+	default:
+		log.Fatal().Msg("Unknown error occurred")
+	}
+
 	// return
 	return systemInfoStruct{
 		Username:              username.Username,
@@ -149,6 +167,7 @@ func getSystemInfo() systemInfoStruct {
 		BatteryFull:           batteryFull,
 		BatteryDesignCapacity: batteryDesignCapacity,
 		BatteryCycleCount:     batteryCycleCount,
+		BatteryTimeToEmpty:    batteryTimeToEmpty,
 	}
 }
 
@@ -188,7 +207,23 @@ func SystemInfo() {
 			batteryPercentStr = color.Red(batteryFormat)
 		}
 
-		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))))
+		// convert BatteryTimeToEmpty from second to hour
+		var batteryTimeToEmptyFormatted string
+		if systemInfo.BatteryTimeToEmpty > 0 {
+			hours := systemInfo.BatteryTimeToEmpty / 3600
+			minutes := (systemInfo.BatteryTimeToEmpty % 3600) / 60
+			batteryTimeToEmptyFormatted = fmt.Sprintf("%02d:%02d", hours, minutes)
+		} else {
+			batteryTimeToEmptyFormatted = "--:--"
+		}
+
+		batteryStdout := fmt.Sprintf(
+			"%s: %s (Health: %s, Cycles: %s, Time Remaining: %s)",
+			color.Green("Battery"), batteryPercentStr,
+			color.Blue(strconv.Itoa(batteryHealth)+"%"),
+			color.Blue(strconv.Itoa(int(systemInfo.BatteryCycleCount))),
+			color.Blue(batteryTimeToEmptyFormatted),
+		)
 		fmt.Println(batteryStdout)
 	}
 }
lib/system/src/battery_time_to_empty.rs
@@ -0,0 +1,120 @@
+use battery;
+use log::error;
+
+#[repr(C)]
+#[derive(Debug)]
+pub enum BatteryTimeToEmptyError {
+    Success = 0,
+    NoBattery = 1,
+    NoTimeToEmpty = 2,
+    ManagerError = 3,
+}
+
+#[repr(C)]
+pub struct BatteryTimeToEmptyResult {
+    pub time_to_empty_seconds: u64,
+    pub error: BatteryTimeToEmptyError,
+}
+
+#[no_mangle]
+pub extern "C" fn battery_time_to_empty() -> BatteryTimeToEmptyResult {
+    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.time_to_empty() {
+                                    Some(time) => {
+                                        // Convert Time to seconds as u64
+                                        let seconds = time.get::<battery::units::time::second>();
+                                        BatteryTimeToEmptyResult {
+                                            time_to_empty_seconds: seconds as u64,
+                                            error: BatteryTimeToEmptyError::Success,
+                                        }
+                                    }
+                                    None => {
+                                        error!("No time to empty available");
+                                        BatteryTimeToEmptyResult {
+                                            time_to_empty_seconds: 0,
+                                            error: BatteryTimeToEmptyError::NoTimeToEmpty,
+                                        }
+                                    }
+                                }
+                            }
+                            Err(e) => {
+                                error!("Battery error: {}", e);
+                                BatteryTimeToEmptyResult {
+                                    time_to_empty_seconds: 0,
+                                    error: BatteryTimeToEmptyError::NoBattery,
+                                }
+                            }
+                        }
+                    } else {
+                        error!("No batteries found");
+                        BatteryTimeToEmptyResult {
+                            time_to_empty_seconds: 0,
+                            error: BatteryTimeToEmptyError::NoBattery,
+                        }
+                    }
+                }
+                Err(e) => {
+                    error!("Failed to get batteries: {}", e);
+                    BatteryTimeToEmptyResult {
+                        time_to_empty_seconds: 0,
+                        error: BatteryTimeToEmptyError::ManagerError,
+                    }
+                }
+            }
+        }
+        Err(e) => {
+            error!("Failed to create battery manager: {}", e);
+            BatteryTimeToEmptyResult {
+                time_to_empty_seconds: 0,
+                error: BatteryTimeToEmptyError::ManagerError,
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_battery_time_to_empty() {
+        let result = battery_time_to_empty();
+
+        match result.error {
+            BatteryTimeToEmptyError::Success => {
+                println!(
+                    "Battery time to empty: {} seconds",
+                    result.time_to_empty_seconds
+                );
+                assert!(result.time_to_empty_seconds > 0);
+            }
+            BatteryTimeToEmptyError::NoBattery => {
+                println!("No battery found");
+                assert_eq!(result.time_to_empty_seconds, 0);
+            }
+            BatteryTimeToEmptyError::NoTimeToEmpty => {
+                println!("No time to empty available");
+                assert_eq!(result.time_to_empty_seconds, 0);
+            }
+            BatteryTimeToEmptyError::ManagerError => {
+                println!("Battery manager error");
+                assert_eq!(result.time_to_empty_seconds, 0);
+            }
+        }
+
+        assert!(matches!(
+            result.error,
+            BatteryTimeToEmptyError::Success
+                | BatteryTimeToEmptyError::NoBattery
+                | BatteryTimeToEmptyError::NoTimeToEmpty
+                | BatteryTimeToEmptyError::ManagerError
+        ));
+    }
+}
lib/system/src/lib.rs
@@ -2,8 +2,12 @@ extern crate battery;
 extern crate log;
 extern crate sysinfo;
 
-mod sensors;
 mod battery_cycle_count;
+mod battery_time_to_empty;
+mod sensors;
 
 pub use battery_cycle_count::{battery_cycle_count, BatteryError, BatteryResult};
+pub use battery_time_to_empty::{
+    battery_time_to_empty, BatteryTimeToEmptyError, BatteryTimeToEmptyResult,
+};
 pub use sensors::{sensors, SensorError, SensorResult};
lib/system.h
@@ -13,6 +13,21 @@ struct BatteryResult {
 
 struct BatteryResult battery_cycle_count(void);
 
+// battery time to empty
+enum BatteryTimeToEmptyError {
+    BATTERY_TIME_TO_EMPTY_SUCCESS = 0,
+    BATTERY_TIME_TO_EMPTY_NO_BATTERY = 1,
+    BATTERY_TIME_TO_EMPTY_NO_TIME_TO_EMPTY = 2,
+    BATTERY_TIME_TO_EMPTY_MANAGER_ERROR = 3
+};
+
+struct BatteryTimeToEmptyResult {
+    unsigned long long time_to_empty_seconds;
+    enum BatteryTimeToEmptyError error;
+};
+
+struct BatteryTimeToEmptyResult battery_time_to_empty(void);
+
 
 // sensors
 enum SensorError {