import { app, 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' import { getNaturalKeys } from './utils/display' import { Stats } from 'fs' const configPath = './config.json' const height = 160 const ALLOWED_PERMISSIONS = ['midi'] const defineWindowCommonSecurity = (browserWindow: BrowserWindow): void => { browserWindow.webContents.setWindowOpenHandler(() => { return { action: 'deny' } }) } const addWindowEvents = (browserWindow: BrowserWindow): void => { browserWindow.on('ready-to-show', () => { browserWindow.show() }) } const addMidiPermissions = (browserWindow: BrowserWindow): void => { browserWindow.webContents.session.setPermissionRequestHandler( (_webContents, permission, callback) => { callback(ALLOWED_PERMISSIONS.includes(permission)) } ) browserWindow.webContents.session.setPermissionCheckHandler((_webContents, permission) => { return ALLOWED_PERMISSIONS.includes(permission) }) } const assignWindowView = async ( browserWindow: BrowserWindow, routeName: string, config: Config ): Promise => { let search = new URLSearchParams() let url: URL | undefined if (is.dev && process.env['ELECTRON_RENDERER_URL']) { 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', routeName) if (typeof url !== 'undefined') { url.search = search.toString() await browserWindow.loadURL(url.toString()) return browserWindow } await browserWindow.loadFile(join(__dirname, '../renderer/index.html'), { search: search.toString() }) return browserWindow } const createMainWindow = async (config: Config): Promise => { 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 }, title: 'Piano MIDI Monitor', 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 }) defineWindowCommonSecurity(mainWindow) addMidiPermissions(mainWindow) addWindowEvents(mainWindow) return assignWindowView(mainWindow, 'main', config) } const createSettingsWindow = async ( parent: BrowserWindow, config: Config ): Promise => { const settingsWindow = new BrowserWindow({ width: 360, height: 640, show: false, autoHideMenuBar: true, modal: true, ...(process.platform === 'linux' ? { icon } : {}), webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: false, devTools: is.dev }, title: 'Settings', maximizable: false, minimizable: false, fullscreenable: false, resizable: false, useContentSize: true, parent }) defineWindowCommonSecurity(settingsWindow) addMidiPermissions(settingsWindow) addWindowEvents(settingsWindow) return assignWindowView(settingsWindow, 'settings', config) } const ensureConfig = async (defaultConfig: Config): Promise => { const effectiveConfig = { ...defaultConfig } let theStat: Stats try { theStat = await stat(configPath) } catch { await writeFile(configPath, JSON.stringify(defaultConfig)) return effectiveConfig } if (theStat.isDirectory()) { throw new Error('Config path is a directory.') } try { 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)) } return effectiveConfig } const main = async (): Promise => { const effectiveConfig = await ensureConfig(defaultConfig) app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit() } }) await app.whenReady() electronApp.setAppUserModelId('sh.modal.pianomidimonitor') app.on('browser-window-created', (_, window) => { optimizer.watchWindowShortcuts(window) }) 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) } }) } void main()