diff --git a/src/backend/browser/sandbox.ts b/src/backend/browser/sandbox.ts index 7ecf1c1950..d7ec97d6e7 100644 --- a/src/backend/browser/sandbox.ts +++ b/src/backend/browser/sandbox.ts @@ -33,6 +33,32 @@ import { // TODO: base pathを設定できるようにするか、ビルド時埋め込みにする const toStaticPath = (fileName: string) => `/${fileName}`; +// FIXME: asを使わないようオーバーロードにした。オーバーロードも使わない書き方にしたい。 +function onReceivedIPCMsg< + T extends { + [K in keyof IpcSOData]: ( + event: unknown, + ...args: IpcSOData[K]["args"] + ) => Promise | IpcSOData[K]["return"]; + }, +>(listeners: T): void; +function onReceivedIPCMsg(listeners: { + [key: string]: (event: unknown, ...args: unknown[]) => unknown; +}) { + // NOTE: もしブラウザ本体からレンダラへのメッセージを実装するならこんな感じ + window.addEventListener( + "message", + ({ + data, + }: MessageEvent<{ + channel: keyof IpcSOData; + args: IpcSOData[keyof IpcSOData]["args"]; + }>) => { + listeners[data.channel]?.({}, ...data.args); + }, + ); +} + /** * Browser版のSandBox実装 * src/type/preload.tsのSandboxを変更した場合は、interfaceに追従した変更が必要 @@ -204,20 +230,7 @@ export const api: Sandbox = { // NOTE: UIの表示状態の制御のためだけなので固定値を返している return Promise.resolve(true); }, - onReceivedIPCMsg( - channel: T, - listener: (_: unknown, ...args: IpcSOData[T]["args"]) => void, - ) { - window.addEventListener("message", (event) => { - const data = event.data as { - channel: keyof IpcSOData; - args: IpcSOData[keyof IpcSOData]; - }; - if (data.channel == channel) { - listener(data.args); - } - }); - }, + onReceivedIPCMsg, closeWindow() { throw new Error(`Not supported on Browser version: closeWindow`); }, diff --git a/src/backend/electron/ipc.ts b/src/backend/electron/ipc.ts index 8ce12818e6..588af9de98 100644 --- a/src/backend/electron/ipc.ts +++ b/src/backend/electron/ipc.ts @@ -1,45 +1,69 @@ -import { ipcMain, IpcMainInvokeEvent, BrowserWindow } from "electron"; +import { + BrowserWindow, + ipcMain, + IpcMainInvokeEvent, + IpcRendererEvent, +} from "electron"; import log from "electron-log/main"; import { IpcIHData, IpcSOData } from "@/type/ipc"; -export function ipcMainHandle( - channel: T, - listener: ( - event: IpcMainInvokeEvent, - ...args: IpcIHData[T]["args"] - ) => IpcIHData[T]["return"] | Promise, -): void; -export function ipcMainHandle( - channel: string, - listener: (event: IpcMainInvokeEvent, ...args: unknown[]) => unknown, -): void { - const errorHandledListener = ( +export type IpcRendererInvoke = { + [K in keyof IpcIHData]: ( + ...args: IpcIHData[K]["args"] + ) => Promise; +}; + +export type IpcMainHandle = { + [K in keyof IpcIHData]: ( event: IpcMainInvokeEvent, - ...args: unknown[] - ) => { - try { - validateIpcSender(event); - return listener(event, ...args); - } catch (e) { - log.error(e); - } - }; - ipcMain.handle(channel, errorHandledListener); -} + ...args: IpcIHData[K]["args"] + ) => Promise | IpcIHData[K]["return"]; +}; -export function ipcMainSend( - win: BrowserWindow, - channel: T, - ...args: IpcSOData[T]["args"] +export type IpcMainSend = { + [K in keyof IpcSOData]: ( + win: BrowserWindow, + ...args: IpcSOData[K]["args"] + ) => void; +}; + +export type IpcRendererOn = { + [K in keyof IpcSOData]: ( + event: IpcRendererEvent, + ...args: IpcSOData[K]["args"] + ) => Promise | IpcSOData[K]["return"]; +}; + +// FIXME: asを使わないようオーバーロードにした。オーバーロードも使わない書き方にしたい。 +export function registerIpcMainHandle( + listeners: T, ): void; -export function ipcMainSend( - win: BrowserWindow, - channel: string, - ...args: unknown[] -): void { - win.webContents.send(channel, ...args); +export function registerIpcMainHandle(listeners: { + [key: string]: (event: IpcMainInvokeEvent, ...args: unknown[]) => unknown; +}) { + Object.entries(listeners).forEach(([channel, listener]) => { + const errorHandledListener: typeof listener = (event, ...args) => { + try { + validateIpcSender(event); + return listener(event, ...args); + } catch (e) { + log.error(e); + } + }; + ipcMain.handle(channel, errorHandledListener); + }); } +export const ipcMainSendProxy = new Proxy( + {}, + { + get: + (_, channel: string) => + (win: BrowserWindow, ...args: unknown[]) => + win.webContents.send(channel, ...args), + }, +) as IpcMainSend; + /** IPCメッセージの送信元を確認する */ const validateIpcSender = (event: IpcMainInvokeEvent) => { let isValid: boolean; diff --git a/src/backend/electron/main.ts b/src/backend/electron/main.ts index 32557a483e..7ba1609326 100644 --- a/src/backend/electron/main.ts +++ b/src/backend/electron/main.ts @@ -24,7 +24,7 @@ import EngineManager from "./manager/engineManager"; import VvppManager, { isVvppFile } from "./manager/vvppManager"; import configMigration014 from "./configMigration014"; import { RuntimeInfoManager } from "./manager/RuntimeInfoManager"; -import { ipcMainHandle, ipcMainSend } from "./ipc"; +import { registerIpcMainHandle, ipcMainSendProxy, IpcMainHandle } from "./ipc"; import { getConfigManager } from "./electronConfig"; import { failure, success } from "@/type/result"; import { @@ -169,7 +169,7 @@ const onEngineProcessError = (engineInfo: EngineInfo, error: Error) => { // winが作られる前にエラーが発生した場合はwinへの通知を諦める // FIXME: winが作られた後にエンジンを起動させる if (win != undefined) { - ipcMainSend(win, "DETECTED_ENGINE_ERROR", { engineId }); + ipcMainSendProxy.DETECTED_ENGINE_ERROR(win, { engineId }); } else { log.error(`onEngineProcessError: win is undefined`); } @@ -256,7 +256,7 @@ async function installVvppEngineWithWarning({ }) .then((result) => { if (result.response === 0) { - ipcMainSend(win, "CHECK_EDITED_AND_NOT_SAVE", { + ipcMainSendProxy.CHECK_EDITED_AND_NOT_SAVE(win, { closeOrReload: "reload", }); } @@ -457,26 +457,26 @@ async function createWindow() { if (isDevelopment && !isTest) win.webContents.openDevTools(); win.on("maximize", () => { - win.webContents.send("DETECT_MAXIMIZED"); + ipcMainSendProxy.DETECT_MAXIMIZED(win); }); win.on("unmaximize", () => { - win.webContents.send("DETECT_UNMAXIMIZED"); + ipcMainSendProxy.DETECT_UNMAXIMIZED(win); }); win.on("enter-full-screen", () => { - win.webContents.send("DETECT_ENTER_FULLSCREEN"); + ipcMainSendProxy.DETECT_ENTER_FULLSCREEN(win); }); win.on("leave-full-screen", () => { - win.webContents.send("DETECT_LEAVE_FULLSCREEN"); + ipcMainSendProxy.DETECT_LEAVE_FULLSCREEN(win); }); win.on("always-on-top-changed", () => { - win.webContents.send( - win.isAlwaysOnTop() ? "DETECT_PINNED" : "DETECT_UNPINNED", - ); + win.isAlwaysOnTop() + ? ipcMainSendProxy.DETECT_PINNED(win) + : ipcMainSendProxy.DETECT_UNPINNED(win); }); win.on("close", (event) => { if (!appState.willQuit) { event.preventDefault(); - ipcMainSend(win, "CHECK_EDITED_AND_NOT_SAVE", { + ipcMainSendProxy.CHECK_EDITED_AND_NOT_SAVE(win, { closeOrReload: "close", }); return; @@ -485,7 +485,7 @@ async function createWindow() { win.on("resize", () => { const windowSize = win.getSize(); - win.webContents.send("DETECT_RESIZED", { + ipcMainSendProxy.DETECT_RESIZED(win, { width: windowSize[0], height: windowSize[1], }); @@ -609,52 +609,6 @@ if (isMac) { } } -// プロセス間通信 -ipcMainHandle("GET_APP_INFOS", () => { - const name = app.getName(); - const version = app.getVersion(); - return { - name, - version, - }; -}); - -ipcMainHandle("GET_HOW_TO_USE_TEXT", () => { - return howToUseText; -}); - -ipcMainHandle("GET_POLICY_TEXT", () => { - return policyText; -}); - -ipcMainHandle("GET_OSS_LICENSES", () => { - return ossLicenses; -}); - -ipcMainHandle("GET_UPDATE_INFOS", () => { - return updateInfos; -}); - -ipcMainHandle("GET_OSS_COMMUNITY_INFOS", () => { - return ossCommunityInfos; -}); - -ipcMainHandle("GET_CONTACT_TEXT", () => { - return contactText; -}); - -ipcMainHandle("GET_Q_AND_A_TEXT", () => { - return qAndAText; -}); - -ipcMainHandle("GET_PRIVACY_POLICY_TEXT", () => { - return privacyPolicyText; -}); - -ipcMainHandle("GET_ALT_PORT_INFOS", () => { - return engineManager.altPortInfo; -}); - /** * 保存に適した場所を選択するかキャンセルするまでダイアログを繰り返し表示する。 * アンインストール等で消えうる場所などを避ける。 @@ -721,115 +675,167 @@ const retryShowSaveDialogWhileSafeDir = async < } }; -ipcMainHandle("SHOW_AUDIO_SAVE_DIALOG", async (_, { title, defaultPath }) => { - const result = await retryShowSaveDialogWhileSafeDir(() => - dialog.showSaveDialog(win, { - title, - defaultPath, - filters: [{ name: "Wave File", extensions: ["wav"] }], - properties: ["createDirectory"], - }), - ); - return result.filePath; -}); +// プロセス間通信 +registerIpcMainHandle({ + GET_APP_INFOS: () => { + const name = app.getName(); + const version = app.getVersion(); + return { + name, + version, + }; + }, + + GET_HOW_TO_USE_TEXT: () => { + return howToUseText; + }, + + GET_POLICY_TEXT: () => { + return policyText; + }, + + GET_OSS_LICENSES: () => { + return ossLicenses; + }, + + GET_UPDATE_INFOS: () => { + return updateInfos; + }, + + GET_OSS_COMMUNITY_INFOS: () => { + return ossCommunityInfos; + }, + + GET_CONTACT_TEXT: () => { + return contactText; + }, + + GET_Q_AND_A_TEXT: () => { + return qAndAText; + }, + + GET_PRIVACY_POLICY_TEXT: () => { + return privacyPolicyText; + }, + + GET_ALT_PORT_INFOS: () => { + return engineManager.altPortInfo; + }, + + SHOW_AUDIO_SAVE_DIALOG: async (_, { title, defaultPath }) => { + const result = await retryShowSaveDialogWhileSafeDir(() => + dialog.showSaveDialog(win, { + title, + defaultPath, + filters: [{ name: "Wave File", extensions: ["wav"] }], + properties: ["createDirectory"], + }), + ); + return result.filePath; + }, + + SHOW_TEXT_SAVE_DIALOG: async (_, { title, defaultPath }) => { + const result = await retryShowSaveDialogWhileSafeDir(() => + dialog.showSaveDialog(win, { + title, + defaultPath, + filters: [{ name: "Text File", extensions: ["txt"] }], + properties: ["createDirectory"], + }), + ); + return result.filePath; + }, + + /** + * 保存先になるディレクトリを選ぶダイアログを表示する。 + */ + SHOW_SAVE_DIRECTORY_DIALOG: async (_, { title }) => { + const result = await retryShowSaveDialogWhileSafeDir(() => + dialog.showOpenDialog(win, { + title, + properties: [ + "openDirectory", + "createDirectory", + "treatPackageAsDirectory", + ], + }), + ); + if (result.canceled) { + return undefined; + } + return result.filePaths[0]; + }, -ipcMainHandle("SHOW_TEXT_SAVE_DIALOG", async (_, { title, defaultPath }) => { - const result = await retryShowSaveDialogWhileSafeDir(() => - dialog.showSaveDialog(win, { + SHOW_VVPP_OPEN_DIALOG: async (_, { title, defaultPath }) => { + const result = await dialog.showOpenDialog(win, { title, defaultPath, - filters: [{ name: "Text File", extensions: ["txt"] }], - properties: ["createDirectory"], - }), - ); - return result.filePath; -}); + filters: [ + { name: "VOICEVOX Plugin Package", extensions: ["vvpp", "vvppp"] }, + ], + properties: ["openFile", "createDirectory", "treatPackageAsDirectory"], + }); + return result.filePaths[0]; + }, -/** - * 保存先になるディレクトリを選ぶダイアログを表示する。 - */ -ipcMainHandle("SHOW_SAVE_DIRECTORY_DIALOG", async (_, { title }) => { - const result = await retryShowSaveDialogWhileSafeDir(() => - dialog.showOpenDialog(win, { + /** + * ディレクトリ選択ダイアログを表示する。 + * 保存先として選ぶ場合は SHOW_SAVE_DIRECTORY_DIALOG を使うべき。 + */ + SHOW_OPEN_DIRECTORY_DIALOG: async (_, { title }) => { + const result = await dialog.showOpenDialog(win, { title, properties: [ "openDirectory", "createDirectory", "treatPackageAsDirectory", ], - }), - ); - if (result.canceled) { - return undefined; - } - return result.filePaths[0]; -}); - -ipcMainHandle("SHOW_VVPP_OPEN_DIALOG", async (_, { title, defaultPath }) => { - const result = await dialog.showOpenDialog(win, { - title, - defaultPath, - filters: [ - { name: "VOICEVOX Plugin Package", extensions: ["vvpp", "vvppp"] }, - ], - properties: ["openFile", "createDirectory", "treatPackageAsDirectory"], - }); - return result.filePaths[0]; -}); + }); + if (result.canceled) { + return undefined; + } + return result.filePaths[0]; + }, -/** - * ディレクトリ選択ダイアログを表示する。 - * 保存先として選ぶ場合は SHOW_SAVE_DIRECTORY_DIALOG を使うべき。 - */ -ipcMainHandle("SHOW_OPEN_DIRECTORY_DIALOG", async (_, { title }) => { - const result = await dialog.showOpenDialog(win, { - title, - properties: ["openDirectory", "createDirectory", "treatPackageAsDirectory"], - }); - if (result.canceled) { - return undefined; - } - return result.filePaths[0]; -}); + SHOW_PROJECT_SAVE_DIALOG: async (_, { title, defaultPath }) => { + const result = await retryShowSaveDialogWhileSafeDir(() => + dialog.showSaveDialog(win, { + title, + defaultPath, + filters: [{ name: "VOICEVOX Project file", extensions: ["vvproj"] }], + properties: ["showOverwriteConfirmation"], + }), + ); + if (result.canceled) { + return undefined; + } + return result.filePath; + }, -ipcMainHandle("SHOW_PROJECT_SAVE_DIALOG", async (_, { title, defaultPath }) => { - const result = await retryShowSaveDialogWhileSafeDir(() => - dialog.showSaveDialog(win, { + SHOW_PROJECT_LOAD_DIALOG: async (_, { title }) => { + const result = await dialog.showOpenDialog(win, { title, - defaultPath, filters: [{ name: "VOICEVOX Project file", extensions: ["vvproj"] }], - properties: ["showOverwriteConfirmation"], - }), - ); - if (result.canceled) { - return undefined; - } - return result.filePath; -}); - -ipcMainHandle("SHOW_PROJECT_LOAD_DIALOG", async (_, { title }) => { - const result = await dialog.showOpenDialog(win, { - title, - filters: [{ name: "VOICEVOX Project file", extensions: ["vvproj"] }], - properties: ["openFile", "createDirectory", "treatPackageAsDirectory"], - }); - if (result.canceled) { - return undefined; - } - return result.filePaths; -}); + properties: ["openFile", "createDirectory", "treatPackageAsDirectory"], + }); + if (result.canceled) { + return undefined; + } + return result.filePaths; + }, -ipcMainHandle("SHOW_MESSAGE_DIALOG", (_, { type, title, message }) => { - return dialog.showMessageBox(win, { - type, - title, - message, - }); -}); + SHOW_MESSAGE_DIALOG: (_, { type, title, message }) => { + return dialog.showMessageBox(win, { + type, + title, + message, + }); + }, -ipcMainHandle( - "SHOW_QUESTION_DIALOG", - (_, { type, title, message, buttons, cancelId, defaultId }) => { + SHOW_QUESTION_DIALOG: ( + _, + { type, title, message, buttons, cancelId, defaultId }, + ) => { return dialog .showMessageBox(win, { type, @@ -844,199 +850,199 @@ ipcMainHandle( return value.response; }); }, -); -ipcMainHandle("SHOW_WARNING_DIALOG", (_, { title, message }) => { - return dialog.showMessageBox(win, { - type: "warning", - title, - message, - }); -}); + SHOW_WARNING_DIALOG: (_, { title, message }) => { + return dialog.showMessageBox(win, { + type: "warning", + title, + message, + }); + }, -ipcMainHandle("SHOW_ERROR_DIALOG", (_, { title, message }) => { - return dialog.showMessageBox(win, { - type: "error", - title, - message, - }); -}); + SHOW_ERROR_DIALOG: (_, { title, message }) => { + return dialog.showMessageBox(win, { + type: "error", + title, + message, + }); + }, -ipcMainHandle("SHOW_IMPORT_FILE_DIALOG", (_, { title, name, extensions }) => { - return dialog.showOpenDialogSync(win, { - title, - filters: [{ name: name ?? "Text", extensions: extensions ?? ["txt"] }], - properties: ["openFile", "createDirectory", "treatPackageAsDirectory"], - })?.[0]; -}); + SHOW_IMPORT_FILE_DIALOG: (_, { title, name, extensions }) => { + return dialog.showOpenDialogSync(win, { + title, + filters: [{ name: name ?? "Text", extensions: extensions ?? ["txt"] }], + properties: ["openFile", "createDirectory", "treatPackageAsDirectory"], + })?.[0]; + }, -ipcMainHandle("IS_AVAILABLE_GPU_MODE", () => { - return hasSupportedGpu(process.platform); -}); + IS_AVAILABLE_GPU_MODE: () => { + return hasSupportedGpu(process.platform); + }, -ipcMainHandle("IS_MAXIMIZED_WINDOW", () => { - return win.isMaximized(); -}); + IS_MAXIMIZED_WINDOW: () => { + return win.isMaximized(); + }, -ipcMainHandle("CLOSE_WINDOW", () => { - appState.willQuit = true; - win.destroy(); -}); -ipcMainHandle("MINIMIZE_WINDOW", () => { - win.minimize(); -}); -ipcMainHandle("MAXIMIZE_WINDOW", () => { - if (win.isMaximized()) { - win.unmaximize(); - } else { - win.maximize(); - } -}); + CLOSE_WINDOW: () => { + appState.willQuit = true; + win.destroy(); + }, + MINIMIZE_WINDOW: () => { + win.minimize(); + }, + MAXIMIZE_WINDOW: () => { + if (win.isMaximized()) { + win.unmaximize(); + } else { + win.maximize(); + } + }, -ipcMainHandle("OPEN_LOG_DIRECTORY", () => { - void shell.openPath(app.getPath("logs")); -}); + OPEN_LOG_DIRECTORY: () => { + void shell.openPath(app.getPath("logs")); + }, -ipcMainHandle("ENGINE_INFOS", () => { - // エンジン情報を設定ファイルに保存しないためにstoreは使わない - return engineManager.fetchEngineInfos(); -}); + ENGINE_INFOS: () => { + // エンジン情報を設定ファイルに保存しないためにstoreは使わない + return engineManager.fetchEngineInfos(); + }, -/** - * エンジンを再起動する。 - * エンジンの起動が開始したらresolve、起動が失敗したらreject。 - */ -ipcMainHandle("RESTART_ENGINE", async (_, { engineId }) => { - await engineManager.restartEngine(engineId); - // TODO: setEngineInfosからexportFileはロックしたほうがより良い - runtimeInfoManager.setEngineInfos(engineManager.fetchEngineInfos()); - await runtimeInfoManager.exportFile(); -}); + /** + * エンジンを再起動する。 + * エンジンの起動が開始したらresolve、起動が失敗したらreject。 + */ + RESTART_ENGINE: async (_, { engineId }) => { + await engineManager.restartEngine(engineId); + // TODO: setEngineInfosからexportFileはロックしたほうがより良い + runtimeInfoManager.setEngineInfos(engineManager.fetchEngineInfos()); + await runtimeInfoManager.exportFile(); + }, -ipcMainHandle("OPEN_ENGINE_DIRECTORY", async (_, { engineId }) => { - openEngineDirectory(engineId); -}); + OPEN_ENGINE_DIRECTORY: async (_, { engineId }) => { + openEngineDirectory(engineId); + }, -ipcMainHandle("HOTKEY_SETTINGS", (_, { newData }) => { - if (newData != undefined) { - const hotkeySettings = configManager.get("hotkeySettings"); - const hotkeySetting = hotkeySettings.find( - (hotkey) => hotkey.action == newData.action, - ); - if (hotkeySetting != undefined) { - hotkeySetting.combination = newData.combination; + HOTKEY_SETTINGS: (_, { newData }) => { + if (newData != undefined) { + const hotkeySettings = configManager.get("hotkeySettings"); + const hotkeySetting = hotkeySettings.find( + (hotkey) => hotkey.action == newData.action, + ); + if (hotkeySetting != undefined) { + hotkeySetting.combination = newData.combination; + } + configManager.set("hotkeySettings", hotkeySettings); } - configManager.set("hotkeySettings", hotkeySettings); - } - return configManager.get("hotkeySettings"); -}); + return configManager.get("hotkeySettings"); + }, -ipcMainHandle("THEME", (_, { newData }) => { - if (newData != undefined) { - configManager.set("currentTheme", newData); - return; - } - return { - currentTheme: configManager.get("currentTheme"), - availableThemes: themes, - }; -}); + THEME: (_, { newData }) => { + if (newData != undefined) { + configManager.set("currentTheme", newData); + return; + } + return { + currentTheme: configManager.get("currentTheme"), + availableThemes: themes, + }; + }, -ipcMainHandle("ON_VUEX_READY", () => { - win.show(); -}); + ON_VUEX_READY: () => { + win.show(); + }, -ipcMainHandle("CHECK_FILE_EXISTS", (_, { file }) => { - return fs.existsSync(file); -}); -ipcMainHandle("CHANGE_PIN_WINDOW", () => { - if (win.isAlwaysOnTop()) { - win.setAlwaysOnTop(false); - } else { - win.setAlwaysOnTop(true); - } -}); + CHECK_FILE_EXISTS: (_, { file }) => { + return fs.existsSync(file); + }, + CHANGE_PIN_WINDOW: () => { + if (win.isAlwaysOnTop()) { + win.setAlwaysOnTop(false); + } else { + win.setAlwaysOnTop(true); + } + }, -ipcMainHandle("GET_DEFAULT_HOTKEY_SETTINGS", () => { - return defaultHotkeySettings; -}); + GET_DEFAULT_HOTKEY_SETTINGS: () => { + return defaultHotkeySettings; + }, -ipcMainHandle("GET_DEFAULT_TOOLBAR_SETTING", () => { - return defaultToolbarButtonSetting; -}); + GET_DEFAULT_TOOLBAR_SETTING: () => { + return defaultToolbarButtonSetting; + }, -ipcMainHandle("GET_SETTING", (_, key) => { - return configManager.get(key); -}); + GET_SETTING: (_, key) => { + return configManager.get(key); + }, -ipcMainHandle("SET_SETTING", (_, key, newValue) => { - configManager.set(key, newValue); - return configManager.get(key); -}); + SET_SETTING: (_, key, newValue) => { + configManager.set(key, newValue); + return configManager.get(key); + }, -ipcMainHandle("SET_ENGINE_SETTING", async (_, engineId, engineSetting) => { - const engineSettings = configManager.get("engineSettings"); - engineSettings[engineId] = engineSetting; - configManager.set(`engineSettings`, engineSettings); -}); + SET_ENGINE_SETTING: async (_, engineId, engineSetting) => { + const engineSettings = configManager.get("engineSettings"); + engineSettings[engineId] = engineSetting; + configManager.set(`engineSettings`, engineSettings); + }, -ipcMainHandle("SET_NATIVE_THEME", (_, source) => { - nativeTheme.themeSource = source; -}); + SET_NATIVE_THEME: (_, source) => { + nativeTheme.themeSource = source; + }, -ipcMainHandle("INSTALL_VVPP_ENGINE", async (_, path: string) => { - return await installVvppEngine(path); -}); + INSTALL_VVPP_ENGINE: async (_, path: string) => { + return await installVvppEngine(path); + }, -ipcMainHandle("UNINSTALL_VVPP_ENGINE", async (_, engineId: EngineId) => { - return await uninstallVvppEngine(engineId); -}); + UNINSTALL_VVPP_ENGINE: async (_, engineId: EngineId) => { + return await uninstallVvppEngine(engineId); + }, -ipcMainHandle("VALIDATE_ENGINE_DIR", (_, { engineDir }) => { - return engineManager.validateEngineDir(engineDir); -}); + VALIDATE_ENGINE_DIR: (_, { engineDir }) => { + return engineManager.validateEngineDir(engineDir); + }, -ipcMainHandle("RELOAD_APP", async (_, { isMultiEngineOffMode }) => { - win.hide(); // FIXME: ダミーページ表示のほうが良い + RELOAD_APP: async (_, { isMultiEngineOffMode }) => { + win.hide(); // FIXME: ダミーページ表示のほうが良い - // 一旦適当なURLに飛ばしてページをアンロードする - await win.loadURL("about:blank"); + // 一旦適当なURLに飛ばしてページをアンロードする + await win.loadURL("about:blank"); - log.info("Checking ENGINE status before reload app"); - const engineCleanupResult = cleanupEngines(); + log.info("Checking ENGINE status before reload app"); + const engineCleanupResult = cleanupEngines(); - // エンジンの停止とエンジン終了後処理の待機 - if (engineCleanupResult != "alreadyCompleted") { - await engineCleanupResult; - } - log.info("Post engine kill process done. Now reloading app"); + // エンジンの停止とエンジン終了後処理の待機 + if (engineCleanupResult != "alreadyCompleted") { + await engineCleanupResult; + } + log.info("Post engine kill process done. Now reloading app"); - await launchEngines(); + await launchEngines(); - await loadUrl({ isMultiEngineOffMode: !!isMultiEngineOffMode }); - win.show(); -}); + await loadUrl({ isMultiEngineOffMode: !!isMultiEngineOffMode }); + win.show(); + }, -ipcMainHandle("WRITE_FILE", (_, { filePath, buffer }) => { - try { - fs.writeFileSync(filePath, new DataView(buffer)); - return success(undefined); - } catch (e) { - // throwだと`.code`の情報が消えるのでreturn - const a = e as SystemError; - return failure(a.code, a); - } -}); + WRITE_FILE: (_, { filePath, buffer }) => { + try { + fs.writeFileSync(filePath, new DataView(buffer)); + return success(undefined); + } catch (e) { + // throwだと`.code`の情報が消えるのでreturn + const a = e as SystemError; + return failure(a.code, a); + } + }, -ipcMainHandle("READ_FILE", async (_, { filePath }) => { - try { - const result = await fs.promises.readFile(filePath); - return success(result); - } catch (e) { - // throwだと`.code`の情報が消えるのでreturn - const a = e as SystemError; - return failure(a.code, a); - } + READ_FILE: async (_, { filePath }) => { + try { + const result = await fs.promises.readFile(filePath); + return success(result); + } catch (e) { + // throwだと`.code`の情報が消えるのでreturn + const a = e as SystemError; + return failure(a.code, a); + } + }, }); // app callback @@ -1071,7 +1077,7 @@ app.on("window-all-closed", () => { app.on("before-quit", async (event) => { if (!appState.willQuit) { event.preventDefault(); - ipcMainSend(win, "CHECK_EDITED_AND_NOT_SAVE", { closeOrReload: "close" }); + ipcMainSendProxy.CHECK_EDITED_AND_NOT_SAVE(win, { closeOrReload: "close" }); return; } @@ -1264,7 +1270,7 @@ app.on("second-instance", async (event, argv, workDir, rawData) => { } } else if (data.filePath.endsWith(".vvproj")) { log.info("Second instance launched with vvproj file"); - ipcMainSend(win, "LOAD_PROJECT_FILE", { + ipcMainSendProxy.LOAD_PROJECT_FILE(win, { filePath: data.filePath, confirm: true, }); diff --git a/src/backend/electron/preload.ts b/src/backend/electron/preload.ts index 1891b99ce2..c94d3d1ea5 100644 --- a/src/backend/electron/preload.ts +++ b/src/backend/electron/preload.ts @@ -1,106 +1,95 @@ -import { - contextBridge, - ipcRenderer, - IpcRenderer, - IpcRendererEvent, -} from "electron"; +import { contextBridge, ipcRenderer } from "electron"; +import { IpcRendererInvoke } from "./ipc"; import { Sandbox, ConfigType, EngineId, SandboxKey } from "@/type/preload"; -import { IpcIHData, IpcSOData } from "@/type/ipc"; - -function ipcRendererInvoke( - channel: T, - ...args: IpcIHData[T]["args"] -): Promise; -function ipcRendererInvoke(channel: string, ...args: unknown[]): unknown { - return ipcRenderer.invoke(channel, ...args) as unknown; -} - -function ipcRendererOn( - channel: T, - listener: (event: IpcRendererEvent, ...args: IpcSOData[T]["args"]) => void, -): IpcRenderer; -function ipcRendererOn( - channel: string, - listener: (event: IpcRendererEvent, ...args: unknown[]) => void, -) { - return ipcRenderer.on(channel, listener); -} + +const ipcRendererInvokeProxy = new Proxy( + {}, + { + get: + (_, channel: string) => + (...args: unknown[]) => + ipcRenderer.invoke(channel, ...args), + }, +) as IpcRendererInvoke; const api: Sandbox = { getAppInfos: async () => { - return await ipcRendererInvoke("GET_APP_INFOS"); + return await ipcRendererInvokeProxy.GET_APP_INFOS(); }, getHowToUseText: async () => { - return await ipcRendererInvoke("GET_HOW_TO_USE_TEXT"); + return await ipcRendererInvokeProxy.GET_HOW_TO_USE_TEXT(); }, getPolicyText: async () => { - return await ipcRendererInvoke("GET_POLICY_TEXT"); + return await ipcRendererInvokeProxy.GET_POLICY_TEXT(); }, getOssLicenses: async () => { - return await ipcRendererInvoke("GET_OSS_LICENSES"); + return await ipcRendererInvokeProxy.GET_OSS_LICENSES(); }, getUpdateInfos: async () => { - return await ipcRendererInvoke("GET_UPDATE_INFOS"); + return await ipcRendererInvokeProxy.GET_UPDATE_INFOS(); }, getContactText: async () => { - return await ipcRendererInvoke("GET_CONTACT_TEXT"); + return await ipcRendererInvokeProxy.GET_CONTACT_TEXT(); }, getQAndAText: async () => { - return await ipcRendererInvoke("GET_Q_AND_A_TEXT"); + return await ipcRendererInvokeProxy.GET_Q_AND_A_TEXT(); }, getOssCommunityInfos: async () => { - return await ipcRendererInvoke("GET_OSS_COMMUNITY_INFOS"); + return await ipcRendererInvokeProxy.GET_OSS_COMMUNITY_INFOS(); }, getPrivacyPolicyText: async () => { - return await ipcRendererInvoke("GET_PRIVACY_POLICY_TEXT"); + return await ipcRendererInvokeProxy.GET_PRIVACY_POLICY_TEXT(); }, getAltPortInfos: async () => { - return await ipcRendererInvoke("GET_ALT_PORT_INFOS"); + return await ipcRendererInvokeProxy.GET_ALT_PORT_INFOS(); }, showAudioSaveDialog: ({ title, defaultPath }) => { - return ipcRendererInvoke("SHOW_AUDIO_SAVE_DIALOG", { title, defaultPath }); + return ipcRendererInvokeProxy.SHOW_AUDIO_SAVE_DIALOG({ + title, + defaultPath, + }); }, showTextSaveDialog: ({ title, defaultPath }) => { - return ipcRendererInvoke("SHOW_TEXT_SAVE_DIALOG", { title, defaultPath }); + return ipcRendererInvokeProxy.SHOW_TEXT_SAVE_DIALOG({ title, defaultPath }); }, showSaveDirectoryDialog: ({ title }) => { - return ipcRendererInvoke("SHOW_SAVE_DIRECTORY_DIALOG", { title }); + return ipcRendererInvokeProxy.SHOW_SAVE_DIRECTORY_DIALOG({ title }); }, showVvppOpenDialog: ({ title, defaultPath }) => { - return ipcRendererInvoke("SHOW_VVPP_OPEN_DIALOG", { title, defaultPath }); + return ipcRendererInvokeProxy.SHOW_VVPP_OPEN_DIALOG({ title, defaultPath }); }, showOpenDirectoryDialog: ({ title }) => { - return ipcRendererInvoke("SHOW_OPEN_DIRECTORY_DIALOG", { title }); + return ipcRendererInvokeProxy.SHOW_OPEN_DIRECTORY_DIALOG({ title }); }, showProjectSaveDialog: ({ title, defaultPath }) => { - return ipcRendererInvoke("SHOW_PROJECT_SAVE_DIALOG", { + return ipcRendererInvokeProxy.SHOW_PROJECT_SAVE_DIALOG({ title, defaultPath, }); }, showProjectLoadDialog: ({ title }) => { - return ipcRendererInvoke("SHOW_PROJECT_LOAD_DIALOG", { title }); + return ipcRendererInvokeProxy.SHOW_PROJECT_LOAD_DIALOG({ title }); }, showMessageDialog: ({ type, title, message }) => { - return ipcRendererInvoke("SHOW_MESSAGE_DIALOG", { type, title, message }); + return ipcRendererInvokeProxy.SHOW_MESSAGE_DIALOG({ type, title, message }); }, showQuestionDialog: ({ @@ -111,7 +100,7 @@ const api: Sandbox = { cancelId, defaultId, }) => { - return ipcRendererInvoke("SHOW_QUESTION_DIALOG", { + return ipcRendererInvokeProxy.SHOW_QUESTION_DIALOG({ type, title, message, @@ -122,7 +111,7 @@ const api: Sandbox = { }, showImportFileDialog: ({ title, name, extensions }) => { - return ipcRendererInvoke("SHOW_IMPORT_FILE_DIALOG", { + return ipcRendererInvokeProxy.SHOW_IMPORT_FILE_DIALOG({ title, name, extensions, @@ -130,35 +119,37 @@ const api: Sandbox = { }, writeFile: async ({ filePath, buffer }) => { - return await ipcRendererInvoke("WRITE_FILE", { filePath, buffer }); + return await ipcRendererInvokeProxy.WRITE_FILE({ filePath, buffer }); }, readFile: async ({ filePath }) => { - return await ipcRendererInvoke("READ_FILE", { filePath }); + return await ipcRendererInvokeProxy.READ_FILE({ filePath }); }, isAvailableGPUMode: () => { - return ipcRendererInvoke("IS_AVAILABLE_GPU_MODE"); + return ipcRendererInvokeProxy.IS_AVAILABLE_GPU_MODE(); }, isMaximizedWindow: () => { - return ipcRendererInvoke("IS_MAXIMIZED_WINDOW"); + return ipcRendererInvokeProxy.IS_MAXIMIZED_WINDOW(); }, - onReceivedIPCMsg: (channel, callback) => { - return ipcRendererOn(channel, callback); + onReceivedIPCMsg: (listeners) => { + Object.entries(listeners).forEach(([channel, listener]) => { + ipcRenderer.on(channel, listener); + }); }, closeWindow: () => { - void ipcRenderer.invoke("CLOSE_WINDOW"); + void ipcRendererInvokeProxy.CLOSE_WINDOW(); }, minimizeWindow: () => { - void ipcRenderer.invoke("MINIMIZE_WINDOW"); + void ipcRendererInvokeProxy.MINIMIZE_WINDOW(); }, maximizeWindow: () => { - void ipcRenderer.invoke("MAXIMIZE_WINDOW"); + void ipcRendererInvokeProxy.MAXIMIZE_WINDOW(); }, logError: (...params) => { @@ -187,59 +178,58 @@ const api: Sandbox = { }, openLogDirectory: () => { - void ipcRenderer.invoke("OPEN_LOG_DIRECTORY"); + void ipcRendererInvokeProxy.OPEN_LOG_DIRECTORY(); }, engineInfos: () => { - return ipcRendererInvoke("ENGINE_INFOS"); + return ipcRendererInvokeProxy.ENGINE_INFOS(); }, restartEngine: (engineId: EngineId) => { - return ipcRendererInvoke("RESTART_ENGINE", { engineId }); + return ipcRendererInvokeProxy.RESTART_ENGINE({ engineId }); }, openEngineDirectory: (engineId: EngineId) => { - return ipcRendererInvoke("OPEN_ENGINE_DIRECTORY", { engineId }); + return ipcRendererInvokeProxy.OPEN_ENGINE_DIRECTORY({ engineId }); }, checkFileExists: (file) => { - return ipcRenderer.invoke("CHECK_FILE_EXISTS", { file }); + return ipcRendererInvokeProxy.CHECK_FILE_EXISTS({ file }); }, changePinWindow: () => { - void ipcRenderer.invoke("CHANGE_PIN_WINDOW"); + void ipcRendererInvokeProxy.CHANGE_PIN_WINDOW(); }, hotkeySettings: (newData) => { - return ipcRenderer.invoke("HOTKEY_SETTINGS", { newData }); + return ipcRendererInvokeProxy.HOTKEY_SETTINGS({ newData }); }, getDefaultHotkeySettings: async () => { - return await ipcRendererInvoke("GET_DEFAULT_HOTKEY_SETTINGS"); + return await ipcRendererInvokeProxy.GET_DEFAULT_HOTKEY_SETTINGS(); }, getDefaultToolbarSetting: async () => { - return await ipcRendererInvoke("GET_DEFAULT_TOOLBAR_SETTING"); + return await ipcRendererInvokeProxy.GET_DEFAULT_TOOLBAR_SETTING(); }, setNativeTheme: (source) => { - void ipcRenderer.invoke("SET_NATIVE_THEME", source); + void ipcRendererInvokeProxy.SET_NATIVE_THEME(source); }, theme: (newData) => { - return ipcRenderer.invoke("THEME", { newData }); + return ipcRendererInvokeProxy.THEME({ newData }); }, vuexReady: () => { - void ipcRenderer.invoke("ON_VUEX_READY"); + void ipcRendererInvokeProxy.ON_VUEX_READY(); }, /** * 設定情報を取得する */ getSetting: async (key) => { - return (await ipcRendererInvoke( - "GET_SETTING", + return (await ipcRendererInvokeProxy.GET_SETTING( key, )) as ConfigType[typeof key]; }, @@ -248,27 +238,26 @@ const api: Sandbox = { * 設定情報を保存する */ setSetting: async (key, newValue) => { - return (await ipcRendererInvoke( - "SET_SETTING", + return (await ipcRendererInvokeProxy.SET_SETTING( key, newValue, )) as typeof newValue; }, setEngineSetting: async (engineId, engineSetting) => { - await ipcRendererInvoke("SET_ENGINE_SETTING", engineId, engineSetting); + await ipcRendererInvokeProxy.SET_ENGINE_SETTING(engineId, engineSetting); }, installVvppEngine: async (filePath) => { - return await ipcRendererInvoke("INSTALL_VVPP_ENGINE", filePath); + return await ipcRendererInvokeProxy.INSTALL_VVPP_ENGINE(filePath); }, uninstallVvppEngine: async (engineId) => { - return await ipcRendererInvoke("UNINSTALL_VVPP_ENGINE", engineId); + return await ipcRendererInvokeProxy.UNINSTALL_VVPP_ENGINE(engineId); }, validateEngineDir: async (engineDir) => { - return await ipcRendererInvoke("VALIDATE_ENGINE_DIR", { engineDir }); + return await ipcRendererInvokeProxy.VALIDATE_ENGINE_DIR({ engineDir }); }, /** @@ -276,7 +265,7 @@ const api: Sandbox = { * 画面以外の情報を刷新する。 */ reloadApp: async ({ isMultiEngineOffMode }) => { - await ipcRendererInvoke("RELOAD_APP", { isMultiEngineOffMode }); + await ipcRendererInvokeProxy.RELOAD_APP({ isMultiEngineOffMode }); }, }; diff --git a/src/plugins/ipcMessageReceiverPlugin.ts b/src/plugins/ipcMessageReceiverPlugin.ts index 3a79ad98aa..011d3710c7 100644 --- a/src/plugins/ipcMessageReceiverPlugin.ts +++ b/src/plugins/ipcMessageReceiverPlugin.ts @@ -8,53 +8,40 @@ export const ipcMessageReceiver: Plugin = { _, options: { store: Store }, ) => { - window.backend.onReceivedIPCMsg( - "LOAD_PROJECT_FILE", - (_, { filePath, confirm } = {}) => - options.store.dispatch("LOAD_PROJECT_FILE", { filePath, confirm }), - ); - - window.backend.onReceivedIPCMsg("DETECT_MAXIMIZED", () => - options.store.dispatch("DETECT_MAXIMIZED"), - ); - - window.backend.onReceivedIPCMsg("DETECT_UNMAXIMIZED", () => - options.store.dispatch("DETECT_UNMAXIMIZED"), - ); - - window.backend.onReceivedIPCMsg( - "DETECTED_ENGINE_ERROR", - (_, { engineId }) => + window.backend.onReceivedIPCMsg({ + LOAD_PROJECT_FILE: (_, { filePath, confirm } = {}) => + void options.store.dispatch("LOAD_PROJECT_FILE", { filePath, confirm }), + + DETECT_MAXIMIZED: () => options.store.dispatch("DETECT_MAXIMIZED"), + + DETECT_UNMAXIMIZED: () => options.store.dispatch("DETECT_UNMAXIMIZED"), + + DETECTED_ENGINE_ERROR: (_, { engineId }) => options.store.dispatch("DETECTED_ENGINE_ERROR", { engineId }), - ); - window.backend.onReceivedIPCMsg("DETECT_PINNED", () => { - void options.store.dispatch("DETECT_PINNED"); - }); + DETECT_PINNED: () => { + void options.store.dispatch("DETECT_PINNED"); + }, - window.backend.onReceivedIPCMsg("DETECT_UNPINNED", () => { - void options.store.dispatch("DETECT_UNPINNED"); - }); + DETECT_UNPINNED: () => { + void options.store.dispatch("DETECT_UNPINNED"); + }, - window.backend.onReceivedIPCMsg("DETECT_ENTER_FULLSCREEN", () => - options.store.dispatch("DETECT_ENTER_FULLSCREEN"), - ); + DETECT_ENTER_FULLSCREEN: () => + options.store.dispatch("DETECT_ENTER_FULLSCREEN"), - window.backend.onReceivedIPCMsg("DETECT_LEAVE_FULLSCREEN", () => - options.store.dispatch("DETECT_LEAVE_FULLSCREEN"), - ); + DETECT_LEAVE_FULLSCREEN: () => + options.store.dispatch("DETECT_LEAVE_FULLSCREEN"), - window.backend.onReceivedIPCMsg("CHECK_EDITED_AND_NOT_SAVE", (_, obj) => { - void options.store.dispatch("CHECK_EDITED_AND_NOT_SAVE", obj); - }); + CHECK_EDITED_AND_NOT_SAVE: (_, obj) => { + void options.store.dispatch("CHECK_EDITED_AND_NOT_SAVE", obj); + }, - window.backend.onReceivedIPCMsg( - "DETECT_RESIZED", - debounce( + DETECT_RESIZED: debounce( (_, { width, height }: { width: number; height: number }) => window.dataLayer?.push({ event: "windowResize", width, height }), 300, ), - ); + }); }, }; diff --git a/src/type/ipc.ts b/src/type/ipc.ts index 599e4a717d..006aca3532 100644 --- a/src/type/ipc.ts +++ b/src/type/ipc.ts @@ -179,21 +179,6 @@ export type IpcIHData = { return: void; }; - LOG_ERROR: { - args: [...params: unknown[]]; - return: void; - }; - - LOG_WARN: { - args: [...params: unknown[]]; - return: void; - }; - - LOG_INFO: { - args: [...params: unknown[]]; - return: void; - }; - OPEN_LOG_DIRECTORY: { args: []; return: void; @@ -204,11 +189,6 @@ export type IpcIHData = { return: EngineInfo[]; }; - RESTART_ENGINE_ALL: { - args: []; - return: void; - }; - RESTART_ENGINE: { args: [obj: { engineId: EngineId }]; return: void; diff --git a/src/type/preload.ts b/src/type/preload.ts index 9b07278510..6b0d7e6826 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -264,10 +264,12 @@ export interface Sandbox { readFile(obj: { filePath: string }): Promise>; isAvailableGPUMode(): Promise; isMaximizedWindow(): Promise; - onReceivedIPCMsg( - channel: T, - listener: (event: unknown, ...args: IpcSOData[T]["args"]) => void, - ): void; + onReceivedIPCMsg(listeners: { + [K in keyof IpcSOData]: ( + event: unknown, + ...args: IpcSOData[K]["args"] + ) => Promise | IpcSOData[K]["return"]; + }): void; closeWindow(): void; minimizeWindow(): void; maximizeWindow(): void;