|
@@ -1,38 +1,71 @@ |
|
|
import { app, shell, BrowserWindow, ipcMain } from 'electron' |
|
|
|
|
|
|
|
|
import { app, BrowserWindow, ipcMain } from 'electron' |
|
|
import { join } from 'path' |
|
|
import { join } from 'path' |
|
|
import { readFile, stat, writeFile } from 'fs/promises' |
|
|
import { readFile, stat, writeFile } from 'fs/promises' |
|
|
import { electronApp, optimizer, is } from '@electron-toolkit/utils' |
|
|
import { electronApp, optimizer, is } from '@electron-toolkit/utils' |
|
|
import icon from '../../resources/icon.png?asset' |
|
|
import icon from '../../resources/icon.png?asset' |
|
|
import { Config, defaultConfig } from '../common/config' |
|
|
import { Config, defaultConfig } from '../common/config' |
|
|
|
|
|
import { getNaturalKeys } from './utils/display' |
|
|
|
|
|
import { Stats } from 'fs' |
|
|
|
|
|
|
|
|
const configPath = './config.json' |
|
|
const configPath = './config.json' |
|
|
const height = 160 |
|
|
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 |
|
|
|
|
|
|
|
|
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<BrowserWindow> => { |
|
|
|
|
|
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 |
|
|
} |
|
|
} |
|
|
return naturalKeys |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await browserWindow.loadFile(join(__dirname, '../renderer/index.html'), { |
|
|
|
|
|
search: search.toString() |
|
|
|
|
|
}) |
|
|
|
|
|
return browserWindow |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const createMainWindow = async (config: Config): Promise<BrowserWindow> => { |
|
|
const createMainWindow = async (config: Config): Promise<BrowserWindow> => { |
|
|
// Create the browser window. |
|
|
|
|
|
const naturalKeys = getNaturalKeys(config.range) |
|
|
const naturalKeys = getNaturalKeys(config.range) |
|
|
const width = naturalKeys * Number(config.naturalKeyWidth || 20) + Number(config.scaleFactor) |
|
|
const width = naturalKeys * Number(config.naturalKeyWidth || 20) + Number(config.scaleFactor) |
|
|
const mainWindow = new BrowserWindow({ |
|
|
const mainWindow = new BrowserWindow({ |
|
@@ -46,6 +79,7 @@ const createMainWindow = async (config: Config): Promise<BrowserWindow> => { |
|
|
sandbox: false, |
|
|
sandbox: false, |
|
|
devTools: is.dev |
|
|
devTools: is.dev |
|
|
}, |
|
|
}, |
|
|
|
|
|
title: 'Piano MIDI Monitor', |
|
|
maximizable: is.dev, |
|
|
maximizable: is.dev, |
|
|
minimizable: false, |
|
|
minimizable: false, |
|
|
maxHeight: is.dev ? undefined : height, |
|
|
maxHeight: is.dev ? undefined : height, |
|
@@ -56,58 +90,57 @@ const createMainWindow = async (config: Config): Promise<BrowserWindow> => { |
|
|
resizable: is.dev, |
|
|
resizable: is.dev, |
|
|
useContentSize: true |
|
|
useContentSize: true |
|
|
}) |
|
|
}) |
|
|
|
|
|
defineWindowCommonSecurity(mainWindow) |
|
|
|
|
|
addMidiPermissions(mainWindow) |
|
|
|
|
|
addWindowEvents(mainWindow) |
|
|
|
|
|
return assignWindowView(mainWindow, 'main', config) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
mainWindow.on('ready-to-show', () => { |
|
|
|
|
|
mainWindow.show() |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
mainWindow.webContents.setWindowOpenHandler((details) => { |
|
|
|
|
|
void shell.openExternal(details.url) |
|
|
|
|
|
return { action: 'deny' } |
|
|
|
|
|
|
|
|
const createSettingsWindow = async ( |
|
|
|
|
|
parent: BrowserWindow, |
|
|
|
|
|
config: Config |
|
|
|
|
|
): Promise<BrowserWindow> => { |
|
|
|
|
|
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) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
const ensureConfig = async (defaultConfig: Config): Promise<Config> => { |
|
|
|
|
|
const effectiveConfig = { ...defaultConfig } |
|
|
|
|
|
let theStat: Stats |
|
|
|
|
|
try { |
|
|
|
|
|
theStat = await stat(configPath) |
|
|
|
|
|
} catch { |
|
|
|
|
|
await writeFile(configPath, JSON.stringify(defaultConfig)) |
|
|
|
|
|
return effectiveConfig |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (theStat.isDirectory()) { |
|
|
|
|
|
throw new Error('Config path is a directory.') |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
app.whenReady().then(async () => { |
|
|
|
|
|
const effectiveConfig = { ...defaultConfig } |
|
|
|
|
|
try { |
|
|
try { |
|
|
const theStat = await stat(configPath) |
|
|
|
|
|
if (theStat.isDirectory()) { |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
const jsonRaw = await readFile(configPath, 'utf-8') |
|
|
const jsonRaw = await readFile(configPath, 'utf-8') |
|
|
const json = JSON.parse(jsonRaw) |
|
|
const json = JSON.parse(jsonRaw) |
|
|
Object.entries(json).forEach(([key, value]) => { |
|
|
Object.entries(json).forEach(([key, value]) => { |
|
@@ -117,79 +150,24 @@ app.whenReady().then(async () => { |
|
|
await writeFile(configPath, JSON.stringify(defaultConfig)) |
|
|
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<BrowserWindow> => { |
|
|
|
|
|
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' } |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
return effectiveConfig |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
settingsWindow.webContents.session.setPermissionRequestHandler( |
|
|
|
|
|
(_webContents, permission, callback) => { |
|
|
|
|
|
if (permission === 'midi') { |
|
|
|
|
|
callback(true) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const main = async (): Promise<void> => { |
|
|
|
|
|
const effectiveConfig = await ensureConfig(defaultConfig) |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
app.on('window-all-closed', () => { |
|
|
|
|
|
if (process.platform !== 'darwin') { |
|
|
|
|
|
app.quit() |
|
|
} |
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
await app.whenReady() |
|
|
|
|
|
electronApp.setAppUserModelId('sh.modal.pianomidimonitor') |
|
|
|
|
|
|
|
|
return settingsWindow |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
app.on('browser-window-created', (_, window) => { |
|
|
|
|
|
optimizer.watchWindowShortcuts(window) |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
ipcMain.on('action', async (event, value, formData) => { |
|
|
ipcMain.on('action', async (event, value, formData) => { |
|
|
const webContents = event.sender |
|
|
const webContents = event.sender |
|
@@ -236,10 +214,6 @@ app.whenReady().then(async () => { |
|
|
await createMainWindow(effectiveConfig) |
|
|
await createMainWindow(effectiveConfig) |
|
|
} |
|
|
} |
|
|
}) |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
app.on('window-all-closed', () => { |
|
|
|
|
|
if (process.platform !== 'darwin') { |
|
|
|
|
|
app.quit() |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
void main() |