232 lines
5.8 KiB
JavaScript
232 lines
5.8 KiB
JavaScript
const { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage, dialog } = require('electron');
|
|
const path = require('path');
|
|
const Store = require('electron-store');
|
|
const si = require('systeminformation');
|
|
const usb = require('usb');
|
|
|
|
const store = new Store({ defaults: { preferences: {} } });
|
|
|
|
let mainWindow = null;
|
|
let tray = null;
|
|
|
|
function createTrayIcon() {
|
|
const size = 16;
|
|
const canvas = Buffer.alloc(size * size * 4);
|
|
for (let y = 0; y < size; y++) {
|
|
for (let x = 0; x < size; x++) {
|
|
const idx = (y * size + x) * 4;
|
|
const cx = x - size / 2, cy = y - size / 2;
|
|
if (Math.sqrt(cx * cx + cy * cy) < size / 2 - 1) {
|
|
canvas[idx] = 0;
|
|
canvas[idx + 1] = 120;
|
|
canvas[idx + 2] = 255;
|
|
canvas[idx + 3] = 255;
|
|
} else {
|
|
canvas[idx] = 0;
|
|
canvas[idx + 1] = 0;
|
|
canvas[idx + 2] = 0;
|
|
canvas[idx + 3] = 0;
|
|
}
|
|
}
|
|
}
|
|
return nativeImage.createFromBuffer(canvas, { width: size, height: size });
|
|
}
|
|
|
|
function createWindow() {
|
|
mainWindow = new BrowserWindow({
|
|
width: 900,
|
|
height: 600,
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
},
|
|
});
|
|
mainWindow.loadFile(path.join(__dirname, 'src', 'index.html'));
|
|
mainWindow.on('close', (e) => {
|
|
if (!app.isQuitting) {
|
|
e.preventDefault();
|
|
mainWindow.hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
function createTray() {
|
|
tray = new Tray(createTrayIcon());
|
|
tray.setToolTip('Corsair LCD Control');
|
|
const contextMenu = Menu.buildFromTemplate([
|
|
{ label: 'Show', click: () => mainWindow.show() },
|
|
{ label: 'Hide', click: () => mainWindow.hide() },
|
|
{ type: 'separator' },
|
|
{ label: 'Quit', click: () => { app.isQuitting = true; app.quit(); } },
|
|
]);
|
|
tray.setContextMenu(contextMenu);
|
|
tray.on('double-click', () => mainWindow.show());
|
|
}
|
|
|
|
const VENDORS = {
|
|
0x1B1C: 'Corsair',
|
|
0x046D: 'Logitech',
|
|
0x0A5C: 'Broadcom',
|
|
0x8087: 'Intel',
|
|
0x05E3: 'Genesys Logic',
|
|
0x04F2: 'Chicony',
|
|
0x0BDA: 'Realtek',
|
|
0x1D6B: 'Linux Foundation',
|
|
0x3034: 'Realtek',
|
|
0x5964: 'Asmedia',
|
|
};
|
|
|
|
function formatHexId(vendorId, productId) {
|
|
const hex = `${vendorId.toString(16).padStart(4, '0')}:${productId.toString(16).padStart(4, '0')}`;
|
|
const name = VENDORS[vendorId] || '';
|
|
return name ? `${name} [${hex}]` : hex;
|
|
}
|
|
|
|
function readStringDescriptor(device, index) {
|
|
return new Promise((resolve) => {
|
|
if (!index) return resolve('');
|
|
try {
|
|
device.getStringDescriptor(index, (err, desc) => {
|
|
resolve(err ? '' : desc);
|
|
});
|
|
} catch {
|
|
resolve('');
|
|
}
|
|
});
|
|
}
|
|
|
|
// --- USB ---
|
|
|
|
ipcMain.handle('usb:listDevices', async () => {
|
|
try {
|
|
const devices = usb.getDeviceList();
|
|
const results = [];
|
|
for (const d of devices) {
|
|
const entry = {
|
|
vendorId: d.deviceDescriptor.idVendor,
|
|
productId: d.deviceDescriptor.idProduct,
|
|
displayName: formatHexId(d.deviceDescriptor.idVendor, d.deviceDescriptor.idProduct),
|
|
manufacturer: '',
|
|
product: '',
|
|
serialNumber: '',
|
|
};
|
|
try {
|
|
d.open(true);
|
|
const [mfg, prod, sn] = await Promise.all([
|
|
readStringDescriptor(d, d.deviceDescriptor.iManufacturer),
|
|
readStringDescriptor(d, d.deviceDescriptor.iProduct),
|
|
readStringDescriptor(d, d.deviceDescriptor.iSerialNumber),
|
|
]);
|
|
if (mfg || prod) {
|
|
entry.manufacturer = mfg;
|
|
entry.product = prod;
|
|
entry.displayName = [mfg, prod].filter(Boolean).join(' ');
|
|
}
|
|
entry.serialNumber = sn;
|
|
d.close();
|
|
} catch {
|
|
// no permission — keep the hex-based displayName
|
|
}
|
|
results.push(entry);
|
|
}
|
|
return results;
|
|
} catch (err) {
|
|
return { error: err.message };
|
|
}
|
|
});
|
|
|
|
ipcMain.handle('usb:connect', async (_e, { vendorId, productId }) => {
|
|
try {
|
|
const device = usb.findByIds(vendorId, productId);
|
|
if (!device) throw new Error('Device not found');
|
|
device.open();
|
|
return { success: true };
|
|
} catch (err) {
|
|
return { error: err.message };
|
|
}
|
|
});
|
|
|
|
// --- Preferences ---
|
|
ipcMain.handle('prefs:get', (_e, key) => {
|
|
if (key) return store.get(key);
|
|
return store.store;
|
|
});
|
|
|
|
ipcMain.handle('prefs:set', (_e, key, value) => {
|
|
store.set(key, value);
|
|
return { success: true };
|
|
});
|
|
|
|
ipcMain.handle('prefs:delete', (_e, key) => {
|
|
store.delete(key);
|
|
return { success: true };
|
|
});
|
|
|
|
// --- File Picker ---
|
|
ipcMain.handle('dialog:openFile', async (_e, options) => {
|
|
const result = await dialog.showOpenDialog(mainWindow, {
|
|
properties: ['openFile'],
|
|
...options,
|
|
});
|
|
return result;
|
|
});
|
|
|
|
ipcMain.handle('dialog:saveFile', async (_e, options) => {
|
|
const result = await dialog.showSaveDialog(mainWindow, options);
|
|
return result;
|
|
});
|
|
|
|
// --- System Info ---
|
|
ipcMain.handle('system:cpuTemp', async () => {
|
|
try {
|
|
const data = await si.cpuTemperature();
|
|
return data;
|
|
} catch (err) {
|
|
return { error: err.message };
|
|
}
|
|
});
|
|
|
|
ipcMain.handle('system:ram', async () => {
|
|
try {
|
|
const data = await si.mem();
|
|
return {
|
|
total: data.total,
|
|
free: data.free,
|
|
used: data.used,
|
|
usedPercent: ((data.used / data.total) * 100).toFixed(1),
|
|
};
|
|
} catch (err) {
|
|
return { error: err.message };
|
|
}
|
|
});
|
|
|
|
ipcMain.handle('system:gpu', async () => {
|
|
try {
|
|
const data = await si.graphics();
|
|
const controllers = data.controllers.map((c) => ({
|
|
vendor: c.vendor,
|
|
model: c.model,
|
|
temperatureGpu: c.temperatureGpu,
|
|
memoryUsed: c.memoryUsed,
|
|
memoryTotal: c.memoryTotal,
|
|
}));
|
|
return controllers;
|
|
} catch (err) {
|
|
return { error: err.message };
|
|
}
|
|
});
|
|
|
|
app.whenReady().then(() => {
|
|
createWindow();
|
|
createTray();
|
|
});
|
|
|
|
app.on('window-all-closed', () => {
|
|
if (process.platform !== 'darwin') app.quit();
|
|
});
|
|
|
|
app.on('activate', () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
});
|