Simple monitor for displaying MIDI status for digital pianos.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 

246 Zeilen
6.5 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. import { Config, defaultConfig } from '../common/config'
  7. const configPath = './config.json'
  8. const height = 160
  9. const getNaturalKeys = (range: string): number => {
  10. const [startKeyRaw, endKeyRaw] = range.split('|')
  11. const startKey = Number(startKeyRaw)
  12. const endKey = Number(endKeyRaw)
  13. let naturalKeys = 0
  14. for (let i = startKey; i <= endKey; i += 1) {
  15. switch (i % 12) {
  16. case 0:
  17. case 2:
  18. case 4:
  19. case 5:
  20. case 7:
  21. case 9:
  22. case 11:
  23. naturalKeys += 1
  24. break
  25. default:
  26. break
  27. }
  28. }
  29. return naturalKeys
  30. }
  31. const createMainWindow = async (config: Config): Promise<BrowserWindow> => {
  32. // Create the browser window.
  33. const naturalKeys = getNaturalKeys(config.range)
  34. const width = naturalKeys * Number(config.naturalKeyWidth || 20) + Number(config.scaleFactor)
  35. const mainWindow = new BrowserWindow({
  36. width,
  37. height,
  38. show: false,
  39. autoHideMenuBar: true,
  40. ...(process.platform === 'linux' ? { icon } : {}),
  41. webPreferences: {
  42. preload: join(__dirname, '../preload/index.js'),
  43. sandbox: false,
  44. devTools: is.dev
  45. },
  46. maximizable: is.dev,
  47. minimizable: false,
  48. maxHeight: is.dev ? undefined : height,
  49. minHeight: is.dev ? undefined : height,
  50. minWidth: is.dev ? undefined : width,
  51. maxWidth: is.dev ? undefined : width,
  52. fullscreenable: false,
  53. resizable: is.dev,
  54. useContentSize: true
  55. })
  56. mainWindow.on('ready-to-show', () => {
  57. mainWindow.show()
  58. })
  59. mainWindow.webContents.setWindowOpenHandler((details) => {
  60. void shell.openExternal(details.url)
  61. return { action: 'deny' }
  62. })
  63. mainWindow.webContents.session.setPermissionRequestHandler(
  64. (_webContents, permission, callback) => {
  65. if (permission === 'midi') {
  66. callback(true)
  67. return
  68. }
  69. callback(false)
  70. }
  71. )
  72. let search: URLSearchParams
  73. if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
  74. const url = new URL(process.env['ELECTRON_RENDERER_URL'])
  75. search = new URLSearchParams(url.searchParams)
  76. Object.entries(config).forEach(([key, value]) => {
  77. search.set(key, value)
  78. })
  79. search.set('window', 'main')
  80. url.search = search.toString()
  81. await mainWindow.loadURL(url.toString())
  82. return mainWindow
  83. }
  84. search = new URLSearchParams()
  85. Object.entries(config).forEach(([key, value]) => {
  86. search.set(key, value)
  87. })
  88. search.set('window', 'main')
  89. await mainWindow.loadFile(join(__dirname, '../renderer/index.html'), {
  90. search: search.toString()
  91. })
  92. return mainWindow
  93. }
  94. app.whenReady().then(async () => {
  95. const effectiveConfig = { ...defaultConfig }
  96. try {
  97. const theStat = await stat(configPath)
  98. if (theStat.isDirectory()) {
  99. return
  100. }
  101. const jsonRaw = await readFile(configPath, 'utf-8')
  102. const json = JSON.parse(jsonRaw)
  103. Object.entries(json).forEach(([key, value]) => {
  104. effectiveConfig[key] = value
  105. })
  106. } catch {
  107. await writeFile(configPath, JSON.stringify(defaultConfig))
  108. }
  109. electronApp.setAppUserModelId('sh.modal.pianomidimonitor')
  110. app.on('browser-window-created', (_, window) => {
  111. optimizer.watchWindowShortcuts(window)
  112. })
  113. const createSettingsWindow = async (
  114. parent: BrowserWindow,
  115. config: Config
  116. ): Promise<BrowserWindow> => {
  117. const settingsWindow = new BrowserWindow({
  118. width: 360,
  119. height: 640,
  120. show: false,
  121. autoHideMenuBar: true,
  122. ...(process.platform === 'linux' ? { icon } : {}),
  123. webPreferences: {
  124. preload: join(__dirname, '../preload/index.js'),
  125. sandbox: false,
  126. devTools: is.dev
  127. },
  128. maximizable: false,
  129. minimizable: false,
  130. fullscreenable: false,
  131. resizable: false,
  132. useContentSize: true,
  133. parent
  134. })
  135. settingsWindow.on('ready-to-show', () => {
  136. settingsWindow.show()
  137. })
  138. settingsWindow.webContents.setWindowOpenHandler((details) => {
  139. void shell.openExternal(details.url)
  140. return { action: 'deny' }
  141. })
  142. settingsWindow.webContents.session.setPermissionRequestHandler(
  143. (_webContents, permission, callback) => {
  144. if (permission === 'midi') {
  145. callback(true)
  146. return
  147. }
  148. callback(false)
  149. }
  150. )
  151. let search: URLSearchParams
  152. if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
  153. const url = new URL(process.env['ELECTRON_RENDERER_URL'])
  154. search = new URLSearchParams(url.searchParams)
  155. Object.entries(config).forEach(([key, value]) => {
  156. search.set(key, value)
  157. })
  158. search.set('window', 'settings')
  159. url.search = search.toString()
  160. await settingsWindow.loadURL(url.toString())
  161. return settingsWindow
  162. }
  163. search = new URLSearchParams()
  164. Object.entries(config).forEach(([key, value]) => {
  165. search.set(key, value)
  166. })
  167. search.set('window', 'settings')
  168. await settingsWindow.loadFile(join(__dirname, '../renderer/index.html'), {
  169. search: search.toString()
  170. })
  171. return settingsWindow
  172. }
  173. ipcMain.on('action', async (event, value, formData) => {
  174. const webContents = event.sender
  175. const win = BrowserWindow.fromWebContents(webContents)
  176. if (!win) {
  177. return
  178. }
  179. switch (value) {
  180. case 'showSettings': {
  181. await createSettingsWindow(win, effectiveConfig)
  182. return
  183. }
  184. case 'cancelSaveConfig': {
  185. win.close()
  186. return
  187. }
  188. case 'resetConfig': {
  189. Object.entries(defaultConfig).forEach(([key, value]) => {
  190. effectiveConfig[key] = value
  191. })
  192. await writeFile(configPath, JSON.stringify(effectiveConfig))
  193. win.close()
  194. win.getParentWindow()?.close()
  195. await createMainWindow(effectiveConfig)
  196. return
  197. }
  198. case 'saveConfig': {
  199. Object.entries(formData).forEach(([key, value]) => {
  200. effectiveConfig[key] = value
  201. })
  202. await writeFile(configPath, JSON.stringify(effectiveConfig))
  203. win.close()
  204. win.getParentWindow()?.close()
  205. await createMainWindow(effectiveConfig)
  206. return
  207. }
  208. }
  209. })
  210. await createMainWindow(effectiveConfig)
  211. app.on('activate', async () => {
  212. if (BrowserWindow.getAllWindows().length === 0) {
  213. await createMainWindow(effectiveConfig)
  214. }
  215. })
  216. })
  217. app.on('window-all-closed', () => {
  218. if (process.platform !== 'darwin') {
  219. app.quit()
  220. }
  221. })