Simple monitor for displaying MIDI status for digital pianos.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

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