予測入力(推測変換)機能を有効にしても変換候補窓の表示時に直接入力の設定を利用できるようにした
はじめに
先日のエントリー「AutoHotkeyで変換候補の窓と予想入力(推測入力)候補候補の窓の違いを判別できない - blechmusikの日記」の続きである。
WinGetPos で取得するウィンドウの位置情報はスクリーン全体からのものであることに気づいたので、 CoordMode, Pixel, Screen のコマンドを書いて特定の色を検出する処理を書いたら、思うように動作した。
このエントリーでは、予測入力(推測変換)を利用していても変換候補窓の表示時に直接入力の設定を使用できるよう、IME.ahk を改変したことを説明したい。
問題のありか
IME.ahk には 「IME 文字入力の状態を返す」 IME_GetConverting関数が実装されている。コメントアウトされている説明によれば、この関数を利用するとIMEの変換候補窓を検出して次のように値を返す。
; 戻り値 1 : 文字入力中 or 変換中 ; 2 : 変換候補窓が出ている ; 0 : その他の状態
DvorakJ では、「変換候補窓の表示時」に「直接入力の設定を使用する」機能を有効にすると、この関数から2の値が返されたときは直接入力の設定を使用し、それ以外の場合は日本語入力の設定を使用している。
この関数の挙動に問題が生じるのはMS-IMEの予測入力やATOKの推測変換を利用するときである。これらの機能を利用して文字を入力し選択候補の窓が開かれると、IME_GetConverting関数は「変換候補窓が出ている」と判定してしまうのだ。このため、この関数から2の値が返されることとなり、それ以後の予測入力や推測変換での入力は直接入力用の設定となってしまう。
そこで、MS-IMEの予測入力やATOKの推測変換の選択候補の窓が開かれているときにはIME_GetConverting関数が1の値を返すようにし、日本語入力用の設定を使い続けるようにしよう。
改変の手段
それでは通常の変換候補窓とMS-IMEの予測入力やATOKの推測変換の選択候補の窓を見分けることはできないだろうか?人間の目であれば両者の違いは一目瞭然である。だがその違いをAutoHotkeyのスクリプトで行うとなると話は別であろう。
通常の変換候補窓が表示されているか、それとも予測入力や推測変換の選択候補の窓が表示されているかを判別するには次の二つのコマンドのいずれかを用いればよい。
ImageSearchコマンドは特定の画像に一致する箇所がスクリーン上に存在するか否かを判定し、PixelSearchコマンドは特定の色に一致する箇所がスクリーン上に存在するか否かを判定する。前者を用いるならば、通常の変換候補窓か、予測入力や推測変換の選択候補の窓のキャプチャ画像を取得し、それぞれにおいて特徴的な箇所を抽出して、画像として保存しておく必要がある。後者の場合は画像でなく色で前者と同様のことをしなければならない。
私は外部ファイルとなる画像ファイルを用意するのが面倒だったので、PixelSearchコマンドを用いて候補窓の検出処理を行うことにした。
各IMEの挙動を踏まえた改変作業
ATOK, MS-IME, Google IME で、通常の変換候補窓と、予測入力や推測変換の選択候補の窓に何かしら色の特徴が現れるかを調べた。その結果、ATOK では通常の変換候補窓と、推測変換の選択候補の窓にそれぞれ特徴的な色が使われており、前者と後者の検出を正しく行えることがわかった。他方、MS-IME と Google IME は通常の変換候補窓に特徴的な色が使われているものの、予測入力の選択候補の窓には特徴的な色が使われていなかった。
これらの調査結果をもとに次のような処理を考えた。
- ATOK, MS-IME, Google IME のすべての IME で、IME_GetConverting関数が「変換候補窓が出ている」と判定したら、それは「予測入力や推測変換の選択候補の窓」が表示されている様子を検出したものかもしれないと疑い、その判定は実は「文字入力中 or 変換中」という判定だったことにする
- そして、ATOK, MS-IME, Google IME のおのおのの IME で、通常の変換候補窓に特徴的な色が使われていることが検出されれば、IME_GetConverting関数の「変換候補窓が出ている」との判定は正しいものだったと認めて、「変換候補窓が出ている」という判定結果を採用する
このような処理を実装したのが以下のソースコードである。こういった処理は特に問題なく動作するようだ。
終わりに
こうして、予測入力(推測変換)機能を有効にしても変換候補窓の表示時に直接入力の設定を利用できるようにした。他の IME の挙動については追々対応したいものである。
なお、上記の処理を実装したことによって、予測入力(推測変換)機能を有効にするとその選択候補窓の表示中にキーを発行する処理が遅くなりうることに気づいた。その選択候補窓の表示処理にIME側が時間を費やし、それに伴ってDvorakJによるキーの発行が遅れてしまうのだろうか?原因が判明したら対処しよう。
ソースコード
IME_GetConverting(WinTitle="A",ConvCls="",CandCls="") { ;IME毎の 入力窓/候補窓Class一覧 ("|" 区切りで適当に足してけばOK) ConvCls .= (ConvCls ? "|" : "") ;--- 入力窓 --- . "ATOK\d+CompStr" ; ATOK系 . "|imejpstcnv\d+" ; MS-IME系 . "|WXGIMEConv" ; WXG . "|SKKIME\d+\.*\d+UCompStr" ; SKKIME Unicode . "|MSCTFIME Composition" ; SKKIME for Windows Vista, Google日本語入力 CandCls .= (CandCls ? "|" : "") ;--- 候補窓 --- . "ATOK\d+Cand" ; ATOK系 . "|imejpstCandList\d+|imejpstcand\d+" ; MS-IME 2002(8.1)XP付属 . "|mscandui\d+\.candidate" ; MS Office IME-2007 . "|WXGIMECand" ; WXG . "|SKKIME\d+\.*\d+UCand" ; SKKIME Unicode CandGCls := "GoogleJapaneseInputCandidateWindow" ;Google日本語入力 ControlGet,hwnd,HWND,,,%WinTitle% if (WinActive(WinTitle)) { ptrSize := !A_PtrSize ? 4 : A_PtrSize VarSetCapacity(stGTI, cbSize:=4+4+(PtrSize*6)+16, 0) NumPut(cbSize, stGTI, 0, "UInt") ; DWORD cbSize; hwnd := DllCall("GetGUIThreadInfo", Uint,0, Uint,&stGTI) ? NumGet(stGTI,8+PtrSize,"UInt") : hwnd } WinGet, pid, PID,% "ahk_id " hwnd tmm:=A_TitleMatchMode SetTitleMatchMode, RegEx ret := WinExist("ahk_class " . CandCls . " ahk_pid " pid) ? 2 : WinExist("ahk_class " . CandGCls ) ? 2 : WinExist("ahk_class " . ConvCls . " ahk_pid " pid) ? 1 : 0 ;; 推測変換(atok)や予想入力(ms-ime)中は候補窓が出ていないものとして取り扱う if (2 == ret) { WinGetPos, X, Y, Width, Height, % "ahk_class " . CandCls . " ahk_pid " pid ;; 該当するウィンドウを見つけられなかったときには ;; Google IME だと仮定して再度ウィンドウを検出する if ("" = X) { WinGetPos, X, Y, Width, Height, % "ahk_class " . CandGCls } X1 := X Y1 := Y X2 := X + Width Y2 := Y + Height CoordMode, Pixel, Screen Try { ;; ATOK については 推測変換中か否かを確実に検出できる ;; MS-IME は変換候補窓の表示中のみを検出できる ;; Google IME も変換候補窓の表示中のみを検出できる ;; そこで変換候補窓が表示されていないと仮定して処理を進めてみる ret := 1 not_auto_cand_list := [0xFFE1C4 ; ATOK , 0xF6E8CB ; MS-IME , 0xFFEAD1] ; Google IME for index, ColorID in not_auto_cand_list { PixelSearch, OutputVarX, OutputVarY, %X1%, %Y1%, %X2%, %Y2%, %ColorID%, 0, Fast ;; the color was not found if (0 == ErrorLevel) { Throw 2 } } } Catch e { ret := e } CoordMode, Pixel, relative } SetTitleMatchMode, %tmm% return ret }