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,
|
||||
|
||||
/// Modify attributes.
|
||||
Set {
|
||||
/// Modify CPU attributes.
|
||||
CpuSet {
|
||||
/// The CPUs to apply the changes to. When unspecified, will be applied to all CPUs.
|
||||
#[arg(short = 'c', long = "for")]
|
||||
for_: Option<Vec<u32>>,
|
||||
|
||||
/// Set the CPU governor.
|
||||
#[arg(long)]
|
||||
#[arg(short = 'g', long)]
|
||||
governor: Option<String>, // TODO: Validate with clap for available governors.
|
||||
|
||||
/// Set CPU Energy Performance Preference (EPP). Short form: --epp.
|
||||
#[arg(long, alias = "epp")]
|
||||
#[arg(short = 'p', long, alias = "epp")]
|
||||
energy_performance_preference: Option<String>,
|
||||
|
||||
/// Set CPU Energy Performance Bias (EPB). Short form: --epb.
|
||||
#[arg(long, alias = "epb")]
|
||||
#[arg(short = 'b', long, alias = "epb")]
|
||||
energy_performance_bias: Option<String>,
|
||||
|
||||
/// Set minimum CPU frequency in MHz. Short form: --freq-min.
|
||||
|
@ -60,20 +60,27 @@ enum Command {
|
|||
frequency_mhz_maximum: Option<u64>,
|
||||
|
||||
/// 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>,
|
||||
},
|
||||
|
||||
/// Set ACPI platform profile. Has to be for all CPUs.
|
||||
#[arg(long, alias = "profile", conflicts_with = "for_")]
|
||||
platform_profile: Option<String>,
|
||||
/// Modify power supply attributes.
|
||||
PowerSet {
|
||||
/// 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.
|
||||
#[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>,
|
||||
|
||||
/// 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>,
|
||||
|
||||
/// 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(())
|
||||
}
|
||||
|
||||
Command::Set {
|
||||
Command::CpuSet {
|
||||
for_,
|
||||
governor,
|
||||
energy_performance_preference,
|
||||
|
@ -104,9 +111,6 @@ fn real_main() -> anyhow::Result<()> {
|
|||
frequency_mhz_minimum,
|
||||
frequency_mhz_maximum,
|
||||
turbo,
|
||||
platform_profile,
|
||||
charge_threshold_start,
|
||||
charge_threshold_end,
|
||||
} => {
|
||||
let cpus = match for_ {
|
||||
Some(cpus) => cpus,
|
||||
|
@ -139,11 +143,33 @@ fn real_main() -> anyhow::Result<()> {
|
|||
cpu::set_turbo(turbo)?;
|
||||
}
|
||||
|
||||
if let Some(platform_profile) = platform_profile.as_ref() {
|
||||
cpu::set_platform_profile(platform_profile)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,38 +2,39 @@ use anyhow::Context;
|
|||
|
||||
use std::{
|
||||
fmt, fs,
|
||||
os::macos::fs::MetadataExt,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// Represents a pattern of path suffixes used to control charge thresholds
|
||||
/// for different device vendors.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct PowerSupplyConfig {
|
||||
pub struct PowerSupplyThresholdConfig {
|
||||
pub manufacturer: &'static str,
|
||||
pub path_start: &'static str,
|
||||
pub path_end: &'static str,
|
||||
}
|
||||
|
||||
/// Charge threshold configs.
|
||||
const POWER_SUPPLY_CONFIGS: &[PowerSupplyConfig] = &[
|
||||
PowerSupplyConfig {
|
||||
/// Power supply threshold configs.
|
||||
const POWER_SUPPLY_THRESHOLD_CONFIGS: &[PowerSupplyThresholdConfig] = &[
|
||||
PowerSupplyThresholdConfig {
|
||||
manufacturer: "Standard",
|
||||
path_start: "charge_control_start_threshold",
|
||||
path_end: "charge_control_end_threshold",
|
||||
},
|
||||
PowerSupplyConfig {
|
||||
PowerSupplyThresholdConfig {
|
||||
manufacturer: "ASUS",
|
||||
path_start: "charge_control_start_percentage",
|
||||
path_end: "charge_control_end_percentage",
|
||||
},
|
||||
// Combine Huawei and ThinkPad since they use identical paths.
|
||||
PowerSupplyConfig {
|
||||
PowerSupplyThresholdConfig {
|
||||
manufacturer: "ThinkPad/Huawei",
|
||||
path_start: "charge_start_threshold",
|
||||
path_end: "charge_stop_threshold",
|
||||
},
|
||||
// Framework laptop support.
|
||||
PowerSupplyConfig {
|
||||
PowerSupplyThresholdConfig {
|
||||
manufacturer: "Framework",
|
||||
path_start: "charge_behaviour_start_threshold",
|
||||
path_end: "charge_behaviour_end_threshold",
|
||||
|
@ -44,27 +45,34 @@ const POWER_SUPPLY_CONFIGS: &[PowerSupplyConfig] = &[
|
|||
pub struct PowerSupply {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
pub config: PowerSupplyConfig,
|
||||
pub threshold_config: Option<PowerSupplyThresholdConfig>,
|
||||
}
|
||||
|
||||
impl fmt::Display for PowerSupply {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"power suppply '{name}' from manufacturer '{manufacturer}'",
|
||||
name = &self.name,
|
||||
manufacturer = &self.config.manufacturer,
|
||||
)
|
||||
write!(f, "power supply '{name}'", name = &self.name)?;
|
||||
|
||||
if let Some(config) = self.threshold_config.as_ref() {
|
||||
write!(
|
||||
f,
|
||||
" from manufacturer '{manufacturer}'",
|
||||
manufacturer = config.manufacturer,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PowerSupply {
|
||||
pub fn charge_threshold_path_start(&self) -> PathBuf {
|
||||
self.path.join(self.config.path_start)
|
||||
pub fn charge_threshold_path_start(&self) -> Option<PathBuf> {
|
||||
self.threshold_config
|
||||
.map(|config| self.path.join(config.path_start))
|
||||
}
|
||||
|
||||
pub fn charge_threshold_path_end(&self) -> PathBuf {
|
||||
self.path.join(self.config.path_end)
|
||||
pub fn charge_threshold_path_end(&self) -> Option<PathBuf> {
|
||||
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_ = fs::read_to_string(&type_path)
|
||||
|
@ -89,13 +97,46 @@ fn is_power_supply(path: &Path) -> anyhow::Result<bool> {
|
|||
Ok(type_ == "Battery")
|
||||
}
|
||||
|
||||
/// Get all batteries in the system that support threshold control.
|
||||
pub fn get_power_supplies() -> anyhow::Result<Vec<PowerSupply>> {
|
||||
const PATH: &str = "/sys/class/power_supply";
|
||||
const POWER_SUPPLY_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();
|
||||
|
||||
'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 {
|
||||
Ok(entry) => entry,
|
||||
|
||||
|
@ -107,38 +148,40 @@ pub fn get_power_supplies() -> anyhow::Result<Vec<PowerSupply>> {
|
|||
|
||||
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!(
|
||||
"failed to determine whether if '{path}' is a power supply",
|
||||
"failed to determine what type of power supply '{path}' is",
|
||||
path = entry_path.display(),
|
||||
)
|
||||
})? {
|
||||
continue;
|
||||
}
|
||||
|
||||
for config in POWER_SUPPLY_CONFIGS {
|
||||
if entry_path.join(config.path_start).exists()
|
||||
&& entry_path.join(config.path_end).exists()
|
||||
{
|
||||
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;
|
||||
for config in POWER_SUPPLY_THRESHOLD_CONFIGS {
|
||||
if entry_path.join(config.path_start).exists()
|
||||
&& entry_path.join(config.path_end).exists()
|
||||
{
|
||||
power_supply_config = Some(*config);
|
||||
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,
|
||||
|
||||
threshold_config: power_supply_config,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(power_supplies)
|
||||
|
@ -149,7 +192,12 @@ pub fn set_charge_threshold_start(
|
|||
charge_threshold_start: u8,
|
||||
) -> anyhow::Result<()> {
|
||||
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(),
|
||||
)
|
||||
.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,
|
||||
) -> anyhow::Result<()> {
|
||||
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(),
|
||||
)
|
||||
.with_context(|| format!("failed to set charge threshold end for {power_supply}"))?;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue