Files
corsair-lcd-control/main.js
2026-06-09 02:29:12 -06:00

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();
});