import { app, BrowserWindow, Menu, Tray, nativeImage } from 'electron' import { electronApp, optimizer } from '@electron-toolkit/utils' import { createAppWindow, startApplication } from './app' import './ipcs/ipcDatabase' import './ipcs/ipcLogger' import './ipcs/ipcFilestorage' import './ipcs/ipcUpdate' import './ipcs/ipcNotification' import './ipcs/ipcDevice' import './ipcs/ipcCore' import './ipcs/ipcRuntime' import { join } from 'path' import { Logger } from './logger' const lockInstance = app.requestSingleInstanceLock() let tray: Tray | null = null const size = process.platform === 'darwin' ? 18 : 22 const logger = Logger('main') const icon = nativeImage .createFromPath(join(__dirname, '../../resources/R.png')) .resize({ width: size, height: size }) if (!lockInstance) { app.quit() process.exit(0) } process.on('unhandledRejection', (reason) => { logger.log(`main thread error, reason: ${reason}`) }) app.disableHardwareAcceleration() app.on('second-instance', () => { const allWindows = BrowserWindow.getAllWindows() if (allWindows.length) { const mainWindow = allWindows[0] if (mainWindow.isMinimized()) mainWindow.restore() if (!mainWindow.isVisible()) mainWindow.show() mainWindow.focus() } }) export const restoreApplicationAfterClickOnTrayOrDock = () => { const allWindows = BrowserWindow.getAllWindows() if (allWindows.length > 0) { const mainWindow = allWindows[0] if (mainWindow.isMinimized()) { mainWindow.restore() return } if (!mainWindow.isVisible()) { mainWindow.show() } mainWindow.focus() } else { createAppWindow() } } app.whenReady().then(async () => { electronApp.setAppUserModelId('Rosetta') // Убираем File/View и оставляем только app + минимальный Edit (roles) if (process.platform === 'darwin') { const minimalMenu = Menu.buildFromTemplate([ { label: app.name, submenu: [ { role: 'about' }, { type: 'separator' }, { role: 'hide' }, { role: 'hideOthers' }, { role: 'unhide' }, { type: 'separator' }, { role: 'quit' } ] }, { label: 'Edit', submenu: [ { role: 'undo' }, { role: 'redo' }, { type: 'separator' }, { role: 'cut' }, { role: 'copy' }, { role: 'paste' }, { role: 'pasteAndMatchStyle' }, { role: 'delete' }, { role: 'selectAll' } ] } ]) Menu.setApplicationMenu(minimalMenu) } else { Menu.setApplicationMenu(null) } tray = new Tray(icon) const contextMenu = Menu.buildFromTemplate([ { label: 'Open App', click: () => restoreApplicationAfterClickOnTrayOrDock() }, { label: 'Quit', click: () => app.quit() } ]) tray.setContextMenu(contextMenu) tray.setToolTip('Rosetta') tray.on('click', () => { restoreApplicationAfterClickOnTrayOrDock() }) startApplication() const isDevBuild = !app.isPackaged || process.env.NODE_ENV === 'development' || Boolean(process.env.ELECTRON_RENDERER_URL) app.on('browser-window-created', (_, window) => { // В production оставляем стандартную защиту шорткатов if (!isDevBuild) { optimizer.watchWindowShortcuts(window) return } // В dev явно разрешаем Ctrl+R и Cmd+R для перезагрузки, так как в режиме разработки это часто нужно window.webContents.on('before-input-event', (event, input) => { const key = input.key?.toLowerCase?.() ?? '' const isReload = input.type === 'keyDown' && (input.meta || input.control) && key === 'r' if (isReload) { event.preventDefault() window.webContents.reloadIgnoringCache() } }) }) app.on('activate', () => { restoreApplicationAfterClickOnTrayOrDock() }) }) app.on('window-all-closed', () => { if (process.platform === 'darwin') { app.hide() } })