mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-11-10 04:46:07 -08:00
- Send controller connected and disabled messages over a new IPC channel so they follow ankiConnect.behavior.notificationType - Remove the separate controller status toast from the overlay - Only announce connected controllers when controller.enabled is on - Announce a controller again after it reconnects, and update usage docs
593 lines
25 KiB
TypeScript
593 lines
25 KiB
TypeScript
/*
|
|
* SubMiner - All-in-one sentence mining overlay
|
|
* Copyright (C) 2024 sudacode
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import { clipboard, contextBridge, ipcRenderer, IpcRendererEvent, webUtils } from 'electron';
|
|
import { resolveOverlayLayerFromArgv } from './preload-args';
|
|
import { createIpcListenerFactories } from './preload-ipc-listeners';
|
|
import {
|
|
DICTIONARY_EXTERNAL_LINK_CHANNEL,
|
|
parseDictionaryExternalUrl,
|
|
} from './shared/dictionary-external-link';
|
|
|
|
window.addEventListener('hachidori-open-external', (event) => {
|
|
if (!(event instanceof CustomEvent)) return;
|
|
const detail: unknown = event.detail;
|
|
if (
|
|
!detail ||
|
|
typeof detail !== 'object' ||
|
|
!('requestId' in detail) ||
|
|
typeof detail.requestId !== 'string' ||
|
|
!('url' in detail)
|
|
)
|
|
return;
|
|
const requestId = detail.requestId;
|
|
void Promise.resolve()
|
|
.then(() =>
|
|
ipcRenderer.invoke(DICTIONARY_EXTERNAL_LINK_CHANNEL, parseDictionaryExternalUrl(detail.url)),
|
|
)
|
|
.then(() => {
|
|
window.dispatchEvent(
|
|
new CustomEvent('hachidori-open-external-result', { detail: { requestId, ok: true } }),
|
|
);
|
|
})
|
|
.catch((error: unknown) => {
|
|
window.dispatchEvent(
|
|
new CustomEvent('hachidori-open-external-result', {
|
|
detail: {
|
|
requestId,
|
|
ok: false,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
},
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
import type {
|
|
SubtitleData,
|
|
SubtitlePosition,
|
|
ElectronAPI,
|
|
SecondarySubMode,
|
|
SubtitleStyleConfig,
|
|
JimakuMediaInfo,
|
|
JimakuSearchQuery,
|
|
JimakuFilesQuery,
|
|
JimakuDownloadQuery,
|
|
JimakuEntry,
|
|
JimakuFileEntry,
|
|
JimakuApiResponse,
|
|
JimakuDownloadResult,
|
|
TsukihimeSearchQuery,
|
|
TsukihimeFilesQuery,
|
|
TsukihimeDownloadQuery,
|
|
TsukihimeEntry,
|
|
TsukihimeSubtitleFile,
|
|
TsukihimeApiResponse,
|
|
TsukihimeDownloadResult,
|
|
SubsyncManualPayload,
|
|
SubsyncManualRunRequest,
|
|
SubsyncResult,
|
|
PlaylistBrowserMutationResult,
|
|
PlaylistBrowserSnapshot,
|
|
KikuFieldGroupingRequestData,
|
|
KikuFieldGroupingChoice,
|
|
KikuMergePreviewRequest,
|
|
KikuMergePreviewResponse,
|
|
RuntimeOptionApplyResult,
|
|
RuntimeOptionId,
|
|
RuntimeOptionState,
|
|
RuntimeOptionValue,
|
|
OverlayContentMeasurement,
|
|
ShortcutsConfig,
|
|
ConfigHotReloadPayload,
|
|
ControllerConfigUpdate,
|
|
ResolvedControllerConfig,
|
|
SessionHelpOpenPayload,
|
|
SessionNumericSelectionStartPayload,
|
|
SubtitleMiningContext,
|
|
YoutubePickerOpenPayload,
|
|
YoutubePickerResolveRequest,
|
|
YoutubePickerResolveResult,
|
|
OverlayNotificationEventPayload,
|
|
OverlayNotificationPosition,
|
|
ChangelogSnapshot,
|
|
PluginPanelSnapshot,
|
|
MediaTimingReviewActionResult,
|
|
MediaTimingReviewOpenPayload,
|
|
MediaTimingReviewPreviewPosition,
|
|
MediaTimingReviewPreviewRequest,
|
|
MediaTimingReviewResolveRequest,
|
|
MediaTimingReviewFrameRequest,
|
|
MediaTimingReviewFrameResult,
|
|
MediaTimingReviewWaveformRequest,
|
|
} from './types';
|
|
import { IPC_CHANNELS } from './shared/ipc/contracts';
|
|
import type { SubtitleGenerationProgress } from './shared/subtitle-generation';
|
|
|
|
const {
|
|
createQueuedIpcListener,
|
|
createQueuedIpcListenerWithPayload,
|
|
createLatestValueIpcListenerWithPayload,
|
|
} = createIpcListenerFactories((channel, handler) =>
|
|
ipcRenderer.on(channel, (_event, payload: unknown) => handler(payload)),
|
|
);
|
|
|
|
const overlayLayer = resolveOverlayLayerFromArgv(process.argv);
|
|
const onSubtitleSelectionOpen = createQueuedIpcListener(IPC_CHANNELS.event.subtitleSelectionOpen);
|
|
const onSubtitleGenerationOpen = createQueuedIpcListener(IPC_CHANNELS.event.subtitleGenerationOpen);
|
|
|
|
const onOpenRuntimeOptionsEvent = createQueuedIpcListener(IPC_CHANNELS.event.runtimeOptionsOpen);
|
|
const onOpenSessionHelpEvent = createQueuedIpcListenerWithPayload<SessionHelpOpenPayload>(
|
|
IPC_CHANNELS.event.sessionHelpOpen,
|
|
(payload) => ({
|
|
commandsEnabled:
|
|
typeof payload === 'object' &&
|
|
payload !== null &&
|
|
(payload as Partial<SessionHelpOpenPayload>).commandsEnabled === true,
|
|
}),
|
|
);
|
|
const onOpenChangelogEvent = createQueuedIpcListener(IPC_CHANNELS.event.changelogOpen);
|
|
function toPluginPanelSnapshot(payload: unknown): PluginPanelSnapshot {
|
|
const value = (typeof payload === 'object' && payload !== null ? payload : {}) as Partial<
|
|
Record<keyof PluginPanelSnapshot, unknown>
|
|
>;
|
|
return {
|
|
panelId: typeof value.panelId === 'number' ? value.panelId : -1,
|
|
title: typeof value.title === 'string' ? value.title : '',
|
|
markdown: typeof value.markdown === 'string' ? value.markdown : '',
|
|
};
|
|
}
|
|
const onOpenPluginPanelEvent = createQueuedIpcListenerWithPayload(
|
|
IPC_CHANNELS.event.pluginPanelOpen,
|
|
toPluginPanelSnapshot,
|
|
);
|
|
const onOpenCharacterDictionaryManagerEvent = createQueuedIpcListener(
|
|
IPC_CHANNELS.event.characterDictionaryManagerOpen,
|
|
);
|
|
const onOpenControllerSelectEvent = createQueuedIpcListener(
|
|
IPC_CHANNELS.event.controllerSelectOpen,
|
|
);
|
|
const onOpenControllerDebugEvent = createQueuedIpcListener(IPC_CHANNELS.event.controllerDebugOpen);
|
|
const onOpenJimakuEvent = createQueuedIpcListener(IPC_CHANNELS.event.jimakuOpen);
|
|
const onOpenTsukihimeEvent = createQueuedIpcListener(IPC_CHANNELS.event.tsukihimeOpen);
|
|
const onOpenYoutubeTrackPickerEvent = createQueuedIpcListenerWithPayload<YoutubePickerOpenPayload>(
|
|
IPC_CHANNELS.event.youtubePickerOpen,
|
|
(payload) => payload as YoutubePickerOpenPayload,
|
|
);
|
|
const onOpenMediaTimingReviewEvent =
|
|
createQueuedIpcListenerWithPayload<MediaTimingReviewOpenPayload>(
|
|
IPC_CHANNELS.event.mediaTimingReviewOpen,
|
|
(payload) => payload as MediaTimingReviewOpenPayload,
|
|
);
|
|
const onMediaTimingReviewPreviewEndedEvent = createQueuedIpcListenerWithPayload<string>(
|
|
IPC_CHANNELS.event.mediaTimingReviewPreviewEnded,
|
|
(payload) => (typeof payload === 'string' ? payload : ''),
|
|
);
|
|
const onOpenPlaylistBrowserEvent = createQueuedIpcListener(IPC_CHANNELS.event.playlistBrowserOpen);
|
|
const onCancelYoutubeTrackPickerEvent = createQueuedIpcListener(
|
|
IPC_CHANNELS.event.youtubePickerCancel,
|
|
);
|
|
const onSessionNumericSelectionStartEvent =
|
|
createQueuedIpcListenerWithPayload<SessionNumericSelectionStartPayload>(
|
|
IPC_CHANNELS.event.sessionNumericSelectionStart,
|
|
(payload) => payload as SessionNumericSelectionStartPayload,
|
|
);
|
|
const onControllerSuspendedChangedEvent = createQueuedIpcListenerWithPayload<boolean>(
|
|
IPC_CHANNELS.event.controllerSuspendedChanged,
|
|
(payload) => payload === true,
|
|
);
|
|
const onPlaybackPauseChangedEvent = createQueuedIpcListenerWithPayload<boolean>(
|
|
IPC_CHANNELS.event.playbackPauseChanged,
|
|
(payload) => payload === true,
|
|
);
|
|
const onControllerConfigChangedEvent = createQueuedIpcListener(
|
|
IPC_CHANNELS.event.controllerConfigChanged,
|
|
);
|
|
const onControllerBackgroundPressEvent = createQueuedIpcListenerWithPayload<number>(
|
|
IPC_CHANNELS.event.controllerBackgroundPress,
|
|
(payload) => (typeof payload === 'number' && Number.isInteger(payload) ? payload : -1),
|
|
);
|
|
const onKeyboardModeToggleRequestedEvent = createQueuedIpcListener(
|
|
IPC_CHANNELS.event.keyboardModeToggleRequested,
|
|
);
|
|
const onLookupWindowToggleRequestedEvent = createQueuedIpcListener(
|
|
IPC_CHANNELS.event.lookupWindowToggleRequested,
|
|
);
|
|
const onSubsyncManualOpenEvent = createQueuedIpcListenerWithPayload<SubsyncManualPayload>(
|
|
IPC_CHANNELS.event.subsyncOpenManual,
|
|
(payload) => payload as SubsyncManualPayload,
|
|
);
|
|
const onSubtitleSidebarToggleEvent = createQueuedIpcListener(
|
|
IPC_CHANNELS.event.subtitleSidebarToggle,
|
|
);
|
|
const onPrimarySubtitleBarToggleEvent = createQueuedIpcListener(
|
|
IPC_CHANNELS.event.primarySubtitleBarToggle,
|
|
);
|
|
const onKikuFieldGroupingRequestEvent =
|
|
createQueuedIpcListenerWithPayload<KikuFieldGroupingRequestData>(
|
|
IPC_CHANNELS.event.kikuFieldGroupingRequest,
|
|
(payload) => payload as KikuFieldGroupingRequestData,
|
|
);
|
|
const onKikuFieldGroupingCancelEvent = createQueuedIpcListener(
|
|
IPC_CHANNELS.event.kikuFieldGroupingCancel,
|
|
);
|
|
const onSubtitleSetEvent = createLatestValueIpcListenerWithPayload<SubtitleData>(
|
|
IPC_CHANNELS.event.subtitleSet,
|
|
(payload) => payload as SubtitleData,
|
|
);
|
|
const onOverlayPointerRecoveryRequestEvent = createQueuedIpcListener(
|
|
IPC_CHANNELS.event.overlayPointerRecoveryRequest,
|
|
);
|
|
const onOverlayNotificationEvent =
|
|
createQueuedIpcListenerWithPayload<OverlayNotificationEventPayload>(
|
|
IPC_CHANNELS.event.overlayNotification,
|
|
(payload) => payload as OverlayNotificationEventPayload,
|
|
);
|
|
const onNotificationHistoryToggleEvent = createQueuedIpcListener(
|
|
IPC_CHANNELS.event.notificationHistoryToggle,
|
|
);
|
|
const onSubtitlePositionSetEvent = createLatestValueIpcListenerWithPayload<SubtitlePosition | null>(
|
|
IPC_CHANNELS.event.subtitlePositionSet,
|
|
(payload) => payload as SubtitlePosition | null,
|
|
);
|
|
const onSecondarySubtitleSetEvent = createLatestValueIpcListenerWithPayload<string>(
|
|
IPC_CHANNELS.event.secondarySubtitleSet,
|
|
(payload) => (typeof payload === 'string' ? payload : ''),
|
|
);
|
|
const onSecondarySubtitleModeEvent = createLatestValueIpcListenerWithPayload<SecondarySubMode>(
|
|
IPC_CHANNELS.event.secondarySubtitleMode,
|
|
(payload) => payload as SecondarySubMode,
|
|
);
|
|
|
|
const electronAPI: ElectronAPI = {
|
|
requestSubtitleGenerationOpen: () =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.requestSubtitleGenerationOpen),
|
|
onSubtitleGenerationOpen,
|
|
getSubtitleGenerationStatus: () =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getSubtitleGenerationStatus),
|
|
selectSubtitleGenerationModel: (model) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.selectSubtitleGenerationModel, model),
|
|
startSubtitleGeneration: () => ipcRenderer.invoke(IPC_CHANNELS.request.startSubtitleGeneration),
|
|
downloadSubtitleGenerationModel: () =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.downloadSubtitleGenerationModel),
|
|
downloadSubtitleGenerationVadModel: () =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.downloadSubtitleGenerationVadModel),
|
|
setSubtitleGenerationVadEnabled: (enabled) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.setSubtitleGenerationVadEnabled, enabled),
|
|
cancelSubtitleGeneration: () => ipcRenderer.invoke(IPC_CHANNELS.request.cancelSubtitleGeneration),
|
|
onSubtitleGenerationProgress: (callback) => {
|
|
const listener = (_event: IpcRendererEvent, progress: SubtitleGenerationProgress) =>
|
|
callback(progress);
|
|
ipcRenderer.on(IPC_CHANNELS.event.subtitleGenerationProgress, listener);
|
|
return () =>
|
|
ipcRenderer.removeListener(IPC_CHANNELS.event.subtitleGenerationProgress, listener);
|
|
},
|
|
getOverlayLayer: () => overlayLayer,
|
|
getPathForFile: (file: File) => webUtils.getPathForFile(file),
|
|
onSubtitle: (callback: (data: SubtitleData) => void) => {
|
|
onSubtitleSetEvent(callback);
|
|
},
|
|
onOverlayPointerRecoveryRequested: onOverlayPointerRecoveryRequestEvent,
|
|
onOverlayNotification: onOverlayNotificationEvent,
|
|
sendOverlayNotificationAction: (
|
|
notificationId: string,
|
|
actionId: string,
|
|
options?: { noteId?: number },
|
|
) => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.overlayNotificationAction, {
|
|
notificationId,
|
|
actionId,
|
|
...(options?.noteId !== undefined ? { noteId: options.noteId } : {}),
|
|
});
|
|
},
|
|
onNotificationHistoryToggle: onNotificationHistoryToggleEvent,
|
|
|
|
onSubtitlePosition: (callback: (position: SubtitlePosition | null) => void) => {
|
|
onSubtitlePositionSetEvent(callback);
|
|
},
|
|
|
|
getCurrentSubtitle: (): Promise<SubtitleData> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getCurrentSubtitle),
|
|
getCurrentSubtitleRaw: (): Promise<string> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getCurrentSubtitleRaw),
|
|
getSubtitleSidebarOpen: (): Promise<boolean> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getSubtitleSidebarOpen),
|
|
getSubtitleSidebarSnapshot: () =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getSubtitleSidebarSnapshot),
|
|
getSubtitleSidebarPlaybackState: () =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getSubtitleSidebarPlaybackState),
|
|
copySubtitleSidebarSelection: async (text: unknown) => {
|
|
if (typeof text !== 'string') throw new TypeError('Subtitle selection must be text.');
|
|
clipboard.writeText(text);
|
|
},
|
|
getPlaybackPaused: (): Promise<boolean | null> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getPlaybackPaused),
|
|
|
|
setIgnoreMouseEvents: (ignore: boolean, options?: { forward?: boolean }) => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.setIgnoreMouseEvents, ignore, options);
|
|
},
|
|
|
|
reportOverlayInteractive: (interactive: boolean) => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.reportOverlayInteractive, interactive);
|
|
},
|
|
|
|
recordYomitanLookup: (context?: SubtitleMiningContext | null) => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.recordYomitanLookup, context ?? null);
|
|
},
|
|
|
|
getSubtitlePosition: (): Promise<SubtitlePosition | null> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getSubtitlePosition),
|
|
saveSubtitlePosition: (position: SubtitlePosition) => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.saveSubtitlePosition, position);
|
|
},
|
|
|
|
sendMpvCommand: (command: (string | number)[]) => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.mpvCommand, command);
|
|
},
|
|
|
|
getMpvInputBindings: () => ipcRenderer.invoke(IPC_CHANNELS.request.getMpvInputBindings),
|
|
getSessionBindings: () => ipcRenderer.invoke(IPC_CHANNELS.request.getSessionBindings),
|
|
getConfiguredShortcuts: (): Promise<Required<ShortcutsConfig>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getConfigShortcuts),
|
|
dispatchSessionAction: (actionId, payload) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.command.dispatchSessionAction, { actionId, payload }),
|
|
getStatsToggleKey: (): Promise<string> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getStatsToggleKey),
|
|
getOverlayNotificationPosition: (): Promise<OverlayNotificationPosition> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getOverlayNotificationPosition),
|
|
getMarkWatchedKey: (): Promise<string> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getMarkWatchedKey),
|
|
markActiveVideoWatched: (): Promise<boolean> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.command.markActiveVideoWatched),
|
|
getControllerConfig: (): Promise<ResolvedControllerConfig> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getControllerConfig),
|
|
saveControllerConfig: (update: ControllerConfigUpdate): Promise<void> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.command.saveControllerConfig, update),
|
|
reportActiveController: (gamepadId: string | null): void => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.reportActiveController, gamepadId);
|
|
},
|
|
notifyControllerStatus: (message: string): void => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.notifyControllerStatus, message);
|
|
},
|
|
getActiveControllerId: (): Promise<string | null> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getActiveControllerId),
|
|
|
|
getJimakuMediaInfo: (): Promise<JimakuMediaInfo> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.jimakuGetMediaInfo),
|
|
jimakuSearchEntries: (query: JimakuSearchQuery): Promise<JimakuApiResponse<JimakuEntry[]>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.jimakuSearchEntries, query),
|
|
jimakuListFiles: (query: JimakuFilesQuery): Promise<JimakuApiResponse<JimakuFileEntry[]>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.jimakuListFiles, query),
|
|
jimakuDownloadFile: (query: JimakuDownloadQuery): Promise<JimakuDownloadResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.jimakuDownloadFile, query),
|
|
|
|
tsukihimeSearchEntries: (
|
|
query: TsukihimeSearchQuery,
|
|
): Promise<TsukihimeApiResponse<TsukihimeEntry[]>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.tsukihimeSearchEntries, query),
|
|
tsukihimeListFiles: (
|
|
query: TsukihimeFilesQuery,
|
|
): Promise<TsukihimeApiResponse<TsukihimeSubtitleFile[]>> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.tsukihimeListFiles, query),
|
|
tsukihimeDownloadFile: (query: TsukihimeDownloadQuery): Promise<TsukihimeDownloadResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.tsukihimeDownloadFile, query),
|
|
tsukihimeGetSecondaryLanguages: (): Promise<string[]> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.tsukihimeGetSecondaryLanguages),
|
|
|
|
toggleDevTools: () => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.toggleDevTools);
|
|
},
|
|
|
|
toggleStatsOverlay: () => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.toggleStatsOverlay);
|
|
},
|
|
|
|
onSecondarySub: (callback: (text: string) => void) => {
|
|
onSecondarySubtitleSetEvent(callback);
|
|
},
|
|
|
|
onSecondarySubMode: (callback: (mode: SecondarySubMode) => void) => {
|
|
onSecondarySubtitleModeEvent(callback);
|
|
},
|
|
|
|
getSecondarySubMode: (): Promise<SecondarySubMode> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getSecondarySubMode),
|
|
getCurrentSecondarySub: (): Promise<string> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getCurrentSecondarySub),
|
|
focusMainWindow: () => ipcRenderer.invoke(IPC_CHANNELS.request.focusMainWindow) as Promise<void>,
|
|
activatePlaybackWindowForOverlayInteraction: () =>
|
|
ipcRenderer.invoke(
|
|
IPC_CHANNELS.request.activatePlaybackWindowForOverlayInteraction,
|
|
) as Promise<boolean>,
|
|
getSubtitleStyle: (): Promise<SubtitleStyleConfig | null> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getSubtitleStyle),
|
|
onSubtitleSelectionOpen,
|
|
getSubtitleSelection: () => ipcRenderer.invoke(IPC_CHANNELS.request.getSubtitleSelection),
|
|
applySubtitleSelection: (
|
|
request: import('./shared/subtitle-selection').SubtitleSelectionRequest,
|
|
) => ipcRenderer.invoke(IPC_CHANNELS.request.applySubtitleSelection, request),
|
|
onSubsyncManualOpen: onSubsyncManualOpenEvent,
|
|
runSubsyncManual: (request: SubsyncManualRunRequest): Promise<SubsyncResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.runSubsyncManual, request),
|
|
|
|
onKikuFieldGroupingRequest: onKikuFieldGroupingRequestEvent,
|
|
onKikuFieldGroupingCancel: onKikuFieldGroupingCancelEvent,
|
|
kikuBuildMergePreview: (request: KikuMergePreviewRequest): Promise<KikuMergePreviewResponse> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.kikuBuildMergePreview, request),
|
|
|
|
kikuFieldGroupingRespond: (choice: KikuFieldGroupingChoice) => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.kikuFieldGroupingRespond, choice);
|
|
},
|
|
|
|
getRuntimeOptions: (): Promise<RuntimeOptionState[]> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getRuntimeOptions),
|
|
setRuntimeOptionValue: (
|
|
id: RuntimeOptionId,
|
|
value: RuntimeOptionValue,
|
|
): Promise<RuntimeOptionApplyResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.setRuntimeOption, id, value),
|
|
onRuntimeOptionsChanged: (callback: (options: RuntimeOptionState[]) => void) => {
|
|
ipcRenderer.on(
|
|
IPC_CHANNELS.event.runtimeOptionsChanged,
|
|
(_event: IpcRendererEvent, options: RuntimeOptionState[]) => {
|
|
callback(options);
|
|
},
|
|
);
|
|
},
|
|
onOpenRuntimeOptions: onOpenRuntimeOptionsEvent,
|
|
onOpenSessionHelp: onOpenSessionHelpEvent,
|
|
onOpenChangelog: onOpenChangelogEvent,
|
|
getChangelogSnapshot: (options?: { refresh?: boolean }): Promise<ChangelogSnapshot> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getChangelogSnapshot, options),
|
|
onOpenPluginPanel: onOpenPluginPanelEvent,
|
|
onPluginPanelUpdate: (callback: (snapshot: PluginPanelSnapshot) => void): void => {
|
|
ipcRenderer.on(IPC_CHANNELS.event.pluginPanelUpdate, (_event, payload: unknown) => {
|
|
callback(toPluginPanelSnapshot(payload));
|
|
});
|
|
},
|
|
onPluginPanelClose: (callback: (panelId: number) => void): void => {
|
|
ipcRenderer.on(IPC_CHANNELS.event.pluginPanelClose, (_event, panelId: unknown) => {
|
|
if (typeof panelId === 'number') callback(panelId);
|
|
});
|
|
},
|
|
onOpenControllerSelect: onOpenControllerSelectEvent,
|
|
onOpenControllerDebug: onOpenControllerDebugEvent,
|
|
onOpenJimaku: onOpenJimakuEvent,
|
|
onOpenTsukihime: onOpenTsukihimeEvent,
|
|
onOpenYoutubeTrackPicker: onOpenYoutubeTrackPickerEvent,
|
|
onOpenMediaTimingReview: onOpenMediaTimingReviewEvent,
|
|
onMediaTimingReviewPreviewEnded: onMediaTimingReviewPreviewEndedEvent,
|
|
onMediaTimingReviewPreviewPosition: (
|
|
callback: (position: MediaTimingReviewPreviewPosition) => void,
|
|
): void => {
|
|
ipcRenderer.on(
|
|
IPC_CHANNELS.event.mediaTimingReviewPreviewPosition,
|
|
(_event, payload: unknown) => {
|
|
if (
|
|
typeof payload !== 'object' ||
|
|
payload === null ||
|
|
!('reviewId' in payload) ||
|
|
typeof payload.reviewId !== 'string' ||
|
|
!('time' in payload) ||
|
|
typeof payload.time !== 'number' ||
|
|
!Number.isFinite(payload.time)
|
|
)
|
|
return;
|
|
callback({ reviewId: payload.reviewId, time: payload.time });
|
|
},
|
|
);
|
|
},
|
|
previewMediaTimingReview: (
|
|
request: MediaTimingReviewPreviewRequest,
|
|
): Promise<MediaTimingReviewActionResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.mediaTimingReviewPreview, request),
|
|
getMediaTimingReviewFrame: (
|
|
request: MediaTimingReviewFrameRequest,
|
|
): Promise<MediaTimingReviewFrameResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.mediaTimingReviewFrame, request),
|
|
getMediaTimingReviewWaveform: (request: MediaTimingReviewWaveformRequest) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.mediaTimingReviewWaveform, request),
|
|
stopMediaTimingReviewPreview: (reviewId: string): Promise<MediaTimingReviewActionResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.mediaTimingReviewStopPreview, reviewId),
|
|
resolveMediaTimingReview: (
|
|
request: MediaTimingReviewResolveRequest,
|
|
): Promise<MediaTimingReviewActionResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.mediaTimingReviewResolve, request),
|
|
onOpenPlaylistBrowser: onOpenPlaylistBrowserEvent,
|
|
onOpenCharacterDictionaryManager: onOpenCharacterDictionaryManagerEvent,
|
|
onSubtitleSidebarToggle: onSubtitleSidebarToggleEvent,
|
|
onPrimarySubtitleBarToggle: onPrimarySubtitleBarToggleEvent,
|
|
onCancelYoutubeTrackPicker: onCancelYoutubeTrackPickerEvent,
|
|
onSessionNumericSelectionStart: onSessionNumericSelectionStartEvent,
|
|
onKeyboardModeToggleRequested: onKeyboardModeToggleRequestedEvent,
|
|
onControllerSuspendedChanged: onControllerSuspendedChangedEvent,
|
|
onControllerConfigChanged: onControllerConfigChangedEvent,
|
|
onControllerBackgroundPress: onControllerBackgroundPressEvent,
|
|
onPlaybackPauseChanged: onPlaybackPauseChangedEvent,
|
|
onLookupWindowToggleRequested: onLookupWindowToggleRequestedEvent,
|
|
getPlaylistBrowserSnapshot: (): Promise<PlaylistBrowserSnapshot> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getPlaylistBrowserSnapshot),
|
|
appendPlaylistBrowserFile: (pathValue: string): Promise<PlaylistBrowserMutationResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.appendPlaylistBrowserFile, pathValue),
|
|
playPlaylistBrowserIndex: (index: number): Promise<PlaylistBrowserMutationResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.playPlaylistBrowserIndex, index),
|
|
removePlaylistBrowserIndex: (index: number): Promise<PlaylistBrowserMutationResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.removePlaylistBrowserIndex, index),
|
|
movePlaylistBrowserIndex: (
|
|
index: number,
|
|
direction: 1 | -1,
|
|
): Promise<PlaylistBrowserMutationResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.movePlaylistBrowserIndex, index, direction),
|
|
youtubePickerResolve: (
|
|
request: YoutubePickerResolveRequest,
|
|
): Promise<YoutubePickerResolveResult> =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.youtubePickerResolve, request),
|
|
getCharacterDictionarySelection: (searchTitle?: string) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getCharacterDictionarySelection, searchTitle),
|
|
setCharacterDictionarySelection: (
|
|
mediaId: number,
|
|
replaceManagedMediaId?: number,
|
|
mediaTitle?: string,
|
|
) =>
|
|
ipcRenderer.invoke(
|
|
IPC_CHANNELS.request.setCharacterDictionarySelection,
|
|
mediaId,
|
|
replaceManagedMediaId,
|
|
mediaTitle,
|
|
),
|
|
getCharacterDictionaryManagerSnapshot: () =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.getCharacterDictionaryManagerSnapshot),
|
|
removeCharacterDictionaryManagedEntry: (mediaId: number) =>
|
|
ipcRenderer.invoke(IPC_CHANNELS.request.removeCharacterDictionaryManagedEntry, mediaId),
|
|
moveCharacterDictionaryManagedEntry: (mediaId: number, direction: 1 | -1) =>
|
|
ipcRenderer.invoke(
|
|
IPC_CHANNELS.request.moveCharacterDictionaryManagedEntry,
|
|
mediaId,
|
|
direction,
|
|
),
|
|
notifyOverlayModalClosed: (modal, instanceId) => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.overlayModalClosed, modal, instanceId);
|
|
},
|
|
notifyOverlayModalOpened: (modal) => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.overlayModalOpened, modal);
|
|
},
|
|
reportOverlayContentBounds: (measurement: OverlayContentMeasurement) => {
|
|
ipcRenderer.send(IPC_CHANNELS.command.reportOverlayContentBounds, measurement);
|
|
},
|
|
onSessionBindingsChanged: (
|
|
callback: (bindings: import('./types').CompiledSessionBinding[]) => void,
|
|
) => {
|
|
ipcRenderer.on(
|
|
IPC_CHANNELS.event.sessionBindingsChanged,
|
|
(_event, bindings: import('./types').CompiledSessionBinding[]) => callback(bindings),
|
|
);
|
|
},
|
|
onMpvInputBindingsChanged: (callback: () => void) => {
|
|
ipcRenderer.on(IPC_CHANNELS.event.mpvInputBindingsChanged, () => callback());
|
|
},
|
|
onConfigHotReload: (callback: (payload: ConfigHotReloadPayload) => void) => {
|
|
ipcRenderer.on(
|
|
IPC_CHANNELS.event.configHotReload,
|
|
(_event: IpcRendererEvent, payload: ConfigHotReloadPayload) => {
|
|
callback(payload);
|
|
},
|
|
);
|
|
},
|
|
};
|
|
|
|
contextBridge.exposeInMainWorld('electronAPI', electronAPI);
|