1
Fork 0
mirror of https://github.com/RGBCube/superfreq synced 2025-07-27 17:07:44 +00:00

power_supply&cpu: use objects

This commit is contained in:
RGBCube 2025-05-19 16:17:54 +03:00
parent 6377480312
commit 6ef4da9113
Signed by: RGBCube
SSH key fingerprint: SHA256:CzqbPcfwt+GxFYNnFVCqoN5Itn4YFrshg1TrnACpA5M
3 changed files with 473 additions and 525 deletions

View file

@ -1,19 +1,6 @@
use anyhow::{Context, bail};
use derive_more::Display;
use serde::{Deserialize, Serialize};
use std::{fs, io, path::Path, string::ToString};
// // Valid EPP (Energy Performance Preference) string values.
// const EPP_FALLBACK_VALUES: &[&str] = &[
// "default",
// "performance",
// "balance-performance",
// "balance_performance", // Alternative form with underscore.
// "balance-power",
// "balance_power", // Alternative form with underscore.
// "power",
// ];
use std::{fs, path::Path, string::ToString};
fn exists(path: impl AsRef<Path>) -> bool {
let path = path.as_ref();
@ -41,8 +28,24 @@ fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
})
}
/// Get real, tunable CPUs.
pub fn get_real_cpus() -> anyhow::Result<Vec<u32>> {
pub struct Cpu {
pub number: u32,
pub has_cpufreq: bool,
}
impl Cpu {
pub fn new(number: u32) -> anyhow::Result<Self> {
let mut cpu = Self {
number,
has_cpufreq: false,
};
cpu.rescan()?;
Ok(cpu)
}
/// Get all CPUs.
pub fn all() -> anyhow::Result<Vec<Cpu>> {
const PATH: &str = "/sys/devices/system/cpu";
let mut cpus = vec![];
@ -62,55 +65,40 @@ pub fn get_real_cpus() -> anyhow::Result<Vec<u32>> {
};
// Has to match "cpu{N}".
let Ok(cpu) = cpu_prefix_removed.parse::<u32>() else {
let Ok(number) = cpu_prefix_removed.parse::<u32>() else {
continue;
};
// Has to match "cpu{N}/cpufreq".
if !entry.path().join("cpufreq").exists() {
continue;
}
cpus.push(cpu);
cpus.push(Self::new(number)?);
}
// Fall back if sysfs iteration above fails to find any cpufreq CPUs.
if cpus.is_empty() {
cpus = (0..num_cpus::get() as u32).collect();
for number in 0..num_cpus::get() as u32 {
cpus.push(Self::new(number)?);
}
}
Ok(cpus)
}
/// Set the governor for a CPU.
pub fn set_governor(governor: &str, cpu: u32) -> anyhow::Result<()> {
let governors = get_available_governors_for(cpu);
/// Rescan CPU, tuning local copy of settings.
pub fn rescan(&mut self) -> anyhow::Result<()> {
let has_cpufreq = exists(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq",
number = self.number,
));
if !governors
.iter()
.any(|avail_governor| avail_governor == governor)
{
bail!(
"governor '{governor}' is not available for CPU {cpu}. valid governors: {governors}",
governors = governors.join(", "),
);
self.has_cpufreq = has_cpufreq;
Ok(())
}
write(
format!("/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_governor"),
governor,
)
.with_context(|| {
format!(
"this probably means that CPU {cpu} doesn't exist or doesn't support changing governors"
)
})
}
pub fn get_available_governors(&self) -> Vec<String> {
let Self { number, .. } = self;
/// Get available CPU governors for a CPU.
fn get_available_governors_for(cpu: u32) -> Vec<String> {
let Ok(content) = fs::read_to_string(format!(
"/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_available_governors"
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_available_governors"
)) else {
return Vec::new();
};
@ -121,21 +109,208 @@ fn get_available_governors_for(cpu: u32) -> Vec<String> {
.collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, clap::ValueEnum)]
pub enum Turbo {
Always,
Never,
pub fn set_governor(&self, governor: &str) -> anyhow::Result<()> {
let Self { number, .. } = self;
let governors = self.get_available_governors();
if !governors
.iter()
.any(|avail_governor| avail_governor == governor)
{
bail!(
"governor '{governor}' is not available for CPU {number}. available governors: {governors}",
governors = governors.join(", "),
);
}
pub fn set_turbo(setting: Turbo) -> anyhow::Result<()> {
let value_boost = match setting {
Turbo::Always => "1", // boost = 1 means turbo is enabled.
Turbo::Never => "0", // boost = 0 means turbo is disabled.
write(
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_governor"),
governor,
)
.with_context(|| {
format!(
"this probably means that CPU {number} doesn't exist or doesn't support changing governors"
)
})
}
pub fn get_available_epps(&self) -> Vec<String> {
let Self { number, .. } = self;
let Ok(content) = fs::read_to_string(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_available_preferences"
)) else {
return Vec::new();
};
let value_boost_negated = match setting {
Turbo::Always => "0", // no_turbo = 0 means turbo is enabled.
Turbo::Never => "1", // no_turbo = 1 means turbo is disabled.
content
.split_whitespace()
.map(ToString::to_string)
.collect()
}
pub fn set_epp(&self, epp: &str) -> anyhow::Result<()> {
let Self { number, .. } = self;
let epps = self.get_available_epps();
if !epps.iter().any(|avail_epp| avail_epp == epp) {
bail!(
"EPP value '{epp}' is not availabile for CPU {number}. available EPP values: {epps}",
epps = epps.join(", "),
);
}
write(
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_preference"),
epp,
)
.with_context(|| {
format!(
"this probably means that CPU {number} doesn't exist or doesn't support changing EPP"
)
})
}
pub fn get_available_epbs(&self) -> &'static [&'static str] {
if !self.has_cpufreq {
return &[];
}
&[
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"performance",
"balance-performance",
"balance_performance", // Alternative form with underscore.
"balance-power",
"balance_power", // Alternative form with underscore.
"power",
]
}
pub fn set_epb(&self, epb: &str) -> anyhow::Result<()> {
let Self { number, .. } = self;
let epbs = self.get_available_epbs();
if !epbs.contains(&epb) {
bail!(
"EPB value '{epb}' is not available for CPU {number}. available EPB values: {valid}",
valid = epbs.join(", "),
);
}
write(
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_bias"),
epb,
)
.with_context(|| {
format!(
"this probably means that CPU {number} doesn't exist or doesn't support changing EPB"
)
})
}
pub fn set_frequency_minimum(&self, frequency_mhz: u64) -> anyhow::Result<()> {
let Self { number, .. } = self;
self.validate_frequency_minimum(frequency_mhz)?;
// We use u64 for the intermediate calculation to prevent overflow
let frequency_khz = u64::from(frequency_mhz) * 1000;
let frequency_khz = frequency_khz.to_string();
write(
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq"),
&frequency_khz,
)
.with_context(|| {
format!("this probably means that CPU {number} doesn't exist or doesn't support changing minimum frequency")
})
}
fn validate_frequency_minimum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> {
let Self { number, .. } = self;
let Ok(minimum_frequency_khz) = read_u64(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq"
)) else {
// Just let it pass if we can't find anything.
return Ok(());
};
if new_frequency_mhz as u64 * 1000 < minimum_frequency_khz {
bail!(
"new minimum frequency ({new_frequency_mhz} MHz) cannot be lower than the minimum frequency ({} MHz) for CPU {number}",
minimum_frequency_khz / 1000,
);
}
Ok(())
}
pub fn set_frequency_maximum(&self, frequency_mhz: u64) -> anyhow::Result<()> {
let Self { number, .. } = self;
self.validate_frequency_maximum(frequency_mhz)?;
// We use u64 for the intermediate calculation to prevent overflow
let frequency_khz = u64::from(frequency_mhz) * 1000;
let frequency_khz = frequency_khz.to_string();
write(
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_max_freq"),
&frequency_khz,
)
.with_context(|| {
format!("this probably means that CPU {number} doesn't exist or doesn't support changing maximum frequency")
})
}
fn validate_frequency_maximum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> {
let Self { number, .. } = self;
let Ok(maximum_frequency_khz) = read_u64(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq"
)) else {
// Just let it pass if we can't find anything.
return Ok(());
};
if new_frequency_mhz * 1000 > maximum_frequency_khz {
bail!(
"new maximum frequency ({new_frequency_mhz} MHz) cannot be higher than the maximum frequency ({} MHz) for CPU {number}",
maximum_frequency_khz / 1000,
);
}
Ok(())
}
pub fn set_turbo(on: bool) -> anyhow::Result<()> {
let value_boost = match on {
true => "1", // boost = 1 means turbo is enabled.
false => "0", // boost = 0 means turbo is disabled.
};
let value_boost_negated = match on {
true => "0", // no_turbo = 0 means turbo is enabled.
false => "1", // no_turbo = 1 means turbo is disabled.
};
// AMD specific paths
@ -161,9 +336,11 @@ pub fn set_turbo(setting: Turbo) -> anyhow::Result<()> {
}
// Also try per-core cpufreq boost for some AMD systems.
if get_real_cpus()?.iter().any(|cpu| {
if Self::all()?.iter().any(|cpu| {
let Cpu { number, .. } = cpu;
write(
&format!("/sys/devices/system/cpu/cpu{cpu}/cpufreq/boost"),
&format!("/sys/devices/system/cpu/cpu{number}/cpufreq/boost"),
value_boost,
)
.is_ok()
@ -173,262 +350,4 @@ pub fn set_turbo(setting: Turbo) -> anyhow::Result<()> {
bail!("no supported CPU boost control mechanism found");
}
pub fn set_epp(epp: &str, cpu: u32) -> anyhow::Result<()> {
// Validate the EPP value against available options
let epps = get_available_epps(cpu);
if !epps.iter().any(|avail_epp| avail_epp == epp) {
bail!(
"epp value '{epp}' is not availabile for CPU {cpu}. valid epp values: {epps}",
epps = epps.join(", "),
);
}
write(
format!("/sys/devices/system/cpu/cpu{cpu}/cpufreq/energy_performance_preference"),
epp,
)
.with_context(|| {
format!("this probably means that CPU {cpu} doesn't exist or doesn't support changing EPP")
})
}
/// Get available EPP values for a CPU.
fn get_available_epps(cpu: u32) -> Vec<String> {
let Ok(content) = fs::read_to_string(format!(
"/sys/devices/system/cpu/cpu{cpu}/cpufreq/energy_performance_available_preferences"
)) else {
return Vec::new();
};
content
.split_whitespace()
.map(ToString::to_string)
.collect()
}
pub fn set_epb(epb: &str, cpu: u32) -> anyhow::Result<()> {
// Validate EPB value - should be a number 0-15 or a recognized string value.
validate_epb_value(epb)?;
write(
format!("/sys/devices/system/cpu/cpu{cpu}/cpufreq/energy_performance_bias"),
epb,
)
.with_context(|| {
format!("this probably means that CPU {cpu} doesn't exist or doesn't support changing EPB")
})
}
fn validate_epb_value(epb: &str) -> anyhow::Result<()> {
// EPB can be a number from 0-15 or a recognized string.
const VALID_EPB_STRINGS: &[&str] = &[
"performance",
"balance-performance",
"balance_performance", // Alternative form with underscore.
"balance-power",
"balance_power", // Alternative form with underscore.
"power",
];
// Try parsing as a number first.
if let Ok(value) = epb.parse::<u8>() {
if value <= 15 {
return Ok(());
}
bail!("EPB numeric value must be between 0 and 15, got {value}");
}
// If not a number, check if it's a recognized string value.
if VALID_EPB_STRINGS.contains(&epb) {
return Ok(());
}
bail!(
"invalid EPB value: '{epb}'. must be a number between 0-15 inclusive or one of: {valid}",
valid = VALID_EPB_STRINGS.join(", "),
);
}
pub fn set_frequency_minimum(frequency_mhz: u64, cpu: u32) -> anyhow::Result<()> {
validate_frequency_minimum(frequency_mhz, cpu)?;
// We use u64 for the intermediate calculation to prevent overflow
let frequency_khz = u64::from(frequency_mhz) * 1000;
let frequency_khz = frequency_khz.to_string();
write(
format!("/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_min_freq"),
&frequency_khz,
)
.with_context(|| {
format!("this probably means that CPU {cpu} doesn't exist or doesn't support changing minimum frequency")
})
}
pub fn set_frequency_maximum(frequency_mhz: u64, cpu: u32) -> anyhow::Result<()> {
validate_max_frequency(frequency_mhz, cpu)?;
// We use u64 for the intermediate calculation to prevent overflow
let frequency_khz = u64::from(frequency_mhz) * 1000;
let frequency_khz = frequency_khz.to_string();
write(
format!("/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_max_freq"),
&frequency_khz,
)
.with_context(|| {
format!("this probably means that CPU {cpu} doesn't exist or doesn't support changing maximum frequency")
})
}
fn validate_frequency_minimum(new_frequency_mhz: u64, cpu: u32) -> anyhow::Result<()> {
let Ok(minimum_frequency_khz) = read_u64(format!(
"/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_min_freq"
)) else {
// Just let it pass if we can't find anything.
return Ok(());
};
if new_frequency_mhz as u64 * 1000 < minimum_frequency_khz {
bail!(
"new minimum frequency ({new_frequency_mhz} MHz) cannot be lower than the minimum frequency ({} MHz) for CPU {cpu}",
minimum_frequency_khz / 1000,
);
}
Ok(())
}
fn validate_max_frequency(new_frequency_mhz: u64, cpu: u32) -> anyhow::Result<()> {
let Ok(maximum_frequency_khz) = read_u64(format!(
"/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_min_freq"
)) else {
// Just let it pass if we can't find anything.
return Ok(());
};
if new_frequency_mhz * 1000 > maximum_frequency_khz {
bail!(
"new maximum frequency ({new_frequency_mhz} MHz) cannot be higher than the maximum frequency ({} MHz) for CPU {cpu}",
maximum_frequency_khz / 1000,
);
}
Ok(())
}
/// Sets the platform profile.
/// This changes the system performance, temperature, fan, and other hardware replated characteristics.
///
/// Also see [`The Kernel docs`] for this.
///
/// [`The Kernel docs`]: <https://docs.kernel.org/userspace-api/sysfs-platform_profile.html>
pub fn set_platform_profile(profile: &str) -> anyhow::Result<()> {
let profiles = get_platform_profiles();
if !profiles
.iter()
.any(|avail_profile| avail_profile == profile)
{
bail!(
"profile '{profile}' is not available for system. valid profiles: {profiles}",
profiles = profiles.join(", "),
);
}
write("/sys/firmware/acpi/platform_profile", profile)
.context("this probably means that your system does not support changing ACPI profiles")
}
/// Get the list of available platform profiles.
pub fn get_platform_profiles() -> Vec<String> {
let path = "/sys/firmware/acpi/platform_profile_choices";
let Ok(content) = fs::read_to_string(path) else {
return Vec::new();
};
content
.split_whitespace()
.map(ToString::to_string)
.collect()
}
/// Path for storing the governor override state.
const GOVERNOR_OVERRIDE_PATH: &str = "/etc/xdg/superfreq/governor_override";
#[derive(Display, Debug, Clone, Copy, clap::ValueEnum)]
pub enum GovernorOverride {
#[display("performance")]
Performance,
#[display("powersave")]
Powersave,
#[display("reset")]
Reset,
}
pub fn set_governor_override(mode: GovernorOverride) -> anyhow::Result<()> {
let parent = Path::new(GOVERNOR_OVERRIDE_PATH).parent().unwrap();
if !parent.exists() {
fs::create_dir_all(parent).with_context(|| {
format!(
"failed to create directory '{path}'",
path = parent.display(),
)
})?;
}
match mode {
GovernorOverride::Reset => {
// Remove the override file if it exists
let result = fs::remove_file(GOVERNOR_OVERRIDE_PATH);
if let Err(error) = result {
if error.kind() != io::ErrorKind::NotFound {
return Err(error).with_context(|| {
format!(
"failed to delete governor override file '{GOVERNOR_OVERRIDE_PATH}'"
)
});
}
}
log::info!(
"governor override has been deleted. normal profile-based settings will be used"
);
}
GovernorOverride::Performance | GovernorOverride::Powersave => {
let governor = mode.to_string();
write(GOVERNOR_OVERRIDE_PATH, &governor)
.context("failed to write governor override")?;
// TODO: Apply the setting too.
log::info!(
"governor override set to '{governor}'. this setting will persist across reboots"
);
log::info!("to reset, run: superfreq set --governor-persist reset");
}
}
Ok(())
}
/// Get the current governor override if set.
pub fn get_governor_override() -> anyhow::Result<Option<String>> {
match fs::read_to_string(GOVERNOR_OVERRIDE_PATH) {
Ok(governor_override) => Ok(Some(governor_override)),
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(error).with_context(|| {
format!("failed to read governor override at '{GOVERNOR_OVERRIDE_PATH}'")
}),
}
}

View file

@ -61,7 +61,7 @@ enum Command {
/// Set turbo boost behaviour. Has to be for all CPUs.
#[arg(short = 't', long, conflicts_with = "for_")]
turbo: Option<cpu::Turbo>,
turbo: Option<bool>,
},
/// Modify power supply attributes.
@ -113,34 +113,42 @@ fn real_main() -> anyhow::Result<()> {
turbo,
} => {
let cpus = match for_ {
Some(cpus) => cpus,
None => cpu::get_real_cpus()?,
Some(numbers) => {
let mut cpus = Vec::with_capacity(numbers.len());
for number in numbers {
cpus.push(cpu::Cpu::new(number)?);
}
cpus
}
None => cpu::Cpu::all()?,
};
for cpu in cpus {
if let Some(governor) = governor.as_ref() {
cpu::set_governor(governor, cpu)?;
cpu.set_governor(governor)?;
}
if let Some(epp) = energy_performance_preference.as_ref() {
cpu::set_epp(epp, cpu)?;
cpu.set_epp(epp)?;
}
if let Some(epb) = energy_performance_bias.as_ref() {
cpu::set_epb(epb, cpu)?;
cpu.set_epb(epb)?;
}
if let Some(mhz_minimum) = frequency_mhz_minimum {
cpu::set_frequency_minimum(mhz_minimum, cpu)?;
cpu.set_frequency_minimum(mhz_minimum)?;
}
if let Some(mhz_maximum) = frequency_mhz_maximum {
cpu::set_frequency_maximum(mhz_maximum, cpu)?;
cpu.set_frequency_maximum(mhz_maximum)?;
}
}
if let Some(turbo) = turbo {
cpu::set_turbo(turbo)?;
cpu::Cpu::set_turbo(turbo)?;
}
Ok(())
@ -157,13 +165,13 @@ fn real_main() -> anyhow::Result<()> {
let power_supplies = Vec::with_capacity(names.len());
for name in names {
power_supplies.push(power_supply::get_power_supply(&name)?);
power_supplies.push(power_supply::PowerSupply::from_name(name)?);
}
power_supplies
}
None => power_supply::get_power_supplies()?
None => power_supply::PowerSupply::all()?
.into_iter()
.filter(|power_supply| power_supply.threshold_config.is_some())
.collect(),
@ -171,16 +179,16 @@ fn real_main() -> anyhow::Result<()> {
for power_supply in power_supplies {
if let Some(threshold_start) = charge_threshold_start {
power_supply::set_charge_threshold_start(&power_supply, threshold_start)?;
power_supply.set_charge_threshold_start(threshold_start)?;
}
if let Some(threshold_end) = charge_threshold_end {
power_supply::set_charge_threshold_end(&power_supply, threshold_end)?;
power_supply.set_charge_threshold_end(threshold_end)?;
}
}
if let Some(platform_profile) = platform_profile.as_ref() {
cpu::set_platform_profile(platform_profile)?;
power_supply::PowerSupply::set_platform_profile(platform_profile);
}
Ok(())

View file

@ -1,11 +1,22 @@
use anyhow::Context;
use anyhow::{Context, bail};
use std::{
fmt, fs,
os::macos::fs::MetadataExt,
path::{Path, PathBuf},
};
// TODO: Migrate to central utils file. Same exists in cpu.rs.
fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
let path = path.as_ref();
fs::write(path, value).with_context(|| {
format!(
"failed to write '{value}' to '{path}'",
path = path.display(),
)
})
}
/// Represents a pattern of path suffixes used to control charge thresholds
/// for different device vendors.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -50,7 +61,12 @@ pub struct PowerSupply {
impl fmt::Display for PowerSupply {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "power supply '{name}'", name = &self.name)?;
write!(
f,
"power supply '{name}' at '{path}'",
name = &self.name,
path = self.path.display(),
)?;
if let Some(config) = self.threshold_config.as_ref() {
write!(
@ -64,74 +80,42 @@ impl fmt::Display for PowerSupply {
}
}
impl PowerSupply {
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) -> Option<PathBuf> {
self.threshold_config
.map(|config| self.path.join(config.path_end))
}
}
// TODO: Migrate to central utils file. Same exists in cpu.rs.
fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
let path = path.as_ref();
fs::write(path, value).with_context(|| {
format!(
"failed to write '{value}' to '{path}'",
path = path.display(),
)
})
}
fn is_battery(path: &Path) -> anyhow::Result<bool> {
let type_path = path.join("type");
let type_ = fs::read_to_string(&type_path)
.with_context(|| format!("failed to read '{path}'", path = type_path.display()))?;
Ok(type_ == "Battery")
}
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);
impl PowerSupply {
pub fn from_name(name: String) -> anyhow::Result<Self> {
let mut power_supply = Self {
path: Path::new(POWER_SUPPLY_PATH).join(&name),
name,
threshold_config: None,
};
let threshold_config = is_battery(&entry_path)
power_supply.rescan()?;
Ok(power_supply)
}
pub fn from_path(path: PathBuf) -> anyhow::Result<Self> {
let mut power_supply = PowerSupply {
name: path
.file_name()
.with_context(|| {
format!(
"failed to determine what type of power supply '{path}' is",
path = entry_path.display(),
)
format!("failed to get file name of '{path}'", path = 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);
}
.to_string_lossy()
.to_string(),
path,
threshold_config: None,
};
power_supply.rescan()?;
Ok(power_supply)
}
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>> {
pub fn all() -> anyhow::Result<Vec<PowerSupply>> {
let mut power_supplies = Vec::new();
for entry in fs::read_dir(POWER_SUPPLY_PATH)
@ -146,83 +130,120 @@ pub fn get_power_supplies() -> anyhow::Result<Vec<PowerSupply>> {
}
};
let entry_path = entry.path();
let mut power_supply_config = None;
if is_battery(&entry_path).with_context(|| {
format!(
"failed to determine what type of power supply '{path}' is",
path = entry_path.display(),
)
})? {
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,
});
power_supplies.push(PowerSupply::from_path(entry.path())?);
}
Ok(power_supplies)
}
pub fn set_charge_threshold_start(
power_supply: &PowerSupply,
charge_threshold_start: u8,
) -> anyhow::Result<()> {
fn is_battery(&self) -> anyhow::Result<bool> {
let type_path = self.path.join("type");
let type_ = fs::read_to_string(&type_path)
.with_context(|| format!("failed to read '{path}'", path = type_path.display()))?;
Ok(type_ == "Battery")
}
pub fn rescan(&mut self) -> anyhow::Result<()> {
let threshold_config = self
.is_battery()
.with_context(|| format!("failed to determine what type of power supply '{self}' is"))?
.then(|| {
for config in POWER_SUPPLY_THRESHOLD_CONFIGS {
if self.path.join(config.path_start).exists()
&& self.path.join(config.path_end).exists()
{
return Some(*config);
}
}
None
})
.flatten();
self.threshold_config = threshold_config;
Ok(())
}
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) -> Option<PathBuf> {
self.threshold_config
.map(|config| self.path.join(config.path_end))
}
pub fn set_charge_threshold_start(&self, charge_threshold_start: u8) -> anyhow::Result<()> {
write(
&power_supply.charge_threshold_path_start().ok_or_else(|| {
&self.charge_threshold_path_start().ok_or_else(|| {
anyhow::anyhow!(
"power supply '{name}' does not support changing charge threshold levels",
name = power_supply.name,
name = self.name,
)
})?,
&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 {self}"))?;
log::info!("set battery threshold start for {power_supply} to {charge_threshold_start}%");
log::info!("set battery threshold start for {self} to {charge_threshold_start}%");
Ok(())
}
pub fn set_charge_threshold_end(
power_supply: &PowerSupply,
charge_threshold_end: u8,
) -> anyhow::Result<()> {
pub fn set_charge_threshold_end(&self, charge_threshold_end: u8) -> anyhow::Result<()> {
write(
&power_supply.charge_threshold_path_end().ok_or_else(|| {
&self.charge_threshold_path_end().ok_or_else(|| {
anyhow::anyhow!(
"power supply '{name}' does not support changing charge threshold levels",
name = power_supply.name,
name = self.name,
)
})?,
&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 {self}"))?;
log::info!("set battery threshold end for {power_supply} to {charge_threshold_end}%");
log::info!("set battery threshold end for {self} to {charge_threshold_end}%");
Ok(())
}
pub fn get_available_platform_profiles() -> Vec<String> {
let path = "/sys/firmware/acpi/platform_profile_choices";
let Ok(content) = fs::read_to_string(path) else {
return Vec::new();
};
content
.split_whitespace()
.map(ToString::to_string)
.collect()
}
/// Sets the platform profile.
/// This changes the system performance, temperature, fan, and other hardware replated characteristics.
///
/// Also see [`The Kernel docs`] for this.
///
/// [`The Kernel docs`]: <https://docs.kernel.org/userspace-api/sysfs-platform_profile.html>
pub fn set_platform_profile(profile: &str) -> anyhow::Result<()> {
let profiles = Self::get_available_platform_profiles();
if !profiles
.iter()
.any(|avail_profile| avail_profile == profile)
{
bail!(
"profile '{profile}' is not available for system. valid profiles: {profiles}",
profiles = profiles.join(", "),
);
}
write("/sys/firmware/acpi/platform_profile", profile)
.context("this probably means that your system does not support changing ACPI profiles")
}
}