feat: fmt gpu name in gpu-picker (#1157)

* feat: fmt gpu name in gpu-picker

* move implementation to daemon

* move to schema

* add Radeon/Geforce prefix back

* remove from info_elements
This commit is contained in:
Roman Makarov
2026-08-17 10:49:12 +03:00
committed by GitHub
parent 979f087096
commit bd687203e2
7 changed files with 113 additions and 22 deletions
+2 -2
View File
@@ -265,7 +265,7 @@ There is also a cli available.
Example output: Example output:
``` ```
10DE:2704-1462:5110-0000:09:00.0 (AD103 [GeForce RTX 4080]) 0: 10DE:2704-1462:5110-0000:09:00.0 (GeForce RTX 4080) [Dedicated]
``` ```
- Getting GPU information: - Getting GPU information:
@@ -277,7 +277,7 @@ There is also a cli available.
$ lact cli info $ lact cli info
GPU 10DE:2704-1462:5110-0000:09:00.0: GPU 10DE:2704-1462:5110-0000:09:00.0:
===================================== =====================================
GPU Model: NVIDIA GeForce RTX 4080 (0x10DE:0x2704) GPU Model: GeForce RTX 4080 (0x10DE:0x2704)
Card Manufacturer: Micro-Star International Co., Ltd. [MSI] (0x1462) Card Manufacturer: Micro-Star International Co., Ltd. [MSI] (0x1462)
Card Model: Unknown (0x5110) Card Model: Unknown (0x5110)
Driver Used: nvidia 570.124.04 Driver Used: nvidia 570.124.04
+8 -2
View File
@@ -8,6 +8,7 @@ use anyhow::{Context, Result, bail};
use lact_client::DaemonClient; use lact_client::DaemonClient;
use lact_schema::{ use lact_schema::{
args::cli::{CliArgs, CliCommand, ProfileAutoSwitchCommand, ProfileCommand}, args::cli::{CliArgs, CliCommand, ProfileAutoSwitchCommand, ProfileCommand},
clean_gpu_name,
config::GpuConfig, config::GpuConfig,
request::ConfirmCommand, request::ConfirmCommand,
}; };
@@ -100,7 +101,11 @@ impl CliContext<'_> {
if entries.len() > 1 { if entries.len() > 1 {
eprintln!( eprintln!(
"GPU id not specified, selecting {}", "GPU id not specified, selecting {}",
first_entry.name.as_deref().unwrap_or("<Unknown>") first_entry
.name
.as_deref()
.map(clean_gpu_name)
.unwrap_or("<Unknown>")
); );
} }
Ok(first_entry.id.clone()) Ok(first_entry.id.clone())
@@ -141,6 +146,7 @@ impl CliContext<'_> {
.await? .await?
.into_iter() .into_iter()
.find(|entry| entry.id == gpu_id) .find(|entry| entry.id == gpu_id)
.and_then(|entry| entry.name)) .and_then(|entry| entry.name)
.map(|name| clean_gpu_name(&name).to_owned()))
} }
} }
+3 -6
View File
@@ -11,13 +11,10 @@ const PROFILE_DEFAULT: &str = "Default";
pub async fn list_gpus(ctx: CliContext<'_>) -> Result<()> { pub async fn list_gpus(ctx: CliContext<'_>) -> Result<()> {
let entries = ctx.client.list_devices().await?; let entries = ctx.client.list_devices().await?;
for (i, entry) in entries.into_iter().enumerate() { for (i, entry) in entries.into_iter().enumerate() {
let id = entry.id; if entry.name.is_some() {
let device_type = entry.device_type; println!("{i}: {} ({entry}) [{}]", entry.id, entry.device_type);
if let Some(name) = entry.name {
println!("{i}: {id} ({name}) [{device_type}]");
} else { } else {
println!("{i}: {id} [{device_type}]"); println!("{i}: {} [{}]", entry.id, entry.device_type);
} }
} }
Ok(()) Ok(())
+10 -2
View File
@@ -6,7 +6,7 @@ use crate::{
}; };
use indexmap::IndexMap; use indexmap::IndexMap;
use jiff::Zoned; use jiff::Zoned;
use lact_schema::DeviceStats; use lact_schema::{DeviceStats, clean_gpu_name};
use schema::{ use schema::{
Attribute, Gauge, GaugeDataPoint, Metric, MetricsPayload, Resource, ResourceMetric, Scope, Attribute, Gauge, GaugeDataPoint, Metric, MetricsPayload, Resource, ResourceMetric, Scope,
ScopeMetric, Value, ScopeMetric, Value,
@@ -335,7 +335,15 @@ async fn get_stats(handler: &Handler) -> anyhow::Result<IndexMap<String, (String
for device in device_list { for device in device_list {
let stats = handler.get_gpu_stats(&device.id).await?; let stats = handler.get_gpu_stats(&device.id).await?;
devices.insert(device.id, (device.name.unwrap_or_default(), stats)); devices.insert(
device.id,
(
device
.name
.map_or_else(String::new, |name| clean_gpu_name(&name).to_owned()),
stats,
),
);
} }
Ok(devices) Ok(devices)
@@ -13,7 +13,7 @@ use crate::app::{
use gtk::pango::AttrList; use gtk::pango::AttrList;
use gtk::prelude::{BoxExt, OrientableExt, PopoverExt as _, WidgetExt}; use gtk::prelude::{BoxExt, OrientableExt, PopoverExt as _, WidgetExt};
use i18n_embed_fl::fl; use i18n_embed_fl::fl;
use lact_schema::{DeviceInfo, DeviceStats, PowerStates, PowerStats}; use lact_schema::{DeviceInfo, DeviceStats, PowerStates, PowerStats, clean_gpu_name};
use relm4::{ComponentParts, ComponentSender, RelmWidgetExt as _}; use relm4::{ComponentParts, ComponentSender, RelmWidgetExt as _};
use std::collections::HashSet; use std::collections::HashSet;
use std::str::FromStr as _; use std::str::FromStr as _;
@@ -360,13 +360,14 @@ impl relm4::SimpleComponent for GpuStatsSection {
GpuStatsSectionMsg::Info(info) => { GpuStatsSectionMsg::Info(info) => {
self.vram_clock_ratio = info.vram_clock_ratio(); self.vram_clock_ratio = info.vram_clock_ratio();
if let Some(pci_info) = &info.pci_info { if let Some(pci_info) = &info.pci_info {
self.gpu_model = info self.gpu_model = clean_gpu_name(
.drm_info info.drm_info
.as_ref() .as_ref()
.and_then(|drm| drm.device_name.as_deref()) .and_then(|drm| drm.device_name.as_deref())
.or(pci_info.device_pci_info.model.as_deref()) .or(pci_info.device_pci_info.model.as_deref())
.unwrap_or("Unknown") .unwrap_or("Unknown"),
.to_owned(); )
.to_owned();
} }
} }
GpuStatsSectionMsg::Stats(stats) => { GpuStatsSectionMsg::Stats(stats) => {
+28 -1
View File
@@ -67,6 +67,33 @@ pub fn bytes_to_mib(bytes: u64) -> f64 {
bytes as f64 / 1024.0 / 1024.0 bytes as f64 / 1024.0 / 1024.0
} }
const GPU_VENDOR_PREFIXES: &[&str] = &["AMD ", "NVIDIA ", "Intel "];
pub fn clean_gpu_name(name: &str) -> &str {
let mut short = name.trim();
if let Some(marketing_name) = GPU_VENDOR_PREFIXES
.iter()
.find_map(|&prefix| short.strip_prefix(prefix))
{
short = marketing_name
.split_once('[')
.map_or(marketing_name, |(model, _)| model.trim_end());
} else if let Some((_, bracketed)) = short.split_once('[')
&& let Some((model, _)) = bracketed.split_once(']')
{
// PCI device names carry the marketing name in brackets, e.g. "DG2 [Arc A770]"
short = model.trim();
}
short = GPU_VENDOR_PREFIXES
.iter()
.find_map(|&prefix| short.strip_prefix(prefix))
.unwrap_or(short);
short
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Pong; pub struct Pong;
@@ -121,7 +148,7 @@ pub struct DeviceListEntry {
impl Display for DeviceListEntry { impl Display for DeviceListEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.name { match &self.name {
Some(name) => Display::fmt(name, f), Some(name) => Display::fmt(clean_gpu_name(name), f),
None => Display::fmt(&self.id, f), None => Display::fmt(&self.id, f),
} }
} }
+53 -1
View File
@@ -1,4 +1,4 @@
use crate::{FanControlMode, FanOptions, PmfwOptions, Pong, Request, Response}; use crate::{FanControlMode, FanOptions, PmfwOptions, Pong, Request, Response, clean_gpu_name};
use anyhow::anyhow; use anyhow::anyhow;
use serde_json::json; use serde_json::json;
use std::collections::BTreeMap; use std::collections::BTreeMap;
@@ -86,3 +86,55 @@ fn set_fan_clocks() {
}); });
assert_eq!(expected_request, request); assert_eq!(expected_request, request);
} }
#[test]
fn clean_gpu_name_removes_vendor_prefixes() {
assert_eq!(clean_gpu_name("AMD Radeon RX 9070 XT"), "Radeon RX 9070 XT");
assert_eq!(
clean_gpu_name("NVIDIA GeForce RTX 5090"),
"GeForce RTX 5090"
);
assert_eq!(clean_gpu_name("NVIDIA GeForce MX450"), "GeForce MX450");
assert_eq!(
clean_gpu_name("NVIDIA GeForce RTX 5090 [Founders Edition]"),
"GeForce RTX 5090"
);
assert_eq!(
clean_gpu_name("NVIDIA GeForce RTX 4070 Super"),
"GeForce RTX 4070 Super"
);
assert_eq!(clean_gpu_name("Intel Arc A380"), "Arc A380");
}
#[test]
fn clean_gpu_name_unwraps_pci_names() {
assert_eq!(
clean_gpu_name("Pitcairn XT [Radeon HD 7870 GHz Edition]"),
"Radeon HD 7870 GHz Edition"
);
assert_eq!(clean_gpu_name("DG2 [Arc A380]"), "Arc A380");
assert_eq!(clean_gpu_name("GK107M [GeForce 710A]"), "GeForce 710A");
assert_eq!(clean_gpu_name("GK107M [GeForce 820M]"), "GeForce 820M");
assert_eq!(clean_gpu_name("TU117M [GeForce MX450]"), "GeForce MX450");
assert_eq!(
clean_gpu_name("TigerLake-LP GT2 [Iris Xe Graphics]"),
"Iris Xe Graphics"
);
}
#[test]
fn clean_gpu_name_keeps_consumer_brands_and_unrecognized_names() {
assert_eq!(
clean_gpu_name("AMD Radeon 780M Graphics"),
"Radeon 780M Graphics"
);
assert_eq!(clean_gpu_name("AMD Radeon VII"), "Radeon VII");
assert_eq!(clean_gpu_name("Phoenix1"), "Phoenix1");
}
#[test]
fn clean_gpu_name_keeps_professional_product_brands() {
assert_eq!(clean_gpu_name("GK107GL [Quadro K600]"), "Quadro K600");
assert_eq!(clean_gpu_name("GK110GL [Tesla K20]"), "Tesla K20");
assert_eq!(clean_gpu_name("NVIDIA Quadro RTX 6000"), "Quadro RTX 6000");
}