; ==========================================
; AHK v1.1: Active WINDOW changed -> IME OFF
; - ignores tab changes (same foreground window)
; - robust: uses SetWinEventHook(EVENT_SYSTEM_FOREGROUND)
; - targets focused control for IME (works better for Chrome/VSCode etc.)
; ==========================================
#NoEnv
#SingleInstance Force
#Persistent
SetBatchLines, -1
global g_LastTop := 0
global g_PendingTop := 0
global g_hWinEventHook := 0
global g_pWinEventProc := 0
InitWinEventHook()
return
InitWinEventHook()
{
global g_hWinEventHook, g_pWinEventProc
EVENT_SYSTEM_FOREGROUND := 0x0003
WINEVENT_OUTOFCONTEXT := 0x0000
WINEVENT_SKIPOWNPROCESS := 0x0002
; コールバックを保持(変数をグローバルで保持するのが重要)
g_pWinEventProc := RegisterCallback("WinEventProc", "Fast", 7)
g_hWinEventHook := DllCall("user32\SetWinEventHook"
, "UInt", EVENT_SYSTEM_FOREGROUND
, "UInt", EVENT_SYSTEM_FOREGROUND
, "Ptr", 0
, "Ptr", g_pWinEventProc
, "UInt", 0
, "UInt", 0
, "UInt", WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS
, "Ptr")
if (!g_hWinEventHook)
{
MsgBox, 16, Error, SetWinEventHook failed.
ExitApp
}
; SetWinEventHook を受けるにはメッセージループが必要(#Persistent で満たす) :contentReference[oaicite:2]{index=2}
OnExit("CleanupWinEventHook")
}
CleanupWinEventHook(reason := "", code := 0)
{
global g_hWinEventHook
if (g_hWinEventHook)
{
DllCall("user32\UnhookWinEvent", "Ptr", g_hWinEventHook)
g_hWinEventHook := 0
}
}
WinEventProc(hWinEventHook, event, hwnd, idObject, idChild, idEventThread, dwmsEventTime)
{
global g_LastTop, g_PendingTop
if (!hwnd)
return
; 念のため「トップレベル(ルート)ウィンドウ」に正規化
hwndTop := DllCall("user32\GetAncestor", "Ptr", hwnd, "UInt", 2, "Ptr") ; GA_ROOT=2
if (!hwndTop)
hwndTop := hwnd
; 同一トップレベルなら何もしない(タブ切替などはここで止まる)
if (hwndTop = g_LastTop)
return
g_LastTop := hwndTop
g_PendingTop := hwndTop
; コールバック内で重い処理をしない(遅延実行)
SetTimer, __DoImeOff, -30
}
__DoImeOff:
global g_PendingTop
hwndTop := g_PendingTop
if (!hwndTop)
return
; 実際に入力している「フォーカス先 hwnd」を狙う
hwndFocus := GetForegroundFocusHwnd()
if (hwndFocus)
IME_SetOpenStatus(hwndFocus, 0)
; 念のためトップレベルにも
IME_SetOpenStatus(hwndTop, 0)
return
GetForegroundFocusHwnd()
{
; GUITHREADINFO 構造体で foreground thread の hwndFocus を取る
cbSize := 8 + (A_PtrSize * 6) + 16
VarSetCapacity(gti, cbSize, 0)
NumPut(cbSize, gti, 0, "UInt")
if !DllCall("user32\GetGUIThreadInfo", "UInt", 0, "Ptr", >i)
return 0
; offset: cbSize(4) flags(4) hwndActive(Ptr) hwndFocus(Ptr)
return NumGet(gti, 8 + A_PtrSize, "Ptr")
}
IME_SetOpenStatus(hwnd, state)
{
; state: 0=OFF, 1=ON
; 1) IMM32 の API で閉じる(効くアプリが多い)
hIMC := DllCall("imm32\ImmGetContext", "Ptr", hwnd, "Ptr")
if (hIMC)
{
DllCall("imm32\ImmSetOpenStatus", "Ptr", hIMC, "Int", state)
DllCall("imm32\ImmReleaseContext", "Ptr", hwnd, "Ptr", hIMC)
}
; 2) 既定IMEウィンドウへ WM_IME_CONTROL でも試す(保険)
hIME := DllCall("imm32\ImmGetDefaultIMEWnd", "Ptr", hwnd, "Ptr")
if (hIME)
SendMessageTimeoutW(hIME, 0x0283, 0x0006, state, 80) ; WM_IME_CONTROL / IMC_SETOPENSTATUS
}
SendMessageTimeoutW(hwnd, msg, wParam, lParam, timeout := 100)
{
; ハング回避(SMTO_ABORTIFHUNG)
ok := DllCall("user32\SendMessageTimeoutW"
, "Ptr", hwnd
, "UInt", msg
, "Ptr", wParam
, "Ptr", lParam
, "UInt", 0x0002
, "UInt", timeout
, "PtrP", out
, "Ptr")
return ok ? out : ""
}
これは何してます?
これは 「アクティブ(最前面)ウィンドウが切り替わった瞬間に、そのウィンドウの IME を強制的に OFF にする」 AHK v1.1 スクリプトです。タブ切り替えのように「同じアプリの同じトップレベルウィンドウ内の変化」は無視するように作られています。
SetWinEventHook(EVENT_SYSTEM_FOREGROUND) を使って、フォアグラウンド(最前面)ウィンドウが変わったことを監視します。の両方に対して IME を OFF(閉じる) する処理を行います。
結果として、別のアプリに切り替えるたびに「IME が勝手に ON のままになる」状況を抑制します。
WinEventProc 内で、渡された hwnd を GetAncestor(..., GA_ROOT) で トップレベル(ルート)ウィンドウに正規化しています。
そして、
if (hwndTop = g_LastTop)
return
となっているので、例えば Chrome のタブ切り替えのように 同じトップレベルウィンドウのままなら、イベントが来ても何もしません。
WinEventHook のコールバック WinEventProc の中で重い処理をすると不安定になりやすいので、
SetTimer, __DoImeOff, -30
で 30ms 後に別処理として IME OFF を実行しています(コールバックを軽く保つため)。
IME_SetOpenStatus(hwnd, 0) が「IME OFF」を担当します。
中では2つの方法を試しています:
1) IMM32 API(標準的)
ImmGetContextImmSetOpenStatus(state)ImmReleaseContextこれが効くアプリが多い。
2) 保険として WM_IME_CONTROL
ImmGetDefaultIMEWnd で IME の内部ウィンドウを取得SendMessageTimeoutW(WM_IME_CONTROL, IMC_SETOPENSTATUS, state) を送るさらに SendMessageTimeoutW を使っていて、相手がハングしてても固まりにくい(SMTO_ABORTIFHUNG)ようにしています。
GetForegroundFocusHwnd() は GetGUIThreadInfo を使って 現在入力対象のコントロール (hwndFocus) を取ります。
Chrome/VSCode みたいに「トップレベルに対して IME をいじっても効きにくい」アプリがあるので、実際の入力欄(フォーカス先)を狙うほうが成功率が上がります。
用途としては「ウィンドウを切り替えるたびに IME を必ず英数(OFF)に戻したい」系の常駐ツールですね。必要なら「特定アプリだけ除外する」「特定アプリは ON にする」みたいな分岐も追加できます。