[AutoHotKey]TextExpanderっぽいことをやる方法と問題点まとめ


環境

Windows7
AutoHotKey 1.1.25.01

日本語環境で使うなら、絶対にAutoHotKey本体をUnicode版にすべき。そうでないと面倒が多すぎるので、以下はその前提で記述する。

概要

AutoHotKeyをTextExpanderのようなスニペット展開アプリとして利用しています。

実装としては、ホットストリングで文字列をペーストする関数をコールするという感じで。

sample.ahk
::ahk::
    PasteString("AutoHotKey")
    Return

んで、このPasteString()関数の中身の話。

方法

AutoHotKeyでTextExpanderっぽい動きをするには、以下のような方法がある。

  • 自動置換型ホットストリング
  • スクリプト実行型ホットストリング
    • Send系コマンド
    • クリップボードに格納してペーストする
      • Send/ControlSendCtrl+Vを送る
      • PostMessage/SendMessageWM_PASTEメッセージを送る

入力はホットストリングで、出力をどうするかという話になる。これがまあ、見事にそれぞれ一長一短。

自動置換型

この場合、IMEオンで実行すると、英文字が日本語入力になってしまうのが最大の問題。
例えば、:0:btw::by the wayとすると、byてぇわyという入力になってしまう。
また、変換未確定状態になるので、いちいち確定するのも面倒。

IMEオフで実行するなら、上記の問題はないし日本語も送れちゃうが、後述のスクリプト実行型と違い、これそのものにIMEオフにする処理を仕込むことはできない。

*オプションを指定するホットストリングでなければ、終了文字のホットキーにIMEオフにする処理を仕込むという対策はいちおう考えられる。

IME.ahkを使えば、IME_SET(0)でIMEをオフにできるので、例えば;を終了文字とするならこんな感じ。

sample.ahk
~`;:: ; `;`キーの押し下げ時、そのままキー入力を通しつつIMEの状態を取得、かつオフにする
    ime_status := IME_GET()
    IME_SET(0)
    Return
`; up:: `;`キーを離した時、IMEの状態をもとに戻す
    ; Sleep, 100 ; 戻るタイミングが早すぎるようなら
    IME_SET(ime_status)
    Return

もちろん、#If !IME_Get()として、IMEがオフの場合のみ有効、といった形にもできる。

スクリプト実行型

[共通]ホットストリングの削除が間に合わない場合がある

どうもホットストリングの削除を待たずにスクリプトの実行を開始するようで、削除が間に合わない場合がある。

ホットストリングを主に文字入力以外に用いるならこのほうがよかろうが、他の用途がメインの人はめったにいないと思う。

Send系コマンド

Send系コマンドの場合、IMEオン時の問題は自動置換型と同様だが、こちらはホットストリング単独で回避可能。

例えば以下のようにすれば問題ない。

sample.ahk
::btw::
    ime_status := IME_GET()
    IME_SET(0)
    Send, by the way
    Sleep, 100 ; 送信中に戻らないように待機
    IME_SET(ime_status)

送る文字列が長いと、送信中にIMEオンに戻ってしまうことがあるので、Sleepを入れるなどの対策が必要。

あと、Google日本語入力だとオンに戻った時通知が出るのがちょっとうっとうしかった。これはGoogle日本語入力側でモード表示をオフにすることで対応。

修飾キーを示す記号(^!+#)や特殊キーのキー名とみなされる文字列({Enter}等)を送信する場合、文字列そのものをエスケープするか、Rawで送信する必要がある。(

実行速度は、PCのスペック次第とはいえ遅い。概ね、見えない誰かがズバズバ入力しているのが見える程度だと考えるべき。

クリップボードに格納してペーストする

[共通]OnClipboardChangeとの干渉

OnClipboardChangeラベル(関数)を利用してクリップボードの監視を行っている場合、当然これもトリガーになる。

関数版であれば、OnClipboardChange("<関数名>",0)とすることで、監視を停止することで対策は可能。([AutoHotKey]OnClipboardChange()関数でクリップボード監視 - Qiita
ラベル版でも、グローバル変数をフラグに使えば同じことはできるが、もうこの際みんな関数版使ったほうが幸せだと思う。

ただし、それもOnClipboardChangePasteString()が同じスクリプト(#Include関係を含む)でなければ成立し得ない。

Windows環境変数とか、OnMessage()関数によるプロセス間通信を使えば、OnClipboardChangeを含むスクリプトに監視停止させることは可能だが、めんどいし実行速度的にもよろしくなさそう。

Send/ControlSendCtrl+Vを送る

これがまあ一番無難で、AutoHotKey Wikiのサンプルでも採用されている。

ただし、ターミナル作業中など、ペーストのキー操作がCtrl+Vではないとか、パスワード入力などで、文字入力だがペースト不可といったケースでは当然動作しない。
このような場合、#IfWinActive等で判定してSendRawに切り替えるといった対策は可能。

PostMessage/SendMessageWM_PASTEメッセージを送る

この場合、キー操作ではなく「ペーストしろ」という命令自体を送るので、どのようなキー操作にペーストが割り当てられていようが対応可能。

ただし、そもそも動作しないウィンドウが少なくない。
例えば、コマンドプロンプトは右クリックから貼り付けは可能だが、WM_PASTE命令は受け付けないようだ。
また、名前が割り当てられていないコントロールの場合も動作しないし、使える状況はかなり限られる。

どちらかといえば、ショートカットはCtrl+Vではないがペーストは可能な、一部のアプリで限定して利用すべきだろう。

sample.ahk
PasteString(String)
{
    OnClipboardChange("ClipChanged",0)
    Backup := ClipboardAll
    Clipboard := String
    ControlGetFocus, focusedControl, A
    SendMessage, 0x0302, , , %focusedControl%, A
    Clipboard := Backup
    OnClipboardChange("ClipChanged",1)
}

割り切りか、分岐か

使えるところでだけ使うと割り切れば、それこそWikiのサンプルスクリプト程度の処理でも問題はない。
どこでもCtrl+V、どこでもSendRawでも、まあ便利ではある。

Ctrl+Vでペーストできない奴がバカなんだバーカ!!!っつって、コマンドプロンプトにClink入れるとか、そういう工夫の方向性だってある。

分岐いっぱいつけてスクリプトをゴージャスにしても実行速度がアレだし、自分のユースケースに合わせて必要な条件を整理して、あとは割り切って使うのが重要な気がする。