feat: show detailed power profile information (#360)

This commit is contained in:
Ilya Zlobintsev 2024-08-18 21:42:44 +03:00 committed by GitHub
parent cd9a3b2f31
commit 32e25dc269
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 219 additions and 34 deletions

4
Cargo.lock generated
View File

@ -53,9 +53,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]] [[package]]
name = "amdgpu-sysfs" name = "amdgpu-sysfs"
version = "0.15.0" version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64b277cf650bff281b49b12f4a2a4a004485e0b3ecfd34961c5f9b258800ea09" checksum = "cd30ccf0f663fbe9465c7819c0f52cfcb9bf890c7445192f23ff381fad0b3204"
dependencies = [ dependencies = [
"enum_dispatch", "enum_dispatch",
"serde", "serde",

View File

@ -10,7 +10,7 @@ members = [
] ]
[workspace.dependencies] [workspace.dependencies]
amdgpu-sysfs = { version = "0.15.0", features = ["serde"] } amdgpu-sysfs = { version = "0.16.0", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_with = { version = "3.5.0", default-features = false, features = [ serde_with = { version = "3.5.0", default-features = false, features = [
"macros", "macros",

View File

@ -2,7 +2,7 @@ mod clocks_frame;
mod gpu_stats_section; mod gpu_stats_section;
mod performance_frame; mod performance_frame;
mod power_cap_section; mod power_cap_section;
// mod power_cap_frame; mod power_profile;
mod power_states; mod power_states;
use self::power_cap_section::PowerCapSection; use self::power_cap_section::PowerCapSection;

View File

@ -5,15 +5,19 @@ use gtk::prelude::*;
use gtk::*; use gtk::*;
use std::{cell::RefCell, rc::Rc, str::FromStr}; use std::{cell::RefCell, rc::Rc, str::FromStr};
use super::power_profile::power_profile_component_grid::PowerProfileComponentGrid;
#[derive(Clone)] #[derive(Clone)]
pub struct PerformanceFrame { pub struct PerformanceFrame {
pub container: PageSection, pub container: PageSection,
level_drop_down: DropDown, level_drop_down: DropDown,
mode_drop_down: DropDown, modes_listbox: ListBox,
mode_menu_button: MenuButton,
description_label: Label, description_label: Label,
manual_info_button: MenuButton, manual_info_button: MenuButton,
mode_box: Box, mode_box: Box,
modes_table: Rc<RefCell<Option<PowerProfileModesTable>>>, modes_table: Rc<RefCell<Option<PowerProfileModesTable>>>,
power_mode_info_notebook: Notebook,
} }
impl PerformanceFrame { impl PerformanceFrame {
@ -41,11 +45,32 @@ impl PerformanceFrame {
let mode_box = Box::new(Orientation::Horizontal, 10); let mode_box = Box::new(Orientation::Horizontal, 10);
let mode_drop_down = DropDown::builder() let mode_menu_button = MenuButton::builder()
.sensitive(false) .sensitive(false)
.halign(Align::End) .halign(Align::End)
.always_show_arrow(false)
.build(); .build();
let modes_listbox = ListBox::builder()
.selection_mode(SelectionMode::Single)
.build();
let modes_popover_content = gtk::Box::builder()
.orientation(Orientation::Horizontal)
.spacing(10)
.margin_start(5)
.margin_end(5)
.margin_top(5)
.margin_bottom(5)
.build();
modes_popover_content.append(&modes_listbox);
let power_mode_info_notebook = Notebook::new();
modes_popover_content.append(&power_mode_info_notebook);
let modes_popover = Popover::builder().child(&modes_popover_content).build();
mode_menu_button.set_popover(Some(&modes_popover));
let unavailable_label = Label::new(Some( let unavailable_label = Label::new(Some(
"Performance level has to be set to \"manual\" to use power states and modes", "Performance level has to be set to \"manual\" to use power states and modes",
)); ));
@ -60,18 +85,20 @@ impl PerformanceFrame {
let mode_title_label = Label::new(Some("Power level mode:")); let mode_title_label = Label::new(Some("Power level mode:"));
mode_box.append(&mode_title_label); mode_box.append(&mode_title_label);
mode_box.append(&manual_info_button); mode_box.append(&manual_info_button);
mode_box.append(&mode_drop_down); mode_box.append(&mode_menu_button);
container.append(&mode_box); container.append(&mode_box);
let frame = Self { let frame = Self {
container, container,
level_drop_down, level_drop_down,
mode_drop_down, mode_menu_button,
description_label, description_label,
manual_info_button, manual_info_button,
modes_listbox,
mode_box, mode_box,
modes_table: Rc::new(RefCell::new(None)), modes_table: Rc::new(RefCell::new(None)),
power_mode_info_notebook,
}; };
frame.level_drop_down.connect_selected_notify(clone!( frame.level_drop_down.connect_selected_notify(clone!(
@ -82,12 +109,10 @@ impl PerformanceFrame {
} }
)); ));
frame.mode_drop_down.connect_selected_notify(clone!( frame.modes_listbox.connect_row_selected(clone!(
#[strong] #[strong]
frame, frame,
move |_| { move |_, _| frame.update_from_selection()
frame.update_from_selection();
}
)); ));
frame frame
@ -107,25 +132,36 @@ impl PerformanceFrame {
pub fn set_power_profile_modes(&self, table: Option<PowerProfileModesTable>) { pub fn set_power_profile_modes(&self, table: Option<PowerProfileModesTable>) {
self.mode_box.set_visible(table.is_some()); self.mode_box.set_visible(table.is_some());
while let Some(row) = self.modes_listbox.row_at_index(0) {
self.modes_listbox.remove(&row);
}
match &table { match &table {
Some(table) => { Some(table) => {
let model: StringList = table.modes.values().cloned().collect(); for profile in table.modes.values() {
let active_pos = table let profile_label = Label::builder()
.modes .label(&profile.name)
.keys() .margin_start(5)
.position(|key| *key == table.active) .margin_end(5)
.expect("No active mode") as u32; .build();
self.modes_listbox.append(&profile_label);
}
self.mode_drop_down.set_model(Some(&model)); let active_row = self
self.mode_drop_down.set_selected(active_pos); .modes_listbox
.row_at_index(table.active as i32)
.unwrap();
self.modes_listbox.select_row(Some(&active_row));
self.mode_drop_down.show(); self.mode_menu_button.show();
} }
None => { None => {
self.mode_drop_down.hide(); self.mode_menu_button.hide();
} }
} }
self.modes_table.replace(table); self.modes_table.replace(table);
self.update_from_selection();
} }
pub fn connect_settings_changed<F: Fn() + 'static + Clone>(&self, f: F) { pub fn connect_settings_changed<F: Fn() + 'static + Clone>(&self, f: F) {
@ -134,7 +170,21 @@ impl PerformanceFrame {
f, f,
move |_| f() move |_| f()
)); ));
self.mode_drop_down.connect_selected_notify(move |_| f()); self.modes_listbox.connect_row_selected(clone!(
#[strong(rename_to = modes_table)]
self.modes_table,
move |_, row| {
let modes_table = modes_table.borrow();
if let Some(row) = row {
if let Some(table) = modes_table.as_ref() {
if row.index() != table.active as i32 {
f();
}
}
}
}
));
} }
pub fn get_selected_performance_level(&self) -> PerformanceLevel { pub fn get_selected_performance_level(&self) -> PerformanceLevel {
@ -148,21 +198,18 @@ impl PerformanceFrame {
} }
pub fn get_selected_power_profile_mode(&self) -> Option<u16> { pub fn get_selected_power_profile_mode(&self) -> Option<u16> {
if self.mode_drop_down.is_sensitive() { if self.mode_menu_button.is_sensitive() {
self.modes_table.borrow().as_ref().map(|table| { self.modes_listbox
let selected_index = table .selected_row()
.modes .map(|row| row.index() as u16)
.keys()
.nth(self.mode_drop_down.selected() as usize)
.expect("Selected mode out of range");
*selected_index
})
} else { } else {
None None
} }
} }
fn update_from_selection(&self) { fn update_from_selection(&self) {
self.power_mode_info_notebook.set_visible(false);
let mut enable_mode_control = false; let mut enable_mode_control = false;
let text = match self.level_drop_down.selected() { let text = match self.level_drop_down.selected() {
@ -176,10 +223,51 @@ impl PerformanceFrame {
_ => unreachable!(), _ => unreachable!(),
}; };
self.description_label.set_text(text); self.description_label.set_text(text);
self.mode_drop_down.set_sensitive(enable_mode_control);
self.manual_info_button.set_visible(!enable_mode_control); self.manual_info_button.set_visible(!enable_mode_control);
self.mode_drop_down.set_hexpand(enable_mode_control);
self.mode_menu_button.set_sensitive(enable_mode_control);
self.mode_menu_button.set_hexpand(enable_mode_control);
let modes_table = self.modes_table.borrow();
if let Some(table) = modes_table.as_ref() {
if let Some(row) = self.modes_listbox.selected_row() {
if let Some(profile) = table.modes.get(&(row.index() as u16)) {
self.mode_menu_button.set_label(&profile.name);
self.power_mode_info_notebook.set_visible(true);
// Save current page to be restored after being refilled
let current_page = self.power_mode_info_notebook.current_page();
// Remove pages
while self.power_mode_info_notebook.n_pages() != 0 {
self.power_mode_info_notebook.remove_page(None);
}
for component in &profile.components {
let values_grid = PowerProfileComponentGrid::new();
values_grid.set_component(component, table);
let title = component.clock_type.as_deref().unwrap_or("All");
let title_label = Label::builder()
.label(title)
.margin_start(5)
.margin_end(5)
.build();
self.power_mode_info_notebook
.append_page(&values_grid, Some(&title_label));
}
self.power_mode_info_notebook
.set_show_tabs(profile.components.len() > 1);
// Restore selected page
if current_page.is_some() {
self.power_mode_info_notebook.set_current_page(current_page);
}
}
}
}
} }
pub fn show(&self) { pub fn show(&self) {

View File

@ -0,0 +1 @@
pub mod power_profile_component_grid;

View File

@ -0,0 +1,90 @@
use amdgpu_sysfs::gpu_handle::power_profile_mode::{PowerProfileComponent, PowerProfileModesTable};
use gtk::{
glib::{self, Object},
prelude::GridExt,
prelude::WidgetExt,
Label,
};
glib::wrapper! {
pub struct PowerProfileComponentGrid(ObjectSubclass<imp::PowerProfileComponentGrid>)
@extends gtk::Grid,
@implements gtk::Accessible, gtk::Buildable, gtk::Widget;
}
impl PowerProfileComponentGrid {
pub fn new() -> Self {
Object::builder()
.property("margin-start", 5)
.property("margin-end", 5)
.property("margin-top", 5)
.property("margin-bottom", 5)
.build()
}
pub fn set_component(&self, component: &PowerProfileComponent, table: &PowerProfileModesTable) {
while let Some(child) = self.first_child() {
self.remove(&child);
}
for (i, value) in component.values.iter().enumerate() {
let name = &table.value_names[i];
let name_label = Label::builder()
.label(&format!("{name}:"))
.hexpand(true)
.halign(gtk::Align::Start)
.build();
self.attach(&name_label, 0, i as i32, 1, 1);
let mut value_label_builder = Label::builder().halign(gtk::Align::End);
value_label_builder = match value {
Some(value) => value_label_builder.label(value.to_string()),
None => value_label_builder.label("Not set"),
};
self.attach(&value_label_builder.build(), 1, i as i32, 1, 1);
}
}
}
impl Default for PowerProfileComponentGrid {
fn default() -> Self {
Self::new()
}
}
mod imp {
use gtk::{
glib::{self, subclass::InitializingObject},
subclass::{
prelude::*,
widget::{CompositeTemplateClass, WidgetImpl},
},
CompositeTemplate,
};
#[derive(CompositeTemplate, Default)]
#[template(file = "ui/oc_page/power_profile/power_profile_component_grid.blp")]
pub struct PowerProfileComponentGrid {}
#[glib::object_subclass]
impl ObjectSubclass for PowerProfileComponentGrid {
const NAME: &'static str = "PowerProfileComponentGrid";
type Type = super::PowerProfileComponentGrid;
type ParentType = gtk::Grid;
fn class_init(class: &mut Self::Class) {
class.bind_template();
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for PowerProfileComponentGrid {}
impl WidgetImpl for PowerProfileComponentGrid {}
impl GridImpl for PowerProfileComponentGrid {}
}

View File

@ -0,0 +1,6 @@
using Gtk 4.0;
template $PowerProfileComponentGrid: Grid {
row-spacing: 5;
column-spacing: 5;
}