mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2025-02-25 18:55:26 -06:00
feat: added static fan control option (#198)
* fix: fixed the confirm apply dialog timer not stopping after choosing an option * feat: added static fan control to the daemon and GUI * fix: cleanup and validate static speed
This commit is contained in:
@@ -8,8 +8,8 @@ use nix::unistd::getuid;
|
||||
use schema::{
|
||||
amdgpu_sysfs::gpu_handle::{power_profile_mode::PowerProfileModesTable, PerformanceLevel},
|
||||
request::{ConfirmCommand, SetClocksCommand},
|
||||
ClocksInfo, DeviceInfo, DeviceListEntry, DeviceStats, FanCurveMap, Request, Response,
|
||||
SystemInfo,
|
||||
ClocksInfo, DeviceInfo, DeviceListEntry, DeviceStats, FanControlMode, FanCurveMap, Request,
|
||||
Response, SystemInfo,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
@@ -106,10 +106,18 @@ impl DaemonClient {
|
||||
&self,
|
||||
id: &str,
|
||||
enabled: bool,
|
||||
mode: Option<FanControlMode>,
|
||||
static_speed: Option<f64>,
|
||||
curve: Option<FanCurveMap>,
|
||||
) -> anyhow::Result<u64> {
|
||||
self.make_request(Request::SetFanControl { id, enabled, curve })?
|
||||
.inner()
|
||||
self.make_request(Request::SetFanControl {
|
||||
id,
|
||||
enabled,
|
||||
mode,
|
||||
static_speed,
|
||||
curve,
|
||||
})?
|
||||
.inner()
|
||||
}
|
||||
|
||||
pub fn set_power_cap(&self, id: &str, cap: Option<f64>) -> anyhow::Result<u64> {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::server::gpu_controller::fan_control::FanCurve;
|
||||
use anyhow::Context;
|
||||
use lact_schema::{amdgpu_sysfs::gpu_handle::PerformanceLevel, request::SetClocksCommand};
|
||||
use lact_schema::{
|
||||
amdgpu_sysfs::gpu_handle::PerformanceLevel, default_fan_curve, request::SetClocksCommand,
|
||||
FanControlMode,
|
||||
};
|
||||
use nix::unistd::getuid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
@@ -102,11 +105,31 @@ impl Gpu {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct FanControlSettings {
|
||||
#[serde(default)]
|
||||
pub mode: FanControlMode,
|
||||
#[serde(default = "default_fan_static_speed")]
|
||||
pub static_speed: f64,
|
||||
pub temperature_key: String,
|
||||
pub interval_ms: u64,
|
||||
pub curve: FanCurve,
|
||||
}
|
||||
|
||||
impl Default for FanControlSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode: FanControlMode::default(),
|
||||
static_speed: default_fan_static_speed(),
|
||||
temperature_key: "edge".to_owned(),
|
||||
interval_ms: 500,
|
||||
curve: FanCurve(default_fan_curve()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_fan_static_speed() -> f64 {
|
||||
0.5
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> anyhow::Result<Option<Self>> {
|
||||
let path = get_path();
|
||||
@@ -159,6 +182,8 @@ fn default_apply_settings_timer() -> u64 {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use lact_schema::FanControlMode;
|
||||
|
||||
use super::{Config, Daemon, FanControlSettings, Gpu};
|
||||
use crate::server::gpu_controller::fan_control::FanCurve;
|
||||
|
||||
@@ -174,6 +199,8 @@ mod tests {
|
||||
curve: FanCurve::default(),
|
||||
temperature_key: "edge".to_owned(),
|
||||
interval_ms: 500,
|
||||
mode: FanControlMode::Curve,
|
||||
static_speed: 0.5,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -231,15 +231,17 @@ impl GpuController {
|
||||
}
|
||||
|
||||
pub fn get_stats(&self, gpu_config: Option<&config::Gpu>) -> anyhow::Result<DeviceStats> {
|
||||
let fan_control_enabled = self
|
||||
.fan_control_handle
|
||||
.try_borrow()
|
||||
.map_err(|err| anyhow!("Could not lock fan control mutex: {err}"))?
|
||||
.is_some();
|
||||
|
||||
Ok(DeviceStats {
|
||||
fan: FanStats {
|
||||
control_enabled: fan_control_enabled,
|
||||
control_enabled: gpu_config
|
||||
.map(|config| config.fan_control_enabled)
|
||||
.unwrap_or_default(),
|
||||
control_mode: gpu_config
|
||||
.and_then(|config| config.fan_control_settings.as_ref())
|
||||
.map(|settings| settings.mode.clone()),
|
||||
static_speed: gpu_config
|
||||
.and_then(|config| config.fan_control_settings.as_ref())
|
||||
.map(|settings| settings.static_speed),
|
||||
curve: gpu_config
|
||||
.and_then(|config| config.fan_control_settings.as_ref())
|
||||
.map(|settings| settings.curve.0.clone()),
|
||||
@@ -291,7 +293,33 @@ impl GpuController {
|
||||
self.handle.hw_monitors.first().map(f)
|
||||
}
|
||||
|
||||
async fn start_fan_control(
|
||||
async fn set_static_fan_control(&self, static_speed: f64) -> anyhow::Result<()> {
|
||||
// Stop existing task to set static speed
|
||||
self.stop_fan_control(false).await?;
|
||||
|
||||
let hw_mon = self
|
||||
.handle
|
||||
.hw_monitors
|
||||
.first()
|
||||
.cloned()
|
||||
.context("This GPU has no monitor")?;
|
||||
|
||||
hw_mon
|
||||
.set_fan_control_method(FanControlMethod::Manual)
|
||||
.context("Could not set fan control method")?;
|
||||
|
||||
let static_speed_converted = (f64::from(u8::MAX) * static_speed) as u8;
|
||||
|
||||
hw_mon
|
||||
.set_fan_pwm(static_speed_converted)
|
||||
.context("could not set fan speed")?;
|
||||
|
||||
debug!("set fan speed to {}", static_speed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn start_curve_fan_control(
|
||||
&self,
|
||||
curve: FanCurve,
|
||||
temp_key: String,
|
||||
@@ -359,18 +387,18 @@ impl GpuController {
|
||||
if let Some((notify, handle)) = maybe_notify {
|
||||
notify.notify_one();
|
||||
handle.await?;
|
||||
}
|
||||
|
||||
if reset_mode {
|
||||
let hw_mon = self
|
||||
.handle
|
||||
.hw_monitors
|
||||
.first()
|
||||
.cloned()
|
||||
.context("This GPU has no monitor")?;
|
||||
hw_mon
|
||||
.set_fan_control_method(FanControlMethod::Auto)
|
||||
.context("Could not set fan control back to automatic")?;
|
||||
}
|
||||
if reset_mode {
|
||||
let hw_mon = self
|
||||
.handle
|
||||
.hw_monitors
|
||||
.first()
|
||||
.cloned()
|
||||
.context("This GPU has no monitor")?;
|
||||
hw_mon
|
||||
.set_fan_control_method(FanControlMethod::Auto)
|
||||
.context("Could not set fan control back to automatic")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -379,17 +407,24 @@ impl GpuController {
|
||||
pub async fn apply_config(&self, config: &config::Gpu) -> anyhow::Result<()> {
|
||||
if config.fan_control_enabled {
|
||||
if let Some(ref settings) = config.fan_control_settings {
|
||||
if settings.curve.0.is_empty() {
|
||||
return Err(anyhow!("Cannot use empty fan curve"));
|
||||
}
|
||||
match settings.mode {
|
||||
lact_schema::FanControlMode::Static => {
|
||||
self.set_static_fan_control(settings.static_speed).await?
|
||||
}
|
||||
lact_schema::FanControlMode::Curve => {
|
||||
if settings.curve.0.is_empty() {
|
||||
return Err(anyhow!("Cannot use empty fan curve"));
|
||||
}
|
||||
|
||||
let interval = Duration::from_millis(settings.interval_ms);
|
||||
self.start_fan_control(
|
||||
settings.curve.clone(),
|
||||
settings.temperature_key.clone(),
|
||||
interval,
|
||||
)
|
||||
.await?;
|
||||
let interval = Duration::from_millis(settings.interval_ms);
|
||||
self.start_curve_fan_control(
|
||||
settings.curve.clone(),
|
||||
settings.temperature_key.clone(),
|
||||
interval,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"Trying to enable fan control with no settings provided"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use super::gpu_controller::{fan_control::FanCurve, GpuController};
|
||||
use crate::config::{self, Config, FanControlSettings};
|
||||
use crate::config::{self, default_fan_static_speed, Config, FanControlSettings};
|
||||
use anyhow::{anyhow, Context};
|
||||
use lact_schema::{
|
||||
amdgpu_sysfs::gpu_handle::{power_profile_mode::PowerProfileModesTable, PerformanceLevel},
|
||||
default_fan_curve,
|
||||
request::{ConfirmCommand, SetClocksCommand},
|
||||
ClocksInfo, DeviceInfo, DeviceListEntry, DeviceStats, FanCurveMap,
|
||||
ClocksInfo, DeviceInfo, DeviceListEntry, DeviceStats, FanControlMode, FanCurveMap,
|
||||
};
|
||||
use std::{cell::RefCell, collections::BTreeMap, env, path::PathBuf, rc::Rc, time::Duration};
|
||||
use tokio::{sync::oneshot, time::sleep};
|
||||
@@ -207,36 +208,69 @@ impl<'a> Handler {
|
||||
&'a self,
|
||||
id: &str,
|
||||
enabled: bool,
|
||||
mode: Option<FanControlMode>,
|
||||
static_speed: Option<f64>,
|
||||
curve: Option<FanCurveMap>,
|
||||
) -> anyhow::Result<u64> {
|
||||
let settings = match curve {
|
||||
Some(raw_curve) => {
|
||||
let curve = FanCurve(raw_curve);
|
||||
curve.validate()?;
|
||||
let settings = {
|
||||
let mut config_guard = self
|
||||
.config
|
||||
.try_borrow_mut()
|
||||
.map_err(|err| anyhow!("{err}"))?;
|
||||
let gpu_config = config_guard.gpus.entry(id.to_owned()).or_default();
|
||||
|
||||
let mut config_guard = self
|
||||
.config
|
||||
.try_borrow_mut()
|
||||
.map_err(|err| anyhow!("{err}"))?;
|
||||
let gpu_config = config_guard.gpus.entry(id.to_owned()).or_default();
|
||||
match mode {
|
||||
Some(mode) => match mode {
|
||||
FanControlMode::Static => {
|
||||
if matches!(static_speed, Some(speed) if !(0.0..=1.0).contains(&speed)) {
|
||||
return Err(anyhow!("static speed value out of range"));
|
||||
}
|
||||
|
||||
if let Some(mut existing_settings) = gpu_config.fan_control_settings.clone() {
|
||||
existing_settings.curve = curve;
|
||||
Some(existing_settings)
|
||||
} else {
|
||||
Some(FanControlSettings {
|
||||
curve,
|
||||
temperature_key: "edge".to_owned(),
|
||||
interval_ms: 500,
|
||||
})
|
||||
}
|
||||
if let Some(mut existing_settings) = gpu_config.fan_control_settings.clone()
|
||||
{
|
||||
existing_settings.mode = mode;
|
||||
if let Some(static_speed) = static_speed {
|
||||
existing_settings.static_speed = static_speed;
|
||||
}
|
||||
Some(existing_settings)
|
||||
} else {
|
||||
Some(FanControlSettings {
|
||||
mode,
|
||||
static_speed: static_speed.unwrap_or_else(default_fan_static_speed),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
FanControlMode::Curve => {
|
||||
if let Some(mut existing_settings) = gpu_config.fan_control_settings.clone()
|
||||
{
|
||||
existing_settings.mode = mode;
|
||||
if let Some(raw_curve) = curve {
|
||||
let curve = FanCurve(raw_curve);
|
||||
curve.validate()?;
|
||||
existing_settings.curve = curve;
|
||||
}
|
||||
Some(existing_settings)
|
||||
} else {
|
||||
let curve = FanCurve(curve.unwrap_or_else(default_fan_curve));
|
||||
curve.validate()?;
|
||||
Some(FanControlSettings {
|
||||
mode,
|
||||
curve,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
self.edit_gpu_config(id.to_owned(), |config| {
|
||||
config.fan_control_enabled = enabled;
|
||||
config.fan_control_settings = settings;
|
||||
if let Some(settings) = settings {
|
||||
config.fan_control_settings = Some(settings);
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -88,9 +88,17 @@ async fn handle_request<'a>(request: Request<'a>, handler: &'a Handler) -> anyho
|
||||
Request::DevicePowerProfileModes { id } => {
|
||||
ok_response(handler.get_power_profile_modes(id)?)
|
||||
}
|
||||
Request::SetFanControl { id, enabled, curve } => {
|
||||
ok_response(handler.set_fan_control(id, enabled, curve).await?)
|
||||
}
|
||||
Request::SetFanControl {
|
||||
id,
|
||||
enabled,
|
||||
mode,
|
||||
static_speed,
|
||||
curve,
|
||||
} => ok_response(
|
||||
handler
|
||||
.set_fan_control(id, enabled, mode, static_speed, curve)
|
||||
.await?,
|
||||
),
|
||||
Request::SetPowerCap { id, cap } => ok_response(handler.set_power_cap(id, cap).await?),
|
||||
Request::SetPerformanceLevel {
|
||||
id,
|
||||
|
||||
@@ -335,6 +335,8 @@ impl App {
|
||||
.set_fan_control(
|
||||
&gpu_id,
|
||||
thermals_settings.manual_fan_control,
|
||||
thermals_settings.mode,
|
||||
thermals_settings.static_speed,
|
||||
thermals_settings.curve,
|
||||
)
|
||||
.context("Could not set fan control")?;
|
||||
|
||||
@@ -21,7 +21,6 @@ pub struct FanCurveFrame {
|
||||
impl FanCurveFrame {
|
||||
pub fn new() -> Self {
|
||||
let root_box = Box::new(Orientation::Vertical, 5);
|
||||
root_box.hide();
|
||||
|
||||
let hbox = Box::new(Orientation::Horizontal, 5);
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
mod fan_curve_frame;
|
||||
|
||||
use fan_curve_frame::FanCurveFrame;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk::*;
|
||||
use lact_client::schema::{default_fan_curve, DeviceStats, FanCurveMap};
|
||||
use lact_client::schema::{default_fan_curve, DeviceStats, FanControlMode, FanCurveMap};
|
||||
|
||||
use super::{label_row, section_box, values_grid, values_row};
|
||||
use self::fan_curve_frame::FanCurveFrame;
|
||||
|
||||
use super::{label_row, section_box, values_grid};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ThermalsSettings {
|
||||
pub manual_fan_control: bool,
|
||||
pub mode: Option<FanControlMode>,
|
||||
pub static_speed: Option<f64>,
|
||||
pub curve: Option<FanCurveMap>,
|
||||
}
|
||||
|
||||
@@ -19,8 +22,10 @@ pub struct ThermalsPage {
|
||||
pub container: Box,
|
||||
temperatures_label: Label,
|
||||
fan_speed_label: Label,
|
||||
fan_control_enabled_switch: Switch,
|
||||
fan_static_speed_adjustment: Adjustment,
|
||||
fan_curve_frame: FanCurveFrame,
|
||||
fan_control_mode_stack: Stack,
|
||||
fan_control_mode_stack_switcher: StackSwitcher,
|
||||
}
|
||||
|
||||
impl ThermalsPage {
|
||||
@@ -37,51 +42,53 @@ impl ThermalsPage {
|
||||
|
||||
container.append(&stats_section);
|
||||
|
||||
let fan_control_section = section_box("Fan control");
|
||||
let fan_control_grid = values_grid();
|
||||
|
||||
let fan_control_enabled_switch = Switch::builder()
|
||||
.active(true)
|
||||
.halign(Align::End)
|
||||
.hexpand(true)
|
||||
.sensitive(false)
|
||||
.build();
|
||||
values_row(
|
||||
"Automatic fan control:",
|
||||
&fan_control_grid,
|
||||
&fan_control_enabled_switch,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
fan_control_section.append(&fan_control_grid);
|
||||
|
||||
let fan_curve_frame = FanCurveFrame::new();
|
||||
|
||||
fan_control_section.append(&fan_curve_frame.container);
|
||||
let fan_static_speed_frame = Box::builder()
|
||||
.orientation(Orientation::Horizontal)
|
||||
.spacing(5)
|
||||
.valign(Align::Start)
|
||||
.build();
|
||||
let fan_static_speed_adjustment = static_speed_adj(&fan_static_speed_frame);
|
||||
|
||||
// Show/hide fan curve when the switch is toggled
|
||||
{
|
||||
let fan_curve_frame = fan_curve_frame.clone();
|
||||
fan_control_enabled_switch.connect_state_set(move |_, state| {
|
||||
if state {
|
||||
show_fan_control_warning();
|
||||
fan_curve_frame.container.hide();
|
||||
} else {
|
||||
fan_curve_frame.container.show();
|
||||
}
|
||||
Inhibit(false)
|
||||
});
|
||||
}
|
||||
let fan_control_section = section_box("Fan control");
|
||||
|
||||
let fan_control_mode_stack = Stack::builder().build();
|
||||
let fan_control_mode_stack_switcher = StackSwitcher::builder()
|
||||
.stack(&fan_control_mode_stack)
|
||||
.visible(false)
|
||||
.sensitive(false)
|
||||
.build();
|
||||
|
||||
fan_control_mode_stack.add_titled(
|
||||
&Box::new(Orientation::Vertical, 15),
|
||||
Some("automatic"),
|
||||
"Automatic",
|
||||
);
|
||||
|
||||
fan_control_mode_stack.add_titled(&fan_curve_frame.container, Some("curve"), "Curve");
|
||||
|
||||
fan_control_mode_stack.add_titled(&fan_static_speed_frame, Some("static"), "Static");
|
||||
|
||||
fan_control_section.append(&fan_control_mode_stack_switcher);
|
||||
fan_control_section.append(&fan_control_mode_stack);
|
||||
|
||||
container.append(&fan_control_section);
|
||||
|
||||
fan_control_mode_stack.connect_visible_child_name_notify(|stack| {
|
||||
if stack.visible_child_name() == Some("automatic".into()) {
|
||||
show_fan_control_warning()
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
container,
|
||||
temperatures_label,
|
||||
fan_speed_label,
|
||||
fan_control_enabled_switch,
|
||||
fan_static_speed_adjustment,
|
||||
fan_curve_frame,
|
||||
fan_control_mode_stack,
|
||||
fan_control_mode_stack_switcher,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,20 +121,31 @@ impl ThermalsPage {
|
||||
}
|
||||
|
||||
if initial {
|
||||
self.fan_control_enabled_switch.set_visible(true);
|
||||
self.fan_control_enabled_switch
|
||||
self.fan_control_mode_stack_switcher.set_visible(true);
|
||||
self.fan_control_mode_stack_switcher
|
||||
.set_sensitive(stats.fan.speed_current.is_some());
|
||||
self.fan_control_enabled_switch
|
||||
.set_active(!stats.fan.control_enabled);
|
||||
|
||||
let child_name = match stats.fan.control_mode {
|
||||
Some(mode) if stats.fan.control_enabled => match mode {
|
||||
FanControlMode::Static => "static",
|
||||
FanControlMode::Curve => "curve",
|
||||
},
|
||||
_ => "automatic",
|
||||
};
|
||||
|
||||
self.fan_control_mode_stack
|
||||
.set_visible_child_name(child_name);
|
||||
|
||||
if let Some(static_speed) = &stats.fan.static_speed {
|
||||
self.fan_static_speed_adjustment
|
||||
.set_value(*static_speed * 100.0);
|
||||
}
|
||||
|
||||
if let Some(curve) = &stats.fan.curve {
|
||||
self.fan_curve_frame.set_curve(curve);
|
||||
}
|
||||
|
||||
if stats.fan.control_enabled {
|
||||
self.fan_curve_frame.container.show();
|
||||
} else {
|
||||
self.fan_curve_frame.container.hide();
|
||||
if !stats.fan.control_enabled {
|
||||
if self.fan_curve_frame.get_curve().is_empty() {
|
||||
self.fan_curve_frame.set_curve(&default_fan_curve());
|
||||
}
|
||||
@@ -136,10 +154,14 @@ impl ThermalsPage {
|
||||
}
|
||||
|
||||
pub fn connect_settings_changed<F: Fn() + 'static + Clone>(&self, f: F) {
|
||||
self.fan_control_enabled_switch
|
||||
.connect_state_set(clone!(@strong f => move |_, _| {
|
||||
self.fan_control_mode_stack
|
||||
.connect_visible_child_name_notify(clone!(@strong f => move |_| {
|
||||
f();
|
||||
}));
|
||||
|
||||
self.fan_static_speed_adjustment
|
||||
.connect_value_changed(clone!(@strong f => move |_| {
|
||||
f();
|
||||
Inhibit(false)
|
||||
}));
|
||||
|
||||
self.fan_curve_frame.connect_adjusted(move || {
|
||||
@@ -148,13 +170,26 @@ impl ThermalsPage {
|
||||
}
|
||||
|
||||
pub fn get_thermals_settings(&self) -> Option<ThermalsSettings> {
|
||||
if self.fan_control_enabled_switch.is_sensitive() {
|
||||
let manual_fan_control = !self.fan_control_enabled_switch.state();
|
||||
if self.fan_control_mode_stack_switcher.is_sensitive() {
|
||||
let name = self.fan_control_mode_stack.visible_child_name();
|
||||
let name = name
|
||||
.as_ref()
|
||||
.map(|name| name.as_str())
|
||||
.expect("No name on the visible child");
|
||||
let (manual_fan_control, mode) = match name {
|
||||
"automatic" => (false, None),
|
||||
"curve" => (true, Some(FanControlMode::Curve)),
|
||||
"static" => (true, Some(FanControlMode::Static)),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let static_speed = Some(self.fan_static_speed_adjustment.value() / 100.0);
|
||||
let curve = self.fan_curve_frame.get_curve();
|
||||
let curve = if curve.is_empty() { None } else { Some(curve) };
|
||||
|
||||
Some(ThermalsSettings {
|
||||
manual_fan_control,
|
||||
mode,
|
||||
static_speed,
|
||||
curve,
|
||||
})
|
||||
} else {
|
||||
@@ -163,6 +198,45 @@ impl ThermalsPage {
|
||||
}
|
||||
}
|
||||
|
||||
fn static_speed_adj(parent_box: &Box) -> Adjustment {
|
||||
let label = Label::builder()
|
||||
.label("Speed (in %)")
|
||||
.halign(Align::Start)
|
||||
.build();
|
||||
|
||||
let adjustment = Adjustment::new(0.0, 0.0, 100.0, 0.1, 1.0, 0.0);
|
||||
|
||||
let scale = Scale::builder()
|
||||
.orientation(Orientation::Horizontal)
|
||||
.adjustment(&adjustment)
|
||||
.hexpand(true)
|
||||
.margin_start(5)
|
||||
.margin_end(5)
|
||||
.build();
|
||||
|
||||
let value_selector = SpinButton::new(Some(&adjustment), 1.0, 1);
|
||||
let value_label = Label::new(None);
|
||||
|
||||
let popover = Popover::builder().child(&value_selector).build();
|
||||
let value_button = MenuButton::builder()
|
||||
.popover(&popover)
|
||||
.child(&value_label)
|
||||
.build();
|
||||
|
||||
adjustment.connect_value_changed(clone!(@strong value_label => move |adjustment| {
|
||||
let value = adjustment.value();
|
||||
value_label.set_text(&format!("{value:.1}"));
|
||||
}));
|
||||
|
||||
adjustment.set_value(50.0);
|
||||
|
||||
parent_box.append(&label);
|
||||
parent_box.append(&scale);
|
||||
parent_box.append(&value_button);
|
||||
|
||||
adjustment
|
||||
}
|
||||
|
||||
fn show_fan_control_warning() {
|
||||
let diag = MessageDialog::new(None::<&Window>, DialogFlags::empty(), MessageType::Warning, ButtonsType::Ok,
|
||||
"Warning! Due to a driver bug, a reboot may be required for fan control to properly switch back to automatic.");
|
||||
|
||||
@@ -22,8 +22,29 @@ use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{BTreeMap, HashMap},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum FanControlMode {
|
||||
Static,
|
||||
#[default]
|
||||
Curve,
|
||||
}
|
||||
|
||||
impl FromStr for FanControlMode {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"curve" => Ok(Self::Curve),
|
||||
"static" => Ok(Self::Static),
|
||||
_ => Err("unknown fan control mode".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type FanCurveMap = BTreeMap<i32, f32>;
|
||||
|
||||
pub fn default_fan_curve() -> FanCurveMap {
|
||||
@@ -165,6 +186,8 @@ pub struct DeviceStats {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct FanStats {
|
||||
pub control_enabled: bool,
|
||||
pub control_mode: Option<FanControlMode>,
|
||||
pub static_speed: Option<f64>,
|
||||
pub curve: Option<FanCurveMap>,
|
||||
pub speed_current: Option<u32>,
|
||||
pub speed_max: Option<u32>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::FanCurveMap;
|
||||
use crate::{FanControlMode, FanCurveMap};
|
||||
use amdgpu_sysfs::gpu_handle::PerformanceLevel;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -23,6 +23,8 @@ pub enum Request<'a> {
|
||||
SetFanControl {
|
||||
id: &'a str,
|
||||
enabled: bool,
|
||||
mode: Option<FanControlMode>,
|
||||
static_speed: Option<f64>,
|
||||
curve: Option<FanCurveMap>,
|
||||
},
|
||||
SetPowerCap {
|
||||
|
||||
Reference in New Issue
Block a user