mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
power_supply: don't ignore non-batteries
This commit is contained in:
parent
baef8af981
commit
6377480312
2 changed files with 153 additions and 70 deletions
68
src/main.rs
68
src/main.rs
|
@ -33,22 +33,22 @@ enum Command {
|
||||||
/// Start the daemon.
|
/// Start the daemon.
|
||||||
Start,
|
Start,
|
||||||
|
|
||||||
/// Modify attributes.
|
/// Modify CPU attributes.
|
||||||
Set {
|
CpuSet {
|
||||||
/// The CPUs to apply the changes to. When unspecified, will be applied to all CPUs.
|
/// The CPUs to apply the changes to. When unspecified, will be applied to all CPUs.
|
||||||
#[arg(short = 'c', long = "for")]
|
#[arg(short = 'c', long = "for")]
|
||||||
for_: Option<Vec<u32>>,
|
for_: Option<Vec<u32>>,
|
||||||
|
|
||||||
/// Set the CPU governor.
|
/// Set the CPU governor.
|
||||||
#[arg(long)]
|
#[arg(short = 'g', long)]
|
||||||
governor: Option<String>, // TODO: Validate with clap for available governors.
|
governor: Option<String>, // TODO: Validate with clap for available governors.
|
||||||
|
|
||||||
/// Set CPU Energy Performance Preference (EPP). Short form: --epp.
|
/// Set CPU Energy Performance Preference (EPP). Short form: --epp.
|
||||||
#[arg(long, alias = "epp")]
|
#[arg(short = 'p', long, alias = "epp")]
|
||||||
energy_performance_preference: Option<String>,
|
energy_performance_preference: Option<String>,
|
||||||
|
|
||||||
/// Set CPU Energy Performance Bias (EPB). Short form: --epb.
|
/// Set CPU Energy Performance Bias (EPB). Short form: --epb.
|
||||||
#[arg(long, alias = "epb")]
|
#[arg(short = 'b', long, alias = "epb")]
|
||||||
energy_performance_bias: Option<String>,
|
energy_performance_bias: Option<String>,
|
||||||
|
|
||||||
/// Set minimum CPU frequency in MHz. Short form: --freq-min.
|
/// Set minimum CPU frequency in MHz. Short form: --freq-min.
|
||||||
|
@ -60,20 +60,27 @@ enum Command {
|
||||||
frequency_mhz_maximum: Option<u64>,
|
frequency_mhz_maximum: Option<u64>,
|
||||||
|
|
||||||
/// Set turbo boost behaviour. Has to be for all CPUs.
|
/// Set turbo boost behaviour. Has to be for all CPUs.
|
||||||
#[arg(long, conflicts_with = "for_")]
|
#[arg(short = 't', long, conflicts_with = "for_")]
|
||||||
turbo: Option<cpu::Turbo>,
|
turbo: Option<cpu::Turbo>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Set ACPI platform profile. Has to be for all CPUs.
|
/// Modify power supply attributes.
|
||||||
#[arg(long, alias = "profile", conflicts_with = "for_")]
|
PowerSet {
|
||||||
platform_profile: Option<String>,
|
/// The power supplies to apply the changes to. When unspecified, will be applied to all power supplies.
|
||||||
|
#[arg(short = 'p', long = "for")]
|
||||||
|
for_: Option<Vec<String>>,
|
||||||
|
|
||||||
/// Set the percentage that the power supply has to drop under for charging to start. Short form: --charge-start.
|
/// Set the percentage that the power supply has to drop under for charging to start. Short form: --charge-start.
|
||||||
#[arg(short = 'p', long, alias = "charge-start", value_parser = clap::value_parser!(u8).range(0..=100), conflicts_with = "for_")]
|
#[arg(short = 'c', long, alias = "charge-start", value_parser = clap::value_parser!(u8).range(0..=100))]
|
||||||
charge_threshold_start: Option<u8>,
|
charge_threshold_start: Option<u8>,
|
||||||
|
|
||||||
/// Set the percentage where charging will stop. Short form: --charge-end.
|
/// Set the percentage where charging will stop. Short form: --charge-end.
|
||||||
#[arg(short = 'P', long, alias = "charge-end", value_parser = clap::value_parser!(u8).range(0..=100), conflicts_with = "for_")]
|
#[arg(short = 'C', long, alias = "charge-end", value_parser = clap::value_parser!(u8).range(0..=100))]
|
||||||
charge_threshold_end: Option<u8>,
|
charge_threshold_end: Option<u8>,
|
||||||
|
|
||||||
|
/// Set ACPI platform profile. Has to be for all power supplies.
|
||||||
|
#[arg(short = 'f', long, alias = "profile", conflicts_with = "for_")]
|
||||||
|
platform_profile: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +103,7 @@ fn real_main() -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::Set {
|
Command::CpuSet {
|
||||||
for_,
|
for_,
|
||||||
governor,
|
governor,
|
||||||
energy_performance_preference,
|
energy_performance_preference,
|
||||||
|
@ -104,9 +111,6 @@ fn real_main() -> anyhow::Result<()> {
|
||||||
frequency_mhz_minimum,
|
frequency_mhz_minimum,
|
||||||
frequency_mhz_maximum,
|
frequency_mhz_maximum,
|
||||||
turbo,
|
turbo,
|
||||||
platform_profile,
|
|
||||||
charge_threshold_start,
|
|
||||||
charge_threshold_end,
|
|
||||||
} => {
|
} => {
|
||||||
let cpus = match for_ {
|
let cpus = match for_ {
|
||||||
Some(cpus) => cpus,
|
Some(cpus) => cpus,
|
||||||
|
@ -139,11 +143,33 @@ fn real_main() -> anyhow::Result<()> {
|
||||||
cpu::set_turbo(turbo)?;
|
cpu::set_turbo(turbo)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(platform_profile) = platform_profile.as_ref() {
|
Ok(())
|
||||||
cpu::set_platform_profile(platform_profile)?;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for power_supply in power_supply::get_power_supplies()? {
|
Command::PowerSet {
|
||||||
|
for_,
|
||||||
|
charge_threshold_start,
|
||||||
|
charge_threshold_end,
|
||||||
|
platform_profile,
|
||||||
|
} => {
|
||||||
|
let power_supplies = match for_ {
|
||||||
|
Some(names) => {
|
||||||
|
let power_supplies = Vec::with_capacity(names.len());
|
||||||
|
|
||||||
|
for name in names {
|
||||||
|
power_supplies.push(power_supply::get_power_supply(&name)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
power_supplies
|
||||||
|
}
|
||||||
|
|
||||||
|
None => power_supply::get_power_supplies()?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|power_supply| power_supply.threshold_config.is_some())
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for power_supply in power_supplies {
|
||||||
if let Some(threshold_start) = charge_threshold_start {
|
if let Some(threshold_start) = charge_threshold_start {
|
||||||
power_supply::set_charge_threshold_start(&power_supply, threshold_start)?;
|
power_supply::set_charge_threshold_start(&power_supply, threshold_start)?;
|
||||||
}
|
}
|
||||||
|
@ -153,6 +179,10 @@ fn real_main() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(platform_profile) = platform_profile.as_ref() {
|
||||||
|
cpu::set_platform_profile(platform_profile)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,38 +2,39 @@ use anyhow::Context;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fmt, fs,
|
fmt, fs,
|
||||||
|
os::macos::fs::MetadataExt,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents a pattern of path suffixes used to control charge thresholds
|
/// Represents a pattern of path suffixes used to control charge thresholds
|
||||||
/// for different device vendors.
|
/// for different device vendors.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct PowerSupplyConfig {
|
pub struct PowerSupplyThresholdConfig {
|
||||||
pub manufacturer: &'static str,
|
pub manufacturer: &'static str,
|
||||||
pub path_start: &'static str,
|
pub path_start: &'static str,
|
||||||
pub path_end: &'static str,
|
pub path_end: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Charge threshold configs.
|
/// Power supply threshold configs.
|
||||||
const POWER_SUPPLY_CONFIGS: &[PowerSupplyConfig] = &[
|
const POWER_SUPPLY_THRESHOLD_CONFIGS: &[PowerSupplyThresholdConfig] = &[
|
||||||
PowerSupplyConfig {
|
PowerSupplyThresholdConfig {
|
||||||
manufacturer: "Standard",
|
manufacturer: "Standard",
|
||||||
path_start: "charge_control_start_threshold",
|
path_start: "charge_control_start_threshold",
|
||||||
path_end: "charge_control_end_threshold",
|
path_end: "charge_control_end_threshold",
|
||||||
},
|
},
|
||||||
PowerSupplyConfig {
|
PowerSupplyThresholdConfig {
|
||||||
manufacturer: "ASUS",
|
manufacturer: "ASUS",
|
||||||
path_start: "charge_control_start_percentage",
|
path_start: "charge_control_start_percentage",
|
||||||
path_end: "charge_control_end_percentage",
|
path_end: "charge_control_end_percentage",
|
||||||
},
|
},
|
||||||
// Combine Huawei and ThinkPad since they use identical paths.
|
// Combine Huawei and ThinkPad since they use identical paths.
|
||||||
PowerSupplyConfig {
|
PowerSupplyThresholdConfig {
|
||||||
manufacturer: "ThinkPad/Huawei",
|
manufacturer: "ThinkPad/Huawei",
|
||||||
path_start: "charge_start_threshold",
|
path_start: "charge_start_threshold",
|
||||||
path_end: "charge_stop_threshold",
|
path_end: "charge_stop_threshold",
|
||||||
},
|
},
|
||||||
// Framework laptop support.
|
// Framework laptop support.
|
||||||
PowerSupplyConfig {
|
PowerSupplyThresholdConfig {
|
||||||
manufacturer: "Framework",
|
manufacturer: "Framework",
|
||||||
path_start: "charge_behaviour_start_threshold",
|
path_start: "charge_behaviour_start_threshold",
|
||||||
path_end: "charge_behaviour_end_threshold",
|
path_end: "charge_behaviour_end_threshold",
|
||||||
|
@ -44,27 +45,34 @@ const POWER_SUPPLY_CONFIGS: &[PowerSupplyConfig] = &[
|
||||||
pub struct PowerSupply {
|
pub struct PowerSupply {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub config: PowerSupplyConfig,
|
pub threshold_config: Option<PowerSupplyThresholdConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for PowerSupply {
|
impl fmt::Display for PowerSupply {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
write!(f, "power supply '{name}'", name = &self.name)?;
|
||||||
f,
|
|
||||||
"power suppply '{name}' from manufacturer '{manufacturer}'",
|
if let Some(config) = self.threshold_config.as_ref() {
|
||||||
name = &self.name,
|
write!(
|
||||||
manufacturer = &self.config.manufacturer,
|
f,
|
||||||
)
|
" from manufacturer '{manufacturer}'",
|
||||||
|
manufacturer = config.manufacturer,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PowerSupply {
|
impl PowerSupply {
|
||||||
pub fn charge_threshold_path_start(&self) -> PathBuf {
|
pub fn charge_threshold_path_start(&self) -> Option<PathBuf> {
|
||||||
self.path.join(self.config.path_start)
|
self.threshold_config
|
||||||
|
.map(|config| self.path.join(config.path_start))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn charge_threshold_path_end(&self) -> PathBuf {
|
pub fn charge_threshold_path_end(&self) -> Option<PathBuf> {
|
||||||
self.path.join(self.config.path_end)
|
self.threshold_config
|
||||||
|
.map(|config| self.path.join(config.path_end))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +88,7 @@ fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_power_supply(path: &Path) -> anyhow::Result<bool> {
|
fn is_battery(path: &Path) -> anyhow::Result<bool> {
|
||||||
let type_path = path.join("type");
|
let type_path = path.join("type");
|
||||||
|
|
||||||
let type_ = fs::read_to_string(&type_path)
|
let type_ = fs::read_to_string(&type_path)
|
||||||
|
@ -89,13 +97,46 @@ fn is_power_supply(path: &Path) -> anyhow::Result<bool> {
|
||||||
Ok(type_ == "Battery")
|
Ok(type_ == "Battery")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all batteries in the system that support threshold control.
|
const POWER_SUPPLY_PATH: &str = "/sys/class/power_supply";
|
||||||
pub fn get_power_supplies() -> anyhow::Result<Vec<PowerSupply>> {
|
|
||||||
const PATH: &str = "/sys/class/power_supply";
|
|
||||||
|
|
||||||
|
/// Get power supply.
|
||||||
|
pub fn get_power_supply(name: &str) -> anyhow::Result<PowerSupply> {
|
||||||
|
let entry_path = Path::new(POWER_SUPPLY_PATH).join(name);
|
||||||
|
|
||||||
|
let threshold_config = is_battery(&entry_path)
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to determine what type of power supply '{path}' is",
|
||||||
|
path = entry_path.display(),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.then(|| {
|
||||||
|
for config in POWER_SUPPLY_THRESHOLD_CONFIGS {
|
||||||
|
if entry_path.join(config.path_start).exists()
|
||||||
|
&& entry_path.join(config.path_end).exists()
|
||||||
|
{
|
||||||
|
return Some(*config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
Ok(PowerSupply {
|
||||||
|
name: name.to_owned(),
|
||||||
|
path: entry_path,
|
||||||
|
threshold_config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all power supplies.
|
||||||
|
pub fn get_power_supplies() -> anyhow::Result<Vec<PowerSupply>> {
|
||||||
let mut power_supplies = Vec::new();
|
let mut power_supplies = Vec::new();
|
||||||
|
|
||||||
'entries: for entry in fs::read_dir(PATH).with_context(|| format!("failed to read '{PATH}'"))? {
|
for entry in fs::read_dir(POWER_SUPPLY_PATH)
|
||||||
|
.with_context(|| format!("failed to read '{POWER_SUPPLY_PATH}'"))?
|
||||||
|
{
|
||||||
let entry = match entry {
|
let entry = match entry {
|
||||||
Ok(entry) => entry,
|
Ok(entry) => entry,
|
||||||
|
|
||||||
|
@ -107,38 +148,40 @@ pub fn get_power_supplies() -> anyhow::Result<Vec<PowerSupply>> {
|
||||||
|
|
||||||
let entry_path = entry.path();
|
let entry_path = entry.path();
|
||||||
|
|
||||||
if !is_power_supply(&entry_path).with_context(|| {
|
let mut power_supply_config = None;
|
||||||
|
|
||||||
|
if is_battery(&entry_path).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"failed to determine whether if '{path}' is a power supply",
|
"failed to determine what type of power supply '{path}' is",
|
||||||
path = entry_path.display(),
|
path = entry_path.display(),
|
||||||
)
|
)
|
||||||
})? {
|
})? {
|
||||||
continue;
|
for config in POWER_SUPPLY_THRESHOLD_CONFIGS {
|
||||||
}
|
if entry_path.join(config.path_start).exists()
|
||||||
|
&& entry_path.join(config.path_end).exists()
|
||||||
for config in POWER_SUPPLY_CONFIGS {
|
{
|
||||||
if entry_path.join(config.path_start).exists()
|
power_supply_config = Some(*config);
|
||||||
&& entry_path.join(config.path_end).exists()
|
break;
|
||||||
{
|
}
|
||||||
power_supplies.push(PowerSupply {
|
|
||||||
name: entry_path
|
|
||||||
.file_name()
|
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to get file name of '{path}'",
|
|
||||||
path = entry_path.display(),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
|
|
||||||
path: entry_path,
|
|
||||||
|
|
||||||
config: *config,
|
|
||||||
});
|
|
||||||
continue 'entries;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
power_supplies.push(PowerSupply {
|
||||||
|
name: entry_path
|
||||||
|
.file_name()
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to get file name of '{path}'",
|
||||||
|
path = entry_path.display(),
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string(),
|
||||||
|
|
||||||
|
path: entry_path,
|
||||||
|
|
||||||
|
threshold_config: power_supply_config,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(power_supplies)
|
Ok(power_supplies)
|
||||||
|
@ -149,7 +192,12 @@ pub fn set_charge_threshold_start(
|
||||||
charge_threshold_start: u8,
|
charge_threshold_start: u8,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
write(
|
write(
|
||||||
&power_supply.charge_threshold_path_start(),
|
&power_supply.charge_threshold_path_start().ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"power supply '{name}' does not support changing charge threshold levels",
|
||||||
|
name = power_supply.name,
|
||||||
|
)
|
||||||
|
})?,
|
||||||
&charge_threshold_start.to_string(),
|
&charge_threshold_start.to_string(),
|
||||||
)
|
)
|
||||||
.with_context(|| format!("failed to set charge threshold start for {power_supply}"))?;
|
.with_context(|| format!("failed to set charge threshold start for {power_supply}"))?;
|
||||||
|
@ -164,7 +212,12 @@ pub fn set_charge_threshold_end(
|
||||||
charge_threshold_end: u8,
|
charge_threshold_end: u8,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
write(
|
write(
|
||||||
&power_supply.charge_threshold_path_end(),
|
&power_supply.charge_threshold_path_end().ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"power supply '{name}' does not support changing charge threshold levels",
|
||||||
|
name = power_supply.name,
|
||||||
|
)
|
||||||
|
})?,
|
||||||
&charge_threshold_end.to_string(),
|
&charge_threshold_end.to_string(),
|
||||||
)
|
)
|
||||||
.with_context(|| format!("failed to set charge threshold end for {power_supply}"))?;
|
.with_context(|| format!("failed to set charge threshold end for {power_supply}"))?;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue