From df20b606dece98866650b7c421847977bf79c24c Mon Sep 17 00:00:00 2001 From: Ilya Zlobintsev Date: Sun, 23 Aug 2026 18:14:33 +0300 Subject: [PATCH] feat: show additional clock domains in UI (#1172) * feat: show additional clock domains in UI * fix: tests --- lact-daemon/src/server/gpu_controller/amd.rs | 2 +- .../src/server/gpu_controller/intel.rs | 2 +- .../src/server/gpu_controller/nvidia.rs | 2 +- lact-gui/i18n/en/lact_gui.ftl | 1 + .../src/app/components/gpu_stats_section.rs | 50 +++++++++++++++++-- lact-gui/src/app/components/info_row.rs | 7 +++ lact-gui/src/app/graphs_window/plot/imp.rs | 2 +- lact-gui/src/app/pages/oc_page.rs | 1 + lact-schema/src/lib.rs | 2 +- 9 files changed, 61 insertions(+), 8 deletions(-) diff --git a/lact-daemon/src/server/gpu_controller/amd.rs b/lact-daemon/src/server/gpu_controller/amd.rs index dd15ce9b..0ed19d03 100644 --- a/lact-daemon/src/server/gpu_controller/amd.rs +++ b/lact-daemon/src/server/gpu_controller/amd.rs @@ -516,7 +516,7 @@ impl AmdGpuController { .map(u64::from) .or_else(|| self.hw_mon_and_then(HwMon::get_vram_clockspeed)); - let mut sensors = HashMap::new(); + let mut sensors = IndexMap::new(); let mut target_gpu_clockspeed = None; diff --git a/lact-daemon/src/server/gpu_controller/intel.rs b/lact-daemon/src/server/gpu_controller/intel.rs index a06adb43..44fcf1c1 100644 --- a/lact-daemon/src/server/gpu_controller/intel.rs +++ b/lact-daemon/src/server/gpu_controller/intel.rs @@ -942,7 +942,7 @@ impl GpuController for IntelGpuController { gpu_clockspeed, target_gpu_clockspeed, vram_clockspeed: None, - sensors: HashMap::new(), + sensors: IndexMap::new(), }; let power = PowerStats { diff --git a/lact-daemon/src/server/gpu_controller/nvidia.rs b/lact-daemon/src/server/gpu_controller/nvidia.rs index bc7882a9..a540bf33 100644 --- a/lact-daemon/src/server/gpu_controller/nvidia.rs +++ b/lact-daemon/src/server/gpu_controller/nvidia.rs @@ -899,7 +899,7 @@ impl GpuController for NvidiaGpuController { ] .into_iter() .filter_map(|(name, value)| Some((name.to_owned(), u64::from(value.ok()?)))) - .collect::>(); + .collect::>(); let mut voltage = None; diff --git a/lact-gui/i18n/en/lact_gui.ftl b/lact-gui/i18n/en/lact_gui.ftl index d16e7499..aa111337 100644 --- a/lact-gui/i18n/en/lact_gui.ftl +++ b/lact-gui/i18n/en/lact_gui.ftl @@ -131,6 +131,7 @@ gpu-temp = Temperature gpu-usage = GPU Usage vram-clock = VRAM Clock power-usage = Power Usage +extra-clocks = Extra Clocks no-throttling = No unknown-throttling = Unknown missing-stat = N/A diff --git a/lact-gui/src/app/components/gpu_stats_section.rs b/lact-gui/src/app/components/gpu_stats_section.rs index a3c7572a..9cf8d457 100644 --- a/lact-gui/src/app/components/gpu_stats_section.rs +++ b/lact-gui/src/app/components/gpu_stats_section.rs @@ -49,7 +49,7 @@ impl relm4::SimpleComponent for GpuStatsSection { set_orientation: gtk::Orientation::Vertical, set_spacing: 10, - PageSection::new("") { + PageSection { #[watch] set_visible: model.visible_count(GpuStatDisplay::Digits) > 0, @@ -144,10 +144,39 @@ impl relm4::SimpleComponent for GpuStatsSection { #[watch] set_visible: model.shows(GpuStat::Temperature) && !secondary_temperatures.is_empty(), }, + + append_child = &InfoRow { + set_name: fl!(I18N, "extra-clocks"), + set_max_value_width_chars: 40, + #[watch] + set_value: extra_clocks.join(", "), + + set_icon: "go-down-symbolic".to_string(), + + #[name = "secondary_clocks_popover"] + set_popover = >k::Popover { + gtk::Label { + set_margin_all: 10, + set_selectable: false, + set_use_markup: true, + set_attributes: Some(&AttrList::from_str("0 -1 weight bold").unwrap()), + + #[watch] + set_label: &extra_clocks.join("\n"), + }, + }, + + connect_clicked[secondary_clocks_popover] => move |_| { + secondary_clocks_popover.popup(); + }, + } -> extra_clocks_item: gtk::FlowBoxChild { + #[watch] + set_visible: model.shows(GpuStat::ExtraClocks), + }, }, }, - PageSection::new("") { + PageSection { #[watch] set_visible: model.visible_count(GpuStatDisplay::LevelBar) > 0, @@ -338,6 +367,7 @@ impl relm4::SimpleComponent for GpuStatsSection { let (primary_temperatures, secondary_temperatures): (Vec, Vec) = (Vec::new(), Vec::new()); + let extra_clocks: Vec = Vec::new(); let model = Self { stats_to_show: config.stats, @@ -385,6 +415,8 @@ impl relm4::SimpleComponent for GpuStatsSection { fn pre_view(&self) { let (primary_temperatures, secondary_temperatures) = formatting::fmt_temperature_text(&model.stats); + + let extra_clocks = extra_clocks(&model.stats); } } @@ -402,6 +434,7 @@ pub enum GpuStat { Temperature, GpuClock, VramClock, + ExtraClocks, GpuUsage, VramUsage, GttUsage, @@ -424,7 +457,8 @@ impl GpuStat { | Self::Throttling | Self::GpuClockTarget | Self::GpuVoltage - | Self::Temperature => GpuStatDisplay::Digits, + | Self::Temperature + | Self::ExtraClocks => GpuStatDisplay::Digits, Self::GpuClock | Self::VramClock | Self::GpuUsage @@ -459,6 +493,7 @@ impl GpuStatsSection { GpuStat::PowerUsage => { self.stats.power.average.is_some() || self.stats.power.current.is_some() } + GpuStat::ExtraClocks => !self.stats.clockspeed.sensors.is_empty(), } } @@ -487,3 +522,12 @@ fn format_current_gfxclk(value: Option) -> String { fl!(I18N, "missing-stat") } } + +fn extra_clocks(stats: &DeviceStats) -> Vec { + stats + .clockspeed + .sensors + .iter() + .map(|(name, value)| format!("{name}: {}", formatting::fmt_clockspeed(Some(*value), 1.0))) + .collect::>() +} diff --git a/lact-gui/src/app/components/info_row.rs b/lact-gui/src/app/components/info_row.rs index f65afe26..49a39485 100644 --- a/lact-gui/src/app/components/info_row.rs +++ b/lact-gui/src/app/components/info_row.rs @@ -147,6 +147,8 @@ mod imp { info_text: RefCell, #[property(get, set)] icon: RefCell, + #[property(get, set)] + max_value_width_chars: RefCell, pub(super) info_menubutton: gtk::MenuButton, pub(super) value_box: gtk::Box, @@ -255,6 +257,11 @@ mod imp { .sync_create() .build(); + obj.bind_property("max-value-width-chars", value_label, "max-width-chars") + .transform_to(|_, value: i32| if value == 0 { None } else { Some(value) }) + .sync_create() + .build(); + obj.bind_property("icon", &icon_image, "icon-name") .sync_create() .build(); diff --git a/lact-gui/src/app/graphs_window/plot/imp.rs b/lact-gui/src/app/graphs_window/plot/imp.rs index 0e17ba49..e1799a27 100644 --- a/lact-gui/src/app/graphs_window/plot/imp.rs +++ b/lact-gui/src/app/graphs_window/plot/imp.rs @@ -159,7 +159,7 @@ mod benches { gpu_clockspeed: Some(500), vram_clockspeed: Some(1000), target_gpu_clockspeed: None, - sensors: HashMap::new(), + sensors: IndexMap::new(), }, active_power_states: Some(ActivePowerStates { core: Some(PowerLevelId::Index(0)), diff --git a/lact-gui/src/app/pages/oc_page.rs b/lact-gui/src/app/pages/oc_page.rs index 47229bde..ad8f070c 100644 --- a/lact-gui/src/app/pages/oc_page.rs +++ b/lact-gui/src/app/pages/oc_page.rs @@ -102,6 +102,7 @@ impl relm4::Component for OcPage { GpuStat::GttUsage, GpuStat::PowerUsage, GpuStat::FanSpeed, + GpuStat::ExtraClocks, ]), }); let power_cap_section = PowerCapSection::detach_default(); diff --git a/lact-schema/src/lib.rs b/lact-schema/src/lib.rs index ea677dac..dbdc6a07 100644 --- a/lact-schema/src/lib.rs +++ b/lact-schema/src/lib.rs @@ -719,7 +719,7 @@ pub struct ClockspeedStats { pub target_gpu_clockspeed: Option, pub vram_clockspeed: Option, #[serde(default)] - pub sensors: HashMap, + pub sensors: IndexMap, } #[skip_serializing_none]