/* * Copyright: (c) 2024, Alex Kaul * GNU General Public License v3.0 or later (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) */ import { join } from 'node:path'; import { hostFreeterApp, schemeFreeterFile } from '@common/infra/network'; import { channelPrefix } from '@common/ipc/ipc'; import { createIpcMain } from '@/infra/ipcMain/ipcMain'; import { app } from 'electron'; import { createRendererWindow } from '@/infra/browserWindow/browserWindow'; import { createIpcMainEventValidator } from '@/infra/ipcMain/ipcMainEventValidator'; import { registerAppFileProtocol } from '@/infra/protocolHandler/registerAppFileProtocol'; import { registerControllers } from '@/controllers/controller'; import { createAppDataStorageControllers } from '@/controllers/appDataStorage'; import { createGetTextFromAppDataStorageUseCase } from '@/application/useCases/appDataStorage/getTextFromAppDataStorage'; import { createSetTextInAppDataStorageUseCase } from '@/application/useCases/appDataStorage/setTextInAppDataStorage'; import { copyFileDataStorage, createFileDataStorage } from '@/infra/dataStorage/fileDataStorage'; import { createContextMenuControllers } from '@/controllers/contextMenu'; import { createPopupContextMenuUseCase } from '@/application/useCases/contextMenu/popupContextMenu'; import { createContextMenuProvider } from '@/infra/contextMenuProvider/contextMenuProvider'; import { createClipboardControllers } from '@/controllers/clipboard'; import { createShellControllers } from '@/controllers/shell'; import { createWriteTextIntoClipboardUseCase } from '@/application/useCases/clipboard/writeTextIntoClipboard'; import { createClipboardProvider } from '@/infra/clipboardProvider/clipboardProvider'; import { createOpenExternalUrlUseCase } from '@/application/useCases/shell/openExternalUrl'; import { createShellProvider } from '@/infra/shellProvider/shellProvider'; import { createProcessControllers } from '@/controllers/process'; import { createGetProcessInfoUseCase } from '@/application/useCases/process/getProcessInfo'; import { createProcessProvider } from '@/infra/processProvider/processProvider'; import { createWriteBookmarkIntoClipboardUseCase } from '@/application/useCases/clipboard/writeBookmarkIntoClipboard'; import { createObjectManager } from '@common/base/objectManager'; import { createGetTextFromWidgetDataStorageUseCase } from '@/application/useCases/widgetDataStorage/getTextFromWidgetDataStorage'; import { createSetTextInWidgetDataStorageUseCase } from '@/application/useCases/widgetDataStorage/setTextInWidgetDataStorage'; import { createWidgetDataStorageControllers } from '@/controllers/widgetDataStorage'; import { createDeleteInWidgetDataStorageUseCase } from '@/application/useCases/widgetDataStorage/deleteInWidgetDataStorage'; import { createClearWidgetDataStorageUseCase } from '@/application/useCases/widgetDataStorage/clearWidgetDataStorage'; import { createGetKeysFromWidgetDataStorageUseCase } from '@/application/useCases/widgetDataStorage/getKeysFromWidgetDataStorage'; import { createDialogControllers } from '@/controllers/dialog'; import { createShowMessageBoxUseCase } from '@/application/useCases/dialog/showMessageBox'; import { createDialogProvider } from '@/infra/dialogProvider/dialogProvider'; import { createAppMenuControllers } from '@/controllers/appMenu'; import { createAppMenuProvider } from '@/infra/appMenuProvider/appMenuProvider'; import { createSetAppMenuUseCase } from '@/application/useCases/appMenu/setAppMenu'; import { createSetAppMenuAutoHideUseCase } from '@/application/useCases/appMenu/setAppMenuAutoHide'; import { createWindowStore } from '@/data/windowStore'; import { createWindowStateStorage } from '@/data/windowStateStorage'; import { setTextOnlyIfChanged } from '@common/infra/dataStorage/setTextOnlyIfChanged'; import { withJson } from '@common/infra/dataStorage/withJson'; import { createGetWindowStateUseCase } from '@/application/useCases/browserWindow/getWindowState'; import { createSetWindowStateUseCase } from '@/application/useCases/browserWindow/setWindowState'; import { BrowserWindow } from '@/application/interfaces/browserWindow'; import { createGlobalShortcutControllers } from '@/controllers/globalShortcut'; import { createSetMainShortcutUseCase } from '@/application/useCases/globalShortcut/setMainShortcut'; import { createGlobalShortcutProvider } from '@/infra/globalShortcut/globalShortcutProvider'; import { createTrayProvider } from '@/infra/trayProvider/trayProvider'; import { createInitTrayUseCase } from '@/application/useCases/tray/initTray'; import { createSetTrayMenuUseCase } from '@/application/useCases/tray/setTrayMenu'; import { createTrayMenuControllers } from '@/controllers/trayMenu'; import { createBrowserWindowControllers } from '@/controllers/browserWindow'; import { createShowBrowserWindowUseCase } from '@/application/useCases/browserWindow/showBrowserWindow'; import { createShowOpenFileDialogUseCase } from '@/application/useCases/dialog/showOpenFileDialog'; import { createShowSaveFileDialogUseCase } from '@/application/useCases/dialog/showSaveFileDialog'; import { createShowOpenDirDialogUseCase } from '@/application/useCases/dialog/showOpenDirDialog'; import { createTerminalControllers } from '@/controllers/terminal'; import { createExecCmdLinesInTerminalUseCase } from '@/application/useCases/terminal/execCmdLinesInTerminal'; import { createAppsProvider } from '@/infra/appsProvider/appsProvider'; import { createChildProcessProvider } from '@/infra/childProcessProvider/childProcessProvider'; import { createOpenPathUseCase } from '@/application/useCases/shell/openPath'; import { createCopyWidgetDataStorageUseCase } from '@/application/useCases/widgetDataStorage/copyWidgetDataStorage'; import { createOpenAppUseCase } from '@/application/useCases/shell/openApp'; let appWindow: BrowserWindow | null = null; // ref to the app window if (!app.requestSingleInstanceLock()) { // there is another instance of the app running app.quit(); } { app.on('second-instance', (_event, _commandLine, _workingDirectory, _additionalData) => { if (appWindow) { if (!appWindow.isVisible()) { appWindow.show(); } if (appWindow.isMinimized()) { appWindow.restore() } appWindow.focus() } }) const globalShortcutProvider = createGlobalShortcutProvider(); const processProvider = createProcessProvider(); const processInfo = processProvider.getProcessInfo(); const { isDevMode } = processInfo; registerAppFileProtocol(isDevMode); // Some keypoints for the user-agent spoofing. // 1. User agent cannot be modified on per website basis, as it will cause // failed verifications on captcha-less web challenge services (which // require the same UA for both the webpage and web worker services) // This is the highest priority as there are many websites depending // on the web challenge. // 2. If it will be needed in the future to add some flexibility in the // UA control, then it might be done by changing UA on per-session basis, // in that case both the webpage and the web worker will share the same UA. // 3. 'Electron' in the user agent causes "unsupported browser" error on some websites. // 4. 'Electron' removal causes issues on some websites (such as google's ones). // For such websites we have to add exceptions and use the original UA. // 5. Currently the original UA for the excepted websites is set on per-website // basis. This might break someday (see point 1), in that case consider // implementing the UA change on per-session basis (see point 2). const uaOriginal = app.userAgentFallback; app.userAgentFallback = app.userAgentFallback.replace(/[Ee]lectron.*?\s/g, ''); app.on('will-quit', () => { // Unregister global shortcuts globalShortcutProvider.destroy(); }) app.whenReady().then(async () => { const ipcMainEventValidator = createIpcMainEventValidator(channelPrefix, hostFreeterApp); const ipcMain = createIpcMain(ipcMainEventValidator); const appDataStorage = await createFileDataStorage('string', join(app.getPath('appData'), 'freeter2', 'freeter-data')); const getTextFromAppDataStorageUseCase = createGetTextFromAppDataStorageUseCase({ appDataStorage }); const setTextInAppDataStorageUseCase = createSetTextInAppDataStorageUseCase({ appDataStorage }); const getWidgetDataStoragePath = (id: string) => join(app.getPath('appData'), 'freeter2', 'freeter-data', 'widgets', id); const widgetDataStorageManager = createObjectManager( (id) => createFileDataStorage('string', getWidgetDataStoragePath(id)), (fromId, toId) => copyFileDataStorage(getWidgetDataStoragePath(fromId), getWidgetDataStoragePath(toId)) ); const getTextFromWidgetDataStorageUseCase = createGetTextFromWidgetDataStorageUseCase({ widgetDataStorageManager }); const setTextInWidgetDataStorageUseCase = createSetTextInWidgetDataStorageUseCase({ widgetDataStorageManager }); const deleteInWidgetDataStorageUseCase = createDeleteInWidgetDataStorageUseCase({ widgetDataStorageManager }); const clearWidgetDataStorageUseCase = createClearWidgetDataStorageUseCase({ widgetDataStorageManager }); const getKeysFromWidgetDataStorageUseCase = createGetKeysFromWidgetDataStorageUseCase({ widgetDataStorageManager }); const copyWidgetDataStorageUseCase = createCopyWidgetDataStorageUseCase({ widgetDataStorageManager }); const contextMenuProvider = createContextMenuProvider(); const popupContextMenuUseCase = createPopupContextMenuUseCase({ contextMenuProvider }); const clipboardProvider = createClipboardProvider(); const writeBookmarkIntoClipboardUseCase = createWriteBookmarkIntoClipboardUseCase({ clipboardProvider }); const writeTextIntoClipboardUseCase = createWriteTextIntoClipboardUseCase({ clipboardProvider }); const shellProvider = createShellProvider(); const openExternalUrlUseCase = createOpenExternalUrlUseCase({ shellProvider }); const openPathUseCase = createOpenPathUseCase({ shellProvider }) const getProcessInfoUseCase = createGetProcessInfoUseCase({ processProvider }); const { isLinux } = await getProcessInfoUseCase(); const dialogProvider = createDialogProvider(); const dialogShowMessageBoxUseCase = createShowMessageBoxUseCase({ dialogProvider }); const showOpenFileDialogUseCase = createShowOpenFileDialogUseCase({ dialogProvider }); const showSaveFileDialogUseCase = createShowSaveFileDialogUseCase({ dialogProvider }); const showOpenDirDialogUseCase = createShowOpenDirDialogUseCase({ dialogProvider }); const appMenuProvider = createAppMenuProvider(); const setAppMenuUseCase = createSetAppMenuUseCase({ appMenuProvider }); const setAppMenuAutoHideUseCase = createSetAppMenuAutoHideUseCase({ appMenuProvider }) const setMainShortcutUseCase = createSetMainShortcutUseCase({ globalShortcutProvider }); const trayProvider = createTrayProvider(join(app.getAppPath(), 'assets', 'app-icons', '16.png')); const setTrayMenuUseCase = createSetTrayMenuUseCase({ trayProvider }); const initTrayUseCase = createInitTrayUseCase({ trayProvider, setTrayMenuUseCase }); const showBrowserWindowUseCase = createShowBrowserWindowUseCase(); const appsProvider = createAppsProvider(); const childProcessProvider = createChildProcessProvider(); const execCmdLinesInTerminalUseCase = createExecCmdLinesInTerminalUseCase({ appsProvider, childProcessProvider, processProvider }) const openAppUseCase = createOpenAppUseCase({ childProcessProvider, processProvider }) registerControllers(ipcMain, [ ...createAppDataStorageControllers({ getTextFromAppDataStorageUseCase, setTextInAppDataStorageUseCase }), ...createWidgetDataStorageControllers({ getTextFromWidgetDataStorageUseCase, setTextInWidgetDataStorageUseCase, clearWidgetDataStorageUseCase, deleteInWidgetDataStorageUseCase, getKeysFromWidgetDataStorageUseCase, copyWidgetDataStorageUseCase, }), ...createContextMenuControllers({ popupContextMenuUseCase }), ...createClipboardControllers({ writeBookmarkIntoClipboardUseCase, writeTextIntoClipboardUseCase }), ...createShellControllers({ openExternalUrlUseCase, openPathUseCase, openAppUseCase }), ...createProcessControllers({ getProcessInfoUseCase }), ...createDialogControllers({ showMessageBoxUseCase: dialogShowMessageBoxUseCase, showOpenDirDialogUseCase, showOpenFileDialogUseCase, showSaveFileDialogUseCase }), ...createAppMenuControllers({ setAppMenuUseCase, setAppMenuAutoHideUseCase }), ...createGlobalShortcutControllers({ setMainShortcutUseCase }), ...createTrayMenuControllers({ setTrayMenuUseCase }), ...createBrowserWindowControllers({ showBrowserWindowUseCase }), ...createTerminalControllers({ execCmdLinesInTerminalUseCase }) ]) const [windowStore] = createWindowStore({ stateStorage: createWindowStateStorage( setTextOnlyIfChanged(withJson(appDataStorage)) ) }, { h: 0, w: 0, x: 0, y: 0, isFull: false, isMaxi: false, isMini: false }, () => { const getWindowStateUseCase = createGetWindowStateUseCase({ windowStore }) const setWindowStateUseCase = createSetWindowStateUseCase({ windowStore }) appWindow = createRendererWindow( `${__dirname}/preload.js`, `${schemeFreeterFile}://${hostFreeterApp}/index.html`, isLinux ? join(app.getAppPath(), 'assets', 'app-icons', '256.png') : undefined, uaOriginal, { getWindowStateUseCase, setWindowStateUseCase }, { devTools: isDevMode, } ) app.on('browser-window-created', (_e, win) => { // Disable menu in child windows if (win !== appWindow) { win.removeMenu(); } }); initTrayUseCase(appWindow); }) }); }