개요
kanata로 키보드를 전면 리매핑해서 쓰고 있다. CapsLock은 탭하면 한/영 전환, 누르고 있으면 마우스 레이어가 뜨도록 완전히 다른 동작으로 바꿔놨다. 그런데 MSTSC(원격 데스크톱)로 접속해서 작업하다 보면, 물리 CapsLock 키를 눌렀을 뿐인데 로컬 PC(HOST)의 CapsLock 잠금 상태(LED)가 같이 켜지는 증상이 있었다. kanata가 그 키를 이미 다른 동작으로 완전히 가로챘는데도 말이다. 이 글은 이 증상을 --debug 로그와 직접 만든 PowerShell 폴링 스크립트로 실측 디버깅해서 근본 원인을 찾아낸 과정과, 같은 계열에서 발견한 두 번째 문제(한/영 키를 누르면 Alt가 고정되는 문제)를 kanata 설정 한 줄로 해결한 과정을 정리한다. 환경은 Windows 11, kanata winIOv2 빌드다.
문제
증상은 두 가지였다.
- MSTSC로 원격 세션에 접속해 작업 중, 물리 CapsLock 키(kanata가 이미 한/영·마우스 레이어로 리매핑한 키)를 누르면 HOST의 CapsLock 잠금 상태가 켜진다.
- 키보드 오른쪽 아래의 한/영 키(물리적으로 RAlt 위치)를 누르면 Alt 키가 눌린 채로 고정되는 문제가 있다.
kanata 설정(bin/kanata.kbd)에는 이미 다음과 같이 CapsLock과 RAlt를 완전히 다른 동작으로 대체해뒀다.
| |
리매핑이 이미 되어 있는데도 물리 키의 부작용(CapsLock 토글, Alt 고정)이 새어 나온다는 게 이상했다. “kanata가 아직 뭔가를 놓치고 있나?“에서 출발해 실측으로 확인하기로 했다.
해결 전략
두 증상을 별개로 다뤘다.
CapsLock 토글은 먼저 재현 조건과 타이밍을 정확히 잡아야 했다. kanata가 키 이벤트를 받는 시점과 Windows가 CapsLock 잠금 상태를 바꾸는 시점을 각각 로그로 남겨서 순서를 비교하면, “kanata가 이벤트를 놓치는 것"인지 “이벤트를 받고도 토글을 못 막는 것"인지 구분할 수 있다고 판단했다. 전자라면 kanata 설정이나 훅 등록 순서를 고치면 되고, 후자라면 사용자 모드 소프트웨어로는 원천적으로 해결이 안 되는 문제라서 접근 자체를 바꿔야 한다.
Alt 고정은 kanata 공식 문서에 있는 AltGr 관련 알려진 이슈(windows-altgr)와 증상이 정확히 일치해서, 문서가 제시하는 설정을 그대로 적용하는 쪽으로 바로 방향을 잡았다.
구현
1. CapsLock 토글 타이밍 실측
kanata를 --debug로 실행해 콘솔에 이벤트 로그를 남기고, 동시에 별도 PowerShell 창에서 CapsLock 잠금 상태를 30ms 간격으로 폴링하는 스크립트를 돌렸다.
| |
물리 CapsLock을 눌러가며 두 로그의 타임스탬프를 나란히 비교했다.
| |
CapsLock 잠금 상태가 먼저 False -> True로 바뀌고, 그로부터 약 80ms 뒤에야 kanata의 저수준 훅(llhook)이 KEY_CAPSLOCK Press 이벤트를 받았다. 두 번째 누름도 마찬가지로 토글이 먼저(True -> False), kanata 수신이 88ms 뒤였다. 즉 Windows OS가 CapsLock 토글 비트를 훅에 이벤트를 넘기기도 전에 이미 확정해버린다 — kanata가 아무리 완벽하게 이벤트를 가로채도 이 시점 이후이므로 토글 자체는 막을 수 없다는 뜻이다.
이 현상은 kanata만의 문제가 아니었다. Microsoft의 공식 PowerToys 저장소에도 동일한 증상이 이슈로 등록되어 있다(#7302). 거기서 개발자는 KeyTweak·SharpKeys는 이 문제가 없다고 언급했는데, 이 두 도구의 공통점은 레지스트리 Scancode Map(HKLM\SYSTEM\CurrentControlSet\Control\Keyboard Layout의 Scancode Map)으로 커널의 키보드 클래스 드라이버 단계에서 스캔코드 자체를 치환한다는 것이다. 이 단계에서 바꾸면 Windows는 애초에 그 키를 “CapsLock"으로 인식하지 못하므로 토글이 생성될 여지가 없다. 반면 kanata의 winIOv2나 PowerToys Keyboard Manager는 저수준 훅(사용자 모드)이라 OS가 토글을 이미 확정한 뒤에야 이벤트를 받는다 — 구조적으로 더 늦다.
추가로 확인한 것은, kanata 자체가 CapsLock의 토글 상태를 읽는 기능이 없다는 점이다(kanata 메인테이너가 GitHub Discussion #1675에서 “Caps Lock state is not detectable by Kanata today"라고 직접 확인). 그래서 “토글이 켜지면 kanata가 자동으로 꺼준다” 같은 설정 자체가 불가능하다. 외부 감시 스크립트로 토글 상태를 폴링하다가 켜지면 다시 눌러 꺼주는 방법도 검토했지만, 그 보정 입력 자체가 다시 물리 CapsLock 스캔코드로 인식되어 kanata의 defsrc caps 규칙에 걸려 원래 설정된 동작(한/영 전환)으로 바뀌어버린다. 즉 순수 사용자 모드 소프트웨어로는 확실한 보정이 불가능하다는 결론에 이르렀고, 남은 선택지는 레지스트리 Scancode Map 또는 커널 필터 드라이버(kanata의 wintercept, Interception 드라이버 기반) 둘 중 하나뿐이다.
2. RAlt(한/영 키) Alt 고정 문제
이 문제는 실측이 필요 없었다. kanata 설정 가이드의 “Windows only: windows-altgr” 항목이 증상을 정확히 설명하고 있었다.
많은 non-US 키보드가 AltGr로 취급하는 RAlt는, Windows가 눌릴 때 내부적으로 가짜 LCtrl keydown을 함께 생성한다.
process-unmapped-keys yes나defsrc에ralt/lctl이 있는 상태에서 이 가짜 LCtrl을 정리하지 않으면 Alt/Ctrl이 고정될 수 있다.
원인은 bin/kanata.kbd에서 한/영 키를 ralt 물리 위치에 매핑하고 process-unmapped-keys yes를 켜둔 것이 정확히 이 조건에 해당했기 때문이다. defcfg에 한 줄만 추가하면 해결된다.
| |
windows-altgr에는 두 값이 있다. cancel-lctl-press는 가짜 LCtrl press 자체를 아예 없애고, add-lctl-release는 RAlt를 뗄 때 LCtrl release를 추가로 보내는 방식이다. RAlt를 계속 눌러야 다른 조합키(예: RA-a처럼 AltGr+문자)로 특수문자를 입력해야 하는 상황이 아니라면 add-lctl-release 쪽이 더 안전하다.
대안: 왜 감시 스크립트로 CapsLock을 보정하지 않았나
CapsLock 문제에 대해 레지스트리·드라이버 없이 순수 소프트웨어로 고치는 방법도 시도해보려 했다. PowerShell로 CapsLock 잠금 상태를 폴링하다가 원치 않게 켜지면 즉시 CapsLock을 한 번 더 눌러서 꺼주는 감시 스크립트다. 하지만 위에서 실측한 대로 kanata가 물리 CapsLock 스캔코드를 완전히 가로채는 구조이기 때문에, 감시 스크립트가 보내는 보정용 CapsLock 입력조차 kanata에 의해 다시 한/영 전환 동작으로 치환되어버려 실제 토글은 꺼지지 않는다. 이 접근은 채택하지 않았고, 레지스트리 Scancode Map 또는 Interception 드라이버 중 하나를 선택하는 문제로 남겨뒀다.
적용 전후 비교
| 항목 | 적용 전 | 적용 후 |
|---|---|---|
| RAlt(한/영 키) 사용 시 Alt 고정 | 발생 | windows-altgr add-lctl-release 추가로 해소 |
| MSTSC 원격 세션에서 CapsLock 물리 키 입력 시 HOST CapsLock 토글 | 발생, kanata 훅 도달보다 약 80~90ms 먼저 OS가 토글 확정(실측) | 근본 해결에는 레지스트리 Scancode Map 또는 Interception 드라이버 필요 — 진단 완료, 조치는 다음 단계로 보류 |
요약
- kanata·PowerToys Keyboard Manager 같은 저수준 사용자 모드 훅은 키 입력의 “동작"은 완전히 바꿀 수 있어도, CapsLock 같은 토글 키의 잠금 비트는 OS가 훅보다 먼저 확정해버리기 때문에 막을 수 없다. 이 순서는
--debug로그와IsKeyLocked폴링 스크립트의 타임스탬프를 나란히 비교하면 직접 실측으로 확인할 수 있다. - 이 문제의 확실한 해법은 레지스트리 Scancode Map(KeyTweak·SharpKeys 방식)이나 커널 필터 드라이버(Interception, kanata의
wintercept빌드) 둘 중 하나뿐이다. 순수 사용자 모드 소프트웨어(감시·보정 스크립트 포함)로는 원천적으로 불가능하다. - RAlt(AltGr, 한/영 키)를 리매핑할 때 Alt/Ctrl이 고정되는 문제는 성격이 다르다 — 레이스 컨디션이 아니라 Windows가 생성하는 가짜 LCtrl 이벤트를 정리하지 않은 것이 원인이라, kanata의
windows-altgr옵션 한 줄로 사용자 모드 안에서 완전히 해결된다. - 같은 “저수준 훅” 카테고리 안에서도 문제의 성격(이벤트 스트림 정리 vs. OS 레벨 상태 레이스)에 따라 해결 가능 여부가 완전히 갈린다는 게 이번 디버깅의 핵심 교훈이었다.
참고 문헌
- KBM: Remapping CapsLock to another key when using RDP incorrectly toggles CapsLock behaviour · Issue #7302, microsoft/PowerToys
- Caps Lock status change isn’t synced to client - Windows Server, Microsoft Learn
- Best practice for custom shiftable keys · jtroo/kanata Discussion #1675
- kanata 공식 설정 가이드 (config.adoc)
- kanata GitHub 저장소
![Featured image of post [Windows] RDP CapsLock 토글 버그 - kanata 훅의 한계](/post/2026-07-05-kanata-windows-capslock-rdp-race/wordcloud_hu_b1e3b108a195f5d5.webp)
![[Windows] Windows 11 RDP 작업표시줄 오류 해결 및 자동 복구](/post/2025-08-27-windows-11-mstsc-taskbar-fix-automation/wordcloud_hu_b30325d1e639ea67.webp)
![[Hyper-V] 가상 머신 해상도 설정: Set-VMVideo 활용 가이드](/post/2025-05-24-hyper-v-virtual-machine-resolution-setting-set-vmvideo-utilization-guide/image_hu_96966cce8efeeb71.webp)
![[Hyper-V] 고급 세션 Windows Hello 로그인 화면 오류 해결 방법](/post/2025-10-13-hyperv-enhanced-session-windows-hello-fix/image01_hu_ffab53ce0d7ddfd6.webp)
![[CMD] BatchGotAdmin으로 배치 파일 UAC 관리자 권한 자동 요청 가이드](/post/2025-07-17-windows-batch-admin-privilege-uac-elevation-guide/index_hu_5a27e05742e84409.webp)
![[Windows] RDP 호스트와 원격 세션 간 클립보드 공유 문제 해결](/post/2025-02-04-rdp-clipboard-share-between-host-and-remote-session/image2_hu_f0f3ce17c7818594.webp)