mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
fix: restore overlay ownership during plugin auto-start
This commit is contained in:
@@ -372,12 +372,9 @@ function M.create(ctx)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
launch_overlay_with_retry(1)
|
||||||
if texthooker_enabled then
|
if texthooker_enabled then
|
||||||
ensure_texthooker_running(function()
|
ensure_texthooker_running(function() end)
|
||||||
launch_overlay_with_retry(1)
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
launch_overlay_with_retry(1)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -481,7 +478,6 @@ function M.create(ctx)
|
|||||||
state.texthooker_running = false
|
state.texthooker_running = false
|
||||||
disarm_auto_play_ready_gate()
|
disarm_auto_play_ready_gate()
|
||||||
|
|
||||||
ensure_texthooker_running(function()
|
|
||||||
local start_args = build_command_args("start")
|
local start_args = build_command_args("start")
|
||||||
subminer_log("info", "process", "Starting overlay: " .. table.concat(start_args, " "))
|
subminer_log("info", "process", "Starting overlay: " .. table.concat(start_args, " "))
|
||||||
|
|
||||||
@@ -505,7 +501,10 @@ function M.create(ctx)
|
|||||||
show_osd("Restarted successfully")
|
show_osd("Restarted successfully")
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end)
|
|
||||||
|
if opts.texthooker_enabled then
|
||||||
|
ensure_texthooker_running(function() end)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -344,6 +344,27 @@ local function count_start_calls(async_calls)
|
|||||||
return count
|
return count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function find_texthooker_call(async_calls)
|
||||||
|
for _, call in ipairs(async_calls) do
|
||||||
|
local args = call.args or {}
|
||||||
|
for i = 1, #args do
|
||||||
|
if args[i] == "--texthooker" then
|
||||||
|
return call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function find_call_index(async_calls, target_call)
|
||||||
|
for index, call in ipairs(async_calls) do
|
||||||
|
if call == target_call then
|
||||||
|
return index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
local function find_control_call(async_calls, flag)
|
local function find_control_call(async_calls, flag)
|
||||||
for _, call in ipairs(async_calls) do
|
for _, call in ipairs(async_calls) do
|
||||||
local args = call.args or {}
|
local args = call.args or {}
|
||||||
@@ -643,6 +664,8 @@ do
|
|||||||
fire_event(recorded, "file-loaded")
|
fire_event(recorded, "file-loaded")
|
||||||
local start_call = find_start_call(recorded.async_calls)
|
local start_call = find_start_call(recorded.async_calls)
|
||||||
assert_true(start_call ~= nil, "auto-start should issue --start command")
|
assert_true(start_call ~= nil, "auto-start should issue --start command")
|
||||||
|
local texthooker_call = find_texthooker_call(recorded.async_calls)
|
||||||
|
assert_true(texthooker_call ~= nil, "auto-start should issue texthooker helper command when enabled")
|
||||||
assert_true(
|
assert_true(
|
||||||
call_has_arg(start_call, "--show-visible-overlay"),
|
call_has_arg(start_call, "--show-visible-overlay"),
|
||||||
"auto-start with visible overlay enabled should include --show-visible-overlay on --start"
|
"auto-start with visible overlay enabled should include --show-visible-overlay on --start"
|
||||||
@@ -655,6 +678,10 @@ do
|
|||||||
find_control_call(recorded.async_calls, "--show-visible-overlay") ~= nil,
|
find_control_call(recorded.async_calls, "--show-visible-overlay") ~= nil,
|
||||||
"auto-start with visible overlay enabled should issue a separate --show-visible-overlay command"
|
"auto-start with visible overlay enabled should issue a separate --show-visible-overlay command"
|
||||||
)
|
)
|
||||||
|
assert_true(
|
||||||
|
find_call_index(recorded.async_calls, start_call) < find_call_index(recorded.async_calls, texthooker_call),
|
||||||
|
"auto-start should launch --start before separate --texthooker helper startup"
|
||||||
|
)
|
||||||
assert_true(
|
assert_true(
|
||||||
not has_property_set(recorded.property_sets, "pause", true),
|
not has_property_set(recorded.property_sets, "pause", true),
|
||||||
"auto-start visible overlay should not force pause without explicit pause-until-ready option"
|
"auto-start visible overlay should not force pause without explicit pause-until-ready option"
|
||||||
|
|||||||
@@ -176,6 +176,22 @@ test('runAppReadyRuntime skips heavy startup when shouldSkipHeavyStartup returns
|
|||||||
assert.ok(calls.indexOf('handleFirstRunSetup') < calls.indexOf('handleInitialArgs'));
|
assert.ok(calls.indexOf('handleFirstRunSetup') < calls.indexOf('handleInitialArgs'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('runAppReadyRuntime uses minimal startup for texthooker-only mode', async () => {
|
||||||
|
const { deps, calls } = makeDeps({
|
||||||
|
texthookerOnlyMode: true,
|
||||||
|
reloadConfig: () => calls.push('reloadConfig'),
|
||||||
|
handleInitialArgs: () => calls.push('handleInitialArgs'),
|
||||||
|
});
|
||||||
|
|
||||||
|
await runAppReadyRuntime(deps);
|
||||||
|
|
||||||
|
assert.deepEqual(calls, [
|
||||||
|
'ensureDefaultConfigBootstrap',
|
||||||
|
'reloadConfig',
|
||||||
|
'handleInitialArgs',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test('runAppReadyRuntime skips Jellyfin remote startup when dependency is not wired', async () => {
|
test('runAppReadyRuntime skips Jellyfin remote startup when dependency is not wired', async () => {
|
||||||
const { deps, calls } = makeDeps({
|
const { deps, calls } = makeDeps({
|
||||||
startJellyfinRemoteSession: undefined,
|
startJellyfinRemoteSession: undefined,
|
||||||
|
|||||||
@@ -200,6 +200,12 @@ export async function runAppReadyRuntime(deps: AppReadyRuntimeDeps): Promise<voi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deps.texthookerOnlyMode) {
|
||||||
|
deps.reloadConfig();
|
||||||
|
deps.handleInitialArgs();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (deps.shouldUseMinimalStartup?.()) {
|
if (deps.shouldUseMinimalStartup?.()) {
|
||||||
deps.reloadConfig();
|
deps.reloadConfig();
|
||||||
deps.handleInitialArgs();
|
deps.handleInitialArgs();
|
||||||
|
|||||||
38
src/main.ts
38
src/main.ts
@@ -3037,10 +3037,11 @@ const { appReadyRuntimeRunner } = composeAppReadyRuntime({
|
|||||||
Boolean(appState.initialArgs && isHeadlessInitialCommand(appState.initialArgs)),
|
Boolean(appState.initialArgs && isHeadlessInitialCommand(appState.initialArgs)),
|
||||||
shouldUseMinimalStartup: () =>
|
shouldUseMinimalStartup: () =>
|
||||||
Boolean(
|
Boolean(
|
||||||
appState.initialArgs?.stats &&
|
appState.initialArgs?.texthooker ||
|
||||||
|
(appState.initialArgs?.stats &&
|
||||||
(appState.initialArgs?.statsCleanup ||
|
(appState.initialArgs?.statsCleanup ||
|
||||||
appState.initialArgs?.statsBackground ||
|
appState.initialArgs?.statsBackground ||
|
||||||
appState.initialArgs?.statsStop),
|
appState.initialArgs?.statsStop)),
|
||||||
),
|
),
|
||||||
shouldSkipHeavyStartup: () =>
|
shouldSkipHeavyStartup: () =>
|
||||||
Boolean(
|
Boolean(
|
||||||
@@ -3130,6 +3131,39 @@ void initializeDiscordPresenceService();
|
|||||||
const handleCliCommand = createCliCommandRuntimeHandler({
|
const handleCliCommand = createCliCommandRuntimeHandler({
|
||||||
handleTexthookerOnlyModeTransitionMainDeps: {
|
handleTexthookerOnlyModeTransitionMainDeps: {
|
||||||
isTexthookerOnlyMode: () => appState.texthookerOnlyMode,
|
isTexthookerOnlyMode: () => appState.texthookerOnlyMode,
|
||||||
|
ensureOverlayStartupPrereqs: () => {
|
||||||
|
if (appState.subtitlePosition === null) {
|
||||||
|
loadSubtitlePosition();
|
||||||
|
}
|
||||||
|
if (appState.keybindings.length === 0) {
|
||||||
|
appState.keybindings = resolveKeybindings(getResolvedConfig(), DEFAULT_KEYBINDINGS);
|
||||||
|
}
|
||||||
|
if (!appState.mpvClient) {
|
||||||
|
appState.mpvClient = createMpvClientRuntimeService();
|
||||||
|
}
|
||||||
|
if (!appState.runtimeOptionsManager) {
|
||||||
|
appState.runtimeOptionsManager = new RuntimeOptionsManager(
|
||||||
|
() => configService.getConfig().ankiConnect,
|
||||||
|
{
|
||||||
|
applyAnkiPatch: (patch) => {
|
||||||
|
if (appState.ankiIntegration) {
|
||||||
|
appState.ankiIntegration.applyRuntimeConfigPatch(patch);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getSubtitleStyleConfig: () => configService.getConfig().subtitleStyle,
|
||||||
|
onOptionsChanged: () => {
|
||||||
|
subtitleProcessingController.invalidateTokenizationCache();
|
||||||
|
subtitlePrefetchService?.onSeek(lastObservedTimePos);
|
||||||
|
broadcastRuntimeOptionsChanged();
|
||||||
|
refreshOverlayShortcuts();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!appState.subtitleTimingTracker) {
|
||||||
|
appState.subtitleTimingTracker = new SubtitleTimingTracker();
|
||||||
|
}
|
||||||
|
},
|
||||||
setTexthookerOnlyMode: (enabled) => {
|
setTexthookerOnlyMode: (enabled) => {
|
||||||
appState.texthookerOnlyMode = enabled;
|
appState.texthookerOnlyMode = enabled;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ test('cli prechecks main deps builder maps transition handlers', () => {
|
|||||||
isTexthookerOnlyMode: () => true,
|
isTexthookerOnlyMode: () => true,
|
||||||
setTexthookerOnlyMode: (enabled) => calls.push(`set:${enabled}`),
|
setTexthookerOnlyMode: (enabled) => calls.push(`set:${enabled}`),
|
||||||
commandNeedsOverlayRuntime: () => true,
|
commandNeedsOverlayRuntime: () => true,
|
||||||
|
ensureOverlayStartupPrereqs: () => calls.push('prereqs'),
|
||||||
startBackgroundWarmups: () => calls.push('warmups'),
|
startBackgroundWarmups: () => calls.push('warmups'),
|
||||||
logInfo: (message) => calls.push(`info:${message}`),
|
logInfo: (message) => calls.push(`info:${message}`),
|
||||||
})();
|
})();
|
||||||
@@ -15,7 +16,8 @@ test('cli prechecks main deps builder maps transition handlers', () => {
|
|||||||
assert.equal(deps.isTexthookerOnlyMode(), true);
|
assert.equal(deps.isTexthookerOnlyMode(), true);
|
||||||
assert.equal(deps.commandNeedsOverlayRuntime({} as never), true);
|
assert.equal(deps.commandNeedsOverlayRuntime({} as never), true);
|
||||||
deps.setTexthookerOnlyMode(false);
|
deps.setTexthookerOnlyMode(false);
|
||||||
|
deps.ensureOverlayStartupPrereqs();
|
||||||
deps.startBackgroundWarmups();
|
deps.startBackgroundWarmups();
|
||||||
deps.logInfo('x');
|
deps.logInfo('x');
|
||||||
assert.deepEqual(calls, ['set:false', 'warmups', 'info:x']);
|
assert.deepEqual(calls, ['set:false', 'prereqs', 'warmups', 'info:x']);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export function createBuildHandleTexthookerOnlyModeTransitionMainDepsHandler(dep
|
|||||||
isTexthookerOnlyMode: () => boolean;
|
isTexthookerOnlyMode: () => boolean;
|
||||||
setTexthookerOnlyMode: (enabled: boolean) => void;
|
setTexthookerOnlyMode: (enabled: boolean) => void;
|
||||||
commandNeedsOverlayRuntime: (args: CliArgs) => boolean;
|
commandNeedsOverlayRuntime: (args: CliArgs) => boolean;
|
||||||
|
ensureOverlayStartupPrereqs: () => void;
|
||||||
startBackgroundWarmups: () => void;
|
startBackgroundWarmups: () => void;
|
||||||
logInfo: (message: string) => void;
|
logInfo: (message: string) => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -11,6 +12,7 @@ export function createBuildHandleTexthookerOnlyModeTransitionMainDepsHandler(dep
|
|||||||
isTexthookerOnlyMode: () => deps.isTexthookerOnlyMode(),
|
isTexthookerOnlyMode: () => deps.isTexthookerOnlyMode(),
|
||||||
setTexthookerOnlyMode: (enabled: boolean) => deps.setTexthookerOnlyMode(enabled),
|
setTexthookerOnlyMode: (enabled: boolean) => deps.setTexthookerOnlyMode(enabled),
|
||||||
commandNeedsOverlayRuntime: (args: CliArgs) => deps.commandNeedsOverlayRuntime(args),
|
commandNeedsOverlayRuntime: (args: CliArgs) => deps.commandNeedsOverlayRuntime(args),
|
||||||
|
ensureOverlayStartupPrereqs: () => deps.ensureOverlayStartupPrereqs(),
|
||||||
startBackgroundWarmups: () => deps.startBackgroundWarmups(),
|
startBackgroundWarmups: () => deps.startBackgroundWarmups(),
|
||||||
logInfo: (message: string) => deps.logInfo(message),
|
logInfo: (message: string) => deps.logInfo(message),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ test('texthooker precheck no-ops when mode is disabled', () => {
|
|||||||
isTexthookerOnlyMode: () => false,
|
isTexthookerOnlyMode: () => false,
|
||||||
setTexthookerOnlyMode: () => {},
|
setTexthookerOnlyMode: () => {},
|
||||||
commandNeedsOverlayRuntime: () => true,
|
commandNeedsOverlayRuntime: () => true,
|
||||||
|
ensureOverlayStartupPrereqs: () => {},
|
||||||
startBackgroundWarmups: () => {
|
startBackgroundWarmups: () => {
|
||||||
warmups += 1;
|
warmups += 1;
|
||||||
},
|
},
|
||||||
@@ -22,12 +23,16 @@ test('texthooker precheck disables mode and warms up on start command', () => {
|
|||||||
let mode = true;
|
let mode = true;
|
||||||
let warmups = 0;
|
let warmups = 0;
|
||||||
let logs = 0;
|
let logs = 0;
|
||||||
|
let prereqs = 0;
|
||||||
const handlePrecheck = createHandleTexthookerOnlyModeTransitionHandler({
|
const handlePrecheck = createHandleTexthookerOnlyModeTransitionHandler({
|
||||||
isTexthookerOnlyMode: () => mode,
|
isTexthookerOnlyMode: () => mode,
|
||||||
setTexthookerOnlyMode: (enabled) => {
|
setTexthookerOnlyMode: (enabled) => {
|
||||||
mode = enabled;
|
mode = enabled;
|
||||||
},
|
},
|
||||||
commandNeedsOverlayRuntime: () => false,
|
commandNeedsOverlayRuntime: () => false,
|
||||||
|
ensureOverlayStartupPrereqs: () => {
|
||||||
|
prereqs += 1;
|
||||||
|
},
|
||||||
startBackgroundWarmups: () => {
|
startBackgroundWarmups: () => {
|
||||||
warmups += 1;
|
warmups += 1;
|
||||||
},
|
},
|
||||||
@@ -38,6 +43,7 @@ test('texthooker precheck disables mode and warms up on start command', () => {
|
|||||||
|
|
||||||
handlePrecheck({ start: true, texthooker: false } as never);
|
handlePrecheck({ start: true, texthooker: false } as never);
|
||||||
assert.equal(mode, false);
|
assert.equal(mode, false);
|
||||||
|
assert.equal(prereqs, 1);
|
||||||
assert.equal(warmups, 1);
|
assert.equal(warmups, 1);
|
||||||
assert.equal(logs, 1);
|
assert.equal(logs, 1);
|
||||||
});
|
});
|
||||||
@@ -50,6 +56,7 @@ test('texthooker precheck no-ops for texthooker command', () => {
|
|||||||
mode = enabled;
|
mode = enabled;
|
||||||
},
|
},
|
||||||
commandNeedsOverlayRuntime: () => true,
|
commandNeedsOverlayRuntime: () => true,
|
||||||
|
ensureOverlayStartupPrereqs: () => {},
|
||||||
startBackgroundWarmups: () => {},
|
startBackgroundWarmups: () => {},
|
||||||
logInfo: () => {},
|
logInfo: () => {},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export function createHandleTexthookerOnlyModeTransitionHandler(deps: {
|
|||||||
isTexthookerOnlyMode: () => boolean;
|
isTexthookerOnlyMode: () => boolean;
|
||||||
setTexthookerOnlyMode: (enabled: boolean) => void;
|
setTexthookerOnlyMode: (enabled: boolean) => void;
|
||||||
commandNeedsOverlayRuntime: (args: CliArgs) => boolean;
|
commandNeedsOverlayRuntime: (args: CliArgs) => boolean;
|
||||||
|
ensureOverlayStartupPrereqs: () => void;
|
||||||
startBackgroundWarmups: () => void;
|
startBackgroundWarmups: () => void;
|
||||||
logInfo: (message: string) => void;
|
logInfo: (message: string) => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -13,6 +14,7 @@ export function createHandleTexthookerOnlyModeTransitionHandler(deps: {
|
|||||||
!args.texthooker &&
|
!args.texthooker &&
|
||||||
(args.start || deps.commandNeedsOverlayRuntime(args))
|
(args.start || deps.commandNeedsOverlayRuntime(args))
|
||||||
) {
|
) {
|
||||||
|
deps.ensureOverlayStartupPrereqs();
|
||||||
deps.setTexthookerOnlyMode(false);
|
deps.setTexthookerOnlyMode(false);
|
||||||
deps.logInfo('Disabling texthooker-only mode after overlay/start command.');
|
deps.logInfo('Disabling texthooker-only mode after overlay/start command.');
|
||||||
deps.startBackgroundWarmups();
|
deps.startBackgroundWarmups();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ test('cli command runtime handler applies precheck and forwards command with con
|
|||||||
isTexthookerOnlyMode: () => true,
|
isTexthookerOnlyMode: () => true,
|
||||||
setTexthookerOnlyMode: () => calls.push('set-mode'),
|
setTexthookerOnlyMode: () => calls.push('set-mode'),
|
||||||
commandNeedsOverlayRuntime: () => true,
|
commandNeedsOverlayRuntime: () => true,
|
||||||
|
ensureOverlayStartupPrereqs: () => calls.push('prereqs'),
|
||||||
startBackgroundWarmups: () => calls.push('warmups'),
|
startBackgroundWarmups: () => calls.push('warmups'),
|
||||||
logInfo: (message) => calls.push(`log:${message}`),
|
logInfo: (message) => calls.push(`log:${message}`),
|
||||||
},
|
},
|
||||||
@@ -24,6 +25,7 @@ test('cli command runtime handler applies precheck and forwards command with con
|
|||||||
handler({ start: true } as never);
|
handler({ start: true } as never);
|
||||||
|
|
||||||
assert.deepEqual(calls, [
|
assert.deepEqual(calls, [
|
||||||
|
'prereqs',
|
||||||
'set-mode',
|
'set-mode',
|
||||||
'log:Disabling texthooker-only mode after overlay/start command.',
|
'log:Disabling texthooker-only mode after overlay/start command.',
|
||||||
'warmups',
|
'warmups',
|
||||||
|
|||||||
Reference in New Issue
Block a user