mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2026-11-03 20:56:39 -06:00
feat: oc cards layout (#1178)
* feat: change OC layout to cards
* feat: switch from checkbox to toggle-buttons for OC optional features
* put into size-group, some small fixes
* make p-states more compact
* remove core offset p-states label
* add dedigated features block for sections
* hacky fix for padding
* update p-state section toggle
* fix adv block bottom border
* use info-dialog to guard manual p-lvl for p-states conf
* upd p-level description to fit in split layout
* independent all p-state toggle for core/vram
* p-level/powermizer: use more compact description
* rollback description changes due to better fix
* use OcAdjustment for performance limit
* shorten description a bit
* remove nvidia oc description
* rename power_section to power_frame for consistency
* fix signal blocking and simplify power frame visibility
* feat: move warning to sidebar and make it more specific
* phrasing
* remove wrapper
* remove OC-adjustment group, make adjustment components shared.
* rename css for consistency
* make adjustment row control it's state
* remove oc_adjustment_row wrapper
* simplify power_frames adjustment wiring
* fix css priority to avoid overrides
* breeze theme should be between adwaita and app css
* rename secondary_clocks
* set border radius to features buttons
* implement MasonryLayout via LayoutManager
* remember children's column
* Revert "remember children's column"
This reverts commit ed82e94eff.
* use 2 columns and flowbox instead of masonry
* cleanup
* redo layout with single column order control
* hide reset button on vram frame
* fix specificity
* upd spacing
* shorten labels
* chore: better label, disable all pstates button when it's not usable
---------
Co-authored-by: Ilya Zlobintsev <ilya.zl@protonmail.com>
This commit is contained in:
1 parent
7145befe5e
commit
4db0e87b66
18 files changed
+828
-788
No files matched your search
@@ -112,7 +112,8 @@ confirm-settings = Confirm Settings
|
||||
revert-button = Revert
|
||||
settings-confirmation = Do you want to keep the new settings? (Reverting in {$seconds_left} seconds)
|
||||
|
||||
power-cap = Power Usage Limit
|
||||
power-cap = Power Limit
|
||||
power-section = Power
|
||||
|
||||
watt = W
|
||||
ghz = GHz
|
||||
@@ -124,12 +125,14 @@ gibibyte = GiB
|
||||
|
||||
stats-section = Statistics
|
||||
gpu-clock = GPU Core Clock
|
||||
core-section = Core
|
||||
gpu-clock-avg = GPU Core Clock (Average)
|
||||
gpu-clock-target = GPU Core Clock (Target)
|
||||
gpu-voltage = GPU Voltage
|
||||
gpu-temp = Temperature
|
||||
gpu-usage = GPU Usage
|
||||
vram-clock = VRAM Clock
|
||||
vram-section = VRAM
|
||||
power-usage = Power Usage
|
||||
extra-clocks = Extra Clocks
|
||||
no-throttling = No
|
||||
@@ -169,21 +172,10 @@ power-mizer-mode-prefer-maximum-performance-description = Favor maximum performa
|
||||
power-mizer-mode-prefer-consistent-performance-description = Lock to GPU base clocks.
|
||||
|
||||
overclock-section = Clockspeed and Voltage
|
||||
nvidia-oc-info = Overclocking Information
|
||||
nvidia-oc-description =
|
||||
Overclocking functionality on Nvidia includes setting offsets for GPU/VRAM clockspeeds and limiting the potential range of clockspeeds using the "locked clocks" feature.
|
||||
|
||||
On many cards, the VRAM clockpeed offset will only affect the actual memory clockspeed by half of the offset value.
|
||||
For example, a +1000MHz VRAM offset may only increase the measured VRAM speed by 500MHz.
|
||||
This is normal, and is how Nvidia handles GDDR data rates. Adjust your overclock accordingly.
|
||||
|
||||
It is possible to achieve a pseudo-undervolt by combining the locked clocks option with a positive clockspeed offset.
|
||||
This will force the GPU to run at a voltage that's constrained by the locked clocks, while achieving a higher clockspeed due to the offset.
|
||||
This can cause system instability if pushed too high.
|
||||
show-all-pstates = Show all P-States
|
||||
enable-gpu-locked-clocks = Enable GPU Locked Clocks
|
||||
enable-vram-locked-clocks = Enable VRAM Locked Clocks
|
||||
enable-vf-curve = Enable VF Curve Edit
|
||||
advanced-features = Advanced features
|
||||
show-all-pstates = All P-States
|
||||
enable-locked-clocks = Locked Clocks
|
||||
enable-vf-curve = Custom VF Curve
|
||||
pstate-list-description = <b>The following values are clock offsets for each P-State, going from highest to lowest.</b>
|
||||
no-clocks-data = No clocks data available
|
||||
reset-oc-tooltip = Warning: this resets all clock settings to defaults!
|
||||
@@ -200,22 +192,17 @@ vf-curve-flatten-right = Flatten curve to the right
|
||||
vf-curve-flatten-selection = Flatten selection
|
||||
vf-curve-editing-disabled = VF curve editing is disabled on the OC page
|
||||
|
||||
gpu-clock-offset = GPU Clock Offset (MHz)
|
||||
max-gpu-clock = Maximum GPU Clock (MHz)
|
||||
max-vram-clock = Maximum VRAM Clock (MHz)
|
||||
max-gpu-voltage = Maximum GPU Voltage (mV)
|
||||
min-gpu-clock = Minimum GPU Clock (MHz)
|
||||
min-vram-clock = Minimum VRAM Clock (MHz)
|
||||
min-gpu-voltage = Minimum GPU Voltage (mV)
|
||||
gpu-voltage-offset = GPU voltage offset (mV)
|
||||
gpu-voltage-boost = GPU Voltage Boost (%)
|
||||
gpu-clock-offset = Clock Offset (MHz)
|
||||
max-clock = Maximum Clock (MHz)
|
||||
max-gpu-voltage = Maximum Voltage (mV)
|
||||
min-clock = Minimum Clock (MHz)
|
||||
min-gpu-voltage = Minimum Voltage (mV)
|
||||
gpu-voltage-offset = Voltage Offset (mV)
|
||||
gpu-voltage-boost = Voltage Boost (%)
|
||||
gpu-voltage-boost-tooltip = Controls how much of the additional voltage headroom defined by the driver is available. 100% means all of this headroom, not 100% of total GPU voltage. More headroom may sustain higher clockspeeds but increases power draw and heat.
|
||||
gpu-pstate-clock-offset = GPU P-State {$pstate} Clock Offset (MHz)
|
||||
vram-pstate-clock-offset = VRAM P-State {$pstate} Clock Offset (MHz)
|
||||
gpu-pstate-clock = GPU P-State {$pstate} Clock (MHz)
|
||||
mem-pstate-clock = VRAM P-State {$pstate} Clock (MHz)
|
||||
gpu-pstate-clock-voltage = GPU P-State {$pstate} Voltage (mV)
|
||||
mem-pstate-clock-voltage = VRAM P-State {$pstate} Voltage (mV)
|
||||
pstate-clock-offset = P-State {$pstate} Clock Offset (MHz)
|
||||
pstate-clock = P-State {$pstate} Clock (MHz)
|
||||
pstate-clock-voltage = P-State {$pstate} Voltage (mV)
|
||||
|
||||
pstates = Power States
|
||||
gpu-pstates = GPU Power States
|
||||
|
||||
@@ -13,12 +13,3 @@
|
||||
.main-sidebar-container .sidebar-section {
|
||||
margin: 4px 8px 8px 8px;
|
||||
}
|
||||
|
||||
.oc-warning-button > button {
|
||||
color: @warning_color;
|
||||
background: alpha(@warning_color, 0.15);
|
||||
}
|
||||
|
||||
.oc-warning-button > button:hover {
|
||||
background: alpha(@warning_color, 0.25);
|
||||
}
|
||||
+2
-8
@@ -39,10 +39,7 @@ use crate::{
|
||||
use adw::prelude::*;
|
||||
use anyhow::{Context, anyhow};
|
||||
use graphs_window::{GraphsWindow, GraphsWindowMsg};
|
||||
use gtk::{
|
||||
STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
glib::{self, ControlFlow, clone},
|
||||
};
|
||||
use gtk::glib::{self, ControlFlow, clone};
|
||||
use i18n_embed_fl::fl;
|
||||
use lact_client::{ConnectionStatusMsg, DaemonClient};
|
||||
use lact_schema::{
|
||||
@@ -368,10 +365,7 @@ impl AsyncComponent for AppModel {
|
||||
.application()
|
||||
.expect("Failed to get application from root window");
|
||||
|
||||
relm4::set_global_css_with_priority(
|
||||
styles::COMBINED_CSS,
|
||||
STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
relm4::set_global_css_with_priority(styles::COMBINED_CSS, styles::APPLICATION_CSS_PRIORITY);
|
||||
|
||||
if let Err(err) = styles::apply_theme(CONFIG.read().theme) {
|
||||
error!("could not apply theme: {err:#}");
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
.adjustment-card-advanced-features {
|
||||
background-color: alpha(@window_fg_color, 0.05);
|
||||
border-radius: 9px 9px 0 0;
|
||||
margin: -10px -10px 0; /* nulify parent's padding */
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.adjustment-card-advanced-features .adjustment-card-option-toggle {
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.adjustment-card-advanced-features .adjustment-card-option-toggle:checked {
|
||||
background-color: @accent_bg_color;
|
||||
color: @accent_fg_color;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use crate::I18N;
|
||||
use gtk::prelude::*;
|
||||
use i18n_embed_fl::fl;
|
||||
use relm4::css;
|
||||
|
||||
#[relm4::widget_template(pub)]
|
||||
impl relm4::WidgetTemplate for AdjustmentCard {
|
||||
view! {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
set_hexpand: true,
|
||||
set_valign: gtk::Align::Start,
|
||||
|
||||
#[name = "advanced_features"]
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
set_visible: false,
|
||||
add_css_class: "adjustment-card-advanced-features",
|
||||
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "advanced-features"),
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: css::DIM_LABEL,
|
||||
add_css_class: css::CAPTION,
|
||||
},
|
||||
|
||||
#[name = "controls"]
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
},
|
||||
|
||||
#[name = "content"]
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub(crate) mod adjustment_card;
|
||||
pub(crate) mod adjustment_row;
|
||||
pub(crate) mod adjustment_value;
|
||||
pub(crate) mod gpu_stats_section;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.oc-page-section {
|
||||
min-width: 400px;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod card_layout;
|
||||
mod clocks_frame;
|
||||
mod performance_frame;
|
||||
mod power_cap_section;
|
||||
mod power_frame;
|
||||
mod power_states;
|
||||
mod vf_curve;
|
||||
|
||||
@@ -11,17 +12,18 @@ use crate::app::components::gpu_stats_section::{
|
||||
use crate::app::pages::PageUpdate;
|
||||
use crate::app::utils::ext::RelmLaunchable as _;
|
||||
use crate::app::{msg::AppMsg, utils::ext::RelmDefaultLauchable};
|
||||
use adw::prelude::*;
|
||||
use amdgpu_sysfs::gpu_handle::{
|
||||
PerformanceLevel, PowerLevelKind, power_profile_mode::PowerProfileModesTable,
|
||||
};
|
||||
use clocks_frame::{ClocksFrame, ClocksFrameInit, ClocksFrameMsg};
|
||||
use gtk::prelude::{BoxExt, OrientableExt, WidgetExt};
|
||||
use card_layout::{CardLayout, ColumnBias};
|
||||
use clocks_frame::{ClockDomain, ClocksFrame, ClocksFrameInit, ClocksFrameMsg};
|
||||
use indexmap::IndexMap;
|
||||
use lact_schema::config;
|
||||
use lact_schema::{ClocksTable, DeviceInfo, PowerStates};
|
||||
use nvml_wrapper::enums::device::PowerMizerMode;
|
||||
use performance_frame::{PerformanceFrame, PerformanceFrameMsg};
|
||||
use power_cap_section::{PowerCapMsg, PowerCapSection};
|
||||
use performance_frame::PerformanceFrameMsg;
|
||||
use power_frame::{PowerFrame, PowerFrameMsg};
|
||||
use power_states::power_states_frame::{PowerStatesFrame, PowerStatesFrameMsg};
|
||||
use relm4::binding::BoolBinding;
|
||||
use relm4::{ComponentController, ComponentParts, ComponentSender, RelmWidgetExt};
|
||||
@@ -34,10 +36,10 @@ pub struct OcPage {
|
||||
stats_section: relm4::Controller<GpuStatsSection>,
|
||||
device_info: Option<Arc<DeviceInfo>>,
|
||||
|
||||
performance_frame: relm4::Controller<PerformanceFrame>,
|
||||
power_cap_section: relm4::Controller<PowerCapSection>,
|
||||
power_frame: relm4::Controller<PowerFrame>,
|
||||
power_states_frame: relm4::Controller<PowerStatesFrame>,
|
||||
clocks_frame: relm4::Controller<ClocksFrame>,
|
||||
gpu_clocks_frame: relm4::Controller<ClocksFrame>,
|
||||
vram_clocks_frame: relm4::Controller<ClocksFrame>,
|
||||
|
||||
vf_curve_editor: relm4::Controller<VfCurveEditor>,
|
||||
}
|
||||
@@ -74,15 +76,38 @@ impl relm4::Component for OcPage {
|
||||
view! {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
set_spacing: 15,
|
||||
set_margin_all: 15,
|
||||
set_margin_top: 20, // align with gpu picker
|
||||
|
||||
model.stats_section.widget(),
|
||||
model.power_cap_section.widget(),
|
||||
model.performance_frame.widget(),
|
||||
model.power_states_frame.widget(),
|
||||
model.clocks_frame.widget(),
|
||||
|
||||
gtk::Box {
|
||||
set_layout_manager: Some(CardLayout::new([
|
||||
ColumnBias::Left, // Core
|
||||
ColumnBias::Right, // VRAM
|
||||
ColumnBias::Left, // Power
|
||||
ColumnBias::Right, // Power States
|
||||
|
||||
])),
|
||||
set_valign: gtk::Align::Start,
|
||||
|
||||
model.gpu_clocks_frame.widget() {
|
||||
add_css_class: "oc-page-section",
|
||||
},
|
||||
|
||||
model.vram_clocks_frame.widget() {
|
||||
add_css_class: "oc-page-section",
|
||||
},
|
||||
|
||||
model.power_frame.widget() {
|
||||
add_css_class: "oc-page-section",
|
||||
},
|
||||
|
||||
model.power_states_frame.widget() {
|
||||
add_css_class: "oc-page-section",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -107,16 +132,22 @@ impl relm4::Component for OcPage {
|
||||
GpuStat::ExtraClocks,
|
||||
]),
|
||||
});
|
||||
let power_cap_section = PowerCapSection::detach_default();
|
||||
let vf_curve_editing = BoolBinding::new(false);
|
||||
let clocks_frame = ClocksFrame::launch(ClocksFrameInit {
|
||||
let gpu_clocks_frame = ClocksFrame::launch(ClocksFrameInit {
|
||||
domain: ClockDomain::Gpu,
|
||||
vf_curve_editing: vf_curve_editing.clone(),
|
||||
show_all_pstates: BoolBinding::new(false),
|
||||
})
|
||||
.forward(sender.input_sender(), |msg| msg);
|
||||
let vram_clocks_frame = ClocksFrame::launch(ClocksFrameInit {
|
||||
domain: ClockDomain::Vram,
|
||||
vf_curve_editing: BoolBinding::new(false),
|
||||
show_all_pstates: BoolBinding::new(false),
|
||||
})
|
||||
.forward(sender.input_sender(), |msg| msg);
|
||||
let power_states_frame =
|
||||
PowerStatesFrame::launch_default().forward(sender.input_sender(), |msg| msg);
|
||||
let performance_frame =
|
||||
PerformanceFrame::launch_default().forward(sender.input_sender(), |msg| msg);
|
||||
let power_frame = PowerFrame::launch_default().forward(sender.input_sender(), |msg| msg);
|
||||
|
||||
let vf_curve_editor = VfCurveEditor::detach(VfCurveEditorInit {
|
||||
global_settings_changed: settings_changed,
|
||||
@@ -126,10 +157,10 @@ impl relm4::Component for OcPage {
|
||||
let model = Self {
|
||||
stats_section,
|
||||
device_info: None,
|
||||
performance_frame,
|
||||
power_cap_section,
|
||||
power_frame,
|
||||
power_states_frame,
|
||||
clocks_frame,
|
||||
gpu_clocks_frame,
|
||||
vram_clocks_frame,
|
||||
vf_curve_editor,
|
||||
};
|
||||
|
||||
@@ -158,25 +189,17 @@ impl relm4::Component for OcPage {
|
||||
.emit(VfCurveEditorMsg::Stats(stats.clone()));
|
||||
|
||||
if initial {
|
||||
self.power_cap_section
|
||||
.emit(PowerCapMsg::Update(update.clone()));
|
||||
|
||||
if stats.power.cap_current.is_some() {
|
||||
self.power_cap_section.widget().set_visible(true);
|
||||
} else {
|
||||
self.power_cap_section.widget().set_visible(false);
|
||||
}
|
||||
|
||||
self.performance_frame
|
||||
.emit(PerformanceFrameMsg::PerformanceLevel(
|
||||
stats.performance_level,
|
||||
));
|
||||
self.performance_frame
|
||||
.emit(PerformanceFrameMsg::PowerMizerInfo {
|
||||
self.power_frame
|
||||
.emit(PowerFrameMsg::PowerStats(stats.power.clone()));
|
||||
self.power_frame.emit(PowerFrameMsg::Performance(
|
||||
PerformanceFrameMsg::PerformanceLevel(stats.performance_level),
|
||||
));
|
||||
self.power_frame.emit(PowerFrameMsg::Performance(
|
||||
PerformanceFrameMsg::PowerMizerInfo {
|
||||
active: stats.active_power_mizer_mode,
|
||||
supported: stats.supported_power_mizer_modes.clone(),
|
||||
});
|
||||
sender.input(OcPageMsg::PerformanceLevelChanged);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
PageUpdate::Info(info) => {
|
||||
@@ -187,7 +210,7 @@ impl relm4::Component for OcPage {
|
||||
.emit(GpuStatsSectionMsg::Info(info.clone()));
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::VramClockRatio(vram_clock_ratio));
|
||||
self.clocks_frame
|
||||
self.vram_clocks_frame
|
||||
.emit(ClocksFrameMsg::VramRatio(vram_clock_ratio));
|
||||
}
|
||||
},
|
||||
@@ -197,7 +220,11 @@ impl relm4::Component for OcPage {
|
||||
} => {
|
||||
let table = table.map(Arc::new);
|
||||
|
||||
self.clocks_frame.emit(ClocksFrameMsg::Clocks {
|
||||
self.gpu_clocks_frame.emit(ClocksFrameMsg::Clocks {
|
||||
table: table.clone(),
|
||||
vf_curve_is_configured,
|
||||
});
|
||||
self.vram_clocks_frame.emit(ClocksFrameMsg::Clocks {
|
||||
table: table.clone(),
|
||||
vf_curve_is_configured,
|
||||
});
|
||||
@@ -205,8 +232,9 @@ impl relm4::Component for OcPage {
|
||||
.emit(VfCurveEditorMsg::Clocks(table.clone()));
|
||||
}
|
||||
OcPageMsg::ProfileModesTable(modes_table) => {
|
||||
self.performance_frame
|
||||
.emit(PerformanceFrameMsg::PowerProfileModes(modes_table));
|
||||
self.power_frame.emit(PowerFrameMsg::Performance(
|
||||
PerformanceFrameMsg::PowerProfileModes(modes_table),
|
||||
));
|
||||
}
|
||||
OcPageMsg::PowerStates {
|
||||
pstates,
|
||||
@@ -235,8 +263,9 @@ impl relm4::Component for OcPage {
|
||||
));
|
||||
}
|
||||
OcPageMsg::SetPerformanceLevel(level) => {
|
||||
self.performance_frame
|
||||
.emit(PerformanceFrameMsg::PerformanceLevel(Some(level)));
|
||||
self.power_frame.emit(PowerFrameMsg::Performance(
|
||||
PerformanceFrameMsg::PerformanceLevel(Some(level)),
|
||||
));
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
}
|
||||
OcPageMsg::EnablePstateConfig => {
|
||||
@@ -248,7 +277,8 @@ impl relm4::Component for OcPage {
|
||||
}
|
||||
OcPageMsg::VfCurveEditingToggled(enabled) => {
|
||||
if enabled {
|
||||
self.clocks_frame.emit(ClocksFrameMsg::ResetGpuClockOffsets);
|
||||
self.gpu_clocks_frame
|
||||
.emit(ClocksFrameMsg::ResetGpuClockOffsets);
|
||||
} else {
|
||||
self.vf_curve_editor.emit(VfCurveEditorMsg::ResetCurve);
|
||||
}
|
||||
@@ -261,29 +291,30 @@ impl relm4::Component for OcPage {
|
||||
|
||||
impl OcPage {
|
||||
pub fn get_performance_level(&self) -> Option<PerformanceLevel> {
|
||||
self.performance_frame.model().performance_level()
|
||||
self.power_frame.model().performance_level()
|
||||
}
|
||||
|
||||
pub fn get_active_power_mizer_mode(&self) -> Option<PowerMizerMode> {
|
||||
self.performance_frame.model().active_power_mizer_mode()
|
||||
self.power_frame.model().active_power_mizer_mode()
|
||||
}
|
||||
|
||||
pub fn get_power_profile_mode(&self) -> Option<u16> {
|
||||
self.performance_frame.model().power_profile_mode()
|
||||
self.power_frame.model().power_profile_mode()
|
||||
}
|
||||
|
||||
pub fn get_power_profile_mode_custom_heuristics(&self) -> Vec<Vec<Option<i32>>> {
|
||||
self.performance_frame
|
||||
self.power_frame
|
||||
.model()
|
||||
.power_profile_mode_custom_heuristics()
|
||||
}
|
||||
|
||||
pub fn get_power_cap(&self) -> Option<f64> {
|
||||
self.power_cap_section.model().get_user_cap()
|
||||
self.power_frame.model().get_user_cap()
|
||||
}
|
||||
|
||||
pub fn apply_clocks_config(&self, config: &mut config::ClocksConfiguration) {
|
||||
let commands = self.clocks_frame.model().get_commands();
|
||||
let mut commands = self.gpu_clocks_frame.model().get_commands();
|
||||
commands.extend(self.vram_clocks_frame.model().get_commands());
|
||||
|
||||
debug!("applying clocks commands {commands:#?}");
|
||||
|
||||
@@ -297,7 +328,7 @@ impl OcPage {
|
||||
}
|
||||
|
||||
pub fn get_enabled_power_states(&self) -> IndexMap<PowerLevelKind, Vec<u8>> {
|
||||
if self.performance_frame.model().performance_level() == Some(PerformanceLevel::Manual) {
|
||||
if self.get_performance_level() == Some(PerformanceLevel::Manual) {
|
||||
self.power_states_frame.model().get_enabled_power_states()
|
||||
} else {
|
||||
IndexMap::new()
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
use gtk::{glib, prelude::*, subclass::prelude::*};
|
||||
use std::cell::OnceCell;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ColumnBias {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct CardLayout(ObjectSubclass<imp::CardLayout>)
|
||||
@extends gtk::LayoutManager;
|
||||
}
|
||||
|
||||
impl CardLayout {
|
||||
pub fn new(column_bias: impl Into<Vec<ColumnBias>>) -> Self {
|
||||
let layout: Self = glib::Object::new();
|
||||
layout.imp().column_bias.set(column_bias.into()).unwrap();
|
||||
layout
|
||||
}
|
||||
}
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CardLayout {
|
||||
pub column_bias: OnceCell<Vec<ColumnBias>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for CardLayout {
|
||||
const NAME: &'static str = "CardLayout";
|
||||
type Type = super::CardLayout;
|
||||
type ParentType = gtk::LayoutManager;
|
||||
}
|
||||
|
||||
impl ObjectImpl for CardLayout {}
|
||||
|
||||
impl LayoutManagerImpl for CardLayout {
|
||||
fn request_mode(&self, _widget: >k::Widget) -> gtk::SizeRequestMode {
|
||||
gtk::SizeRequestMode::HeightForWidth
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&self,
|
||||
widget: >k::Widget,
|
||||
orientation: gtk::Orientation,
|
||||
for_size: i32,
|
||||
) -> (i32, i32, i32, i32) {
|
||||
let (minimum, natural) = measure_width(widget);
|
||||
if orientation == gtk::Orientation::Horizontal {
|
||||
(minimum, natural, -1, -1)
|
||||
} else {
|
||||
let width = if for_size < 0 { natural } else { for_size };
|
||||
let height = layout(widget, width, self.column_bias.get().unwrap(), |_, _| {});
|
||||
(height, height, -1, -1)
|
||||
}
|
||||
}
|
||||
|
||||
fn allocate(&self, widget: >k::Widget, width: i32, _height: i32, _baseline: i32) {
|
||||
layout(
|
||||
widget,
|
||||
width,
|
||||
self.column_bias.get().unwrap(),
|
||||
|child, allocation| {
|
||||
child.size_allocate(allocation, -1);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SPACING: i32 = 15;
|
||||
|
||||
fn children(widget: >k::Widget) -> impl Iterator<Item = (usize, gtk::Widget)> {
|
||||
std::iter::successors(widget.first_child(), |child| child.next_sibling())
|
||||
.enumerate()
|
||||
.filter(|(_, child)| child.should_layout())
|
||||
}
|
||||
|
||||
fn measure_width(widget: >k::Widget) -> (i32, i32) {
|
||||
let mut minimum = 0;
|
||||
let mut natural = 0;
|
||||
let mut count = 0;
|
||||
for (_, child) in children(widget) {
|
||||
let (child_minimum, child_natural, _, _) = child.measure(gtk::Orientation::Horizontal, -1);
|
||||
minimum = minimum.max(child_minimum);
|
||||
natural = natural.max(child_natural);
|
||||
count += 1;
|
||||
}
|
||||
if count > 1 {
|
||||
natural = natural * 2 + SPACING;
|
||||
}
|
||||
(minimum, natural)
|
||||
}
|
||||
|
||||
// Measurement and allocation share placement so the scroller gets the full height.
|
||||
fn layout(
|
||||
widget: >k::Widget,
|
||||
width: i32,
|
||||
column_bias: &[ColumnBias],
|
||||
mut place: impl FnMut(>k::Widget, >k::Allocation),
|
||||
) -> i32 {
|
||||
let (minimum, _) = measure_width(widget);
|
||||
let columns = if children(widget).count() > 1 && width >= minimum * 2 + SPACING {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let column_width = ((width - SPACING * (columns as i32 - 1)) / columns as i32).max(0);
|
||||
let mut heights = [0, 0];
|
||||
for (index, child) in children(widget) {
|
||||
let column = usize::from(columns == 2 && matches!(column_bias[index], ColumnBias::Right));
|
||||
let x = column as i32 * (column_width + SPACING);
|
||||
let child_width = if column == columns - 1 {
|
||||
width - x
|
||||
} else {
|
||||
column_width
|
||||
};
|
||||
let (_, height, _, _) = child.measure(gtk::Orientation::Vertical, child_width);
|
||||
let y = heights[column];
|
||||
place(&child, >k::Allocation::new(x, y, child_width, height));
|
||||
heights[column] = y + height + SPACING;
|
||||
}
|
||||
(heights[0].max(heights[1]) - SPACING).max(0)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
.clocks-frame-group {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.clocks-frame-group .card {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.clocks-frame-group .adjustment-row > scale {
|
||||
min-width: 100px;
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
use super::{ClocksData, clock_title};
|
||||
use crate::{
|
||||
APP_BROKER, I18N,
|
||||
app::{
|
||||
components::adjustment_row::{AdjustmentRow, AdjustmentRowInit, AdjustmentRowMsg},
|
||||
msg::AppMsg,
|
||||
},
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use i18n_embed_fl::fl;
|
||||
use lact_schema::request::ClockspeedType;
|
||||
use relm4::{css, factory::FactoryHashMap, prelude::FactoryComponent};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ClockCategory {
|
||||
CoreClock,
|
||||
CoreVoltage,
|
||||
VramClock,
|
||||
CoreCurveClock,
|
||||
VramCurveClock,
|
||||
CoreCurveVoltage,
|
||||
VramCurveVoltage,
|
||||
}
|
||||
|
||||
impl ClockCategory {
|
||||
pub fn from_type(clock_type: ClockspeedType) -> Self {
|
||||
match clock_type {
|
||||
ClockspeedType::MaxCoreClock
|
||||
| ClockspeedType::MinCoreClock
|
||||
| ClockspeedType::GpuClockOffset(_) => ClockCategory::CoreClock,
|
||||
ClockspeedType::MinVoltage
|
||||
| ClockspeedType::MaxVoltage
|
||||
| ClockspeedType::VoltageOffset
|
||||
| ClockspeedType::VoltageBoost => ClockCategory::CoreVoltage,
|
||||
ClockspeedType::MaxMemoryClock
|
||||
| ClockspeedType::MinMemoryClock
|
||||
| ClockspeedType::MemClockOffset(_) => ClockCategory::VramClock,
|
||||
ClockspeedType::GpuVfCurveClock(_) => ClockCategory::CoreCurveClock,
|
||||
ClockspeedType::MemVfCurveClock(_) => ClockCategory::VramCurveClock,
|
||||
ClockspeedType::GpuVfCurveVoltage(_) => ClockCategory::CoreCurveVoltage,
|
||||
ClockspeedType::MemVfCurveVoltage(_) => ClockCategory::VramCurveVoltage,
|
||||
ClockspeedType::Reset => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_core(&self) -> bool {
|
||||
Self::CORE.contains(self)
|
||||
}
|
||||
|
||||
pub fn is_vram(&self) -> bool {
|
||||
Self::VRAM.contains(self)
|
||||
}
|
||||
|
||||
pub const CORE: [ClockCategory; 4] = [
|
||||
ClockCategory::CoreClock,
|
||||
ClockCategory::CoreVoltage,
|
||||
ClockCategory::CoreCurveClock,
|
||||
ClockCategory::CoreCurveVoltage,
|
||||
];
|
||||
|
||||
pub const VRAM: [ClockCategory; 3] = [
|
||||
ClockCategory::VramClock,
|
||||
ClockCategory::VramCurveClock,
|
||||
ClockCategory::VramCurveVoltage,
|
||||
];
|
||||
}
|
||||
|
||||
pub struct AdjustmentGroup {
|
||||
adjustments: FactoryHashMap<ClockspeedType, AdjustmentRow<ClockspeedType>>,
|
||||
secondary_clocks: HashSet<ClockspeedType>,
|
||||
}
|
||||
|
||||
impl AdjustmentGroup {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.adjustments.is_empty()
|
||||
}
|
||||
|
||||
pub fn has_secondary(&self) -> bool {
|
||||
!self.secondary_clocks.is_empty()
|
||||
}
|
||||
|
||||
pub fn set_clock(&mut self, clock_type: ClockspeedType, data: ClocksData) {
|
||||
self.adjustments.insert(
|
||||
clock_type,
|
||||
AdjustmentRowInit {
|
||||
title: data.custom_title.unwrap_or_else(|| clock_title(clock_type)),
|
||||
info_text: if clock_type == ClockspeedType::VoltageBoost {
|
||||
fl!(I18N, "gpu-voltage-boost-tooltip")
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
value: f64::from(data.current),
|
||||
lower: f64::from(data.min),
|
||||
upper: f64::from(data.max),
|
||||
step_increment: get_row_step(clock_type),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
if data.is_secondary {
|
||||
self.secondary_clocks.insert(clock_type);
|
||||
} else {
|
||||
self.secondary_clocks.remove(&clock_type);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_size_group(&self, label_group: gtk::SizeGroup, input_group: gtk::SizeGroup) {
|
||||
for clock_type in self.adjustments.keys() {
|
||||
self.adjustments.send(
|
||||
clock_type,
|
||||
AdjustmentRowMsg::AddSizeGroup {
|
||||
label_group: label_group.clone(),
|
||||
input_group: input_group.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_value_ratio(&self, ratio: f64) {
|
||||
for clock_type in self.adjustments.keys() {
|
||||
self.adjustments
|
||||
.send(clock_type, AdjustmentRowMsg::ValueRatio(ratio));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_secondary_visibility(
|
||||
&self,
|
||||
show_secondary: bool,
|
||||
show_nvidia_options: bool,
|
||||
enable_gpu_locked: bool,
|
||||
enable_vram_locked: bool,
|
||||
vf_curve_editing: bool,
|
||||
) {
|
||||
let mut any_visible = false;
|
||||
|
||||
for key in self.adjustments.keys() {
|
||||
let show_current = match key {
|
||||
ClockspeedType::MaxCoreClock | ClockspeedType::MinCoreClock
|
||||
if show_nvidia_options =>
|
||||
{
|
||||
enable_gpu_locked
|
||||
}
|
||||
ClockspeedType::MaxMemoryClock | ClockspeedType::MinMemoryClock
|
||||
if show_nvidia_options =>
|
||||
{
|
||||
enable_vram_locked
|
||||
}
|
||||
ClockspeedType::GpuClockOffset(_) if show_nvidia_options && vf_curve_editing => {
|
||||
false
|
||||
}
|
||||
_ => !self.secondary_clocks.contains(key) || show_secondary,
|
||||
};
|
||||
|
||||
any_visible |= show_current;
|
||||
|
||||
self.adjustments
|
||||
.send(key, AdjustmentRowMsg::SetVisible(show_current));
|
||||
}
|
||||
|
||||
// removes empty card
|
||||
self.adjustments.widget().set_visible(any_visible);
|
||||
}
|
||||
|
||||
pub fn get_commands(&self) -> Vec<(ClockspeedType, Option<i32>)> {
|
||||
self.adjustments
|
||||
.iter()
|
||||
.map(|(clock_type, row)| {
|
||||
(
|
||||
*clock_type,
|
||||
row.get_changed_value().map(|value| value as i32),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn reset_gpu_clock_offsets(&self) {
|
||||
for clock_type in self.adjustments.keys() {
|
||||
if matches!(clock_type, ClockspeedType::GpuClockOffset(_)) {
|
||||
self.adjustments
|
||||
.send(clock_type, AdjustmentRowMsg::SetValue(0.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_raw_value(&self, clock_type: ClockspeedType) -> i32 {
|
||||
self.adjustments
|
||||
.get(&clock_type)
|
||||
.map(|row| row.get_value() as i32)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_row_step(clock_type: ClockspeedType) -> f64 {
|
||||
match ClockCategory::from_type(clock_type) {
|
||||
ClockCategory::CoreClock
|
||||
| ClockCategory::VramClock
|
||||
| ClockCategory::CoreCurveClock
|
||||
| ClockCategory::VramCurveClock => 5.0,
|
||||
ClockCategory::CoreVoltage
|
||||
| ClockCategory::CoreCurveVoltage
|
||||
| ClockCategory::VramCurveVoltage => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
impl FactoryComponent for AdjustmentGroup {
|
||||
type Init = ();
|
||||
type Input = ();
|
||||
type Output = ();
|
||||
type CommandOutput = ();
|
||||
type ParentWidget = gtk::Box;
|
||||
type Index = ClockCategory;
|
||||
|
||||
view! {
|
||||
self.adjustments.widget().clone() -> gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
set_valign: gtk::Align::Start,
|
||||
add_css_class: css::CARD,
|
||||
}
|
||||
}
|
||||
|
||||
fn init_model(_: Self::Init, _: &Self::Index, _: relm4::FactorySender<Self>) -> Self {
|
||||
Self {
|
||||
adjustments: FactoryHashMap::builder()
|
||||
.launch_default()
|
||||
.forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged),
|
||||
secondary_clocks: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
mod adjustment_group;
|
||||
|
||||
use crate::{
|
||||
APP_BROKER, I18N,
|
||||
app::{components::page_section::PageSection, msg::AppMsg, pages::oc_page::OcPageMsg},
|
||||
app::{
|
||||
components::{
|
||||
adjustment_card::AdjustmentCard,
|
||||
adjustment_row::{AdjustmentRow, AdjustmentRowInit, AdjustmentRowMsg},
|
||||
page_section::PageSection,
|
||||
},
|
||||
msg::AppMsg,
|
||||
pages::oc_page::OcPageMsg,
|
||||
},
|
||||
};
|
||||
use adjustment_group::{AdjustmentGroup, ClockCategory};
|
||||
use adw::prelude::*;
|
||||
use amdgpu_sysfs::gpu_handle::overdrive::ClocksTableGen as AmdClocksTable;
|
||||
use gtk::{
|
||||
glib::object::ObjectExt,
|
||||
pango,
|
||||
prelude::{BoxExt, ButtonExt, CheckButtonExt, OrientableExt, WidgetExt},
|
||||
};
|
||||
use i18n_embed_fl::fl;
|
||||
use lact_schema::{
|
||||
ClocksTable, IntelClocksTable, NvidiaClockOffset, NvidiaClocksTable,
|
||||
@@ -20,20 +21,20 @@ use relm4::{
|
||||
ComponentParts, ComponentSender, RelmObjectExt, RelmWidgetExt, binding::BoolBinding, css,
|
||||
factory::FactoryHashMap,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
const DEFAULT_VOLTAGE_OFFSET_RANGE: i32 = 250;
|
||||
|
||||
pub struct ClocksFrame {
|
||||
core_groups: FactoryHashMap<ClockCategory, AdjustmentGroup>,
|
||||
vram_groups: FactoryHashMap<ClockCategory, AdjustmentGroup>,
|
||||
adjustments: FactoryHashMap<ClockspeedType, AdjustmentRow<ClockspeedType>>,
|
||||
secondary_p_state_clocks: HashSet<ClockspeedType>,
|
||||
domain: ClockDomain,
|
||||
vram_clock_ratio: f64,
|
||||
show_nvidia_options: bool,
|
||||
vf_curve_available: bool,
|
||||
show_all_pstates: BoolBinding,
|
||||
vf_curve_editing: BoolBinding,
|
||||
enable_gpu_locked_clocks: BoolBinding,
|
||||
enable_vram_locked_clocks: BoolBinding,
|
||||
enable_locked_clocks: BoolBinding,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -42,7 +43,7 @@ struct ClocksData {
|
||||
min: i32,
|
||||
max: i32,
|
||||
custom_title: Option<String>,
|
||||
is_secondary: bool,
|
||||
is_secondary_p_state: bool,
|
||||
}
|
||||
|
||||
impl ClocksData {
|
||||
@@ -57,7 +58,38 @@ impl ClocksData {
|
||||
}
|
||||
|
||||
pub struct ClocksFrameInit {
|
||||
pub domain: ClockDomain,
|
||||
pub vf_curve_editing: BoolBinding,
|
||||
pub show_all_pstates: BoolBinding,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ClockDomain {
|
||||
Gpu,
|
||||
Vram,
|
||||
}
|
||||
|
||||
impl ClockDomain {
|
||||
fn matches(self, clock_type: ClockspeedType) -> bool {
|
||||
let domain = match clock_type {
|
||||
ClockspeedType::MaxCoreClock
|
||||
| ClockspeedType::MinCoreClock
|
||||
| ClockspeedType::GpuClockOffset(_)
|
||||
| ClockspeedType::MinVoltage
|
||||
| ClockspeedType::MaxVoltage
|
||||
| ClockspeedType::VoltageOffset
|
||||
| ClockspeedType::VoltageBoost
|
||||
| ClockspeedType::GpuVfCurveClock(_)
|
||||
| ClockspeedType::GpuVfCurveVoltage(_) => Self::Gpu,
|
||||
ClockspeedType::MaxMemoryClock
|
||||
| ClockspeedType::MinMemoryClock
|
||||
| ClockspeedType::MemClockOffset(_)
|
||||
| ClockspeedType::MemVfCurveClock(_)
|
||||
| ClockspeedType::MemVfCurveVoltage(_) => Self::Vram,
|
||||
ClockspeedType::Reset => unreachable!(),
|
||||
};
|
||||
self == domain
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -79,36 +111,27 @@ impl relm4::Component for ClocksFrame {
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
PageSection::new(&fl!(I18N, "overclock-section")) {
|
||||
set_hide_visible_container: true,
|
||||
add_css_class: "clocks-frame",
|
||||
PageSection::new("") {
|
||||
#[watch]
|
||||
set_name: match model.domain {
|
||||
ClockDomain::Gpu => fl!(I18N, "core-section"),
|
||||
ClockDomain::Vram => fl!(I18N, "vram-section"),
|
||||
},
|
||||
#[watch]
|
||||
set_visible: model.domain == ClockDomain::Gpu || model.has_any_clocks(),
|
||||
|
||||
append_header = >k::Box {
|
||||
set_spacing: 10,
|
||||
set_hexpand: true,
|
||||
set_halign: gtk::Align::End,
|
||||
|
||||
append = >k::MenuButton {
|
||||
#[watch]
|
||||
set_visible: model.show_nvidia_options,
|
||||
set_label: &fl!(I18N, "nvidia-oc-info"),
|
||||
|
||||
#[wrap(Some)]
|
||||
set_popover = >k::Popover {
|
||||
gtk::Label {
|
||||
set_margin_all: 5,
|
||||
set_markup: &fl!(I18N, "nvidia-oc-description"),
|
||||
set_wrap: true,
|
||||
set_max_width_chars: 75,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
append = >k::Button {
|
||||
set_label: &fl!(I18N, "vf-curve-editor"),
|
||||
|
||||
#[watch]
|
||||
set_visible: model.show_nvidia_options && model.vf_curve_available,
|
||||
set_visible: model.domain == ClockDomain::Gpu
|
||||
&& model.show_nvidia_options
|
||||
&& model.vf_curve_available,
|
||||
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.output(OcPageMsg::ShowVfCurveEditor).unwrap();
|
||||
@@ -122,7 +145,7 @@ impl relm4::Component for ClocksFrame {
|
||||
add_css_class: css::DESTRUCTIVE_ACTION,
|
||||
|
||||
#[watch]
|
||||
set_visible: model.has_any_clocks(),
|
||||
set_visible: model.domain == ClockDomain::Gpu && model.has_any_clocks(),
|
||||
|
||||
connect_clicked => move |_| {
|
||||
APP_BROKER.send(AppMsg::ResetClocks);
|
||||
@@ -130,141 +153,129 @@ impl relm4::Component for ClocksFrame {
|
||||
},
|
||||
},
|
||||
|
||||
append_child = >k::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
#[template]
|
||||
append_child = &AdjustmentCard {
|
||||
#[template_child]
|
||||
advanced_features {
|
||||
#[watch]
|
||||
set_visible: model.has_secondary_p_states() || model.show_nvidia_options,
|
||||
},
|
||||
|
||||
append = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_homogeneous: true,
|
||||
set_hexpand: true,
|
||||
|
||||
append = >k::CheckButton {
|
||||
#[template_child]
|
||||
controls {
|
||||
append = >k::ToggleButton {
|
||||
#[watch]
|
||||
set_visible: model.any_is_secondary(),
|
||||
set_visible: model.has_secondary_p_states(),
|
||||
|
||||
set_label: Some(&fl!(I18N, "show-all-pstates")),
|
||||
add_css_class: "adjustment-card-option-toggle",
|
||||
add_binding["active"]: &model.show_all_pstates,
|
||||
|
||||
#[watch]
|
||||
set_sensitive: {
|
||||
if model.domain == ClockDomain::Gpu
|
||||
&& model.show_nvidia_options
|
||||
&& model.vf_curve_available {
|
||||
!model.vf_curve_editing.value()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
},
|
||||
|
||||
#[wrap(Some)]
|
||||
set_child = >k::Box {
|
||||
append = >k::Label {
|
||||
set_label: &fl!(I18N, "show-all-pstates"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
append: gpu_locked_clocks_togglebutton = >k::CheckButton {
|
||||
append: locked_clocks_togglebutton = >k::ToggleButton {
|
||||
#[watch]
|
||||
set_visible: model.show_nvidia_options,
|
||||
set_label: Some(&fl!(I18N, "enable-gpu-locked-clocks")),
|
||||
add_binding["active"]: &model.enable_gpu_locked_clocks,
|
||||
add_css_class: "adjustment-card-option-toggle",
|
||||
add_binding["active"]: &model.enable_locked_clocks,
|
||||
|
||||
#[wrap(Some)]
|
||||
set_child = >k::Box {
|
||||
append = >k::Label {
|
||||
set_label: &fl!(I18N, "enable-locked-clocks"),
|
||||
},
|
||||
},
|
||||
connect_toggled => move |_| {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
} @ gpu_locked_clock_signal,
|
||||
} @ locked_clock_signal,
|
||||
},
|
||||
|
||||
append: vf_curve_editing_togglebutton = >k::CheckButton {
|
||||
append: vf_curve_editing_togglebutton = >k::ToggleButton {
|
||||
#[watch]
|
||||
set_visible: model.show_nvidia_options && model.vf_curve_available,
|
||||
set_label: Some(&fl!(I18N, "enable-vf-curve")),
|
||||
set_visible: model.domain == ClockDomain::Gpu
|
||||
&& model.show_nvidia_options
|
||||
&& model.vf_curve_available,
|
||||
add_css_class: "adjustment-card-option-toggle",
|
||||
add_css_class: css::WARNING,
|
||||
add_binding["active"]: &model.vf_curve_editing,
|
||||
|
||||
#[wrap(Some)]
|
||||
set_child = >k::Box {
|
||||
append = >k::Label {
|
||||
set_label: &fl!(I18N, "enable-vf-curve"),
|
||||
},
|
||||
},
|
||||
|
||||
connect_toggled[sender] => move |button| {
|
||||
sender.output(OcPageMsg::VfCurveEditingToggled(button.is_active())).unwrap();
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
} @ vf_curve_editing_signal,
|
||||
},
|
||||
},
|
||||
|
||||
append: vram_locked_clocks_togglebutton = >k::CheckButton {
|
||||
#[template_child]
|
||||
content {
|
||||
#[local_ref]
|
||||
adjustments_widget -> gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "no-clocks-data"),
|
||||
set_margin_horizontal: 10,
|
||||
set_halign: gtk::Align::Start,
|
||||
#[watch]
|
||||
set_visible: model.show_nvidia_options,
|
||||
set_label: Some(&fl!(I18N, "enable-vram-locked-clocks")),
|
||||
add_binding["active"]: &model.enable_vram_locked_clocks,
|
||||
connect_toggled => move |_| {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
} @ vram_locked_clock_signal,
|
||||
set_visible: !model.has_any_clocks(),
|
||||
},
|
||||
},
|
||||
|
||||
append = >k::Label {
|
||||
#[watch]
|
||||
set_visible: model.show_all_pstates.value() && model.show_nvidia_options,
|
||||
|
||||
set_margin_horizontal: 5,
|
||||
set_markup: &fl!(I18N, "pstate-list-description"),
|
||||
set_wrap_mode: pango::WrapMode::Word,
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
},
|
||||
|
||||
append_child = >k::FlowBox {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
#[watch]
|
||||
set_max_children_per_line: if model.core_any_visible() && model.vram_any_visible() { 2 } else { 1 },
|
||||
set_column_spacing: 10,
|
||||
set_row_spacing: 10,
|
||||
set_homogeneous: false,
|
||||
set_valign: gtk::Align::Start,
|
||||
set_hexpand: true,
|
||||
|
||||
append = >k::FlowBoxChild {
|
||||
add_css_class: "clocks-frame-group",
|
||||
set_valign: gtk::Align::Start,
|
||||
#[watch]
|
||||
set_visible: model.core_any_visible(),
|
||||
|
||||
#[local_ref]
|
||||
core_groups_widget -> gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_valign: gtk::Align::Start,
|
||||
set_spacing: 10,
|
||||
set_hexpand: true,
|
||||
},
|
||||
},
|
||||
|
||||
append = >k::FlowBoxChild {
|
||||
add_css_class: "clocks-frame-group",
|
||||
set_valign: gtk::Align::Start,
|
||||
#[watch]
|
||||
set_visible: model.vram_any_visible(),
|
||||
|
||||
#[local_ref]
|
||||
vram_groups_widget -> gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_valign: gtk::Align::Start,
|
||||
set_spacing: 10,
|
||||
set_hexpand: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
append_child = >k::Label {
|
||||
set_label: &fl!(I18N, "no-clocks-data"),
|
||||
set_margin_horizontal: 10,
|
||||
set_halign: gtk::Align::Start,
|
||||
#[watch]
|
||||
set_visible: !model.has_any_clocks(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
ClocksFrameInit { vf_curve_editing }: Self::Init,
|
||||
ClocksFrameInit {
|
||||
domain,
|
||||
vf_curve_editing,
|
||||
show_all_pstates,
|
||||
}: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let model = Self {
|
||||
core_groups: FactoryHashMap::builder().launch_default().detach(),
|
||||
vram_groups: FactoryHashMap::builder().launch_default().detach(),
|
||||
adjustments: FactoryHashMap::builder()
|
||||
.launch_default()
|
||||
.forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged),
|
||||
secondary_p_state_clocks: HashSet::new(),
|
||||
domain,
|
||||
vram_clock_ratio: 1.0,
|
||||
show_nvidia_options: false,
|
||||
vf_curve_available: false,
|
||||
show_all_pstates: BoolBinding::new(false),
|
||||
show_all_pstates,
|
||||
vf_curve_editing,
|
||||
enable_gpu_locked_clocks: BoolBinding::new(false),
|
||||
enable_vram_locked_clocks: BoolBinding::new(false),
|
||||
enable_locked_clocks: BoolBinding::new(false),
|
||||
};
|
||||
|
||||
for binding in [
|
||||
&model.show_all_pstates,
|
||||
&model.vf_curve_editing,
|
||||
&model.enable_gpu_locked_clocks,
|
||||
&model.enable_vram_locked_clocks,
|
||||
&model.enable_locked_clocks,
|
||||
] {
|
||||
let sender = sender.clone();
|
||||
binding.connect_value_notify(move |_| {
|
||||
@@ -272,9 +283,7 @@ impl relm4::Component for ClocksFrame {
|
||||
});
|
||||
}
|
||||
|
||||
let core_groups_widget = model.core_groups.widget();
|
||||
let vram_groups_widget = model.vram_groups.widget();
|
||||
|
||||
let adjustments_widget = model.adjustments.widget();
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
@@ -296,17 +305,13 @@ impl relm4::Component for ClocksFrame {
|
||||
.vf_curve_editing_togglebutton
|
||||
.block_signal(&widgets.vf_curve_editing_signal);
|
||||
widgets
|
||||
.gpu_locked_clocks_togglebutton
|
||||
.block_signal(&widgets.gpu_locked_clock_signal);
|
||||
widgets
|
||||
.vram_locked_clocks_togglebutton
|
||||
.block_signal(&widgets.vram_locked_clock_signal);
|
||||
.locked_clocks_togglebutton
|
||||
.block_signal(&widgets.locked_clock_signal);
|
||||
|
||||
self.core_groups.clear();
|
||||
self.vram_groups.clear();
|
||||
self.adjustments.clear();
|
||||
self.secondary_p_state_clocks.clear();
|
||||
|
||||
self.enable_gpu_locked_clocks.set_value(false);
|
||||
self.enable_vram_locked_clocks.set_value(false);
|
||||
self.enable_locked_clocks.set_value(false);
|
||||
self.vf_curve_editing.set_value(vf_curve_is_configured);
|
||||
self.show_nvidia_options = false;
|
||||
|
||||
@@ -321,26 +326,32 @@ impl relm4::Component for ClocksFrame {
|
||||
let label_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
|
||||
let input_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal);
|
||||
|
||||
for group in self.all_groups() {
|
||||
group.add_size_group(label_size_group.clone(), input_size_group.clone());
|
||||
for clock_type in self.adjustments.keys() {
|
||||
self.adjustments.send(
|
||||
clock_type,
|
||||
AdjustmentRowMsg::AddSizeGroup {
|
||||
label_group: label_size_group.clone(),
|
||||
input_group: input_size_group.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
widgets
|
||||
.vf_curve_editing_togglebutton
|
||||
.unblock_signal(&widgets.vf_curve_editing_signal);
|
||||
widgets
|
||||
.gpu_locked_clocks_togglebutton
|
||||
.unblock_signal(&widgets.gpu_locked_clock_signal);
|
||||
widgets
|
||||
.vram_locked_clocks_togglebutton
|
||||
.unblock_signal(&widgets.vram_locked_clock_signal);
|
||||
.locked_clocks_togglebutton
|
||||
.unblock_signal(&widgets.locked_clock_signal);
|
||||
|
||||
self.update_vram_clock_ratio();
|
||||
sender.input(ClocksFrameMsg::TogglePStatesVisibility);
|
||||
}
|
||||
ClocksFrameMsg::ResetGpuClockOffsets => {
|
||||
for group in self.core_groups.values() {
|
||||
group.reset_gpu_clock_offsets();
|
||||
for clock_type in self.adjustments.keys() {
|
||||
if matches!(clock_type, ClockspeedType::GpuClockOffset(_)) {
|
||||
self.adjustments
|
||||
.send(clock_type, AdjustmentRowMsg::SetValue(0.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
ClocksFrameMsg::VramRatio(vram_ratio) => {
|
||||
@@ -348,14 +359,28 @@ impl relm4::Component for ClocksFrame {
|
||||
self.update_vram_clock_ratio();
|
||||
}
|
||||
ClocksFrameMsg::TogglePStatesVisibility => {
|
||||
for group in self.all_groups() {
|
||||
group.toggle_secondary_visibility(
|
||||
self.show_all_pstates.value(),
|
||||
self.show_nvidia_options,
|
||||
self.enable_gpu_locked_clocks.value(),
|
||||
self.enable_vram_locked_clocks.value(),
|
||||
self.vf_curve_editing.value(),
|
||||
);
|
||||
for clock_type in self.adjustments.keys() {
|
||||
let visible = match clock_type {
|
||||
ClockspeedType::MaxCoreClock
|
||||
| ClockspeedType::MinCoreClock
|
||||
| ClockspeedType::MaxMemoryClock
|
||||
| ClockspeedType::MinMemoryClock
|
||||
if self.show_nvidia_options =>
|
||||
{
|
||||
self.enable_locked_clocks.value()
|
||||
}
|
||||
ClockspeedType::GpuClockOffset(_)
|
||||
if self.show_nvidia_options && self.vf_curve_editing.value() =>
|
||||
{
|
||||
false
|
||||
}
|
||||
_ => {
|
||||
!self.secondary_p_state_clocks.contains(clock_type)
|
||||
|| self.show_all_pstates.value()
|
||||
}
|
||||
};
|
||||
self.adjustments
|
||||
.send(clock_type, AdjustmentRowMsg::SetVisible(visible));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,49 +391,54 @@ impl relm4::Component for ClocksFrame {
|
||||
|
||||
impl ClocksFrame {
|
||||
fn set_clock(&mut self, clock_type: ClockspeedType, data: ClocksData) {
|
||||
let category = ClockCategory::from_type(clock_type);
|
||||
if !self.domain.matches(clock_type) {
|
||||
return;
|
||||
}
|
||||
|
||||
let groups = if category.is_core() {
|
||||
&mut self.core_groups
|
||||
} else if category.is_vram() {
|
||||
&mut self.vram_groups
|
||||
self.adjustments.insert(
|
||||
clock_type,
|
||||
AdjustmentRowInit {
|
||||
title: data.custom_title.unwrap_or_else(|| clock_title(clock_type)),
|
||||
info_text: if clock_type == ClockspeedType::VoltageBoost {
|
||||
fl!(I18N, "gpu-voltage-boost-tooltip")
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
value: f64::from(data.current),
|
||||
lower: f64::from(data.min),
|
||||
upper: f64::from(data.max),
|
||||
step_increment: get_row_step(clock_type),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
if data.is_secondary_p_state {
|
||||
self.secondary_p_state_clocks.insert(clock_type);
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let mut group = if let Some(group) = groups.get_mut(&category) {
|
||||
group
|
||||
} else {
|
||||
groups.insert(category, ());
|
||||
groups.get_mut(&category).unwrap()
|
||||
};
|
||||
|
||||
group.set_clock(clock_type, data);
|
||||
}
|
||||
|
||||
fn all_groups(&self) -> impl Iterator<Item = &AdjustmentGroup> {
|
||||
self.core_groups.values().chain(self.vram_groups.values())
|
||||
self.secondary_p_state_clocks.remove(&clock_type);
|
||||
}
|
||||
}
|
||||
|
||||
fn has_any_clocks(&self) -> bool {
|
||||
self.core_groups.values().any(|group| !group.is_empty())
|
||||
!self.adjustments.is_empty()
|
||||
}
|
||||
|
||||
fn any_is_secondary(&self) -> bool {
|
||||
self.all_groups().any(|group| group.has_secondary())
|
||||
}
|
||||
|
||||
fn core_any_visible(&self) -> bool {
|
||||
self.core_groups.values().any(|group| !group.is_empty())
|
||||
}
|
||||
|
||||
fn vram_any_visible(&self) -> bool {
|
||||
self.vram_groups.values().any(|group| !group.is_empty())
|
||||
fn has_secondary_p_states(&self) -> bool {
|
||||
!self.secondary_p_state_clocks.is_empty()
|
||||
}
|
||||
|
||||
fn update_vram_clock_ratio(&self) {
|
||||
if let Some(vram_group) = self.vram_groups.get(&ClockCategory::VramClock) {
|
||||
vram_group.set_value_ratio(self.vram_clock_ratio);
|
||||
for clock_type in self.adjustments.keys() {
|
||||
if matches!(
|
||||
clock_type,
|
||||
ClockspeedType::MaxMemoryClock
|
||||
| ClockspeedType::MinMemoryClock
|
||||
| ClockspeedType::MemClockOffset(_)
|
||||
) {
|
||||
self.adjustments.send(
|
||||
clock_type,
|
||||
AdjustmentRowMsg::ValueRatio(self.vram_clock_ratio),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,7 +607,7 @@ impl ClocksFrame {
|
||||
) {
|
||||
let values_len = values.len();
|
||||
for (i, value) in values.enumerate().rev() {
|
||||
let is_secondary = i > 0 && i < values_len - 1;
|
||||
let is_secondary_p_state = i > 0 && i < values_len - 1;
|
||||
|
||||
self.set_clock(
|
||||
clock_type(i as u8),
|
||||
@@ -585,7 +615,7 @@ impl ClocksFrame {
|
||||
current: value,
|
||||
min,
|
||||
max,
|
||||
is_secondary,
|
||||
is_secondary_p_state,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
@@ -596,42 +626,38 @@ impl ClocksFrame {
|
||||
self.show_nvidia_options = true;
|
||||
self.vf_curve_available = !table.gpu_vf_curve.is_empty();
|
||||
|
||||
let locked_clocks = [
|
||||
(
|
||||
let (clock_range, locked_clocks, min_type, max_type) = match self.domain {
|
||||
ClockDomain::Gpu => (
|
||||
table.gpu_clock_range,
|
||||
table.gpu_locked_clocks,
|
||||
ClockspeedType::MinCoreClock,
|
||||
ClockspeedType::MaxCoreClock,
|
||||
self.enable_gpu_locked_clocks.clone(),
|
||||
),
|
||||
(
|
||||
ClockDomain::Vram => (
|
||||
table.vram_clock_range,
|
||||
table.vram_locked_clocks,
|
||||
ClockspeedType::MinMemoryClock,
|
||||
ClockspeedType::MaxMemoryClock,
|
||||
self.enable_vram_locked_clocks.clone(),
|
||||
),
|
||||
];
|
||||
};
|
||||
|
||||
for (clock_range, locked_clocks, min_type, max_type, enable_binding) in locked_clocks {
|
||||
if let Some((gpu_min, gpu_max)) = clock_range {
|
||||
let (current_min, current_max) = match locked_clocks {
|
||||
Some(locked_range) => {
|
||||
enable_binding.set_value(true);
|
||||
locked_range
|
||||
}
|
||||
None => (gpu_min, gpu_max),
|
||||
};
|
||||
if let Some((gpu_min, gpu_max)) = clock_range {
|
||||
let (current_min, current_max) = match locked_clocks {
|
||||
Some(locked_range) => {
|
||||
self.enable_locked_clocks.set_value(true);
|
||||
locked_range
|
||||
}
|
||||
None => (gpu_min, gpu_max),
|
||||
};
|
||||
|
||||
self.set_clock(
|
||||
min_type,
|
||||
ClocksData::new(current_min as i32, gpu_min as i32, gpu_max as i32),
|
||||
);
|
||||
self.set_clock(
|
||||
max_type,
|
||||
ClocksData::new(current_max as i32, gpu_min as i32, gpu_max as i32),
|
||||
);
|
||||
}
|
||||
self.set_clock(
|
||||
min_type,
|
||||
ClocksData::new(current_min as i32, gpu_min as i32, gpu_max as i32),
|
||||
);
|
||||
self.set_clock(
|
||||
max_type,
|
||||
ClocksData::new(current_max as i32, gpu_min as i32, gpu_max as i32),
|
||||
);
|
||||
}
|
||||
|
||||
for (pstate, offset) in &table.gpu_offsets {
|
||||
@@ -678,30 +704,20 @@ impl ClocksFrame {
|
||||
}
|
||||
|
||||
pub fn get_commands(&self) -> Vec<SetClocksCommand> {
|
||||
self.all_groups()
|
||||
.flat_map(|group| group.get_commands())
|
||||
.filter_map(|(clock_type, configured_value)| {
|
||||
self.adjustments
|
||||
.iter()
|
||||
.filter_map(|(clock_type, row)| {
|
||||
let configured_value = row.get_changed_value().map(|value| value as i32);
|
||||
// If nvidia options are enabled, we always set locked clocks to None or Some
|
||||
let value = if self.show_nvidia_options {
|
||||
match clock_type {
|
||||
ClockspeedType::MinCoreClock | ClockspeedType::MaxCoreClock => self
|
||||
.enable_gpu_locked_clocks
|
||||
ClockspeedType::MinCoreClock
|
||||
| ClockspeedType::MaxCoreClock
|
||||
| ClockspeedType::MinMemoryClock
|
||||
| ClockspeedType::MaxMemoryClock => self
|
||||
.enable_locked_clocks
|
||||
.value()
|
||||
.then(|| {
|
||||
self.core_groups
|
||||
.get(&ClockCategory::from_type(clock_type))
|
||||
.map(|group| group.get_raw_value(clock_type))
|
||||
})
|
||||
.flatten(),
|
||||
ClockspeedType::MinMemoryClock | ClockspeedType::MaxMemoryClock => self
|
||||
.enable_vram_locked_clocks
|
||||
.value()
|
||||
.then(|| {
|
||||
self.vram_groups
|
||||
.get(&ClockCategory::from_type(clock_type))
|
||||
.map(|group| group.get_raw_value(clock_type))
|
||||
})
|
||||
.flatten(),
|
||||
.then(|| row.get_value() as i32),
|
||||
_ => Some(configured_value?),
|
||||
}
|
||||
} else {
|
||||
@@ -709,7 +725,7 @@ impl ClocksFrame {
|
||||
};
|
||||
|
||||
Some(SetClocksCommand {
|
||||
r#type: clock_type,
|
||||
r#type: *clock_type,
|
||||
value,
|
||||
})
|
||||
})
|
||||
@@ -717,40 +733,56 @@ impl ClocksFrame {
|
||||
}
|
||||
}
|
||||
|
||||
fn nvidia_clock_offset_to_data(clock_info: &NvidiaClockOffset, is_secondary: bool) -> ClocksData {
|
||||
fn nvidia_clock_offset_to_data(
|
||||
clock_info: &NvidiaClockOffset,
|
||||
is_secondary_p_state: bool,
|
||||
) -> ClocksData {
|
||||
ClocksData {
|
||||
current: clock_info.current,
|
||||
min: clock_info.min,
|
||||
max: clock_info.max,
|
||||
is_secondary,
|
||||
is_secondary_p_state,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn clock_title(clock_type: ClockspeedType) -> String {
|
||||
match clock_type {
|
||||
ClockspeedType::MaxCoreClock => fl!(I18N, "max-gpu-clock"),
|
||||
ClockspeedType::MaxMemoryClock => fl!(I18N, "max-vram-clock"),
|
||||
ClockspeedType::MaxCoreClock | ClockspeedType::MaxMemoryClock => fl!(I18N, "max-clock"),
|
||||
ClockspeedType::MaxVoltage => fl!(I18N, "max-gpu-voltage"),
|
||||
ClockspeedType::MinCoreClock => fl!(I18N, "min-gpu-clock"),
|
||||
ClockspeedType::MinMemoryClock => fl!(I18N, "min-vram-clock"),
|
||||
ClockspeedType::MinCoreClock | ClockspeedType::MinMemoryClock => fl!(I18N, "min-clock"),
|
||||
ClockspeedType::MinVoltage => fl!(I18N, "min-gpu-voltage"),
|
||||
ClockspeedType::VoltageOffset => fl!(I18N, "gpu-voltage-offset"),
|
||||
ClockspeedType::VoltageBoost => fl!(I18N, "gpu-voltage-boost"),
|
||||
ClockspeedType::GpuClockOffset(pstate) => {
|
||||
fl!(I18N, "gpu-pstate-clock-offset", pstate = pstate)
|
||||
ClockspeedType::GpuClockOffset(pstate) | ClockspeedType::MemClockOffset(pstate) => {
|
||||
fl!(I18N, "pstate-clock-offset", pstate = pstate)
|
||||
}
|
||||
ClockspeedType::MemClockOffset(pstate) => {
|
||||
fl!(I18N, "vram-pstate-clock-offset", pstate = pstate)
|
||||
ClockspeedType::GpuVfCurveClock(pstate) | ClockspeedType::MemVfCurveClock(pstate) => {
|
||||
fl!(I18N, "pstate-clock", pstate = pstate)
|
||||
}
|
||||
ClockspeedType::GpuVfCurveClock(pstate) => fl!(I18N, "gpu-pstate-clock", pstate = pstate),
|
||||
ClockspeedType::MemVfCurveClock(pstate) => fl!(I18N, "mem-pstate-clock", pstate = pstate),
|
||||
ClockspeedType::GpuVfCurveVoltage(pstate) => {
|
||||
fl!(I18N, "gpu-pstate-clock-voltage", pstate = pstate)
|
||||
}
|
||||
ClockspeedType::MemVfCurveVoltage(pstate) => {
|
||||
fl!(I18N, "mem-pstate-clock-voltage", pstate = pstate)
|
||||
ClockspeedType::GpuVfCurveVoltage(pstate) | ClockspeedType::MemVfCurveVoltage(pstate) => {
|
||||
fl!(I18N, "pstate-clock-voltage", pstate = pstate)
|
||||
}
|
||||
ClockspeedType::Reset => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_row_step(clock_type: ClockspeedType) -> f64 {
|
||||
match clock_type {
|
||||
ClockspeedType::MaxCoreClock
|
||||
| ClockspeedType::MinCoreClock
|
||||
| ClockspeedType::GpuClockOffset(_)
|
||||
| ClockspeedType::MaxMemoryClock
|
||||
| ClockspeedType::MinMemoryClock
|
||||
| ClockspeedType::MemClockOffset(_)
|
||||
| ClockspeedType::GpuVfCurveClock(_)
|
||||
| ClockspeedType::MemVfCurveClock(_) => 5.0,
|
||||
ClockspeedType::MinVoltage
|
||||
| ClockspeedType::MaxVoltage
|
||||
| ClockspeedType::VoltageOffset
|
||||
| ClockspeedType::VoltageBoost
|
||||
| ClockspeedType::GpuVfCurveVoltage(_)
|
||||
| ClockspeedType::MemVfCurveVoltage(_) => 1.0,
|
||||
ClockspeedType::Reset => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,11 @@ mod heuristics_list;
|
||||
|
||||
use crate::{
|
||||
APP_BROKER, I18N,
|
||||
app::{components::page_section::PageSection, msg::AppMsg, pages::oc_page::OcPageMsg},
|
||||
app::{msg::AppMsg, pages::oc_page::OcPageMsg},
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use amdgpu_sysfs::gpu_handle::{PerformanceLevel, power_profile_mode::PowerProfileModesTable};
|
||||
use gtk::{
|
||||
StringObject,
|
||||
gio::prelude::ListModelExt,
|
||||
glib::object::Cast,
|
||||
prelude::{BoxExt, ListBoxRowExt, OrientableExt, WidgetExt},
|
||||
};
|
||||
use gtk::StringObject;
|
||||
use heuristics_list::PowerProfileHeuristicsList;
|
||||
use i18n_embed_fl::fl;
|
||||
use nvml_wrapper::enums::device::PowerMizerMode;
|
||||
@@ -57,11 +53,15 @@ impl relm4::Component for PerformanceFrame {
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
PageSection::new("Performance") {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
#[watch]
|
||||
set_visible: model.performance_level.is_some() || model.active_power_mizer_mode.is_some(),
|
||||
set_visible: model.performance_level.is_some()
|
||||
|| model.power_profile_modes_table.is_some()
|
||||
|| model.active_power_mizer_mode.is_some(),
|
||||
|
||||
append_child = >k::Box {
|
||||
append = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
#[watch]
|
||||
@@ -114,7 +114,7 @@ impl relm4::Component for PerformanceFrame {
|
||||
},
|
||||
},
|
||||
|
||||
append_child = >k::Box {
|
||||
append = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
#[watch]
|
||||
@@ -183,7 +183,7 @@ impl relm4::Component for PerformanceFrame {
|
||||
},
|
||||
},
|
||||
|
||||
append_child = >k::Box {
|
||||
append = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
#[watch]
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
use crate::{
|
||||
APP_BROKER, I18N,
|
||||
app::{
|
||||
components::{adjustment_value::AdjustmentValue, page_section::PageSection},
|
||||
msg::AppMsg,
|
||||
pages::PageUpdate,
|
||||
},
|
||||
};
|
||||
use gtk::{
|
||||
glib::object::ObjectExt,
|
||||
prelude::{AdjustmentExt, BoxExt, ButtonExt, OrientableExt, RangeExt, ScaleExt, WidgetExt},
|
||||
};
|
||||
use i18n_embed_fl::fl;
|
||||
use lact_schema::PowerStats;
|
||||
use relm4::{ComponentParts, ComponentSender, RelmWidgetExt};
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PowerCapSection {
|
||||
power: PowerStats,
|
||||
adjustment: AdjustmentValue,
|
||||
value_text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PowerCapMsg {
|
||||
Update(PageUpdate),
|
||||
RefreshText,
|
||||
Reset,
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl relm4::Component for PowerCapSection {
|
||||
type Init = ();
|
||||
type Input = PowerCapMsg;
|
||||
type Output = ();
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
#[root]
|
||||
PageSection::new(&fl!(I18N, "power-cap")) {
|
||||
append_header = >k::Button {
|
||||
set_label: &fl!(I18N, "default-button"),
|
||||
connect_clicked => PowerCapMsg::Reset,
|
||||
|
||||
set_halign: gtk::Align::End,
|
||||
set_hexpand: true,
|
||||
},
|
||||
append_child = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 10,
|
||||
|
||||
gtk::Label {
|
||||
#[watch]
|
||||
set_label: &model.value_text,
|
||||
},
|
||||
|
||||
gtk::Scale {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_hexpand: true,
|
||||
set_round_digits: 0,
|
||||
set_margin_horizontal: 5,
|
||||
set_draw_value: false,
|
||||
set_adjustment: adjustment,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
#[local_ref]
|
||||
adjustment -> AdjustmentValue {
|
||||
connect_value_notify => move |_| {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
} @ value_notify,
|
||||
connect_value_notify => PowerCapMsg::RefreshText,
|
||||
connect_upper_notify => PowerCapMsg::RefreshText,
|
||||
},
|
||||
}
|
||||
|
||||
fn init(
|
||||
_init: Self::Init,
|
||||
root: Self::Root,
|
||||
_sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let model = Self::default();
|
||||
let adjustment = &model.adjustment;
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update_with_view(
|
||||
&mut self,
|
||||
widgets: &mut Self::Widgets,
|
||||
msg: Self::Input,
|
||||
sender: ComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
match msg {
|
||||
PowerCapMsg::Update(PageUpdate::Stats(stats)) => {
|
||||
// The signal blocking has to be manual,
|
||||
// because relm's signal block macro feature doesn't seem to work with non-widget objects
|
||||
self.adjustment.block_signal(&widgets.value_notify);
|
||||
let power = stats.power.clone();
|
||||
|
||||
self.adjustment.set_upper(power.cap_max.unwrap_or_default());
|
||||
self.adjustment.set_lower(power.cap_min.unwrap_or_default());
|
||||
self.adjustment
|
||||
.set_initial_value(power.cap_current.unwrap_or_default());
|
||||
|
||||
self.adjustment.unblock_signal(&widgets.value_notify);
|
||||
|
||||
self.power = power;
|
||||
}
|
||||
PowerCapMsg::Update(PageUpdate::Info(_)) => (),
|
||||
PowerCapMsg::RefreshText => {
|
||||
self.value_text.clear();
|
||||
write!(
|
||||
self.value_text,
|
||||
"{}/{} {}",
|
||||
self.adjustment.value(),
|
||||
self.adjustment.upper(),
|
||||
fl!(I18N, "watt")
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
PowerCapMsg::Reset => {
|
||||
self.adjustment
|
||||
.set_value(self.power.cap_default.unwrap_or_default());
|
||||
}
|
||||
}
|
||||
|
||||
self.update_view(widgets, sender);
|
||||
}
|
||||
}
|
||||
|
||||
impl PowerCapSection {
|
||||
pub fn get_user_cap(&self) -> Option<f64> {
|
||||
self.adjustment.get_changed_value(true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
use crate::{
|
||||
APP_BROKER, I18N,
|
||||
app::{
|
||||
components::{
|
||||
adjustment_card::AdjustmentCard,
|
||||
adjustment_row::{AdjustmentRow, AdjustmentRowInit, AdjustmentRowMsg},
|
||||
page_section::PageSection,
|
||||
},
|
||||
msg::AppMsg,
|
||||
pages::oc_page::{
|
||||
OcPageMsg,
|
||||
performance_frame::{PerformanceFrame, PerformanceFrameMsg},
|
||||
},
|
||||
utils::ext::RelmDefaultLauchable,
|
||||
},
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use amdgpu_sysfs::gpu_handle::PerformanceLevel;
|
||||
use i18n_embed_fl::fl;
|
||||
use lact_schema::PowerStats;
|
||||
use nvml_wrapper::enums::device::PowerMizerMode;
|
||||
use relm4::{ComponentController, ComponentParts, ComponentSender, factory::FactoryHashMap};
|
||||
|
||||
pub struct PowerFrame {
|
||||
power: PowerStats,
|
||||
power_row: FactoryHashMap<(), AdjustmentRow<()>>,
|
||||
performance_frame: relm4::Controller<PerformanceFrame>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PowerFrameMsg {
|
||||
PowerStats(PowerStats),
|
||||
Performance(PerformanceFrameMsg),
|
||||
RefreshVisibility,
|
||||
Reset,
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl relm4::Component for PowerFrame {
|
||||
type Init = ();
|
||||
type Input = PowerFrameMsg;
|
||||
type Output = OcPageMsg;
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
#[root]
|
||||
PageSection::new(&fl!(I18N, "power-section")) {
|
||||
#[watch]
|
||||
set_visible: model.is_available(),
|
||||
|
||||
append_header = >k::Button {
|
||||
set_label: &fl!(I18N, "default-button"),
|
||||
connect_clicked => PowerFrameMsg::Reset,
|
||||
|
||||
set_halign: gtk::Align::End,
|
||||
set_hexpand: true,
|
||||
#[watch]
|
||||
set_visible: !model.power_row.is_empty(),
|
||||
},
|
||||
#[template]
|
||||
append_child = &AdjustmentCard {
|
||||
#[template_child]
|
||||
content {
|
||||
#[local_ref]
|
||||
power_row_widget -> gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
#[watch]
|
||||
set_visible: !model.power_row.is_empty(),
|
||||
},
|
||||
|
||||
append: model.performance_frame.widget(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fn init(
|
||||
_init: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let model = Self {
|
||||
power: PowerStats::default(),
|
||||
power_row: FactoryHashMap::builder()
|
||||
.launch_default()
|
||||
.forward(APP_BROKER.sender(), |()| AppMsg::SettingsChanged),
|
||||
performance_frame: PerformanceFrame::launch_default()
|
||||
.forward(sender.output_sender(), |msg| msg),
|
||||
};
|
||||
let visibility_sender = sender.clone();
|
||||
model
|
||||
.performance_frame
|
||||
.widget()
|
||||
.connect_visible_notify(move |_| {
|
||||
visibility_sender.input(PowerFrameMsg::RefreshVisibility);
|
||||
});
|
||||
|
||||
let power_row_widget = model.power_row.widget();
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update_with_view(
|
||||
&mut self,
|
||||
widgets: &mut Self::Widgets,
|
||||
msg: Self::Input,
|
||||
sender: ComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
match msg {
|
||||
PowerFrameMsg::PowerStats(power) => {
|
||||
self.power_row.clear();
|
||||
|
||||
if let Some(value) = power.cap_current {
|
||||
self.power_row.insert(
|
||||
(),
|
||||
AdjustmentRowInit {
|
||||
title: format!("{} ({})", fl!(I18N, "power-cap"), fl!(I18N, "watt")),
|
||||
value,
|
||||
lower: power.cap_min.unwrap_or_default(),
|
||||
upper: power.cap_max.unwrap_or_default(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
self.power = power;
|
||||
}
|
||||
PowerFrameMsg::Performance(msg) => {
|
||||
self.performance_frame.emit(msg);
|
||||
}
|
||||
PowerFrameMsg::RefreshVisibility => (),
|
||||
PowerFrameMsg::Reset => {
|
||||
if !self.power_row.is_empty() {
|
||||
self.power_row.send(
|
||||
&(),
|
||||
AdjustmentRowMsg::SetValue(self.power.cap_default.unwrap_or_default()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.update_view(widgets, sender);
|
||||
}
|
||||
}
|
||||
|
||||
impl PowerFrame {
|
||||
fn is_available(&self) -> bool {
|
||||
!self.power_row.is_empty() || self.performance_frame.widget().get_visible()
|
||||
}
|
||||
|
||||
pub fn get_user_cap(&self) -> Option<f64> {
|
||||
self.power_row
|
||||
.get(&())?
|
||||
.get_changed_value()
|
||||
.filter(|value| *value != 0.0)
|
||||
}
|
||||
|
||||
pub fn performance_level(&self) -> Option<PerformanceLevel> {
|
||||
self.performance_frame.model().performance_level()
|
||||
}
|
||||
|
||||
pub fn active_power_mizer_mode(&self) -> Option<PowerMizerMode> {
|
||||
self.performance_frame.model().active_power_mizer_mode()
|
||||
}
|
||||
|
||||
pub fn power_profile_mode(&self) -> Option<u16> {
|
||||
self.performance_frame.model().power_profile_mode()
|
||||
}
|
||||
|
||||
pub fn power_profile_mode_custom_heuristics(&self) -> Vec<Vec<Option<i32>>> {
|
||||
self.performance_frame
|
||||
.model()
|
||||
.power_profile_mode_custom_heuristics()
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,15 @@ use super::power_states_list::{PowerStatesListMsg, PowerStatesListOptions};
|
||||
use crate::{
|
||||
APP_BROKER, I18N,
|
||||
app::{
|
||||
components::page_section_expander::PageSectionExpander, msg::AppMsg,
|
||||
pages::oc_page::OcPageMsg, utils::ext::RelmLaunchable as _,
|
||||
components::{adjustment_card::AdjustmentCard, page_section::PageSection},
|
||||
msg::AppMsg,
|
||||
pages::oc_page::OcPageMsg,
|
||||
utils::ext::RelmLaunchable as _,
|
||||
},
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use amdgpu_sysfs::gpu_handle::{PerformanceLevel, PowerLevelKind};
|
||||
use gtk::{
|
||||
glib::{SignalHandlerId, object::ObjectExt},
|
||||
prelude::{BoxExt, CheckButtonExt, OrientableExt, WidgetExt},
|
||||
};
|
||||
use gtk::glib::{SignalHandlerId, object::ObjectExt};
|
||||
use i18n_embed_fl::fl;
|
||||
use indexmap::IndexMap;
|
||||
use lact_schema::{DeviceStats, PowerStates};
|
||||
@@ -57,44 +57,61 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
type Output = OcPageMsg;
|
||||
|
||||
view! {
|
||||
PageSectionExpander::new(&fl!(I18N, "pstates")) {
|
||||
append_expandable = >k::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
|
||||
gtk::CheckButton {
|
||||
set_label: Some(&fl!(I18N, "enable-pstate-config")),
|
||||
#[watch]
|
||||
#[block_signal(configured_toggled_handler)]
|
||||
set_active: model.states_configuration_enabled.value(),
|
||||
|
||||
connect_toggled[sender] => move |button| {
|
||||
sender.input(PowerStatesFrameMsg::ConfiguredToggled {
|
||||
configured: button.is_active(),
|
||||
});
|
||||
} @ configured_toggled_handler,
|
||||
|
||||
PageSection::new(&fl!(I18N, "pstates")) {
|
||||
#[template]
|
||||
append_child = &AdjustmentCard {
|
||||
#[template_child]
|
||||
advanced_features {
|
||||
#[watch]
|
||||
set_visible: model.performance_level.is_some(),
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
set_spacing: 10,
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
#[template_child]
|
||||
controls {
|
||||
gtk::ToggleButton {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "adjustment-card-option-toggle",
|
||||
|
||||
gtk::Box {
|
||||
#[watch]
|
||||
set_visible: model.has_core_states,
|
||||
append = model.core_states_list.widget(),
|
||||
},
|
||||
#[block_signal(configured_toggled_handler)]
|
||||
set_active: model.states_configuration_enabled.value(),
|
||||
|
||||
gtk::Box {
|
||||
#[watch]
|
||||
set_visible: model.has_vram_states,
|
||||
append = model.vram_states_list.widget(),
|
||||
connect_toggled[sender] => move |button| {
|
||||
sender.input(PowerStatesFrameMsg::ConfiguredToggled {
|
||||
configured: button.is_active(),
|
||||
});
|
||||
} @ configured_toggled_handler,
|
||||
|
||||
#[wrap(Some)]
|
||||
set_child = >k::Box {
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "enable-pstate-config"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
content {
|
||||
gtk::Box {
|
||||
set_spacing: 10,
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_homogeneous: true,
|
||||
|
||||
gtk::Box {
|
||||
#[watch]
|
||||
set_visible: model.has_core_states,
|
||||
append = model.core_states_list.widget(),
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
#[watch]
|
||||
set_visible: model.has_vram_states,
|
||||
append = model.vram_states_list.widget(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -542,14 +542,14 @@ impl VfCurveEditor {
|
||||
if let Some((min_freq, max_freq)) = self.locked_clocks_range.get() {
|
||||
let mut curves = vec![(
|
||||
[(x_spec.start, max_freq), (x_spec.end, max_freq)],
|
||||
fl!(I18N, "max-gpu-clock"),
|
||||
fl!(I18N, "max-clock"),
|
||||
colors.error,
|
||||
)];
|
||||
|
||||
if min_freq >= y_spec.start {
|
||||
curves.push((
|
||||
[(x_spec.start, min_freq), (x_spec.end, min_freq)],
|
||||
fl!(I18N, "min-gpu-clock"),
|
||||
fl!(I18N, "min-clock"),
|
||||
colors.warning,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ use std::cell::RefCell;
|
||||
use crate::{CONFIG, app::utils::color_scheme::AppColorScheme};
|
||||
|
||||
pub const COMBINED_CSS: &str = include_str!(concat!(env!("OUT_DIR"), "/combined.css"));
|
||||
// cosmic gtk4 override has 800 prio, so we have to beat it
|
||||
const BREEZE_CSS_PRIORITY: u32 = 801;
|
||||
pub const APPLICATION_CSS_PRIORITY: u32 = 802;
|
||||
|
||||
macro_rules! include_theme_str {
|
||||
($file:literal) => {
|
||||
@@ -64,7 +67,7 @@ pub fn apply_theme(theme: AppTheme) -> anyhow::Result<()> {
|
||||
#[allow(deprecated)]
|
||||
provider.load_from_data(css);
|
||||
|
||||
style_context_add_provider_for_display(&display, &provider, 900);
|
||||
style_context_add_provider_for_display(&display, &provider, BREEZE_CSS_PRIORITY);
|
||||
|
||||
EXISTING_STYLE_PROVIDER.set(Some(provider));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user