diff --git a/README.md b/README.md index 8224cb0abf..f632cce848 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,9 @@ pnpm run electron:serve # ビルド時に近い環境で実行 pnpm run electron:serve --mode production + +# 引数を指定して実行 +pnpm run electron:serve -- ... ``` 音声合成エンジンのリポジトリはこちらです @@ -128,7 +131,7 @@ fork したリポジトリで Actions を ON にし、workflow_dispatch で`buil pnpm run test:unit pnpm run test-watch:unit # 監視モード pnpm run test-ui:unit # VitestのUIを表示 -pnpm run test:unit -- --update # スナップショットの更新 +pnpm run test:unit --update # スナップショットの更新 ``` > [!NOTE] @@ -148,7 +151,7 @@ Electron の機能が不要な、UI や音声合成などの End to End テス ```bash pnpm run test:browser-e2e pnpm run test-watch:browser-e2e # 監視モード -pnpm run test-watch:browser-e2e -- --headed # テスト中の UI を表示 +pnpm run test-watch:browser-e2e --headed # テスト中の UI を表示 pnpm run test-ui:browser-e2e # Playwright の UI を表示 ``` @@ -216,7 +219,7 @@ pnpm run test-ui:storybook-vrt # Playwright の UI を表示 ローカル PC の OS に対応したもののみが更新されます。 ```bash -pnpm run test:browser-e2e -- --update-snapshots +pnpm run test:browser-e2e --update-snapshots ``` ### Electron End to End テスト diff --git a/src/backend/browser/sandbox.ts b/src/backend/browser/sandbox.ts index f1ef8025ab..aa1deec0f6 100644 --- a/src/backend/browser/sandbox.ts +++ b/src/backend/browser/sandbox.ts @@ -76,6 +76,9 @@ export const api: Sandbox = { // NOTE: ブラウザ版ではサポートされていません return Promise.resolve({}); }, + getInitialProjectFilePath() { + return Promise.resolve(undefined); + }, showSaveDirectoryDialog(obj: { title: string }) { return showOpenDirectoryDialogImpl(obj); }, diff --git a/src/backend/electron/main.ts b/src/backend/electron/main.ts index 1e8c87f23a..d158bad9ce 100644 --- a/src/backend/electron/main.ts +++ b/src/backend/electron/main.ts @@ -38,7 +38,7 @@ import { EngineId, TextAsset, } from "@/type/preload"; -import { isMac } from "@/helpers/platform"; +import { isMac, isProduction } from "@/helpers/platform"; import { createLogger } from "@/helpers/log"; type SingleInstanceLockData = { @@ -245,7 +245,25 @@ function checkMultiEngineEnabled(): boolean { return enabled; } -let filePathOnMac: string | undefined = undefined; +/** コマンドライン引数を取得する */ +function getArgv(): string[] { + // 製品版でmacOS以外の場合、引数はargv[1]以降をそのまま + if (isProduction) { + if (!isMac) { + return process.argv.slice(1); + } + } + // 開発版の場合、引数は`--`がある場合は`--`以降、無い場合は引数なしとして扱う + else { + const index = process.argv.indexOf("--"); + if (index !== -1) { + return process.argv.slice(index + 1); + } + } + return []; +} + +let initialFilePath: string | undefined = getArgv()[0]; // TODO: カプセル化する const menuTemplateForMac: Electron.MenuItemConstructorOptions[] = [ { @@ -355,6 +373,12 @@ registerIpcMainHandle({ return engineInfoManager.altPortInfos; }, + GET_INITIAL_PROJECT_FILE_PATH: async () => { + if (initialFilePath && initialFilePath.endsWith(".vvproj")) { + return initialFilePath; + } + }, + /** * 保存先になるディレクトリを選ぶダイアログを表示する。 */ @@ -689,7 +713,7 @@ app.once("will-finish-launching", () => { // macOS only app.once("open-file", (event, filePath) => { event.preventDefault(); - filePathOnMac = filePath; + initialFilePath = filePath; }); }); @@ -817,45 +841,38 @@ void app.whenReady().then(async () => { ); } - // runEngineAllの前にVVPPを読み込む - let filePath: string | undefined; - if (process.platform === "darwin") { - filePath = filePathOnMac; - } else { - if (process.argv.length > 1) { - filePath = process.argv[1]; - } - } - // 多重起動防止 // TODO: readyを待たずにもっと早く実行すべき if ( !isDevelopment && !isTest && !app.requestSingleInstanceLock({ - filePath, - } as SingleInstanceLockData) + filePath: initialFilePath, + } satisfies SingleInstanceLockData) ) { log.info("VOICEVOX already running. Cancelling launch."); - log.info(`File path sent: ${filePath}`); + log.info(`File path sent: ${initialFilePath}`); appState.willQuit = true; app.quit(); return; } - if (filePath && isVvppFile(filePath)) { - log.info(`vvpp file install: ${filePath}`); - // FIXME: GUI側に合流させる - if (checkMultiEngineEnabled()) { - await engineAndVvppController.installVvppEngineWithWarning({ - vvppPath: filePath, - reloadNeeded: false, - }); + if (initialFilePath) { + log.info(`Initial file path provided: ${initialFilePath}`); + if (isVvppFile(initialFilePath)) { + log.info(`vvpp file install: ${initialFilePath}`); + // FIXME: GUI側に合流させる + if (checkMultiEngineEnabled()) { + await engineAndVvppController.installVvppEngineWithWarning({ + vvppPath: initialFilePath, + reloadNeeded: false, + }); + } } } await engineAndVvppController.launchEngines(); - await windowManager.createWindow(filePath); + await windowManager.createWindow(); }); // 他のプロセスが起動したとき、`requestSingleInstanceLock`経由で`rawData`が送信される。 diff --git a/src/backend/electron/manager/windowManager.ts b/src/backend/electron/manager/windowManager.ts index 3c8d9e51c0..5624360376 100644 --- a/src/backend/electron/manager/windowManager.ts +++ b/src/backend/electron/manager/windowManager.ts @@ -1,4 +1,3 @@ -import fs from "fs"; import path from "path"; import { BrowserWindow, @@ -13,7 +12,6 @@ import windowStateKeeper from "electron-window-state"; import { getConfigManager } from "../electronConfig"; import { getEngineAndVvppController } from "../engineAndVvppController"; import { ipcMainSendProxy } from "../ipc"; -import { isMac } from "@/helpers/platform"; import { themes } from "@/domain/theme"; import { createLogger } from "@/helpers/log"; @@ -57,7 +55,7 @@ class WindowManager { return this._win; } - public async createWindow(filePathOnMac: string | undefined) { + public async createWindow() { if (this.win != undefined) { throw new Error("Window has already been created"); } @@ -88,27 +86,6 @@ class WindowManager { icon: path.join(this.staticDir, "icon.png"), }); - let projectFilePath = ""; - if (isMac) { - if (filePathOnMac) { - if (filePathOnMac.endsWith(".vvproj")) { - projectFilePath = filePathOnMac; - } - filePathOnMac = undefined; - } - } else { - if (process.argv.length >= 2) { - const filePath = process.argv[1]; - if ( - fs.existsSync(filePath) && - fs.statSync(filePath).isFile() && - filePath.endsWith(".vvproj") - ) { - projectFilePath = filePath; - } - } - } - win.on("maximize", () => { ipcMainSendProxy.DETECT_MAXIMIZED(win); }); @@ -149,7 +126,7 @@ class WindowManager { mainWindowState.manage(win); this._win = win; - await this.load({ projectFilePath }); + await this.load({}); if (this.isDevelopment && !this.isTest) win.webContents.openDevTools(); } @@ -157,13 +134,9 @@ class WindowManager { /** * 画面の読み込みを開始する。 * @param obj.isMultiEngineOffMode マルチエンジンオフモードにするかどうか。無指定時はfalse扱いになる。 - * @param obj.projectFilePath 初期化時に読み込むプロジェクトファイル。無指定時は何も読み込まない。 * @returns ロードの完了を待つPromise。 */ - public async load(obj: { - isMultiEngineOffMode?: boolean; - projectFilePath?: string; - }) { + public async load(obj: { isMultiEngineOffMode?: boolean }) { const win = this.getWindow(); const firstUrl = import.meta.env.VITE_DEV_SERVER_URL ?? "app://./index.html"; @@ -172,7 +145,6 @@ class WindowManager { "isMultiEngineOffMode", (obj?.isMultiEngineOffMode ?? false).toString(), ); - url.searchParams.append("projectFilePath", obj?.projectFilePath ?? ""); await win.loadURL(url.toString()); } diff --git a/src/backend/electron/preload.ts b/src/backend/electron/preload.ts index 23a3370a4f..276e49274d 100644 --- a/src/backend/electron/preload.ts +++ b/src/backend/electron/preload.ts @@ -33,6 +33,10 @@ const api: Sandbox = { return await ipcRendererInvokeProxy.GET_ALT_PORT_INFOS(); }, + getInitialProjectFilePath: async () => { + return await ipcRendererInvokeProxy.GET_INITIAL_PROJECT_FILE_PATH(); + }, + showSaveDirectoryDialog: ({ title }) => { return ipcRendererInvokeProxy.SHOW_SAVE_DIRECTORY_DIALOG({ title }); }, diff --git a/src/components/App.vue b/src/components/App.vue index 51cdbfea1b..797dd891f4 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -114,9 +114,6 @@ onMounted(async () => { await store.actions.INIT_VUEX(); - // プロジェクトファイルのパスを取得 - const projectFilePath = urlParams.get("projectFilePath"); - // ショートカットキーの設定を登録 const hotkeySettings = store.state.hotkeySettings; hotkeyManager.load(structuredClone(toRaw(hotkeySettings))); @@ -163,7 +160,8 @@ onMounted(async () => { }); // プロジェクトファイルが指定されていればロード - if (typeof projectFilePath === "string" && projectFilePath !== "") { + const projectFilePath = await store.actions.GET_INITIAL_PROJECT_FILE_PATH(); + if (projectFilePath != undefined) { isProjectFileLoaded.value = await store.actions.LOAD_PROJECT_FILE({ type: "path", filePath: projectFilePath, diff --git a/src/store/project.ts b/src/store/project.ts index 3e5a0862d4..6c96430064 100755 --- a/src/store/project.ts +++ b/src/store/project.ts @@ -378,6 +378,12 @@ export const projectStore = createPartialStore({ }), }, + GET_INITIAL_PROJECT_FILE_PATH: { + action: async () => { + return await window.backend.getInitialProjectFilePath(); + }, + }, + IS_EDITED: { getter(state, getters) { // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/store/type.ts b/src/store/type.ts index ebb5f10314..0e7ce9d289 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -1859,6 +1859,10 @@ export type ProjectStoreTypes = { }): "saved" | "discarded" | "canceled"; }; + GET_INITIAL_PROJECT_FILE_PATH: { + action(): Promise; + }; + IS_EDITED: { getter: boolean; }; diff --git a/src/type/ipc.ts b/src/type/ipc.ts index 8c35a089f5..8e959c7a3b 100644 --- a/src/type/ipc.ts +++ b/src/type/ipc.ts @@ -33,6 +33,11 @@ export type IpcIHData = { return: AltPortInfos; }; + GET_INITIAL_PROJECT_FILE_PATH: { + args: []; + return: string | undefined; + }; + SHOW_SAVE_DIRECTORY_DIALOG: { args: [obj: { title: string }]; return?: string; diff --git a/src/type/preload.ts b/src/type/preload.ts index 3e11726fb8..a9851025a7 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -73,6 +73,7 @@ export interface Sandbox { getAppInfos(): Promise; getTextAsset(textType: K): Promise; getAltPortInfos(): Promise; + getInitialProjectFilePath(): Promise; showSaveDirectoryDialog(obj: { title: string }): Promise; showVvppOpenDialog(obj: { title: string; diff --git a/vite.config.mts b/vite.config.mts index f091ae42cd..7d15893278 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -97,7 +97,14 @@ export default defineConfig((options) => { onstart: ({ startup }) => { console.log("main process build is complete."); if (!skipLaunchElectron) { - void startup([".", "--no-sandbox"]); + // ここのprocess.argvは以下のような形で渡ってくる: + // ["node", ".../vite.js", (...vite用の引数...), "--", その他引数...] + const args: string[] = [".", "--no-sandbox"]; + const doubleDashIndex = process.argv.indexOf("--"); + if (doubleDashIndex !== -1) { + args.push("--", ...process.argv.slice(doubleDashIndex + 1)); + } + void startup(args); } }, vite: {