mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2025-02-25 18:55:26 -06:00
fix: avoid freezing the UI when enabling/disabling overdrive (#300)
This commit is contained in:
parent
d9553a91a1
commit
76e5c2bcc4
@ -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<String> {
|
||||
pub async fn generate_snapshot(&self) -> anyhow::Result<String> {
|
||||
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()),
|
||||
};
|
||||
|
@ -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<Vec<u8>> {
|
||||
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)?)
|
||||
}
|
||||
|
@ -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<SystemInfo<'static>> {
|
||||
pub async fn info() -> anyhow::Result<SystemInfo<'static>> {
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
let profile = if cfg!(debug_assertions) {
|
||||
"debug"
|
||||
@ -24,6 +24,7 @@ pub fn info() -> anyhow::Result<SystemInfo<'static>> {
|
||||
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<SystemInfo<'static>> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn enable_overdrive() -> anyhow::Result<String> {
|
||||
pub async fn enable_overdrive() -> anyhow::Result<String> {
|
||||
let current_mask = read_current_mask()?;
|
||||
|
||||
let new_mask = current_mask | PP_OVERDRIVE_MASK;
|
||||
@ -62,7 +63,7 @@ pub fn enable_overdrive() -> anyhow::Result<String> {
|
||||
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<String> {
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
pub fn disable_overdrive() -> anyhow::Result<String> {
|
||||
pub async fn disable_overdrive() -> anyhow::Result<String> {
|
||||
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> {
|
||||
u64::from_str_radix(ppfeaturemask, 16).context("Invalid ppfeaturemask")
|
||||
}
|
||||
|
||||
fn regenerate_initramfs() -> anyhow::Result<InitramfsType> {
|
||||
async fn regenerate_initramfs() -> anyhow::Result<InitramfsType> {
|
||||
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<InitramfsType> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn detect_initramfs_type(os_release: &OsRelease) -> Option<InitramfsType> {
|
||||
pub(crate) async fn detect_initramfs_type(os_release: &OsRelease) -> Option<InitramfsType> {
|
||||
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<InitramfsT
|
||||
None
|
||||
}
|
||||
} else if os_release.id == "fedora" {
|
||||
if Command::new("dracut").arg("--version").output().is_ok() {
|
||||
if Command::new("dracut")
|
||||
.arg("--version")
|
||||
.output()
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
Some(InitramfsType::Dracut)
|
||||
} else {
|
||||
warn!("Fedora without dracut detected, refusing to regenerate initramfs");
|
||||
@ -142,11 +155,12 @@ pub(crate) fn detect_initramfs_type(os_release: &OsRelease) -> Option<InitramfsT
|
||||
}
|
||||
}
|
||||
|
||||
fn run_command(exec: &str, args: &[&str]) -> 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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 <b>{MODULE_CONF_PATH}</b> 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 <b>{MODULE_CONF_PATH}</b> 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) {
|
||||
|
@ -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<anyhow::Error>)> {
|
||||
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");
|
||||
|
Loading…
Reference in New Issue
Block a user