히스토리 팝업 수정: ESC 닫기 + Arrow 키 네비게이션

- Arrow↑↓/Enter/Escape/1-9 키를 팝업 열릴 때 globalShortcut 등록, 닫힐 때 해제
- showHistoryPopup: 2-phase 제거 → 즉시 show (렌더러 비활성 문제 방지)
- hideHistoryPopup/itemSelected/popupDismissed에서 nav 키 해제
This commit is contained in:
Yun Chan 2026-04-05 12:38:54 +09:00
parent 2392d05ded
commit 6e9e8b1f29
2 changed files with 59 additions and 21 deletions

View file

@ -117,11 +117,49 @@ async function initPopupWindows(): Promise<void> {
globalShortcut.register('Ctrl+Shift+V', () => {
if (isHistoryPopupVisible()) {
hideHistoryPopup()
unregisterPopupNavKeys()
} else {
const entries = getHistoryService().list({ page: 0, pageSize: 10 }).entries
showHistoryPopup(entries as unknown as Array<Record<string, unknown>>)
registerPopupNavKeys()
}
})
}
// 히스토리 팝업 키 네비게이션 등록/해제
const POPUP_NAV_KEYS: Array<{ accel: string; key: string }> = [
{ accel: 'Up', key: 'ArrowUp' },
{ accel: 'Down', key: 'ArrowDown' },
{ accel: 'Return', key: 'Enter' },
{ accel: 'Escape', key: 'Escape' },
{ accel: '1', key: '1' }, { accel: '2', key: '2' }, { accel: '3', key: '3' },
{ accel: '4', key: '4' }, { accel: '5', key: '5' }, { accel: '6', key: '6' },
{ accel: '7', key: '7' }, { accel: '8', key: '8' }, { accel: '9', key: '9' },
]
function registerPopupNavKeys(): void {
for (const { accel, key } of POPUP_NAV_KEYS) {
try {
globalShortcut.register(accel, () => {
if (isHistoryPopupVisible()) {
sendKeyToHistoryPopup(key)
}
})
} catch {
// 일부 키는 globalShortcut으로 등록 불가할 수 있음
}
}
}
function unregisterPopupNavKeys(): void {
for (const { accel } of POPUP_NAV_KEYS) {
try {
globalShortcut.unregister(accel)
} catch {
// 이미 해제된 경우 무시
}
}
}
async function initVoiceMode(): Promise<void> {
@ -188,6 +226,7 @@ function setupHistoryPopupIPC(): void {
// 아이템 선택 → 텍스트 삽입
ipcMainRef.on('history:itemSelected', (_event: Electron.IpcMainEvent, data: { text: string }) => {
hideHistoryPopup()
unregisterPopupNavKeys()
setTimeout(async () => {
try {
await getTextInsertService().insertText(data.text)
@ -200,5 +239,6 @@ function setupHistoryPopupIPC(): void {
// 팝업 닫기
ipcMainRef.on('history:popupDismissed', () => {
hideHistoryPopup()
unregisterPopupNavKeys()
})
}

View file

@ -308,32 +308,30 @@ export function getHistoryPopupWindow(): BrowserWindow {
export function showHistoryPopup(entries: Array<Record<string, unknown>>): void {
const win = getHistoryPopupWindow()
win.webContents.send('history:showItems', { entries })
const handler = (_event: Electron.IpcMainEvent, data: { width: number; height: number }) => {
ipcMain.removeListener('history:popupMeasured', handler)
// 커서 위치에 즉시 배치+표시 (2-phase 제거 — 숨겨진 윈도우 렌더러 비활성 문제 방지)
const cursorPos = screen.getCursorScreenPoint()
const display = screen.getDisplayNearestPoint(cursorPos)
let x = cursorPos.x - Math.round(data.width / 2)
let y = cursorPos.y - data.height - 20
const popupWidth = 380
const popupHeight = Math.min(60 + entries.length * 44, 500)
x = Math.max(display.workArea.x, Math.min(x, display.workArea.x + display.workArea.width - data.width))
let x = cursorPos.x - Math.round(popupWidth / 2)
let y = cursorPos.y - popupHeight - 20
x = Math.max(display.workArea.x, Math.min(x, display.workArea.x + display.workArea.width - popupWidth))
if (y < display.workArea.y) {
y = cursorPos.y + 20
}
win.setBounds({ x, y, width: data.width, height: data.height })
win.setBounds({ x, y, width: popupWidth, height: popupHeight })
win.webContents.send('history:showItems', { entries })
if (!win.isVisible()) {
win.showInactive()
}
win.webContents.send('history:show', {})
}
ipcMain.on('history:popupMeasured', handler)
}
export function hideHistoryPopup(): void {