Wireframe

AutoHotkey 한영 상태 감지하기

오토핫키에서 키보드 매크로(hot string)는 키보드의 한/영 상태를 고려하지 않고 작동하기 때문에, 한/영 상태에 따라서 다른 핫스트링을 정의해야 하는 경우가 있다. 이를 위한 함수 코드가 여러 커뮤니티나 블로그를 통해서 공유되고 있는 걸 줏어서 소개한다. (물론 구글에서 검색해보면 이 글보다는 다른 글들이 먼저 보이니까, 이 글을 보시는 분들은 아마 보셨던 코드일 것이다.)

원리는 대충 윈도 API를 통해 어떤 응용 프로그램 내의 컨트롤에 ‘메시지’를 보내어 원격으로 어떤 기능을 실행할 수 있게 하는 것이 기본적인 아이디어이다. 현재 활성화된 창에서 기본 IME의 상태창을 열도록, 응용 프로그램의 기본 IME 창에게 메시지를 보낸다. 만약 현재 한글 IME가 선택된 경우에는, 이 요청을 처리하지 못하고 에러가 반환되기 때문에 이 메시지의 결과가 실패라면 한글로 선택되어 있다고 상정할 수 있는 것이다.

따라서 이 함수는 크게 두 개의 동작으로 나눠진다.

  1. 현재 창에서 사용하는 기본 IME창의 핸들을 획득한다. (여기에 메시지를 보내야 하므로)
  2. 1에서 획득한 창에 메시지를 보낸다.

코드는 다음과 같다. 원본은 2개의 함수로 나뉘어 있었는데, 사실 간단하게 하나로 합칠 수 있다.

[UPDATE] 윈도11의 22H2 업데이트에서 Microsoft IME가 새로운 버전으로 업그레이드 되었고, 이제 이 함수에서 소개하는 방법으로는 더 이상 정상적으로 한/영 상태를 구분할 수 없다 IMC_OPENSTATUSWINDOW 명령은 최초 영문 상태일 때에만 0을 리턴하며, 한 번이라도 한글로 전환한 이후부터는 한/영 상태에 상관없이 1을 리턴한다. 현재 유일한 대안은 날개셋 IME를 사용하는 것이다.

; for AHK v1.1

IMECheckHangul()
{
  WinGet hWnd, ID, A
  hIME := DllCall("imm32\ImmGetDefaultIMEWnd", "UInt", hWnd, "UInt")
  Temp := A_DetectHiddenWindows
  DetectHiddenWindows ON
  SendMessage 0x283, 0x005, 0, ,ahk_id %IMEWnd%
  Res := ErrorLevel
  if (Temp <> A_DetectHiddenWindows)
    DetectHiddenWindows %Temp%
  return Res <> 0
}

+F1::MsgBox % IMECheckHangul()

이렇게 만든 스크립트를 실행하고 <shift>F1 키를 누르면 한영 상태에 따라서 값이 메시지 박스로 표시된다. 영어일 때 0, 한글일 때 1이면 성공이다.

AHK V2 용 코드

AHK V2가 정식으로 릴리즈 되었다. 많은 분들이 관심을 갖고 보시는 것 같아서, V2용 코드를 추가한다. 이 코드와 관련하여 AHK V2의 변경사항은 다음과 같다.

  1. 모든 “명령”은 함수로 대체되었다. 단, 인자가 없을 때에 괄호는 생략할 수 있다.
  2. 할당 구문은 := 연산자만 사용한다.
  3. 대부분의 구문은 이제 “표현식”으로 인식한다. 문자열은 식별자와 구별하기 위해 따옴표로 감싼다.
  4. WinGet 명령에 1:1로 대응하는 함수는 없다. WinGetID() 를 사용하여 창의 핸들을 얻는다.
  5. SendMessage() 함수는 메시지에서 리턴하는 결과값을 리턴한다.
  6. 예외처리는 try { .. } catch ... { .. } 구문을 통해 처리한다. A_ErrorLevel 내장 변수는 제거되었다.

참고로 함수는 입력방식 관리자(Input Method Manager) 관련한 윈도 API를 사용하는 부분이 있기 때문에, 호출 시마다 내부적으로 imm32.dll 파일을 로드한다. 스크립트의 수명 주기에서 같은 dll을 반복적으로 사용한다면 매번 함수에서 로드하기 보다는 미리 로드했다가, 스크립트가 종료되는 시점에 언로드하는 것이 성능면에서 크게 도움이 된다고 한다.

  1. kernel32.dll 의 LoadLibrary() 를 사용하여 dll 파일을 로드해둔다.
  2. OnExit()를 사용하여 스크립트가 종료되기 직전에 FreeLibrary() 를 사용하여 로드해둔 dll을 언로드한다.

OnExit 명령을 사용하면, 종료 시점에 호출할 함수를 지정할 수 있다. 이 함수에서 “FreeLibrary” 함수를 호출하도록 하여 로드해놓은 dll 파일을 해제해준다.

imm32 := DllCall("LoadLibrary", "Str", "imm32.dll", "Ptr")

IMECheckHangul()  ; 0: 영어, 1: 한글
{
  hWnd := WinGetID("A")   
  ; WinGet 명령은 제거되었고, 창의 핸들을 얻기위해서는 WinGet("A")를 사용한다.
  hIME := DllCall("imm32\ImmGetDefaultIMEWnd", "UInt", hWnd, "UInt")
  temp := A_DetectHiddenWindows
  DetectHiddenWinows(True)
  res := SendMessage(0x0283, 0x0005, 0x0000, , "ahk_id " hIME)
  DetectHiddenWindows(temp)
  return res
}

ExitFunc(ExitReason, ExitCode)
{
  DllCall("FreeLibary", "Ptr", imm32)
}

OnExit("ExitFunc")
Exit mobile version