diff --git a/package-lock.json b/package-lock.json
index 4471f9645b..c464c20ded 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2282,8 +2282,7 @@
"@types/semver": {
"version": "7.3.9",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz",
- "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==",
- "dev": true
+ "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ=="
},
"@types/serve-static": {
"version": "1.13.9",
@@ -8418,6 +8417,77 @@
"integrity": "sha512-qKD5Pbq+QMk4nea4lMuncUMhpEiQwaJyCW7MrvissnRcBDENhVfDmAqQYRQ3X525oTzhar9Zh1cK0L2d1UKYcw==",
"dev": true
},
+ "electron-updater": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.1.tgz",
+ "integrity": "sha512-YsU1mHqXLrXXmBMsxhxy24PrbaB8rnpZDPmFa2gOkTYk/Ch13+R0fjsRSpPYvqtskVVY0ux8fu+HnUkVkqc7og==",
+ "requires": {
+ "@types/semver": "^7.3.6",
+ "builder-util-runtime": "8.9.1",
+ "fs-extra": "^10.0.0",
+ "js-yaml": "^4.1.0",
+ "lazy-val": "^1.0.5",
+ "lodash.escaperegexp": "^4.1.2",
+ "lodash.isequal": "^4.5.0",
+ "semver": "^7.3.5"
+ },
+ "dependencies": {
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ },
+ "builder-util-runtime": {
+ "version": "8.9.1",
+ "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.1.tgz",
+ "integrity": "sha512-c8a8J3wK6BIVLW7ls+7TRK9igspTbzWmUqxFbgK0m40Ggm6efUbxtWVCGIjc+dtchyr5qAMAUL6iEGRdS/6vwg==",
+ "requires": {
+ "debug": "^4.3.2",
+ "sax": "^1.2.4"
+ }
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "requires": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ }
+ },
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
+ "jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "requires": {
+ "graceful-fs": "^4.1.6",
+ "universalify": "^2.0.0"
+ }
+ },
+ "universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
+ }
+ }
+ },
"electron-window-state": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/electron-window-state/-/electron-window-state-5.0.3.tgz",
@@ -11824,8 +11894,7 @@
"lazy-val": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
- "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
- "dev": true
+ "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q=="
},
"levn": {
"version": "0.3.0",
@@ -12289,12 +12358,22 @@
"integrity": "sha1-uvr7yRi1UVTheRdqALsK76rIVLc=",
"dev": true
},
+ "lodash.escaperegexp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
+ "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="
+ },
"lodash.flatten": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
"dev": true
},
+ "lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
+ },
"lodash.kebabcase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
@@ -13513,8 +13592,7 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"multicast-dns": {
"version": "6.2.3",
@@ -16104,8 +16182,7 @@
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
- "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
- "dev": true
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"saxes": {
"version": "3.1.11",
diff --git a/package.json b/package.json
index 76a1d759ff..3169e7e047 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
"dotenv": "16.0.0",
"electron-log": "4.4.1",
"electron-store": "8.0.0",
+ "electron-updater": "4.6.1",
"electron-window-state": "5.0.3",
"encoding-japanese": "1.0.30",
"immer": "9.0.2",
diff --git a/src/background.ts b/src/background.ts
index e17f64ccfd..278eaa9ea5 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -39,6 +39,7 @@ import {
SplitterPosition,
} from "./type/preload";
+import { autoUpdater } from "electron-updater";
import log from "electron-log";
import dayjs from "dayjs";
import windowStateKeeper from "electron-window-state";
@@ -62,6 +63,9 @@ if (isDevelopment) {
);
}
+autoUpdater.logger = log;
+autoUpdater.autoDownload = false;
+
let win: BrowserWindow;
// 多重起動防止
@@ -219,6 +223,7 @@ const store = new Store<{
experimentalSetting: ExperimentalSetting;
acceptRetrieveTelemetry: AcceptRetrieveTelemetryStatus;
acceptTerms: AcceptTermsStatus;
+ isAutoUpdateCheck: boolean;
splitTextWhenPaste: SplitTextWhenPasteType;
splitterPosition: SplitterPosition;
}>({
@@ -348,6 +353,10 @@ const store = new Store<{
type: "string",
default: "Default",
},
+ isAutoUpdateCheck: {
+ type: "boolean",
+ default: false,
+ },
experimentalSetting: {
type: "object",
properties: {
@@ -1056,6 +1065,22 @@ ipcMainHandle("ACTIVE_POINT_SCROLL_MODE", (_, { newValue }) => {
return store.get("activePointScrollMode", "OFF");
});
+ipcMainHandle("IS_AUTO_UPDATE_CHECK", (_, { newValue }) => {
+ if (newValue !== undefined) {
+ store.set("isAutoUpdateCheck", newValue);
+ }
+
+ return store.get("isAutoUpdateCheck", false);
+});
+
+ipcMainHandle("UPDATE_CHECK", async () => {
+ try {
+ await autoUpdater.checkForUpdatesAndNotify();
+ } catch (err: unknown) {
+ return;
+ }
+});
+
ipcMainHandle("IS_AVAILABLE_GPU_MODE", () => {
return hasSupportedGpu();
});
@@ -1310,6 +1335,10 @@ app.on("ready", async () => {
}
createWindow().then(() => runEngineAll());
+ const isAutoUpdateCheck = store.get("isAutoUpdateCheck");
+ if (isAutoUpdateCheck) {
+ await autoUpdater.checkForUpdatesAndNotify();
+ }
});
app.on("second-instance", () => {
@@ -1333,3 +1362,36 @@ if (isDevelopment) {
});
}
}
+
+//-------------------------------------------
+// 自動アップデート関連のイベント処理
+//-------------------------------------------
+// アップデートをチェック開始
+autoUpdater.on("checking-for-update", () => {
+ log.info(process.pid, "checking-for-update...");
+});
+// アップデートが見つかった
+autoUpdater.on("update-available", () => {
+ log.info(process.pid, "Update available.");
+ const dialogOpts = {
+ type: "info",
+ buttons: ["はい", "いいえ"],
+ message: "アップデート",
+ detail: "新しいバージョンをがありました。公式サイトを開きますか?",
+ };
+ // ダイアログを表示しすぐに再起動するか確認
+ dialog.showMessageBox(win, dialogOpts).then((returnValue) => {
+ if (returnValue.response === 0) {
+ shell.openExternal("https://voicevox.hiroshiba.jp/");
+ log.info(process.pid, "Open Official Site.");
+ }
+ });
+});
+// アップデートがなかった(最新版だった)
+autoUpdater.on("update-not-available", () => {
+ log.info(process.pid, "Update not available.");
+});
+// エラーが発生
+autoUpdater.on("error", (err) => {
+ log.error(process.pid, err);
+});
diff --git a/src/components/MenuBar.vue b/src/components/MenuBar.vue
index 1066d76d9c..48455eb57a 100644
--- a/src/components/MenuBar.vue
+++ b/src/components/MenuBar.vue
@@ -361,6 +361,16 @@ export default defineComponent({
});
},
},
+ { type: "separator" },
+ {
+ type: "button",
+ label: "アップデート確認",
+ onClick() {
+ store.dispatch("IS_UPDATE_CHECK_DIALOG_OPEN", {
+ isUpdateCheckDialogOpen: true,
+ });
+ },
+ },
],
},
{
diff --git a/src/components/SettingDialog.vue b/src/components/SettingDialog.vue
index d322ec1a46..b3f9babf24 100644
--- a/src/components/SettingDialog.vue
+++ b/src/components/SettingDialog.vue
@@ -573,6 +573,24 @@
>
+
+ 自動アップデートチェック
+
+
+
+ 起動時にアップデートチェックを行います
+
+
+
@@ -779,6 +797,8 @@ export default defineComponent({
const experimentalSetting = computed(() => store.state.experimentalSetting);
+ const isAutoUpdateCheck = computed(() => store.state.isAutoUpdateCheck);
+
const currentThemeNameComputed = computed({
get: () => store.state.themeSetting.currentTheme,
set: (currentTheme: string) => {
@@ -890,6 +910,11 @@ export default defineComponent({
});
};
+ const changeIsAutoUpdateCheck = async (isAutoUpdateCheck: boolean) => {
+ if (store.state.isAutoUpdateCheck === isAutoUpdateCheck) return;
+ store.dispatch("SET_IS_AUTO_UPDATE_CHECK", { isAutoUpdateCheck });
+ };
+
const restartEngineProcess = () => {
store.dispatch("RESTART_ENGINE", {
engineKey: store.state.engineInfos[0].key,
@@ -966,6 +991,8 @@ export default defineComponent({
availableAudioOutputDevices,
changeinheritAudioInfo,
changeExperimentalSetting,
+ isAutoUpdateCheck,
+ changeIsAutoUpdateCheck,
restartEngineProcess,
savingSetting,
handleSavingSettingChange,
diff --git a/src/electron/preload.ts b/src/electron/preload.ts
index a2c20356e4..3773ec44fb 100644
--- a/src/electron/preload.ts
+++ b/src/electron/preload.ts
@@ -166,6 +166,14 @@ const api: Sandbox = {
return ipcRendererInvoke("ACTIVE_POINT_SCROLL_MODE", { newValue });
},
+ isAutoUpdateCheck: (newValue) => {
+ return ipcRendererInvoke("IS_AUTO_UPDATE_CHECK", { newValue });
+ },
+
+ updateCheck: () => {
+ return ipcRendererInvoke("UPDATE_CHECK");
+ },
+
isAvailableGPUMode: () => {
return ipcRendererInvoke("IS_AVAILABLE_GPU_MODE");
},
diff --git a/src/store/type.ts b/src/store/type.ts
index 8ffa020cf3..c16cdc30ce 100644
--- a/src/store/type.ts
+++ b/src/store/type.ts
@@ -891,6 +891,7 @@ export type UiStoreState = {
isHelpDialogOpen: boolean;
isSettingDialogOpen: boolean;
isCharacterOrderDialogOpen: boolean;
+ isUpdateCheckDialogOpen: boolean;
isDefaultStyleSelectDialogOpen: boolean;
isHotkeySettingDialogOpen: boolean;
isToolbarSettingDialogOpen: boolean;
@@ -899,6 +900,7 @@ export type UiStoreState = {
isDictionaryManageDialogOpen: boolean;
isMaximized: boolean;
isPinned: boolean;
+ isAutoUpdateCheck: boolean;
isFullscreen: boolean;
};
@@ -949,6 +951,11 @@ type UiStoreTypes = {
action(payload: { isSettingDialogOpen: boolean }): void;
};
+ IS_UPDATE_CHECK_DIALOG_OPEN: {
+ mutation: { isUpdateCheckDialogOpen: boolean };
+ action(payload: { isUpdateCheckDialogOpen: boolean }): void;
+ };
+
IS_HOTKEY_SETTING_DIALOG_OPEN: {
mutation: { isHotkeySettingDialogOpen: boolean };
action(payload: { isHotkeySettingDialogOpen: boolean }): void;
@@ -1021,6 +1028,11 @@ type UiStoreTypes = {
action(payload: { activePointScrollMode: ActivePointScrollMode }): void;
};
+ SET_IS_AUTO_UPDATE_CHECK: {
+ mutation: { isAutoUpdateCheck: boolean };
+ action(payload: { isAutoUpdateCheck: boolean }): void;
+ };
+
DETECT_UNMAXIMIZED: {
mutation: undefined;
action(): void;
diff --git a/src/store/ui.ts b/src/store/ui.ts
index d6a2b496b2..47555a13dd 100644
--- a/src/store/ui.ts
+++ b/src/store/ui.ts
@@ -35,6 +35,7 @@ export const uiStoreState: UiStoreState = {
activePointScrollMode: "OFF",
isHelpDialogOpen: false,
isSettingDialogOpen: false,
+ isUpdateCheckDialogOpen: false,
isHotkeySettingDialogOpen: false,
isToolbarSettingDialogOpen: false,
isCharacterOrderDialogOpen: false,
@@ -45,6 +46,7 @@ export const uiStoreState: UiStoreState = {
isMaximized: false,
isPinned: false,
isFullscreen: false,
+ isAutoUpdateCheck: false,
};
export const uiStore: VoiceVoxStoreOptions =
@@ -94,6 +96,12 @@ export const uiStore: VoiceVoxStoreOptions =
) {
state.isSettingDialogOpen = isSettingDialogOpen;
},
+ IS_UPDATE_CHECK_DIALOG_OPEN(
+ state,
+ { isUpdateCheckDialogOpen }: { isUpdateCheckDialogOpen: boolean }
+ ) {
+ state.isUpdateCheckDialogOpen = isUpdateCheckDialogOpen;
+ },
IS_HOTKEY_SETTING_DIALOG_OPEN(state, { isHotkeySettingDialogOpen }) {
state.isHotkeySettingDialogOpen = isHotkeySettingDialogOpen;
},
@@ -155,6 +163,12 @@ export const uiStore: VoiceVoxStoreOptions =
) {
state.activePointScrollMode = activePointScrollMode;
},
+ SET_IS_AUTO_UPDATE_CHECK(
+ state,
+ { isAutoUpdateCheck }: { isAutoUpdateCheck: boolean }
+ ) {
+ state.isAutoUpdateCheck = isAutoUpdateCheck;
+ },
DETECT_UNMAXIMIZED(state) {
state.isMaximized = false;
},
@@ -225,6 +239,31 @@ export const uiStore: VoiceVoxStoreOptions =
commit("IS_SETTING_DIALOG_OPEN", { isSettingDialogOpen });
},
+ async IS_UPDATE_CHECK_DIALOG_OPEN(
+ { state, commit },
+ { isUpdateCheckDialogOpen }: { isUpdateCheckDialogOpen: boolean }
+ ) {
+ if (state.isUpdateCheckDialogOpen === isUpdateCheckDialogOpen) return;
+
+ if (isUpdateCheckDialogOpen) {
+ commit("LOCK_UI");
+ commit("LOCK_MENUBAR");
+
+ const result: number = await window.electron.showQuestionDialog({
+ type: "info",
+ title: "アップデートチェック",
+ message: "アップデートチェックを行います。\nよろしいですか?",
+ buttons: ["はい", "いいえ"],
+ cancelId: 1,
+ });
+ commit("UNLOCK_UI");
+ commit("UNLOCK_MENUBAR");
+ if (result == 1) {
+ return;
+ }
+ window.electron.updateCheck();
+ }
+ },
IS_HOTKEY_SETTING_DIALOG_OPEN(
{ state, commit },
{ isHotkeySettingDialogOpen }
@@ -384,6 +423,16 @@ export const uiStore: VoiceVoxStoreOptions =
),
});
},
+ async SET_IS_AUTO_UPDATE_CHECK(
+ { commit },
+ { isAutoUpdateCheck }: { isAutoUpdateCheck: boolean }
+ ) {
+ commit("SET_IS_AUTO_UPDATE_CHECK", {
+ isAutoUpdateCheck: await window.electron.isAutoUpdateCheck(
+ isAutoUpdateCheck
+ ),
+ });
+ },
async GET_ACTIVE_POINT_SCROLL_MODE({ commit }) {
commit("SET_ACTIVE_POINT_SCROLL_MODE", {
activePointScrollMode: await window.electron.activePointScrollMode(),
diff --git a/src/type/ipc.d.ts b/src/type/ipc.d.ts
index 8054057061..bcd65f3e6d 100644
--- a/src/type/ipc.d.ts
+++ b/src/type/ipc.d.ts
@@ -123,6 +123,16 @@ type IpcIHData = {
return: import("@/type/preload").ActivePointScrollMode;
};
+ IS_AUTO_UPDATE_CHECK: {
+ args: [obj: { newValue?: boolean }];
+ return: boolean;
+ };
+
+ UPDATE_CHECK: {
+ args: [];
+ return: void;
+ };
+
IS_AVAILABLE_GPU_MODE: {
args: [];
return: boolean;
diff --git a/src/type/preload.d.ts b/src/type/preload.d.ts
index 40d64f2831..61b9d3b58c 100644
--- a/src/type/preload.d.ts
+++ b/src/type/preload.d.ts
@@ -51,6 +51,8 @@ export interface Sandbox {
activePointScrollMode(
newValue?: ActivePointScrollMode
): Promise;
+ isAutoUpdateCheck(newValue?: boolean): Promise;
+ updateCheck(): void;
isAvailableGPUMode(): Promise;
onReceivedIPCMsg(
channel: T,
diff --git a/tests/unit/store/Vuex.spec.ts b/tests/unit/store/Vuex.spec.ts
index 22b73beab6..53cdfb6f6f 100644
--- a/tests/unit/store/Vuex.spec.ts
+++ b/tests/unit/store/Vuex.spec.ts
@@ -35,6 +35,7 @@ describe("store/vuex.js test", () => {
activePointScrollMode: "OFF",
isHelpDialogOpen: false,
isSettingDialogOpen: false,
+ isUpdateCheckDialogOpen: false,
isHotkeySettingDialogOpen: false,
isToolbarSettingDialogOpen: false,
isCharacterOrderDialogOpen: false,
@@ -44,6 +45,7 @@ describe("store/vuex.js test", () => {
isAcceptTermsDialogOpen: false,
isMaximized: false,
savedLastCommandUnixMillisec: null,
+ isAutoUpdateCheck: false,
savingSetting: {
fileEncoding: "UTF-8",
fileNamePattern: "",
@@ -147,8 +149,10 @@ describe("store/vuex.js test", () => {
assert.equal(store.state.useGpu, false);
assert.equal(store.state.inheritAudioInfo, true);
assert.equal(store.state.activePointScrollMode, "OFF");
+ assert.equal(store.state.isAutoUpdateCheck, false);
assert.equal(store.state.isHelpDialogOpen, false);
assert.equal(store.state.isSettingDialogOpen, false);
+ assert.equal(store.state.isUpdateCheckDialogOpen, false);
assert.equal(store.state.isHotkeySettingDialogOpen, false);
assert.equal(store.state.isCharacterOrderDialogOpen, false);
assert.equal(store.state.isDefaultStyleSelectDialogOpen, false);