auto-cpufreq 3.1.1

Automatic CPU speed & power optimizer for Linux
Documentation
// src/battery/thinkpad.rs

use anyhow::{Context, Result};
use std::path::{Path, PathBuf};

use super::{get_batteries, BatteryManager};
use crate::config::Config;

const POWER_SUPPLY_DIR: &str = "/sys/class/power_supply/";

pub struct ThinkpadManager;

impl BatteryManager for ThinkpadManager {
    fn setup(&self, config: &Config) -> Result<()> {
        if !config
            .get_bool("battery", "enable_thresholds")
            .unwrap_or(false)
        {
            return Ok(());
        }

        if !Path::new(POWER_SUPPLY_DIR).exists() {
            println!("WARNING {} does NOT exist", POWER_SUPPLY_DIR);
            return Ok(());
        }

        let batteries = get_batteries()?;

        for bat in batteries {
            let start_threshold = get_threshold_value(config, "start");
            let stop_threshold = get_threshold_value(config, "stop");

            set_battery(start_threshold, "start", &bat)?;
            set_battery(stop_threshold, "stop", &bat)?;
        }

        Ok(())
    }

    fn print_thresholds(&self) -> Result<()> {
        let batteries = get_batteries()?;

        println!(
            "\n{}\n",
            "-".repeat(32) + " Battery Info " + &"-".repeat(33)
        );
        println!("battery count = {}", batteries.len());

        for bat in &batteries {
            match read_threshold(bat, "start") {
                Ok(val) => println!("{} start threshold = {}", bat, val),
                Err(e) => println!("ERROR: failed to read battery {} thresholds: {}", bat, e),
            }

            match read_threshold(bat, "stop") {
                Ok(val) => println!("{} stop threshold = {}", bat, val),
                Err(e) => println!("ERROR: failed to read battery {} thresholds: {}", bat, e),
            }
        }

        Ok(())
    }
}

fn get_threshold_value(config: &Config, mode: &str) -> u8 {
    config
        .get_threshold(mode)
        .unwrap_or_else(|_| if mode == "start" { 0 } else { 100 })
}

fn set_battery(value: u8, mode: &str, battery: &str) -> Result<()> {
    let file_path = PathBuf::from(format!(
        "{}{}/charge_{}_threshold",
        POWER_SUPPLY_DIR, battery, mode
    ));

    if !file_path.exists() {
        println!("WARNING: {} does NOT exist", file_path.display());
        return Ok(());
    }

    match std::process::Command::new("sh")
        .arg("-c")
        .arg(format!("echo {} | tee {}", value, file_path.display()))
        .output()
    {
        Ok(output) => {
            if !output.status.success() {
                println!("WARNING: Failed to set {} threshold for {}", mode, battery);
                println!("  stderr: {}", String::from_utf8_lossy(&output.stderr));
            }
        }
        Err(e) => {
            println!("WARNING: Command failed for {} threshold: {}", mode, e);
        }
    }

    Ok(())
}

fn read_threshold(battery: &str, mode: &str) -> Result<String> {
    let file_path = PathBuf::from(format!(
        "{}{}/charge_{}_threshold",
        POWER_SUPPLY_DIR, battery, mode
    ));

    std::process::Command::new("cat")
        .arg(&file_path)
        .output()
        .with_context(|| "Failed to read threshold".to_string())
        .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string())
}