mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
Prepare Windows release and signing process (#16)
This commit is contained in:
194
src/main.ts
194
src/main.ts
@@ -99,6 +99,7 @@ import { SubtitleTimingTracker } from './subtitle-timing-tracker';
|
||||
import { RuntimeOptionsManager } from './runtime-options';
|
||||
import { downloadToFile, isRemoteMediaPath, parseMediaInfo } from './jimaku/utils';
|
||||
import { createLogger, setLogLevel, type LogLevelSource } from './logger';
|
||||
import { resolveDefaultLogFilePath } from './logger';
|
||||
import {
|
||||
commandNeedsOverlayRuntime,
|
||||
parseArgs,
|
||||
@@ -310,12 +311,17 @@ import {
|
||||
createMaybeFocusExistingFirstRunSetupWindowHandler,
|
||||
createOpenFirstRunSetupWindowHandler,
|
||||
parseFirstRunSetupSubmissionUrl,
|
||||
type FirstRunSetupAction,
|
||||
type FirstRunSetupSubmission,
|
||||
} from './main/runtime/first-run-setup-window';
|
||||
import {
|
||||
detectInstalledFirstRunPlugin,
|
||||
installFirstRunPluginToDefaultLocation,
|
||||
} from './main/runtime/first-run-setup-plugin';
|
||||
import {
|
||||
applyWindowsMpvShortcuts,
|
||||
detectWindowsMpvShortcuts,
|
||||
resolveWindowsMpvShortcutPaths,
|
||||
} from './main/runtime/windows-mpv-shortcuts';
|
||||
import { createImmersionTrackerStartupHandler } from './main/runtime/immersion-startup';
|
||||
import { createBuildImmersionTrackerStartupMainDepsHandler } from './main/runtime/immersion-startup-main-deps';
|
||||
import { createAnilistUpdateQueue } from './core/services/anilist/anilist-update-queue';
|
||||
@@ -344,6 +350,10 @@ import {
|
||||
} from './main/runtime/composers';
|
||||
import { createStartupBootstrapRuntimeDeps } from './main/startup';
|
||||
import { createAppLifecycleRuntimeRunner } from './main/startup-lifecycle';
|
||||
import {
|
||||
registerSecondInstanceHandlerEarly,
|
||||
requestSingleInstanceLockEarly,
|
||||
} from './main/early-single-instance';
|
||||
import { handleMpvCommandFromIpcRuntime } from './main/ipc-mpv-command';
|
||||
import { registerIpcRuntimeServices } from './main/ipc-runtime';
|
||||
import { createAnkiJimakuIpcRuntimeServiceDeps } from './main/dependencies';
|
||||
@@ -362,6 +372,10 @@ import { createMediaRuntimeService } from './main/media-runtime';
|
||||
import { createOverlayVisibilityRuntimeService } from './main/overlay-visibility-runtime';
|
||||
import { createCharacterDictionaryRuntimeService } from './main/character-dictionary-runtime';
|
||||
import { createCharacterDictionaryAutoSyncRuntimeService } from './main/runtime/character-dictionary-auto-sync';
|
||||
import {
|
||||
getPreferredYomitanAnkiServerUrl as getPreferredYomitanAnkiServerUrlRuntime,
|
||||
shouldForceOverrideYomitanAnkiServer,
|
||||
} from './main/runtime/yomitan-anki-server';
|
||||
import {
|
||||
type AnilistMediaGuessRuntimeState,
|
||||
type StartupState,
|
||||
@@ -401,7 +415,11 @@ if (process.platform === 'linux') {
|
||||
app.setName('SubMiner');
|
||||
|
||||
const DEFAULT_TEXTHOOKER_PORT = 5174;
|
||||
const DEFAULT_MPV_LOG_FILE = path.join(os.homedir(), '.cache', 'SubMiner', 'mp.log');
|
||||
const DEFAULT_MPV_LOG_FILE = resolveDefaultLogFilePath({
|
||||
platform: process.platform,
|
||||
homeDir: os.homedir(),
|
||||
appDataDir: process.env.APPDATA,
|
||||
});
|
||||
const ANILIST_SETUP_CLIENT_ID_URL = 'https://anilist.co/api/v2/oauth/authorize';
|
||||
const ANILIST_SETUP_RESPONSE_TYPE = 'token';
|
||||
const ANILIST_DEFAULT_CLIENT_ID = '36084';
|
||||
@@ -462,6 +480,8 @@ function applyJellyfinMpvDefaults(
|
||||
}
|
||||
|
||||
const CONFIG_DIR = resolveConfigDir({
|
||||
platform: process.platform,
|
||||
appDataDir: process.env.APPDATA,
|
||||
xdgConfigHome: process.env.XDG_CONFIG_HOME,
|
||||
homeDir: os.homedir(),
|
||||
existsSync: fs.existsSync,
|
||||
@@ -480,7 +500,7 @@ const configService = (() => {
|
||||
{
|
||||
logError: (details) => console.error(details),
|
||||
showErrorBox: (title, details) => dialog.showErrorBox(title, details),
|
||||
quit: () => app.quit(),
|
||||
quit: () => requestAppQuit(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -552,6 +572,22 @@ const appLogger = {
|
||||
},
|
||||
};
|
||||
const runtimeRegistry = createMainRuntimeRegistry();
|
||||
const appLifecycleApp = {
|
||||
requestSingleInstanceLock: () => requestSingleInstanceLockEarly(app),
|
||||
quit: () => app.quit(),
|
||||
on: (event: string, listener: (...args: unknown[]) => void) => {
|
||||
if (event === 'second-instance') {
|
||||
registerSecondInstanceHandlerEarly(
|
||||
app,
|
||||
listener as (_event: unknown, argv: string[]) => void,
|
||||
);
|
||||
return app;
|
||||
}
|
||||
app.on(event as Parameters<typeof app.on>[0], listener as (...args: any[]) => void);
|
||||
return app;
|
||||
},
|
||||
whenReady: () => app.whenReady(),
|
||||
};
|
||||
|
||||
const buildGetDefaultSocketPathMainDepsHandler = createBuildGetDefaultSocketPathMainDepsHandler({
|
||||
platform: process.platform,
|
||||
@@ -568,11 +604,23 @@ if (!fs.existsSync(USER_DATA_PATH)) {
|
||||
}
|
||||
app.setPath('userData', USER_DATA_PATH);
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
let forceQuitTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function requestAppQuit(): void {
|
||||
if (!forceQuitTimer) {
|
||||
forceQuitTimer = setTimeout(() => {
|
||||
logger.warn('App quit timed out; forcing process exit.');
|
||||
app.exit(0);
|
||||
}, 2000);
|
||||
}
|
||||
app.quit();
|
||||
}
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
requestAppQuit();
|
||||
});
|
||||
process.on('SIGTERM', () => {
|
||||
app.quit();
|
||||
requestAppQuit();
|
||||
});
|
||||
|
||||
const overlayManager = createOverlayManager();
|
||||
@@ -623,7 +671,13 @@ const appState = createAppState({
|
||||
texthookerPort: DEFAULT_TEXTHOOKER_PORT,
|
||||
});
|
||||
let firstRunSetupMessage: string | null = null;
|
||||
const resolveWindowsMpvShortcutRuntimePaths = () =>
|
||||
resolveWindowsMpvShortcutPaths({
|
||||
appDataDir: app.getPath('appData'),
|
||||
desktopDir: app.getPath('desktop'),
|
||||
});
|
||||
const firstRunSetupService = createFirstRunSetupService({
|
||||
platform: process.platform,
|
||||
configDir: CONFIG_DIR,
|
||||
getYomitanDictionaryCount: async () => {
|
||||
await ensureYomitanExtensionLoaded();
|
||||
@@ -650,6 +704,31 @@ const firstRunSetupService = createFirstRunSetupService({
|
||||
appPath: app.getAppPath(),
|
||||
resourcesPath: process.resourcesPath,
|
||||
}),
|
||||
detectWindowsMpvShortcuts: () => {
|
||||
if (process.platform !== 'win32') {
|
||||
return {
|
||||
startMenuInstalled: false,
|
||||
desktopInstalled: false,
|
||||
};
|
||||
}
|
||||
return detectWindowsMpvShortcuts(resolveWindowsMpvShortcutRuntimePaths());
|
||||
},
|
||||
applyWindowsMpvShortcuts: async (preferences) => {
|
||||
if (process.platform !== 'win32') {
|
||||
return {
|
||||
ok: true,
|
||||
status: 'unknown' as const,
|
||||
message: '',
|
||||
};
|
||||
}
|
||||
return applyWindowsMpvShortcuts({
|
||||
preferences,
|
||||
paths: resolveWindowsMpvShortcutRuntimePaths(),
|
||||
exePath: process.execPath,
|
||||
writeShortcutLink: (shortcutPath, operation, details) =>
|
||||
shell.writeShortcutLink(shortcutPath, operation, details),
|
||||
});
|
||||
},
|
||||
onStateChanged: (state) => {
|
||||
appState.firstRunSetupCompleted = state.status === 'completed';
|
||||
if (appTray) {
|
||||
@@ -969,8 +1048,22 @@ const overlayShortcutsRuntime = createOverlayShortcutsRuntimeService(
|
||||
appState.shortcutsRegistered = registered;
|
||||
},
|
||||
isOverlayRuntimeInitialized: () => appState.overlayRuntimeInitialized,
|
||||
isMacOSPlatform: () => process.platform === 'darwin',
|
||||
isTrackedMpvWindowFocused: () => appState.windowTracker?.isFocused() ?? false,
|
||||
isOverlayShortcutContextActive: () => {
|
||||
if (process.platform !== 'win32') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!overlayManager.getVisibleOverlayVisible()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const windowTracker = appState.windowTracker;
|
||||
if (!windowTracker || !windowTracker.isTracking()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return windowTracker.isTargetWindowFocused();
|
||||
},
|
||||
showMpvOsd: (text: string) => showMpvOsd(text),
|
||||
openRuntimeOptionsPalette: () => {
|
||||
openRuntimeOptionsPalette();
|
||||
@@ -1080,22 +1173,26 @@ const configHotReloadRuntime = createConfigHotReloadRuntime(
|
||||
);
|
||||
|
||||
const buildDictionaryRootsHandler = createBuildDictionaryRootsMainHandler({
|
||||
platform: process.platform,
|
||||
dirname: __dirname,
|
||||
appPath: app.getAppPath(),
|
||||
resourcesPath: process.resourcesPath,
|
||||
userDataPath: USER_DATA_PATH,
|
||||
appUserDataPath: app.getPath('userData'),
|
||||
homeDir: os.homedir(),
|
||||
appDataDir: process.env.APPDATA,
|
||||
cwd: process.cwd(),
|
||||
joinPath: (...parts) => path.join(...parts),
|
||||
});
|
||||
const buildFrequencyDictionaryRootsHandler = createBuildFrequencyDictionaryRootsMainHandler({
|
||||
platform: process.platform,
|
||||
dirname: __dirname,
|
||||
appPath: app.getAppPath(),
|
||||
resourcesPath: process.resourcesPath,
|
||||
userDataPath: USER_DATA_PATH,
|
||||
appUserDataPath: app.getPath('userData'),
|
||||
homeDir: os.homedir(),
|
||||
appDataDir: process.env.APPDATA,
|
||||
cwd: process.cwd(),
|
||||
joinPath: (...parts) => path.join(...parts),
|
||||
});
|
||||
@@ -1292,6 +1389,7 @@ const overlayVisibilityRuntime = createOverlayVisibilityRuntimeService(
|
||||
overlayShortcutsRuntime.syncOverlayShortcuts();
|
||||
},
|
||||
isMacOSPlatform: () => process.platform === 'darwin',
|
||||
isWindowsPlatform: () => process.platform === 'win32',
|
||||
showOverlayLoadingOsd: (message: string) => {
|
||||
showMpvOsd(message);
|
||||
},
|
||||
@@ -1687,28 +1785,37 @@ const openFirstRunSetupWindowHandler = createOpenFirstRunSetupWindowHandler({
|
||||
canFinish: snapshot.canFinish,
|
||||
pluginStatus: snapshot.pluginStatus,
|
||||
pluginInstallPathSummary: snapshot.pluginInstallPathSummary,
|
||||
windowsMpvShortcuts: snapshot.windowsMpvShortcuts,
|
||||
message: firstRunSetupMessage,
|
||||
};
|
||||
},
|
||||
buildSetupHtml: (model) => buildFirstRunSetupHtml(model),
|
||||
parseSubmissionUrl: (rawUrl) => parseFirstRunSetupSubmissionUrl(rawUrl),
|
||||
handleAction: async (action: FirstRunSetupAction) => {
|
||||
if (action === 'install-plugin') {
|
||||
handleAction: async (submission: FirstRunSetupSubmission) => {
|
||||
if (submission.action === 'install-plugin') {
|
||||
const snapshot = await firstRunSetupService.installMpvPlugin();
|
||||
firstRunSetupMessage = snapshot.message;
|
||||
return;
|
||||
}
|
||||
if (action === 'open-yomitan-settings') {
|
||||
if (submission.action === 'configure-windows-mpv-shortcuts') {
|
||||
const snapshot = await firstRunSetupService.configureWindowsMpvShortcuts({
|
||||
startMenuEnabled: submission.startMenuEnabled === true,
|
||||
desktopEnabled: submission.desktopEnabled === true,
|
||||
});
|
||||
firstRunSetupMessage = snapshot.message;
|
||||
return;
|
||||
}
|
||||
if (submission.action === 'open-yomitan-settings') {
|
||||
openYomitanSettings();
|
||||
firstRunSetupMessage = 'Opened Yomitan settings. Install dictionaries, then refresh status.';
|
||||
return;
|
||||
}
|
||||
if (action === 'refresh') {
|
||||
if (submission.action === 'refresh') {
|
||||
const snapshot = await firstRunSetupService.refreshStatus('Status refreshed.');
|
||||
firstRunSetupMessage = snapshot.message;
|
||||
return;
|
||||
}
|
||||
if (action === 'skip-plugin') {
|
||||
if (submission.action === 'skip-plugin') {
|
||||
await firstRunSetupService.skipPluginInstall();
|
||||
firstRunSetupMessage = 'mpv plugin installation skipped.';
|
||||
return;
|
||||
@@ -1731,6 +1838,8 @@ const openFirstRunSetupWindowHandler = createOpenFirstRunSetupWindowHandler({
|
||||
await firstRunSetupService.markSetupCancelled();
|
||||
},
|
||||
isSetupCompleted: () => firstRunSetupService.isSetupCompleted(),
|
||||
shouldQuitWhenClosedIncomplete: () => !appState.backgroundMode,
|
||||
quitApp: () => requestAppQuit(),
|
||||
clearSetupWindow: () => {
|
||||
appState.firstRunSetupWindow = null;
|
||||
},
|
||||
@@ -2151,7 +2260,7 @@ const {
|
||||
app.on('open-url', listener);
|
||||
},
|
||||
registerSecondInstance: (listener) => {
|
||||
app.on('second-instance', listener);
|
||||
registerSecondInstanceHandlerEarly(app, listener);
|
||||
},
|
||||
handleAnilistSetupProtocolUrl: (rawUrl) => handleAnilistSetupProtocolUrl(rawUrl),
|
||||
findAnilistSetupDeepLinkArgvUrl: (argv) => findAnilistSetupDeepLinkArgvUrl(argv),
|
||||
@@ -2202,6 +2311,14 @@ const {
|
||||
clearJellyfinSetupWindow: () => {
|
||||
appState.jellyfinSetupWindow = null;
|
||||
},
|
||||
getFirstRunSetupWindow: () => appState.firstRunSetupWindow,
|
||||
clearFirstRunSetupWindow: () => {
|
||||
appState.firstRunSetupWindow = null;
|
||||
},
|
||||
getYomitanSettingsWindow: () => appState.yomitanSettingsWindow,
|
||||
clearYomitanSettingsWindow: () => {
|
||||
appState.yomitanSettingsWindow = null;
|
||||
},
|
||||
stopJellyfinRemoteSession: () => stopJellyfinRemoteSession(),
|
||||
stopDiscordPresenceService: () => {
|
||||
void appState.discordPresenceService?.stop();
|
||||
@@ -2266,10 +2383,7 @@ const { appReadyRuntimeRunner } = composeAppReadyRuntime({
|
||||
failHandlers: {
|
||||
logError: (details) => logger.error(details),
|
||||
showErrorBox: (title, details) => dialog.showErrorBox(title, details),
|
||||
setExitCode: (code) => {
|
||||
process.exitCode = code;
|
||||
},
|
||||
quit: () => app.quit(),
|
||||
quit: () => requestAppQuit(),
|
||||
},
|
||||
},
|
||||
criticalConfigErrorMainDeps: {
|
||||
@@ -2277,10 +2391,7 @@ const { appReadyRuntimeRunner } = composeAppReadyRuntime({
|
||||
failHandlers: {
|
||||
logError: (message) => logger.error(message),
|
||||
showErrorBox: (title, message) => dialog.showErrorBox(title, message),
|
||||
setExitCode: (code) => {
|
||||
process.exitCode = code;
|
||||
},
|
||||
quit: () => app.quit(),
|
||||
quit: () => requestAppQuit(),
|
||||
},
|
||||
},
|
||||
appReadyRuntimeMainDeps: {
|
||||
@@ -2432,7 +2543,7 @@ const { runAndApplyStartupState } = runtimeRegistry.startup.createStartupRuntime
|
||||
ReturnType<typeof createStartupBootstrapRuntimeDeps>
|
||||
>({
|
||||
appLifecycleRuntimeRunnerMainDeps: {
|
||||
app,
|
||||
app: appLifecycleApp,
|
||||
platform: process.platform,
|
||||
shouldStartApp: (nextArgs: CliArgs) => shouldStartApp(nextArgs),
|
||||
parseArgs: (argv: string[]) => parseArgs(argv),
|
||||
@@ -2476,7 +2587,7 @@ const { runAndApplyStartupState } = runtimeRegistry.startup.createStartupRuntime
|
||||
setExitCode: (code) => {
|
||||
process.exitCode = code;
|
||||
},
|
||||
quitApp: () => app.quit(),
|
||||
quitApp: () => requestAppQuit(),
|
||||
logGenerateConfigError: (message) => logger.error(message),
|
||||
startAppLifecycle,
|
||||
}),
|
||||
@@ -2510,6 +2621,7 @@ const handleCliCommand = createCliCommandRuntimeHandler({
|
||||
const handleInitialArgsRuntimeHandler = createInitialArgsRuntimeHandler({
|
||||
getInitialArgs: () => appState.initialArgs,
|
||||
isBackgroundMode: () => appState.backgroundMode,
|
||||
shouldEnsureTrayOnStartup: () => process.platform === 'win32',
|
||||
ensureTray: () => ensureTray(),
|
||||
isTexthookerOnlyMode: () => appState.texthookerOnlyMode,
|
||||
hasImmersionTracker: () => Boolean(appState.immersionTracker),
|
||||
@@ -2526,10 +2638,10 @@ const {
|
||||
createMpvClientRuntimeService: createMpvClientRuntimeServiceHandler,
|
||||
updateMpvSubtitleRenderMetrics: updateMpvSubtitleRenderMetricsHandler,
|
||||
tokenizeSubtitle,
|
||||
isTokenizationWarmupReady,
|
||||
createMecabTokenizerAndCheck,
|
||||
prewarmSubtitleDictionaries,
|
||||
startBackgroundWarmups,
|
||||
isTokenizationWarmupReady,
|
||||
} = composeMpvRuntimeHandlers<
|
||||
MpvIpcClient,
|
||||
ReturnType<typeof createTokenizerDepsRuntime>,
|
||||
@@ -2541,7 +2653,7 @@ const {
|
||||
scheduleQuitCheck: (callback) => {
|
||||
setTimeout(callback, 500);
|
||||
},
|
||||
quitApp: () => app.quit(),
|
||||
quitApp: () => requestAppQuit(),
|
||||
reportJellyfinRemoteStopped: () => {
|
||||
void reportJellyfinRemoteStopped();
|
||||
},
|
||||
@@ -2566,12 +2678,6 @@ const {
|
||||
}
|
||||
mediaRuntime.updateCurrentMediaPath(path);
|
||||
},
|
||||
signalAutoplayReadyIfWarm: (path) => {
|
||||
if (!isTokenizationWarmupReady()) {
|
||||
return;
|
||||
}
|
||||
maybeSignalPluginAutoplayReady({ text: path, tokens: null }, { forceWhilePaused: true });
|
||||
},
|
||||
restoreMpvSubVisibility: () => {
|
||||
restoreOverlayMpvSubtitles();
|
||||
},
|
||||
@@ -2588,6 +2694,15 @@ const {
|
||||
syncImmersionMediaState: () => {
|
||||
immersionMediaRuntime.syncFromCurrentMediaState();
|
||||
},
|
||||
signalAutoplayReadyIfWarm: () => {
|
||||
if (!isTokenizationWarmupReady()) {
|
||||
return;
|
||||
}
|
||||
maybeSignalPluginAutoplayReady(
|
||||
{ text: '__warm__', tokens: null },
|
||||
{ forceWhilePaused: true },
|
||||
);
|
||||
},
|
||||
scheduleCharacterDictionarySync: () => {
|
||||
characterDictionaryAutoSyncRuntime.scheduleSync();
|
||||
},
|
||||
@@ -2849,13 +2964,7 @@ async function ensureYomitanExtensionLoaded(): Promise<Extension | null> {
|
||||
let lastSyncedYomitanAnkiServer: string | null = null;
|
||||
|
||||
function getPreferredYomitanAnkiServerUrl(): string {
|
||||
const config = getResolvedConfig().ankiConnect;
|
||||
if (config.proxy?.enabled) {
|
||||
const host = config.proxy.host || '127.0.0.1';
|
||||
const port = config.proxy.port || 8766;
|
||||
return `http://${host}:${port}`;
|
||||
}
|
||||
return config.url;
|
||||
return getPreferredYomitanAnkiServerUrlRuntime(getResolvedConfig().ankiConnect);
|
||||
}
|
||||
|
||||
function getYomitanParserRuntimeDeps() {
|
||||
@@ -2894,7 +3003,7 @@ async function syncYomitanDefaultProfileAnkiServer(): Promise<void> {
|
||||
},
|
||||
},
|
||||
{
|
||||
forceOverride: getResolvedConfig().ankiConnect.proxy?.enabled === true,
|
||||
forceOverride: shouldForceOverrideYomitanAnkiServer(getResolvedConfig().ankiConnect),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -3244,7 +3353,7 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
|
||||
overlayModalRuntime.notifyOverlayModalOpened(modal);
|
||||
},
|
||||
openYomitanSettings: () => openYomitanSettings(),
|
||||
quitApp: () => app.quit(),
|
||||
quitApp: () => requestAppQuit(),
|
||||
toggleVisibleOverlay: () => toggleVisibleOverlay(),
|
||||
tokenizeCurrentSubtitle: () => tokenizeSubtitle(appState.currentSubText),
|
||||
getCurrentSubtitleRaw: () => appState.currentSubText,
|
||||
@@ -3345,7 +3454,7 @@ const createCliCommandContextHandler = createCliCommandContextFactory({
|
||||
cycleSecondarySubMode: () => handleCycleSecondarySubMode(),
|
||||
openRuntimeOptionsPalette: () => openRuntimeOptionsPalette(),
|
||||
printHelp: () => printHelp(DEFAULT_TEXTHOOKER_PORT),
|
||||
stopApp: () => app.quit(),
|
||||
stopApp: () => requestAppQuit(),
|
||||
hasMainWindow: () => Boolean(overlayManager.getMainWindow()),
|
||||
getMultiCopyTimeoutMs: () => getConfiguredShortcuts().multiCopyTimeoutMs,
|
||||
schedule: (fn: () => void, delayMs: number) => setTimeout(fn, delayMs),
|
||||
@@ -3395,11 +3504,12 @@ const { ensureTray: ensureTrayHandler, destroyTray: destroyTrayHandler } =
|
||||
setVisibleOverlayVisible: (visible) => setVisibleOverlayVisible(visible),
|
||||
showFirstRunSetup: () => !firstRunSetupService.isSetupCompleted(),
|
||||
openFirstRunSetupWindow: () => openFirstRunSetupWindow(),
|
||||
showWindowsMpvLauncherSetup: () => process.platform === 'win32',
|
||||
openYomitanSettings: () => openYomitanSettings(),
|
||||
openRuntimeOptionsPalette: () => openRuntimeOptionsPalette(),
|
||||
openJellyfinSetupWindow: () => openJellyfinSetupWindow(),
|
||||
openAnilistSetupWindow: () => openAnilistSetupWindow(),
|
||||
quitApp: () => app.quit(),
|
||||
quitApp: () => requestAppQuit(),
|
||||
},
|
||||
ensureTrayDeps: {
|
||||
getTray: () => appTray,
|
||||
|
||||
Reference in New Issue
Block a user