Initial Commit
This commit is contained in:
449
main.js
Normal file
449
main.js
Normal file
@@ -0,0 +1,449 @@
|
||||
import { app, BrowserWindow, ipcMain, dialog, Tray, Menu, nativeImage } from 'electron';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { LoupedeckDevice } from './src/loupedeck/device.js';
|
||||
import { ConfigManager } from './src/loupedeck/config.js';
|
||||
import { PageManager } from './src/loupedeck/pages.js';
|
||||
import { StreamDeckPedalDevice } from './src/streamdeck/device.js';
|
||||
import { PedalPageManager } from './src/streamdeck/pages.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
function getTrayIcon() {
|
||||
const iconPath = path.join(__dirname, 'assets', 'icons', 'Icon.png');
|
||||
return nativeImage.createFromPath(iconPath);
|
||||
}
|
||||
|
||||
let mainWindow;
|
||||
let tray;
|
||||
let loupedeckDevice;
|
||||
let configManager;
|
||||
let pageManager;
|
||||
let deviceStatus = { connected: false };
|
||||
let pedalDevice;
|
||||
let pedalPageManager;
|
||||
let pedalStatus = { connected: false };
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1580,
|
||||
height: 800,
|
||||
minWidth: 1380,
|
||||
minHeight: 600,
|
||||
frame: false,
|
||||
icon: path.join(__dirname, 'assets', 'icons', 'Icon.png'),
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.cjs'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false
|
||||
},
|
||||
backgroundColor: '#1a1a2e',
|
||||
title: 'Packed PackControl - Loupedeck Live'
|
||||
});
|
||||
|
||||
mainWindow.loadFile('src/renderer/index.html');
|
||||
|
||||
mainWindow.webContents.on('did-finish-load', () => {
|
||||
mainWindow.webContents.send('device-status', deviceStatus);
|
||||
});
|
||||
|
||||
mainWindow.on('close', (event) => {
|
||||
if (!app.isQuitting) {
|
||||
event.preventDefault();
|
||||
mainWindow.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createTray() {
|
||||
tray = new Tray(getTrayIcon());
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{ label: 'Open', click: () => mainWindow.show() },
|
||||
{ label: 'Quit', click: () => { app.isQuitting = true; app.quit(); } }
|
||||
]);
|
||||
tray.setToolTip('Packed');
|
||||
tray.setContextMenu(contextMenu);
|
||||
tray.on('click', () => mainWindow.show());
|
||||
}
|
||||
|
||||
async function initializeLoupedeck() {
|
||||
configManager = new ConfigManager();
|
||||
await configManager.load();
|
||||
|
||||
pageManager = new PageManager(configManager);
|
||||
|
||||
loupedeckDevice = new LoupedeckDevice(pageManager, configManager);
|
||||
|
||||
loupedeckDevice.on('connected', () => {
|
||||
deviceStatus = { connected: true };
|
||||
mainWindow.webContents.send('device-status', { connected: true });
|
||||
});
|
||||
|
||||
loupedeckDevice.on('disconnected', () => {
|
||||
deviceStatus = { connected: false };
|
||||
mainWindow.webContents.send('device-status', { connected: false });
|
||||
});
|
||||
|
||||
loupedeckDevice.on('button-press', (data) => {
|
||||
mainWindow.webContents.send('button-press', data);
|
||||
});
|
||||
|
||||
loupedeckDevice.on('knob-rotate', (data) => {
|
||||
mainWindow.webContents.send('knob-rotate', data);
|
||||
});
|
||||
|
||||
loupedeckDevice.on('page-changed', (pageIndex) => {
|
||||
mainWindow.webContents.send('page-changed', pageIndex);
|
||||
});
|
||||
|
||||
loupedeckDevice.on('metric-update', (updates) => {
|
||||
mainWindow.webContents.send('metric-update', updates);
|
||||
});
|
||||
|
||||
loupedeckDevice.on('button-toggle', (data) => {
|
||||
mainWindow.webContents.send('button-toggle', data);
|
||||
});
|
||||
|
||||
await loupedeckDevice.connect();
|
||||
}
|
||||
|
||||
async function initializePedal() {
|
||||
pedalPageManager = new PedalPageManager(configManager);
|
||||
pedalDevice = new StreamDeckPedalDevice(pedalPageManager, configManager);
|
||||
|
||||
pedalDevice.on('connected', () => {
|
||||
pedalStatus = { connected: true };
|
||||
mainWindow.webContents.send('pedal-status', { connected: true });
|
||||
});
|
||||
|
||||
pedalDevice.on('disconnected', () => {
|
||||
pedalStatus = { connected: false };
|
||||
mainWindow.webContents.send('pedal-status', { connected: false });
|
||||
});
|
||||
|
||||
pedalDevice.on('button-press', (data) => {
|
||||
mainWindow.webContents.send('pedal-button-press', data);
|
||||
});
|
||||
|
||||
await pedalDevice.connect();
|
||||
}
|
||||
|
||||
// Pedal-IPC-Handler
|
||||
ipcMain.handle('get-pedal-pages', () => pedalPageManager?.getPages() ?? []);
|
||||
ipcMain.handle('get-pedal-current-page', () => pedalPageManager?.getCurrentPageIndex() ?? 0);
|
||||
ipcMain.handle('get-pedal-status', () => pedalStatus);
|
||||
|
||||
ipcMain.handle('set-pedal-button-config', async (event, { pageIndex, buttonIndex, config }) => {
|
||||
if (!pedalPageManager) return false;
|
||||
pedalPageManager.setButtonConfig(pageIndex, buttonIndex, config);
|
||||
await configManager.save();
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('reset-pedal-button-config', async (event, { pageIndex, buttonIndex }) => {
|
||||
if (!pedalPageManager) return null;
|
||||
const defaultConfig = pedalPageManager.resetButtonConfig(pageIndex, buttonIndex);
|
||||
if (!defaultConfig) return null;
|
||||
await configManager.save();
|
||||
return defaultConfig;
|
||||
});
|
||||
|
||||
ipcMain.handle('add-pedal-page', async () => {
|
||||
if (!pedalPageManager) return null;
|
||||
const newPage = pedalPageManager.addPage();
|
||||
await configManager.save();
|
||||
return newPage;
|
||||
});
|
||||
|
||||
ipcMain.handle('rename-pedal-page', async (event, { pageIndex, name }) => {
|
||||
if (!pedalPageManager) return false;
|
||||
pedalPageManager.renamePage(pageIndex, name);
|
||||
await configManager.save();
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-pedal-page', async (event, pageIndex) => {
|
||||
if (!pedalPageManager) return false;
|
||||
pedalPageManager.deletePage(pageIndex);
|
||||
await configManager.save();
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('switch-pedal-page', async (event, pageIndex) => {
|
||||
if (!pedalPageManager) return false;
|
||||
pedalPageManager.switchPage(pageIndex);
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('reconnect-pedal', async () => {
|
||||
if (!pedalDevice) return;
|
||||
await pedalDevice.disconnect();
|
||||
await pedalDevice.connect();
|
||||
});
|
||||
|
||||
// IPC-Handler
|
||||
ipcMain.handle('get-config', () => configManager.getConfig());
|
||||
ipcMain.handle('get-pages', () => pageManager.getPages());
|
||||
ipcMain.handle('get-current-page', () => pageManager.getCurrentPageIndex());
|
||||
ipcMain.handle('get-device-status', () => deviceStatus);
|
||||
|
||||
ipcMain.handle('set-button-config', async (event, { pageIndex, buttonIndex, config }) => {
|
||||
pageManager.setButtonConfig(pageIndex, buttonIndex, config);
|
||||
await configManager.save();
|
||||
if (pageIndex === pageManager.getCurrentPageIndex()) {
|
||||
await loupedeckDevice.renderButtonIndex(buttonIndex, config);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('reset-button-config', async (event, { pageIndex, buttonIndex }) => {
|
||||
const defaultConfig = pageManager.resetButtonConfig(pageIndex, buttonIndex);
|
||||
if (!defaultConfig) return null;
|
||||
await configManager.save();
|
||||
if (pageIndex === pageManager.getCurrentPageIndex()) {
|
||||
await loupedeckDevice.renderButtonIndex(buttonIndex, defaultConfig);
|
||||
}
|
||||
return defaultConfig;
|
||||
});
|
||||
|
||||
ipcMain.handle('swap-button-configs', async (event, { pageIndex, sourceIndex, targetIndex }) => {
|
||||
const result = pageManager.swapButtonConfigs(pageIndex, sourceIndex, targetIndex);
|
||||
if (!result) return null;
|
||||
await configManager.save();
|
||||
if (pageIndex === pageManager.getCurrentPageIndex()) {
|
||||
await loupedeckDevice.renderButtonIndex(sourceIndex, result.source);
|
||||
await loupedeckDevice.renderButtonIndex(targetIndex, result.target);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
ipcMain.handle('set-knob-config', async (event, { knobIndex, config }) => {
|
||||
pageManager.setKnobConfig(knobIndex, config);
|
||||
await configManager.save();
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('reset-knob-config', async (event, { knobIndex }) => {
|
||||
const defaultConfig = pageManager.resetKnobConfig(knobIndex);
|
||||
if (!defaultConfig) return null;
|
||||
await configManager.save();
|
||||
if (loupedeckDevice && loupedeckDevice.connected) {
|
||||
await loupedeckDevice.renderSideDisplays();
|
||||
}
|
||||
return defaultConfig;
|
||||
});
|
||||
|
||||
ipcMain.handle('add-page', async () => {
|
||||
const newPage = pageManager.addPage();
|
||||
await configManager.save();
|
||||
return newPage;
|
||||
});
|
||||
|
||||
ipcMain.handle('rename-page', async (event, { pageIndex, name }) => {
|
||||
pageManager.renamePage(pageIndex, name);
|
||||
await configManager.save();
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('set-page-focus-config', async (event, { pageIndex, config }) => {
|
||||
const updated = pageManager.setPageFocusConfig(pageIndex, config);
|
||||
if (!updated) return false;
|
||||
await configManager.save();
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('delete-page', async (event, pageIndex) => {
|
||||
pageManager.deletePage(pageIndex);
|
||||
await configManager.save();
|
||||
pageManager.renderCurrentPage(loupedeckDevice);
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('switch-page', async (event, pageIndex) => {
|
||||
pageManager.switchPage(pageIndex);
|
||||
await pageManager.renderCurrentPage(loupedeckDevice);
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('select-image', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile'],
|
||||
filters: [{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif'] }]
|
||||
});
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
return result.filePaths[0];
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
ipcMain.handle('read-image-data', async (event, filePath) => {
|
||||
if (!filePath) return null;
|
||||
try {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const mime = ext === '.png'
|
||||
? 'image/png'
|
||||
: (ext === '.jpg' || ext === '.jpeg'
|
||||
? 'image/jpeg'
|
||||
: (ext === '.gif' ? 'image/gif' : 'application/octet-stream'));
|
||||
const data = await fs.readFile(filePath);
|
||||
return `data:${mime};base64,${data.toString('base64')}`;
|
||||
} catch (error) {
|
||||
console.error('Could not read image:', error.message);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('export-config', async () => {
|
||||
const result = await dialog.showSaveDialog(mainWindow, {
|
||||
defaultPath: 'packcontrol-config.json',
|
||||
filters: [{ name: 'JSON', extensions: ['json'] }]
|
||||
});
|
||||
if (!result.canceled) {
|
||||
await configManager.exportTo(result.filePath);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
ipcMain.handle('import-config', async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile'],
|
||||
filters: [{ name: 'JSON', extensions: ['json'] }]
|
||||
});
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
await configManager.importFrom(result.filePaths[0]);
|
||||
pageManager.reload(configManager);
|
||||
pageManager.renderCurrentPage(loupedeckDevice);
|
||||
return configManager.getConfig();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
ipcMain.handle('reconnect-device', async () => {
|
||||
await loupedeckDevice.disconnect();
|
||||
await loupedeckDevice.connect();
|
||||
});
|
||||
|
||||
ipcMain.handle('set-setting', async (event, { key, value }) => {
|
||||
const previousAccent = key === 'accentColor' ? configManager.getSetting('accentColor') : null;
|
||||
configManager.setSetting(key, value);
|
||||
if (key === 'accentColor') {
|
||||
const config = configManager.getConfig();
|
||||
if (Array.isArray(config.pages)) {
|
||||
config.pages.forEach((page) => {
|
||||
if (!page || !Array.isArray(page.buttons)) return;
|
||||
page.buttons.forEach((button) => {
|
||||
if (!button) return;
|
||||
if (button.borderColorAuto === undefined) {
|
||||
if (button.borderColor && previousAccent && button.borderColor.toLowerCase() !== previousAccent.toLowerCase()) {
|
||||
button.borderColorAuto = false;
|
||||
return;
|
||||
}
|
||||
button.borderColorAuto = true;
|
||||
}
|
||||
if (button.borderColorAuto !== false) {
|
||||
button.borderColor = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
config.pages.forEach((page) => {
|
||||
if (!page || !page.knobs) return;
|
||||
Object.values(page.knobs).forEach((knob) => {
|
||||
if (!knob) return;
|
||||
if (knob.knobColorAuto === undefined) {
|
||||
if (knob.knobColor && previousAccent && knob.knobColor.toLowerCase() !== previousAccent.toLowerCase()) {
|
||||
knob.knobColorAuto = false;
|
||||
return;
|
||||
}
|
||||
knob.knobColorAuto = true;
|
||||
}
|
||||
if (knob.knobColorAuto !== false) {
|
||||
knob.knobColor = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
await configManager.save();
|
||||
if (key === 'accentColor' && loupedeckDevice) {
|
||||
await loupedeckDevice.renderCurrentPage();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('set-circle-button-config', async (event, { index, config }) => {
|
||||
pageManager.setCircleButtonConfig(index, config);
|
||||
await configManager.save();
|
||||
// Farbe am Geraet direkt aktualisieren
|
||||
if (loupedeckDevice && loupedeckDevice.connected) {
|
||||
const isActive = config.pageIndex === pageManager.getCurrentPageIndex();
|
||||
const color = loupedeckDevice.getCircleButtonColor(config.pageIndex, isActive);
|
||||
try {
|
||||
await loupedeckDevice.device.setButtonColor({ id: index, color });
|
||||
} catch (e) {
|
||||
// Ignorieren
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
ipcMain.handle('render-page', async () => {
|
||||
if (loupedeckDevice) {
|
||||
await loupedeckDevice.renderCurrentPage();
|
||||
}
|
||||
});
|
||||
|
||||
// Fenstersteuerung
|
||||
ipcMain.handle('window-minimize', () => {
|
||||
mainWindow.minimize();
|
||||
});
|
||||
|
||||
ipcMain.handle('window-maximize', () => {
|
||||
if (mainWindow.isMaximized()) {
|
||||
mainWindow.unmaximize();
|
||||
} else {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('window-close', () => {
|
||||
mainWindow.close();
|
||||
});
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
createWindow();
|
||||
|
||||
// Tray nur bauen, wenn das Icon da ist
|
||||
try {
|
||||
createTray();
|
||||
} catch (e) {
|
||||
console.log('Tray icon not found, skipping tray');
|
||||
}
|
||||
|
||||
await initializeLoupedeck();
|
||||
await initializePedal();
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('before-quit', async () => {
|
||||
app.isQuitting = true;
|
||||
if (loupedeckDevice) {
|
||||
await loupedeckDevice.disconnect();
|
||||
}
|
||||
if (pedalDevice) {
|
||||
await pedalDevice.disconnect();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user