강좌와 팁

다른 프로세스의 IME 모드 조사 및 변경 날짜:2022-2-22 7:54:53 조회수:113
작성자 : daypark
포인트 : 957
가입일 : 2020-02-14 10:42:05
방문횟수 : 194
글 137개, 댓글 25개
소개 : 자기소개를 입력하십시오.
작성글 보기
쪽지 보내기
IME 모드는 문자 입력 방식을 의미하는데 쉽게 말해서 한글이냐 영문이냐입니다.
각 스레드마다 IME 컨텍스트를 따로 가지는데 스레드 소속의 윈도우는 모두 같은 모드를 씁니다.
프로세스마다 UI 스레드가 여러 개 있는 특수한 경우를 빼고는 보통 같은 프로그램의 윈도우는 IME 모드가 같은 셈이죠.
사용자가 한글키나 Shift+Space를 눌러 모드를 바꿔가며 쓰는데 때로는 프로그램이 직접 이 모드를 조정해야 할 경우가 있습니다.
반드시 영문으로 입력받아야 한다거나 시작하자 마자 한글로 입력받을 때 등등
그런데 자신이 스스로의 IME 모드를 조사하거나 변경하는 것은 아주 간단합니다.
반면 다른 프로세스의 모드를 조사하거나 변경하는 것은 간단치가 않습니다.
다음 예제는 이런 방법을 연구해 보기 위해 만들었습니다. 

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
HWND hWndMain;
LPCTSTR lpszClass = TEXT("IMEControl");

int APIENTRY WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
    , _In_ LPSTR lpszCmdParam, _In_ int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;

    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);

    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 600, 400,
        NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);

    while (GetMessage(&Message, NULL, 0, 0)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return (int)Message.wParam;
}

#include <imm.h>
#pragma comment(lib, "imm32.lib")

TCHAR stNotepad[256] = L"메모장 실행중 아님";
TCHAR stForeWnd[256] = L"활성 윈도우 상태 모름";
HWND hNotepad = NULL;
HWND hForeWnd = NULL;
enum { ENG, HAN };
enum { N_TOGGLE, N_HAN, N_ENG, F_TOGGLE, F_HAN, F_ENG };

// 특정 윈도우의 IME 모드를 조사한다.
int GetWndIMEMode(HWND hWnd)
{
    if (hWnd == NULL) return -1;
    HWND hIme = ImmGetDefaultIMEWnd(hWnd);
    if (hIme == NULL) return -1;
    // 명령 5의 의미를 알 수 없음. 
    return (int)SendMessage(hIme, WM_IME_CONTROL, 5, NULL);
}

// 특정 윈도우의 IME 모드 변경. 동작하지 않음
void SetWndIMEMode(HWND hWnd, int imeMode)
{
    if (hWnd == NULL) return;
    int nowMode = GetWndIMEMode(hWnd);
    if (nowMode != imeMode) {
        SendMessage(hWnd, WM_KEYDOWN, VK_HANGUL, 0);
        SendMessage(hWnd, WM_KEYUP, VK_HANGUL, 0);
    }
}

// IME 모드 토글을 위한 키보드 이벤트 강제 생성. 대상은 항상 현재 포커스를 가진 활성 윈도우이다.
void ToggleIMEMode()
{
    keybd_event(VK_HANGUL, 0, 0, 0);
    keybd_event(VK_HANGUL, 0, KEYEVENTF_KEYUP, 0);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    DWORD imeMode;
    TCHAR cap[256];

    switch (iMessage) {
    case WM_CREATE:
        hWndMain = hWnd;
        SetTimer(hWnd, 1, 1000, NULL);
        CreateWindow(TEXT("button"), TEXT("토글"), WS_CHILD | WS_VISIBLE |
            BS_PUSHBUTTON, 10, 30, 90, 25, hWnd, (HMENU)N_TOGGLE, g_hInst, NULL);
        CreateWindow(TEXT("button"), TEXT("한글"), WS_CHILD | WS_VISIBLE |
            BS_PUSHBUTTON, 110, 30, 90, 25, hWnd, (HMENU)N_HAN, g_hInst, NULL);
        CreateWindow(TEXT("button"), TEXT("영문"), WS_CHILD | WS_VISIBLE |
            BS_PUSHBUTTON, 210, 30, 90, 25, hWnd, (HMENU)N_ENG, g_hInst, NULL);
        CreateWindow(TEXT("button"), TEXT("2초 후 토글"), WS_CHILD | WS_VISIBLE |
            BS_PUSHBUTTON, 10, 130, 90, 25, hWnd, (HMENU)F_TOGGLE, g_hInst, NULL);
        CreateWindow(TEXT("button"), TEXT("2초 후 한글"), WS_CHILD | WS_VISIBLE |
            BS_PUSHBUTTON, 110, 130, 90, 25, hWnd, (HMENU)F_HAN, g_hInst, NULL);
        CreateWindow(TEXT("button"), TEXT("2초 후 영문"), WS_CHILD | WS_VISIBLE |
            BS_PUSHBUTTON, 210, 130, 90, 25, hWnd, (HMENU)F_ENG, g_hInst, NULL);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        TextOut(hdc, 10, 10, stNotepad, lstrlen(stNotepad));
        TextOut(hdc, 10, 110, stForeWnd, lstrlen(stForeWnd));
        EndPaint(hWnd, &ps);
        return 0;
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case N_TOGGLE:
            SetWndIMEMode(hNotepad, GetWndIMEMode(hNotepad) == HAN ? ENG:HAN);
            break;
        case N_HAN:
            SetWndIMEMode(hNotepad, HAN);
            break;
        case N_ENG:
            SetWndIMEMode(hNotepad, ENG);
            break;
        case F_TOGGLE:
            SetTimer(hWnd, F_TOGGLE, 2000, NULL);
            break;
        case F_HAN:
            SetTimer(hWnd, F_HAN, 2000, NULL);
            break;
        case F_ENG:
            SetTimer(hWnd, F_ENG, 2000, NULL);
            break;
        }
        return 0;
    case WM_TIMER:
        switch (wParam) {
        case 1:
            hNotepad = FindWindow(L"NotePad", NULL);
            if (hNotepad == NULL) {
                lstrcpy(stNotepad, L"메모장 실행중 아님");
            } else {
                imeMode = GetWndIMEMode(hNotepad);
                wsprintf(stNotepad, L"메모장 : %s", imeMode == 1 ? L"한글" : L"영문");
            }
            hForeWnd = GetForegroundWindow();
            if (hForeWnd == NULL) {
                lstrcpy(stForeWnd, L"활성 윈도우를 찾을 수 없음");
            } else {
                imeMode = GetWndIMEMode(hForeWnd);
                GetWindowText(hForeWnd, cap, 256);
                wsprintf(stForeWnd, L"활성 윈도우(%s) : %s", cap, imeMode == 1 ? L"한글" : L"영문");
            }
            InvalidateRect(hWnd, NULL, TRUE);
            break;
        case F_TOGGLE:
            // 2초후 활성 윈도우 IME 모드 토글. 포커스를 옮길 수 있도록 2초의 시간을 준다.
            KillTimer(hWnd, F_TOGGLE);
            ToggleIMEMode();
            break;
        case F_HAN:
            KillTimer(hWnd, F_HAN);
            hForeWnd = GetForegroundWindow();
            if (hForeWnd != NULL) {
                imeMode = GetWndIMEMode(hForeWnd);
                if (imeMode == 0) {
                    ToggleIMEMode();
                }
            }
            break;
        case F_ENG:
            KillTimer(hWnd, F_ENG);
            hForeWnd = GetForegroundWindow();
            if (hForeWnd != NULL) {
                imeMode = GetWndIMEMode(hForeWnd);
                if (imeMode == 1) {
                    ToggleIMEMode();
                }
            }
            break;
        }
        return 0;
    case WM_DESTROY:
        KillTimer(hWnd, 1);
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}

메모장과 활성 윈도우에 대해 IME 모드를 조사 및 변경하는 코드입니다.
조사할 때는 WM_IME_CONTROL 메시지로 5번 명령을 보내면 됩니다.
명령 5는 구글링해서 찾아낸 건데 구체적인 의미는 아직 모르겠어요.
아무튼 잘 조사됩니다.

특정 윈도우의 IME 모드를 강제로 변경하는 방법은 아직 찾지 못했습니다. 
SendMessage로 VK_HANGUL 키를 보내도 바뀌지 않더군요.
이 부분은 좀 더 연구가 필요한데 프로세스간의 간섭이라 쉽지 않을 거 같네요.

활성 윈도우의 모드를 변경하는 것은 그나마 좀 간단합니다. 
keydb_event 함수로 시스템에 VK_HANGUL 키만 보내면 됩니다.
이 키가 포커스를 가진 윈도우로 전달되어 모드를 바꾸게 됩니다.
즉, 이 명령은 IME 모드를 토글합니다.
특정 상태로 바꾸고 싶으면 현재 상태를 먼저 조사한 후 원하는 상태가 아니면 토글하면 되겠죠. 



테스트 예제는 2초 후에 활성 윈도우의 IME 모드를 토글 또는 한, 영 모드로 각각 전환합니다.
2초의 시간을 둔 이유는 버튼을 누를 시점에는 항상 이 프로그램이 활성 윈도우이기 때문에
원하는 윈도우로 포커스를 옮길 시간을 주는 겁니다.
아주 잘 동작하며 가상 키보드처럼 남의 IME 모드를 건드려야 하는 프로그램에서 이 기능이 유용합니다.
특정 윈도우의 IME 모드를 변경하는 방법은 아직 찾지 못했는데 없을 수도 있습니다.
좀 더 연구해 보고 더 좋은 방법을 찾으면 댓글로 기록하도록 할게요.



 


 

목록보기 삭제 수정 신고 스크랩


로그인하셔야 댓글을 달 수 있습니다.