Simple monitor for displaying MIDI status for digital pianos.
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

246 wiersze
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. })