From e4e3e49419ad5c9987792849846ed93bc82823fb Mon Sep 17 00:00:00 2001 From: lacvet Date: Mon, 6 Apr 2026 18:09:21 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B8=80=EB=A1=9C=EB=B2=8C=20=ED=82=A4?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20=ED=9B=85=20=ED=83=80=EC=9D=B4=ED=95=91=20?= =?UTF-8?q?=EB=B2=84=EB=B2=85=EC=9E=84=20=EC=99=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 다른 앱 타이핑 시에도 AX Copilot가 과하게 개입하던 글로벌 키보드 훅의 핫패스를 줄였다. InputListener는 모든 키마다 파일 대화상자 억제 창 검사를 하지 않고 실제 핫키·캡처·키필터 후보 키에서만 검사하도록 최적화했다. SnippetExpander는 추적 중이 아닐 때 ';' 시작 키 외에는 즉시 반환하게 바꿔 일반 타이핑 중 반복적인 modifier 상태 확인과 버퍼 처리를 제거했다. README와 DEVELOPMENT 문서를 2026-04-06 18:34 (KST) 기준으로 갱신했고, Release 빌드 검증에서 경고 0 / 오류 0을 확인했다. --- README.md | 3 +++ docs/DEVELOPMENT.md | 2 ++ src/AxCopilot/Core/InputListener.cs | 13 +++++++++---- src/AxCopilot/Core/SnippetExpander.cs | 26 ++++++++++++++++++-------- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8be7161..0303b54 100644 --- a/README.md +++ b/README.md @@ -1322,3 +1322,6 @@ MIT License - 런처 색인 구조를 임시 지연 실행에서 `영속 캐시 + watcher 증분 반영` 방식으로 바꿨다. [IndexService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/IndexService.cs)는 이제 `%APPDATA%\\AxCopilot\\index\\launcher-index.json`에 파일 시스템 인덱스를 저장하고, 앱 시작 시 캐시를 즉시 로드해 첫 검색부터 이전 색인을 재사용한다. - `FileSystemWatcher`도 더 이상 파일 하나 바뀔 때마다 3초 뒤 전체 재빌드를 때리지 않고, 생성/삭제/파일 이름 변경은 가능한 범위에서 해당 항목만 증분 반영한다. 디렉터리 이름 변경처럼 하위 경로 전체 영향이 큰 경우에만 전체 재색인으로 폴백한다. - [App.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/App.xaml.cs) 는 앱 시작 시 캐시 로드와 watcher 시작을 바로 수행하고, 실제 무거운 전체 재색인은 첫 검색 시 `EnsureIndexWarmupStarted()`로 한 번만 보강 실행하도록 정리했다. 이 변경으로 런처는 즉시 검색 가능 상태를 유지하면서도, 평소엔 전체 재색인 비용을 반복해서 치르지 않게 됐다. +- 업데이트: 2026-04-06 18:34 (KST) + - 다른 앱에서 타이핑할 때도 AX Copilot가 키 입력 훅 경로에서 과하게 개입하던 부분을 줄였다. [InputListener.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Core/InputListener.cs)는 이제 모든 키다운마다 시스템 파일 대화상자 판정을 하지 않고, 실제 핫키 메인 키·캡처 메인 키·키 필터가 필요한 경우에만 억제 창 검사를 수행한다. + - [SnippetExpander.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Core/SnippetExpander.cs)는 추적 중이 아닐 때 `;` 시작 키 외에는 즉시 반환하도록 바꿨다. 이전에는 일반 타이핑 중에도 모든 키마다 `Ctrl/Alt/Shift` 상태를 읽고 버퍼 로직을 거쳤는데, 이제는 실제 스니펫 시작 상황에서만 그런 검사를 하게 되어 글로벌 키보드 훅의 평상시 부담을 줄였다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index e59c397..c9fdf53 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -5002,3 +5002,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - Document update: 2026-04-06 18:24 (KST) - Reworked launcher indexing in `IndexService.cs` from a “full rebuild on every change” structure to a persisted cache plus incremental watcher model. Launcher file-system entries are now saved to `%APPDATA%\AxCopilot\index\launcher-index.json`, loaded immediately at startup, and merged with aliases/built-in app entries before the first search. - Document update: 2026-04-06 18:24 (KST) - `FileSystemWatcher` changes are now handled incrementally where possible. File create/delete/rename events update only the affected launcher index entries and debounce cache persistence, while high-impact directory rename/delete cases still fall back to a full rebuild for correctness. - Document update: 2026-04-06 18:24 (KST) - `App.xaml.cs` now restores eager cache availability by calling `LoadCachedIndex()` and `StartWatchers()` at startup, while keeping the heavier full scan on the existing `EnsureIndexWarmupStarted()` path. This keeps the launcher responsive from the first open without reintroducing continuous full rescans as idle background work. +- Document update: 2026-04-06 18:34 (KST) - Reduced the per-keystroke hot-path cost of the global keyboard hook. `InputListener.cs` now performs the suppressed-foreground-window check only when the current key could actually trigger the launcher hotkey, the capture hotkey, or a registered key filter, instead of doing the window-class lookup on every keydown. +- Document update: 2026-04-06 18:34 (KST) - `SnippetExpander.cs` now short-circuits immediately for normal typing unless snippet tracking is already active or the current key is the `;` trigger. This removes repeated `GetAsyncKeyState` and buffer logic from ordinary typing in other apps and makes the low-level hook substantially lighter under continuous input. diff --git a/src/AxCopilot/Core/InputListener.cs b/src/AxCopilot/Core/InputListener.cs index 7d895fd..c0565e6 100644 --- a/src/AxCopilot/Core/InputListener.cs +++ b/src/AxCopilot/Core/InputListener.cs @@ -177,12 +177,17 @@ public class InputListener : IDisposable if (wParam != WM_KEYDOWN && wParam != WM_SYSKEYDOWN) return CallNextHookEx(_hookHandle, nCode, wParam, lParam); + var isHotkeyMainKey = vkCode == _hotkey.VkCode; + var isCaptureMainKey = _captureHotkeyEnabled && vkCode == _captureHotkey.VkCode; + var shouldRunKeyFilter = KeyFilter != null; + var needsSuppressedWindowCheck = isHotkeyMainKey || isCaptureMainKey || shouldRunKeyFilter; + // ─── 시스템 파일 대화상자에서는 핫키·스니펫 전부 비활성 ────────────── - if (IsSuppressedForegroundWindow()) + if (needsSuppressedWindowCheck && IsSuppressedForegroundWindow()) return CallNextHookEx(_hookHandle, nCode, wParam, lParam); // ─── 핫키 감지 ────────────────────────────────────────────────────── - if (!SuspendHotkey && vkCode == _hotkey.VkCode) + if (!SuspendHotkey && isHotkeyMainKey) { bool ctrlOk = !_hotkey.Ctrl || (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0; bool altOk = !_hotkey.Alt || (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; @@ -202,7 +207,7 @@ public class InputListener : IDisposable } // ─── 글로벌 캡처 단축키 감지 ───────────────────────────────────────── - if (!SuspendHotkey && _captureHotkeyEnabled && vkCode == _captureHotkey.VkCode) + if (!SuspendHotkey && isCaptureMainKey) { bool ctrlOk = !_captureHotkey.Ctrl || (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0; bool altOk = !_captureHotkey.Alt || (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; @@ -221,7 +226,7 @@ public class InputListener : IDisposable } // ─── 스니펫 키 필터 ───────────────────────────────────────────────── - if (KeyFilter?.Invoke(vkCode) == true) + if (shouldRunKeyFilter && KeyFilter?.Invoke(vkCode) == true) return (IntPtr)1; return CallNextHookEx(_hookHandle, nCode, wParam, lParam); diff --git a/src/AxCopilot/Core/SnippetExpander.cs b/src/AxCopilot/Core/SnippetExpander.cs index 4d3bddb..b31c537 100644 --- a/src/AxCopilot/Core/SnippetExpander.cs +++ b/src/AxCopilot/Core/SnippetExpander.cs @@ -49,20 +49,30 @@ public class SnippetExpander // 자동 확장 비활성화 시 즉시 통과 if (!_settings.Settings.Launcher.SnippetAutoExpand) return false; - // Ctrl/Alt 조합은 무시 (단축키와 충돌 방지) - if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) { _tracking = false; _buffer.Clear(); return false; } - if ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0) { _tracking = false; _buffer.Clear(); return false; } - - // ─── 트리거 시작: ';' 입력 ────────────────────────────────────────── - if (vkCode == VK_OEM_1 && (GetAsyncKeyState(VK_SHIFT) & 0x8000) == 0) + // 추적 중이 아닐 때는 ';' 시작 키만 검사해서 훅 경로 부담을 최소화합니다. + if (!_tracking) { + if (vkCode != VK_OEM_1) + return false; + + if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0) + return false; + + if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) + return false; + + if ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0) + return false; + _tracking = true; _buffer.Clear(); _buffer.Append(';'); - return false; // ';'는 소비하지 않고 앱으로 전달 + return false; } - if (!_tracking) return false; + // Ctrl/Alt 조합은 무시 (단축키와 충돌 방지) + if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) { _tracking = false; _buffer.Clear(); return false; } + if ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0) { _tracking = false; _buffer.Clear(); return false; } // ─── 영문자/숫자 — 버퍼에 추가 ───────────────────────────────────── if ((vkCode >= 0x41 && vkCode <= 0x5A) || // A-Z