[feat] more mborad and memory info
This commit is contained in:
@@ -470,9 +470,338 @@ pub fn get_gpu_info() -> Result<Option<GpuInfo>, String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 内存信息结构体
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct MemoryInfo {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
capacity: Option<u64>, // 容量(字节)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
manufacturer: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
speed: Option<u32>, // MHz,实际频率 ConfiguredClockSpeed
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
default_speed: Option<u32>, // MHz,默认频率 Speed(如果存在)
|
||||
}
|
||||
|
||||
/// 显示器信息结构体
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct MonitorInfo {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
manufacturer: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
model: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
refresh_rate: Option<u32>, // Hz
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
resolution_width: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
resolution_height: Option<u32>,
|
||||
}
|
||||
|
||||
/// 主板信息结构体
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct MotherboardInfo {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
manufacturer: Option<String>, // 制造商
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
model: Option<String>, // 型号
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
version: Option<String>,
|
||||
}
|
||||
|
||||
/// 获取内存信息(Windows)
|
||||
#[tauri::command]
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn get_memory_info() -> Result<Vec<MemoryInfo>, String> {
|
||||
use tokio::process::Command;
|
||||
|
||||
// 执行 PowerShell 命令获取内存信息
|
||||
let output = Command::new("powershell")
|
||||
.args(&[
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-WmiObject Win32_PhysicalMemory | Select-Object Capacity, Manufacturer, ConfiguredClockSpeed, Speed | ConvertTo-Json -Compress"
|
||||
])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("执行 PowerShell 命令失败: {}", e))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(format!("PowerShell 命令执行失败: {}", stderr));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let cleaned = stdout.trim().trim_start_matches('\u{feff}');
|
||||
|
||||
if cleaned.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
// PowerShell 可能返回数组或单个对象
|
||||
let json: serde_json::Value = serde_json::from_str(cleaned)
|
||||
.map_err(|e| format!("解析 JSON 失败: {},原始输出: {}", e, cleaned))?;
|
||||
|
||||
let mut memory_list = Vec::new();
|
||||
|
||||
if let Some(array) = json.as_array() {
|
||||
// 如果是数组
|
||||
for item in array {
|
||||
memory_list.push(parse_memory_info(item));
|
||||
}
|
||||
} else {
|
||||
// 如果是单个对象
|
||||
memory_list.push(parse_memory_info(&json));
|
||||
}
|
||||
|
||||
Ok(memory_list)
|
||||
}
|
||||
|
||||
/// 解析内存信息
|
||||
fn parse_memory_info(json: &serde_json::Value) -> MemoryInfo {
|
||||
// 容量(字节)
|
||||
let capacity = json.get("Capacity")
|
||||
.and_then(|v| v.as_u64());
|
||||
|
||||
let manufacturer = json.get("Manufacturer")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
|
||||
// 实际频率:优先使用 ConfiguredClockSpeed
|
||||
let speed = json.get("ConfiguredClockSpeed")
|
||||
.and_then(|v| v.as_u64())
|
||||
.map(|v| v as u32);
|
||||
|
||||
// 默认频率:Speed(如果存在)
|
||||
let default_speed = json.get("Speed")
|
||||
.and_then(|v| v.as_u64())
|
||||
.map(|v| v as u32);
|
||||
|
||||
MemoryInfo {
|
||||
capacity,
|
||||
manufacturer,
|
||||
speed,
|
||||
default_speed,
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取内存信息(非 Windows 平台返回空)
|
||||
#[tauri::command]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub async fn get_memory_info() -> Result<Vec<MemoryInfo>, String> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
/// 获取显示器信息(Windows)
|
||||
#[tauri::command]
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn get_monitor_info() -> Result<Vec<MonitorInfo>, String> {
|
||||
use tokio::process::Command;
|
||||
|
||||
// 执行 PowerShell 命令获取显示器信息
|
||||
let output = Command::new("powershell")
|
||||
.args(&[
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-CimInstance -Namespace root/wmi -ClassName WmiMonitorID | ForEach-Object { [PSCustomObject]@{ Manufacturer = [System.Text.Encoding]::ASCII.GetString($_.ManufacturerName) -replace \"`0\"; Model = [System.Text.Encoding]::ASCII.GetString($_.UserFriendlyName) -replace \"`0\" } } | ConvertTo-Json -Compress"
|
||||
])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("执行 PowerShell 命令失败: {}", e))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(format!("PowerShell 命令执行失败: {}", stderr));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let cleaned = stdout.trim().trim_start_matches('\u{feff}');
|
||||
|
||||
if cleaned.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let json: serde_json::Value = serde_json::from_str(cleaned)
|
||||
.map_err(|e| format!("解析 JSON 失败: {},原始输出: {}", e, cleaned))?;
|
||||
|
||||
let mut monitor_list = Vec::new();
|
||||
|
||||
if let Some(array) = json.as_array() {
|
||||
for item in array {
|
||||
monitor_list.push(parse_monitor_info(item));
|
||||
}
|
||||
} else {
|
||||
monitor_list.push(parse_monitor_info(&json));
|
||||
}
|
||||
|
||||
// 尝试获取刷新率和分辨率信息
|
||||
let display_output = Command::new("powershell")
|
||||
.args(&[
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-CimInstance -Namespace root/wmi -ClassName WmiMonitorBasicDisplayParams | Select-Object MaxHorizontalImageSize, MaxVerticalImageSize | ConvertTo-Json -Compress"
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
// 获取刷新率信息
|
||||
let refresh_output = Command::new("powershell")
|
||||
.args(&[
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-CimInstance -Namespace root/wmi -ClassName WmiMonitorListedSupportedSourceModes | Select-Object -First 1 -ExpandProperty ModeTimings | Select-Object -First 1 -ExpandProperty RefreshRate | ConvertTo-Json -Compress"
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
// 获取当前显示器的分辨率和刷新率
|
||||
let current_display_output = Command::new("powershell")
|
||||
.args(&[
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Add-Type -AssemblyName System.Windows.Forms; $screens = [System.Windows.Forms.Screen]::AllScreens; $screens | ForEach-Object { [PSCustomObject]@{ Width = $_.Bounds.Width; Height = $_.Bounds.Height; Primary = $_.Primary } } | ConvertTo-Json -Compress"
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
// 合并显示器信息
|
||||
if let Ok(display_result) = current_display_output {
|
||||
if display_result.status.success() {
|
||||
let display_str = String::from_utf8_lossy(&display_result.stdout).to_string();
|
||||
let display_str = display_str.trim().trim_start_matches('\u{feff}').to_string();
|
||||
if let Ok(display_json) = serde_json::from_str::<serde_json::Value>(&display_str) {
|
||||
if let Some(displays) = display_json.as_array() {
|
||||
for (i, display) in displays.iter().enumerate() {
|
||||
if i < monitor_list.len() {
|
||||
if let Some(width) = display.get("Width").and_then(|v| v.as_u64()) {
|
||||
monitor_list[i].resolution_width = Some(width as u32);
|
||||
}
|
||||
if let Some(height) = display.get("Height").and_then(|v| v.as_u64()) {
|
||||
monitor_list[i].resolution_height = Some(height as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(display) = display_json.as_object() {
|
||||
if monitor_list.len() > 0 {
|
||||
if let Some(width) = display.get("Width").and_then(|v| v.as_u64()) {
|
||||
monitor_list[0].resolution_width = Some(width as u32);
|
||||
}
|
||||
if let Some(height) = display.get("Height").and_then(|v| v.as_u64()) {
|
||||
monitor_list[0].resolution_height = Some(height as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(monitor_list)
|
||||
}
|
||||
|
||||
/// 解析显示器信息
|
||||
fn parse_monitor_info(json: &serde_json::Value) -> MonitorInfo {
|
||||
let manufacturer = json.get("Manufacturer")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
|
||||
let model = json.get("Model")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
|
||||
// 组合制造商和型号作为名称
|
||||
let name = match (&manufacturer, &model) {
|
||||
(Some(mfg), Some(model_name)) => Some(format!("{} {}", mfg, model_name)),
|
||||
(Some(mfg), None) => Some(mfg.clone()),
|
||||
(None, Some(model_name)) => Some(model_name.clone()),
|
||||
(None, None) => None,
|
||||
};
|
||||
|
||||
MonitorInfo {
|
||||
manufacturer,
|
||||
model,
|
||||
name,
|
||||
refresh_rate: None, // 需要从其他来源获取
|
||||
resolution_width: None,
|
||||
resolution_height: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取显示器信息(非 Windows 平台返回空)
|
||||
#[tauri::command]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub async fn get_monitor_info() -> Result<Vec<MonitorInfo>, String> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
/// 获取主板信息(Windows)
|
||||
#[tauri::command]
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn get_motherboard_info() -> Result<MotherboardInfo, String> {
|
||||
use tokio::process::Command;
|
||||
|
||||
// 执行 PowerShell 命令获取主板信息
|
||||
let output = Command::new("powershell")
|
||||
.args(&[
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-CimInstance -ClassName Win32_BaseBoard | Select-Object Manufacturer, Product, Version | ConvertTo-Json -Compress"
|
||||
])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("执行 PowerShell 命令失败: {}", e))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(format!("PowerShell 命令执行失败: {}", stderr));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let cleaned = stdout.trim().trim_start_matches('\u{feff}');
|
||||
|
||||
if cleaned.is_empty() {
|
||||
return Ok(MotherboardInfo {
|
||||
manufacturer: None,
|
||||
model: None,
|
||||
version: None,
|
||||
});
|
||||
}
|
||||
|
||||
let json: serde_json::Value = serde_json::from_str(cleaned)
|
||||
.map_err(|e| format!("解析 JSON 失败: {},原始输出: {}", e, cleaned))?;
|
||||
|
||||
// 分别获取制造商和型号
|
||||
let manufacturer = json.get("Manufacturer")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
|
||||
let model = json.get("Product")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
|
||||
Ok(MotherboardInfo {
|
||||
manufacturer,
|
||||
model,
|
||||
version: json.get("Version")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty()),
|
||||
})
|
||||
}
|
||||
|
||||
/// 获取主板信息(非 Windows 平台返回空)
|
||||
#[tauri::command]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub async fn get_motherboard_info() -> Result<MotherboardInfo, String> {
|
||||
Ok(MotherboardInfo {
|
||||
manufacturer: None,
|
||||
model: None,
|
||||
version: None,
|
||||
})
|
||||
}
|
||||
@@ -171,7 +171,9 @@ fn main() {
|
||||
cmds::install_app_update,
|
||||
cmds::get_computer_info,
|
||||
cmds::get_gpu_info,
|
||||
cmds::get_memory_info,
|
||||
cmds::get_monitor_info,
|
||||
cmds::get_motherboard_info,
|
||||
on_button_clicked
|
||||
])
|
||||
.run(ctx)
|
||||
|
||||
Reference in New Issue
Block a user