import { app, shell, BrowserWindow, ipcMain } from 'electron' import { join } from 'path' import { readFile, stat, writeFile } from 'fs/promises' import { electronApp, optimizer, is } from '@electron-toolkit/utils' import icon from '../../resources/icon.png?asset' import { Config, defaultConfig } from '../common/config' const configPath = './config.json' const height = 160 const getNaturalKeys = (range: string): number => { const [startKeyRaw, endKeyRaw] = range.split('|') const startKey = Number(startKeyRaw) const endKey = Number(endKeyRaw) let naturalKeys = 0 for (let i = startKey; i <= endKey; i += 1) { switch (i % 12) { case 0: case 2: case 4: case 5: case 7: case 9: case 11: naturalKeys += 1 break default: break } } return naturalKeys } const createMainWindow = async (config: Config): Promise => { // Create the browser window. const naturalKeys = getNaturalKeys(config.range) const width = naturalKeys * Number(config.naturalKeyWidth || 20) + Number(config.scaleFactor) const mainWindow = new BrowserWindow({ width, height, show: false, autoHideMenuBar: true, ...(process.platform === 'linux' ? { icon } : {}), webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: false, devTools: is.dev }, maximizable: is.dev, minimizable: false, maxHeight: is.dev ? undefined : height, minHeight: is.dev ? undefined : height, minWidth: is.dev ? undefined : width, maxWidth: is.dev ? undefined : width, fullscreenable: false, resizable: is.dev, useContentSize: true }) mainWindow.on('ready-to-show', () => { mainWindow.show() }) mainWindow.webContents.setWindowOpenHandler((details) => { void shell.openExternal(details.url) return { action: 'deny' } }) mainWindow.webContents.session.setPermissionRequestHandler( (_webContents, permission, callback) => { if (permission === 'midi') { callback(true) return } callback(false) } ) let search: URLSearchParams if (is.dev && process.env['ELECTRON_RENDERER_URL']) { const url = new URL(process.env['ELECTRON_RENDERER_URL']) search = new URLSearchParams(url.searchParams) Object.entries(config).forEach(([key, value]) => { search.set(key, value) }) search.set('window', 'main') url.search = search.toString() await mainWindow.loadURL(url.toString()) return mainWindow } search = new URLSearchParams() Object.entries(config).forEach(([key, value]) => { search.set(key, value) }) search.set('window', 'main') await mainWindow.loadFile(join(__dirname, '../renderer/index.html'), { search: search.toString() }) return mainWindow } app.whenReady().then(async () => { const effectiveConfig = { ...defaultConfig } try { const theStat = await stat(configPath) if (theStat.isDirectory()) { return } const jsonRaw = await readFile(configPath, 'utf-8') const json = JSON.parse(jsonRaw) Object.entries(json).forEach(([key, value]) => { effectiveConfig[key] = value }) } catch { await writeFile(configPath, JSON.stringify(defaultConfig)) } electronApp.setAppUserModelId('sh.modal.pianomidimonitor') app.on('browser-window-created', (_, window) => { optimizer.watchWindowShortcuts(window) }) const createSettingsWindow = async ( parent: BrowserWindow, config: Config ): Promise => { const settingsWindow = new BrowserWindow({ width: 360, height: 640, show: false, autoHideMenuBar: true, ...(process.platform === 'linux' ? { icon } : {}), webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: false, devTools: is.dev }, maximizable: false, minimizable: false, fullscreenable: false, resizable: false, useContentSize: true, parent }) settingsWindow.on('ready-to-show', () => { settingsWindow.show() }) settingsWindow.webContents.setWindowOpenHandler((details) => { void shell.openExternal(details.url) return { action: 'deny' } }) settingsWindow.webContents.session.setPermissionRequestHandler( (_webContents, permission, callback) => { if (permission === 'midi') { callback(true) return } callback(false) } ) let search: URLSearchParams if (is.dev && process.env['ELECTRON_RENDERER_URL']) { const url = new URL(process.env['ELECTRON_RENDERER_URL']) search = new URLSearchParams(url.searchParams) Object.entries(config).forEach(([key, value]) => { search.set(key, value) }) search.set('window', 'settings') url.search = search.toString() await settingsWindow.loadURL(url.toString()) return settingsWindow } search = new URLSearchParams() Object.entries(config).forEach(([key, value]) => { search.set(key, value) }) search.set('window', 'settings') await settingsWindow.loadFile(join(__dirname, '../renderer/index.html'), { search: search.toString() }) return settingsWindow } ipcMain.on('action', async (event, value, formData) => { const webContents = event.sender const win = BrowserWindow.fromWebContents(webContents) if (!win) { return } switch (value) { case 'showSettings': { await createSettingsWindow(win, effectiveConfig) return } case 'cancelSaveConfig': { win.close() return } case 'resetConfig': { Object.entries(defaultConfig).forEach(([key, value]) => { effectiveConfig[key] = value }) await writeFile(configPath, JSON.stringify(effectiveConfig)) win.close() win.getParentWindow()?.close() await createMainWindow(effectiveConfig) return } case 'saveConfig': { Object.entries(formData).forEach(([key, value]) => { effectiveConfig[key] = value }) await writeFile(configPath, JSON.stringify(effectiveConfig)) win.close() win.getParentWindow()?.close() await createMainWindow(effectiveConfig) return } } }) await createMainWindow(effectiveConfig) app.on('activate', async () => { if (BrowserWindow.getAllWindows().length === 0) { await createMainWindow(effectiveConfig) } }) }) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } })