diff --git a/src/commands/battery.rs b/src/commands/battery.rs
index 9fbdfd9..3df3c75 100644
--- a/src/commands/battery.rs
+++ b/src/commands/battery.rs
@@ -15,10 +15,6 @@ trait Lapctl {
}
fn try_call_daemon(command: &BatteryCommands) -> bool {
- if std::env::var("LAPCTL_DAEMON_INTERNAL").is_ok() {
- return false;
- }
-
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return false,
@@ -54,20 +50,27 @@ fn try_call_daemon(command: &BatteryCommands) -> bool {
}
pub fn execute(command: &BatteryCommands) {
- if matches!(command, BatteryCommands::Limit { .. }) && try_call_daemon(command) {
- println!("Request handled by lapctld daemon.");
- return;
- }
-
match command {
BatteryCommands::Limit { percent } => {
if !(&1..=&100).contains(&percent) {
error!("Invalid percentage. Please provide a value between 1 and 100.");
std::process::exit(1);
}
-
println!("Setting battery charge limit to {}%", percent);
+ }
+ BatteryCommands::Status => {}
+ }
+ if matches!(command, BatteryCommands::Limit { .. }) && try_call_daemon(command) {
+ return;
+ }
+
+ execute_local(command);
+}
+
+pub fn execute_local(command: &BatteryCommands) {
+ match command {
+ BatteryCommands::Limit { percent } => {
let mut success_count = 0;
let sys_class_power = Path::new("/sys/class/power_supply");
@@ -90,47 +93,34 @@ pub fn execute(command: &BatteryCommands) {
}
} else {
// Ideapad fallback
- let mut ideapad_found = false;
- 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");
- if conservation_path.exists() {
- ideapad_found = true;
- 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
- ),
+ 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),
}
}
- if !ideapad_found {
- debug!("limit not supported for {} (missing thresholds)", name_str);
- }
}
}
}
@@ -145,7 +135,6 @@ pub fn execute(command: &BatteryCommands) {
}
}
BatteryCommands::Status => {
- // Re-use logic from `lapctl status` to show battery-specific details:
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() {
@@ -168,21 +157,16 @@ pub fn execute(command: &BatteryCommands) {
println!(" Charge Limit: {}%", limit.trim());
} else {
// Ideapad fallback display
- if let Ok(ideapad_entries) =
- fs::read_dir("/sys/bus/platform/drivers/ideapad_acpi")
+ 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 ideapad_entry in ideapad_entries.flatten() {
- let conservation_path =
- ideapad_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%)");
- } else {
- println!(" Charge Limit: 100%");
- }
- }
+ if mode.trim() == "1" {
+ println!(" Charge Limit: Conservation Mode (~60%)");
+ } else {
+ println!(" Charge Limit: 100%");
}
}
}
diff --git a/src/commands/cooling.rs b/src/commands/cooling.rs
index f75146f..706792f 100644
--- a/src/commands/cooling.rs
+++ b/src/commands/cooling.rs
@@ -15,11 +15,6 @@ trait Lapctl {
}
fn try_call_daemon(command: &CoolingCommands) -> bool {
- if std::env::var("LAPCTL_DAEMON_INTERNAL").is_ok() {
- debug!("Internal daemon call detected. Skipping D-Bus self-call.");
- return false;
- }
-
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return false,
@@ -102,11 +97,21 @@ fn set_asus_throttle_policy(mode: &str) -> bool {
}
pub fn execute(command: &CoolingCommands) {
+ let desc = match command {
+ CoolingCommands::Performance => "Performance",
+ CoolingCommands::Balanced => "Balanced",
+ CoolingCommands::Quiet => "Quiet",
+ };
+ println!("Setting thermal/cooling profile to {}", desc);
+
if try_call_daemon(command) {
- println!("Request handled by lapctld daemon.");
return;
}
+ execute_local(command);
+}
+
+pub fn execute_local(command: &CoolingCommands) {
let (ideapad_mode, asus_mode, desc) = match command {
CoolingCommands::Performance => ("1", "1", "Performance"),
CoolingCommands::Balanced => ("0", "0", "Balanced"),
diff --git a/src/commands/gpu.rs b/src/commands/gpu.rs
index 52622b1..5c9c46f 100644
--- a/src/commands/gpu.rs
+++ b/src/commands/gpu.rs
@@ -40,10 +40,6 @@ trait Lapctl {
}
fn try_call_daemon(cmd: &GpuCommands) -> bool {
- if std::env::var("LAPCTL_DAEMON_INTERNAL").is_ok() {
- return false;
- }
-
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return false,
@@ -748,11 +744,21 @@ fn run_on_dgpu(command: &[String]) {
}
pub fn execute(cmd: &GpuCommands) {
+ match cmd {
+ GpuCommands::Integrated { .. } => println!("Switching to Integrated GPU mode..."),
+ GpuCommands::Hybrid { .. } => println!("Switching to Hybrid GPU mode..."),
+ GpuCommands::Nvidia { .. } => println!("Switching to NVIDIA-only GPU mode..."),
+ _ => {}
+ }
+
if try_call_daemon(cmd) {
- println!("Request handled by lapctld daemon.");
return;
}
+ execute_local(cmd);
+}
+
+pub fn execute_local(cmd: &GpuCommands) {
match cmd {
GpuCommands::Query => {
let mode = get_current_mode();
diff --git a/src/commands/inhibit.rs b/src/commands/inhibit.rs
index 4679cc3..0c53371 100644
--- a/src/commands/inhibit.rs
+++ b/src/commands/inhibit.rs
@@ -18,10 +18,6 @@ trait Lapctl {
}
fn try_call_daemon_inhibit(active: bool, why: String, who: String) -> bool {
- if std::env::var("LAPCTL_DAEMON_INTERNAL").is_ok() {
- return false;
- }
-
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return false,
@@ -62,6 +58,10 @@ pub fn execute(command_args: &[String], why: &str, who: &str, daemon: bool, stop
}
if daemon && command_args.is_empty() {
+ println!(
+ "Attempting to activate system inhibition via lapctld: '{}' by '{}'",
+ why, who
+ );
if try_call_daemon_inhibit(true, why.to_string(), who.to_string()) {
println!("Persistent system inhibition activated via lapctld.");
println!("The system will stay awake until you run: lapctl inhibit --stop");
diff --git a/src/commands/install_rules.rs b/src/commands/install_rules.rs
index 6a14def..d319bd3 100644
--- a/src/commands/install_rules.rs
+++ b/src/commands/install_rules.rs
@@ -20,7 +20,7 @@ const POLKIT_POLICY: &str = r#"
auth_admin
yes
- /usr/bin/lapctl
+ {EXE_PATH}
false
@@ -66,7 +66,7 @@ After=dbus.service
[Service]
Type=simple
-ExecStart=/usr/bin/lapctl daemon
+ExecStart={EXE_PATH} daemon
Restart=on-failure
[Install]
@@ -76,21 +76,27 @@ WantedBy=multi-user.target
pub fn execute() {
assert_root();
- println!("Installing lapctl configuration for daemon communication...");
+ let current_exe = std::env::current_exe().expect("Failed to get current executable path");
+ let exe_path = current_exe.to_str().expect("Invalid executable path");
+
+ println!("Found lapctl at: {}", exe_path);
+ println!("Installing lapctl configuration with dynamic paths...");
let dbus_policy_path = "/etc/dbus-1/system.d/org.lapctl.conf";
create_file(dbus_policy_path, DBUS_POLICY, false);
+ let systemd_content = SYSTEMD_SERVICE.replace("{EXE_PATH}", exe_path);
let service_path = "/etc/systemd/system/lapctld.service";
- create_file(service_path, SYSTEMD_SERVICE, false);
+ create_file(service_path, &systemd_content, false);
// Also install the udev rules file (even if empty now) to avoid errors if it was previously there
let rules_path = "/etc/udev/rules.d/99-lapctl.rules";
create_file(rules_path, LAPCTL_UDEV_RULES, false);
// Polkit fallbacks for rootless execution without the daemon
+ let polkit_content = POLKIT_POLICY.replace("{EXE_PATH}", exe_path);
let polkit_policy_path = "/usr/share/polkit-1/actions/org.lapctl.policy";
- create_file(polkit_policy_path, POLKIT_POLICY, false);
+ create_file(polkit_policy_path, &polkit_content, false);
let polkit_rules_path = "/etc/polkit-1/rules.d/10-lapctl.rules";
create_file(polkit_rules_path, POLKIT_RULES, false);
@@ -115,5 +121,5 @@ pub fn execute() {
}
println!("Installation successful! The lapctld daemon is now running.");
- println!("You can now use all lapctl commands without sudo.");
+ println!("You can now use all lapctl commands from any location.");
}
diff --git a/src/commands/power.rs b/src/commands/power.rs
index c33e265..6365c6b 100644
--- a/src/commands/power.rs
+++ b/src/commands/power.rs
@@ -16,10 +16,6 @@ trait Lapctl {
}
fn try_call_daemon(command: &PowerCommands) -> bool {
- if std::env::var("LAPCTL_DAEMON_INTERNAL").is_ok() {
- return false;
- }
-
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return false,
@@ -226,11 +222,23 @@ fn set_platform_profile(profile: &str) {
}
pub fn execute(command: &PowerCommands) {
+ match command {
+ PowerCommands::Performance => println!("Setting power profile to Performance"),
+ PowerCommands::Balanced => println!("Setting power profile to Balanced"),
+ PowerCommands::BatterySave => println!("Setting power profile to Battery Saver"),
+ PowerCommands::LimitTdp { watts } => {
+ println!("Setting CPU Package TDP Limit to {} Watts...", watts)
+ }
+ }
+
if try_call_daemon(command) {
- println!("Request handled by lapctld daemon.");
return;
}
+ execute_local(command);
+}
+
+pub fn execute_local(command: &PowerCommands) {
match command {
PowerCommands::Performance => {
println!("Setting power profile to Performance");
@@ -242,8 +250,6 @@ 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
- // usually rejects invalid profiles, and standard Intel/AMD systems use EPP now anyway.
set_cpu_governor("schedutil");
set_energy_performance_preference("balance_performance");
println!("Operation completed successfully.");
diff --git a/src/commands/touchpad.rs b/src/commands/touchpad.rs
index 13ac4bd..f45c557 100644
--- a/src/commands/touchpad.rs
+++ b/src/commands/touchpad.rs
@@ -1,5 +1,5 @@
use crate::cli::TouchpadCommands;
-use log::{error, info};
+use log::{debug, error, info};
use std::fs;
use std::path::Path;
use zbus::Connection;
@@ -15,10 +15,6 @@ trait Lapctl {
}
fn try_call_daemon(command: &TouchpadCommands) -> bool {
- if std::env::var("LAPCTL_DAEMON_INTERNAL").is_ok() {
- return false;
- }
-
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return false,
@@ -60,56 +56,64 @@ fn try_call_daemon(command: &TouchpadCommands) -> bool {
}
pub fn execute(command: &TouchpadCommands) {
+ match command {
+ TouchpadCommands::Enable => println!("Enabling touchpad..."),
+ TouchpadCommands::Disable => println!("Disabling touchpad..."),
+ }
+
if try_call_daemon(command) {
- println!("Request handled by lapctld daemon.");
return;
}
- let sys_class_input = Path::new("/sys/class/input");
- if !sys_class_input.exists() {
+ execute_local(command);
+}
+
+pub fn execute_local(command: &TouchpadCommands) {
+ match command {
+ TouchpadCommands::Enable => set_touchpad_inhibition(false),
+ TouchpadCommands::Disable => set_touchpad_inhibition(true),
+ }
+}
+
+fn set_touchpad_inhibition(inhibited: bool) {
+ let dev_input = Path::new("/sys/class/input");
+ if !dev_input.exists() {
error!("Could not find /sys/class/input. Is this a Linux system?");
return;
}
- let mut found_touchpads = Vec::new();
-
- if let Ok(entries) = fs::read_dir(sys_class_input) {
+ if let Ok(entries) = fs::read_dir(dev_input) {
+ let mut found = false;
for entry in entries.flatten() {
- let path = entry.path();
- let name_path = path.join("name");
- let inhibited_path = path.join("inhibited");
+ let name = entry.file_name();
+ let name_str = name.to_string_lossy();
- if name_path.exists()
- && inhibited_path.exists()
- && let Ok(name) = fs::read_to_string(&name_path)
- && name.to_lowercase().contains("touchpad")
- {
- found_touchpads.push((name.trim().to_string(), inhibited_path));
- }
- }
- }
-
- if found_touchpads.is_empty() {
- error!("No touchpad devices found with 'inhibited' support in /sys/class/input.");
- return;
- }
-
- match command {
- TouchpadCommands::Enable => {
- for (name, path) in found_touchpads {
- match fs::write(&path, "0") {
- Ok(_) => info!("Enabled touchpad: {}", name),
- Err(e) => error!("Failed to enable touchpad {}: {}", name, e),
+ if name_str.starts_with("input") {
+ let name_file = entry.path().join("name");
+ if let Ok(content) = fs::read_to_string(name_file) {
+ let content_lower = content.to_lowercase();
+ if content_lower.contains("touchpad") || content_lower.contains("trackpad") {
+ found = true;
+ let inhibited_file = entry.path().join("inhibited");
+ if inhibited_file.exists() {
+ let val = if inhibited { "1" } else { "0" };
+ match fs::write(&inhibited_file, val) {
+ Ok(_) => info!("Set inhibit={} for {}", inhibited, name_str),
+ Err(e) => {
+ debug!("Failed to write to {}: {}", inhibited_file.display(), e)
+ }
+ }
+ }
+ }
}
}
}
- TouchpadCommands::Disable => {
- for (name, path) in found_touchpads {
- match fs::write(&path, "1") {
- Ok(_) => info!("Disabled touchpad: {}", name),
- Err(e) => error!("Failed to disable touchpad {}: {}", name, e),
- }
- }
+ if found {
+ println!("Operation completed successfully.");
+ } else {
+ error!("No touchpad device found in /sys/class/input.");
}
+ } else {
+ error!("Failed to read directory /sys/class/input.");
}
}