12 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
sankalp 01d2e148a0 refactor(src/daemon): replace execute calls with execute_local for dbus commands
Why: Adopt local execution path to streamline command handling and reduce RPC overhead.

New Features
-

Enhancements
- Replace `gpu::execute` with `gpu::execute_local` in integrated, hybrid, and nvidia command invocations.
- Replace `crate::commands::power`, `battery`, `cooling`, and `touchpad` executions with `execute_local`.
- Remove the unused `LAPCTL_DAEMON_INTERNAL` environment variable setting.

Bug Fixes
- None
2026-03-23 16:16:31 +05:45
sankalp 0dc00ec5c9 refactor(src/commands): Refactor battery and touchpad command execution
Refactored command flow to separate daemon invocation, added status handling, and improved error handling.

- New Features: Added `execute_local` helper and `BatteryCommands::Status` handling.
- Enhancements: Simplified daemon checks, removed redundant condition, improved readability and error messages.
- Bug Fixes: Fixed missed daemon return path for status command and ensured proper touchpad inhibition logic.
2026-03-23 16:16:25 +05:45
sankalp 319ca54300 bumped to 0.3.1 2026-03-23 15:40:19 +05:45
sankalp f12134a9c8 feat(src/daemon): set LAPCTL_DAEMON_INTERNAL env var internally
Set an internal environment variable when the daemon starts to allow components to detect internal mode and prepare for future runtime configuration.

- New Features
  - Introduce `LAPCTL_DAEMON_INTERNAL` set to "1" inside the daemon.
- Enhancements
  - Enable downstream components to detect internal daemon execution.
2026-03-23 15:39:08 +05:45
sankalp 86ac9b556c chore(root): remove PKGBUILD and update .gitignore
Removed legacy packaging script and cleaned .gitignore entries.

New Features
-

Enhancements
-

Bug Fixes
-
2026-03-23 15:39:04 +05:45
sankalp c564259a19 refactor(src/commands): add timeouthandling and internal daemon guard
Adds 2‑second timeouts around D‑Bus interactions and guards against internal daemon self‑calls; refactors command handling to use a consistent async timeout pattern and improves error reporting.

New Features
- Wrap D‑Bus calls with a 2‑second timeout to prevent hangs
- Early‑exit when $LAPCTL_DAEMON_INTERNAL is set to skip self‑calls- Apply consistent timeout usage across all commands

Enhancements
- Unified error handling using the `matches!` macro
- Reduced race conditions in connection establishment

Bug Fixes
- Prevent silent failures when a connection timeout occurs
2026-03-23 15:38:57 +05:45
sankalp 46cbcb5b4b update 2026-03-23 15:28:18 +05:45
sankalp 899d4c831f docs(root): Simplify Arch Linux installation instructions
Remove outdated Arch AUR install recommendation and clean up documentation wording

New Features
-

Enhancements
- Updated `yay -S` command to install `lapctl` instead of `lapctl-git`
- Removed redundant dependency and udev rules note
- Adjusted emphasis formatting in `install-rules` description

Bug Fixes
- none
2026-03-22 18:05:36 +05:45
12 changed files with 349 additions and 201 deletions

No files matched your search

+1 -3
View File
@@ -23,6 +23,4 @@ target
# Added by cargo
/target
./lapctl.py
lapctl.py
/target
Generated
+1 -1
View File
@@ -596,7 +596,7 @@ dependencies = [
[[package]]
name = "lapctl"
version = "0.2.0"
version = "0.3.2"
dependencies = [
"clap",
"env_logger",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "lapctl"
version = "0.2.0"
version = "0.3.2"
edition = "2024"
[dependencies]
+2 -4
View File
@@ -35,10 +35,8 @@ Built with performance and simplicity in mind, it talks directly to your system'
#### Option 1: Arch Linux (AUR)
If you are on Arch Linux, you can install **lapctl-git** from the AUR. This is the recommended method for Arch users as it automatically handles dependencies and udev rules.
```bash
yay -S lapctl-git
yay -S lapctl
```
#### Option 2: Using `just` (Recommended for other distros)
@@ -50,7 +48,7 @@ just install
sudo lapctl install-rules
```
*The `install-rules` command automatically sets up the D-Bus policy and starts the `lapctld` background service.*
_The `install-rules` command automatically sets up the D-Bus policy and starts the `lapctld` background service._
#### Option 3: Using `cargo`
+99 -74
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,
@@ -21,30 +25,35 @@ fn try_call_daemon(command: &BatteryCommands) -> bool {
};
rt.block_on(async {
let connection = match Connection::system().await {
Ok(conn) => conn,
Err(_) => return false,
};
let connection =
match tokio::time::timeout(std::time::Duration::from_secs(2), Connection::system())
.await
{
Ok(Ok(conn)) => conn,
_ => return false,
};
let proxy = match LapctlProxy::new(&connection).await {
Ok(p) => p,
Err(_) => return false,
};
match command {
let res = match command {
BatteryCommands::Limit { percent } => {
proxy.set_battery_limit(*percent as u32).await.is_ok()
tokio::time::timeout(
std::time::Duration::from_secs(2),
proxy.set_battery_limit(*percent as u32),
)
.await
}
_ => false,
}
_ => return false,
};
matches!(res, Ok(Ok(_)))
})
}
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) {
@@ -52,9 +61,51 @@ pub fn execute(command: &BatteryCommands) {
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 => {}
}
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;
// 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) {
@@ -65,6 +116,10 @@ pub fn execute(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);
@@ -74,49 +129,6 @@ pub fn execute(command: &BatteryCommands) {
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")
{
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
),
}
}
}
}
if !ideapad_found {
debug!("limit not supported for {} (missing thresholds)", name_str);
}
}
}
}
@@ -131,7 +143,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() {
@@ -148,25 +159,39 @@ pub fn execute(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
if let Ok(ideapad_entries) =
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) =
fs::read_to_string(bat_path.join("charge_control_end_threshold"))
{
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: {}%", 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;
}
}
}
+42 -14
View File
@@ -21,26 +21,44 @@ fn try_call_daemon(command: &CoolingCommands) -> bool {
};
rt.block_on(async {
let connection = match Connection::system().await {
Ok(conn) => conn,
Err(_) => return false,
};
let connection =
match tokio::time::timeout(std::time::Duration::from_secs(2), Connection::system())
.await
{
Ok(Ok(conn)) => conn,
_ => return false,
};
let proxy = match LapctlProxy::new(&connection).await {
Ok(p) => p,
Err(_) => return false,
};
match command {
CoolingCommands::Performance => proxy
.set_cooling_profile("performance".to_string())
let res = match command {
CoolingCommands::Performance => {
tokio::time::timeout(
std::time::Duration::from_secs(2),
proxy.set_cooling_profile("performance".to_string()),
)
.await
.is_ok(),
CoolingCommands::Balanced => proxy
.set_cooling_profile("balanced".to_string())
}
CoolingCommands::Balanced => {
tokio::time::timeout(
std::time::Duration::from_secs(2),
proxy.set_cooling_profile("balanced".to_string()),
)
.await
.is_ok(),
CoolingCommands::Quiet => proxy.set_cooling_profile("quiet".to_string()).await.is_ok(),
}
}
CoolingCommands::Quiet => {
tokio::time::timeout(
std::time::Duration::from_secs(2),
proxy.set_cooling_profile("quiet".to_string()),
)
.await
}
};
matches!(res, Ok(Ok(_)))
})
}
@@ -79,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"),
+40 -18
View File
@@ -46,18 +46,26 @@ fn try_call_daemon(cmd: &GpuCommands) -> bool {
};
rt.block_on(async {
let connection = match Connection::system().await {
Ok(conn) => conn,
Err(_) => return false,
};
let connection =
match tokio::time::timeout(std::time::Duration::from_secs(2), Connection::system())
.await
{
Ok(Ok(conn)) => conn,
_ => return false,
};
let proxy = match LapctlProxy::new(&connection).await {
Ok(p) => p,
Err(_) => return false,
};
match cmd {
let res = match cmd {
GpuCommands::Integrated { no_reboot } => {
proxy.switch_gpu_integrated(*no_reboot).await.is_ok()
tokio::time::timeout(
std::time::Duration::from_secs(5),
proxy.switch_gpu_integrated(*no_reboot),
)
.await
}
GpuCommands::Hybrid {
rtd3,
@@ -65,10 +73,11 @@ fn try_call_daemon(cmd: &GpuCommands) -> bool {
no_reboot,
} => {
let rtd3_val = rtd3.map(|v| v as i32).unwrap_or(-1);
proxy
.switch_gpu_hybrid(rtd3_val, *use_nvidia_current, *no_reboot)
.await
.is_ok()
tokio::time::timeout(
std::time::Duration::from_secs(5),
proxy.switch_gpu_hybrid(rtd3_val, *use_nvidia_current, *no_reboot),
)
.await
}
GpuCommands::Nvidia {
dm,
@@ -80,20 +89,23 @@ fn try_call_daemon(cmd: &GpuCommands) -> bool {
} => {
let dm_val = dm.clone().unwrap_or_default();
let coolbits_val = coolbits.map(|v| v as i32).unwrap_or(-1);
proxy
.switch_gpu_nvidia(
tokio::time::timeout(
std::time::Duration::from_secs(5),
proxy.switch_gpu_nvidia(
dm_val,
*force_comp,
coolbits_val,
*use_nvidia_current,
*wayland,
*no_reboot,
)
.await
.is_ok()
),
)
.await
}
_ => false,
}
_ => return false,
};
matches!(res, Ok(Ok(_)))
})
}
@@ -732,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();
+19 -5
View File
@@ -24,16 +24,26 @@ fn try_call_daemon_inhibit(active: bool, why: String, who: String) -> bool {
};
rt.block_on(async {
let connection = match Connection::system().await {
Ok(conn) => conn,
Err(_) => return false,
};
let connection =
match tokio::time::timeout(std::time::Duration::from_secs(2), Connection::system())
.await
{
Ok(Ok(conn)) => conn,
_ => return false,
};
let proxy = match LapctlProxy::new(&connection).await {
Ok(p) => p,
Err(_) => return false,
};
proxy.set_system_inhibition(active, why, who).await.is_ok()
let res = tokio::time::timeout(
std::time::Duration::from_secs(2),
proxy.set_system_inhibition(active, why, who),
)
.await;
matches!(res, Ok(Ok(_)))
})
}
@@ -48,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");
+14 -8
View File
@@ -12,7 +12,7 @@ const POLKIT_POLICY: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<policyconfig>
<vendor>lapctl</vendor>
<vendor_url>https://github.com/S4NKALP/lapctl</vendor_url>
<action id="org.s4nkalp.lapctl.execute">
<action id="org.lapctl.execute">
<description>Elevated execution for lapctl</description>
<message>Authentication is required to perform privileged lapctl operations</message>
<defaults>
@@ -20,7 +20,7 @@ const POLKIT_POLICY: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/bin/lapctl</annotate>
<annotate key="org.freedesktop.policykit.exec.path">{EXE_PATH}</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">false</annotate>
</action>
</policyconfig>
@@ -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_policy_path = "/usr/share/polkit-1/actions/org.s4nkalp.lapctl.policy";
create_file(polkit_policy_path, POLKIT_POLICY, false);
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_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.");
}
+50 -19
View File
@@ -22,30 +22,51 @@ fn try_call_daemon(command: &PowerCommands) -> bool {
};
rt.block_on(async {
let connection = match Connection::system().await {
Ok(conn) => conn,
Err(_) => return false,
};
let connection =
match tokio::time::timeout(std::time::Duration::from_secs(2), Connection::system())
.await
{
Ok(Ok(conn)) => conn,
_ => return false,
};
let proxy = match LapctlProxy::new(&connection).await {
Ok(p) => p,
Err(_) => return false,
};
match command {
PowerCommands::Performance => proxy
.set_power_profile("performance".to_string())
let res = match command {
PowerCommands::Performance => {
tokio::time::timeout(
std::time::Duration::from_secs(2),
proxy.set_power_profile("performance".to_string()),
)
.await
.is_ok(),
PowerCommands::Balanced => proxy
.set_power_profile("balanced".to_string())
}
PowerCommands::Balanced => {
tokio::time::timeout(
std::time::Duration::from_secs(2),
proxy.set_power_profile("balanced".to_string()),
)
.await
.is_ok(),
PowerCommands::BatterySave => proxy
.set_power_profile("battery-save".to_string())
}
PowerCommands::BatterySave => {
tokio::time::timeout(
std::time::Duration::from_secs(2),
proxy.set_power_profile("battery-save".to_string()),
)
.await
.is_ok(),
PowerCommands::LimitTdp { watts } => proxy.set_tdp_limit(*watts).await.is_ok(),
}
}
PowerCommands::LimitTdp { watts } => {
tokio::time::timeout(
std::time::Duration::from_secs(2),
proxy.set_tdp_limit(*watts),
)
.await
}
};
matches!(res, Ok(Ok(_)))
})
}
@@ -201,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");
@@ -217,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.");
+72 -46
View File
@@ -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;
@@ -21,73 +21,99 @@ fn try_call_daemon(command: &TouchpadCommands) -> bool {
};
rt.block_on(async {
let connection = match Connection::system().await {
Ok(conn) => conn,
Err(_) => return false,
};
let connection =
match tokio::time::timeout(std::time::Duration::from_secs(2), Connection::system())
.await
{
Ok(Ok(conn)) => conn,
_ => return false,
};
let proxy = match LapctlProxy::new(&connection).await {
Ok(p) => p,
Err(_) => return false,
};
match command {
TouchpadCommands::Enable => proxy.set_touchpad_inhibition(false).await.is_ok(),
TouchpadCommands::Disable => proxy.set_touchpad_inhibition(true).await.is_ok(),
}
let res = match command {
TouchpadCommands::Enable => {
tokio::time::timeout(
std::time::Duration::from_secs(2),
proxy.set_touchpad_inhibition(false),
)
.await
}
TouchpadCommands::Disable => {
tokio::time::timeout(
std::time::Duration::from_secs(2),
proxy.set_touchpad_inhibition(true),
)
.await
}
};
matches!(res, Ok(Ok(_)))
})
}
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.");
}
}
+8 -8
View File
@@ -20,7 +20,7 @@ impl Default for LapctlInterface {
#[interface(name = "org.lapctl1")]
impl LapctlInterface {
pub async fn switch_gpu_integrated(&self, no_reboot: bool) -> zbus::fdo::Result<()> {
gpu::execute(&GpuCommands::Integrated { no_reboot });
gpu::execute_local(&GpuCommands::Integrated { no_reboot });
Ok(())
}
@@ -31,7 +31,7 @@ impl LapctlInterface {
no_reboot: bool,
) -> zbus::fdo::Result<()> {
let rtd3_opt = if rtd3 < 0 { None } else { Some(rtd3 as u8) };
gpu::execute(&GpuCommands::Hybrid {
gpu::execute_local(&GpuCommands::Hybrid {
rtd3: rtd3_opt,
use_nvidia_current,
no_reboot,
@@ -54,7 +54,7 @@ impl LapctlInterface {
} else {
Some(coolbits as u32)
};
gpu::execute(&GpuCommands::Nvidia {
gpu::execute_local(&GpuCommands::Nvidia {
dm: dm_opt,
force_comp,
coolbits: coolbits_opt,
@@ -66,7 +66,7 @@ impl LapctlInterface {
}
pub async fn set_battery_limit(&self, percent: u32) -> zbus::fdo::Result<()> {
crate::commands::battery::execute(&crate::cli::BatteryCommands::Limit {
crate::commands::battery::execute_local(&crate::cli::BatteryCommands::Limit {
percent: percent as u8,
});
Ok(())
@@ -84,12 +84,12 @@ impl LapctlInterface {
)));
}
};
crate::commands::power::execute(&cmd);
crate::commands::power::execute_local(&cmd);
Ok(())
}
pub async fn set_tdp_limit(&self, watts: u32) -> zbus::fdo::Result<()> {
crate::commands::power::execute(&crate::cli::PowerCommands::LimitTdp { watts });
crate::commands::power::execute_local(&crate::cli::PowerCommands::LimitTdp { watts });
Ok(())
}
@@ -105,7 +105,7 @@ impl LapctlInterface {
)));
}
};
crate::commands::cooling::execute(&cmd);
crate::commands::cooling::execute_local(&cmd);
Ok(())
}
@@ -115,7 +115,7 @@ impl LapctlInterface {
} else {
crate::cli::TouchpadCommands::Enable
};
crate::commands::touchpad::execute(&cmd);
crate::commands::touchpad::execute_local(&cmd);
Ok(())
}