1- const { app, BrowserWindow, globalShortcut, screen } = require ( 'electron' ) ;
1+ const { app, BrowserWindow, globalShortcut, screen, ipcMain } = require ( 'electron' ) ;
22const path = require ( 'path' ) ;
33const fs = require ( 'fs' ) ;
4+ const os = require ( 'os' ) ;
5+ const util = require ( 'util' ) ;
6+ const execAsync = util . promisify ( require ( 'child_process' ) . exec ) ;
47
58let mainWindow ;
69let config = { autoLaunch : false , shortcut : 'CommandOrControl+G' } ;
10+ let updateInterval ;
11+
12+ async function collectStats ( ) {
13+ const stats = { } ;
14+
15+ // CPU Temperature
16+ try {
17+ let { stdout } = await execAsync ( 'sensors | grep -A 0 "Tctl:" | cut -c15-22' ) ;
18+ stats . cpuTemp = parseFloat ( stdout . trim ( ) ) . toFixed ( 1 ) ;
19+ } catch {
20+ try {
21+ const raw = fs . readFileSync ( '/sys/class/thermal/thermal_zone0/temp' , 'utf8' ) ;
22+ stats . cpuTemp = ( parseInt ( raw ) / 1000 ) . toFixed ( 1 ) ;
23+ } catch {
24+ stats . cpuTemp = 'N/A' ;
25+ }
26+ }
27+
28+ // CPU Usage
29+ try {
30+ let { stdout } = await execAsync ( 'top -bn1 | grep "Cpu(s)" | sed "s/.*, *\\([0-9.]*\\)%* id.*/\\1/" | awk \'{print 100 - $1}\'' ) ;
31+ stats . cpuUsage = parseFloat ( stdout . trim ( ) ) . toFixed ( 1 ) ;
32+ } catch {
33+ stats . cpuUsage = 'N/A' ;
34+ }
35+
36+ // CPU Frequency
37+ try {
38+ let { stdout } = await execAsync ( 'cat /proc/cpuinfo | grep "cpu MHz" | head -1 | awk \'{print $4}\'' ) ;
39+ stats . cpuFreq = parseFloat ( stdout . trim ( ) ) . toFixed ( 0 ) ;
40+ } catch {
41+ stats . cpuFreq = 'N/A' ;
42+ }
43+
44+ // CPU Fan
45+ try {
46+ let { stdout } = await execAsync ( 'sensors | grep "fan1:" | awk \'{print $2}\'' ) ;
47+ stats . cpuFan = stdout . trim ( ) ;
48+ } catch {
49+ stats . cpuFan = 'N/A' ;
50+ }
51+
52+ // GPU Temperature
53+ try {
54+ let { stdout } = await execAsync ( 'nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader' ) ;
55+ stats . gpuTemp = parseFloat ( stdout . trim ( ) ) . toFixed ( 1 ) ;
56+ } catch {
57+ try {
58+ let { stdout } = await execAsync ( 'rocm-smi --showtemp | grep "GPU Temp" | awk \'{print $4}\'' ) ;
59+ stats . gpuTemp = parseFloat ( stdout . trim ( ) ) . toFixed ( 1 ) ;
60+ } catch {
61+ stats . gpuTemp = 'N/A' ;
62+ }
63+ }
64+
65+ // GPU Usage
66+ try {
67+ let { stdout } = await execAsync ( 'nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader' ) ;
68+ stats . gpuUsage = parseFloat ( stdout . trim ( ) ) . toFixed ( 1 ) ;
69+ } catch {
70+ try {
71+ let { stdout } = await execAsync ( 'rocm-smi --showutil | grep "GPU use" | awk \'{print $4}\'' ) ;
72+ stats . gpuUsage = parseFloat ( stdout . trim ( ) ) . toFixed ( 1 ) ;
73+ } catch {
74+ stats . gpuUsage = 'N/A' ;
75+ }
76+ }
77+
78+ // GPU Fan
79+ try {
80+ let { stdout } = await execAsync ( 'nvidia-smi --query-gpu=fan.speed --format=csv,noheader' ) ;
81+ stats . gpuFan = parseFloat ( stdout . trim ( ) ) . toFixed ( 1 ) ;
82+ } catch {
83+ try {
84+ let { stdout } = await execAsync ( 'rocm-smi --showfan | grep "Fan Level" | awk \'{print $4}\'' ) ;
85+ stats . gpuFan = parseFloat ( stdout . trim ( ) ) . toFixed ( 1 ) ;
86+ } catch {
87+ stats . gpuFan = 'N/A' ;
88+ }
89+ }
90+
91+ // GPU Memory
92+ try {
93+ let { stdout } = await execAsync ( 'nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader' ) ;
94+ const [ used , total ] = stdout . trim ( ) . split ( ',' ) . map ( s => s . trim ( ) . replace ( ' MiB' , '' ) ) ;
95+ stats . gpuMem = `${ used } /${ total } ` ;
96+ } catch {
97+ try {
98+ let { stdout } = await execAsync ( 'rocm-smi --showmeminfo vram | grep "Used" | awk \'{print $4 "/" $5}\'' ) ;
99+ stats . gpuMem = stdout . trim ( ) ;
100+ } catch {
101+ stats . gpuMem = 'N/A' ;
102+ }
103+ }
104+
105+ // RAM Usage
106+ const totalMem = os . totalmem ( ) ;
107+ const freeMem = os . freemem ( ) ;
108+ const usedMem = totalMem - freeMem ;
109+ stats . ramUsage = ( ( usedMem / totalMem ) * 100 ) . toFixed ( 1 ) ;
110+
111+ // Disk Usage
112+ try {
113+ let { stdout } = await execAsync ( 'df -h / | tail -1 | awk \'{print $5}\' | sed \'s/%//\'' ) ;
114+ stats . diskUsage = stdout . trim ( ) ;
115+ } catch {
116+ stats . diskUsage = 'N/A' ;
117+ }
118+
119+ // Battery Level
120+ try {
121+ const batteryPath = '/sys/class/power_supply/BAT0/capacity' ;
122+ if ( fs . existsSync ( batteryPath ) ) {
123+ stats . batteryLevel = fs . readFileSync ( batteryPath , 'utf8' ) . trim ( ) ;
124+ } else {
125+ stats . batteryLevel = 'N/A' ;
126+ }
127+ } catch {
128+ stats . batteryLevel = 'N/A' ;
129+ }
130+
131+ // Uptime
132+ const uptimeSeconds = os . uptime ( ) ;
133+ const days = Math . floor ( uptimeSeconds / ( 3600 * 24 ) ) ;
134+ const hours = Math . floor ( ( uptimeSeconds % ( 3600 * 24 ) ) / 3600 ) ;
135+ const minutes = Math . floor ( ( uptimeSeconds % 3600 ) / 60 ) ;
136+ const seconds = Math . floor ( uptimeSeconds % 60 ) ;
137+ stats . uptime = `${ days } d ${ hours } h ${ minutes } m ${ seconds } s` ;
138+
139+ // Network (placeholder)
140+ stats . netDownload = ( Math . random ( ) * 100 ) . toFixed ( 1 ) ;
141+ stats . netUpload = ( Math . random ( ) * 20 ) . toFixed ( 1 ) ;
142+
143+ // FPS (placeholder)
144+ stats . fps = Math . floor ( Math . random ( ) * 60 + 30 ) ;
145+
146+ return stats ;
147+ }
148+
149+ function startUpdating ( ) {
150+ if ( updateInterval ) clearInterval ( updateInterval ) ;
151+ updateInterval = setInterval ( async ( ) => {
152+ if ( mainWindow && mainWindow . isVisible ( ) ) {
153+ const stats = await collectStats ( ) ;
154+ mainWindow . webContents . send ( 'update-stats' , stats ) ;
155+ }
156+ } , 1000 ) ;
157+ }
158+
159+ function stopUpdating ( ) {
160+ if ( updateInterval ) clearInterval ( updateInterval ) ;
161+ }
7162
8163function createWindow ( ) {
9164 const { width, height } = screen . getPrimaryDisplay ( ) . workAreaSize ;
@@ -22,26 +177,27 @@ function createWindow() {
22177 resizable : false ,
23178 webPreferences : {
24179 preload : path . join ( __dirname , 'preload.js' ) ,
25- nodeIntegration : true ,
26- contextIsolation : false
180+ nodeIntegration : false ,
181+ contextIsolation : true
27182 }
28183 } ) ;
29184
30185 mainWindow . loadFile ( 'index.html' ) ;
31186 mainWindow . setIgnoreMouseEvents ( true ) ;
32- mainWindow . hide ( ) ; // Start hidden
187+ mainWindow . hide ( ) ;
188+
189+ mainWindow . on ( 'show' , startUpdating ) ;
190+ mainWindow . on ( 'hide' , stopUpdating ) ;
33191}
34192
35193app . whenReady ( ) . then ( ( ) => {
36- // Load config
37194 const configPath = path . join ( __dirname , 'config.json' ) ;
38195 if ( fs . existsSync ( configPath ) ) {
39196 config = JSON . parse ( fs . readFileSync ( configPath , 'utf8' ) ) ;
40197 }
41198
42199 createWindow ( ) ;
43200
44- // Register global shortcut
45201 const ret = globalShortcut . register ( config . shortcut , ( ) => {
46202 if ( mainWindow . isVisible ( ) ) {
47203 mainWindow . hide ( ) ;
@@ -54,7 +210,6 @@ app.whenReady().then(() => {
54210 console . log ( 'Shortcut registration failed' ) ;
55211 }
56212
57- // If autoLaunch is true, show immediately
58213 if ( config . autoLaunch ) {
59214 mainWindow . show ( ) ;
60215 }
0 commit comments