Next-sentence suggestions now arrive one at a time up to twelve, shown three per page with Ctrl+Alt+Up/Down to move, Left/Right to page, Enter to accept and Esc to close; old default bindings migrate and the panel guide follows the live bindings. The overlay is redesigned, stays put while candidates stream and sits outside the input box when no caret is reported. The personal phrase memory stops learning from terminals, code editors and the coding-agent hub, ignores symbol-heavy lines and empty-field placeholders, and prunes existing entries that break those rules. Fixes suggestion keys starting dictation, installs stuck on a pre-1.5.0 speech engine without the focus endpoint, Ollama runner windows flashing while typing, the speech engine starting twice, and cold-model timeouts. Live captions can be dragged to a remembered position and show a waiting notice until the first line arrives. Bumps the product version to 1.6.0 (Android/iOS build 1060000).
83 lines
3.1 KiB
TypeScript
83 lines
3.1 KiB
TypeScript
// src/main/ipc/caption-handlers.ts
|
|
// Phase 10.1: Live Caption IPC 핸들러
|
|
|
|
import { ipcMain, session, desktopCapturer } from 'electron'
|
|
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
|
|
import { ipcSuccess, ipcError, ErrorCode } from '@d3ro/core/errors'
|
|
import { getCaptionService } from '../services/CaptionService'
|
|
import type { CaptionConfig } from '@d3ro/core/types'
|
|
import {
|
|
endCaptionOverlayDrag,
|
|
resetCaptionOverlayPosition,
|
|
setCaptionOverlayInteractive,
|
|
startCaptionOverlayDrag
|
|
} from '../windows/WindowManager'
|
|
|
|
export function registerCaptionHandlers(): void {
|
|
// ── 자막 창 손잡이 (팝업 → 메인) ──
|
|
ipcMain.on(IPC_CHANNELS.POPUP_CAPTION.SET_INTERACTIVE, (_event, interactive: unknown) => {
|
|
setCaptionOverlayInteractive(interactive === true)
|
|
})
|
|
ipcMain.on(IPC_CHANNELS.POPUP_CAPTION.DRAG_START, () => startCaptionOverlayDrag())
|
|
ipcMain.on(IPC_CHANNELS.POPUP_CAPTION.DRAG_END, () => endCaptionOverlayDrag())
|
|
ipcMain.on(IPC_CHANNELS.POPUP_CAPTION.RESET_POSITION, () => resetCaptionOverlayPosition())
|
|
|
|
// 시스템 오디오 루프백: setDisplayMediaRequestHandler로 audio: 'loopback' 설정
|
|
ipcMain.handle(IPC_CHANNELS.SYSTEM_AUDIO.ENABLE_LOOPBACK, async () => {
|
|
session.defaultSession.setDisplayMediaRequestHandler(async (_request, callback) => {
|
|
const sources = await desktopCapturer.getSources({ types: ['screen'] })
|
|
if (sources.length === 0) {
|
|
callback({})
|
|
return
|
|
}
|
|
callback({ video: sources[0], audio: 'loopback' })
|
|
})
|
|
return ipcSuccess(undefined)
|
|
})
|
|
|
|
ipcMain.handle(IPC_CHANNELS.SYSTEM_AUDIO.DISABLE_LOOPBACK, async () => {
|
|
session.defaultSession.setDisplayMediaRequestHandler(null)
|
|
return ipcSuccess(undefined)
|
|
})
|
|
ipcMain.handle(IPC_CHANNELS.CAPTION.START, async () => {
|
|
try {
|
|
await getCaptionService().start()
|
|
return ipcSuccess(undefined)
|
|
} catch (err) {
|
|
const code =
|
|
err instanceof Error && 'code' in err
|
|
? (err as { code: number }).code
|
|
: ErrorCode.CaptionStartFailed
|
|
const message = err instanceof Error ? err.message : String(err)
|
|
return ipcError(code, message)
|
|
}
|
|
})
|
|
|
|
ipcMain.handle(IPC_CHANNELS.CAPTION.STOP, async () => {
|
|
try {
|
|
await getCaptionService().stop()
|
|
return ipcSuccess(undefined)
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err)
|
|
return ipcError(ErrorCode.CaptionStartFailed, message)
|
|
}
|
|
})
|
|
|
|
ipcMain.handle(IPC_CHANNELS.CAPTION.GET_STATE, async () => {
|
|
return ipcSuccess(getCaptionService().getState())
|
|
})
|
|
|
|
ipcMain.handle(IPC_CHANNELS.CAPTION.SET_CONFIG, async (_event, config: Partial<CaptionConfig>) => {
|
|
getCaptionService().setConfig(config)
|
|
return ipcSuccess(undefined)
|
|
})
|
|
|
|
ipcMain.handle(IPC_CHANNELS.CAPTION.GET_CONFIG, async () => {
|
|
return ipcSuccess(getCaptionService().getConfig())
|
|
})
|
|
|
|
// 렌더러 → 메인: 시스템 오디오 PCM 데이터 수신
|
|
ipcMain.on(IPC_CHANNELS.CAPTION.SYSTEM_AUDIO_DATA, (_event, data: ArrayBuffer) => {
|
|
getCaptionService().onSystemAudioData(Buffer.from(data))
|
|
})
|
|
}
|