Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d12d0a0916 | ||
|
|
30dadec637 | ||
|
|
dba3995608 | ||
|
|
c8a688f44f | ||
|
|
7de76d0365 | ||
|
|
055fdcd500 | ||
|
|
934aeda6aa | ||
|
|
8947bd643a | ||
|
|
5ac2aed516 | ||
|
|
4020f65e28 | ||
|
|
d52b3992e3 | ||
|
|
01d2e148a0 | ||
|
|
0dc00ec5c9 | ||
|
|
319ca54300 | ||
|
|
f12134a9c8 | ||
|
|
86ac9b556c | ||
|
|
c564259a19 | ||
|
|
46cbcb5b4b | ||
|
|
899d4c831f |
No files matched your search
+1
-3
@@ -23,6 +23,4 @@ target
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
./lapctl.py
|
||||
lapctl.py
|
||||
/target
|
||||
Generated
+1
-1
@@ -596,7 +596,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lapctl"
|
||||
version = "0.2.0"
|
||||
version = "0.3.3"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lapctl"
|
||||
version = "0.2.0"
|
||||
version = "0.3.3"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -24,7 +24,6 @@ Built with performance and simplicity in mind, it talks directly to your system'
|
||||
- **Intelligent Cooling**: Force your fans into Performance, Balanced, or Quiet modes (supporting ASUS and Lenovo laptops).
|
||||
- **Display Refresh Rate**: Easily query available refresh rates and change your active display's Hz on-the-fly (100% native Rust Wayland implementation using `zwlr_output_manager_v1` for wlroots compositors like Sway and Hyprland).
|
||||
- **Touchpad Toggle**: Quickly enable or disable your touchpad from the terminal when using an external mouse.
|
||||
- **Sleep Inhibitor**: Running a long compile or a critical download? Use the inhibitor to stop your laptop from falling asleep mid task.
|
||||
- **Instant Status**: Get a bird's eye view of your hardware state, battery health, and current limits with one simple command.
|
||||
|
||||
---
|
||||
@@ -35,10 +34,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 +47,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`
|
||||
|
||||
@@ -61,7 +58,6 @@ sudo lapctl install-rules
|
||||
|
||||
#### Requirements
|
||||
|
||||
- **systemd**: For sleep inhibitor (`systemd-inhibit`)
|
||||
- **GPU Switching (Optional)**: `xrandr` and `nvidia-settings` are strictly required **ONLY** when using the `lapctl gpu` command on X11 (to route proprietary NVIDIA Optimus drivers).
|
||||
- **Wayland Display**: Built entirely natively using `wayland-client` and `wayland-protocols-wlr` (no `wlr-randr` required!)
|
||||
|
||||
@@ -106,11 +102,6 @@ lapctl touchpad enable
|
||||
lapctl display rates
|
||||
lapctl display set-rate 144
|
||||
|
||||
# Keep it awake (requires lapctld)
|
||||
lapctl inhibit --daemon # Stay awake persistently (via daemon)
|
||||
lapctl inhibit --stop # Allow sleep again
|
||||
lapctl inhibit --why "Keeping awake" -- sleep 60 # One-shot (local)
|
||||
|
||||
# Check everything
|
||||
lapctl status
|
||||
```
|
||||
|
||||
-17
@@ -51,23 +51,6 @@ pub enum Commands {
|
||||
#[command(subcommand)]
|
||||
command: TouchpadCommands,
|
||||
},
|
||||
/// Inhibit system sleep/suspend
|
||||
Inhibit {
|
||||
/// The command to run while inhibiting (optional)
|
||||
command: Vec<String>,
|
||||
/// Why the system is being inhibited
|
||||
#[arg(long, default_value = "lapctl inhibiting sleep")]
|
||||
why: String,
|
||||
/// Who is inhibiting the system
|
||||
#[arg(long, default_value = "lapctl")]
|
||||
who: String,
|
||||
/// Run the inhibitor in the background (daemon mode)
|
||||
#[arg(long)]
|
||||
daemon: bool,
|
||||
/// Stop any active persistent inhibition managed by the daemon
|
||||
#[arg(long)]
|
||||
stop: bool,
|
||||
},
|
||||
/// Start the lapctl background daemon (requires root)
|
||||
Daemon,
|
||||
}
|
||||
|
||||
+99
-74
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
use log::{error, info};
|
||||
use std::process::Command;
|
||||
use zbus::Connection;
|
||||
use zbus::proxy;
|
||||
|
||||
#[proxy(
|
||||
interface = "org.lapctl1",
|
||||
default_service = "org.lapctl",
|
||||
default_path = "/org/lapctl"
|
||||
)]
|
||||
trait Lapctl {
|
||||
async fn set_system_inhibition(
|
||||
&self,
|
||||
active: bool,
|
||||
why: String,
|
||||
who: String,
|
||||
) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
fn try_call_daemon_inhibit(active: bool, why: String, who: String) -> bool {
|
||||
let rt = match tokio::runtime::Runtime::new() {
|
||||
Ok(rt) => rt,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
rt.block_on(async {
|
||||
let connection = match Connection::system().await {
|
||||
Ok(conn) => conn,
|
||||
Err(_) => 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()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn execute(command_args: &[String], why: &str, who: &str, daemon: bool, stop: bool) {
|
||||
if stop {
|
||||
if try_call_daemon_inhibit(false, why.to_string(), who.to_string()) {
|
||||
println!("Persistent system inhibition deactivated via lapctld.");
|
||||
} else {
|
||||
error!("Failed to deactivate inhibition via daemon. Is lapctld running?");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if daemon && command_args.is_empty() {
|
||||
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");
|
||||
return;
|
||||
} else {
|
||||
error!("Failed to activate inhibition via daemon. Is lapctld running?");
|
||||
// Fallback to local systemd-inhibit if daemon call fails
|
||||
}
|
||||
}
|
||||
|
||||
let args = build_args(command_args, why, who);
|
||||
|
||||
if command_args.is_empty() {
|
||||
if !daemon {
|
||||
info!("No command provided. Inhibiting sleep indefinitely. Press Ctrl+C to stop.");
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
"Running command while inhibiting sleep: {}",
|
||||
command_args.join(" ")
|
||||
);
|
||||
}
|
||||
|
||||
if daemon {
|
||||
match Command::new("systemd-inhibit").args(&args).spawn() {
|
||||
Ok(child) => {
|
||||
info!("Inhibitor started in background with PID: {}", child.id());
|
||||
info!("To stop it, use: pkill -P {} systemd-inhibit", child.id());
|
||||
// In background mode, we just let the child run and exit lapctl
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to spawn systemd-inhibit: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match Command::new("systemd-inhibit").args(&args).status() {
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
error!("systemd-inhibit exited with status: {}", status);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to execute systemd-inhibit: {}", e);
|
||||
eprintln!(
|
||||
"Error: make sure 'systemd-inhibit' is installed and available in your PATH."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_args(command_args: &[String], why: &str, who: &str) -> Vec<String> {
|
||||
let mut args = vec![
|
||||
"--why".to_string(),
|
||||
why.to_string(),
|
||||
"--who".to_string(),
|
||||
who.to_string(),
|
||||
"--what".to_string(),
|
||||
"sleep:idle".to_string(),
|
||||
];
|
||||
|
||||
if command_args.is_empty() {
|
||||
args.push("sleep".to_string());
|
||||
args.push("infinity".to_string());
|
||||
} else {
|
||||
args.extend(command_args.iter().cloned());
|
||||
}
|
||||
args
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_build_args_no_command() {
|
||||
let args = build_args(&[], "testing", "lapctl");
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![
|
||||
"--why",
|
||||
"testing",
|
||||
"--who",
|
||||
"lapctl",
|
||||
"--what",
|
||||
"sleep:idle",
|
||||
"sleep",
|
||||
"infinity"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_args_with_command() {
|
||||
let command_args = vec!["ls".to_string(), "-l".to_string()];
|
||||
let args = build_args(&command_args, "testing", "lapctl");
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![
|
||||
"--why",
|
||||
"testing",
|
||||
"--who",
|
||||
"lapctl",
|
||||
"--what",
|
||||
"sleep:idle",
|
||||
"ls",
|
||||
"-l"
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
@@ -2,7 +2,6 @@ pub mod battery;
|
||||
pub mod cooling;
|
||||
pub mod display;
|
||||
pub mod gpu;
|
||||
pub mod inhibit;
|
||||
pub mod install_rules;
|
||||
pub mod power;
|
||||
pub mod status;
|
||||
|
||||
+50
-19
@@ -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
@@ -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.");
|
||||
}
|
||||
}
|
||||
+10
-65
@@ -1,26 +1,14 @@
|
||||
use crate::cli::GpuCommands;
|
||||
use crate::commands::gpu;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use zbus::interface;
|
||||
use zbus::zvariant::OwnedFd;
|
||||
|
||||
pub struct LapctlInterface {
|
||||
inhibit_fd: Arc<Mutex<Option<OwnedFd>>>,
|
||||
}
|
||||
|
||||
impl Default for LapctlInterface {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inhibit_fd: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct 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 +19,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 +42,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 +54,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 +72,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 +93,7 @@ impl LapctlInterface {
|
||||
)));
|
||||
}
|
||||
};
|
||||
crate::commands::cooling::execute(&cmd);
|
||||
crate::commands::cooling::execute_local(&cmd);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -115,50 +103,7 @@ impl LapctlInterface {
|
||||
} else {
|
||||
crate::cli::TouchpadCommands::Enable
|
||||
};
|
||||
crate::commands::touchpad::execute(&cmd);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_system_inhibition(
|
||||
&self,
|
||||
active: bool,
|
||||
why: String,
|
||||
who: String,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let mut fd_lock = self.inhibit_fd.lock().await;
|
||||
|
||||
if active {
|
||||
if fd_lock.is_some() {
|
||||
return Ok(()); // Already inhibiting
|
||||
}
|
||||
|
||||
let conn = zbus::Connection::system().await.map_err(|e| {
|
||||
zbus::fdo::Error::Failed(format!("Failed to connect to system bus: {}", e))
|
||||
})?;
|
||||
|
||||
let msg = conn
|
||||
.call_method(
|
||||
Some("org.freedesktop.login1"),
|
||||
"/org/freedesktop/login1",
|
||||
Some("org.freedesktop.login1.Manager"),
|
||||
"Inhibit",
|
||||
&("sleep:idle", who, why, "block"),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| zbus::fdo::Error::Failed(format!("Failed to inhibit: {}", e)))?;
|
||||
|
||||
let fd: OwnedFd = msg
|
||||
.body()
|
||||
.deserialize()
|
||||
.map_err(|e| zbus::fdo::Error::Failed(format!("Failed to parse FD: {}", e)))?;
|
||||
|
||||
*fd_lock = Some(fd);
|
||||
log::info!("System inhibition activated via lapctld.");
|
||||
} else {
|
||||
*fd_lock = None;
|
||||
log::info!("System inhibition deactivated via lapctld.");
|
||||
}
|
||||
|
||||
crate::commands::touchpad::execute_local(&cmd);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -30,13 +30,6 @@ fn main() {
|
||||
Commands::Status => commands::status::execute(),
|
||||
Commands::InstallRules => commands::install_rules::execute(),
|
||||
Commands::Touchpad { command } => commands::touchpad::execute(command),
|
||||
Commands::Inhibit {
|
||||
command,
|
||||
why,
|
||||
who,
|
||||
daemon,
|
||||
stop,
|
||||
} => commands::inhibit::execute(command, why, who, *daemon, *stop),
|
||||
Commands::Daemon => {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
|
||||
@@ -148,20 +148,6 @@ pub fn rebuild_initramfs() {
|
||||
command = vec!["mkinitcpio", "-P"];
|
||||
}
|
||||
|
||||
if let Ok(which) = Command::new("which").arg("systemd-inhibit").output()
|
||||
&& which.status.success()
|
||||
{
|
||||
let mut new_cmd = vec![
|
||||
"systemd-inhibit",
|
||||
"--who=lapctl",
|
||||
"--why",
|
||||
"Rebuilding initramfs",
|
||||
"--",
|
||||
];
|
||||
new_cmd.extend(command.iter());
|
||||
command = new_cmd;
|
||||
}
|
||||
|
||||
if !command.is_empty() {
|
||||
println!("Rebuilding the initramfs...");
|
||||
let is_debug = log::log_enabled!(log::Level::Debug);
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
use clap::Parser;
|
||||
use lapctl::cli::{Cli, Commands};
|
||||
|
||||
#[test]
|
||||
fn test_inhibit_cli_parsing() {
|
||||
let args = vec!["lapctl", "inhibit", "sleep", "10", "--why", "testing"];
|
||||
let cli = Cli::parse_from(args);
|
||||
|
||||
if let Commands::Inhibit {
|
||||
command,
|
||||
why,
|
||||
who,
|
||||
daemon,
|
||||
stop: _,
|
||||
} = cli.command
|
||||
{
|
||||
assert_eq!(command, vec!["sleep".to_string(), "10".to_string()]);
|
||||
assert_eq!(why, "testing");
|
||||
assert_eq!(who, "lapctl"); // default value
|
||||
assert!(!daemon);
|
||||
} else {
|
||||
panic!("Command should be Inhibit");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inhibit_daemon_parsing() {
|
||||
let args = vec!["lapctl", "inhibit", "--daemon"];
|
||||
let cli = Cli::parse_from(args);
|
||||
|
||||
if let Commands::Inhibit {
|
||||
command,
|
||||
why,
|
||||
who: _,
|
||||
daemon,
|
||||
stop: _,
|
||||
} = cli.command
|
||||
{
|
||||
assert!(command.is_empty());
|
||||
assert_eq!(why, "lapctl inhibiting sleep"); // default value
|
||||
assert!(daemon);
|
||||
} else {
|
||||
panic!("Command should be Inhibit");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user