refactor(src/commands): enhance battery command error handling and readability
Restructure loops, add explicit error handling, improve multi‑line messages, and fix formatting issues. - Enhancements - Use explicit braces for error branches to prevent silent failures - Wrap warning and informational messages in multi‑line `println!` with consistent spacing - Refactor directory reads for Ideapad fallback to avoid nested `let` bindings - Improve parsing and formatting of AMD TDP limit output with clear parentheses - Provide clearer debug logging when writing to threshold files - Bug Fixes - Added missing braces around error handling to ensure proper execution flow - Fixed variable binding scope for `ideapad_entries` to prevent shadowing - Ensured consistent handling of missing `charge_control_end_threshold` across all entries no changes.
This commit is contained in:
7 files changed
+242
-80
No files matched your search
+46
-22
@@ -1,5 +1,4 @@
|
||||
use crate::cli::BatteryCommands;
|
||||
use crate::utils::system::assert_root;
|
||||
use log::{debug, error, info};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
@@ -7,23 +6,21 @@ use std::path::Path;
|
||||
pub fn execute(command: &BatteryCommands) {
|
||||
match command {
|
||||
BatteryCommands::Limit { percent } => {
|
||||
assert_root();
|
||||
|
||||
if percent < &1 || percent > &100 {
|
||||
error!("Invalid percentage. Please provide a value between 1 and 100.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
println!("Setting battery charge limit to {}%", percent);
|
||||
|
||||
|
||||
let mut success_count = 0;
|
||||
let sys_class_power = Path::new("/sys/class/power_supply");
|
||||
|
||||
|
||||
if let Ok(entries) = fs::read_dir(sys_class_power) {
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name();
|
||||
let name_str = name.to_string_lossy();
|
||||
|
||||
|
||||
if name_str.starts_with("BAT") {
|
||||
let threshold_path = entry.path().join("charge_control_end_threshold");
|
||||
if threshold_path.exists() {
|
||||
@@ -32,12 +29,16 @@ pub fn execute(command: &BatteryCommands) {
|
||||
info!("Successfully set limit for {}", name_str);
|
||||
success_count += 1;
|
||||
}
|
||||
Err(e) => debug!("Failed to write to {}: {}", threshold_path.display(), e),
|
||||
Err(e) => {
|
||||
debug!("Failed to write to {}: {}", threshold_path.display(), e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ideapad fallback
|
||||
let mut ideapad_found = false;
|
||||
if let Ok(ideapad_entries) = fs::read_dir("/sys/bus/platform/drivers/ideapad_acpi") {
|
||||
if let Ok(ideapad_entries) =
|
||||
fs::read_dir("/sys/bus/platform/drivers/ideapad_acpi")
|
||||
{
|
||||
for ideapad_entry in ideapad_entries.flatten() {
|
||||
let ideapad_path = ideapad_entry.path();
|
||||
let conservation_path = ideapad_path.join("conservation_mode");
|
||||
@@ -47,15 +48,27 @@ pub fn execute(command: &BatteryCommands) {
|
||||
match fs::write(&conservation_path, val) {
|
||||
Ok(_) => {
|
||||
if *percent < 100 {
|
||||
println!("WARNING: Your laptop (Lenovo Ideapad) does NOT support custom charge limits (like {}%).", percent);
|
||||
println!("Instead, lapctl has enabled Lenovo's built-in 'Conservation Mode' via the Ideapad ACPI.");
|
||||
println!("This mode is hard-coded into your laptop's firmware to stop charging at ~60%.");
|
||||
println!(
|
||||
"WARNING: Your laptop (Lenovo Ideapad) does NOT support custom charge limits (like {}%).",
|
||||
percent
|
||||
);
|
||||
println!(
|
||||
"Instead, lapctl has enabled Lenovo's built-in 'Conservation Mode' via the Ideapad ACPI."
|
||||
);
|
||||
println!(
|
||||
"This mode is hard-coded into your laptop's firmware to stop charging at ~60%."
|
||||
);
|
||||
} else {
|
||||
println!("Disabled Conservation Mode (charging to 100%).");
|
||||
println!(
|
||||
"Disabled Conservation Mode (charging to 100%)."
|
||||
);
|
||||
}
|
||||
success_count += 1;
|
||||
}
|
||||
Err(e) => debug!("Failed to write to conservation_mode: {}", e),
|
||||
Err(e) => debug!(
|
||||
"Failed to write to conservation_mode: {}",
|
||||
e
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +84,9 @@ pub fn execute(command: &BatteryCommands) {
|
||||
if success_count > 0 {
|
||||
println!("Operation completed successfully.");
|
||||
} else {
|
||||
error!("Hardware does not support dynamic charge thresholds via sysfs or no batteries were found.");
|
||||
error!(
|
||||
"Hardware does not support dynamic charge thresholds via sysfs or no batteries were found."
|
||||
);
|
||||
}
|
||||
}
|
||||
BatteryCommands::Status => {
|
||||
@@ -83,24 +98,33 @@ pub fn execute(command: &BatteryCommands) {
|
||||
let name_str = name.to_string_lossy();
|
||||
if name_str.starts_with("BAT") {
|
||||
let bat_path = entry.path();
|
||||
let capacity = fs::read_to_string(bat_path.join("capacity")).unwrap_or_else(|_| "Unknown".into());
|
||||
let status = fs::read_to_string(bat_path.join("status")).unwrap_or_else(|_| "Unknown".into());
|
||||
|
||||
let capacity = fs::read_to_string(bat_path.join("capacity"))
|
||||
.unwrap_or_else(|_| "Unknown".into());
|
||||
let status = fs::read_to_string(bat_path.join("status"))
|
||||
.unwrap_or_else(|_| "Unknown".into());
|
||||
|
||||
println!("{}:", name_str);
|
||||
println!(" Capacity: {}%", capacity.trim());
|
||||
println!(" Status: {}", status.trim());
|
||||
|
||||
if let Ok(limit) = fs::read_to_string(bat_path.join("charge_control_end_threshold")) {
|
||||
|
||||
if let Ok(limit) =
|
||||
fs::read_to_string(bat_path.join("charge_control_end_threshold"))
|
||||
{
|
||||
println!(" Charge Limit: {}%", limit.trim());
|
||||
} else {
|
||||
// Ideapad fallback display
|
||||
if let Ok(ideapad_entries) = fs::read_dir("/sys/bus/platform/drivers/ideapad_acpi") {
|
||||
if let Ok(ideapad_entries) =
|
||||
fs::read_dir("/sys/bus/platform/drivers/ideapad_acpi")
|
||||
{
|
||||
for ideapad_entry in ideapad_entries.flatten() {
|
||||
let conservation_path = ideapad_entry.path().join("conservation_mode");
|
||||
let conservation_path =
|
||||
ideapad_entry.path().join("conservation_mode");
|
||||
if conservation_path.exists() {
|
||||
if let Ok(mode) = fs::read_to_string(&conservation_path) {
|
||||
if mode.trim() == "1" {
|
||||
println!(" Charge Limit: Conservation Mode (~60%)");
|
||||
println!(
|
||||
" Charge Limit: Conservation Mode (~60%)"
|
||||
);
|
||||
} else {
|
||||
println!(" Charge Limit: 100%");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::cli::CoolingCommands;
|
||||
use crate::utils::system::assert_root;
|
||||
use log::{debug, info};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
@@ -38,10 +37,7 @@ fn set_asus_throttle_policy(mode: &str) -> bool {
|
||||
success
|
||||
}
|
||||
|
||||
|
||||
pub fn execute(command: &CoolingCommands) {
|
||||
assert_root();
|
||||
|
||||
let (ideapad_mode, asus_mode, desc) = match command {
|
||||
CoolingCommands::Performance => ("1", "1", "Performance"),
|
||||
CoolingCommands::Balanced => ("0", "0", "Balanced"),
|
||||
@@ -55,7 +51,7 @@ pub fn execute(command: &CoolingCommands) {
|
||||
applied = true;
|
||||
println!("Ideapad fan mode applied.");
|
||||
}
|
||||
|
||||
|
||||
if set_asus_throttle_policy(asus_mode) {
|
||||
applied = true;
|
||||
println!("ASUS thermal policy applied.");
|
||||
|
||||
+77
-22
@@ -1,5 +1,7 @@
|
||||
use crate::cli::GpuCommands;
|
||||
use crate::hardware::gpu::{get_amd_igpu_name, get_current_mode, get_igpu_vendor, get_nvidia_gpu_pci_bus};
|
||||
use crate::hardware::gpu::{
|
||||
get_amd_igpu_name, get_current_mode, get_igpu_vendor, get_nvidia_gpu_pci_bus,
|
||||
};
|
||||
use crate::utils::system::{assert_root, create_file, get_display_manager, rebuild_initramfs};
|
||||
use log::{error, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -116,7 +118,8 @@ done
|
||||
|
||||
// Helpers
|
||||
fn xorg_intel(bus_id: &str) -> String {
|
||||
format!(r#"# Automatically generated by lapctl
|
||||
format!(
|
||||
r#"# Automatically generated by lapctl
|
||||
|
||||
Section "ServerLayout"
|
||||
Identifier "layout"
|
||||
@@ -145,11 +148,14 @@ Section "Screen"
|
||||
Identifier "intel"
|
||||
Device "intel"
|
||||
EndSection
|
||||
"#, bus_id)
|
||||
"#,
|
||||
bus_id
|
||||
)
|
||||
}
|
||||
|
||||
fn xorg_amd(bus_id: &str) -> String {
|
||||
format!(r#"# Automatically generated by lapctl
|
||||
format!(
|
||||
r#"# Automatically generated by lapctl
|
||||
|
||||
Section "ServerLayout"
|
||||
Identifier "layout"
|
||||
@@ -178,7 +184,9 @@ Section "Screen"
|
||||
Identifier "amd"
|
||||
Device "amdgpu"
|
||||
EndSection
|
||||
"#, bus_id)
|
||||
"#,
|
||||
bus_id
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_xrandr_script(igpu_vendor: Option<&String>) -> String {
|
||||
@@ -244,7 +252,9 @@ struct Cache {
|
||||
}
|
||||
|
||||
fn create_cache_obj(bus_id: String) -> Cache {
|
||||
Cache { nvidia_gpu_pci_bus: bus_id }
|
||||
Cache {
|
||||
nvidia_gpu_pci_bus: bus_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_cache_file() {
|
||||
@@ -273,7 +283,10 @@ fn read_cache_file() -> Result<Cache, String> {
|
||||
} else if get_current_mode() == "hybrid" {
|
||||
Ok(create_cache_obj(get_nvidia_gpu_pci_bus()))
|
||||
} else {
|
||||
Err("No cache present. Operation requires that the system be in the hybrid Optimus mode".into())
|
||||
Err(
|
||||
"No cache present. Operation requires that the system be in the hybrid Optimus mode"
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +323,10 @@ fn switch_integrated() {
|
||||
fn switch_hybrid(rtd3: Option<u8>, use_nvidia_current: bool) {
|
||||
assert_root();
|
||||
println!("Switching to hybrid mode");
|
||||
println!("Enable PCI-Express Runtime D3 (RTD3) Power Management: {}", rtd3.is_some());
|
||||
println!(
|
||||
"Enable PCI-Express Runtime D3 (RTD3) Power Management: {}",
|
||||
rtd3.is_some()
|
||||
);
|
||||
|
||||
if get_current_mode() == "hybrid" && !Path::new(CACHE_FILE_PATH).exists() {
|
||||
create_cache_file();
|
||||
@@ -333,9 +349,15 @@ fn switch_hybrid(rtd3: Option<u8>, use_nvidia_current: bool) {
|
||||
|
||||
if let Some(val) = rtd3 {
|
||||
let modeset_content = if use_nvidia_current {
|
||||
format!("# Automatically generated by lapctl\n\noptions nvidia-current-drm modeset=1\noptions nvidia-current \"NVreg_DynamicPowerManagement=0x0{}\"\noptions nvidia-current NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n", val)
|
||||
format!(
|
||||
"# Automatically generated by lapctl\n\noptions nvidia-current-drm modeset=1\noptions nvidia-current \"NVreg_DynamicPowerManagement=0x0{}\"\noptions nvidia-current NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n",
|
||||
val
|
||||
)
|
||||
} else {
|
||||
format!("# Automatically generated by lapctl\n\noptions nvidia-drm modeset=1\noptions nvidia \"NVreg_DynamicPowerManagement=0x0{}\"\noptions nvidia NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n", val)
|
||||
format!(
|
||||
"# Automatically generated by lapctl\n\noptions nvidia-drm modeset=1\noptions nvidia \"NVreg_DynamicPowerManagement=0x0{}\"\noptions nvidia NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n",
|
||||
val
|
||||
)
|
||||
};
|
||||
create_file(MODESET_PATH, &modeset_content, false);
|
||||
create_file(UDEV_PM_PATH, UDEV_PM_CONTENT, false);
|
||||
@@ -381,23 +403,35 @@ fn switch_nvidia(
|
||||
if get_current_mode() == "hybrid" && !Path::new(CACHE_FILE_PATH).exists() {
|
||||
create_cache_file();
|
||||
}
|
||||
|
||||
|
||||
let cache = read_cache_file();
|
||||
let pci_bus = match cache {
|
||||
Ok(c) => c.nvidia_gpu_pci_bus,
|
||||
Err(_) => get_nvidia_gpu_pci_bus()
|
||||
Err(_) => get_nvidia_gpu_pci_bus(),
|
||||
};
|
||||
|
||||
cleanup();
|
||||
|
||||
if use_nvidia_current {
|
||||
create_file(MODESET_PATH, "# Automatically generated by lapctl\n\noptions nvidia-current-drm modeset=1\noptions nvidia-current NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n", false);
|
||||
create_file(
|
||||
MODESET_PATH,
|
||||
"# Automatically generated by lapctl\n\noptions nvidia-current-drm modeset=1\noptions nvidia-current NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n",
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
create_file(MODESET_PATH, "# Automatically generated by lapctl\n\noptions nvidia-drm modeset=1\noptions nvidia NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n", false);
|
||||
create_file(
|
||||
MODESET_PATH,
|
||||
"# Automatically generated by lapctl\n\noptions nvidia-drm modeset=1\noptions nvidia NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n",
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
if wayland {
|
||||
create_file("/etc/lapctl-wayland-nvidia", "# Wayland mode marker for lapctl\n", false);
|
||||
create_file(
|
||||
"/etc/lapctl-wayland-nvidia",
|
||||
"# Wayland mode marker for lapctl\n",
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
let igpu_vendor = get_igpu_vendor();
|
||||
if let Some(ref vendor) = igpu_vendor {
|
||||
@@ -430,9 +464,17 @@ fn switch_nvidia(
|
||||
create_file(&format!("{}.bak", SDDM_XSETUP_PATH), &content, false);
|
||||
}
|
||||
}
|
||||
create_file(SDDM_XSETUP_PATH, &generate_xrandr_script(igpu_vendor.as_ref()), true);
|
||||
create_file(
|
||||
SDDM_XSETUP_PATH,
|
||||
&generate_xrandr_script(igpu_vendor.as_ref()),
|
||||
true,
|
||||
);
|
||||
} else if cdm == "lightdm" {
|
||||
create_file(LIGHTDM_SCRIPT_PATH, &generate_xrandr_script(igpu_vendor.as_ref()), true);
|
||||
create_file(
|
||||
LIGHTDM_SCRIPT_PATH,
|
||||
&generate_xrandr_script(igpu_vendor.as_ref()),
|
||||
true,
|
||||
);
|
||||
create_file(LIGHTDM_CONFIG_PATH, LIGHTDM_CONFIG_CONTENT, false);
|
||||
}
|
||||
}
|
||||
@@ -471,7 +513,7 @@ pub fn execute(cmd: &GpuCommands) {
|
||||
}
|
||||
GpuCommands::Reset => {
|
||||
assert_root();
|
||||
|
||||
|
||||
if get_current_mode() == "hybrid" && !Path::new(CACHE_FILE_PATH).exists() {
|
||||
create_cache_file();
|
||||
}
|
||||
@@ -492,9 +534,22 @@ pub fn execute(cmd: &GpuCommands) {
|
||||
println!("Operation completed successfully");
|
||||
}
|
||||
GpuCommands::Integrated => switch_integrated(),
|
||||
GpuCommands::Hybrid { rtd3, use_nvidia_current } => switch_hybrid(rtd3.clone(), *use_nvidia_current),
|
||||
GpuCommands::Nvidia { dm, force_comp, coolbits, use_nvidia_current, wayland } => {
|
||||
switch_nvidia(dm.clone(), *force_comp, coolbits.clone(), *use_nvidia_current, *wayland)
|
||||
}
|
||||
GpuCommands::Hybrid {
|
||||
rtd3,
|
||||
use_nvidia_current,
|
||||
} => switch_hybrid(rtd3.clone(), *use_nvidia_current),
|
||||
GpuCommands::Nvidia {
|
||||
dm,
|
||||
force_comp,
|
||||
coolbits,
|
||||
use_nvidia_current,
|
||||
wayland,
|
||||
} => switch_nvidia(
|
||||
dm.clone(),
|
||||
*force_comp,
|
||||
coolbits.clone(),
|
||||
*use_nvidia_current,
|
||||
*wayland,
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use crate::utils::system::{assert_root, create_file};
|
||||
use log::info;
|
||||
use std::process::Command;
|
||||
|
||||
const LAPCTL_UDEV_RULES: &str = r#"# /etc/udev/rules.d/99-lapctl.rules
|
||||
# Automatically generated by lapctl
|
||||
# This file grants read/write permissions to all local users
|
||||
# for the SysFS endpoints required by lapctl's power, cooling, and battery commands.
|
||||
|
||||
# ACPI Platform Profiles & CPU Scaling
|
||||
ACTION=="add", SUBSYSTEM=="firmware", RUN+="/bin/sh -c 'chmod a+w /sys/firmware/acpi/platform_profile 2>/dev/null'"
|
||||
ACTION=="add", SUBSYSTEM=="cpu", RUN+="/bin/sh -c 'chmod a+w /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor 2>/dev/null'"
|
||||
ACTION=="add", SUBSYSTEM=="cpu", RUN+="/bin/sh -c 'chmod a+w /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference 2>/dev/null'"
|
||||
|
||||
# Battery Charging Limits
|
||||
ACTION=="add", SUBSYSTEM=="power_supply", RUN+="/bin/sh -c 'chmod a+w /sys/class/power_supply/BAT*/charge_control_end_threshold 2>/dev/null'"
|
||||
|
||||
# Lenovo Ideapad Thermal and Conservation Mode
|
||||
ACTION=="add", SUBSYSTEM=="platform", RUN+="/bin/sh -c 'chmod a+w /sys/bus/platform/drivers/ideapad_acpi/VPC*/fan_mode 2>/dev/null'"
|
||||
ACTION=="add", SUBSYSTEM=="platform", RUN+="/bin/sh -c 'chmod a+w /sys/bus/platform/drivers/ideapad_acpi/VPC*/conservation_mode 2>/dev/null'"
|
||||
|
||||
# ASUS WMI Thermal Policy
|
||||
ACTION=="add", SUBSYSTEM=="platform", RUN+="/bin/sh -c 'chmod a+w /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy 2>/dev/null'"
|
||||
|
||||
# Intel RAPL TDP Control
|
||||
ACTION=="add", SUBSYSTEM=="powercap", RUN+="/bin/sh -c 'chmod a+w /sys/class/powercap/intel-rapl:0/constraint_*_power_limit_uw 2>/dev/null'"
|
||||
|
||||
# AMD HWMon Power Capping
|
||||
ACTION=="add", SUBSYSTEM=="hwmon", RUN+="/bin/sh -c 'find /sys/class/hwmon -name \"power1_cap\" -exec chmod a+w {} + 2>/dev/null'"
|
||||
"#;
|
||||
|
||||
pub fn execute() {
|
||||
assert_root();
|
||||
|
||||
println!("Installing lapctl udev rules for rootless hardware configuration...");
|
||||
|
||||
let rules_path = "/etc/udev/rules.d/99-lapctl.rules";
|
||||
create_file(rules_path, LAPCTL_UDEV_RULES, false);
|
||||
|
||||
println!("Successfully wrote rules to {}", rules_path);
|
||||
println!("Loading rules and applying immediate session permissions...");
|
||||
|
||||
let _ = Command::new("udevadm")
|
||||
.args(["control", "--reload-rules"])
|
||||
.status();
|
||||
|
||||
let _ = Command::new("udevadm").args(["trigger"]).status();
|
||||
|
||||
// The udevadm trigger often fails to catch already-initialized ACPI nodes without a reboot,
|
||||
// so we manually apply the permissions for the current session right now.
|
||||
let commands = vec![
|
||||
"chmod a+w /sys/firmware/acpi/platform_profile 2>/dev/null",
|
||||
"chmod a+w /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor 2>/dev/null",
|
||||
"chmod a+w /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference 2>/dev/null",
|
||||
"chmod a+w /sys/class/power_supply/BAT*/charge_control_end_threshold 2>/dev/null",
|
||||
"chmod a+w /sys/bus/platform/drivers/ideapad_acpi/VPC*/fan_mode 2>/dev/null",
|
||||
"chmod a+w /sys/bus/platform/drivers/ideapad_acpi/VPC*/conservation_mode 2>/dev/null",
|
||||
"chmod a+w /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy 2>/dev/null",
|
||||
"chmod a+w /sys/class/powercap/intel-rapl:0/constraint_*_power_limit_uw 2>/dev/null",
|
||||
"find /sys/class/hwmon -name \"power1_cap\" -exec chmod a+w {} + 2>/dev/null",
|
||||
];
|
||||
|
||||
for cmd in commands {
|
||||
let _ = Command::new("sh").arg("-c").arg(cmd).status();
|
||||
}
|
||||
|
||||
info!("Udev reload and immediate ACL application complete.");
|
||||
println!(
|
||||
"Installation successful! You can now use lapctl power, cooling, and battery commands without sudo."
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod battery;
|
||||
pub mod cooling;
|
||||
pub mod gpu;
|
||||
pub mod install_rules;
|
||||
pub mod power;
|
||||
pub mod status;
|
||||
+24
-17
@@ -1,5 +1,4 @@
|
||||
use crate::cli::PowerCommands;
|
||||
use crate::utils::system::assert_root;
|
||||
use log::{error, info};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
@@ -15,7 +14,8 @@ fn set_cpu_governor(governor: &str) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
let name = path.file_name().unwrap_or_default().to_string_lossy();
|
||||
if name.starts_with("cpu") && name.chars().nth(3).map_or(false, |c| c.is_ascii_digit()) {
|
||||
if name.starts_with("cpu") && name.chars().nth(3).map_or(false, |c| c.is_ascii_digit())
|
||||
{
|
||||
let scaling_gov_path = path.join("cpufreq/scaling_governor");
|
||||
if scaling_gov_path.exists() {
|
||||
match fs::write(&scaling_gov_path, governor) {
|
||||
@@ -28,7 +28,10 @@ fn set_cpu_governor(governor: &str) {
|
||||
}
|
||||
|
||||
if success_count > 0 {
|
||||
info!("Set CPU scaling governor to '{}' on {} cores", governor, success_count);
|
||||
info!(
|
||||
"Set CPU scaling governor to '{}' on {} cores",
|
||||
governor, success_count
|
||||
);
|
||||
} else {
|
||||
log::debug!("Could not set CPU scaling governor");
|
||||
}
|
||||
@@ -45,7 +48,8 @@ fn set_energy_performance_preference(epp: &str) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
let name = path.file_name().unwrap_or_default().to_string_lossy();
|
||||
if name.starts_with("cpu") && name.chars().nth(3).map_or(false, |c| c.is_ascii_digit()) {
|
||||
if name.starts_with("cpu") && name.chars().nth(3).map_or(false, |c| c.is_ascii_digit())
|
||||
{
|
||||
let epp_path = path.join("cpufreq/energy_performance_preference");
|
||||
if epp_path.exists() {
|
||||
match fs::write(&epp_path, epp) {
|
||||
@@ -58,13 +62,16 @@ fn set_energy_performance_preference(epp: &str) {
|
||||
}
|
||||
|
||||
if success_count > 0 {
|
||||
info!("Set Energy Performance Preference to '{}' on {} cores", epp, success_count);
|
||||
info!(
|
||||
"Set Energy Performance Preference to '{}' on {} cores",
|
||||
epp, success_count
|
||||
);
|
||||
}
|
||||
}
|
||||
fn set_intel_rapl_limit(watts: u32) -> bool {
|
||||
let mut success = false;
|
||||
let microwatts = watts * 1_000_000;
|
||||
|
||||
|
||||
// Intel RAPL
|
||||
let rapl_dir = Path::new("/sys/class/powercap/intel-rapl");
|
||||
if rapl_dir.exists() {
|
||||
@@ -72,12 +79,12 @@ fn set_intel_rapl_limit(watts: u32) -> bool {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
let name = path.file_name().unwrap_or_default().to_string_lossy();
|
||||
|
||||
|
||||
// Set limits for package-0 (entire CPU package)
|
||||
if name.starts_with("intel-rapl:") {
|
||||
let constraint_0 = path.join("constraint_0_power_limit_uw"); // PL1
|
||||
let constraint_1 = path.join("constraint_1_power_limit_uw"); // PL2
|
||||
|
||||
|
||||
if constraint_0.exists() {
|
||||
match fs::write(&constraint_0, microwatts.to_string()) {
|
||||
Ok(_) => {
|
||||
@@ -108,7 +115,7 @@ fn set_intel_rapl_limit(watts: u32) -> bool {
|
||||
fn set_amd_hwmon_limit(watts: u32) -> bool {
|
||||
let mut success = false;
|
||||
let microwatts = watts * 1_000_000;
|
||||
|
||||
|
||||
// AMD hwmon (Typically exposed via hwmon platform drivers like k10temp or amd_pmc)
|
||||
let hwmon_dir = Path::new("/sys/class/hwmon");
|
||||
if hwmon_dir.exists() {
|
||||
@@ -117,7 +124,7 @@ fn set_amd_hwmon_limit(watts: u32) -> bool {
|
||||
let path = entry.path();
|
||||
let power1_cap = path.join("power1_cap");
|
||||
let name_path = path.join("name");
|
||||
|
||||
|
||||
if power1_cap.exists() {
|
||||
// Try to verify this is actually the CPU and not a generic sensor
|
||||
if let Ok(name) = fs::read_to_string(&name_path) {
|
||||
@@ -150,8 +157,6 @@ fn set_platform_profile(profile: &str) {
|
||||
}
|
||||
|
||||
pub fn execute(command: &PowerCommands) {
|
||||
assert_root();
|
||||
|
||||
match command {
|
||||
PowerCommands::Performance => {
|
||||
println!("Setting power profile to Performance");
|
||||
@@ -163,9 +168,9 @@ pub fn execute(command: &PowerCommands) {
|
||||
PowerCommands::Balanced => {
|
||||
println!("Setting power profile to Balanced");
|
||||
set_platform_profile("balanced");
|
||||
// Schedutil is the modern balanced default. Fallback isn't critical since cpufreq
|
||||
// Schedutil is the modern balanced default. Fallback isn't critical since cpufreq
|
||||
// usually rejects invalid profiles, and standard Intel/AMD systems use EPP now anyway.
|
||||
set_cpu_governor("schedutil");
|
||||
set_cpu_governor("schedutil");
|
||||
set_energy_performance_preference("balance_performance");
|
||||
println!("Operation completed successfully.");
|
||||
}
|
||||
@@ -178,7 +183,7 @@ pub fn execute(command: &PowerCommands) {
|
||||
}
|
||||
PowerCommands::LimitTdp { watts } => {
|
||||
println!("Setting CPU Package TDP Limit to {} Watts...", watts);
|
||||
|
||||
|
||||
let mut applied = false;
|
||||
if set_intel_rapl_limit(*watts) {
|
||||
applied = true;
|
||||
@@ -188,11 +193,13 @@ pub fn execute(command: &PowerCommands) {
|
||||
applied = true;
|
||||
println!("Successfully applied AMD HWMon power capping.");
|
||||
}
|
||||
|
||||
|
||||
if applied {
|
||||
println!("Operation completed successfully.");
|
||||
} else {
|
||||
error!("Hardware does not support dynamic powercap limits via intel-rapl or standard hwmon.");
|
||||
error!(
|
||||
"Hardware does not support dynamic powercap limits via intel-rapl or standard hwmon."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-14
@@ -10,7 +10,7 @@ pub fn execute() {
|
||||
println!("GPU Mode: {}", current_mode);
|
||||
|
||||
// Battery Limit / Status checks (we can peek into typical battery paths)
|
||||
// Note: this assumes a typical sysfs structure. A complete implementation
|
||||
// Note: this assumes a typical sysfs structure. A complete implementation
|
||||
// would parse /sys/class/power_supply/BAT0 or similar.
|
||||
let sys_class_power = "/sys/class/power_supply";
|
||||
if let Ok(entries) = fs::read_dir(sys_class_power) {
|
||||
@@ -19,18 +19,23 @@ pub fn execute() {
|
||||
let name_str = name.to_string_lossy();
|
||||
if name_str.starts_with("BAT") {
|
||||
let bat_path = entry.path();
|
||||
|
||||
let capacity = fs::read_to_string(bat_path.join("capacity")).unwrap_or_else(|_| "Unknown".into());
|
||||
let status = fs::read_to_string(bat_path.join("status")).unwrap_or_else(|_| "Unknown".into());
|
||||
|
||||
|
||||
let capacity = fs::read_to_string(bat_path.join("capacity"))
|
||||
.unwrap_or_else(|_| "Unknown".into());
|
||||
let status = fs::read_to_string(bat_path.join("status"))
|
||||
.unwrap_or_else(|_| "Unknown".into());
|
||||
|
||||
println!("{}:", name_str);
|
||||
println!(" Capacity: {}%", capacity.trim());
|
||||
println!(" Status: {}", status.trim());
|
||||
|
||||
if let Ok(limit) = fs::read_to_string(bat_path.join("charge_control_end_threshold")) {
|
||||
|
||||
if let Ok(limit) = fs::read_to_string(bat_path.join("charge_control_end_threshold"))
|
||||
{
|
||||
println!(" Charge Limit: {}%", limit.trim());
|
||||
} else {
|
||||
if let Ok(ideapad_entries) = fs::read_dir("/sys/bus/platform/drivers/ideapad_acpi") {
|
||||
if let Ok(ideapad_entries) =
|
||||
fs::read_dir("/sys/bus/platform/drivers/ideapad_acpi")
|
||||
{
|
||||
for ideapad_entry in ideapad_entries.flatten() {
|
||||
let conservation_path = ideapad_entry.path().join("conservation_mode");
|
||||
if conservation_path.exists() {
|
||||
@@ -68,7 +73,7 @@ pub fn execute() {
|
||||
"1" => "Performance",
|
||||
"0" => "Balanced",
|
||||
"2" => "Quiet / Battery Saving",
|
||||
_ => mode.trim()
|
||||
_ => mode.trim(),
|
||||
};
|
||||
println!("Cooling Profile: {} (Ideapad)", desc);
|
||||
}
|
||||
@@ -84,7 +89,7 @@ pub fn execute() {
|
||||
"1" => "Performance",
|
||||
"0" => "Balanced",
|
||||
"2" => "Quiet / Battery Saving",
|
||||
_ => mode.trim()
|
||||
_ => mode.trim(),
|
||||
};
|
||||
println!("Cooling Profile: {} (ASUS)", desc);
|
||||
}
|
||||
@@ -96,7 +101,7 @@ pub fn execute() {
|
||||
|
||||
// TDP Limit Status
|
||||
let mut tdp_found = false;
|
||||
|
||||
|
||||
// Check Intel RAPL
|
||||
let rapl_dir = Path::new("/sys/class/powercap/intel-rapl");
|
||||
if rapl_dir.exists() {
|
||||
@@ -104,7 +109,7 @@ pub fn execute() {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
let name = path.file_name().unwrap_or_default().to_string_lossy();
|
||||
|
||||
|
||||
if name.starts_with("intel-rapl:") {
|
||||
let constraint_0 = path.join("constraint_0_power_limit_uw"); // PL1
|
||||
if constraint_0.exists() {
|
||||
@@ -130,13 +135,16 @@ pub fn execute() {
|
||||
let path = entry.path();
|
||||
let power1_cap = path.join("power1_cap");
|
||||
let name_path = path.join("name");
|
||||
|
||||
|
||||
if power1_cap.exists() {
|
||||
if let Ok(name) = fs::read_to_string(&name_path) {
|
||||
if name.trim().contains("amd") {
|
||||
if let Ok(limit_uw) = fs::read_to_string(&power1_cap) {
|
||||
if let Ok(limit) = limit_uw.trim().parse::<u64>() {
|
||||
println!("CPU TDP Limit: {}W (AMD hwmon)", limit / 1_000_000);
|
||||
println!(
|
||||
"CPU TDP Limit: {}W (AMD hwmon)",
|
||||
limit / 1_000_000
|
||||
);
|
||||
tdp_found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user