Initial code commit for project
This commit is contained in:
231
main.js
Normal file
231
main.js
Normal file
@@ -0,0 +1,231 @@
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user