← 목록
OS 2026-07-11 14KB 읽기 14분

CrownyOS 한글 IME 배선 — crownyc_window.m 키 이벤트 → VM 키 큐

개요

CrownyOS 데스크톱 셸(하늘나라셸.한선)의 터미널 앱(crownyc/libs/앱_터미널.한선, 2026-07-11 실셸 승격)에 한글 내장 명령(도움/지우기/크라우니 <질문>)이 로직상 있었으나, 실제 맥 키보드로 한글을 입력할 경로가 막혀 있었다. 원인을 규명하고 Cocoa 키 이벤트 → VM 키 큐 → 앱 레이어까지 한글 코드포인트가 흐르도록 배선했다.

조사 결과 (핵심 — 예상과 다른 두 가지 발견)

1) 진짜 필터 위치는 libs/앱_터미널.한선터미널_키()

crownyc_window.m은 이미 NSTextInputClient 프로토콜(insertText:/setMarkedText: 등, 76~349행)을 구현해 macOS 시스템 IME(두벌식 등)의 조합 결과를 받고 있었다. 즉 한글 자모 조합 자체는 이미 OS가 처리하고, 완성된 커밋 문자열이 insertText:로 들어온다 — crowny-browser의 한글조합기.한선 같은 별도 두벌식 조합기를 새로 만들 필요가 없었다(과설계 방지, 지시사항 2-a와 일치).

문제는 그 커밋 문자열이 ime_committed 전역 버퍼(opcode 901 텍스트큐)에만 쌓이고, 앱들이 실제로 읽는 채널인 key_queue(opcode 900 키큐)/key_pressed (opcode 362 키입력)로는 전혀 전달되지 않았다는 것. 그 결과:

  • 한글뿐 아니라 일반 ASCII 문자 입력도 insertText: 경로로는 key_queue
도달하지 못했다(백스페이스(8)/엔터(13)/화살표 등 doCommandBySelector: 특수키만 crowny_push_key()로 큐에 들어감). 앱_터미널.한선의 32~126 필터는 정확히 존재했지만, 애초에 그 코드가 도달할 방법이 charater 입력 경로에는 없었다.
  • 즉 "한글만 막혀있다"는 원래 가정보다 범위가 넓은 실제 원인: insertText:
일반 문자 키를 VM 키 큐로 전혀 forward하지 않고 있었다.

2) 하늘나라셸.한선의 이벤트 디스패치(이벤트확인/이벤트데이터, opcode

810~815)는 현재 죽은 코드 경로 — 금번 작업 범위 밖, 별도 이슈로 기록 crownyc.cevt_queue/crowny_evt_push()(주석: "Phase 6 — 브라우저 이벤트 루프")는 정의만 있고 crownyc.c/crownyc_window.m/compositor.c 어디에서도 crowny_evt_push()를 호출하는 곳이 없다(전수 grep, crownyc.c/crownyc_big.c/ crownyc_jitmain.c/pkg/crownyc.c/build/crownyc-next.c 전부 동일). 그런데 하늘나라셸.한선의 메인 루프는 이벤트확인()으로 이벤트 타입을 폴링하고 e==2일 때만 셸_앱키(현재앱, 이벤트데이터())를 호출한다. 이벤트확인()은 빈 큐이므로 항상 0을 반환 → 실제 GUI 실행에서는 셸_앱키가 전혀 호출되지 않는다(한글/ASCII 무관하게 키 입력이 앱까지 도달 못함). 이는 하늘나라셸.한선 수정이 금지된 이번 작업 범위 밖의 선행 버그이며, 향후 세션에서 다음 둘 중 하나로 해결해야 한다:
  • (a) crownyc_window.m에서 crowny_evt_push()를 extern으로 노출해 키/클릭
  • 이벤트를 evt_queue에도 채우기, 또는
  • (b) 하늘나라셸.한선 메인 루프를 이벤트확인()/이벤트데이터() 대신
  • 키큐()(900)/키입력()(362) 직접 폴링으로 변경.

    구현

    crownyc_window.minsertText: (NSTextInputClient)

    기존 ime_committed 적재 로직은 그대로 유지(회귀 없음, opcode 901 소비자가 생기면 계속 동작). 그 아래에 커밋 문자열의 각 UTF-16 코드유닛(한글 자모/음절은 전부 BMP라 1코드유닛=1글자, 서로게이트 페어 불필요)을 기존 crowny_push_key() (원래 Cmd단축키·백스페이스 등에만 쓰이던 VM 키 큐 push 함수)로 그대로 push하도록 추가했다. 마킹텍스트(조합중) UI는 처리하지 않음 — 지시사항대로 "커밋 문자열만 큐잉"으로 최소 배선.

    objcNSUInteger slen = [s length];
    for (NSUInteger i = 0; i < slen; i++) {
        unichar uc = [s characterAtIndex:i];
        if (uc > 0) crowny_push_key((int)uc);
    }
    

    이 한 변경으로 한글(자모 0x3131~0x318E, 음절 0xAC00~0xD7A3)뿐 아니라 그동안 누락돼 있던 일반 ASCII 문자 키 입력도 함께 key_queue에 도달하게 됐다 (둘 다 같은 채널을 공유하므로 회귀 아님 — 오히려 선행 버그를 같이 고침).

    libs/앱_터미널.한선터미널_키(코드)

    기존 ASCII(32~126) 필터에 한글 호환 자모(12593~12686)와 완성형 음절 (44032~55203) 두 구간을 추가. 글자변환(코드)는 이미 U+10000 이상 4바이트 문자까지 지원(2026-06-21 수정)하므로 BMP 내의 한글 코드포인트는 문제없이 문자로 복원된다.

    VM 측(crownyc.c) 필터

    crowny_push_key()(정수 큐잉)에는 애초에 ASCII 전용 필터가 없다 — 필터는 전적으로 앱 레이어(터미널_키)에만 있었으므로 crownyc.c 자체는 수정하지 않았다.

    검증

    • `cc -O2 -DCROWNY_WINDOW -I. -framework Cocoa -framework Security -framework
    CoreFoundation -lpthread -o crownyc_win crownyc.c crownyc_window.m compositor.c theme.c ui.c -lm → exit 0 (Makefile의 crownyc_win` 타깃과 동일 링크 조합; 최초 시도에서 compositor.c theme.c ui.c 누락으로 링크 에러 발생 → Makefile 확인 후 정정).
    • 기존 crownyc_win(7/7 09:22 빌드)을 crownyc_win.bak-20260711mv
    백업 후 새 바이너리를 같은 자리로 mv 배치(제자리 cp 덮어쓰기 금지 규칙 준수).
    • ./hanseonc_high libs/앱_터미널.한선 → exit 0 (7918 큐브).
    • ./hanseonc_high 하늘나라셸.한선 → exit 0 (24938 큐브, 전체 셸 임포트 체인
    포함 컴파일 성공, 읽기 전용으로 확인만 함).
    • 헤드리스 하네스(한글IME하네스.한선, /tmp/claude-501/.../scratchpad/
    작성)로 터미널_키()를 직접 호출해 6개 검증 전부 PASS: 1. 코드포인트 46020('도')+50880('움') 순차 주입 → 입력줄 "도움" 형성 2. Enter(13) → 내장 도움 명령 출력("Commands available" 라인) 확인 3. 자모 단독(12593='ㄱ') 주입 → 입력줄 "ㄱ" 형성 4. ASCII 회귀 없음("v"+"e"+"r" → "ver") 5. "ver" 명령 실행 확인("crownyc VM" 라인) 6. 범위 밖 코드(200) 주입 시 입력줄에 유입되지 않음(무시) 확인

    ※ 작업지시서 예시 코드포인트(도=44536, 움=50780)는 실측과 달라(Python ord('도')=46020, ord('움')=50880) 정정해 사용함 — 지시서의 오탈자로 판단.

    관련 파일

    • /Users/ef/CrownyOS/crownyc/crownyc_window.m (76~360행, insertText: 수정)
    • /Users/ef/CrownyOS/crownyc/libs/앱_터미널.한선 (터미널_키() 함수, 필터 확장)
    • /Users/ef/CrownyOS/crownyc/crownyc_win (재빌드된 바이너리, 신규)
    • /Users/ef/CrownyOS/crownyc/crownyc_win.bak-20260711 (구 바이너리 백업)
    • /tmp/claude-501/-Users-ef/929a8ec9-1c47-4929-9008-6af6618baaf0/scratchpad/한글IME하네스.한선
    (헤드리스 검증 하네스, 세션 스크래치패드 — 영구 보존 위치 아님)
    • 하늘나라셸.한선 (읽기 전용으로만 확인, 미수정)

    잔여 이슈

    1. [미해결 — 별도 작업 필요] 하늘나라셸.한선 이벤트 디스패치 자체가
    현재 GUI 실행에서 죽은 코드: 이벤트확인()(810)이 폴링하는 evt_queue를 채우는 crowny_evt_push() 호출이 코드베이스 전체에 없음 → 실제 창을 띄워도 셸_앱키()가 호출되지 않아 이번에 배선한 한글 키 큐까지 도달하지 못할 수 있음. 이번 작업은 "코드가 터미널_키()에 도달했을 때" 정상 동작하는 것까지만 헤드리스로 검증했고, 이 상위 배선 문제는 하늘나라셸.한선 수정 금지 지시에 따라 손대지 않았다. 위 "조사 결과 2)" 참조.
    1. GUI 실측 미실시: 지시에 따라 실제 macOS 창을 띄우는 것은 하지 않았다.
    crownyc_win(신규 바이너리)을 실행해 실제 두벌식 키보드로 "도움"을 입력해 보는 것은 사용자 몫으로 남긴다. 실행 명령: `./crownyc_win --window run /tmp/하늘나라셸_test.toau` 또는 서비스매니페스트 경로 참조.
    1. 위 이슈 1이 해결되기 전까지는, 이번 배선이 실제 창에서 체감되려면
    하늘나라셸.한선(또는 후속 세션)이 이벤트확인()/이벤트데이터() 대신 키큐()/키입력()을 폴링하도록 바뀌어야 한다.

    크라우니코드 보고

    lookup HIT 0건 / MISS 2건(생성) / learn 추가 2건 (터미널키한글필터, cocoa_insertText_한글키큐배선)


    2차 배선 (같은 날 후속 — 옵션 A 승인·완료): crowny_evt_push 공개 + 키/클릭 이벤트 evt_queue 배선

    개요

    1차 작업에서 발견·문서화한 선행 버그(잔여 이슈 1: crowny_evt_push() 호출부가 코드베이스 전체에 0곳 → 하늘나라셸.한선 메인 루프의 이벤트확인()(810)이 라이브 GUI에서 영원히 빈 큐만 폴링)를 옵션 A로 배선 완료. 오케스트레이터 승인.

    이벤트 포맷 역추적 근거 (소비부 = SSOT)

    • crownyc.c opcode 810(이벤트확인): crowny_evt_poll(&evt) 성공 시 evt.type push, 빈 큐면 0.
    • opcode 812/813(이벤트x/이벤트y): 직전 폴링된 이벤트(evt_queue[(evt_head-1+256)%256])의 .x/.y.
    • opcode 815(이벤트데이터): 같은 슬롯의 .data1.
    • 타입 상수(crownyc.c 182~189행): CROWNY_EVT_CLICK=1, CROWNY_EVT_KEY=2, CROWNY_EVT_QUIT=7.
    • 하늘나라셸.한선 소비 계약(132~155행, 읽기만): e==1이벤트x()/이벤트y()
    클릭 처리, e==2셸_앱키(현재앱, 이벤트데이터()), e==7 → 종료.
    • 따라서: 키 = crowny_evt_push(2, 0, 0, 키코드, 0, NULL),
    클릭 = crowny_evt_push(1, fb좌표x, fb좌표y, 0, 0, NULL).

    수정 지점

    1. /Users/ef/CrownyOS/crownyc/crownyc.c (약 200행): crowny_evt_push 정의의
    static 제거(공개 심볼화) + 사유 주석. 로직 무변경. (한선씨 동반 규칙은 1차와 동일하게 "하드웨어 이벤트 배선 예외" — 본 문서가 동반 문서.)
    1. /Users/ef/CrownyOS/crownyc/crownyc_window.m:
    - crowny_push_key() (약 268행): 기존 key_queue/key_pressed push 유지 + crowny_evt_push(2, 0, 0, code, 0, NULL) 병행 push. 이 함수가 세 경로 (Cmd 단축키, doCommandBySelector 특수키, insertText IME 커밋 문자)의 단일 합류점이므로 여기 한 곳 추가로 전 키 경로 커버. - mouseDown: (약 227행): view_to_fb 변환 후 crowny_evt_push(1, mouse_x, mouse_y, 0, 0, NULL) — 프레임버퍼 좌표로 클릭 이벤트 전달. mouseDown에서만(1회) push, mouseUp은 미전달(셸 계약이 e==1=클릭 단발이므로 이중 발화 방지). - 파일 상단(선언 순서상 mouseDown보다 뒤에 있는 공용 extern과 별개로, mouseDown 내부 블록 로컬 extern 선언 사용).
    1. 기존 배선 회귀 없음: key_queue(키큐 900)/key_pressed(키입력 362)/
    ime_committed(텍스트큐 901) 채널 전부 그대로 유지 — evt_queue는 병행 추가.

    빌드·검증 증거

    • 빌드: Makefile crownyc_win 타깃과 동일 조합(`cc -O2 -DCROWNY_WINDOW -I.
    -framework Cocoa -framework Security -framework CoreFoundation -lpthread crownyc.c crownyc_window.m compositor.c theme.c ui.c -lm`) → exit 0, 에러 0, 신규 경고 0(기존 링커 정렬 노트만).
    • 심볼: nm -u crownyc_window.o_crowny_evt_push (미해결 참조 존재 = 사용
    확인), nm crownyc.oT _crowny_evt_push (전역 텍스트 심볼로 공개 확인).
  • 헤드리스 하네스 2종(신규 바이너리로 실행):
  • 이벤트폴링하네스.한선: 이벤트확인() 100회 폴링 전부 0(빈 큐)·크래시
  • 없음 + 이벤트x/이벤트y/이벤트데이터 빈 큐 기본값 0 — PASS 2/2. (GUI 없는 실행에서 evt_queue가 오염되지 않음 = 회귀 없음)
  • 1차 한글IME하네스.한선 재실행: 6/6 PASS (터미널_키 로직 회귀 없음).
  • 바이너리 배치: 1차 빌드본 → crownyc_win.bak-20260711-2로 mv 백업,
  • 2차 빌드본 → crownyc_win으로 mv 배치. 원본(7/7) 백업 crownyc_win.bak-20260711 보존. (제자리 cp 금지 준수)

    사용자 실측 커맨드 및 기대 동작 (GUI는 헤드리스 검증 불가 — 사용자 몫)

    bashcd /Users/ef/CrownyOS/crownyc
    ./hanseonc_high 하늘나라셸.한선 > /tmp/크라우니OS.toau
    ./crownyc_win --window run /tmp/크라우니OS.toau
    

    기대: ①독에서 Terminal 클릭(이제 클릭이 evt_queue로 셸에 도달) → 터미널 열림 ②영문 타이핑(예: ver) + Enter → 명령 실행 ③맥 입력소스를 한글(두벌식)로 전환 후 "도움" 타이핑 + Enter → 내장 도움말 출력. 조합 중 글자는 화면에 안 보이고(마킹텍스트 UI 없음) 음절 확정 시점에 입력줄에 나타나는 것이 정상.

    잔여 이슈 (2차 후 갱신)

    • 1차 잔여 이슈 1(evt_queue 미배선)은 본 2차로 해결.
    • GUI 실측은 여전히 사용자 몫(위 커맨드).
    • 스크롤(EVT_SCROLL=3)/종료(EVT_QUIT=7)/우클릭은 이번에 배선하지 않음 —
    하늘나라셸이 e==7을 소비하지만 창 닫기 시 [NSApp terminate]로 프로세스가 종료되므로 실익 없음. 필요해지면 같은 패턴으로 1줄씩 추가.
    • evt_queue는 Cocoa 메인스레드(생산)/VM 스레드(소비) 무락 공유 — 기존
    key_queue와 동일한 수용된 패턴(단일생산자/단일소비자 링버퍼, head/tail 분리) 이라 그대로 따름.

    크라우니코드 보고 (2차)

    lookup HIT 0건 / MISS 1건(생성) / learn 추가 1건 (evt_queue_공개push_배선)