Simple monitor for displaying MIDI status for digital pianos.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 

160 行
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 = 200
  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. })
  66. mainWindow.on('ready-to-show', () => {
  67. mainWindow.show()
  68. })
  69. mainWindow.webContents.setWindowOpenHandler((details) => {
  70. void shell.openExternal(details.url)
  71. return { action: 'deny' }
  72. })
  73. let search: URLSearchParams
  74. if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
  75. const url = new URL(process.env['ELECTRON_RENDERER_URL'])
  76. search = new URLSearchParams(url.searchParams)
  77. Object.entries(config).forEach(([key, value]) => {
  78. search.set(key, value)
  79. })
  80. url.search = search.toString()
  81. void mainWindow.loadURL(url.toString())
  82. return
  83. }
  84. search = new URLSearchParams()
  85. Object.entries(config).forEach(([key, value]) => {
  86. search.set(key, value)
  87. })
  88. void mainWindow.loadFile(join(__dirname, '../renderer/index.html'), {
  89. search: search.toString()
  90. })
  91. }
  92. app.whenReady().then(async () => {
  93. const effectiveConfig = { ...defaultConfig }
  94. try {
  95. const theStat = await stat(configPath)
  96. if (theStat.isDirectory()) {
  97. return
  98. }
  99. const jsonRaw = await readFile(configPath, 'utf-8')
  100. const json = JSON.parse(jsonRaw)
  101. Object.entries(json).forEach(([key, value]) => {
  102. effectiveConfig[key] = value
  103. })
  104. } catch {
  105. await writeFile(configPath, JSON.stringify(defaultConfig))
  106. }
  107. electronApp.setAppUserModelId('sh.modal.pianomidimonitor')
  108. app.on('browser-window-created', (_, window) => {
  109. optimizer.watchWindowShortcuts(window)
  110. })
  111. ipcMain.on('querydevicekeychange', async (_event, value) => {
  112. effectiveConfig.queryDeviceKey = value
  113. await writeFile(configPath, JSON.stringify(effectiveConfig))
  114. })
  115. ipcMain.on('scalefactorchange', async (_event, value) => {
  116. effectiveConfig.scaleFactor = value
  117. await writeFile(configPath, JSON.stringify(effectiveConfig))
  118. })
  119. ipcMain.on('rangechange', async (event, value) => {
  120. effectiveConfig.range = value
  121. await writeFile(configPath, JSON.stringify(effectiveConfig))
  122. const webContents = event.sender
  123. const win = BrowserWindow.fromWebContents(webContents)
  124. if (!win) {
  125. return
  126. }
  127. win.close()
  128. createWindow(effectiveConfig)
  129. })
  130. createWindow(effectiveConfig)
  131. app.on('activate', function () {
  132. if (BrowserWindow.getAllWindows().length === 0) {
  133. createWindow(effectiveConfig)
  134. }
  135. })
  136. })
  137. app.on('window-all-closed', () => {
  138. if (process.platform !== 'darwin') {
  139. app.quit()
  140. }
  141. })