mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2026-11-03 12:50:31 -06:00
refactor: migrate thermals-page to adjustmentRow (#1208)
* move adjustmentRow refactoring * refactor back to FactoryComponent * migrate thermals page to adjustmentRow * remove thermal page tabs * block signal * basic fixes * layout fixes * fanCruveFrame: extract chart state into it's own struct * don't create uneeded adjustments instead of hiding them
This commit is contained in:
1 parent
ad250926ef
commit
37f7bc0842
3 files changed
+487
-710
No files matched your search
@@ -0,0 +1,22 @@
|
||||
.thermals-page .adjustment-row > scale {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.thermals-page .adjustment-row > spinbutton {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.fan-control-section .adjustment-list {
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.fan-control-section .adjustment-list > row {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.fan-control-section .fan-control-option {
|
||||
margin: 5px 10px;
|
||||
}
|
||||
@@ -7,24 +7,22 @@ use crate::app::pages::PageUpdate;
|
||||
use crate::{
|
||||
APP_BROKER, I18N,
|
||||
app::{
|
||||
components::{adjustment_value::AdjustmentValue, page_section::PageSection},
|
||||
components::{
|
||||
adjustment_card::AdjustmentCard,
|
||||
adjustment_row::{AdjustmentRow, AdjustmentRowInit, AdjustmentRowMsg},
|
||||
page_section::PageSection,
|
||||
},
|
||||
msg::AppMsg,
|
||||
utils::ext::{RelmLaunchable as _, make_event_controller_no_scroll},
|
||||
utils::ext::RelmLaunchable as _,
|
||||
},
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use amdgpu_sysfs::gpu_handle::fan_control::FanInfo;
|
||||
use anyhow::anyhow;
|
||||
use fan_curve_frame::{
|
||||
CurveSetupMsg, DEFAULT_SPEED_RANGE, DEFAULT_TEMP_RANGE, FanCurveFrame, FanCurveFrameMsg,
|
||||
};
|
||||
use gtk::{
|
||||
Adjustment,
|
||||
glib::{
|
||||
self, SignalHandlerId,
|
||||
object::{Cast, ObjectExt},
|
||||
},
|
||||
prelude::{AdjustmentExt, BoxExt, ButtonExt, OrientableExt, RangeExt, ScaleExt, WidgetExt},
|
||||
};
|
||||
use gtk::glib::{self, SignalHandlerId};
|
||||
use i18n_embed_fl::fl;
|
||||
use lact_schema::{
|
||||
DeviceFlag, FanControlMode,
|
||||
@@ -32,76 +30,46 @@ use lact_schema::{
|
||||
default_fan_curve,
|
||||
};
|
||||
use relm4::{
|
||||
ComponentController, ComponentParts, ComponentSender, RelmObjectExt, RelmWidgetExt,
|
||||
binding::{Binding, BoolBinding, ConnectBinding, StringBinding},
|
||||
ComponentController, ComponentParts, ComponentSender, RelmWidgetExt, WidgetTemplate,
|
||||
binding::{Binding, BoolBinding, ConnectBinding},
|
||||
factory::FactoryHashMap,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
const AUTO_PAGE: &str = "automatic";
|
||||
const CURVE_PAGE: &str = "curve";
|
||||
const STATIC_PAGE: &str = "static";
|
||||
|
||||
pub struct ThermalsPage {
|
||||
stats_section: relm4::Controller<GpuStatsSection>,
|
||||
pmfw_rows: FactoryHashMap<ThermalSetting, AdjustmentRow<ThermalSetting>>,
|
||||
nvidia_target_temperature: FactoryHashMap<(), AdjustmentRow<()>>,
|
||||
zero_rpm_temperature: FactoryHashMap<(), AdjustmentRow<()>>,
|
||||
static_speed: FactoryHashMap<(), AdjustmentRow<()>>,
|
||||
fan_curve_frame: relm4::Controller<FanCurveFrame>,
|
||||
selected_mode: StringBinding,
|
||||
selected_mode: Option<FanControlMode>,
|
||||
|
||||
custom_control_supported: bool,
|
||||
has_fan_speed: bool,
|
||||
has_pmfw: bool,
|
||||
has_auto_threshold: bool,
|
||||
pmfw_options: PmfwOptions,
|
||||
nvidia_thermal_options: NvidiaThermalOptions,
|
||||
option_change_signals: Vec<(glib::Object, SignalHandlerId)>,
|
||||
|
||||
static_speed_adj: Adjustment,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct PmfwOptions {
|
||||
target_temperature: AdjustmentValue,
|
||||
acoustic_limit: AdjustmentValue,
|
||||
acoustic_target: AdjustmentValue,
|
||||
minimum_pwm: AdjustmentValue,
|
||||
zero_rpm_temperature: AdjustmentValue,
|
||||
zero_rpm_available: Rc<Cell<bool>>,
|
||||
label_size_group: gtk::SizeGroup,
|
||||
input_size_group: gtk::SizeGroup,
|
||||
zero_rpm: BoolBinding,
|
||||
zero_rpm_available: bool,
|
||||
zero_rpm_change_signal: SignalHandlerId,
|
||||
target_temperature_default: Option<u32>,
|
||||
}
|
||||
|
||||
impl PmfwOptions {
|
||||
fn adjustments(&self) -> [&AdjustmentValue; 5] {
|
||||
[
|
||||
&self.target_temperature,
|
||||
&self.acoustic_limit,
|
||||
&self.acoustic_target,
|
||||
&self.minimum_pwm,
|
||||
&self.zero_rpm_temperature,
|
||||
]
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.adjustments().iter().all(|adj| adj_is_empty(adj)) && !self.zero_rpm_available.get()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct NvidiaThermalOptions {
|
||||
target_temperature: AdjustmentValue,
|
||||
target_temperaure_default: Option<u32>,
|
||||
}
|
||||
|
||||
impl NvidiaThermalOptions {
|
||||
fn adjustments(&self) -> [&AdjustmentValue; 1] {
|
||||
[&self.target_temperature]
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ThermalSetting {
|
||||
TargetTemperature,
|
||||
AcousticLimit,
|
||||
AcousticTarget,
|
||||
MinimumPwm,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ThermalsPageMsg {
|
||||
Update { update: PageUpdate, initial: bool },
|
||||
FanModeSelected,
|
||||
FanModeSelected(Option<FanControlMode>),
|
||||
RestNvidiaOptions,
|
||||
}
|
||||
|
||||
@@ -113,8 +81,10 @@ impl relm4::Component for ThermalsPage {
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
#[root]
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
add_css_class: "thermals-page",
|
||||
set_spacing: 15,
|
||||
set_margin_all: 15,
|
||||
set_margin_top: 20, // align with gpu picker
|
||||
@@ -123,8 +93,9 @@ impl relm4::Component for ThermalsPage {
|
||||
|
||||
PageSection {
|
||||
set_name: fl!(I18N, "thresholds-section"),
|
||||
set_hide_visible_container: true,
|
||||
#[watch]
|
||||
set_visible: !adj_is_empty(&model.nvidia_thermal_options.target_temperature),
|
||||
set_visible: !model.nvidia_target_temperature.is_empty(),
|
||||
|
||||
append_header = >k::Button {
|
||||
set_label: &fl!(I18N, "default-button"),
|
||||
@@ -133,227 +104,120 @@ impl relm4::Component for ThermalsPage {
|
||||
connect_clicked => ThermalsPageMsg::RestNvidiaOptions,
|
||||
},
|
||||
|
||||
#[template]
|
||||
append_child = &FanSettingRow {
|
||||
#[template_child]
|
||||
label {
|
||||
set_label: &fl!(I18N, "target-temp"),
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
scale {
|
||||
set_adjustment: &model.nvidia_thermal_options.target_temperature,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
spinbutton {
|
||||
set_adjustment: &model.nvidia_thermal_options.target_temperature,
|
||||
set_size_group: &spin_size_group,
|
||||
},
|
||||
},
|
||||
append_child = model.nvidia_target_temperature.widget(),
|
||||
},
|
||||
|
||||
PageSection {
|
||||
set_name: fl!(I18N, "fan-control-section"),
|
||||
add_css_class: "fan-control-section",
|
||||
// Disable fan configuration when overdrive is disabled on GPUs that have PMFW (RDNA3+)
|
||||
#[watch]
|
||||
set_sensitive: model.custom_control_supported,
|
||||
|
||||
append_child = >k::StackSwitcher {
|
||||
set_stack: Some(&stack),
|
||||
append_child = >k::Box {
|
||||
add_css_class: "linked",
|
||||
set_homogeneous: true,
|
||||
|
||||
#[name = "automatic_mode"]
|
||||
gtk::ToggleButton {
|
||||
set_label: &fl!(I18N, "auto-page"),
|
||||
#[watch]
|
||||
#[block_signal(automatic_mode_signal)]
|
||||
set_active: model.selected_mode.is_none(),
|
||||
connect_toggled[sender] => move |button| {
|
||||
if button.is_active() {
|
||||
sender.input(ThermalsPageMsg::FanModeSelected(None));
|
||||
}
|
||||
} @ automatic_mode_signal,
|
||||
},
|
||||
|
||||
gtk::ToggleButton {
|
||||
set_label: &fl!(I18N, "curve-page"),
|
||||
set_group: Some(&automatic_mode),
|
||||
#[watch]
|
||||
#[block_signal(curve_mode_signal)]
|
||||
set_active: model.selected_mode == Some(FanControlMode::Curve),
|
||||
connect_toggled[sender] => move |button| {
|
||||
if button.is_active() {
|
||||
sender.input(ThermalsPageMsg::FanModeSelected(Some(FanControlMode::Curve)));
|
||||
}
|
||||
} @ curve_mode_signal,
|
||||
},
|
||||
|
||||
gtk::ToggleButton {
|
||||
set_label: &fl!(I18N, "static-page"),
|
||||
set_group: Some(&automatic_mode),
|
||||
#[watch]
|
||||
#[block_signal(static_mode_signal)]
|
||||
set_active: model.selected_mode == Some(FanControlMode::Static),
|
||||
connect_toggled[sender] => move |button| {
|
||||
if button.is_active() {
|
||||
sender.input(ThermalsPageMsg::FanModeSelected(Some(FanControlMode::Static)));
|
||||
}
|
||||
} @ static_mode_signal,
|
||||
},
|
||||
},
|
||||
|
||||
#[name = "stack"]
|
||||
append_child = >k::Stack {
|
||||
set_vexpand: false,
|
||||
set_vhomogeneous: false,
|
||||
append_child = >k::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
#[watch]
|
||||
set_visible: model.fan_settings_available(),
|
||||
|
||||
add_titled[Some(AUTO_PAGE), &fl!(I18N, "auto-page")] = >k::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
model.pmfw_rows.widget().clone() -> gtk::ListBox {
|
||||
#[watch]
|
||||
set_visible: model.selected_mode.is_none() && !model.pmfw_rows.is_empty(),
|
||||
},
|
||||
|
||||
model.fan_curve_frame.widget().clone() -> gtk::Box {
|
||||
#[watch]
|
||||
set_visible: model.selected_mode == Some(FanControlMode::Curve),
|
||||
},
|
||||
|
||||
model.static_speed.widget().clone() -> gtk::ListBox {
|
||||
#[watch]
|
||||
set_visible: model.selected_mode == Some(FanControlMode::Static),
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
add_css_class: "fan-control-option",
|
||||
set_spacing: 5,
|
||||
#[watch]
|
||||
set_visible: model.selected_mode != Some(FanControlMode::Static)
|
||||
&& model.zero_rpm_available,
|
||||
|
||||
#[template]
|
||||
FanSettingRow {
|
||||
#[watch]
|
||||
set_visible: !adj_is_empty(&model.pmfw_options.target_temperature),
|
||||
|
||||
#[template_child]
|
||||
label {
|
||||
set_label: &fl!(I18N, "target-temp"),
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
scale {
|
||||
set_adjustment: &model.pmfw_options.target_temperature,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
spinbutton {
|
||||
set_adjustment: &model.pmfw_options.target_temperature,
|
||||
set_size_group: &spin_size_group,
|
||||
},
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "zero-rpm"),
|
||||
set_size_group: &model.label_size_group,
|
||||
set_xalign: 0.0,
|
||||
},
|
||||
|
||||
#[template]
|
||||
FanSettingRow {
|
||||
#[watch]
|
||||
set_visible: !adj_is_empty(&model.pmfw_options.acoustic_limit),
|
||||
|
||||
#[template_child]
|
||||
label {
|
||||
set_label: &fl!(I18N, "acoustic-limit"),
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
scale {
|
||||
set_adjustment: &model.pmfw_options.acoustic_limit,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
spinbutton {
|
||||
set_adjustment: &model.pmfw_options.acoustic_limit,
|
||||
set_size_group: &spin_size_group,
|
||||
},
|
||||
},
|
||||
|
||||
#[template]
|
||||
FanSettingRow {
|
||||
#[watch]
|
||||
set_visible: !adj_is_empty(&model.pmfw_options.acoustic_target),
|
||||
|
||||
#[template_child]
|
||||
label {
|
||||
set_label: &fl!(I18N, "acoustic-target"),
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
scale {
|
||||
set_adjustment: &model.pmfw_options.acoustic_target,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
spinbutton {
|
||||
set_adjustment: &model.pmfw_options.acoustic_target,
|
||||
set_size_group: &spin_size_group,
|
||||
},
|
||||
},
|
||||
|
||||
#[template]
|
||||
FanSettingRow {
|
||||
#[watch]
|
||||
set_visible: !adj_is_empty(&model.pmfw_options.minimum_pwm),
|
||||
|
||||
#[template_child]
|
||||
label {
|
||||
set_label: &fl!(I18N, "min-fan-speed"),
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
scale {
|
||||
set_adjustment: &model.pmfw_options.minimum_pwm,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
spinbutton {
|
||||
set_adjustment: &model.pmfw_options.minimum_pwm,
|
||||
set_size_group: &spin_size_group,
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
#[watch]
|
||||
set_visible: model.pmfw_options.zero_rpm_available.get(),
|
||||
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "zero-rpm"),
|
||||
set_xalign: 0.0,
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
gtk::Switch {
|
||||
bind: &model.pmfw_options.zero_rpm,
|
||||
set_hexpand: true,
|
||||
set_halign: gtk::Align::End,
|
||||
},
|
||||
},
|
||||
|
||||
#[template]
|
||||
FanSettingRow {
|
||||
#[watch]
|
||||
set_visible: !adj_is_empty(&model.pmfw_options.zero_rpm_temperature),
|
||||
|
||||
#[template_child]
|
||||
label {
|
||||
set_label: &fl!(I18N, "zero-rpm-stop-temp"),
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
scale {
|
||||
set_adjustment: &model.pmfw_options.zero_rpm_temperature,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
spinbutton {
|
||||
set_adjustment: &model.pmfw_options.zero_rpm_temperature,
|
||||
set_size_group: &spin_size_group,
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: &fl!(I18N, "reset-now-button"),
|
||||
gtk::Switch {
|
||||
bind: &model.zero_rpm,
|
||||
set_hexpand: true,
|
||||
set_halign: gtk::Align::End,
|
||||
set_margin_vertical: 5,
|
||||
set_tooltip_text: Some(&fl!(I18N, "pmfw-reset-warning")),
|
||||
add_css_class: "destructive-action",
|
||||
set_size_group: &spin_size_group,
|
||||
#[watch]
|
||||
set_visible: !model.pmfw_options.is_empty(),
|
||||
connect_clicked => move |_| {
|
||||
APP_BROKER.send(AppMsg::ResetPmfw);
|
||||
}
|
||||
},
|
||||
},
|
||||
add_titled[Some(CURVE_PAGE), &fl!(I18N, "curve-page")] = model.fan_curve_frame.widget(),
|
||||
add_titled[Some(STATIC_PAGE), &fl!(I18N, "static-page")] = >k::Box {
|
||||
set_valign: gtk::Align::Start,
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
|
||||
#[template]
|
||||
#[name = "static_speed_row"]
|
||||
FanSettingRow {
|
||||
#[template_child]
|
||||
label {
|
||||
set_label: &fl!(I18N, "static-speed"),
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
scale {
|
||||
set_adjustment: &model.static_speed_adj,
|
||||
connect_value_changed => move |_| {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
} @ static_speed_changed_signal,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
spinbutton {
|
||||
set_adjustment: &model.static_speed_adj,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
add_binding: (&model.selected_mode, "visible-child-name"),
|
||||
connect_visible_child_name_notify => ThermalsPageMsg::FanModeSelected @ mode_selected_signal,
|
||||
model.zero_rpm_temperature.widget().clone() -> gtk::ListBox {
|
||||
#[watch]
|
||||
set_visible: model.selected_mode != Some(FanControlMode::Static)
|
||||
&& !model.zero_rpm_temperature.is_empty(),
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_label: &fl!(I18N, "reset-now-button"),
|
||||
set_size_group: &model.input_size_group,
|
||||
set_halign: gtk::Align::End,
|
||||
set_margin_vertical: 5,
|
||||
set_tooltip_text: Some(&fl!(I18N, "pmfw-reset-warning")),
|
||||
add_css_class: "destructive-action",
|
||||
#[watch]
|
||||
set_visible: model.selected_mode.is_none() && model.has_pmfw_options(),
|
||||
connect_clicked => move |_| {
|
||||
APP_BROKER.send(AppMsg::ResetPmfw);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -362,52 +226,13 @@ impl relm4::Component for ThermalsPage {
|
||||
fn init(
|
||||
_init: Self::Init,
|
||||
root: Self::Root,
|
||||
_sender: ComponentSender<Self>,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let pmfw_options = PmfwOptions::default();
|
||||
let nvidia_thermal_options = NvidiaThermalOptions::default();
|
||||
|
||||
for adj in pmfw_options.adjustments() {
|
||||
adj.set_step_increment(1.0);
|
||||
adj.set_page_increment(5.0);
|
||||
}
|
||||
|
||||
for adj in nvidia_thermal_options.adjustments() {
|
||||
adj.set_step_increment(1.0);
|
||||
adj.set_page_increment(5.0);
|
||||
}
|
||||
|
||||
let pmfw_change_signals = pmfw_options
|
||||
.adjustments()
|
||||
.into_iter()
|
||||
.map(|adj| {
|
||||
let signal = adj.connect_value_changed(|_| {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
});
|
||||
(adj.clone().upcast(), signal)
|
||||
})
|
||||
.chain([(
|
||||
pmfw_options.zero_rpm.clone().upcast(),
|
||||
pmfw_options.zero_rpm.connect_value_notify(|_| {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
}),
|
||||
)]);
|
||||
|
||||
let nvidia_thermal_change_signals =
|
||||
nvidia_thermal_options.adjustments().into_iter().map(|adj| {
|
||||
let signal = adj.connect_value_changed(|_| {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
});
|
||||
(adj.clone().upcast(), signal)
|
||||
});
|
||||
|
||||
let option_change_signals = pmfw_change_signals
|
||||
.chain(nvidia_thermal_change_signals)
|
||||
.collect();
|
||||
|
||||
let fan_curve_frame = FanCurveFrame::builder()
|
||||
.launch(pmfw_options.clone())
|
||||
.detach();
|
||||
let zero_rpm = BoolBinding::new(false);
|
||||
let zero_rpm_change_signal = zero_rpm.connect_value_notify(|_| {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
});
|
||||
let fan_curve_frame = FanCurveFrame::detach(());
|
||||
let stats_section = GpuStatsSection::detach(GpuStatsSectionConfig {
|
||||
stats: HashSet::from([
|
||||
GpuStat::Throttling,
|
||||
@@ -418,22 +243,33 @@ impl relm4::Component for ThermalsPage {
|
||||
});
|
||||
|
||||
let model = Self {
|
||||
pmfw_rows: FactoryHashMap::builder()
|
||||
.launch(AdjustmentCard::init(()).content.clone())
|
||||
.forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged),
|
||||
nvidia_target_temperature: FactoryHashMap::builder()
|
||||
.launch(AdjustmentCard::init(()).content.clone())
|
||||
.forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged),
|
||||
zero_rpm_temperature: FactoryHashMap::builder()
|
||||
.launch(AdjustmentCard::init(()).content.clone())
|
||||
.forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged),
|
||||
static_speed: FactoryHashMap::builder()
|
||||
.launch(AdjustmentCard::init(()).content.clone())
|
||||
.forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged),
|
||||
stats_section,
|
||||
fan_curve_frame,
|
||||
pmfw_options,
|
||||
nvidia_thermal_options,
|
||||
option_change_signals,
|
||||
label_size_group: gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal),
|
||||
input_size_group: gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal),
|
||||
zero_rpm,
|
||||
zero_rpm_available: false,
|
||||
zero_rpm_change_signal,
|
||||
target_temperature_default: None,
|
||||
custom_control_supported: false,
|
||||
has_fan_speed: false,
|
||||
has_pmfw: false,
|
||||
has_auto_threshold: false,
|
||||
static_speed_adj: Adjustment::new(50.0, 0.0, 100.0, 1.0, 5.0, 0.0),
|
||||
selected_mode: StringBinding::new(AUTO_PAGE),
|
||||
selected_mode: None,
|
||||
};
|
||||
|
||||
let label_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
|
||||
let spin_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
@@ -464,17 +300,8 @@ impl relm4::Component for ThermalsPage {
|
||||
self.has_fan_speed =
|
||||
stats.fan.pwm_current.is_some() || stats.fan.speed_current.is_some();
|
||||
if initial {
|
||||
let page_name = match stats.fan.control_mode {
|
||||
Some(mode) if stats.fan.control_enabled => match mode {
|
||||
FanControlMode::Static => STATIC_PAGE,
|
||||
FanControlMode::Curve => CURVE_PAGE,
|
||||
},
|
||||
_ => AUTO_PAGE,
|
||||
};
|
||||
|
||||
widgets.stack.block_signal(&widgets.mode_selected_signal);
|
||||
self.selected_mode.set(page_name.to_owned());
|
||||
widgets.stack.unblock_signal(&widgets.mode_selected_signal);
|
||||
self.selected_mode =
|
||||
stats.fan.control_mode.filter(|_| stats.fan.control_enabled);
|
||||
|
||||
let speed_range = stats
|
||||
.fan
|
||||
@@ -487,22 +314,22 @@ impl relm4::Component for ThermalsPage {
|
||||
})
|
||||
.unwrap_or(DEFAULT_SPEED_RANGE);
|
||||
|
||||
widgets
|
||||
.static_speed_row
|
||||
.scale
|
||||
.block_signal(&widgets.static_speed_changed_signal);
|
||||
self.pmfw_rows.clear();
|
||||
self.nvidia_target_temperature.clear();
|
||||
self.zero_rpm_temperature.clear();
|
||||
self.static_speed.clear();
|
||||
|
||||
self.static_speed_adj
|
||||
.set_lower((*speed_range.start() as f64 * 100.0).round());
|
||||
self.static_speed_adj
|
||||
.set_upper((*speed_range.end() as f64 * 100.0).round());
|
||||
self.static_speed_adj
|
||||
.set_value((stats.fan.static_speed.unwrap_or(0.5) * 100.0).into());
|
||||
|
||||
widgets
|
||||
.static_speed_row
|
||||
.scale
|
||||
.unblock_signal(&widgets.static_speed_changed_signal);
|
||||
self.static_speed.insert(
|
||||
(),
|
||||
AdjustmentRowInit {
|
||||
title: glib::markup_escape_text(&fl!(I18N, "static-speed")).into(),
|
||||
value: (stats.fan.static_speed.unwrap_or(0.5) * 100.0).into(),
|
||||
lower: (*speed_range.start() as f64 * 100.0).round(),
|
||||
upper: (*speed_range.end() as f64 * 100.0).round(),
|
||||
page_increment: 5.0,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let temperature_range = stats
|
||||
.fan
|
||||
@@ -525,49 +352,87 @@ impl relm4::Component for ThermalsPage {
|
||||
self.fan_curve_frame.emit(FanCurveFrameMsg::Curve(msg));
|
||||
|
||||
let info = stats.fan.pmfw_info;
|
||||
let pmfw_options = &mut self.pmfw_options;
|
||||
let nvidia_thermal_options = &mut self.nvidia_thermal_options;
|
||||
|
||||
for (obj, signal) in &self.option_change_signals {
|
||||
obj.block_signal(signal);
|
||||
let lower_label_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
|
||||
let upper_label_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
|
||||
for (setting, title, info) in [
|
||||
(
|
||||
ThermalSetting::TargetTemperature,
|
||||
fl!(I18N, "target-temp"),
|
||||
info.target_temp,
|
||||
),
|
||||
(
|
||||
ThermalSetting::AcousticLimit,
|
||||
fl!(I18N, "acoustic-limit"),
|
||||
info.acoustic_limit,
|
||||
),
|
||||
(
|
||||
ThermalSetting::AcousticTarget,
|
||||
fl!(I18N, "acoustic-target"),
|
||||
info.acoustic_target,
|
||||
),
|
||||
(
|
||||
ThermalSetting::MinimumPwm,
|
||||
fl!(I18N, "min-fan-speed"),
|
||||
info.minimum_pwm,
|
||||
),
|
||||
] {
|
||||
if let Some(init) = fan_info_init(title, info) {
|
||||
self.pmfw_rows.insert(setting, init);
|
||||
self.pmfw_rows.send(
|
||||
&setting,
|
||||
AdjustmentRowMsg::AddSizeGroup {
|
||||
label_group: self.label_size_group.clone(),
|
||||
input_group: self.input_size_group.clone(),
|
||||
lower_label_group: lower_label_group.clone(),
|
||||
upper_label_group: upper_label_group.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
set_fan_info(&pmfw_options.acoustic_limit, info.acoustic_limit);
|
||||
set_fan_info(&pmfw_options.acoustic_target, info.acoustic_target);
|
||||
set_fan_info(&pmfw_options.minimum_pwm, info.minimum_pwm);
|
||||
set_fan_info(&pmfw_options.target_temperature, info.target_temp);
|
||||
set_fan_info(
|
||||
&nvidia_thermal_options.target_temperature,
|
||||
stats.nvidia_thermal_info.target_temp,
|
||||
);
|
||||
nvidia_thermal_options.target_temperaure_default =
|
||||
for (rows, title, info) in [
|
||||
(
|
||||
&mut self.nvidia_target_temperature,
|
||||
fl!(I18N, "target-temp"),
|
||||
stats.nvidia_thermal_info.target_temp,
|
||||
),
|
||||
(
|
||||
&mut self.zero_rpm_temperature,
|
||||
fl!(I18N, "zero-rpm-stop-temp"),
|
||||
info.zero_rpm_temperature,
|
||||
),
|
||||
] {
|
||||
if let Some(init) = fan_info_init(title, info) {
|
||||
rows.insert((), init);
|
||||
rows.send(
|
||||
&(),
|
||||
AdjustmentRowMsg::AddSizeGroup {
|
||||
label_group: self.label_size_group.clone(),
|
||||
input_group: self.input_size_group.clone(),
|
||||
lower_label_group: lower_label_group.clone(),
|
||||
upper_label_group: upper_label_group.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
self.target_temperature_default =
|
||||
stats.nvidia_thermal_info.target_temp_default;
|
||||
set_fan_info(
|
||||
&pmfw_options.zero_rpm_temperature,
|
||||
info.zero_rpm_temperature,
|
||||
);
|
||||
|
||||
pmfw_options
|
||||
.zero_rpm_available
|
||||
.set(info.zero_rpm_enable.is_some());
|
||||
pmfw_options
|
||||
.zero_rpm
|
||||
.set(info.zero_rpm_enable.unwrap_or(false));
|
||||
|
||||
for (obj, signal) in &self.option_change_signals {
|
||||
obj.unblock_signal(signal);
|
||||
}
|
||||
self.zero_rpm_available = info.zero_rpm_enable.is_some();
|
||||
self.zero_rpm.block_signal(&self.zero_rpm_change_signal);
|
||||
self.zero_rpm.set(info.zero_rpm_enable.unwrap_or(false));
|
||||
self.zero_rpm.unblock_signal(&self.zero_rpm_change_signal);
|
||||
}
|
||||
}
|
||||
},
|
||||
ThermalsPageMsg::FanModeSelected => {
|
||||
ThermalsPageMsg::FanModeSelected(mode) => {
|
||||
self.selected_mode = mode;
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
}
|
||||
ThermalsPageMsg::RestNvidiaOptions => {
|
||||
if let Some(default) = self.nvidia_thermal_options.target_temperaure_default {
|
||||
self.nvidia_thermal_options
|
||||
.target_temperature
|
||||
.set_value(default as f64);
|
||||
if let Some(default) = self.target_temperature_default {
|
||||
if !self.nvidia_target_temperature.is_empty() {
|
||||
self.nvidia_target_temperature
|
||||
.send(&(), AdjustmentRowMsg::SetValue(default as f64));
|
||||
}
|
||||
} else {
|
||||
APP_BROKER.send(AppMsg::Error(Arc::new(anyhow!(
|
||||
"No default target temperature present"
|
||||
@@ -582,23 +447,18 @@ impl relm4::Component for ThermalsPage {
|
||||
|
||||
impl ThermalsPage {
|
||||
fn fan_settings_available(&self) -> bool {
|
||||
self.has_fan_speed
|
||||
&& (self.selected_mode.value() != AUTO_PAGE || !self.pmfw_options.is_empty())
|
||||
self.has_fan_speed && (self.selected_mode.is_some() || self.has_pmfw_options())
|
||||
}
|
||||
|
||||
pub fn apply_config(&self, config: &mut GpuConfig) {
|
||||
let selected_page = self.selected_mode.value();
|
||||
|
||||
if selected_page == AUTO_PAGE {
|
||||
config.fan_control_enabled = false;
|
||||
} else {
|
||||
if let Some(mode) = self.selected_mode {
|
||||
config.fan_control_enabled = true;
|
||||
let fan_settings = config
|
||||
.fan_control_settings
|
||||
.get_or_insert_with(FanControlSettings::default);
|
||||
|
||||
match selected_page.as_str() {
|
||||
CURVE_PAGE => {
|
||||
match mode {
|
||||
FanControlMode::Curve => {
|
||||
fan_settings.mode = FanControlMode::Curve;
|
||||
|
||||
let fan_curve_model = self.fan_curve_frame.model();
|
||||
@@ -614,103 +474,78 @@ impl ThermalsPage {
|
||||
fan_settings.temperature_key = temp_key;
|
||||
}
|
||||
}
|
||||
STATIC_PAGE => {
|
||||
FanControlMode::Static => {
|
||||
fan_settings.mode = FanControlMode::Static;
|
||||
fan_settings.static_speed = self.static_speed_adj.value() as f32 / 100.0;
|
||||
fan_settings.static_speed = self
|
||||
.static_speed
|
||||
.get(&())
|
||||
.map(|row| row.get_value() as f32 / 100.0)
|
||||
.unwrap_or(0.5);
|
||||
}
|
||||
_ => unreachable!("Invalid fan control page selected"),
|
||||
}
|
||||
} else {
|
||||
config.fan_control_enabled = false;
|
||||
}
|
||||
|
||||
let pmfw = &self.pmfw_options;
|
||||
let pmfw_config = &mut config.pmfw_options;
|
||||
let options = [
|
||||
(&pmfw.acoustic_limit, &mut pmfw_config.acoustic_limit),
|
||||
(&pmfw.acoustic_target, &mut pmfw_config.acoustic_target),
|
||||
for (setting, config_value) in [
|
||||
(
|
||||
&pmfw.target_temperature,
|
||||
ThermalSetting::AcousticLimit,
|
||||
&mut pmfw_config.acoustic_limit,
|
||||
),
|
||||
(
|
||||
ThermalSetting::AcousticTarget,
|
||||
&mut pmfw_config.acoustic_target,
|
||||
),
|
||||
(
|
||||
ThermalSetting::TargetTemperature,
|
||||
&mut pmfw_config.target_temperature,
|
||||
),
|
||||
(&pmfw.minimum_pwm, &mut pmfw_config.minimum_pwm),
|
||||
(
|
||||
&pmfw.zero_rpm_temperature,
|
||||
&mut pmfw_config.zero_rpm_threshold,
|
||||
),
|
||||
];
|
||||
|
||||
for (adj, config_value) in options {
|
||||
if let Some(value) = adj.get_changed_value(false) {
|
||||
(ThermalSetting::MinimumPwm, &mut pmfw_config.minimum_pwm),
|
||||
] {
|
||||
if let Some(value) = self
|
||||
.pmfw_rows
|
||||
.get(&setting)
|
||||
.and_then(|row| row.get_changed_value())
|
||||
{
|
||||
*config_value = Some(value as u32);
|
||||
}
|
||||
}
|
||||
|
||||
if pmfw.zero_rpm_available.get() {
|
||||
pmfw_config.zero_rpm = Some(pmfw.zero_rpm.value());
|
||||
}
|
||||
|
||||
if let Some(value) = self
|
||||
.nvidia_thermal_options
|
||||
.target_temperature
|
||||
.get_changed_value(false)
|
||||
.zero_rpm_temperature
|
||||
.get(&())
|
||||
.and_then(|row| row.get_changed_value())
|
||||
{
|
||||
pmfw_config.zero_rpm_threshold = Some(value as u32);
|
||||
}
|
||||
if let Some(value) = self
|
||||
.nvidia_target_temperature
|
||||
.get(&())
|
||||
.and_then(|row| row.get_changed_value())
|
||||
{
|
||||
config.nvidia_thermal_options.target_temperature = Some(value as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[relm4::widget_template(pub)]
|
||||
impl relm4::WidgetTemplate for FanSettingRow {
|
||||
view! {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
|
||||
#[name = "label"]
|
||||
gtk::Label {
|
||||
set_xalign: 0.0,
|
||||
},
|
||||
|
||||
#[name = "scale"]
|
||||
gtk::Scale {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_hexpand: true,
|
||||
set_digits: 0,
|
||||
set_round_digits: 0,
|
||||
set_value_pos: gtk::PositionType::Right,
|
||||
set_margin_horizontal: 5,
|
||||
add_controller = make_event_controller_no_scroll(),
|
||||
},
|
||||
|
||||
#[name = "spinbutton"]
|
||||
gtk::SpinButton {
|
||||
set_width_request: 120,
|
||||
add_controller = make_event_controller_no_scroll(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn adj_is_empty(adj: &AdjustmentValue) -> bool {
|
||||
adj.lower() == 0.0 && adj.upper() == 0.0
|
||||
}
|
||||
|
||||
fn set_fan_info(adjustment: &AdjustmentValue, info: Option<FanInfo>) {
|
||||
match info {
|
||||
Some(info) => {
|
||||
if let Some((min, max)) = info.allowed_range {
|
||||
adjustment.set_lower(min as f64);
|
||||
adjustment.set_upper(max as f64);
|
||||
} else {
|
||||
adjustment.set_lower(0.0);
|
||||
adjustment.set_upper(info.current as f64);
|
||||
}
|
||||
|
||||
adjustment.set_initial_value(info.current as f64);
|
||||
}
|
||||
None => {
|
||||
adjustment.set_lower(0.0);
|
||||
adjustment.set_upper(0.0);
|
||||
adjustment.set_initial_value(0.0);
|
||||
if self.zero_rpm_available {
|
||||
pmfw_config.zero_rpm = Some(self.zero_rpm.value());
|
||||
}
|
||||
}
|
||||
|
||||
fn has_pmfw_options(&self) -> bool {
|
||||
self.zero_rpm_available
|
||||
|| !self.pmfw_rows.is_empty()
|
||||
|| !self.zero_rpm_temperature.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn fan_info_init(title: String, info: Option<FanInfo>) -> Option<AdjustmentRowInit> {
|
||||
let info = info?;
|
||||
let (min, max) = info.allowed_range.unwrap_or((0, info.current));
|
||||
(min != 0 || max != 0).then(|| AdjustmentRowInit {
|
||||
title: glib::markup_escape_text(&title).into(),
|
||||
value: info.current as f64,
|
||||
lower: min as f64,
|
||||
upper: max as f64,
|
||||
page_increment: 5.0,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
@@ -1,22 +1,16 @@
|
||||
use super::{FanSettingRow, PmfwOptions, adj_is_empty};
|
||||
use crate::{
|
||||
APP_BROKER, I18N,
|
||||
app::{
|
||||
components::adjustment_value::AdjustmentValue, graphs_window::plot::PlotColorScheme,
|
||||
components::{
|
||||
adjustment_card::AdjustmentCard,
|
||||
adjustment_row::{AdjustmentRow, AdjustmentRowInit, AdjustmentRowMsg},
|
||||
},
|
||||
graphs_window::plot::PlotColorScheme,
|
||||
msg::AppMsg,
|
||||
},
|
||||
};
|
||||
use gtk::{
|
||||
gdk,
|
||||
gio::prelude::ListModelExt,
|
||||
glib::{
|
||||
self, SignalHandlerId,
|
||||
object::{Cast, ObjectExt},
|
||||
},
|
||||
prelude::{
|
||||
AdjustmentExt, BoxExt, ButtonExt, DrawingAreaExtManual, OrientableExt, RangeExt, WidgetExt,
|
||||
},
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use gtk::{gdk, glib};
|
||||
use i18n_embed_fl::fl;
|
||||
use indexmap::IndexMap;
|
||||
use lact_schema::{FanCurveMap, TemperatureEntry, default_fan_curve};
|
||||
@@ -32,14 +26,13 @@ use plotters::{
|
||||
};
|
||||
use plotters_cairo::CairoBackend;
|
||||
use relm4::{
|
||||
ComponentParts, ComponentSender, RelmObjectExt, RelmWidgetExt,
|
||||
binding::{ConnectBinding, U32Binding},
|
||||
ComponentParts, ComponentSender, RelmObjectExt, RelmWidgetExt, WidgetTemplate,
|
||||
binding::U32Binding, factory::FactoryHashMap,
|
||||
};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
ops::RangeInclusive,
|
||||
rc::Rc,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
pub const DEFAULT_TEMP_RANGE: RangeInclusive<f32> = 20.0..=115.0;
|
||||
@@ -51,33 +44,39 @@ const DEFAULT_SPINDOWN_DELAY_MS: u64 = 5000;
|
||||
const TEMPERATURE_DRAG_MARGIN: f32 = 4.0;
|
||||
const PERCENTAGE_DRAG_MARGIN: f32 = 0.04;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct FanCurveFrame {
|
||||
pmfw_options: PmfwOptions,
|
||||
adjustments: FactoryHashMap<CurveSetting, AdjustmentRow<CurveSetting>>,
|
||||
label_size_group: gtk::SizeGroup,
|
||||
input_size_group: gtk::SizeGroup,
|
||||
/// PMFW fan control on AMD RDNA3+ with fixed length
|
||||
hw_based_fan_curve: Rc<AtomicBool>,
|
||||
|
||||
data: Rc<RefCell<Vec<(i32, f32)>>>,
|
||||
speed_range: Rc<RefCell<RangeInclusive<f32>>>,
|
||||
temperature_range: Rc<RefCell<RangeInclusive<f32>>>,
|
||||
hw_based_fan_curve: bool,
|
||||
chart: Rc<ChartState>,
|
||||
temp_keys: gtk::StringList,
|
||||
current_temp_key: U32Binding,
|
||||
}
|
||||
|
||||
spindown_delay_adj: AdjustmentValue,
|
||||
change_threshold_adj: AdjustmentValue,
|
||||
auto_threshold_adj: AdjustmentValue,
|
||||
change_signals: Rc<[(glib::Object, SignalHandlerId)]>,
|
||||
struct ChartState {
|
||||
data: RefCell<Vec<(i32, f32)>>,
|
||||
speed_range: RefCell<RangeInclusive<f32>>,
|
||||
temperature_range: RefCell<RangeInclusive<f32>>,
|
||||
|
||||
is_dragging: Rc<AtomicBool>,
|
||||
is_dragging: Cell<bool>,
|
||||
/// Index of the point currently being dragged
|
||||
drag_point: Rc<Cell<Option<usize>>>,
|
||||
drag_point: Cell<Option<usize>>,
|
||||
/// Where the point was last moved to
|
||||
drag_coord: Rc<Cell<Option<(f64, f64)>>>,
|
||||
drag_coord: Cell<Option<(f64, f64)>>,
|
||||
|
||||
/// Index of the point currently being hovered on
|
||||
hover_point: Rc<Cell<Option<usize>>>,
|
||||
hover_point: Cell<Option<usize>>,
|
||||
/// Where the point was last hovered on
|
||||
hover_coord: Rc<Cell<Option<(f64, f64)>>>,
|
||||
hover_coord: Cell<Option<(f64, f64)>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub(super) enum CurveSetting {
|
||||
SpindownDelay,
|
||||
ChangeThreshold,
|
||||
AutoThreshold,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -109,12 +108,13 @@ pub(super) struct CurveSetupMsg {
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl relm4::Component for FanCurveFrame {
|
||||
type Init = PmfwOptions;
|
||||
type Init = ();
|
||||
type Input = FanCurveFrameMsg;
|
||||
type Output = ();
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
#[root]
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
@@ -123,8 +123,8 @@ impl relm4::Component for FanCurveFrame {
|
||||
gtk::DrawingArea {
|
||||
set_expand: true,
|
||||
set_height_request: 350,
|
||||
set_draw_func[model = model.clone()] => move |_, ctx, width, height| {
|
||||
model.draw_chart(ctx, width, height, PlotColorScheme::current());
|
||||
set_draw_func[chart = model.chart.clone()] => move |_, ctx, width, height| {
|
||||
chart.draw_chart(ctx, width, height, PlotColorScheme::current());
|
||||
},
|
||||
|
||||
add_controller = gtk::GestureClick {
|
||||
@@ -144,7 +144,7 @@ impl relm4::Component for FanCurveFrame {
|
||||
},
|
||||
|
||||
#[watch]
|
||||
set_cursor: if model.is_dragging.load(Ordering::Relaxed) {
|
||||
set_cursor: if model.chart.is_dragging.get() {
|
||||
gdk::Cursor::from_name("move", None)
|
||||
} else {
|
||||
None
|
||||
@@ -160,14 +160,14 @@ impl relm4::Component for FanCurveFrame {
|
||||
set_icon_name: "list-add-symbolic",
|
||||
connect_clicked => FanCurveFrameMsg::AddPoint,
|
||||
#[watch]
|
||||
set_visible: !model.hw_based_fan_curve.load(Ordering::SeqCst),
|
||||
set_visible: !model.hw_based_fan_curve,
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
set_icon_name: "list-remove-symbolic",
|
||||
connect_clicked => FanCurveFrameMsg::RemovePoint,
|
||||
#[watch]
|
||||
set_visible: !model.hw_based_fan_curve.load(Ordering::SeqCst),
|
||||
set_visible: !model.hw_based_fan_curve,
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
@@ -178,14 +178,15 @@ impl relm4::Component for FanCurveFrame {
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
add_css_class: "fan-control-option",
|
||||
set_spacing: 5,
|
||||
#[watch]
|
||||
set_visible: model.temp_keys_available(),
|
||||
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "temperature-sensor"),
|
||||
set_size_group: &model.label_size_group,
|
||||
set_xalign: 0.0,
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
#[name = "temp_key_dropdown"]
|
||||
@@ -197,184 +198,58 @@ impl relm4::Component for FanCurveFrame {
|
||||
},
|
||||
},
|
||||
|
||||
#[template]
|
||||
FanSettingRow {
|
||||
model.adjustments.widget().clone() -> gtk::ListBox {
|
||||
#[watch]
|
||||
set_visible: !model.hw_based_fan_curve.load(Ordering::SeqCst),
|
||||
|
||||
#[template_child]
|
||||
label {
|
||||
set_label: &fl!(I18N, "spindown-delay"),
|
||||
set_tooltip: &fl!(I18N, "spindown-delay-tooltip"),
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
scale {
|
||||
set_adjustment: &model.spindown_delay_adj,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
spinbutton {
|
||||
set_adjustment: &model.spindown_delay_adj,
|
||||
set_size_group: &spin_size_group,
|
||||
},
|
||||
set_visible: !model.adjustments.is_empty(),
|
||||
},
|
||||
},
|
||||
|
||||
#[template]
|
||||
FanSettingRow {
|
||||
#[watch]
|
||||
set_visible: !model.hw_based_fan_curve.load(Ordering::SeqCst),
|
||||
|
||||
#[template_child]
|
||||
label {
|
||||
set_label: &fl!(I18N, "speed-change-threshold"),
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
scale {
|
||||
set_adjustment: &model.change_threshold_adj,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
spinbutton {
|
||||
set_adjustment: &model.change_threshold_adj,
|
||||
set_size_group: &spin_size_group,
|
||||
},
|
||||
},
|
||||
|
||||
#[template]
|
||||
FanSettingRow {
|
||||
#[watch]
|
||||
set_visible: !adj_is_empty(&model.auto_threshold_adj),
|
||||
|
||||
#[template_child]
|
||||
label {
|
||||
set_label: &fl!(I18N, "automatic-mode-threshold"),
|
||||
set_tooltip: &fl!(I18N, "automatic-mode-threshold-tooltip"),
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
scale {
|
||||
set_adjustment: &model.auto_threshold_adj,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
spinbutton {
|
||||
set_adjustment: &model.auto_threshold_adj,
|
||||
set_size_group: &spin_size_group,
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
#[watch]
|
||||
set_visible: model.pmfw_options.zero_rpm_available.get(),
|
||||
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "zero-rpm"),
|
||||
set_xalign: 0.0,
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
gtk::Switch {
|
||||
bind: &model.pmfw_options.zero_rpm,
|
||||
set_hexpand: true,
|
||||
set_halign: gtk::Align::End,
|
||||
},
|
||||
},
|
||||
|
||||
#[template]
|
||||
FanSettingRow {
|
||||
#[watch]
|
||||
set_visible: !adj_is_empty(&model.pmfw_options.zero_rpm_temperature),
|
||||
|
||||
#[template_child]
|
||||
label {
|
||||
set_label: &fl!(I18N, "zero-rpm-stop-temp"),
|
||||
set_size_group: &label_size_group,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
scale {
|
||||
set_adjustment: &model.pmfw_options.zero_rpm_temperature,
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
spinbutton {
|
||||
set_adjustment: &model.pmfw_options.zero_rpm_temperature,
|
||||
set_size_group: &spin_size_group,
|
||||
},
|
||||
},
|
||||
}
|
||||
#[local_ref]
|
||||
current_temp_key -> U32Binding {
|
||||
connect_value_notify => move |_| {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
} @ temp_key_change_signal,
|
||||
},
|
||||
}
|
||||
|
||||
fn post_view() {
|
||||
if self.is_dragging.load(Ordering::SeqCst) {
|
||||
if self.chart.is_dragging.get() {
|
||||
drawing_area.queue_draw();
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
pmfw_options: Self::Init,
|
||||
_init: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let spindown_delay_adj =
|
||||
AdjustmentValue::new(DEFAULT_SPINDOWN_DELAY_MS as f64, 0.0, 30_000.0, 10.0, 10.0);
|
||||
let change_threshold_adj =
|
||||
AdjustmentValue::new(DEFAULT_CHANGE_THRESHOLD as f64, 0.0, 10.0, 1.0, 1.0);
|
||||
let auto_threshold_adj = AdjustmentValue::new(0.0, 0.0, 0.0, 1.0, 5.0);
|
||||
let temp_keys = gtk::StringList::default();
|
||||
let current_temp_key = U32Binding::new(0u32);
|
||||
|
||||
let mut model = Self {
|
||||
pmfw_options,
|
||||
hw_based_fan_curve: Rc::new(AtomicBool::new(false)),
|
||||
is_dragging: Rc::new(AtomicBool::new(false)),
|
||||
speed_range: Rc::new(RefCell::new(DEFAULT_SPEED_RANGE)),
|
||||
temperature_range: Rc::new(RefCell::new(DEFAULT_TEMP_RANGE)),
|
||||
spindown_delay_adj,
|
||||
change_threshold_adj,
|
||||
auto_threshold_adj,
|
||||
let model = Self {
|
||||
adjustments: FactoryHashMap::builder()
|
||||
.launch(AdjustmentCard::init(()).content.clone())
|
||||
.forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged),
|
||||
label_size_group: gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal),
|
||||
input_size_group: gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal),
|
||||
hw_based_fan_curve: false,
|
||||
chart: Rc::new(ChartState {
|
||||
data: RefCell::default(),
|
||||
speed_range: RefCell::new(DEFAULT_SPEED_RANGE),
|
||||
temperature_range: RefCell::new(DEFAULT_TEMP_RANGE),
|
||||
is_dragging: Cell::new(false),
|
||||
drag_coord: Cell::default(),
|
||||
drag_point: Cell::default(),
|
||||
hover_coord: Cell::default(),
|
||||
hover_point: Cell::default(),
|
||||
}),
|
||||
temp_keys,
|
||||
current_temp_key,
|
||||
change_signals: Rc::default(),
|
||||
data: Rc::default(),
|
||||
drag_coord: Rc::default(),
|
||||
drag_point: Rc::default(),
|
||||
hover_coord: Rc::default(),
|
||||
hover_point: Rc::default(),
|
||||
};
|
||||
|
||||
let label_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
|
||||
let spin_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
|
||||
|
||||
let current_temp_key = &model.current_temp_key;
|
||||
let widgets = view_output!();
|
||||
|
||||
model.change_signals = [
|
||||
&model.spindown_delay_adj,
|
||||
&model.change_threshold_adj,
|
||||
&model.auto_threshold_adj,
|
||||
]
|
||||
.into_iter()
|
||||
.map(|adj| {
|
||||
let signal = adj.connect_value_changed(|_| {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
});
|
||||
(adj.clone().upcast(), signal)
|
||||
})
|
||||
.chain([(
|
||||
model.current_temp_key.clone().upcast(),
|
||||
model.current_temp_key.connect_value_notify(|_| {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
}),
|
||||
)])
|
||||
.collect();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
@@ -387,17 +262,15 @@ impl relm4::Component for FanCurveFrame {
|
||||
) {
|
||||
match msg {
|
||||
FanCurveFrameMsg::Curve(msg) => {
|
||||
*self.data.borrow_mut() =
|
||||
*self.chart.data.borrow_mut() =
|
||||
normalize_fan_curve(msg.curve, &msg.temperature_range, &msg.speed_range)
|
||||
.collect();
|
||||
*self.speed_range.borrow_mut() = msg.speed_range;
|
||||
*self.temperature_range.borrow_mut() = msg.temperature_range.clone();
|
||||
self.hw_based_fan_curve
|
||||
.store(msg.hw_based, Ordering::SeqCst);
|
||||
*self.chart.speed_range.borrow_mut() = msg.speed_range;
|
||||
*self.chart.temperature_range.borrow_mut() = msg.temperature_range.clone();
|
||||
self.hw_based_fan_curve = msg.hw_based;
|
||||
|
||||
for (adj, signal) in self.change_signals.iter() {
|
||||
adj.block_signal(signal);
|
||||
}
|
||||
self.current_temp_key
|
||||
.block_signal(&widgets.temp_key_change_signal);
|
||||
|
||||
let mut temp_keys = msg
|
||||
.current_temperatures
|
||||
@@ -424,51 +297,84 @@ impl relm4::Component for FanCurveFrame {
|
||||
widgets.temp_key_dropdown.set_selected(idx as u32);
|
||||
}
|
||||
|
||||
self.spindown_delay_adj.set_initial_value(
|
||||
msg.spindown_delay.unwrap_or(DEFAULT_SPINDOWN_DELAY_MS) as f64,
|
||||
);
|
||||
self.change_threshold_adj.set_initial_value(
|
||||
msg.change_threshold.unwrap_or(DEFAULT_CHANGE_THRESHOLD) as f64,
|
||||
);
|
||||
|
||||
if msg.auto_threshold_supported {
|
||||
self.auto_threshold_adj.set_lower(0.0);
|
||||
self.auto_threshold_adj
|
||||
.set_upper(*msg.temperature_range.end() as f64);
|
||||
self.auto_threshold_adj.set_initial_value(
|
||||
msg.auto_threshold.unwrap_or(DEFAULT_AUTO_THRESHOLD) as f64,
|
||||
self.current_temp_key
|
||||
.unblock_signal(&widgets.temp_key_change_signal);
|
||||
self.adjustments.clear();
|
||||
if !msg.hw_based {
|
||||
self.adjustments.insert(
|
||||
CurveSetting::SpindownDelay,
|
||||
AdjustmentRowInit {
|
||||
title: glib::markup_escape_text(&fl!(I18N, "spindown-delay")).into(),
|
||||
value: msg.spindown_delay.unwrap_or(DEFAULT_SPINDOWN_DELAY_MS) as f64,
|
||||
upper: 30_000.0,
|
||||
step_increment: 10.0,
|
||||
page_increment: 10.0,
|
||||
title_tooltip: fl!(I18N, "spindown-delay-tooltip"),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
self.adjustments.insert(
|
||||
CurveSetting::ChangeThreshold,
|
||||
AdjustmentRowInit {
|
||||
title: glib::markup_escape_text(&fl!(I18N, "speed-change-threshold"))
|
||||
.into(),
|
||||
value: msg.change_threshold.unwrap_or(DEFAULT_CHANGE_THRESHOLD) as f64,
|
||||
upper: 10.0,
|
||||
page_increment: 1.0,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
if msg.auto_threshold_supported && *msg.temperature_range.end() != 0.0 {
|
||||
self.adjustments.insert(
|
||||
CurveSetting::AutoThreshold,
|
||||
AdjustmentRowInit {
|
||||
title: glib::markup_escape_text(&fl!(I18N, "automatic-mode-threshold"))
|
||||
.into(),
|
||||
value: msg.auto_threshold.unwrap_or(DEFAULT_AUTO_THRESHOLD) as f64,
|
||||
upper: *msg.temperature_range.end() as f64,
|
||||
title_tooltip: fl!(I18N, "automatic-mode-threshold-tooltip"),
|
||||
page_increment: 5.0,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
} else {
|
||||
self.auto_threshold_adj.set_lower(0.0);
|
||||
self.auto_threshold_adj.set_upper(0.0);
|
||||
}
|
||||
|
||||
for (adj, signal) in self.change_signals.iter() {
|
||||
adj.unblock_signal(signal);
|
||||
let lower_label_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
|
||||
let upper_label_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
|
||||
for setting in self.adjustments.keys() {
|
||||
self.adjustments.send(
|
||||
setting,
|
||||
AdjustmentRowMsg::AddSizeGroup {
|
||||
label_group: self.label_size_group.clone(),
|
||||
input_group: self.input_size_group.clone(),
|
||||
lower_label_group: lower_label_group.clone(),
|
||||
upper_label_group: upper_label_group.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
widgets.drawing_area.queue_draw();
|
||||
}
|
||||
FanCurveFrameMsg::DragStart => {
|
||||
self.is_dragging.store(true, Ordering::SeqCst);
|
||||
self.chart.is_dragging.set(true);
|
||||
}
|
||||
FanCurveFrameMsg::DragUpdate(x, y) => {
|
||||
self.hover_coord.set(Some((x, y)));
|
||||
self.chart.hover_coord.set(Some((x, y)));
|
||||
|
||||
if self.is_dragging.load(Ordering::SeqCst) {
|
||||
self.drag_coord.set(Some((x, y)));
|
||||
if self.chart.is_dragging.get() {
|
||||
self.chart.drag_coord.set(Some((x, y)));
|
||||
}
|
||||
|
||||
widgets.drawing_area.queue_draw();
|
||||
}
|
||||
FanCurveFrameMsg::DragEnd => {
|
||||
self.drag_coord.take();
|
||||
self.drag_point.take();
|
||||
self.is_dragging.store(false, Ordering::SeqCst);
|
||||
self.chart.drag_coord.take();
|
||||
self.chart.drag_point.take();
|
||||
self.chart.is_dragging.set(false);
|
||||
}
|
||||
FanCurveFrameMsg::AddPoint => {
|
||||
let temp_range = self.temperature_range.borrow();
|
||||
let speed_range = self.speed_range.borrow();
|
||||
let temp_range = self.chart.temperature_range.borrow();
|
||||
let speed_range = self.chart.speed_range.borrow();
|
||||
|
||||
self.edit_curve(
|
||||
|curve| {
|
||||
@@ -490,17 +396,22 @@ impl relm4::Component for FanCurveFrame {
|
||||
|curve| {
|
||||
*curve = normalize_fan_curve(
|
||||
default_fan_curve(),
|
||||
&self.temperature_range.borrow(),
|
||||
&self.speed_range.borrow(),
|
||||
&self.chart.temperature_range.borrow(),
|
||||
&self.chart.speed_range.borrow(),
|
||||
)
|
||||
.collect();
|
||||
},
|
||||
widgets,
|
||||
);
|
||||
self.spindown_delay_adj
|
||||
.set_value(DEFAULT_SPINDOWN_DELAY_MS as f64);
|
||||
self.change_threshold_adj
|
||||
.set_value(DEFAULT_CHANGE_THRESHOLD as f64);
|
||||
for (setting, value) in [
|
||||
(CurveSetting::SpindownDelay, DEFAULT_SPINDOWN_DELAY_MS),
|
||||
(CurveSetting::ChangeThreshold, DEFAULT_CHANGE_THRESHOLD),
|
||||
] {
|
||||
if self.adjustments.get(&setting).is_some() {
|
||||
self.adjustments
|
||||
.send(&setting, AdjustmentRowMsg::SetValue(value as f64));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.update_view(widgets, sender);
|
||||
@@ -509,15 +420,21 @@ impl relm4::Component for FanCurveFrame {
|
||||
|
||||
impl FanCurveFrame {
|
||||
pub fn get_curve(&self) -> FanCurveMap {
|
||||
self.data.borrow().iter().copied().collect()
|
||||
self.chart.data.borrow().iter().copied().collect()
|
||||
}
|
||||
|
||||
pub fn spindown_delay(&self) -> u64 {
|
||||
self.spindown_delay_adj.value() as u64
|
||||
self.adjustments
|
||||
.get(&CurveSetting::SpindownDelay)
|
||||
.map(|row| row.get_value() as u64)
|
||||
.unwrap_or(DEFAULT_SPINDOWN_DELAY_MS)
|
||||
}
|
||||
|
||||
pub fn change_threshold(&self) -> u64 {
|
||||
self.change_threshold_adj.value() as u64
|
||||
self.adjustments
|
||||
.get(&CurveSetting::ChangeThreshold)
|
||||
.map(|row| row.get_value() as u64)
|
||||
.unwrap_or(DEFAULT_CHANGE_THRESHOLD)
|
||||
}
|
||||
|
||||
pub fn temperature_key(&self) -> Option<String> {
|
||||
@@ -531,24 +448,27 @@ impl FanCurveFrame {
|
||||
}
|
||||
|
||||
pub fn auto_threshold(&self) -> Option<u64> {
|
||||
self.auto_threshold_adj
|
||||
.get_changed_value(false)
|
||||
.map(|val| val as u64)
|
||||
self.adjustments
|
||||
.get(&CurveSetting::AutoThreshold)
|
||||
.and_then(|row| row.get_changed_value())
|
||||
.map(|value| value as u64)
|
||||
}
|
||||
|
||||
fn temp_keys_available(&self) -> bool {
|
||||
!self.hw_based_fan_curve.load(Ordering::SeqCst)
|
||||
!self.hw_based_fan_curve
|
||||
&& self.temp_keys.n_items() > 1
|
||||
&& (self.auto_threshold_adj.upper() == 0.0) // Disable key selection on nvidia
|
||||
&& self.adjustments.get(&CurveSetting::AutoThreshold).is_none() // Disable key selection on nvidia
|
||||
}
|
||||
|
||||
fn edit_curve(&self, f: impl FnOnce(&mut Vec<(i32, f32)>), widgets: &FanCurveFrameWidgets) {
|
||||
f(&mut self.data.borrow_mut());
|
||||
f(&mut self.chart.data.borrow_mut());
|
||||
|
||||
widgets.drawing_area.queue_draw();
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
}
|
||||
}
|
||||
|
||||
impl ChartState {
|
||||
fn draw_chart(&self, ctx: &cairo::Context, width: i32, height: i32, colors: PlotColorScheme) {
|
||||
let cairo_backend = CairoBackend::new(ctx, (width as u32, height as u32)).unwrap();
|
||||
|
||||
|
||||
Reference in new issue
Block a user