俺と Win32 コンソール(3):とりあえず IME のオン/オフとかやってみる
まあ Alt+漢字のキーストロークを keybd_event で叩き込むだけでいけるんですが。
void ToggleIMEStateByKeyStroke(void) { /* キー注入結果を反映させるための待ち(手元の環境では単なる Yield 扱いの 0 でも問題なさそうだった。環境によってはもっと大きめの値がいるかも) */ const DWORD dwYieldMSecForKey = 1; keybd_event(VK_MENU, 0 /* 0x38 */, 0, 0); /* down Alt */ keybd_event(VK_KANJI, 0 /* 0x29 */, 0, 0); /* down Kanji */ keybd_event(VK_MENU, 0 /* 0x38 */, KEYEVENTF_KEYUP, 0); /* up Alt */ keybd_event(VK_KANJI, 0 /* 0x29 */, KEYEVENTF_KEYUP, 0); /* up Kanji */ Sleep(dwYieldMSecForKey); /* wait for a proof */ }
ただ、コンソールウィンドウ自身がキー入力のフォーカスを持っていないと、よそのウィンドウの IME が開いたり閉じたりしちゃう罠。とりあえず GetConsoleWindow() == GetForegroundWindow() の確認が必要です。
あともうひとつの問題は、コンソールウィンドウの場合、現在の IME のモードを確実に判定する方法がどうも存在しないっぽいことです。IME 系の API で問い合わせてもダメだしなあ(実は Win9x なら取れるのだが)…
いろいろ悪あがきしてみたけど「確実」にはほど遠い。一応それらしいソースも晒してみます。
#include <windows.h> #include <malloc.h> #include <stdio.h> #include <string.h> static BOOL CALLBACK iter_find_console_win9x(HWND hwnd, LPARAM lprm) { /* コンソールクラス名: NT 系は "ConsoleWindowClass" */ const TCHAR szConClass[] = TEXT("tty"); HWND *hwndResult = (void *)lprm; TCHAR szClass[128]; szClass[sizeof(szClass)/sizeof(szClass[0])-1] = 0; if (GetClassName(hwnd, szClass, sizeof(szClass)/sizeof(szClass[0])-1) > 0 && lstrcmpi(szClass, szConClass) == 0) { DWORD pcur, tcur; /* Win9x: ウィンドウが現行プロセスに属するか調べる。 コンソールウィンドウの pid と tid は拡張ウィンドウメモリにも 入っているので、GetWindowLong で取り出してもよい。 pid = GetWindowLong(hwnd, 0); tid = GetWindowLong(hwnd, 4); NT 系の場合、コンソールウィンドウはコンソールプロセスに属していない ため、この方法では検出できない */ tcur = GetWindowThreadProcessId(hwnd, &pcur); if (tcur != 0 && pcur == GetCurrentProcessId()) { *hwndResult = hwnd; return FALSE; } } return TRUE; } static HWND WINAPI myGetConsoleWindowForWin9x(void) { HWND hwnd = NULL; EnumWindows(iter_find_console_win9x, (LPARAM)&hwnd); return hwnd; } static HWND WINAPI myGetConsoleWindowStub(void) { #if 0 SetLastError(ERROR_NOT_SUPPORTED); #endif return NULL; } /* コンソールウィンドウのハンドルを得る。 Win9x (95/98/Me), Win2000以上兼用。 バージョン 4.0 以下の Windows NT は未対応。無念。 */ HWND WINAPI myGetConsoleWindow(void) { static HWND (WINAPI *lpfn_getconsolewindow)(void) = NULL; if (!lpfn_getconsolewindow) { DWORD dwVer = GetVersion(); HMODULE hm = GetModuleHandle(TEXT("KERNEL32")); FARPROC lpfnGetConWnd; if (hm != NULL && (lpfnGetConWnd = GetProcAddress(hm, TEXT("GetConsoleWindow"))) != NULL) { /* use API (Windows2000 or above) */ lpfn_getconsolewindow = (HWND (WINAPI *)(void))lpfnGetConWnd; } else if ((dwVer & 0x80000000U) != 0 && (dwVer & 0xff) >= 4) { /* Win95, 98, 98SE, Me */ lpfn_getconsolewindow = myGetConsoleWindowForWin9x; } else { /* oh poor NT version 4.0 (or below) */ lpfn_getconsolewindow = myGetConsoleWindowStub; } } return (*lpfn_getconsolewindow)(); } void ToggleIMEStateByKeyStroke(void) { /* キー注入結果を反映させるための待ち(手元の環境では単なる Yield 扱いの 0 でも問題なさそうだった。環境によってはもっと大きめの値がいるかも) */ const DWORD dwYieldMSecForKey = 1; keybd_event(VK_MENU, 0 /* 0x38 */, 0, 0); /* down Alt */ keybd_event(VK_KANJI, 0 /* 0x29 */, 0, 0); /* down Kanji */ keybd_event(VK_MENU, 0 /* 0x38 */, KEYEVENTF_KEYUP, 0); /* up Alt */ keybd_event(VK_KANJI, 0 /* 0x29 */, KEYEVENTF_KEYUP, 0); /* up Kanji */ Sleep(dwYieldMSecForKey); /* wait for a proof */ } static BOOL peek_console_key_state_from_buffer(DWORD *dwState, const INPUT_RECORD *ir, DWORD ir_count) { BOOL rc = FALSE; DWORD i; for(i=0; i<ir_count; ++i) { if (ir[i].EventType == KEY_EVENT) { if (dwState) *dwState = ir[i].Event.KeyEvent.dwControlKeyState; rc = TRUE; /* do not break */ } } return rc; } DWORD PeekLatestConsoleKeyStateForNT(HANDLE hConin) { const DWORD dwIRRESERVED = 2; const DWORD dwYieldMSecForKey = 1; DWORD dwKeyState; INPUT_RECORD *ir; DWORD dwIRMAX, dwIRRead; BOOL b; dwIRMAX = 0; if (!GetNumberOfConsoleInputEvents(hConin, &dwIRMAX)) return 0; ir = alloca((dwIRMAX + dwIRRESERVED) * sizeof(INPUT_RECORD)); b = PeekConsoleInput(hConin, ir, dwIRMAX+dwIRRESERVED, &dwIRRead); if (b) { b = peek_console_key_state_from_buffer(&dwKeyState, ir, dwIRRead); if (!b) { /* KeyStroke not exist - send dummy keystate and try again */ keybd_event(0 /*VK_NULL*/, 0, 0, 0); keybd_event(0 /*VK_NULL*/, 0, KEYEVENTF_KEYUP, 0); Sleep(dwYieldMSecForKey); /* and yield (for a proof) */ if (PeekConsoleInput(hConin, ir, dwIRMAX+dwIRRESERVED, &dwIRRead)) { b = peek_console_key_state_from_buffer(&dwKeyState, ir, dwIRRead); } } } if (!b) dwKeyState = 0; return dwKeyState; } BOOL IsWin32Console(HANDLE h) { DWORD dw; /* if (GetFileType(h) != FILE_TYPE_CHAR) return FALSE; */ return GetConsoleMode(h, &dw); } #ifdef TEST #ifndef NLS_DBCSCHAR # define NLS_ALPHANUMERIC 0x00000000 # define NLS_DBCSCHAR 0x00010000 # define NLS_KATAKANA 0x00020000 # define NLS_HIRAGANA 0x00040000 # define NLS_ROMAN 0x00400000 # define NLS_IME_CONVERSION 0x00800000 # define NLS_IME_DISABLE 0x20000000 #endif void print_keystate(DWORD dw) { const char *spc = ""; const char *spc2 = " "; BOOL bKanaLck; if (dw & LEFT_ALT_PRESSED) { printf("%sAlt-L", spc); spc = spc2; } if (dw & RIGHT_ALT_PRESSED) { printf("%sAlt-R", spc); spc = spc2; } if (dw & LEFT_CTRL_PRESSED) { printf("%sCtrl-L", spc); spc = spc2; } if (dw & RIGHT_CTRL_PRESSED) { printf("%sCtrl-R", spc); spc = spc2; } if (dw & SHIFT_PRESSED) { printf("%sShift", spc); spc = spc2; } if (dw & NUMLOCK_ON) { printf("%sNumLk", spc); spc = spc2; } if (dw & SCROLLLOCK_ON) { printf("%sScrLk", spc); spc = spc2; } if (dw & CAPSLOCK_ON) { printf("%sCapsLk", spc); spc = spc2; } if (dw & SCROLLLOCK_ON) { printf("%sScrLk", spc); spc = spc2; } bKanaLck = GetKeyState(VK_KANA); if (bKanaLck) { printf("%sカナ", spc); spc = spc2; } /* IME オン時の入力モード(不完全版) ぶっちゃけ「(ローマ字/カナ的な意味での)カナ入力」と 「(ひらがな/カタカナモード的な意味での)カナモード」とが微妙に ごっちゃになっていて、正確な判定が不可能となっている。 たとえば、IME2000 のツールバーで日本語入力時のみカナロックを 有効にすると NLS_KATAKANA がセットされるが、実際はひらがな入力のまま。 そして、カナロックを解除して、"Shift+ひらがな"でカタカナモードに してもやはり NLS_KATAKANA がセットされる(ローマ字入力モード)。 IME オン時の「カタカナ/ひらがな」モードを正確に取得する方法って あるんですかねえ… (Win9x の場合は INPUT_RECORD のEvent.KeyEvent.dwControlKeyState に 何も情報が帰らないので全く無意味。tty クラスのウィンドウに IME 方面の API で問い合わせをすればいいような気がする。) */ if (dw & NLS_IME_CONVERSION) { printf("%s%s", spc, (dw & NLS_DBCSCHAR) ? "全" : "半"); #ifdef dont_need_kanalock_kludge if (dw & NLS_HIRAGANA) printf("あ"); #else if ((dw & NLS_HIRAGANA) || (bKanaLck && (dw & NLS_KATAKANA))) printf("あ"); #endif else if (dw & NLS_KATAKANA) printf("%s", (dw & NLS_DBCSCHAR) ? "カ" : "カ "); else printf("%s", (dw & NLS_DBCSCHAR) ? "A" : "A "); if (dw & NLS_ROMAN) printf("ローマ"); spc = spc2; } } int main(void) { HWND hwndFG, hwndCon; HANDLE hConin; DWORD dw; hwndFG = GetForegroundWindow(); hwndCon = myGetConsoleWindow(); printf("GetForegroundWindow 0x%x\n", hwndFG); printf("GetConsoleWindow 0x%x\n", hwndCon); if (!hwndCon || hwndFG != hwndCon) { printf("not foreground window.\n"); return 1; } hConin = GetStdHandle(STD_INPUT_HANDLE); if (!IsWin32Console(hConin)) { printf("Console is redirected.\n"); return 1; } FlushConsoleInputBuffer(hConin); dw = PeekLatestConsoleKeyStateForNT(hConin); printf("Previous Control State 0x%x", dw); printf(" ["); print_keystate(dw); printf("]\n"); ToggleIMEStateByKeyStroke(); dw = PeekLatestConsoleKeyStateForNT(hConin); printf("Currect Control State 0x%x", dw); printf(" ["); print_keystate(dw); printf("]\n"); return 0; } #endif
…余談ですが、「コンソールウィンドウでも(Alt+漢字じゃなくて)漢字キー一発で IME を制御したい」みたいな場合はスキャンコードを書き換えちゃうのがいちばん確実だと思います(最近の Windows でこの技が使えるのか分かりませんが…)。