CapsLock + 알파벳으로 앱 전환하기 — Kanata 와 AHK 를 나눠 쓴 이유
AutoHotkey 단독으로 CapsLock 을 modifier 로 쓰면 토글이 새거나 오발이 난다. Kanata 로 드라이버 수준에서 조합키로 바꾸고 AHK 는 앱 전환만 맡기면 안정된다.
CapsLock 을 누른 채 알파벳 한 글자로 자주 쓰는 앱을 띄우고 싶었다. AutoHotkey 로 만들면 30줄이면 될 것 같았는데, 실제로 써보니 계속 어긋났다.
- CapsLock 이 진짜로 toggle 돼서 대문자가 나옴
- CapsLock 을 안 눌렀는데 알파벳만으로 앱이 전환되는 오발
- 창 포커스가 바뀌는 타이밍에 특히 잘 깨짐
AHK 를 붙잡고 고치는 대신 역할을 둘로 쪼개서 해결했다.
구조 — 어디서 끊을 것인가
Kanata (드라이버 수준) → CapsLock 을 가로채 Ctrl+Alt+Shift+키 로 변환
AutoHotkey → ^!+키 를 받아 앱 전환만 수행
AHK 입장에서는 그냥 평범한 조합키 핫키가 된다. CapsLock 이라는 단어가 AHK 스크립트에서 완전히 사라지고, 불안정의 원인도 같이 사라진다.
이 분리가 통하는 이유는 계층이 다르기 때문이다. Kanata 는 키보드 입력을 OS 가 해석하기 전에 가로채므로 CapsLock 의 토글 상태 자체가 발생하지 않는다. AHK 는 OS 가 이미 해석한 키 이벤트를 받는 위치라, CapsLock 을 억누르려면 항상 뒤늦게 취소하는 모양이 된다. 그 시간차가 오발의 정체였다.
설치
Kanata
GitHub Releases 의 windows-binaries-x64.zip 에서 다음 바이너리를 쓴다.
kanata_winiov2_cmd_allowed_x64_gui.exe
이름이 긴 만큼 선택지가 셋 들어 있다.
winiov2— 별도 드라이버 설치가 필요 없다.wintercept빌드는 버그가 있어 권하지 않는다.cmd_allowed—cmd액션을 쓸 수 있다.gui— 시스템 트레이로 실행된다. 검은 터미널 창이 항상 떠 있지 않아도 된다.
Windows Smart App Control 이 막으면 파일 우클릭 → 속성 → 차단 해제.
Kanata 는 관리자 권한이 필요해서 시작 프로그램 폴더로는 자동 실행이 안 된다. Task Scheduler 에 등록한다.
- 트리거: 로그온할 때
- “가장 높은 수준의 권한으로 실행” 체크
- 시작 위치를 반드시 지정 —
kanata.kbd를 상대경로로 찾기 때문에 비워두면 설정 파일을 못 찾는다
AutoHotkey
이쪽은 관리자 권한이 필요 없다. shell:startup 폴더에 바로가기만 넣으면 끝이다.
kanata.kbd
(defcfg
process-unmapped-keys yes
)
(defsrc
caps 3 4 c d e f g k n w
)
(deflayer default
@caps 3 4 c d e f g k n w
)
(deflayer appswitch
_ @gc @wt @ch @dc @ex @fk @dg @kk @nt @ws
)
(defalias
;; tap-hold-press: 다른 키를 누르는 순간 즉시 hold 발동
caps (tap-hold-press 200 200 esc (layer-while-held appswitch))
gc (multi lctl lalt lsft 3)
wt (multi lctl lalt lsft 4)
ch (multi lctl lalt lsft c)
dc (multi lctl lalt lsft d)
ex (multi lctl lalt lsft e)
fk (multi lctl lalt lsft f)
dg (multi lctl lalt lsft g)
kk (multi lctl lalt lsft k)
nt (multi lctl lalt lsft n)
ws (multi lctl lalt lsft w)
)
두 가지가 중요하다.
tap-hold 가 아니라 tap-hold-press 다. tap-hold 는 지정한 시간이 지나야 hold 로 확정한다. 빠르게 CapsLock+C 를 치면 아직 확정 전이라 tap(=Esc)으로 새는 경우가 생긴다. tap-hold-press 는 다른 키가 눌리는 순간 즉시 hold 로 확정하므로 타이핑 속도와 무관하게 일관된다. 간헐적 오작동이 여기서 대부분 사라졌다.
defsrc 는 파일 전체에 하나만 허용된다. 키를 추가할 때 블록을 새로 쓰면 에러가 난다. 기존 블록을 고쳐야 한다.
CapsLock 을 짧게 탭하면 Esc 가 나온다. 어차피 뺏은 키라 놀리느니 쓸모를 주는 편이 낫다.
app-switch.ahk
#Requires AutoHotkey v2.0
AppData := EnvGet("LOCALAPPDATA")
^!+3::SwitchOrRun("Google Chat ahk_exe chrome.exe", "chrome.exe --app=https://chat.google.com")
^!+4::SwitchOrRun("ahk_exe WindowsTerminal.exe", "wt.exe")
^!+c::{
wins := WinGetList("ahk_exe chrome.exe")
for hwnd in wins {
title := WinGetTitle("ahk_id " hwnd)
; title != "" 체크 필수 — WinGetList 가 숨김 유틸리티 창도 반환한다
if (title != "" && !InStr(title, "Google Chat")) {
if WinActive("ahk_id " hwnd)
return
WinActivate "ahk_id " hwnd
return
}
}
Run "chrome.exe"
}
^!+d::SwitchOrRun("ahk_exe Discord.exe", AppData "\Discord\Update.exe --processStart Discord.exe")
^!+e::SwitchOrRun("ahk_exe explorer.exe", "explorer.exe")
^!+f::SwitchOrRun("ahk_exe Fork.exe", AppData "\Fork\current\Fork.exe")
^!+g::SwitchOrRun("ahk_exe datagrip64.exe", AppData "\JetBrains\Toolbox\scripts\datagrip.cmd")
^!+n::SwitchOrRun("ahk_exe Notion.exe", AppData "\Programs\Notion\Notion.exe")
^!+w::SwitchOrRun("ahk_exe webstorm64.exe", AppData "\JetBrains\Toolbox\scripts\webstorm.cmd")
SwitchOrRun(winTitle, target) {
if WinExist(winTitle) {
if WinActive(winTitle)
return
WinActivate winTitle
} else {
Run target
}
}
SwitchOrRun 이 이미 활성화된 창이면 아무것도 안 하고 return 하는 게 의도적이다. 같은 키를 두 번 눌렀을 때 창이 깜빡이거나 포커스가 튀는 걸 막는다.
^!+c 만 함수를 안 쓰고 풀어 쓴 이유는 Chrome 창을 골라야 하기 때문이다. 앱 모드로 띄운 Chrome 창과 일반 브라우저 창이 같은 chrome.exe 라서, 제목으로 걸러야 원하는 쪽이 잡힌다.
여기서 title != "" 체크가 없으면 동작이 이상해진다. WinGetList 는 제목이 빈 숨김 유틸리티 창까지 반환한다. 그게 먼저 잡히면 WinActivate 가 보이지 않는 창을 활성화하고 함수가 끝나버려서, 사용자 눈에는 “아무 일도 안 일어남” 또는 “왜 새 창이 뜨지”로 보인다.
앱을 추가할 때
키만 바꾸는 경우라면 AHK 파일에서 ^!+<키>:: 줄만 고치면 된다. Kanata 는 손댈 필요가 없다.
새 키를 추가하는 경우에는 kanata.kbd 의 네 곳을 모두 고쳐야 한다.
defsrcdeflayer defaultdeflayer appswitchdefalias
하나라도 빠지면 레이어 간 칸 수가 안 맞아 로드에 실패한다.
함정 모음
| 함정 | 해결 |
|---|---|
| AHK 단독 CapsLock 이 불안정 | Kanata 로 계층 분리 |
WinGetList 가 숨김 창 반환 → 엉뚱한 창 활성화 |
title != "" 체크 |
tap-hold 간헐적 오작동 |
tap-hold-press 로 교체 |
| Kanata 가 관리자 권한을 요구 | Task Scheduler “최고 권한” + 시작 위치 지정 |
| Smart App Control 차단 | 속성 → 차단 해제 |
defsrc 중복 에러 |
블록 추가가 아니라 교체 |
정리
한 도구로 다 하려다 실패한 케이스였다. CapsLock 을 modifier 로 바꾸는 일은 드라이버 계층이, 창을 찾아 활성화하는 일은 애플리케이션 계층이 잘한다. 각자 잘하는 층에 맡기니 설정 파일 두 개가 늘어난 대신 오작동이 없어졌다.