mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-09 16:19:25 -07:00
Add Windows mpv executable path override
This commit is contained in:
@@ -35,8 +35,17 @@ const {
|
||||
startupWarmups,
|
||||
auto_start_overlay,
|
||||
} = CORE_DEFAULT_CONFIG;
|
||||
const { ankiConnect, jimaku, anilist, yomitan, jellyfin, discordPresence, ai, youtubeSubgen } =
|
||||
INTEGRATIONS_DEFAULT_CONFIG;
|
||||
const {
|
||||
ankiConnect,
|
||||
jimaku,
|
||||
anilist,
|
||||
mpv,
|
||||
yomitan,
|
||||
jellyfin,
|
||||
discordPresence,
|
||||
ai,
|
||||
youtubeSubgen,
|
||||
} = INTEGRATIONS_DEFAULT_CONFIG;
|
||||
const { subtitleStyle, subtitleSidebar } = SUBTITLE_DEFAULT_CONFIG;
|
||||
const { immersionTracking } = IMMERSION_DEFAULT_CONFIG;
|
||||
const { stats } = STATS_DEFAULT_CONFIG;
|
||||
@@ -60,6 +69,7 @@ export const DEFAULT_CONFIG: ResolvedConfig = {
|
||||
auto_start_overlay,
|
||||
jimaku,
|
||||
anilist,
|
||||
mpv,
|
||||
yomitan,
|
||||
jellyfin,
|
||||
discordPresence,
|
||||
|
||||
@@ -5,6 +5,7 @@ export const INTEGRATIONS_DEFAULT_CONFIG: Pick<
|
||||
| 'ankiConnect'
|
||||
| 'jimaku'
|
||||
| 'anilist'
|
||||
| 'mpv'
|
||||
| 'yomitan'
|
||||
| 'jellyfin'
|
||||
| 'discordPresence'
|
||||
@@ -90,6 +91,9 @@ export const INTEGRATIONS_DEFAULT_CONFIG: Pick<
|
||||
languagePreference: 'ja',
|
||||
maxEntryResults: 10,
|
||||
},
|
||||
mpv: {
|
||||
executablePath: '',
|
||||
},
|
||||
anilist: {
|
||||
enabled: false,
|
||||
accessToken: '',
|
||||
|
||||
@@ -28,6 +28,7 @@ test('config option registry includes critical paths and has unique entries', ()
|
||||
'ankiConnect.enabled',
|
||||
'anilist.characterDictionary.enabled',
|
||||
'anilist.characterDictionary.collapsibleSections.description',
|
||||
'mpv.executablePath',
|
||||
'yomitan.externalProfilePath',
|
||||
'immersionTracking.enabled',
|
||||
]) {
|
||||
@@ -48,6 +49,7 @@ test('config template sections include expected domains and unique keys', () =>
|
||||
'subtitleStyle',
|
||||
'ankiConnect',
|
||||
'yomitan',
|
||||
'mpv',
|
||||
'immersionTracking',
|
||||
];
|
||||
|
||||
|
||||
@@ -238,6 +238,13 @@ export function buildIntegrationConfigOptionRegistry(
|
||||
description:
|
||||
'Optional external Yomitan Electron profile path to use in read-only mode for shared dictionaries/settings. Example: ~/.config/gsm_overlay',
|
||||
},
|
||||
{
|
||||
path: 'mpv.executablePath',
|
||||
kind: 'string',
|
||||
defaultValue: defaultConfig.mpv.executablePath,
|
||||
description:
|
||||
'Optional absolute path to mpv.exe for Windows launch flows. Leave empty to auto-discover from SUBMINER_MPV_PATH or PATH.',
|
||||
},
|
||||
{
|
||||
path: 'jellyfin.enabled',
|
||||
kind: 'boolean',
|
||||
|
||||
@@ -153,6 +153,14 @@ const INTEGRATION_TEMPLATE_SECTIONS: ConfigTemplateSection[] = [
|
||||
],
|
||||
key: 'yomitan',
|
||||
},
|
||||
{
|
||||
title: 'MPV Launcher',
|
||||
description: [
|
||||
'Optional mpv.exe override for Windows playback entry points.',
|
||||
'Leave mpv.executablePath blank to auto-discover mpv.exe from SUBMINER_MPV_PATH or PATH.',
|
||||
],
|
||||
key: 'mpv',
|
||||
},
|
||||
{
|
||||
title: 'Jellyfin',
|
||||
description: [
|
||||
|
||||
31
src/config/resolve/integrations.test.ts
Normal file
31
src/config/resolve/integrations.test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { resolveConfig } from '../resolve';
|
||||
|
||||
test('resolveConfig trims configured mpv executable path', () => {
|
||||
const { resolved, warnings } = resolveConfig({
|
||||
mpv: {
|
||||
executablePath: ' C:\\Program Files\\mpv\\mpv.exe ',
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(resolved.mpv.executablePath, 'C:\\Program Files\\mpv\\mpv.exe');
|
||||
assert.deepEqual(warnings, []);
|
||||
});
|
||||
|
||||
test('resolveConfig warns for invalid mpv executable path type', () => {
|
||||
const { resolved, warnings } = resolveConfig({
|
||||
mpv: {
|
||||
executablePath: 42 as never,
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(resolved.mpv.executablePath, '');
|
||||
assert.equal(warnings.length, 1);
|
||||
assert.deepEqual(warnings[0], {
|
||||
path: 'mpv.executablePath',
|
||||
value: 42,
|
||||
fallback: '',
|
||||
message: 'Expected string.',
|
||||
});
|
||||
});
|
||||
@@ -228,6 +228,22 @@ export function applyIntegrationConfig(context: ResolveContext): void {
|
||||
warn('yomitan', src.yomitan, resolved.yomitan, 'Expected object.');
|
||||
}
|
||||
|
||||
if (isObject(src.mpv)) {
|
||||
const executablePath = asString(src.mpv.executablePath);
|
||||
if (executablePath !== undefined) {
|
||||
resolved.mpv.executablePath = executablePath.trim();
|
||||
} else if (src.mpv.executablePath !== undefined) {
|
||||
warn(
|
||||
'mpv.executablePath',
|
||||
src.mpv.executablePath,
|
||||
resolved.mpv.executablePath,
|
||||
'Expected string.',
|
||||
);
|
||||
}
|
||||
} else if (src.mpv !== undefined) {
|
||||
warn('mpv', src.mpv, resolved.mpv, 'Expected object.');
|
||||
}
|
||||
|
||||
if (isObject(src.jellyfin)) {
|
||||
const enabled = asBoolean(src.jellyfin.enabled);
|
||||
if (enabled !== undefined) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import path from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { app, dialog } from 'electron';
|
||||
import { printHelp } from './cli/help';
|
||||
import { loadRawConfigStrict } from './config/load';
|
||||
import {
|
||||
configureEarlyAppPaths,
|
||||
normalizeLaunchMpvExtraArgs,
|
||||
@@ -35,6 +36,21 @@ function applySanitizedEnv(sanitizedEnv: NodeJS.ProcessEnv): void {
|
||||
}
|
||||
}
|
||||
|
||||
function readConfiguredWindowsMpvExecutablePath(configDir: string): string {
|
||||
const loadResult = loadRawConfigStrict({
|
||||
configDir,
|
||||
configFileJsonc: path.join(configDir, 'config.jsonc'),
|
||||
configFileJson: path.join(configDir, 'config.json'),
|
||||
});
|
||||
if (!loadResult.ok) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return typeof loadResult.config.mpv?.executablePath === 'string'
|
||||
? loadResult.config.mpv.executablePath.trim()
|
||||
: '';
|
||||
}
|
||||
|
||||
function resolveBundledWindowsMpvPluginEntrypoint(): string | undefined {
|
||||
const assets = resolvePackagedFirstRunPluginAssets({
|
||||
dirname: __dirname,
|
||||
@@ -50,7 +66,7 @@ function resolveBundledWindowsMpvPluginEntrypoint(): string | undefined {
|
||||
|
||||
process.argv = normalizeStartupArgv(process.argv, process.env);
|
||||
applySanitizedEnv(sanitizeStartupEnv(process.env));
|
||||
configureEarlyAppPaths(app);
|
||||
const userDataPath = configureEarlyAppPaths(app);
|
||||
|
||||
if (shouldDetachBackgroundLaunch(process.argv, process.env)) {
|
||||
const child = spawn(process.execPath, process.argv.slice(1), {
|
||||
@@ -87,6 +103,7 @@ if (shouldHandleLaunchMpvAtEntry(process.argv, process.env)) {
|
||||
normalizeLaunchMpvExtraArgs(process.argv),
|
||||
process.execPath,
|
||||
resolveBundledWindowsMpvPluginEntrypoint(),
|
||||
readConfiguredWindowsMpvExecutablePath(userDataPath),
|
||||
);
|
||||
app.exit(result.ok ? 0 : 1);
|
||||
});
|
||||
|
||||
16
src/main.ts
16
src/main.ts
@@ -1037,6 +1037,9 @@ const youtubePlaybackRuntime = createYoutubePlaybackRuntime({
|
||||
showError: (title, content) => dialog.showErrorBox(title, content),
|
||||
}),
|
||||
[...args, `--log-file=${DEFAULT_MPV_LOG_PATH}`],
|
||||
undefined,
|
||||
undefined,
|
||||
getResolvedConfig().mpv.executablePath,
|
||||
),
|
||||
waitForYoutubeMpvConnected: (timeoutMs) => waitForYoutubeMpvConnected(timeoutMs),
|
||||
prepareYoutubePlaybackInMpv: (request) => prepareYoutubePlaybackInMpv(request),
|
||||
@@ -2220,6 +2223,7 @@ const openFirstRunSetupWindowHandler = createOpenFirstRunSetupWindowHandler({
|
||||
externalYomitanConfigured: snapshot.externalYomitanConfigured,
|
||||
pluginStatus: snapshot.pluginStatus,
|
||||
pluginInstallPathSummary: snapshot.pluginInstallPathSummary,
|
||||
mpvExecutablePath: getResolvedConfig().mpv.executablePath,
|
||||
windowsMpvShortcuts: snapshot.windowsMpvShortcuts,
|
||||
message: firstRunSetupMessage,
|
||||
};
|
||||
@@ -2232,6 +2236,18 @@ const openFirstRunSetupWindowHandler = createOpenFirstRunSetupWindowHandler({
|
||||
firstRunSetupMessage = snapshot.message;
|
||||
return;
|
||||
}
|
||||
if (submission.action === 'configure-mpv-executable-path') {
|
||||
const mpvExecutablePath = submission.mpvExecutablePath?.trim() ?? '';
|
||||
configService.patchRawConfig({
|
||||
mpv: {
|
||||
executablePath: mpvExecutablePath,
|
||||
},
|
||||
});
|
||||
firstRunSetupMessage = mpvExecutablePath
|
||||
? `Saved mpv executable path: ${mpvExecutablePath}`
|
||||
: 'Cleared mpv executable path. SubMiner will auto-discover mpv.exe from PATH.';
|
||||
return;
|
||||
}
|
||||
if (submission.action === 'configure-windows-mpv-shortcuts') {
|
||||
const snapshot = await firstRunSetupService.configureWindowsMpvShortcuts({
|
||||
startMenuEnabled: submission.startMenuEnabled === true,
|
||||
|
||||
@@ -16,6 +16,7 @@ test('buildFirstRunSetupHtml renders macchiato setup actions and disabled finish
|
||||
externalYomitanConfigured: false,
|
||||
pluginStatus: 'required',
|
||||
pluginInstallPathSummary: null,
|
||||
mpvExecutablePath: '',
|
||||
windowsMpvShortcuts: {
|
||||
supported: false,
|
||||
startMenuEnabled: true,
|
||||
@@ -43,6 +44,7 @@ test('buildFirstRunSetupHtml switches plugin action to reinstall when already in
|
||||
externalYomitanConfigured: false,
|
||||
pluginStatus: 'installed',
|
||||
pluginInstallPathSummary: '/tmp/mpv',
|
||||
mpvExecutablePath: 'C:\\Program Files\\mpv\\mpv.exe',
|
||||
windowsMpvShortcuts: {
|
||||
supported: true,
|
||||
startMenuEnabled: true,
|
||||
@@ -55,6 +57,8 @@ test('buildFirstRunSetupHtml switches plugin action to reinstall when already in
|
||||
});
|
||||
|
||||
assert.match(html, /Reinstall mpv plugin/);
|
||||
assert.match(html, /mpv executable path/);
|
||||
assert.match(html, /Leave blank to auto-discover mpv\.exe from PATH\./);
|
||||
assert.match(
|
||||
html,
|
||||
/Finish stays unlocked once the mpv plugin is installed and Yomitan reports at least one installed dictionary\./,
|
||||
@@ -69,6 +73,7 @@ test('buildFirstRunSetupHtml explains the config blocker when setup is missing c
|
||||
externalYomitanConfigured: false,
|
||||
pluginStatus: 'required',
|
||||
pluginInstallPathSummary: null,
|
||||
mpvExecutablePath: '',
|
||||
windowsMpvShortcuts: {
|
||||
supported: false,
|
||||
startMenuEnabled: true,
|
||||
@@ -91,6 +96,7 @@ test('buildFirstRunSetupHtml explains external yomitan mode and keeps finish ena
|
||||
externalYomitanConfigured: true,
|
||||
pluginStatus: 'installed',
|
||||
pluginInstallPathSummary: null,
|
||||
mpvExecutablePath: '',
|
||||
windowsMpvShortcuts: {
|
||||
supported: false,
|
||||
startMenuEnabled: true,
|
||||
@@ -107,6 +113,15 @@ test('buildFirstRunSetupHtml explains external yomitan mode and keeps finish ena
|
||||
});
|
||||
|
||||
test('parseFirstRunSetupSubmissionUrl parses supported custom actions', () => {
|
||||
assert.deepEqual(
|
||||
parseFirstRunSetupSubmissionUrl(
|
||||
'subminer://first-run-setup?action=configure-mpv-executable-path&mpvExecutablePath=C%3A%5CApps%5Cmpv%5Cmpv.exe',
|
||||
),
|
||||
{
|
||||
action: 'configure-mpv-executable-path',
|
||||
mpvExecutablePath: 'C:\\Apps\\mpv\\mpv.exe',
|
||||
},
|
||||
);
|
||||
assert.deepEqual(parseFirstRunSetupSubmissionUrl('subminer://first-run-setup?action=refresh'), {
|
||||
action: 'refresh',
|
||||
});
|
||||
@@ -192,6 +207,7 @@ test('closing incomplete first-run setup quits app outside background mode', asy
|
||||
externalYomitanConfigured: false,
|
||||
pluginStatus: 'required',
|
||||
pluginInstallPathSummary: null,
|
||||
mpvExecutablePath: '',
|
||||
windowsMpvShortcuts: {
|
||||
supported: false,
|
||||
startMenuEnabled: true,
|
||||
|
||||
@@ -17,6 +17,7 @@ type FirstRunSetupWindowLike = FocusableWindowLike & {
|
||||
};
|
||||
|
||||
export type FirstRunSetupAction =
|
||||
| 'configure-mpv-executable-path'
|
||||
| 'install-plugin'
|
||||
| 'configure-windows-mpv-shortcuts'
|
||||
| 'open-yomitan-settings'
|
||||
@@ -25,6 +26,7 @@ export type FirstRunSetupAction =
|
||||
|
||||
export interface FirstRunSetupSubmission {
|
||||
action: FirstRunSetupAction;
|
||||
mpvExecutablePath?: string;
|
||||
startMenuEnabled?: boolean;
|
||||
desktopEnabled?: boolean;
|
||||
}
|
||||
@@ -36,6 +38,7 @@ export interface FirstRunSetupHtmlModel {
|
||||
externalYomitanConfigured: boolean;
|
||||
pluginStatus: 'installed' | 'required' | 'failed';
|
||||
pluginInstallPathSummary: string | null;
|
||||
mpvExecutablePath: string;
|
||||
windowsMpvShortcuts: {
|
||||
supported: boolean;
|
||||
startMenuEnabled: boolean;
|
||||
@@ -90,6 +93,34 @@ export function buildFirstRunSetupHtml(model: FirstRunSetupHtmlModel): string {
|
||||
: model.windowsMpvShortcuts.status === 'skipped'
|
||||
? 'muted'
|
||||
: 'warn';
|
||||
const mpvExecutablePathLabel =
|
||||
model.mpvExecutablePath.trim().length > 0 ? 'Configured' : 'Blank';
|
||||
const mpvExecutablePathTone = model.mpvExecutablePath.trim().length > 0 ? 'ready' : 'muted';
|
||||
const mpvExecutablePathCard = model.windowsMpvShortcuts.supported
|
||||
? `
|
||||
<div class="card block">
|
||||
<div class="card-head">
|
||||
<div>
|
||||
<strong>mpv executable path</strong>
|
||||
<div class="meta">Leave blank to auto-discover mpv.exe from PATH.</div>
|
||||
<div class="meta">Current: ${escapeHtml(model.mpvExecutablePath.trim().length > 0 ? model.mpvExecutablePath : 'blank (PATH discovery)')}</div>
|
||||
</div>
|
||||
${renderStatusBadge(mpvExecutablePathLabel, mpvExecutablePathTone)}
|
||||
</div>
|
||||
<form
|
||||
class="path-form"
|
||||
onsubmit="event.preventDefault(); const params = new URLSearchParams({ action: 'configure-mpv-executable-path', mpvExecutablePath: document.getElementById('mpv-executable-path').value }); window.location.href = 'subminer://first-run-setup?' + params.toString();"
|
||||
>
|
||||
<input
|
||||
id="mpv-executable-path"
|
||||
type="text"
|
||||
value="${escapeHtml(model.mpvExecutablePath)}"
|
||||
placeholder="C:\\Program Files\\mpv\\mpv.exe"
|
||||
/>
|
||||
<button type="submit">Save mpv executable path</button>
|
||||
</form>
|
||||
</div>`
|
||||
: '';
|
||||
const windowsShortcutCard = model.windowsMpvShortcuts.supported
|
||||
? `
|
||||
<div class="card block">
|
||||
@@ -218,6 +249,24 @@ export function buildFirstRunSetupHtml(model: FirstRunSetupHtmlModel): string {
|
||||
.badge.warn { background: rgba(238, 212, 159, 0.18); color: var(--yellow); }
|
||||
.badge.muted { background: rgba(184, 192, 224, 0.12); color: var(--muted); }
|
||||
.badge.danger { background: rgba(237, 135, 150, 0.16); color: var(--red); }
|
||||
.path-form {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.path-form input[type='text'] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid rgba(202, 211, 245, 0.12);
|
||||
border-radius: 10px;
|
||||
padding: 9px 10px;
|
||||
color: var(--text);
|
||||
background: rgba(30, 32, 48, 0.72);
|
||||
font: inherit;
|
||||
}
|
||||
.path-form input[type='text']::placeholder {
|
||||
color: rgba(184, 192, 224, 0.65);
|
||||
}
|
||||
.actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
@@ -282,6 +331,7 @@ export function buildFirstRunSetupHtml(model: FirstRunSetupHtmlModel): string {
|
||||
</div>
|
||||
${renderStatusBadge(yomitanBadgeLabel, yomitanBadgeTone)}
|
||||
</div>
|
||||
${mpvExecutablePathCard}
|
||||
${windowsShortcutCard}
|
||||
<div class="actions">
|
||||
<button onclick="window.location.href='subminer://first-run-setup?action=install-plugin'">${pluginActionLabel}</button>
|
||||
@@ -303,6 +353,7 @@ export function parseFirstRunSetupSubmissionUrl(rawUrl: string): FirstRunSetupSu
|
||||
const parsed = new URL(rawUrl);
|
||||
const action = parsed.searchParams.get('action');
|
||||
if (
|
||||
action !== 'configure-mpv-executable-path' &&
|
||||
action !== 'install-plugin' &&
|
||||
action !== 'configure-windows-mpv-shortcuts' &&
|
||||
action !== 'open-yomitan-settings' &&
|
||||
@@ -311,6 +362,12 @@ export function parseFirstRunSetupSubmissionUrl(rawUrl: string): FirstRunSetupSu
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (action === 'configure-mpv-executable-path') {
|
||||
return {
|
||||
action,
|
||||
mpvExecutablePath: parsed.searchParams.get('mpvExecutablePath') ?? '',
|
||||
};
|
||||
}
|
||||
if (action === 'configure-windows-mpv-shortcuts') {
|
||||
return {
|
||||
action,
|
||||
|
||||
@@ -29,6 +29,19 @@ test('resolveWindowsMpvPath prefers SUBMINER_MPV_PATH', () => {
|
||||
assert.equal(resolved, 'C:\\mpv\\mpv.exe');
|
||||
});
|
||||
|
||||
test('resolveWindowsMpvPath prefers configured executable path before PATH', () => {
|
||||
const resolved = resolveWindowsMpvPath(
|
||||
createDeps({
|
||||
getEnv: () => undefined,
|
||||
runWhere: () => ({ status: 0, stdout: 'C:\\tools\\mpv.exe\r\n' }),
|
||||
fileExists: (candidate) => candidate === 'C:\\mpv\\mpv.exe',
|
||||
}),
|
||||
' C:\\mpv\\mpv.exe ',
|
||||
);
|
||||
|
||||
assert.equal(resolved, 'C:\\mpv\\mpv.exe');
|
||||
});
|
||||
|
||||
test('resolveWindowsMpvPath falls back to where.exe output', () => {
|
||||
const resolved = resolveWindowsMpvPath(
|
||||
createDeps({
|
||||
@@ -132,7 +145,7 @@ test('launchWindowsMpv reports missing mpv path', () => {
|
||||
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.mpvPath, '');
|
||||
assert.match(errors[0] ?? '', /Could not find mpv\.exe/i);
|
||||
assert.match(errors[0] ?? '', /mpv\.executablePath/i);
|
||||
});
|
||||
|
||||
test('launchWindowsMpv spawns detached mpv with targets', () => {
|
||||
|
||||
@@ -13,7 +13,15 @@ function normalizeCandidate(candidate: string | undefined): string {
|
||||
return typeof candidate === 'string' ? candidate.trim() : '';
|
||||
}
|
||||
|
||||
export function resolveWindowsMpvPath(deps: WindowsMpvLaunchDeps): string {
|
||||
export function resolveWindowsMpvPath(
|
||||
deps: WindowsMpvLaunchDeps,
|
||||
configuredMpvPath = '',
|
||||
): string {
|
||||
const configPath = normalizeCandidate(configuredMpvPath);
|
||||
if (configPath && deps.fileExists(configPath)) {
|
||||
return configPath;
|
||||
}
|
||||
|
||||
const envPath = normalizeCandidate(deps.getEnv('SUBMINER_MPV_PATH'));
|
||||
if (envPath && deps.fileExists(envPath)) {
|
||||
return envPath;
|
||||
@@ -97,12 +105,13 @@ export function launchWindowsMpv(
|
||||
extraArgs: string[] = [],
|
||||
binaryPath?: string,
|
||||
pluginEntrypointPath?: string,
|
||||
configuredMpvPath?: string,
|
||||
): { ok: boolean; mpvPath: string } {
|
||||
const mpvPath = resolveWindowsMpvPath(deps);
|
||||
const mpvPath = resolveWindowsMpvPath(deps, configuredMpvPath);
|
||||
if (!mpvPath) {
|
||||
deps.showError(
|
||||
'SubMiner mpv launcher',
|
||||
'Could not find mpv.exe. Install mpv and add it to PATH, or set SUBMINER_MPV_PATH.',
|
||||
'Could not find mpv.exe. Set mpv.executablePath, set SUBMINER_MPV_PATH, or add mpv.exe to PATH.',
|
||||
);
|
||||
return { ok: false, mpvPath: '' };
|
||||
}
|
||||
|
||||
@@ -50,6 +50,10 @@ export interface TexthookerConfig {
|
||||
openBrowser?: boolean;
|
||||
}
|
||||
|
||||
export interface MpvConfig {
|
||||
executablePath?: string;
|
||||
}
|
||||
|
||||
export type SubsyncMode = 'auto' | 'manual';
|
||||
|
||||
export interface SubsyncConfig {
|
||||
@@ -90,6 +94,7 @@ export interface Config {
|
||||
websocket?: WebSocketConfig;
|
||||
annotationWebsocket?: AnnotationWebSocketConfig;
|
||||
texthooker?: TexthookerConfig;
|
||||
mpv?: MpvConfig;
|
||||
controller?: ControllerConfig;
|
||||
ankiConnect?: AnkiConnectConfig;
|
||||
shortcuts?: ShortcutsConfig;
|
||||
@@ -122,6 +127,9 @@ export interface ResolvedConfig {
|
||||
websocket: Required<WebSocketConfig>;
|
||||
annotationWebsocket: Required<AnnotationWebSocketConfig>;
|
||||
texthooker: Required<TexthookerConfig>;
|
||||
mpv: {
|
||||
executablePath: string;
|
||||
};
|
||||
controller: {
|
||||
enabled: boolean;
|
||||
preferredGamepadId: string;
|
||||
|
||||
Reference in New Issue
Block a user