diff --git a/lact-daemon/src/server/handler.rs b/lact-daemon/src/server/handler.rs index c729896..30f98fd 100644 --- a/lact-daemon/src/server/handler.rs +++ b/lact-daemon/src/server/handler.rs @@ -463,7 +463,7 @@ impl<'a> Handler { .context("Failed to edit GPU config and set enabled power states") } - pub fn generate_snapshot(&self) -> anyhow::Result { + pub async fn generate_snapshot(&self) -> anyhow::Result { let datetime = chrono::Local::now().format("%Y%m%d-%H%M%S"); let out_path = format!("/tmp/LACT-sysfs-snapshot-{datetime}.tar.gz"); @@ -516,10 +516,12 @@ impl<'a> Handler { } let system_info = system::info() + .await .ok() .map(|info| serde_json::to_value(info).unwrap()); let initramfs_type = match OS_RELEASE.as_ref() { Ok(os_release) => detect_initramfs_type(os_release) + .await .map(|initramfs_type| serde_json::to_value(initramfs_type).unwrap()), Err(err) => Some(err.to_string().into()), }; diff --git a/lact-daemon/src/server/mod.rs b/lact-daemon/src/server/mod.rs index 1eeb7c0..ee1cb24 100644 --- a/lact-daemon/src/server/mod.rs +++ b/lact-daemon/src/server/mod.rs @@ -78,7 +78,7 @@ pub async fn handle_stream(stream: UnixStream, handler: Handler) -> anyhow::Resu async fn handle_request<'a>(request: Request<'a>, handler: &'a Handler) -> anyhow::Result> { match request { Request::Ping => ok_response(ping()), - Request::SystemInfo => ok_response(system::info()?), + Request::SystemInfo => ok_response(system::info().await?), Request::ListDevices => ok_response(handler.list_devices()), Request::DeviceInfo { id } => ok_response(handler.get_device_info(id)?), Request::DeviceStats { id } => ok_response(handler.get_gpu_stats(id)?), @@ -106,9 +106,9 @@ async fn handle_request<'a>(request: Request<'a>, handler: &'a Handler) -> anyho Request::SetEnabledPowerStates { id, kind, states } => { ok_response(handler.set_enabled_power_states(id, kind, states).await?) } - Request::EnableOverdrive => ok_response(system::enable_overdrive()?), - Request::DisableOverdrive => ok_response(system::disable_overdrive()?), - Request::GenerateSnapshot => ok_response(handler.generate_snapshot()?), + Request::EnableOverdrive => ok_response(system::enable_overdrive().await?), + Request::DisableOverdrive => ok_response(system::disable_overdrive().await?), + Request::GenerateSnapshot => ok_response(handler.generate_snapshot().await?), Request::ConfirmPendingConfig(command) => { ok_response(handler.confirm_pending_config(command)?) } diff --git a/lact-daemon/src/server/system.rs b/lact-daemon/src/server/system.rs index 4abd5fd..f8ecf50 100644 --- a/lact-daemon/src/server/system.rs +++ b/lact-daemon/src/server/system.rs @@ -6,15 +6,15 @@ use std::{ io::Write, os::unix::prelude::PermissionsExt, path::Path, - process::Command, }; +use tokio::process::Command; use tracing::{info, warn}; const PP_OVERDRIVE_MASK: u64 = 0x4000; pub const PP_FEATURE_MASK_PATH: &str = "/sys/module/amdgpu/parameters/ppfeaturemask"; pub const MODULE_CONF_PATH: &str = "/etc/modprobe.d/99-amdgpu-overdrive.conf"; -pub fn info() -> anyhow::Result> { +pub async fn info() -> anyhow::Result> { let version = env!("CARGO_PKG_VERSION"); let profile = if cfg!(debug_assertions) { "debug" @@ -24,6 +24,7 @@ pub fn info() -> anyhow::Result> { let kernel_output = Command::new("uname") .arg("-r") .output() + .await .context("Could not read kernel version")?; let kernel_version = String::from_utf8(kernel_output.stdout) .context("Invalid kernel version output")? @@ -45,7 +46,7 @@ pub fn info() -> anyhow::Result> { }) } -pub fn enable_overdrive() -> anyhow::Result { +pub async fn enable_overdrive() -> anyhow::Result { let current_mask = read_current_mask()?; let new_mask = current_mask | PP_OVERDRIVE_MASK; @@ -62,7 +63,7 @@ pub fn enable_overdrive() -> anyhow::Result { file.write_all(conf.as_bytes()) .context("Could not write config")?; - let message = match regenerate_initramfs() { + let message = match regenerate_initramfs().await { Ok(initramfs_type) => { format!("Initramfs was successfully regenerated (detected type {initramfs_type:?})") } @@ -72,10 +73,10 @@ pub fn enable_overdrive() -> anyhow::Result { Ok(message) } -pub fn disable_overdrive() -> anyhow::Result { +pub async fn disable_overdrive() -> anyhow::Result { if Path::new(MODULE_CONF_PATH).exists() { fs::remove_file(MODULE_CONF_PATH).context("Could not remove module config file")?; - match regenerate_initramfs() { + match regenerate_initramfs().await { Ok(initramfs_type) => Ok(format!( "Initramfs was successfully regenerated (detected type {initramfs_type:?})" )), @@ -98,15 +99,17 @@ fn read_current_mask() -> anyhow::Result { u64::from_str_radix(ppfeaturemask, 16).context("Invalid ppfeaturemask") } -fn regenerate_initramfs() -> anyhow::Result { +async fn regenerate_initramfs() -> anyhow::Result { let os_release = OS_RELEASE.as_ref().context("Could not detect distro")?; - match detect_initramfs_type(os_release) { + match detect_initramfs_type(os_release).await { Some(initramfs_type) => { info!("Detected initramfs type {initramfs_type:?}, regenerating"); let result = match initramfs_type { - InitramfsType::Debian => run_command("update-initramfs", &["-u"]), - InitramfsType::Mkinitcpio => run_command("mkinitcpio", &["-P"]), - InitramfsType::Dracut => run_command("dracut", &["--regenerate-all", "--force"]), + InitramfsType::Debian => run_command("update-initramfs", &["-u"]).await, + InitramfsType::Mkinitcpio => run_command("mkinitcpio", &["-P"]).await, + InitramfsType::Dracut => { + run_command("dracut", &["--regenerate-all", "--force"]).await + } }; result.map(|()| initramfs_type) } @@ -116,13 +119,18 @@ fn regenerate_initramfs() -> anyhow::Result { } } -pub(crate) fn detect_initramfs_type(os_release: &OsRelease) -> Option { +pub(crate) async fn detect_initramfs_type(os_release: &OsRelease) -> Option { let id_like: Vec<_> = os_release.id_like.split_whitespace().collect(); if os_release.id == "debian" || id_like.contains(&"debian") { Some(InitramfsType::Debian) } else if os_release.id == "arch" || id_like.contains(&"arch") { - if Command::new("mkinitcpio").arg("--version").output().is_ok() { + if Command::new("mkinitcpio") + .arg("--version") + .output() + .await + .is_ok() + { Some(InitramfsType::Mkinitcpio) } else { warn!( @@ -131,7 +139,12 @@ pub(crate) fn detect_initramfs_type(os_release: &OsRelease) -> Option Option anyhow::Result<()> { +async fn run_command(exec: &str, args: &[&str]) -> anyhow::Result<()> { info!("Running {exec} with args {args:?}"); let output = Command::new(exec) .args(args) .output() + .await .context("Could not run command")?; if output.status.success() { Ok(()) @@ -163,8 +177,8 @@ mod tests { use lact_schema::InitramfsType; use os_release::OsRelease; - #[test] - fn detect_initramfs_debian() { + #[tokio::test] + async fn detect_initramfs_debian() { let data = r#" PRETTY_NAME="Debian GNU/Linux trixie/sid" NAME="Debian GNU/Linux" @@ -177,12 +191,12 @@ BUG_REPORT_URL="https://bugs.debian.org/" let os_release: OsRelease = data.lines().map(str::to_owned).collect(); assert_eq!( Some(InitramfsType::Debian), - detect_initramfs_type(&os_release) + detect_initramfs_type(&os_release).await ); } - #[test] - fn detect_initramfs_mint() { + #[tokio::test] + async fn detect_initramfs_mint() { let data = r#" NAME="Linux Mint" VERSION="21.2 (Victoria)" @@ -200,7 +214,7 @@ UBUNTU_CODENAME=jammy let os_release: OsRelease = data.lines().map(str::to_owned).collect(); assert_eq!( Some(InitramfsType::Debian), - detect_initramfs_type(&os_release) + detect_initramfs_type(&os_release).await ); } } diff --git a/lact-gui/src/app/mod.rs b/lact-gui/src/app/mod.rs index b99018e..6e3b4b4 100644 --- a/lact-gui/src/app/mod.rs +++ b/lact-gui/src/app/mod.rs @@ -4,7 +4,7 @@ mod info_row; mod page_section; mod root_stack; -use crate::{APP_ID, GUI_VERSION}; +use crate::{create_connection, APP_ID, GUI_VERSION}; use anyhow::{anyhow, Context}; use apply_revealer::ApplyRevealer; use glib::clone; @@ -536,7 +536,7 @@ impl App { } fn enable_overclocking(&self) { - let text = format!("This will enable the overdrive feature of the amdgpu driver by creating a file at {MODULE_CONF_PATH} and updating the initramfs. Are you sure you want to do this? (Note: the GUI may freeze for a bit)"); + let text = format!("This will enable the overdrive feature of the amdgpu driver by creating a file at {MODULE_CONF_PATH} and updating the initramfs. Are you sure you want to do this?"); let dialog = MessageDialog::builder() .title("Enable Overclocking") .use_markup(true) @@ -548,11 +548,70 @@ impl App { dialog.run_async(clone!(@strong self as app => move |diag, response| { if response == ResponseType::Ok { - match app.daemon_client.enable_overdrive().and_then(|buffer| buffer.inner()) { + let handle = gio::spawn_blocking(|| { + let (daemon_client, _) = create_connection().expect("Could not create new daemon connection"); + daemon_client.enable_overdrive().and_then(|buffer| buffer.inner()) + }); + + let dialog = app.spinner_dialog("Turning overclocking on (this may take a while)"); + dialog.show(); + + glib::spawn_future_local(async move { + let result = handle.await.unwrap(); + dialog.hide(); + + match result { + Ok(msg) => { + let success_dialog = MessageDialog::builder() + .title("Success") + .text(format!("Overclocking successfully enabled. A system reboot is required to apply the changes.\nSystem message: {msg}")) + .message_type(MessageType::Info) + .buttons(ButtonsType::Ok) + .build(); + success_dialog.run_async(move |diag, _| { + diag.hide(); + }); + } + Err(err) => { + show_error(&app.window, err); + } + } + }); + } + diag.hide(); + })); + } + + fn disable_overclocking(&self) { + let dialog = MessageDialog::builder() + .title("Disable Overclocking") + .use_markup(true) + .text("The overclocking functionality in the driver will now be turned off.") + .message_type(MessageType::Info) + .buttons(ButtonsType::Ok) + .transient_for(&self.window) + .build(); + + dialog.run_async(clone!(@strong self as app => move |diag, _| { + diag.hide(); + + let handle = gio::spawn_blocking(|| { + let (daemon_client, _) = create_connection().expect("Could not create new daemon connection"); + daemon_client.disable_overdrive().and_then(|buffer| buffer.inner()) + }); + + let dialog = app.spinner_dialog("Turning overclocking off (this may take a while)"); + dialog.show(); + + glib::spawn_future_local(async move { + let result = handle.await.unwrap(); + dialog.hide(); + + match result { Ok(msg) => { let success_dialog = MessageDialog::builder() .title("Success") - .text(format!("Overclocking successfully enabled. A system reboot is required to apply the changes.\nSystem message: {msg}")) + .text(format!("Overclocking successfully disabled. A system reboot is required to apply the changes.\nSystem message: {msg}")) .message_type(MessageType::Info) .buttons(ButtonsType::Ok) .build(); @@ -564,39 +623,8 @@ impl App { show_error(&app.window, err); } } - } - diag.hide(); - })); - } + }); - fn disable_overclocking(&self) { - let dialog = MessageDialog::builder() - .title("Disable Overclocking") - .use_markup(true) - .text("The overclocking functionality in the driver will now be turned off. (Note: the LACT window might hang)") - .message_type(MessageType::Info) - .buttons(ButtonsType::Ok) - .transient_for(&self.window) - .build(); - - dialog.run_async(clone!(@strong self as app => move |diag, _| { - diag.hide(); - match app.daemon_client.disable_overdrive().and_then(|buffer| buffer.inner()) { - Ok(msg) => { - let success_dialog = MessageDialog::builder() - .title("Success") - .text(format!("Overclocking successfully disabled. A system reboot is required to apply the changes.\nSystem message: {msg}")) - .message_type(MessageType::Info) - .buttons(ButtonsType::Ok) - .build(); - success_dialog.run_async(move |diag, _| { - diag.hide(); - }); - } - Err(err) => { - show_error(&app.window, err); - } - } })); } @@ -650,6 +678,25 @@ impl App { app.set_initial(&gpu_id); })); } + + fn spinner_dialog(&self, title: &str) -> MessageDialog { + let spinner = gtk::Spinner::new(); + spinner.start(); + spinner.set_margin_top(10); + spinner.set_margin_bottom(10); + + let dialog = MessageDialog::builder() + .title(title) + .child(&spinner) + .message_type(MessageType::Info) + .transient_for(&self.window) + .build(); + + dialog.titlebar().unwrap().set_margin_start(15); + dialog.titlebar().unwrap().set_margin_end(15); + + dialog + } } fn show_error(parent: &ApplicationWindow, err: anyhow::Error) { diff --git a/lact-gui/src/lib.rs b/lact-gui/src/lib.rs index 876b3ff..f66aaed 100644 --- a/lact-gui/src/lib.rs +++ b/lact-gui/src/lib.rs @@ -4,7 +4,7 @@ use anyhow::{anyhow, Context}; use app::App; use lact_client::{schema::args::GuiArgs, DaemonClient}; use std::os::unix::net::UnixStream; -use tracing::{error, info, metadata::LevelFilter}; +use tracing::{debug, error, info, metadata::LevelFilter}; use tracing_subscriber::EnvFilter; const GUI_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -29,7 +29,10 @@ pub fn run(args: GuiArgs) -> anyhow::Result<()> { fn create_connection() -> anyhow::Result<(DaemonClient, Option)> { match DaemonClient::connect() { - Ok(connection) => Ok((connection, None)), + Ok(connection) => { + debug!("Established daemon connection"); + Ok((connection, None)) + } Err(err) => { info!("could not connect to socket: {err:#}"); info!("using a local daemon");