4 Commits
Author SHA1 Message Date
sankalp 8947bd643a feat(commands):simplify battery limit messaging for Ideapad laptopsThe command now detects Ideapad hardware and provides clearer messages about Conservation Mode, removing outdated warnings. 2026-03-23 18:30:37 +05:45
sankalp 5ac2aed516 chore(root): bump lapctl version to 0.3.2
Updated Cargo.lock and Cargo.toml to version 0.3.2 of the `lapctl` package.

New Features
- None

Enhancements
- Updated dependency version to 0.3.2Bug Fixes
- None
2026-03-23 17:28:36 +05:45
sankalp 4020f65e28 bumped to 0.3.2 2026-03-23 17:10:37 +05:45
sankalp d52b3992e3 feat(src/commands):add Ideapad conservation mode fallback and improve charge limit messaging 2026-03-23 17:08:36 +05:45
3 changed files with 74 additions and 47 deletions

No files matched your search

Generated
+1 -1
View File
@@ -596,7 +596,7 @@ dependencies = [
[[package]]
name = "lapctl"
version = "0.3.1"
version = "0.3.2"
dependencies = [
"clap",
"env_logger",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "lapctl"
version = "0.3.1"
version = "0.3.2"
edition = "2024"
[dependencies]
+72 -45
View File
@@ -14,6 +14,10 @@ trait Lapctl {
async fn set_battery_limit(&self, percent: u32) -> zbus::Result<()>;
}
fn is_ideapad() -> bool {
Path::new("/sys/bus/platform/drivers/ideapad_acpi").exists()
}
fn try_call_daemon(command: &BatteryCommands) -> bool {
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
@@ -56,7 +60,16 @@ pub fn execute(command: &BatteryCommands) {
error!("Invalid percentage. Please provide a value between 1 and 100.");
std::process::exit(1);
}
println!("Setting battery charge limit to {}%", percent);
if is_ideapad() {
if *percent < 100 {
println!("Setting battery charge limit (Lenovo Conservation Mode ~60%)...");
} else {
println!("Disabling battery charge limit (charging to 100%)...");
}
} else {
println!("Setting battery charge limit to {}%...", percent);
}
}
BatteryCommands::Status => {}
}
@@ -72,6 +85,27 @@ pub fn execute_local(command: &BatteryCommands) {
match command {
BatteryCommands::Limit { percent } => {
let mut success_count = 0;
// Ideapad fallback - search for conservation_mode in ideapad_acpi driver directories
if let Ok(drivers) = fs::read_dir("/sys/bus/platform/drivers/ideapad_acpi") {
for entry in drivers.flatten() {
let conservation_path = entry.path().join("conservation_mode");
if conservation_path.exists() {
let val = if *percent < 100 { "1" } else { "0" };
match fs::write(&conservation_path, val) {
Ok(_) => {
success_count += 1;
}
Err(e) => {
debug!("Failed to write to {}: {}", conservation_path.display(), e)
}
}
}
}
}
// Only try standard thresholds if we haven't already succeeded with Ideapad fallback
// or if we want to ensure all bases are covered. For now, let's do both but prioritize the message.
let sys_class_power = Path::new("/sys/class/power_supply");
if let Ok(entries) = fs::read_dir(sys_class_power) {
@@ -82,6 +116,10 @@ pub fn execute_local(command: &BatteryCommands) {
if name_str.starts_with("BAT") {
let threshold_path = entry.path().join("charge_control_end_threshold");
if threshold_path.exists() {
println!(
"Setting battery charge limit for {} to {}%",
name_str, percent
);
match fs::write(&threshold_path, percent.to_string()) {
Ok(_) => {
info!("Successfully set limit for {}", name_str);
@@ -91,36 +129,6 @@ pub fn execute_local(command: &BatteryCommands) {
debug!("Failed to write to {}: {}", threshold_path.display(), e)
}
}
} else {
// Ideapad fallback
let conservation_path = Path::new(
"/sys/bus/platform/devices/ideapad_acpi/conservation_mode",
);
if conservation_path.exists() {
let val = if *percent < 100 { "1" } else { "0" };
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%."
);
} else {
println!(
"Disabled Conservation Mode (charging to 100%)."
);
}
success_count += 1;
}
Err(e) => debug!("Failed to write to conservation_mode: {}", e),
}
}
}
}
}
@@ -151,22 +159,41 @@ pub fn execute_local(command: &BatteryCommands) {
println!(" Capacity: {}%", capacity.trim());
println!(" Status: {}", status.trim());
if let Ok(limit) =
fs::read_to_string(bat_path.join("charge_control_end_threshold"))
// Check for Ideapad conservation mode first using robust detection
let mut limit_shown = false;
if let Ok(drivers) = fs::read_dir("/sys/bus/platform/drivers/ideapad_acpi")
{
println!(" Charge Limit: {}%", limit.trim());
} else {
// Ideapad fallback display
let conservation_path = Path::new(
"/sys/bus/platform/devices/ideapad_acpi/conservation_mode",
);
if conservation_path.exists()
&& let Ok(mode) = fs::read_to_string(conservation_path)
for entry in drivers.flatten() {
let conservation_path = entry.path().join("conservation_mode");
if conservation_path.exists()
&& let Ok(mode) = fs::read_to_string(&conservation_path)
{
if mode.trim() == "1" {
println!(" Charge Limit: Conservation Mode (~60%)");
limit_shown = true;
} else if mode.trim() == "0" {
// On Ideapads 0 means 100% or off.
}
}
}
}
if !limit_shown {
if let Ok(limit) =
fs::read_to_string(bat_path.join("charge_control_end_threshold"))
{
if mode.trim() == "1" {
println!(" Charge Limit: Conservation Mode (~60%)");
} else {
println!(" Charge Limit: 100%");
println!(" Charge Limit: {}%", limit.trim());
} else {
// Re-check if it's an ideapad and we haven't shown a limit (likely 100%)
if let Ok(drivers) =
fs::read_dir("/sys/bus/platform/drivers/ideapad_acpi")
{
for entry in drivers.flatten() {
if entry.path().join("conservation_mode").exists() {
println!(" Charge Limit: 100%");
break;
}
}
}
}
}