Simple monitor for displaying MIDI status for digital pianos.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

161 lines
4.1 KiB

  1. import { app, shell, BrowserWindow, ipcMain } from 'electron'
  2. import { join } from 'path'
  3. import { readFile, stat, writeFile } from "fs/promises";
  4. import { electronApp, optimizer, is } from '@electron-toolkit/utils'
  5. import icon from '../../resources/icon.png?asset'
  6. interface Config {
  7. range: string
  8. queryDeviceKey: string
  9. scaleFactor: string
  10. }
  11. const defaultConfig: Config = {
  12. range: '21|108',
  13. queryDeviceKey: '',
  14. scaleFactor: '1'
  15. }
  16. const configPath = './config.json'
  17. const naturalKeyWidth = 20
  18. const pedalBoardWidth = 239
  19. const height = 175
  20. const getNaturalKeys = (range: string): number => {
  21. const [startKeyRaw, endKeyRaw] = range.split('|')
  22. const startKey = Number(startKeyRaw)
  23. const endKey = Number(endKeyRaw)
  24. let naturalKeys = 0
  25. for (let i = startKey; i <= endKey; i += 1) {
  26. switch (i % 12) {
  27. case 0:
  28. case 2:
  29. case 4:
  30. case 5:
  31. case 7:
  32. case 9:
  33. case 11:
  34. naturalKeys += 1
  35. break
  36. default:
  37. break
  38. }
  39. }
  40. return naturalKeys
  41. }
  42. function createWindow(config: Config): void {
  43. // Create the browser window.
  44. const naturalKeys = getNaturalKeys(config.range)
  45. const width = naturalKeys * naturalKeyWidth + pedalBoardWidth
  46. const mainWindow = new BrowserWindow({
  47. width,
  48. height,
  49. show: false,
  50. autoHideMenuBar: true,
  51. ...(process.platform === 'linux' ? { icon } : {}),
  52. webPreferences: {
  53. preload: join(__dirname, '../preload/index.js'),
  54. sandbox: false,
  55. devTools: is.dev
  56. },
  57. maximizable: is.dev,
  58. minimizable: false,
  59. maxHeight: is.dev ? undefined : height,
  60. minHeight: is.dev ? undefined : height,
  61. minWidth: is.dev ? undefined : width,
  62. maxWidth: is.dev ? undefined : width,
  63. fullscreenable: false,
  64. resizable: is.dev,
  65. useContentSize: true,
  66. })
  67. mainWindow.on('ready-to-show', () => {
  68. mainWindow.show()
  69. })
  70. mainWindow.webContents.setWindowOpenHandler((details) => {
  71. void shell.openExternal(details.url)
  72. return { action: 'deny' }
  73. })
  74. let search: URLSearchParams
  75. if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
  76. const url = new URL(process.env['ELECTRON_RENDERER_URL'])
  77. search = new URLSearchParams(url.searchParams)
  78. Object.entries(config).forEach(([key, value]) => {
  79. search.set(key, value)
  80. })
  81. url.search = search.toString()
  82. void mainWindow.loadURL(url.toString())
  83. return
  84. }
  85. search = new URLSearchParams()
  86. Object.entries(config).forEach(([key, value]) => {
  87. search.set(key, value)
  88. })
  89. void mainWindow.loadFile(join(__dirname, '../renderer/index.html'), {
  90. search: search.toString()
  91. })
  92. }
  93. app.whenReady().then(async () => {
  94. const effectiveConfig = { ...defaultConfig }
  95. try {
  96. const theStat = await stat(configPath)
  97. if (theStat.isDirectory()) {
  98. return
  99. }
  100. const jsonRaw = await readFile(configPath, 'utf-8')
  101. const json = JSON.parse(jsonRaw)
  102. Object.entries(json).forEach(([key, value]) => {
  103. effectiveConfig[key] = value
  104. })
  105. } catch {
  106. await writeFile(configPath, JSON.stringify(defaultConfig))
  107. }
  108. electronApp.setAppUserModelId('sh.modal.pianomidimonitor')
  109. app.on('browser-window-created', (_, window) => {
  110. optimizer.watchWindowShortcuts(window)
  111. })
  112. ipcMain.on('querydevicekeychange', async (_event, value) => {
  113. effectiveConfig.queryDeviceKey = value
  114. await writeFile(configPath, JSON.stringify(effectiveConfig))
  115. })
  116. ipcMain.on('scalefactorchange', async (_event, value) => {
  117. effectiveConfig.scaleFactor = value
  118. await writeFile(configPath, JSON.stringify(effectiveConfig))
  119. })
  120. ipcMain.on('rangechange', async (event, value) => {
  121. effectiveConfig.range = value
  122. await writeFile(configPath, JSON.stringify(effectiveConfig))
  123. const webContents = event.sender
  124. const win = BrowserWindow.fromWebContents(webContents)
  125. if (!win) {
  126. return
  127. }
  128. win.close()
  129. createWindow(effectiveConfig)
  130. })
  131. createWindow(effectiveConfig)
  132. app.on('activate', function () {
  133. if (BrowserWindow.getAllWindows().length === 0) {
  134. createWindow(effectiveConfig)
  135. }
  136. })
  137. })
  138. app.on('window-all-closed', () => {
  139. if (process.platform !== 'darwin') {
  140. app.quit()
  141. }
  142. })