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.
833 lines
26 KiB
Rust
833 lines
26 KiB
Rust
use crate::cli::GpuCommands;
|
|
use crate::hardware::gpu::{
|
|
get_amd_igpu_name, get_current_mode, get_igpu_vendor, get_nvidia_gpu_pci_addr,
|
|
get_nvidia_gpu_pci_bus, kill_gpu_processes, remove_gpu, rescan_pci, unbind_gpu,
|
|
};
|
|
use crate::utils::system::{
|
|
assert_root, create_file, get_active_graphical_sessions, get_display_manager,
|
|
is_service_active, manage_service, rebuild_initramfs, terminate_session,
|
|
};
|
|
use log::{error, info};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::fs;
|
|
use std::path::Path;
|
|
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 switch_gpu_integrated(&self, no_reboot: bool) -> zbus::Result<()>;
|
|
async fn switch_gpu_hybrid(
|
|
&self,
|
|
rtd3: i32,
|
|
use_nvidia_current: bool,
|
|
no_reboot: bool,
|
|
) -> zbus::Result<()>;
|
|
async fn switch_gpu_nvidia(
|
|
&self,
|
|
dm: String,
|
|
force_comp: bool,
|
|
coolbits: i32,
|
|
use_nvidia_current: bool,
|
|
wayland: bool,
|
|
no_reboot: bool,
|
|
) -> zbus::Result<()>;
|
|
}
|
|
|
|
fn try_call_daemon(cmd: &GpuCommands) -> bool {
|
|
let rt = match tokio::runtime::Runtime::new() {
|
|
Ok(rt) => rt,
|
|
Err(_) => return false,
|
|
};
|
|
|
|
rt.block_on(async {
|
|
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,
|
|
};
|
|
|
|
let res = match cmd {
|
|
GpuCommands::Integrated { no_reboot } => {
|
|
tokio::time::timeout(
|
|
std::time::Duration::from_secs(5),
|
|
proxy.switch_gpu_integrated(*no_reboot),
|
|
)
|
|
.await
|
|
}
|
|
GpuCommands::Hybrid {
|
|
rtd3,
|
|
use_nvidia_current,
|
|
no_reboot,
|
|
} => {
|
|
let rtd3_val = rtd3.map(|v| v as i32).unwrap_or(-1);
|
|
tokio::time::timeout(
|
|
std::time::Duration::from_secs(5),
|
|
proxy.switch_gpu_hybrid(rtd3_val, *use_nvidia_current, *no_reboot),
|
|
)
|
|
.await
|
|
}
|
|
GpuCommands::Nvidia {
|
|
dm,
|
|
force_comp,
|
|
coolbits,
|
|
use_nvidia_current,
|
|
wayland,
|
|
no_reboot,
|
|
} => {
|
|
let dm_val = dm.clone().unwrap_or_default();
|
|
let coolbits_val = coolbits.map(|v| v as i32).unwrap_or(-1);
|
|
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
|
|
}
|
|
_ => return false,
|
|
};
|
|
|
|
matches!(res, Ok(Ok(_)))
|
|
})
|
|
}
|
|
|
|
const CACHE_FILE_PATH: &str = "/var/cache/lapctl/cache.json";
|
|
const BLACKLIST_PATH: &str = "/etc/modprobe.d/blacklist-nvidia.conf";
|
|
const BLACKLIST_CONTENT: &str = r#"# Automatically generated by lapctl
|
|
blacklist nouveau
|
|
blacklist nova_core
|
|
blacklist nova_drm
|
|
blacklist nvidia
|
|
blacklist nvidia_drm
|
|
blacklist nvidia_uvm
|
|
blacklist nvidia_modeset
|
|
blacklist nvidia_current
|
|
blacklist nvidia_current_drm
|
|
blacklist nvidia_current_uvm
|
|
blacklist nvidia_current_modeset
|
|
blacklist i2c_nvidia_gpu
|
|
alias nouveau off
|
|
alias nova_core off
|
|
alias nova_drm off
|
|
alias nvidia off
|
|
alias nvidia_drm off
|
|
alias nvidia_uvm off
|
|
alias nvidia_modeset off
|
|
alias nvidia_current off
|
|
alias nvidia_current_drm off
|
|
alias nvidia_current_uvm off
|
|
alias nvidia_current_modeset off
|
|
alias i2c_nvidia_gpu off
|
|
"#;
|
|
|
|
const UDEV_INTEGRATED_PATH: &str = "/etc/udev/rules.d/50-remove-nvidia.rules";
|
|
const UDEV_INTEGRATED: &str = r#"# Automatically generated by lapctl
|
|
|
|
# Remove NVIDIA USB xHCI Host Controller devices, if present
|
|
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c0330", ATTR{power/control}="auto", ATTR{remove}="1"
|
|
|
|
# Remove NVIDIA USB Type-C UCSI devices, if present
|
|
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c8000", ATTR{power/control}="auto", ATTR{remove}="1"
|
|
|
|
# Remove NVIDIA Audio devices, if present
|
|
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x040300", ATTR{power/control}="auto", ATTR{remove}="1"
|
|
|
|
# Remove NVIDIA VGA/3D controller devices
|
|
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x03[0-9]*", ATTR{power/control}="auto", ATTR{remove}="1"
|
|
"#;
|
|
|
|
const UDEV_PM_PATH: &str = "/etc/udev/rules.d/80-nvidia-pm.rules";
|
|
const UDEV_PM_CONTENT: &str = r#"# Automatically generated by lapctl
|
|
|
|
# Remove NVIDIA USB xHCI Host Controller devices, if present
|
|
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c0330", ATTR{remove}="1"
|
|
|
|
# Remove NVIDIA USB Type-C UCSI devices, if present
|
|
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c8000", ATTR{remove}="1"
|
|
|
|
# Remove NVIDIA Audio devices, if present
|
|
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x040300", ATTR{remove}="1"
|
|
|
|
# Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind
|
|
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto"
|
|
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto"
|
|
|
|
# Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind
|
|
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on"
|
|
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on"
|
|
"#;
|
|
|
|
const XORG_PATH: &str = "/etc/X11/xorg.conf";
|
|
const EXTRA_XORG_PATH: &str = "/etc/X11/xorg.conf.d/10-nvidia.conf";
|
|
const MODESET_PATH: &str = "/etc/modprobe.d/nvidia.conf";
|
|
const SDDM_XSETUP_PATH: &str = "/usr/share/sddm/scripts/Xsetup";
|
|
const LIGHTDM_SCRIPT_PATH: &str = "/etc/lightdm/nvidia.sh";
|
|
const LIGHTDM_CONFIG_PATH: &str = "/etc/lightdm/lightdm.conf.d/20-nvidia.conf";
|
|
|
|
const SDDM_XSETUP_CONTENT: &str = r#"#!/bin/sh
|
|
# Xsetup - run as root before the login dialog appears
|
|
|
|
"#;
|
|
|
|
const EXTRA_XORG_CONTENT: &str = r#"# Automatically generated by lapctl
|
|
|
|
Section "OutputClass"
|
|
Identifier "nvidia"
|
|
MatchDriver "nvidia-drm"
|
|
Driver "nvidia"
|
|
"#;
|
|
|
|
const LIGHTDM_CONFIG_CONTENT: &str = r#"# Automatically generated by lapctl
|
|
|
|
[Seat:*]
|
|
display-setup-script=/etc/lightdm/nvidia.sh
|
|
"#;
|
|
|
|
const NVIDIA_XRANDR_SCRIPT: &str = r#"#!/bin/sh
|
|
# Automatically generated by lapctl
|
|
|
|
current=""
|
|
|
|
xrandr --setprovideroutputsource "{}" NVIDIA-0
|
|
xrandr --auto
|
|
|
|
for next in $(xrandr --listmonitors | grep -E " *[0-9]+:.*" | cut -d" " -f6); do
|
|
[ -z "$current" ] && current=$next && continue
|
|
xrandr --output "$current" --auto --output "$next" --auto --right-of "$current"
|
|
current=$next
|
|
done
|
|
"#;
|
|
|
|
// Helpers
|
|
pub fn xorg_intel(bus_id: &str) -> String {
|
|
format!(
|
|
r#"# Automatically generated by lapctl
|
|
|
|
Section "ServerLayout"
|
|
Identifier "layout"
|
|
Screen 0 "nvidia"
|
|
Inactive "intel"
|
|
EndSection
|
|
|
|
Section "Device"
|
|
Identifier "nvidia"
|
|
Driver "nvidia"
|
|
BusID "{}"
|
|
EndSection
|
|
|
|
Section "Screen"
|
|
Identifier "nvidia"
|
|
Device "nvidia"
|
|
Option "AllowEmptyInitialConfiguration"
|
|
EndSection
|
|
|
|
Section "Device"
|
|
Identifier "intel"
|
|
Driver "modesetting"
|
|
EndSection
|
|
|
|
Section "Screen"
|
|
Identifier "intel"
|
|
Device "intel"
|
|
EndSection
|
|
"#,
|
|
bus_id
|
|
)
|
|
}
|
|
|
|
pub fn xorg_amd(bus_id: &str) -> String {
|
|
format!(
|
|
r#"# Automatically generated by lapctl
|
|
|
|
Section "ServerLayout"
|
|
Identifier "layout"
|
|
Screen 0 "nvidia"
|
|
Inactive "amdgpu"
|
|
EndSection
|
|
|
|
Section "Device"
|
|
Identifier "nvidia"
|
|
Driver "nvidia"
|
|
BusID "{}"
|
|
EndSection
|
|
|
|
Section "Screen"
|
|
Identifier "nvidia"
|
|
Device "nvidia"
|
|
Option "AllowEmptyInitialConfiguration"
|
|
EndSection
|
|
|
|
Section "Device"
|
|
Identifier "amdgpu"
|
|
Driver "amdgpu"
|
|
EndSection
|
|
|
|
Section "Screen"
|
|
Identifier "amd"
|
|
Device "amdgpu"
|
|
EndSection
|
|
"#,
|
|
bus_id
|
|
)
|
|
}
|
|
|
|
pub fn generate_xrandr_script(igpu_vendor: Option<&String>) -> String {
|
|
let source_name = match igpu_vendor.map(|s| s.as_str()) {
|
|
Some("intel") => "modesetting".to_string(),
|
|
Some("amd") => {
|
|
if let Some(amd_name) = get_amd_igpu_name() {
|
|
amd_name
|
|
} else {
|
|
"modesetting".to_string()
|
|
}
|
|
}
|
|
_ => "modesetting".to_string(),
|
|
};
|
|
NVIDIA_XRANDR_SCRIPT.replace("{}", &source_name)
|
|
}
|
|
|
|
fn cleanup() {
|
|
let to_remove = vec![
|
|
BLACKLIST_PATH,
|
|
UDEV_INTEGRATED_PATH,
|
|
UDEV_PM_PATH,
|
|
XORG_PATH,
|
|
EXTRA_XORG_PATH,
|
|
MODESET_PATH,
|
|
LIGHTDM_SCRIPT_PATH,
|
|
LIGHTDM_CONFIG_PATH,
|
|
"/etc/lapctl-wayland-nvidia",
|
|
// legacy paths
|
|
"/etc/X11/xorg.conf.d/90-nvidia.conf",
|
|
"/lib/udev/rules.d/50-remove-nvidia.rules",
|
|
"/lib/udev/rules.d/80-nvidia-pm.rules",
|
|
];
|
|
|
|
for file_path in to_remove {
|
|
if Path::new(file_path).exists() {
|
|
if let Err(e) = fs::remove_file(file_path) {
|
|
error!("Failed to remove file '{}': {}", file_path, e);
|
|
} else {
|
|
info!("Removed file {}", file_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
let backup_path = format!("{}.bak", SDDM_XSETUP_PATH);
|
|
if Path::new(&backup_path).exists() {
|
|
info!("Restoring Xsetup backup");
|
|
if let Ok(content) = fs::read_to_string(&backup_path) {
|
|
create_file(SDDM_XSETUP_PATH, &content, true);
|
|
}
|
|
if let Err(e) = fs::remove_file(&backup_path) {
|
|
error!("Failed to remove backup: {}", e);
|
|
} else {
|
|
info!("Removed file {}", backup_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
// CACHING LOGIC
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
struct Cache {
|
|
nvidia_gpu_pci_bus: String,
|
|
}
|
|
|
|
fn create_cache_obj(bus_id: String) -> Cache {
|
|
Cache {
|
|
nvidia_gpu_pci_bus: bus_id,
|
|
}
|
|
}
|
|
|
|
fn create_cache_file() {
|
|
let pci_addr = get_nvidia_gpu_pci_addr();
|
|
if pci_addr.is_none() {
|
|
log::debug!("NVIDIA GPU not found, skipping cache creation");
|
|
return;
|
|
}
|
|
|
|
let bus_id = get_nvidia_gpu_pci_bus();
|
|
let cache = create_cache_obj(bus_id);
|
|
|
|
if let Some(parent) = Path::new(CACHE_FILE_PATH).parent() {
|
|
let _ = fs::create_dir_all(parent);
|
|
}
|
|
|
|
if let Ok(json) = serde_json::to_string_pretty(&cache)
|
|
&& fs::write(CACHE_FILE_PATH, json).is_ok()
|
|
{
|
|
log::debug!("Created/Updated cache file {}", CACHE_FILE_PATH);
|
|
}
|
|
}
|
|
|
|
fn read_cache_file() -> Result<Cache, String> {
|
|
if Path::new(CACHE_FILE_PATH).exists() {
|
|
let content = fs::read_to_string(CACHE_FILE_PATH).map_err(|e| e.to_string())?;
|
|
serde_json::from_str(&content).map_err(|e| e.to_string())
|
|
} else if get_current_mode() == "hybrid" {
|
|
Ok(create_cache_obj(get_nvidia_gpu_pci_bus()))
|
|
} else {
|
|
Err(
|
|
"No cache present. Operation requires that the system be in the hybrid Optimus mode"
|
|
.into(),
|
|
)
|
|
}
|
|
}
|
|
|
|
fn prepare_no_reboot(dm_flag: Option<String>) -> Result<Option<String>, String> {
|
|
let dm_name = dm_flag.or_else(get_display_manager);
|
|
|
|
if let Some(ref name) = dm_name
|
|
&& is_service_active(name)
|
|
{
|
|
info!("Stopping display manager: {}", name);
|
|
manage_service(name, "stop")?;
|
|
return Ok(Some(name.clone()));
|
|
}
|
|
|
|
// If no active DM found, check for graphical sessions
|
|
let sessions = get_active_graphical_sessions();
|
|
if !sessions.is_empty() {
|
|
println!("No active display manager found, but graphical sessions are running.");
|
|
println!("Terminating graphical sessions for no-reboot GPU switching...");
|
|
for session_id in sessions {
|
|
if let Err(e) = terminate_session(&session_id) {
|
|
error!("Failed to terminate session {}: {}", session_id, e);
|
|
}
|
|
}
|
|
// Give some time for sessions to terminate
|
|
std::thread::sleep(std::time::Duration::from_secs(2));
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
|
|
fn finalize_no_reboot(stopped_dm: Option<String>) {
|
|
if let Some(name) = stopped_dm {
|
|
info!("Starting display manager: {}", name);
|
|
let _ = manage_service(&name, "start");
|
|
}
|
|
}
|
|
|
|
fn switch_integrated(no_reboot: bool) {
|
|
assert_root();
|
|
println!("Switching to integrated mode");
|
|
|
|
if !Path::new(CACHE_FILE_PATH).exists() {
|
|
create_cache_file();
|
|
}
|
|
|
|
cleanup();
|
|
|
|
create_file(BLACKLIST_PATH, BLACKLIST_CONTENT, false);
|
|
create_file(UDEV_INTEGRATED_PATH, UDEV_INTEGRATED, false);
|
|
|
|
let mut stopped_dm = None;
|
|
if no_reboot {
|
|
match prepare_no_reboot(None) {
|
|
Ok(dm) => stopped_dm = dm,
|
|
Err(e) => {
|
|
error!("{}", e);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if let Err(e) = kill_gpu_processes() {
|
|
error!("Failed to kill GPU processes: {}", e);
|
|
}
|
|
|
|
// Try to unload modules
|
|
let modules = ["nvidia_uvm", "nvidia_modeset", "nvidia_drm", "nvidia"];
|
|
for module in modules {
|
|
let _ = Command::new("modprobe").args(["-r", module]).status();
|
|
}
|
|
|
|
if let Some(pci_addr) = get_nvidia_gpu_pci_addr() {
|
|
if let Err(e) = unbind_gpu(&pci_addr) {
|
|
error!("Failed to unbind GPU: {}", e);
|
|
}
|
|
if let Err(e) = remove_gpu(&pci_addr) {
|
|
error!("Failed to remove GPU: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
let is_debug = log::log_enabled!(log::Level::Debug);
|
|
let mut dis_cmd = Command::new("systemctl");
|
|
dis_cmd.args(["disable", "nvidia-persistenced.service"]);
|
|
if !is_debug {
|
|
dis_cmd.stdout(std::process::Stdio::null());
|
|
dis_cmd.stderr(std::process::Stdio::null());
|
|
}
|
|
match dis_cmd.status() {
|
|
Ok(s) if s.success() => println!("Successfully disabled nvidia-persistenced.service"),
|
|
_ => error!("An error ocurred while disabling service"),
|
|
}
|
|
|
|
if no_reboot {
|
|
println!("Operation completed successfully");
|
|
finalize_no_reboot(stopped_dm);
|
|
} else {
|
|
rebuild_initramfs();
|
|
println!("Operation completed successfully");
|
|
println!("Please reboot your computer for changes to take effect!");
|
|
}
|
|
}
|
|
|
|
fn switch_hybrid(rtd3: Option<u8>, use_nvidia_current: bool, no_reboot: bool) {
|
|
assert_root();
|
|
println!("Switching to hybrid mode");
|
|
println!(
|
|
"Enable PCI-Express Runtime D3 (RTD3) Power Management: {}",
|
|
rtd3.is_some()
|
|
);
|
|
|
|
if no_reboot && let Err(e) = rescan_pci() {
|
|
error!("Failed to rescan PCI bus: {}", e);
|
|
}
|
|
|
|
if !Path::new(CACHE_FILE_PATH).exists() {
|
|
create_cache_file();
|
|
}
|
|
|
|
cleanup();
|
|
|
|
let mut stopped_dm = None;
|
|
if no_reboot {
|
|
match prepare_no_reboot(None) {
|
|
Ok(dm) => stopped_dm = dm,
|
|
Err(e) => {
|
|
error!("{}", e);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if let Err(e) = kill_gpu_processes() {
|
|
error!("Failed to kill GPU processes: {}", e);
|
|
}
|
|
}
|
|
|
|
// persistenced enable
|
|
let is_debug = log::log_enabled!(log::Level::Debug);
|
|
let mut enable_cmd = Command::new("systemctl");
|
|
enable_cmd.args(["enable", "nvidia-persistenced.service"]);
|
|
if !is_debug {
|
|
enable_cmd.stdout(std::process::Stdio::null());
|
|
enable_cmd.stderr(std::process::Stdio::null());
|
|
}
|
|
match enable_cmd.status() {
|
|
Ok(s) if s.success() => println!("Successfully enabled nvidia-persistenced.service"),
|
|
_ => error!("An error ocurred while enabling service"),
|
|
}
|
|
|
|
if let Some(val) = rtd3 {
|
|
let modeset_content = if use_nvidia_current {
|
|
format!(
|
|
"# Automatically generated by lapctl\n\noptions nvidia-current-drm modeset=1\noptions nvidia-current \"NVreg_DynamicPowerManagement=0x0{}\"\noptions nvidia-current NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n",
|
|
val
|
|
)
|
|
} else {
|
|
format!(
|
|
"# Automatically generated by lapctl\n\noptions nvidia-drm modeset=1\noptions nvidia \"NVreg_DynamicPowerManagement=0x0{}\"\noptions nvidia NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n",
|
|
val
|
|
)
|
|
};
|
|
create_file(MODESET_PATH, &modeset_content, false);
|
|
create_file(UDEV_PM_PATH, UDEV_PM_CONTENT, false);
|
|
} else {
|
|
let modeset_content = if use_nvidia_current {
|
|
"# Automatically generated by lapctl\n\noptions nvidia-current-drm modeset=1\noptions nvidia-current NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n".to_string()
|
|
} else {
|
|
"# Automatically generated by lapctl\n\noptions nvidia-drm modeset=1\noptions nvidia NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n".to_string()
|
|
};
|
|
create_file(MODESET_PATH, &modeset_content, false);
|
|
}
|
|
|
|
if no_reboot {
|
|
println!("Operation completed successfully");
|
|
finalize_no_reboot(stopped_dm);
|
|
} else {
|
|
rebuild_initramfs();
|
|
println!("Operation completed successfully");
|
|
println!("Please reboot your computer for changes to take effect!");
|
|
}
|
|
}
|
|
|
|
fn switch_nvidia(
|
|
dm: Option<String>,
|
|
force_comp: bool,
|
|
coolbits: Option<u32>,
|
|
use_nvidia_current: bool,
|
|
wayland: bool,
|
|
no_reboot: bool,
|
|
) {
|
|
assert_root();
|
|
println!("Switching to nvidia mode");
|
|
println!("Enable ForceCompositionPipeline: {}", force_comp);
|
|
println!("Enable Coolbits: {}", coolbits.is_some());
|
|
|
|
if no_reboot && let Err(e) = rescan_pci() {
|
|
error!("Failed to rescan PCI bus: {}", e);
|
|
}
|
|
|
|
if !Path::new(CACHE_FILE_PATH).exists() {
|
|
create_cache_file();
|
|
}
|
|
|
|
let cache = read_cache_file();
|
|
let pci_bus = match cache {
|
|
Ok(c) => c.nvidia_gpu_pci_bus,
|
|
Err(_) => get_nvidia_gpu_pci_bus(),
|
|
};
|
|
|
|
cleanup();
|
|
|
|
if use_nvidia_current {
|
|
create_file(
|
|
MODESET_PATH,
|
|
"# Automatically generated by lapctl\n\noptions nvidia-current-drm modeset=1\noptions nvidia-current NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n",
|
|
false,
|
|
);
|
|
} else {
|
|
create_file(
|
|
MODESET_PATH,
|
|
"# Automatically generated by lapctl\n\noptions nvidia-drm modeset=1\noptions nvidia NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0\n",
|
|
false,
|
|
);
|
|
}
|
|
|
|
if wayland {
|
|
create_file(
|
|
"/etc/lapctl-wayland-nvidia",
|
|
"# Wayland mode marker for lapctl\n",
|
|
false,
|
|
);
|
|
} else {
|
|
let igpu_vendor = get_igpu_vendor();
|
|
if let Some(ref vendor) = igpu_vendor {
|
|
if vendor == "intel" {
|
|
create_file(XORG_PATH, &xorg_intel(&pci_bus), false);
|
|
} else if vendor == "amd" {
|
|
create_file(XORG_PATH, &xorg_amd(&pci_bus), false);
|
|
}
|
|
}
|
|
|
|
let mut extra_xorg = EXTRA_XORG_CONTENT.to_string();
|
|
if force_comp {
|
|
extra_xorg.push_str(" Option \"ForceCompositionPipeline\" \"true\"\n");
|
|
}
|
|
if let Some(cb) = coolbits {
|
|
extra_xorg.push_str(&format!(" Option \"Coolbits\" \"{}\"\n", cb));
|
|
}
|
|
if force_comp || coolbits.is_some() {
|
|
extra_xorg.push_str("EndSection\n");
|
|
create_file(EXTRA_XORG_PATH, &extra_xorg, false);
|
|
}
|
|
|
|
let display_manager = dm.clone().or_else(get_display_manager);
|
|
|
|
if let Some(cdm) = display_manager {
|
|
if cdm == "sddm" {
|
|
if Path::new(SDDM_XSETUP_PATH).exists() {
|
|
info!("Creating Xsetup backup");
|
|
if let Ok(content) = fs::read_to_string(SDDM_XSETUP_PATH) {
|
|
create_file(&format!("{}.bak", SDDM_XSETUP_PATH), &content, false);
|
|
}
|
|
}
|
|
create_file(
|
|
SDDM_XSETUP_PATH,
|
|
&generate_xrandr_script(igpu_vendor.as_ref()),
|
|
true,
|
|
);
|
|
} else if cdm == "lightdm" {
|
|
create_file(
|
|
LIGHTDM_SCRIPT_PATH,
|
|
&generate_xrandr_script(igpu_vendor.as_ref()),
|
|
true,
|
|
);
|
|
create_file(LIGHTDM_CONFIG_PATH, LIGHTDM_CONFIG_CONTENT, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut stopped_dm = None;
|
|
if no_reboot {
|
|
match prepare_no_reboot(dm.clone()) {
|
|
Ok(dm_name) => stopped_dm = dm_name,
|
|
Err(e) => {
|
|
error!("{}", e);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if let Err(e) = kill_gpu_processes() {
|
|
error!("Failed to kill GPU processes: {}", e);
|
|
}
|
|
}
|
|
|
|
// persistenced enable
|
|
let is_debug = log::log_enabled!(log::Level::Debug);
|
|
let mut enable_cmd = Command::new("systemctl");
|
|
enable_cmd.args(["enable", "nvidia-persistenced.service"]);
|
|
if !is_debug {
|
|
enable_cmd.stdout(std::process::Stdio::null());
|
|
enable_cmd.stderr(std::process::Stdio::null());
|
|
}
|
|
match enable_cmd.status() {
|
|
Ok(s) if s.success() => println!("Successfully enabled nvidia-persistenced.service"),
|
|
_ => error!("An error ocurred while enabling service"),
|
|
}
|
|
|
|
if no_reboot {
|
|
println!("Operation completed successfully");
|
|
finalize_no_reboot(stopped_dm);
|
|
} else {
|
|
rebuild_initramfs();
|
|
println!("Operation completed successfully");
|
|
println!("Please reboot your computer for changes to take effect!");
|
|
}
|
|
}
|
|
|
|
fn run_on_dgpu(command: &[String]) {
|
|
if command.is_empty() {
|
|
error!("No command provided to run.");
|
|
return;
|
|
}
|
|
|
|
if get_current_mode() != "hybrid" {
|
|
log::warn!(
|
|
"Running on dGPU is typically only effective in Hybrid mode. \
|
|
Your current mode is '{}'. The application may fail to start or \
|
|
not use the NVIDIA GPU.",
|
|
get_current_mode()
|
|
);
|
|
}
|
|
|
|
log::info!("Launching '{}' on the discrete GPU...", command.join(" "));
|
|
|
|
let mut child = Command::new(&command[0]);
|
|
if command.len() > 1 {
|
|
child.args(&command[1..]);
|
|
}
|
|
|
|
// Set PRIME offload variables natively
|
|
child.env("__NV_PRIME_RENDER_OFFLOAD", "1");
|
|
child.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
|
|
child.env("__VK_LAYER_NV_optimus", "NVIDIA_only");
|
|
|
|
match child.status() {
|
|
Ok(status) => {
|
|
log::info!("Application exited with: {}", status);
|
|
}
|
|
Err(e) => {
|
|
error!("Failed to launch application: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
return;
|
|
}
|
|
|
|
execute_local(cmd);
|
|
}
|
|
|
|
pub fn execute_local(cmd: &GpuCommands) {
|
|
match cmd {
|
|
GpuCommands::Query => {
|
|
let mode = get_current_mode();
|
|
println!("{}", mode);
|
|
}
|
|
GpuCommands::CacheCreate => {
|
|
assert_root();
|
|
create_cache_file();
|
|
}
|
|
GpuCommands::CacheDelete => {
|
|
assert_root();
|
|
if Path::new(CACHE_FILE_PATH).exists() {
|
|
let _ = fs::remove_file(CACHE_FILE_PATH);
|
|
if let Some(parent) = Path::new(CACHE_FILE_PATH).parent() {
|
|
let _ = fs::remove_dir(parent);
|
|
}
|
|
}
|
|
}
|
|
GpuCommands::CacheQuery => {
|
|
if let Ok(content) = fs::read_to_string(CACHE_FILE_PATH) {
|
|
println!("{}", content);
|
|
} else {
|
|
println!("ERROR: Could not read {}", CACHE_FILE_PATH);
|
|
}
|
|
}
|
|
GpuCommands::Reset => {
|
|
assert_root();
|
|
|
|
if get_current_mode() == "hybrid" && !Path::new(CACHE_FILE_PATH).exists() {
|
|
create_cache_file();
|
|
}
|
|
|
|
cleanup();
|
|
if Path::new(CACHE_FILE_PATH).exists() {
|
|
let _ = fs::remove_file(CACHE_FILE_PATH);
|
|
if let Some(parent) = Path::new(CACHE_FILE_PATH).parent() {
|
|
let _ = fs::remove_dir(parent);
|
|
}
|
|
}
|
|
rebuild_initramfs();
|
|
println!("Operation completed successfully");
|
|
}
|
|
GpuCommands::ResetSddm => {
|
|
assert_root();
|
|
create_file(SDDM_XSETUP_PATH, SDDM_XSETUP_CONTENT, true);
|
|
println!("Operation completed successfully");
|
|
}
|
|
GpuCommands::Integrated { no_reboot } => switch_integrated(*no_reboot),
|
|
GpuCommands::Hybrid {
|
|
rtd3,
|
|
use_nvidia_current,
|
|
no_reboot,
|
|
} => switch_hybrid(*rtd3, *use_nvidia_current, *no_reboot),
|
|
GpuCommands::Nvidia {
|
|
dm,
|
|
force_comp,
|
|
coolbits,
|
|
use_nvidia_current,
|
|
wayland,
|
|
no_reboot,
|
|
} => switch_nvidia(
|
|
dm.clone(),
|
|
*force_comp,
|
|
*coolbits,
|
|
*use_nvidia_current,
|
|
*wayland,
|
|
*no_reboot,
|
|
),
|
|
GpuCommands::Run { command } => run_on_dgpu(command),
|
|
}
|
|
}
|