mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2026-11-03 20:56:39 -06:00
feat: inline power states (#1213)
* feat: inline power states * markup fixes * polish p-states row * align the p-state values by right side * refactor --------- Co-authored-by: Ilya Zlobintsev <ilya.zl@protonmail.com>
This commit is contained in:
1 parent
6a2e669a2f
commit
8d7361a4a9
11 files changed
+336
-161
No files matched your search
@@ -217,6 +217,7 @@ pstate-clock-offset = P-State {$pstate} Clock Offset
|
||||
pstate-clock = P-State {$pstate} Clock
|
||||
pstate-clock-voltage = P-State {$pstate} Voltage
|
||||
|
||||
pstate = Power State
|
||||
pstates = Power States
|
||||
gpu-pstates = GPU Power States
|
||||
vram-pstates = VRAM Power States
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
.adjustment-list > row {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.adjustment-list > row.adjustment-row {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.adjustment-list > row:first-child {
|
||||
.adjustment-list > row.adjustment-row:first-child {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.adjustment-list > row:last-child {
|
||||
.adjustment-list > row.adjustment-row:last-child {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
/* The card owns row spacing, including Adwaita action rows. */
|
||||
.adjustment-list > row > box.header {
|
||||
margin: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.adjustment-list > row > box.header > box.title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.adjustment-row scale {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
@@ -75,11 +75,10 @@ impl<Key: 'static> FactoryComponent for AdjustmentRow<Key> {
|
||||
gtk::ListBoxRow {
|
||||
set_activatable: false,
|
||||
set_selectable: false,
|
||||
add_css_class: "adjustment-row",
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
add_css_class: "adjustment-row",
|
||||
|
||||
gtk::Box {
|
||||
set_spacing: 12,
|
||||
|
||||
@@ -2,3 +2,14 @@
|
||||
min-width: 400px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.power-states-dialog-card list:not(.boxed-list) {
|
||||
/* Embedded lists use the surrounding card's background, not the view background. */
|
||||
background: none;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.power-states-dialog-card .page-section-children-box,
|
||||
.power-states-dialog-card .adjustment-card-advanced-features {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ use lact_schema::{ClocksTable, DeviceInfo, PowerStates};
|
||||
use nvml_wrapper::enums::device::PowerMizerMode;
|
||||
use performance_frame::PerformanceFrameMsg;
|
||||
use power_frame::{PowerFrame, PowerFrameMsg};
|
||||
use power_states::power_states_frame::{PowerStatesFrame, PowerStatesFrameMsg};
|
||||
use power_states::power_states_dialog::{PowerStatesDialog, PowerStatesDialogMsg};
|
||||
use relm4::binding::BoolBinding;
|
||||
use relm4::{ComponentController, ComponentParts, ComponentSender, RelmWidgetExt};
|
||||
use std::collections::HashSet;
|
||||
@@ -37,7 +37,7 @@ pub struct OcPage {
|
||||
device_info: Option<Arc<DeviceInfo>>,
|
||||
|
||||
power_frame: relm4::Controller<PowerFrame>,
|
||||
power_states_frame: relm4::Controller<PowerStatesFrame>,
|
||||
power_states_dialog: relm4::Controller<PowerStatesDialog>,
|
||||
gpu_clocks_frame: relm4::Controller<ClocksFrame>,
|
||||
vram_clocks_frame: relm4::Controller<ClocksFrame>,
|
||||
|
||||
@@ -62,6 +62,7 @@ pub enum OcPageMsg {
|
||||
PerformanceLevelChanged,
|
||||
SetPerformanceLevel(PerformanceLevel),
|
||||
EnablePstateConfig,
|
||||
ShowPowerStates(ClockDomain),
|
||||
ShowVfCurveEditor,
|
||||
VfCurveEditingToggled(bool),
|
||||
}
|
||||
@@ -87,8 +88,6 @@ impl relm4::Component for OcPage {
|
||||
ColumnBias::Left, // Core
|
||||
ColumnBias::Right, // VRAM
|
||||
ColumnBias::Left, // Power
|
||||
ColumnBias::Right, // Power States
|
||||
|
||||
])),
|
||||
set_valign: gtk::Align::Start,
|
||||
|
||||
@@ -103,10 +102,6 @@ impl relm4::Component for OcPage {
|
||||
model.power_frame.widget() {
|
||||
add_css_class: "oc-page-section",
|
||||
},
|
||||
|
||||
model.power_states_frame.widget() {
|
||||
add_css_class: "oc-page-section",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -145,8 +140,8 @@ impl relm4::Component for OcPage {
|
||||
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 power_states_dialog =
|
||||
PowerStatesDialog::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 {
|
||||
@@ -158,7 +153,7 @@ impl relm4::Component for OcPage {
|
||||
stats_section,
|
||||
device_info: None,
|
||||
power_frame,
|
||||
power_states_frame,
|
||||
power_states_dialog,
|
||||
gpu_clocks_frame,
|
||||
vram_clocks_frame,
|
||||
vf_curve_editor,
|
||||
@@ -174,13 +169,17 @@ impl relm4::Component for OcPage {
|
||||
widgets: &mut Self::Widgets,
|
||||
msg: Self::Input,
|
||||
sender: ComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
root: &Self::Root,
|
||||
) {
|
||||
match msg {
|
||||
OcPageMsg::Update { update, initial } => match &update {
|
||||
PageUpdate::Stats(stats) => {
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::Stats(stats.clone()));
|
||||
self.power_states_dialog
|
||||
.emit(PowerStatesDialogMsg::Stats(stats.clone()));
|
||||
self.gpu_clocks_frame
|
||||
.emit(ClocksFrameMsg::Stats(stats.clone()));
|
||||
self.vram_clocks_frame
|
||||
.emit(ClocksFrameMsg::Stats(stats.clone()));
|
||||
|
||||
self.stats_section
|
||||
.emit(GpuStatsSectionMsg::Stats(stats.clone()));
|
||||
@@ -208,8 +207,8 @@ impl relm4::Component for OcPage {
|
||||
self.device_info = Some(info.clone());
|
||||
self.stats_section
|
||||
.emit(GpuStatsSectionMsg::Info(info.clone()));
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::VramClockRatio(vram_clock_ratio));
|
||||
self.power_states_dialog
|
||||
.emit(PowerStatesDialogMsg::VramClockRatio(vram_clock_ratio));
|
||||
self.vram_clocks_frame
|
||||
.emit(ClocksFrameMsg::VramRatio(vram_clock_ratio));
|
||||
}
|
||||
@@ -240,11 +239,19 @@ impl relm4::Component for OcPage {
|
||||
pstates,
|
||||
configured,
|
||||
} => {
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::PowerStates {
|
||||
self.power_states_dialog
|
||||
.emit(PowerStatesDialogMsg::PowerStates {
|
||||
pstates: pstates.clone(),
|
||||
configured,
|
||||
});
|
||||
self.gpu_clocks_frame
|
||||
.emit(ClocksFrameMsg::PowerStatesAvailable(
|
||||
!pstates.core.is_empty(),
|
||||
));
|
||||
self.vram_clocks_frame
|
||||
.emit(ClocksFrameMsg::PowerStatesAvailable(
|
||||
!pstates.vram.is_empty(),
|
||||
));
|
||||
self.stats_section
|
||||
.emit(GpuStatsSectionMsg::PowerStates(Arc::new(pstates)));
|
||||
sender.input(OcPageMsg::PerformanceLevelChanged);
|
||||
@@ -252,13 +259,13 @@ impl relm4::Component for OcPage {
|
||||
OcPageMsg::PerformanceLevelChanged => {
|
||||
let custom_pstates_configurable =
|
||||
self.get_performance_level() == Some(PerformanceLevel::Manual);
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::Configurable(
|
||||
self.power_states_dialog
|
||||
.emit(PowerStatesDialogMsg::Configurable(
|
||||
custom_pstates_configurable,
|
||||
));
|
||||
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::PerformanceLevel(
|
||||
self.power_states_dialog
|
||||
.emit(PowerStatesDialogMsg::PerformanceLevel(
|
||||
self.get_performance_level(),
|
||||
));
|
||||
}
|
||||
@@ -269,8 +276,14 @@ impl relm4::Component for OcPage {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
}
|
||||
OcPageMsg::EnablePstateConfig => {
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::EnableWithManualPerformanceLevel);
|
||||
self.power_states_dialog
|
||||
.emit(PowerStatesDialogMsg::EnableWithManualPerformanceLevel);
|
||||
}
|
||||
OcPageMsg::ShowPowerStates(domain) => {
|
||||
self.power_states_dialog.emit(PowerStatesDialogMsg::Show {
|
||||
domain,
|
||||
parent: root.clone().upcast(),
|
||||
});
|
||||
}
|
||||
OcPageMsg::ShowVfCurveEditor => {
|
||||
self.vf_curve_editor.emit(VfCurveEditorMsg::Show);
|
||||
@@ -329,7 +342,7 @@ impl OcPage {
|
||||
|
||||
pub fn get_enabled_power_states(&self) -> IndexMap<PowerLevelKind, Vec<u8>> {
|
||||
if self.get_performance_level() == Some(PerformanceLevel::Manual) {
|
||||
self.power_states_frame.model().get_enabled_power_states()
|
||||
self.power_states_dialog.model().get_enabled_power_states()
|
||||
} else {
|
||||
IndexMap::new()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.adjustment-list > row.pstates {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
@@ -11,10 +11,10 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use amdgpu_sysfs::gpu_handle::overdrive::ClocksTableGen as AmdClocksTable;
|
||||
use amdgpu_sysfs::gpu_handle::{PowerLevelId, overdrive::ClocksTableGen as AmdClocksTable};
|
||||
use i18n_embed_fl::fl;
|
||||
use lact_schema::{
|
||||
ClocksTable, IntelClocksTable, NvidiaClockOffset, NvidiaClocksTable,
|
||||
ClocksTable, DeviceStats, IntelClocksTable, NvidiaClockOffset, NvidiaClocksTable,
|
||||
request::{ClockspeedType, SetClocksCommand},
|
||||
};
|
||||
use relm4::{
|
||||
@@ -35,6 +35,9 @@ pub struct ClocksFrame {
|
||||
show_all_pstates: BoolBinding,
|
||||
vf_curve_editing: BoolBinding,
|
||||
enable_locked_clocks: BoolBinding,
|
||||
has_power_states: bool,
|
||||
active_power_state: Option<PowerLevelId>,
|
||||
current_clock: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -99,6 +102,8 @@ pub enum ClocksFrameMsg {
|
||||
vf_curve_is_configured: bool,
|
||||
},
|
||||
VramRatio(f64),
|
||||
PowerStatesAvailable(bool),
|
||||
Stats(Arc<DeviceStats>),
|
||||
TogglePStatesVisibility,
|
||||
ResetGpuClockOffsets,
|
||||
}
|
||||
@@ -119,7 +124,9 @@ impl relm4::Component for ClocksFrame {
|
||||
ClockDomain::Vram => fl!(I18N, "vram-section"),
|
||||
},
|
||||
#[watch]
|
||||
set_visible: model.domain == ClockDomain::Gpu || model.has_any_clocks(),
|
||||
set_visible: model.domain == ClockDomain::Gpu
|
||||
|| model.has_any_clocks()
|
||||
|| model.has_power_states,
|
||||
|
||||
append_header = >k::Box {
|
||||
set_spacing: 10,
|
||||
@@ -245,6 +252,43 @@ impl relm4::Component for ClocksFrame {
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
},
|
||||
|
||||
adw::ActionRow {
|
||||
set_title: &fl!(I18N, "pstate"),
|
||||
set_activatable: true,
|
||||
add_css_class: "pstates",
|
||||
#[watch]
|
||||
set_visible: model.has_power_states,
|
||||
connect_activated[sender, domain = model.domain] => move |_| {
|
||||
sender.output(OcPageMsg::ShowPowerStates(domain)).unwrap();
|
||||
},
|
||||
|
||||
add_suffix = >k::Box {
|
||||
set_spacing: 4,
|
||||
set_valign: gtk::Align::Center,
|
||||
|
||||
gtk::Label {
|
||||
add_css_class: css::HEADING,
|
||||
#[watch]
|
||||
set_label: &match model.active_power_state {
|
||||
Some(PowerLevelId::Index(index)) => format!("P{index}"),
|
||||
Some(PowerLevelId::Sleep) => "S".to_owned(),
|
||||
None => "N/A".to_owned(),
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
add_css_class: css::DIM_LABEL,
|
||||
#[watch]
|
||||
set_label: &model.power_state_clock_label(),
|
||||
},
|
||||
},
|
||||
|
||||
add_suffix = >k::Image {
|
||||
set_icon_name: Some("go-next-symbolic"),
|
||||
add_css_class: css::DIM_LABEL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -272,6 +316,9 @@ impl relm4::Component for ClocksFrame {
|
||||
show_all_pstates,
|
||||
vf_curve_editing,
|
||||
enable_locked_clocks: BoolBinding::new(false),
|
||||
has_power_states: false,
|
||||
active_power_state: None,
|
||||
current_clock: None,
|
||||
};
|
||||
|
||||
for binding in [
|
||||
@@ -287,6 +334,13 @@ impl relm4::Component for ClocksFrame {
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
model.adjustments.widget().set_sort_func(|left, right| {
|
||||
// Keep the pstates row after dynamically inserted clock controls.
|
||||
left.has_css_class("pstates")
|
||||
.cmp(&right.has_css_class("pstates"))
|
||||
.into()
|
||||
});
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
@@ -363,6 +417,16 @@ impl relm4::Component for ClocksFrame {
|
||||
self.vram_clock_ratio = vram_ratio;
|
||||
self.update_vram_clock_ratio();
|
||||
}
|
||||
ClocksFrameMsg::PowerStatesAvailable(available) => {
|
||||
self.has_power_states = available;
|
||||
}
|
||||
ClocksFrameMsg::Stats(stats) => {
|
||||
let active_states = stats.active_power_states.unwrap_or_default();
|
||||
(self.active_power_state, self.current_clock) = match self.domain {
|
||||
ClockDomain::Gpu => (active_states.core, stats.clockspeed.gpu_clockspeed),
|
||||
ClockDomain::Vram => (active_states.memory, stats.clockspeed.vram_clockspeed),
|
||||
};
|
||||
}
|
||||
ClocksFrameMsg::TogglePStatesVisibility => {
|
||||
if !self.secondary_p_states_enabled() && self.show_all_pstates.value() {
|
||||
self.show_all_pstates.set_value(false);
|
||||
@@ -399,6 +463,19 @@ impl relm4::Component for ClocksFrame {
|
||||
}
|
||||
|
||||
impl ClocksFrame {
|
||||
fn power_state_clock_label(&self) -> String {
|
||||
match self.current_clock {
|
||||
Some(clock) => {
|
||||
let ratio = match self.domain {
|
||||
ClockDomain::Gpu => 1.0,
|
||||
ClockDomain::Vram => self.vram_clock_ratio,
|
||||
};
|
||||
format!("{:.0} {}", clock as f64 * ratio, fl!(I18N, "mhz"))
|
||||
}
|
||||
None => "N/A".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn secondary_p_states_enabled(&self) -> bool {
|
||||
!self.show_nvidia_options || CONFIG.read().experimental_nvidia_pstate_offsets
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pub mod power_states_frame;
|
||||
pub mod power_states_dialog;
|
||||
mod power_states_list;
|
||||
mod power_states_row;
|
||||
+110
-78
@@ -5,7 +5,7 @@ use crate::{
|
||||
app::{
|
||||
components::{adjustment_card::AdjustmentCard, page_section::PageSection},
|
||||
msg::AppMsg,
|
||||
pages::oc_page::OcPageMsg,
|
||||
pages::oc_page::{OcPageMsg, clocks_frame::ClockDomain},
|
||||
utils::ext::RelmLaunchable as _,
|
||||
},
|
||||
};
|
||||
@@ -16,12 +16,13 @@ use i18n_embed_fl::fl;
|
||||
use indexmap::IndexMap;
|
||||
use lact_schema::{DeviceStats, PowerStates};
|
||||
use relm4::{
|
||||
ComponentController, ComponentParts, ComponentSender,
|
||||
ComponentController, ComponentParts, ComponentSender, RelmWidgetExt,
|
||||
binding::{Binding, BoolBinding},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct PowerStatesFrame {
|
||||
pub struct PowerStatesDialog {
|
||||
domain: ClockDomain,
|
||||
core_states_list: relm4::Controller<PowerStatesList>,
|
||||
vram_states_list: relm4::Controller<PowerStatesList>,
|
||||
states_configurable: BoolBinding,
|
||||
@@ -34,7 +35,11 @@ pub struct PowerStatesFrame {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PowerStatesFrameMsg {
|
||||
pub enum PowerStatesDialogMsg {
|
||||
Show {
|
||||
domain: ClockDomain,
|
||||
parent: gtk::Widget,
|
||||
},
|
||||
PowerStates {
|
||||
pstates: PowerStates,
|
||||
configured: bool,
|
||||
@@ -51,69 +56,83 @@ pub enum PowerStatesFrameMsg {
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
impl relm4::Component for PowerStatesDialog {
|
||||
type Init = ();
|
||||
type Input = PowerStatesFrameMsg;
|
||||
type Input = PowerStatesDialogMsg;
|
||||
type Output = OcPageMsg;
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
PageSection {
|
||||
set_name: fl!(I18N, "pstates"),
|
||||
set_hide_visible_container: true,
|
||||
#[template]
|
||||
append_child = &AdjustmentCard {
|
||||
#[template_child]
|
||||
advanced_features {
|
||||
#[watch]
|
||||
set_visible: model.performance_level.is_some(),
|
||||
},
|
||||
adw::Dialog {
|
||||
set_content_width: 420,
|
||||
set_follows_content_size: true,
|
||||
#[watch]
|
||||
set_title: &match model.domain {
|
||||
ClockDomain::Gpu => fl!(I18N, "gpu-pstates"),
|
||||
ClockDomain::Vram => fl!(I18N, "vram-pstates"),
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
controls {
|
||||
gtk::ToggleButton {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "adjustment-card-option-toggle",
|
||||
#[wrap(Some)]
|
||||
set_child = &adw::ToolbarView {
|
||||
add_top_bar = &adw::HeaderBar {},
|
||||
|
||||
#[watch]
|
||||
#[block_signal(configured_toggled_handler)]
|
||||
set_active: model.states_configuration_enabled.value(),
|
||||
#[wrap(Some)]
|
||||
set_content = >k::ScrolledWindow {
|
||||
set_hscrollbar_policy: gtk::PolicyType::Never,
|
||||
set_propagate_natural_height: true,
|
||||
set_max_content_height: 500,
|
||||
|
||||
connect_toggled[sender] => move |button| {
|
||||
sender.input(PowerStatesFrameMsg::ConfiguredToggled {
|
||||
configured: button.is_active(),
|
||||
});
|
||||
} @ configured_toggled_handler,
|
||||
PageSection {
|
||||
set_hide_visible_container: true,
|
||||
set_margin_all: 15,
|
||||
add_css_class: "power-states-dialog-card",
|
||||
|
||||
#[wrap(Some)]
|
||||
set_child = >k::Box {
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "enable-pstate-config"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
content {
|
||||
gtk::ListBoxRow {
|
||||
set_activatable: false,
|
||||
set_selectable: false,
|
||||
|
||||
gtk::Box {
|
||||
set_spacing: 10,
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_homogeneous: true,
|
||||
|
||||
gtk::Box {
|
||||
#[template]
|
||||
append_child = &AdjustmentCard {
|
||||
#[template_child]
|
||||
advanced_features {
|
||||
#[watch]
|
||||
set_visible: model.has_core_states,
|
||||
append = model.core_states_list.widget(),
|
||||
set_visible: model.performance_level.is_some(),
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
#[watch]
|
||||
set_visible: model.has_vram_states,
|
||||
append = model.vram_states_list.widget(),
|
||||
#[template_child]
|
||||
controls {
|
||||
gtk::ToggleButton {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "adjustment-card-option-toggle",
|
||||
set_label: &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(PowerStatesDialogMsg::ConfiguredToggled {
|
||||
configured: button.is_active(),
|
||||
});
|
||||
} @ configured_toggled_handler,
|
||||
},
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
content {
|
||||
gtk::ListBoxRow {
|
||||
set_activatable: false,
|
||||
set_selectable: false,
|
||||
#[watch]
|
||||
set_visible: model.domain == ClockDomain::Gpu && model.has_core_states,
|
||||
#[wrap(Some)]
|
||||
set_child = model.core_states_list.widget(),
|
||||
},
|
||||
|
||||
gtk::ListBoxRow {
|
||||
set_activatable: false,
|
||||
set_selectable: false,
|
||||
#[watch]
|
||||
set_visible: model.domain == ClockDomain::Vram && model.has_vram_states,
|
||||
#[wrap(Some)]
|
||||
set_child = model.vram_states_list.widget(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -128,11 +147,9 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let core_states_list = PowerStatesList::detach(PowerStatesListOptions {
|
||||
title: fl!(I18N, "gpu-pstates"),
|
||||
value_suffix: fl!(I18N, "mhz"),
|
||||
});
|
||||
let vram_states_list = PowerStatesList::detach(PowerStatesListOptions {
|
||||
title: fl!(I18N, "vram-pstates"),
|
||||
value_suffix: fl!(I18N, "mhz"),
|
||||
});
|
||||
|
||||
@@ -141,13 +158,14 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
let configured_sender = sender.clone();
|
||||
let configured_signal =
|
||||
states_configuration_enabled.connect_value_notify(move |states_configured| {
|
||||
configured_sender.input(PowerStatesFrameMsg::InternalConfigurableChanged(
|
||||
configured_sender.input(PowerStatesDialogMsg::InternalConfigurableChanged(
|
||||
states_configured.get(),
|
||||
));
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
});
|
||||
|
||||
let model = Self {
|
||||
domain: ClockDomain::Gpu,
|
||||
core_states_list,
|
||||
vram_states_list,
|
||||
states_configurable: BoolBinding::new(false),
|
||||
@@ -164,9 +182,20 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||
fn update_with_view(
|
||||
&mut self,
|
||||
widgets: &mut Self::Widgets,
|
||||
msg: Self::Input,
|
||||
sender: ComponentSender<Self>,
|
||||
root: &Self::Root,
|
||||
) {
|
||||
match msg {
|
||||
PowerStatesFrameMsg::PowerStates {
|
||||
PowerStatesDialogMsg::Show { domain, parent } => {
|
||||
self.domain = domain;
|
||||
self.update_view(widgets, sender.clone());
|
||||
root.present(Some(&parent));
|
||||
}
|
||||
PowerStatesDialogMsg::PowerStates {
|
||||
pstates,
|
||||
configured,
|
||||
} => {
|
||||
@@ -186,7 +215,7 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
self.vram_clock_ratio,
|
||||
));
|
||||
}
|
||||
PowerStatesFrameMsg::Stats(stats) => {
|
||||
PowerStatesDialogMsg::Stats(stats) => {
|
||||
self.core_states_list.emit(PowerStatesListMsg::ActiveState(
|
||||
stats.active_power_states.and_then(|states| states.core),
|
||||
));
|
||||
@@ -194,10 +223,12 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
stats.active_power_states.and_then(|states| states.memory),
|
||||
));
|
||||
}
|
||||
PowerStatesFrameMsg::VramClockRatio(ratio) => {
|
||||
PowerStatesDialogMsg::VramClockRatio(ratio) => {
|
||||
self.vram_clock_ratio = ratio;
|
||||
self.vram_states_list
|
||||
.emit(PowerStatesListMsg::ValueRatio(ratio));
|
||||
}
|
||||
PowerStatesFrameMsg::Configurable(is_plvl_manual) => {
|
||||
PowerStatesDialogMsg::Configurable(is_plvl_manual) => {
|
||||
let configurable = is_plvl_manual && (self.has_core_states || self.has_vram_states);
|
||||
self.states_configurable.set_value(configurable);
|
||||
|
||||
@@ -216,33 +247,34 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
configurable && self.states_configuration_enabled.value(),
|
||||
));
|
||||
}
|
||||
PowerStatesFrameMsg::PerformanceLevel(level) => {
|
||||
PowerStatesDialogMsg::PerformanceLevel(level) => {
|
||||
self.performance_level = level;
|
||||
}
|
||||
PowerStatesFrameMsg::ConfiguredToggled { configured } => {
|
||||
PowerStatesDialogMsg::ConfiguredToggled { configured } => {
|
||||
if !configured || self.performance_level == Some(PerformanceLevel::Manual) {
|
||||
self.states_configuration_enabled.set_value(configured);
|
||||
} else {
|
||||
APP_BROKER.send(AppMsg::EnablePstateConfig);
|
||||
}
|
||||
}
|
||||
PowerStatesFrameMsg::EnableWithManualPerformanceLevel => {
|
||||
PowerStatesDialogMsg::EnableWithManualPerformanceLevel => {
|
||||
sender
|
||||
.output(OcPageMsg::SetPerformanceLevel(PerformanceLevel::Manual))
|
||||
.unwrap();
|
||||
self.states_configuration_enabled.set_value(true);
|
||||
}
|
||||
PowerStatesFrameMsg::InternalConfigurableChanged(configurable) => {
|
||||
PowerStatesDialogMsg::InternalConfigurableChanged(configurable) => {
|
||||
self.core_states_list
|
||||
.emit(PowerStatesListMsg::Configurable(configurable));
|
||||
self.vram_states_list
|
||||
.emit(PowerStatesListMsg::Configurable(configurable));
|
||||
}
|
||||
}
|
||||
self.update_view(widgets, sender);
|
||||
}
|
||||
}
|
||||
|
||||
impl PowerStatesFrame {
|
||||
impl PowerStatesDialog {
|
||||
pub fn get_enabled_power_states(&self) -> IndexMap<PowerLevelKind, Vec<u8>> {
|
||||
if self.states_configuration_enabled.value() {
|
||||
let state_types = [
|
||||
@@ -279,7 +311,7 @@ mod tests {
|
||||
adw::init().unwrap();
|
||||
let context = gtk::glib::MainContext::default();
|
||||
let _guard = context.acquire().unwrap();
|
||||
let frame = PowerStatesFrame::detach(());
|
||||
let dialog = PowerStatesDialog::detach(());
|
||||
let states = PowerStates {
|
||||
core: vec![],
|
||||
vram: (0..4)
|
||||
@@ -293,30 +325,30 @@ mod tests {
|
||||
};
|
||||
|
||||
// Queue the startup messages together, before child components can update
|
||||
frame.emit(PowerStatesFrameMsg::PowerStates {
|
||||
dialog.emit(PowerStatesDialogMsg::PowerStates {
|
||||
pstates: states,
|
||||
configured: true,
|
||||
});
|
||||
frame.emit(PowerStatesFrameMsg::Configurable(true));
|
||||
dialog.emit(PowerStatesDialogMsg::Configurable(true));
|
||||
while context.pending() {
|
||||
context.iteration(false);
|
||||
}
|
||||
assert!(frame.model().states_configuration_enabled.value());
|
||||
assert!(dialog.model().states_configuration_enabled.value());
|
||||
assert_eq!(
|
||||
frame.model().get_enabled_power_states()[&PowerLevelKind::MemoryClock],
|
||||
dialog.model().get_enabled_power_states()[&PowerLevelKind::MemoryClock],
|
||||
vec![3],
|
||||
);
|
||||
|
||||
// Changing to a GPU without power states must clear the previous capability
|
||||
frame.emit(PowerStatesFrameMsg::PowerStates {
|
||||
dialog.emit(PowerStatesDialogMsg::PowerStates {
|
||||
pstates: PowerStates::default(),
|
||||
configured: false,
|
||||
});
|
||||
frame.emit(PowerStatesFrameMsg::Configurable(true));
|
||||
dialog.emit(PowerStatesDialogMsg::Configurable(true));
|
||||
while context.pending() {
|
||||
context.iteration(false);
|
||||
}
|
||||
assert!(!frame.model().states_configurable.value());
|
||||
assert!(frame.model().get_enabled_power_states().is_empty());
|
||||
assert!(!dialog.model().states_configurable.value());
|
||||
assert!(dialog.model().get_enabled_power_states().is_empty());
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,18 @@
|
||||
use super::power_states_row::{PowerStateRow, PowerStateRowMsg, PowerStateRowOptions};
|
||||
use amdgpu_sysfs::gpu_handle::PowerLevelId;
|
||||
use gtk::prelude::{FrameExt, WidgetExt};
|
||||
use gtk::prelude::WidgetExt;
|
||||
use lact_schema::PowerState;
|
||||
use relm4::{
|
||||
ComponentParts, ComponentSender, RelmWidgetExt, binding::BoolBinding, css,
|
||||
prelude::FactoryVecDeque,
|
||||
};
|
||||
use relm4::{ComponentParts, ComponentSender, binding::BoolBinding, prelude::FactoryVecDeque};
|
||||
|
||||
pub struct PowerStatesList {
|
||||
states: FactoryVecDeque<PowerStateRow>,
|
||||
value_suffix: String,
|
||||
is_active_indicator_visible: BoolBinding,
|
||||
configurable: BoolBinding,
|
||||
active_state: Option<PowerLevelId>,
|
||||
}
|
||||
|
||||
pub struct PowerStatesListOptions {
|
||||
pub title: String,
|
||||
pub value_suffix: String,
|
||||
}
|
||||
|
||||
@@ -24,6 +21,7 @@ pub enum PowerStatesListMsg {
|
||||
PowerStates(Vec<PowerState>, f64),
|
||||
ActiveState(Option<PowerLevelId>),
|
||||
Configurable(bool),
|
||||
ValueRatio(f64),
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
@@ -33,16 +31,11 @@ impl relm4::SimpleComponent for PowerStatesList {
|
||||
type Output = ();
|
||||
|
||||
view! {
|
||||
gtk::Frame {
|
||||
gtk::Box {
|
||||
set_hexpand: true,
|
||||
#[wrap(Some)]
|
||||
set_label_widget = >k::Label {
|
||||
set_label: &opts.title,
|
||||
set_margin_horizontal: 5,
|
||||
add_css_class: css::CAPTION_HEADING,
|
||||
},
|
||||
#[local_ref]
|
||||
states_widget -> gtk::ListBox {
|
||||
set_hexpand: true,
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
},
|
||||
}
|
||||
@@ -60,6 +53,7 @@ impl relm4::SimpleComponent for PowerStatesList {
|
||||
value_suffix: opts.value_suffix,
|
||||
is_active_indicator_visible: BoolBinding::new(false),
|
||||
configurable: BoolBinding::new(true),
|
||||
active_state: None,
|
||||
};
|
||||
|
||||
let states_widget = model.states.widget();
|
||||
@@ -75,12 +69,12 @@ impl relm4::SimpleComponent for PowerStatesList {
|
||||
let mut states = self.states.guard();
|
||||
states.clear();
|
||||
|
||||
for mut power_state in new_pstates {
|
||||
power_state.value = (power_state.value as f64 * value_ratio) as u64;
|
||||
for power_state in new_pstates {
|
||||
let opts = PowerStateRowOptions {
|
||||
power_state,
|
||||
value_suffix: self.value_suffix.clone(),
|
||||
active: false,
|
||||
value_ratio,
|
||||
active: self.active_state.is_some() && power_state.id == self.active_state,
|
||||
show_active_indicator: self.is_active_indicator_visible.clone(),
|
||||
configurable: self.configurable.clone(),
|
||||
};
|
||||
@@ -88,10 +82,11 @@ impl relm4::SimpleComponent for PowerStatesList {
|
||||
}
|
||||
}
|
||||
PowerStatesListMsg::ActiveState(active_idx) => {
|
||||
self.active_state = active_idx;
|
||||
self.is_active_indicator_visible
|
||||
.set_value(active_idx.is_some());
|
||||
for (i, row) in self.states.iter().enumerate() {
|
||||
let is_active = row.power_state.id == active_idx;
|
||||
let is_active = active_idx.is_some() && row.power_state.id == active_idx;
|
||||
|
||||
self.states.send(i, PowerStateRowMsg::Active(is_active));
|
||||
}
|
||||
@@ -99,6 +94,9 @@ impl relm4::SimpleComponent for PowerStatesList {
|
||||
PowerStatesListMsg::Configurable(configurable) => {
|
||||
self.configurable.set_value(configurable);
|
||||
}
|
||||
PowerStatesListMsg::ValueRatio(ratio) => {
|
||||
self.states.broadcast(PowerStateRowMsg::ValueRatio(ratio));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{APP_BROKER, app::msg::AppMsg};
|
||||
use amdgpu_sysfs::gpu_handle::PowerLevelId;
|
||||
use gtk::prelude::{BoxExt, OrientableExt, WidgetExt};
|
||||
use gtk::prelude::{BoxExt, ListBoxRowExt, OrientableExt, WidgetExt};
|
||||
use lact_schema::PowerState;
|
||||
use relm4::{RelmObjectExt, RelmWidgetExt, binding::BoolBinding, css};
|
||||
|
||||
@@ -9,6 +9,7 @@ pub struct PowerStateRow {
|
||||
pub(super) enabled: BoolBinding,
|
||||
pub(super) power_state: PowerState,
|
||||
value_suffix: String,
|
||||
value_ratio: f64,
|
||||
configurable: BoolBinding,
|
||||
show_active_indicator: BoolBinding,
|
||||
}
|
||||
@@ -16,6 +17,7 @@ pub struct PowerStateRow {
|
||||
pub struct PowerStateRowOptions {
|
||||
pub power_state: PowerState,
|
||||
pub value_suffix: String,
|
||||
pub value_ratio: f64,
|
||||
pub active: bool,
|
||||
pub show_active_indicator: BoolBinding,
|
||||
pub configurable: BoolBinding,
|
||||
@@ -24,6 +26,7 @@ pub struct PowerStateRowOptions {
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PowerStateRowMsg {
|
||||
Active(bool),
|
||||
ValueRatio(f64),
|
||||
}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
@@ -35,40 +38,60 @@ impl relm4::factory::FactoryComponent for PowerStateRow {
|
||||
type Init = PowerStateRowOptions;
|
||||
|
||||
view! {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
set_margin_vertical: 2,
|
||||
set_margin_horizontal: 5,
|
||||
gtk::ListBoxRow {
|
||||
set_selectable: false,
|
||||
set_activatable: false,
|
||||
set_focusable: false,
|
||||
|
||||
append: image = >k::Image {
|
||||
set_icon_name: Some("pan-end-symbolic"),
|
||||
add_binding: (&self.show_active_indicator, "visible"),
|
||||
#[watch]
|
||||
set_opacity: if self.active.value() { 1.0 } else { 0.0 },
|
||||
},
|
||||
#[wrap(Some)]
|
||||
set_child = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
set_margin_vertical: 2,
|
||||
set_margin_horizontal: 5,
|
||||
|
||||
append = >k::CheckButton {
|
||||
add_binding: (&self.enabled, "active"),
|
||||
add_binding: (&self.configurable, "visible"),
|
||||
set_sensitive: matches!(self.power_state.id, Some(PowerLevelId::Index(_))),
|
||||
},
|
||||
append: image = >k::Image {
|
||||
set_icon_name: Some("pan-end-symbolic"),
|
||||
add_binding: (&self.show_active_indicator, "visible"),
|
||||
#[watch]
|
||||
set_opacity: if self.active.value() { 1.0 } else { 0.0 },
|
||||
},
|
||||
|
||||
append = >k::Label {
|
||||
add_css_class: css::MONOSPACE,
|
||||
#[watch]
|
||||
set_class_active: (css::DIM_LABEL, !self.active.value()),
|
||||
set_label: &{
|
||||
let index_text = match self.power_state.id {
|
||||
Some(PowerLevelId::Index(index)) => index.to_string(),
|
||||
Some(PowerLevelId::Sleep) => "S".to_owned(),
|
||||
append = >k::CheckButton {
|
||||
add_binding: (&self.enabled, "active"),
|
||||
add_binding: (&self.configurable, "visible"),
|
||||
set_sensitive: matches!(self.power_state.id, Some(PowerLevelId::Index(_))),
|
||||
},
|
||||
|
||||
append = >k::Label {
|
||||
add_css_class: css::MONOSPACE,
|
||||
#[watch]
|
||||
set_class_active: (css::DIM_LABEL, !self.active.value()),
|
||||
set_label: &match self.power_state.id {
|
||||
Some(PowerLevelId::Index(index)) => format!("{index}:"),
|
||||
Some(PowerLevelId::Sleep) => "S:".to_owned(),
|
||||
None => String::new(),
|
||||
};
|
||||
let value_text = match self.power_state.min_value {
|
||||
Some(min) if min != self.power_state.value => format!("{min}-{}", self.power_state.value),
|
||||
_ => self.power_state.value.to_string(),
|
||||
};
|
||||
format!("{index_text}: {value_text} {}", self.value_suffix)
|
||||
},
|
||||
},
|
||||
|
||||
append = >k::Label {
|
||||
add_css_class: css::MONOSPACE,
|
||||
set_hexpand: true,
|
||||
set_xalign: 1.0,
|
||||
#[watch]
|
||||
set_class_active: (css::DIM_LABEL, !self.active.value()),
|
||||
#[watch]
|
||||
set_label: &{
|
||||
let value = (self.power_state.value as f64 * self.value_ratio) as u64;
|
||||
let value_text = match self.power_state.min_value {
|
||||
Some(min) if min != self.power_state.value => {
|
||||
let min = (min as f64 * self.value_ratio) as u64;
|
||||
format!("{min}-{value}")
|
||||
}
|
||||
_ => value.to_string(),
|
||||
};
|
||||
format!("{value_text} {}", self.value_suffix)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -87,13 +110,16 @@ impl relm4::factory::FactoryComponent for PowerStateRow {
|
||||
active: BoolBinding::new(opts.active),
|
||||
power_state: opts.power_state,
|
||||
value_suffix: opts.value_suffix,
|
||||
value_ratio: opts.value_ratio,
|
||||
configurable: opts.configurable,
|
||||
show_active_indicator: opts.show_active_indicator,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, _: relm4::FactorySender<Self>) {
|
||||
let PowerStateRowMsg::Active(active) = msg;
|
||||
self.active.set_value(active);
|
||||
match msg {
|
||||
PowerStateRowMsg::Active(active) => self.active.set_value(active),
|
||||
PowerStateRowMsg::ValueRatio(ratio) => self.value_ratio = ratio,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user