このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

プログラミング言語としての AutoHotKey

AutoHotKeyは、ユーザーがホットキー(OSやバックグラウンドプロセスが処理する特殊なキーコンビネーション)を自由に設定できるWindowsソフトウェアです。スクリプト言語としても使用できます。が、スクリプト言語としての使用はおすすめはできません。ホットキー設定をする上でスクリプティングが必要になったときだけスクリプト言語機能を使うのが吉です。その場合でも、かなり癖の強い言語なので注意が必要です。

内容:

プログラミング初心者はやめとけ

AutoHotKeyのWikipedia項目によると、

構文は簡易であり初心者でもコーディングしやすく、すぐ動かせるものを作ることができる。

とありますが、プログラミング初心者がAutoHotKeyを入門や学習用に使うことはまったくおすすめできません

ごく簡単なことをするときは簡易に見えても、構文は一貫性がなく複雑怪奇です*1。プロセスやスレッド、入力デバイスやウィンドウシステムの知識がないと、スクリプトの挙動を理解することは難しいでしょう。そういった予備知識も含めて学習するにしても、選べるプログラミング言語は他にいくらでもあります。プログラミング言語としてはスジ悪なAutoHotKeyを選ぶ理由はありません。

ホットキーを定義することに関しては、他に適切な選択肢がないようなので、その目的でAutoHotKeyはもちろん有効・有意義です。また、ある程度プログラミング言語に慣れている人なら、奇妙な言語仕様もご愛嬌だと割り切って楽しく使えるかも知れません。

概要:檜山が想定している歴史的経緯

AutoHotKeyの歴史について調べたわけではありません(Wikipediaに書いてあるのを見ただけ)。これから書くことは、檜山の推測です。事実と異なるかも知れません。が、歴史的経緯を推測することにより、AutoHotKeyの構造や構文を理解しやすくなります。

おそらくAutoHotKeyは、次のような順番で機能が拡充してきたのだと思います。

  1. 基本的なホットキー設定機能
  2. ディレクティブを追加
  3. レガシースタイルのスクリプト機能を追加
  4. ベタースタイルのスクリプト機能を追加

ディレクティブとは、C言語プリプロセッサのように、プログラミング言語とは別な機能・構文による指令です*2レガシースタイルとは、古いBASIC言語やWindows(というよりMS-DOS)バッチファイルと似た機能・構文のことです。ベタースタイルとは、レガシースタイルよりはマシ〈better〉な機能・構文を指しています。

これら複数の機能・構文が混ぜこぜになっていることが、プログラミング言語としてのAutoHotKeyろくでもないところです。

この記事で、複数の機能・構文を分離・整理してみます。使っている用語は一般的かつ常識的ですが、必ずしもAutoHotKeyドキュメンテーションの用語とは一致していませんのでご注意ください。

なお、AutoHotKeyに関する詳しい情報源としては、日本語のWikiサイトと、本家ドキュメンテーションがあります。

ホットキー設定

まずはサンプル。キーバインドEmacs風にするホットキー設定です。

; EmacsLikeKeymap.ahk 
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn  ; Enable warnings to assist with detecting common errors.
#SingleInstance, Force
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.

^h::Send {BS} ; 後退削除
^d::Send {Delete} ; 削除
^m::Send {Enter} ; エンター

^f::Send {Right} ; 右(前方)へ
^b::Send {Left}  ; 左(後方)へ
^p::Send {Up} ; 上(前)へ
^n::Send {Down} ; 下(次)へ

^a::Send {Home} ; 行頭/ページ先頭へ
^e::Send {End} ; 行末/ページ末尾へ

セミコロンから後はコメントです。C言語スタイルのコメント /* ... */ も使えますが、驚くべきことに /* コメント */ のように書くと、その行から下はすべてコメント扱いになります。コメントエンドが、別な行にないと認識されないのです。こういう奇妙な構文があるので、初心者にはおすすめできないのです。

2行目から5行目(行が折り返されているかも知れないので注意)は推奨されているオマジナイです。シャープ記号からはじまる行はディレクティブ(後述)で、ホットキー設定ともスクリプトとも違う構文ですが、AutoHotKeyへの指令であることに変わりありません。

SendModeはコマンド(後述)です。SendModeによりSendコマンドの挙動を設定します。とりあえず今は気にしなくていいです。ディレクティブとコマンドの機能的区別は曖昧です(構文的区別はシャープ記号があるので明らかです)。

さて、7行目から下がホットキー設定です。1行でひとつのホットキーが定義されます。コロン2つ('::')の左に入力キー、右にキーが入力されたときに実行するコマンドを書きます。キーの記述は次のようです。

Sendはコマンド、つまり何らかの動作を引き起こす命令です。Sendは(それが実行されると)、フォーカスを持つウィンドウに対して指定されたキー入力(文字列も可能)を送ります。

次のようにしてAutHotKeyを実行します。

> AutoHotkey.exe /r .\EmacsLikeKeymap.ahk

/r は、既に実行されているAutoHotKeyがあったときに(その実行中のプログラムと)置き換わるオプションです。ただし、異なる“.ahkファイル”で実行されているAutoHotKeyはそのままです。

実行されたAutoHotKeyは、キー入を監視して、ホットキー設定で指定されたキー入力があると、それに対応するコマンドを実行します。AutoHotKey(バックグラウンド・プロセス)を止めるには、タスクバーから緑のHアイコン(インジケータ)を探して右クリック → Exit とします。

今この節で述べた内容だけでも、ホットキー設定にAutoHotKeyを使えるでしょう。

プログラムと記述ファイル

AutoHotKeyのプログラム実行ファイルは次のようです(2020年7月時点での最新版)。

サイズ ファイル名
784896 AutoHotkeyA32.exe
904192 AutoHotkeyU32.exe
1198592 AutoHotkeyU64.exe
1198592 AutoHotkey.exe

それぞれ、アスキー文字32ビット版、ユニコード文字32ビット版、ユニコード文字64ビット版でしょう。サイズから、AutoHotkey.exe = AutoHotkeyU64.exe です。

拡張子が.ahkであるファイルにAutoHotKeyプログラムへの指示を書きます。そして、AutoHotKey foo.ahk のようにして実行します。AutoHotKeyがどのように動くかは foo.ahk の記述次第です。

ここから先では、AutoHotKeyのプログラム実行ファイル、または実行中のプロセスをAHKプログラムと呼び、拡張子が.ahkであるファイルをAHKファイルと呼ぶことにします。

AHKファイルの内容は、次のいずれか、または両方です。

  1. ホットキー設定
  2. スクリプトコード

ホットキー設定だけ、スクリプトコードだけのAHKファイルを試してみて、慣れたらその両方を混ぜるようにするのがいいと思います。

ディレクティブ

前節のAHKファイルによるホットキー設定はすべてのアプリケーショに対して有効になります。Chromeブラウザだけにホットキーが有効になるようにしましょう。

; EmacsLikeKeymapForChrome.ahk 
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn  ; Enable warnings to assist with detecting common errors.
#SingleInstance, Force
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.

#IfWinActive ahk_class Chrome_WidgetWin_1 ahk_exe chrome.exe
^h::Send {BS} ; 後退削除
^d::Send {Delete} ; 削除
^m::Send {Enter} ; エンター

^f::Send {Right} ; 右(前方)へ
^b::Send {Left}  ; 左(後方)へ
^p::Send {Up} ; 上(前)へ
^n::Send {Down} ; 下(次)へ

^a::Send {Home} ; 行頭/ページ先頭へ
^e::Send {End} ; 行末/ページ末尾へ
#IfWinActive

#IfWinActiveディレクティブです。ホットキー設定が有効になるウィンドウを指定します。次の条件の両方を満たすウィンドウに対してだけ、ホットキー変換を実行します。

  • ahk_class Chrome_WidgetWin_1 : ウィンドウクラス名が Chrome_WidgetWin_1 である。
  • ahk_exe chrome.exe : そのウィンドウを実行しているプログラムファイル名が chrome.exe である。

ウィンドウクラス名だけだとChromeを特定できないときがあります。VSCodeエディタのウィンドウクラス名が何故か Chrome_WidgetWin_1 でした。

1行目から3行目に出てきている #NoEnv, #Warn, #SingleInstance もディレクティブです。ディレクティブはシャープ記号から始まります。ディレクティブ #IfWinActive の範囲の終わりも #IfWinActive なので注意してください。BEGIN/END の区別がないのです。

初期のAutoHotKeyは、おそらく(檜山の推測)ホットキー設定と上のような条件ディレクティブだけだったのでしょう。ホットキー設定とディレクティブは役割が違うので、違った構文です。C言語プリプロセッサ・ディレクティブがそうであったように、条件部分に満足な論理式が書けない状況から進化したと思われます。現在の #If ディレクティブは、論理演算子をサポートしています。#If を使った例を挙げましょう。

; EmacsLikeKeymapForChromeAndFirefox.ahk 
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn  ; Enable warnings to assist with detecting common errors.
#SingleInstance, Force
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.

#If (WinActive("ahk_class Chrome_WidgetWin_1") && WinActive("ahk_exe chrome.exe")) || (WinActive("ahk_class MozillaWindowClass") && WinActive("ahk_exe firefox.exe"))
^h::Send {BS} ; 後退削除
^d::Send {Delete} ; 削除
^m::Send {Enter} ; エンター

^f::Send {Right} ; 右(前方)へ
^b::Send {Left}  ; 左(後方)へ
^p::Send {Up} ; 上(前)へ
^n::Send {Down} ; 下(次)へ

^a::Send {Home} ; 行頭/ページ先頭へ
^e::Send {End} ; 行末/ページ末尾へ
#If

これは、ChromeブラウザとFirefoxブラウザに対してホットキーを有効にします。#If ディレクティブ内のWinActiveはブール値を返す関数〈述語〉です。論理アンド(&&)や論理オア(||)も使えます。WinActiveに渡す引数は旧ディレクティブ構文を文字列にしたものです(2引数にすればいいのに、と思いますけど)。

コマンドライン

ここからは、ホットキー設定のことは忘れて、スクリプトを書いてみます。

; AHK-01.ahk
#SingleInstance, Force

InputBox, UserInput, Input Box, Please input your name.
MsgBox, Hello`, %UserInput%.
Return

このAHKファイルを実行すると、名前を聞くダイアログボックスを出して挨拶をします。AHKスクリプトとは、基本的にはコマンドラインを並べたものです。コマンドライン次の構文です。

  • コマンド名 (コマンド名だけ)
  • コマンド名 カンマ 引数1 (引数が1個)
  • コマンド名 カンマ 引数1 カンマ 引数2 (引数が2個)
  • コマンド名 カンマ 引数1 カンマ 引数2 ...(引数がn個)

コマンド名のすぐ後のカンマはたいてい省略可能ですが、場合によっては省略できません。例外を見極めるのが難しいので、念のためにコンマを付けている人が多いようです。構文に一貫性がなく例外規則が多いのも、互換性を維持しながら進化してきた言語にありがちなことです。

コマンドの引数では、データ型の概念はなくはない*4ですが、すべてクォーテーションなしの文字列として書きます。コマンド側で文字列を適当に解釈します。例えば、InputBoxの引数仕様は次のようです。

  • InputBox, OutputVar , Title, Prompt, HIDE, Width, Height, X, Y, Locale, Timeout, Default

最初のOutputVar以外は省略可能です。それぞれの意味は:

  1. OutputVar -- 入力された文字列を格納する変数の名前
  2. Title -- ダイアログボックスのタイトル文字列
  3. Prompt -- プロンプトに使う文字列
  4. HIDE -- パスワード入力などのために、入力文字列を隠すかどうか。HIDEという名前を指定すると隠す。
  5. Width -- ダイアログボックスの幅の整数値
  6. Height -- ダイアログボックスの高さの整数値
  7. X -- ダイアログボックスの位置 X座標の整数値
  8. Y -- ダイアログボックスの位置 Y座標の整数値
  9. Locale -- UIに使うロケール名前
  10. Timeout -- ユーザー入力を待つ時間 秒数の整数値
  11. Default -- デフォルト文字列

文字列引数であってもクォーテーションする必要はありません。クォーテーション〈引用符記号〉を入れるとそのまま唯の文字扱いになります。文字列内にカンマを入れるときは `, とします。バッククォートがエスケープ文字です。指定する必要がない引数は省略できます。次は、InputBoxに引数をたくさん指定した例です。

; AHK-02.ahk
#SingleInstance, Force

InputBox, UserInput, Input Box, Please input your name., ,
  , 300, 0, 0, , 10, World
MsgBox, Hello`, %UserInput%.
Return

行の末尾がカンマなら、次の行に継続してコマンドラインを書けます。引数内に変数の値を展開したいなら、変数名を'%'で囲みます。

AHKスクリプトは、Windowsのバッチファイル、Unix/Linuxシェルスクリプトと似たような機能・構文を持ちます。プログラミング言語としては太古の姿を留めているものです。(ただし、後述の新しい構文もサポートしています。)

制御構造:GotoとGosub

レガシースタイルのスクリプトにおける制御構造は、GotoとGosubです。わざとらしい例ですが:

; AHK-03.ahk
#SingleInstance, Force

InputBox, UserInput, Input Box, Please input your name., ,
  , 300, 0, 0, , 10, World
if (ErrorLevel = 2) {
 ; Timeout
 Goto Timeout
} else if (ErrorLevel = 1) {
 ; Canceled
 Goto Canceled
} else {
 Gosub Greeting
}
MsgBox, My name is AHK.
Return

Greeting:
  MsgBox, Hello`, %UserInput%.
  Return

Timeout:
  Return

Canceled:
  MsgBox, Sorry.
  Return

今どきの若者は「Goto文」や「サブルーチン」という言葉を知らないかも。調べてみてください。Goto*5は指定されたラベルに飛び、ラベルから先のスクリプトコードを実行します。Gosubでは、飛び先のラベルはサブルーチン名とみなされます。サブルーチン呼び出しであるGosubの場合は、行った先のReturnで呼び出した場所の次の位置に戻ります。Gotoでは戻ることはありません(行ったきり)。サブルーチンではないReturnはスレッドを終了します。そのスレッドがプロセスのメインスレッドならプロセスが終了します*6

高齢者の皆様におかれましては懐かしい気分に浸れるでしょうが、こういう制御構造を知らないからといって、若い方々が新たに学ぶ必要はあません。

ベタースタイルのスクリプト言語

前節で紹介したような古色蒼然たる構文は、AHKコミュニティのなかでレガシー構文と呼ばれています。AHKレガシー構文は、貧弱・低機能なコマンド言語であるWindowsバッチファイルに影響を受けているようです。さすがに古すぎるので新しい構文が追加されています。新しい機能・構文はベタースタイル/ベター構文です。

ベタースタイルのスクリプト言語は、レガシースタイルのスクリプト言語とは別物だと思ったほうがいいです。つまり、AHKスクリプト言語は二種類あるということです。

  1. レガシースタイルのスクリプト言語
  2. ベタースタイルのスクリプト言語

二種類のスクリプト言語が、良く言えば「シームレス」、悪く言えば「ゴチャ混ぜ」になっています。実は、前節のレガシースタイルのコード中でも、ブロック構造を持つif文はベタースタイルの構文です。ベタースタイルの構文では、変数、関数、式、代入文、if文、while文などの、通常のプログラミング言語と同様な機能と構造を持っています。ベタースタイル構文をごく短く紹介すれば:

  1. 変数参照にパーセント記号は不要で、名前だけでよい。
  2. 文字列リテラルは二重引用符で囲む。
  3. 代入は、:= を使う。= はレガシースタイルの代入、またはイコール。
  4. 関数定義と関数呼び出しができる。
  5. 配列データ構造が使える。

関数は通常のプログラミング言語の関数(関数型言語の意味の純関数ではない)です。組み込み関数もたくさんあります。ただし、コマンドは相変わらず必要なので、コマンドライン構文を捨てることはできません。おそらく歴史的事情から、似た機能のコマンドと関数が混じったりしています。例えば、文字列から部分文字列を検索する StringGetPos はコマンド、文字列の部分文字列を置換する StringReplace は関数です。

コマンドでしか実現されない機能は致し方ないとして、できるだけ関数ベースでプログラミングするほうがいいでしょう。コマンドを関数でラップした例を二例示します。

GetNameFromUser_1() {
    local UserInput
    InputBox, UserInput, Input Box, Please input your name., ,
    , 300, 0, 0, , 10, World
    if (ErrorLevel != 0) {
        Return ""
    } else {
        Return UserInput
    }
}

GetNameFromUser_2(outputArray) {
    local UserInput
    InputBox, UserInput, Input Box, Please input your name., ,
    , 300, 0, 0, , 10, World
    outputArray[1] := ErrorLevel
    outputArray[2] := UserInput
    Return
}

関数GetNameFromUser_1は、入力した文字列を戻り値で返します。タイムアウトやキャンセルの状況は、エラーレベル*7を調べる必要があります。関数GetNameFromUser_2は、エラーレベルと入力文字列を配列にして返します(戻り値ではない)。結果を入れる配列は呼び側で準備して関数の引数として渡します。すべての情報を戻り値で返す例も作れるでしょう(練習問題)。

常駐モードと非常駐モード

AHKファイルにホットキー設定が書いてあった場合、AHKプログラムは常駐モード〈persistent mode〉で実行されます。バックグラウンドプロセスになり、キー入力を監視し続けます。一方、AHKファイルにスクリプトコードだけが書いてあれば、AHKプログラムは非常駐モードでの実行となり、スクリプトの実行終了とともにプロセスも消えます。

ホットキー設定がないスクリプトを常駐モードで実行するには、#Persistent ディレクティブを指定します*8ファイルシステムを監視したり、指定の時刻で何かするようなスクリプトを書けます。バックグラウンドで実行されるAHKプログラムは、コンソール(標準入出力)とは切り離されてしまうので、ユーザーへの通知などはウィンドウシステムを使います*9

AHKバックグラウンドプロセスの稼働状況を簡単に確認する方法はないのですが、タスクトレイのアイコンを右クリック → Open で出てくる実行ログ・コンソールが役に立つことがあります。

ホットキー設定とスクリプトとの関係

ホットキー設定において、2つのコロンの後ですぐ改行すると、ホットキー設定がサブルーチンとして扱われます。次は、Ctrl+K で、カーソル位置から右の文字列を削除するものです。

^k::
  Send +{End}
  Send ^x
  Return

これは、キー入力により呼び出されるサブルーチンですね。この形のサブルーチンのなかで関数呼び出しができるので、必要な関数をスクリプトとして記述します。次は、前の例を Shift+Alt+1 で呼びすようにしたものです。

; AHK-04.ahk
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn  ; Enable warnings to assist with detecting common errors.
#SingleInstance, Force
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.

GetNameFromUser_1() {
    local UserInput
    InputBox, UserInput, Input Box, Please input your name., ,
    , 300, 0, 0, , 10, World
    if (ErrorLevel != 0) {
        Return ""
    } else {
        Return UserInput
    }
}

Canceled() {
  MsgBox, Sorry.
  Return
}

Greeting(Name) {
  MsgBox, Hello`, %Name%.
  Return
}

+!1::
 Name := GetNameFromUser_1()
 if (ErrorLevel = 2) {
  ; Timeout
 } else if (ErrorLevel = 1) {
  ; Canceled
  Canceled()
 } else {
  Greeting(Name)
  MsgBox, My name is AHK.
 }
 Return

おわりに

僕は爺さんなので、Goto/Gosubに特に嫌悪感は抱きません。ですが、互換性を死守しながらユーザーリクエストを受け入れて“便利になった”プログラミング言語(もと設定データ)の成れの果てともいえる言語仕様にはちょっと辟易します。

本来の目的であるホットキー設定に関していえば、十分な機能性を持つし、多くの人に長年使われてきた実績もあり、安心して利用できます。アプリケーションごとの設定によらずに、キーボード入力のポリシーをカスタマイズするには、AutoHotKeyが最良・最強の手段だと思います。

*1:[追記]式の中に改行を入れるとエラーになったり意味が変わってしまうことがあるのですが、問題なく改行を入れられることもあります。これが、どういう規則なのか? 試行錯誤で調べても分かりませんでした。[/追記]

*2:AutoHotKeyのディレクティブは、AutoHotKey自体で処理されるので、メカニズム的にはC言語プリプロセッサとは違います。

*3:実は、中括弧で囲まなくてもいいときがあります。ホットキー設定で、LCtrl::Alt のように書けます。しかし、例外規則にいちいち言及するのは煩雑なので、確実に動く構文を述べます。

*4:あるデータの型を特定することは出来ません。あるデータが、ある型とみなせるか? の判定が出来るだけです。

*5:2020年7月現在、GoToキャンペーンが問題になってます。

*6:後述の常駐モードでは、Returnでプロセスを終了することは出来ません。プロセス終了にはExitAppコマンドを使います。Return, Exit, ExitApp の挙動をちゃんと理解するのはけっこう難しいです。

*7:エラーレベルとは、コマンドの結果を示す整数値です。

*8:ホットキー設定を持たないAHKファイルはあまりないと思いますけどね。

*9:MsgBox より Tooltip が便利でしょう。