Skip to content

Commit 58b004f

Browse files
game1024claude
andcommitted
feat: GPU stats, single instance, UI improvements
Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3bcb4f7 commit 58b004f

10 files changed

Lines changed: 210 additions & 43 deletions

File tree

src-tauri/Cargo.lock

Lines changed: 92 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,5 @@ windows = { version = "0.58", features = [
4545
] }
4646
base64 = "0.22"
4747
png = "0.17"
48+
hypomnesis = "0.2"
4849

src-tauri/binaries/bridge32.exe

0 Bytes
Binary file not shown.

src-tauri/binaries/bridge64.exe

0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

src-tauri/src/main.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
22
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
33

4+
use windows::Win32::System::Threading::CreateMutexW;
5+
use windows::Win32::Foundation::{GetLastError, ERROR_ALREADY_EXISTS, HANDLE, CloseHandle};
6+
use windows::core::PCWSTR;
7+
48
fn main() {
9+
// Single instance guard
10+
let name: Vec<u16> = "OpenSpeedy_SingleInstance\0".encode_utf16().collect();
11+
unsafe {
12+
if let Ok(h) = CreateMutexW(None, true, PCWSTR::from_raw(name.as_ptr())) {
13+
if GetLastError() == ERROR_ALREADY_EXISTS {
14+
let _ = CloseHandle(h);
15+
return;
16+
}
17+
}
18+
}
19+
520
openspeedy_lib::run()
621
}

src-tauri/src/system_stats.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,19 @@ use windows::Win32::System::SystemInformation::{
77
};
88
use windows::Win32::System::Threading::GetSystemTimes;
99

10+
#[derive(Debug, Clone, Serialize)]
11+
pub struct GpuStats {
12+
pub name: String,
13+
pub used_mb: u64,
14+
pub total_mb: u64,
15+
}
16+
1017
#[derive(Debug, Clone, Serialize)]
1118
pub struct SystemStats {
1219
pub memory_pct: f64,
1320
pub cpu_pct: f64,
1421
pub os_version: String,
22+
pub gpu: Option<GpuStats>,
1523
}
1624

1725
fn os_version() -> String {
@@ -112,7 +120,16 @@ pub fn get_system_stats() -> SystemStats {
112120
}
113121
};
114122

115-
SystemStats { memory_pct, cpu_pct, os_version: os_version() }
123+
// GPU
124+
let gpu = hypomnesis::Snapshot::now(0).ok().and_then(|snap| {
125+
snap.gpu_device.map(|dev| GpuStats {
126+
name: dev.name.unwrap_or_default(),
127+
used_mb: (dev.total_bytes - dev.free_bytes) / (1024 * 1024),
128+
total_mb: dev.total_bytes / (1024 * 1024),
129+
})
130+
});
131+
132+
SystemStats { memory_pct, cpu_pct, os_version: os_version(), gpu }
116133
}
117134

118135
fn ft_to_u64(ft: &FILETIME) -> u64 {

src-tauri/tauri.conf.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"title": "OpenSpeedy",
1616
"width": 960,
1717
"height": 640,
18+
"minWidth": 480,
19+
"minHeight": 360,
1820
"decorations": false,
1921
"dragDropEnabled": true
2022
}

src/App.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { useState, useEffect, useMemo } from "react";
22
import { useTranslation } from "react-i18next";
33
import { getVersion } from "@tauri-apps/api/app";
4-
import { ThemeProvider, createTheme, CssBaseline, Box, Tabs, Tab, Typography, Paper, Switch } from "@mui/material";
4+
import { ThemeProvider, createTheme, CssBaseline, Box, Tabs, Tab, Typography, Paper, Switch, Chip } from "@mui/material";
5+
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
6+
import ErrorIcon from "@mui/icons-material/Error";
57
import SpeedIcon from "@mui/icons-material/Speed";
68
import SettingsIcon from "@mui/icons-material/Settings";
79
import InfoIcon from "@mui/icons-material/Info";
@@ -28,6 +30,9 @@ function App() {
2830
const [version, setVersion] = useState("");
2931
const [b64, setB64] = useState<boolean | null>(null);
3032
const [b32, setB32] = useState<boolean | null>(null);
33+
const [gpuName, setGpuName] = useState("");
34+
const [gpuUsedMb, setGpuUsedMb] = useState(0);
35+
const [gpuTotalMb, setGpuTotalMb] = useState(0);
3136
const [darkMode, setDarkMode] = useState(false);
3237

3338
// Sync Blueprint dark mode class
@@ -65,10 +70,11 @@ function App() {
6570

6671
useInterval(async () => {
6772
try {
68-
const s = await invoke<{ memory_pct: number; cpu_pct: number; os_version: string }>("get_system_stats");
73+
const s = await invoke<{ memory_pct: number; cpu_pct: number; os_version: string; gpu: { name: string; used_mb: number; total_mb: number } | null }>("get_system_stats");
6974
setMemPct(s.memory_pct);
7075
setCpuPct(s.cpu_pct);
7176
setOsVer(s.os_version);
77+
if (s.gpu) { setGpuName(s.gpu.name); setGpuUsedMb(s.gpu.used_mb); setGpuTotalMb(s.gpu.total_mb); }
7278
} catch {}
7379
}, 5000);
7480

@@ -84,8 +90,8 @@ function App() {
8490

8591
// Initial fetch
8692
useEffect(() => {
87-
invoke<{ memory_pct: number; cpu_pct: number; os_version: string }>("get_system_stats")
88-
.then(s => { setMemPct(s.memory_pct); setCpuPct(s.cpu_pct); setOsVer(s.os_version); }).catch(() => {});
93+
invoke<{ memory_pct: number; cpu_pct: number; os_version: string; gpu: { name: string; used_mb: number; total_mb: number } | null }>("get_system_stats")
94+
.then(s => { setMemPct(s.memory_pct); setCpuPct(s.cpu_pct); setOsVer(s.os_version); if (s.gpu) { setGpuName(s.gpu.name); setGpuUsedMb(s.gpu.used_mb); setGpuTotalMb(s.gpu.total_mb); } }).catch(() => {});
8995
getVersion().then(setVersion).catch(() => {});
9096
invoke<boolean>("bridge64_health").catch(() => false).then(setB64);
9197
invoke<boolean>("bridge32_health").catch(() => false).then(setB32);
@@ -96,7 +102,7 @@ function App() {
96102
<CssBaseline />
97103
<SnackbarProvider>
98104
<Box sx={{ height: "100vh", display: "flex", flexDirection: "column", bgcolor: "background.default" }}>
99-
<TitleBar osVer={osVer} cpuPct={cpuPct} memPct={memPct} b64={b64} b32={b32} />
105+
<TitleBar osVer={osVer} cpuPct={cpuPct} memPct={memPct} gpuName={gpuName} gpuUsedMb={gpuUsedMb} gpuTotalMb={gpuTotalMb} />
100106

101107
<Box sx={{ flex: 1, display: "flex" }}>
102108
<Box sx={{ height: "calc(100vh - 48px)", borderRight: 1, borderColor: "divider", bgcolor: "background.paper", display: "flex", flexDirection: "column" }}>
@@ -107,6 +113,12 @@ function App() {
107113
<Tab icon={<InfoIcon />} label={t("app.tabs.about")} />
108114
</Tabs>
109115
<Box sx={{ flex: 1 }} />
116+
117+
<Box sx={{ display: "flex", flexDirection: "column", gap: 0.5, px: 1, pb: 0.5 }}>
118+
<BridgeChip ok={b64} label="B64" />
119+
<BridgeChip ok={b32} label="B32" />
120+
</Box>
121+
110122
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "center", pb: 1 }}>
111123
{darkMode ? <DarkModeIcon sx={{ fontSize: 14, color: "text.secondary" }} /> : <LightModeIcon sx={{ fontSize: 14, color: "text.secondary" }} />}
112124
<Switch size="small" checked={darkMode} onChange={(_, v) => setDarkMode(v)} />
@@ -165,4 +177,17 @@ function App() {
165177
);
166178
}
167179

180+
function BridgeChip({ ok, label }: { ok: boolean | null; label: string }) {
181+
return (
182+
<Chip
183+
icon={ok === null ? undefined : ok ? <CheckCircleIcon /> : <ErrorIcon />}
184+
label={ok === null ? label : ok ? label : label}
185+
size="small"
186+
color={ok === null ? "default" : ok ? "success" : "error"}
187+
variant="filled"
188+
sx={{ height: 22, fontSize: "0.65rem" }}
189+
/>
190+
);
191+
}
192+
168193
export default App;

0 commit comments

Comments
 (0)