Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8947bd643a | ||
|
|
5ac2aed516 | ||
|
|
4020f65e28 | ||
|
|
d52b3992e3 |
No files matched your search
Generated
+1
-1
@@ -596,7 +596,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lapctl"
|
name = "lapctl"
|
||||||
version = "0.3.1"
|
version = "0.3.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lapctl"
|
name = "lapctl"
|
||||||
version = "0.3.1"
|
version = "0.3.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
+67
-40
@@ -14,6 +14,10 @@ trait Lapctl {
|
|||||||
async fn set_battery_limit(&self, percent: u32) -> zbus::Result<()>;
|
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 {
|
fn try_call_daemon(command: &BatteryCommands) -> bool {
|
||||||
let rt = match tokio::runtime::Runtime::new() {
|
let rt = match tokio::runtime::Runtime::new() {
|
||||||
Ok(rt) => rt,
|
Ok(rt) => rt,
|
||||||
@@ -56,7 +60,16 @@ pub fn execute(command: &BatteryCommands) {
|
|||||||
error!("Invalid percentage. Please provide a value between 1 and 100.");
|
error!("Invalid percentage. Please provide a value between 1 and 100.");
|
||||||
std::process::exit(1);
|
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 => {}
|
BatteryCommands::Status => {}
|
||||||
}
|
}
|
||||||
@@ -72,6 +85,27 @@ pub fn execute_local(command: &BatteryCommands) {
|
|||||||
match command {
|
match command {
|
||||||
BatteryCommands::Limit { percent } => {
|
BatteryCommands::Limit { percent } => {
|
||||||
let mut success_count = 0;
|
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");
|
let sys_class_power = Path::new("/sys/class/power_supply");
|
||||||
|
|
||||||
if let Ok(entries) = fs::read_dir(sys_class_power) {
|
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") {
|
if name_str.starts_with("BAT") {
|
||||||
let threshold_path = entry.path().join("charge_control_end_threshold");
|
let threshold_path = entry.path().join("charge_control_end_threshold");
|
||||||
if threshold_path.exists() {
|
if threshold_path.exists() {
|
||||||
|
println!(
|
||||||
|
"Setting battery charge limit for {} to {}%",
|
||||||
|
name_str, percent
|
||||||
|
);
|
||||||
match fs::write(&threshold_path, percent.to_string()) {
|
match fs::write(&threshold_path, percent.to_string()) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("Successfully set limit for {}", name_str);
|
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)
|
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!(" Capacity: {}%", capacity.trim());
|
||||||
println!(" Status: {}", status.trim());
|
println!(" Status: {}", status.trim());
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
{
|
||||||
|
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) =
|
if let Ok(limit) =
|
||||||
fs::read_to_string(bat_path.join("charge_control_end_threshold"))
|
fs::read_to_string(bat_path.join("charge_control_end_threshold"))
|
||||||
{
|
{
|
||||||
println!(" Charge Limit: {}%", limit.trim());
|
println!(" Charge Limit: {}%", limit.trim());
|
||||||
} else {
|
} else {
|
||||||
// Ideapad fallback display
|
// Re-check if it's an ideapad and we haven't shown a limit (likely 100%)
|
||||||
let conservation_path = Path::new(
|
if let Ok(drivers) =
|
||||||
"/sys/bus/platform/devices/ideapad_acpi/conservation_mode",
|
fs::read_dir("/sys/bus/platform/drivers/ideapad_acpi")
|
||||||
);
|
|
||||||
if conservation_path.exists()
|
|
||||||
&& let Ok(mode) = fs::read_to_string(conservation_path)
|
|
||||||
{
|
{
|
||||||
if mode.trim() == "1" {
|
for entry in drivers.flatten() {
|
||||||
println!(" Charge Limit: Conservation Mode (~60%)");
|
if entry.path().join("conservation_mode").exists() {
|
||||||
} else {
|
|
||||||
println!(" Charge Limit: 100%");
|
println!(" Charge Limit: 100%");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user