AHK 八倒記 (1)

窓使いの憂鬱 (mayu) から AutoHotkey (ahk) への移行で七転八倒しとります。七転八起じゃないところがポイント。
そもそも ahk は、その腐りきった文法と基本設計の限界がインストールする前から皓々と透けて見えていただけに導入に対してはとても消極的だったんですが、 Vista 対応とポータビリティ*1を考えるといつまでも mayu に固執しているわけにもいかないので、新マシンではあえて mayu をインストールしませんでした。
というわけで、ここからは ahk との格闘の記録(あるいは敗走の足跡)を暴露していきます。攻略法をご存じの先生方はぜひコメントしていただきたいと思います。

やりたいこと (1)

親指 + 文字キー を 機能キー に割り当てたい。たとえば「Space+コロン = PageDown」と定義したら

  • Space だけを押して離すと Space が入力される
  • Space を押しながら コロン を押すと PageDown が入力される
  • Shift と Space を押しながら コロン を押すと Shift+PageDown が入力される
  • Ctrl と Shift と Space を押しながら コロン を押すと Ctrl+Shift+PageDown が入力される
  • 以下同様

という動作をさせたい。 mayu で書けば次の2行でできる。

 mod Mod0 += !!Space
 def subst M0-*Colon = ~M0-*PageDown

ahk には remap という機能がある。

「キーA::キーB」という行を記述すると、キーAにキーBを割り当てることができる。例えば、以下のようにすると、「A」キーを押したときに「b」が入力されるようになる。

 a::b

このとき、Shift+AはShift+Bに、Ctrl+AはCtrl+Bにというように、あらゆる状態でキーの変換が有効になる。

http://lukewarm.s101.xrea.com/remap.html

それから custom combinations という機能がある。たとえば「テンキーの0 を押しながら テンキーの1 を押したら Alt+Tab を発動する」という定義は

 Numpad0 & Numpad1::AltTab
http://lukewarm.s101.xrea.com/Hotkeys.html

こう書けばよい。以上のことから類推して

 Space & : :: PgDn

と書けば所望の動作になるかなー、と思ったら甘かった。ここには少なくとも5つの罠がある。

  1. 特殊キー名の記述
  2. リマップは文法上の例外である
  3. モディファイヤの透過
  4. プレフィクスキー単独で押して離したときの処理
  5. 他のモディファイヤを押しながらプレフィクスキーを押して離したときの処理

正解はこうだ。(5. は未解決)

 *Space:: Send {Blind}{Space}
 Space & sc028 :: Send {Blind}{PgDn} ; colon

罠 (1-1) 特殊キー名の記述

コロンは特殊文字だからそのまま書いてもダメなのはわかるんだけど、じゃあどう書けばいいのかがわからない。

その他の記号
基本的にその文字自身だが、「;」など特別な意味を持つキーは「`;」のようにエスケープする必要がある。
http://lukewarm.s101.xrea.com/KeyList.html

この記述を信じて「`:」と書いてみてもダメだし、キーリストにも載ってないし、エスケープシーケンスの一覧にも載ってない。万策尽きたので仕方なく「vkBAsc028」のように仮想キーコードとスキャンコードを直接指定することにした。本当に他の方法はないんだろうか? 英語キーボードではコロンは単独キーじゃないから問題にならないのか? んなアホな…。
いずれにせよ必要になるだろうから、日本語109キーボードにある全キーの仮想キーコードとスキャンコードのリストを作った。

sc を使うべきか vk を使うべきか

キー名が定義されてない 無変換 のようなキーを使うときは vk1Dsc07B のように仮想キーコードとスキャンコードを書くんだが、 vk1D とか sc07B のように片方だけでもいい。だったらどっちを書くのがベターか。仮想キーコードとスキャンコードはレイヤが違う。スキャンコードはキーボードコントローラが生成してる低レイヤの値で、いくつかの例外*2を除き、キーと値が1対1に対応する。仮想キーコードはドライバによって読み替えられた高レイヤの値で、同じキーでも NumLock の状態によって値が変わったり*3、 Enter とテンキーの Enter を区別できなかったり、モディファイヤによって値が違ったり*4、 down と up で値が違ったり*5、奇数回目と偶数回目で値が違ったり*6する。よって、スキャンコードを使った方が落とし穴が少ないと思われる。

罠 (1-2) リマップは文法上の例外である

remap の書き方はこうだ。

 a::b

これはマニュアルの最初の方に出てくるので ahk における一般的な書き方なのかと思ったら、実は「::」の直後にキー名が来るのは remap と hotstring だけの例外的な書き方らしい。一般的には

 トリガー:: オペコード[,] [オペランド1[, オペランド2[, ...]]]

のように、「::」の直後にはオペコードが来る。キーを送信するオペコードは「Send」だ。それから、トリガーに来るキー名は裸でいいが、オペランドに来るキー名は「{ }」で囲まなければならない。よって、 custom combination の結果としてキーを送信する処理は

 Space & sc028 :: Send {PgDn}

のように書かなければならない。

罠 (1-3) モディファイヤの透過

これだとモディファイヤが無視されて「Space+コロン」も「Shift+Space+コロン」も同じ「PageDown」になってしまう。モディファイヤを透過するには送信するキー列の先頭に「{Blind}」を加えなければならない。「透過」と "Blind" は語義が真逆な気がしていまいちピンとこないんだが、そういう仕様だから仕方ない。

 Space & sc028 :: Send {Blind}{PgDn}

罠 (1-4) プレフィクスキー単独で押して離したときの処理

これだと Space を単独で押して離したときに何も起きない。スクリプト中のどこか1か所で custom combination のプレフィクスとして使われたキーは自身の本来の機能を失うからだ。よって、本来の機能を復活させる処理を別に書く必要がある。

 Space:: Send {Blind}{Space}
 Space & sc028 :: Send {Blind}{PgDn}

これでよし…と思ったらまだ罠があった。これだと Space は入力できるが Shift+Space や Ctrl+Space は依然として入力できない。 custom combination のトリガーである「Space & sc028::」はモディファイヤを意識しないのに対し、 remap のトリガーである「Space::」はモディファイヤを意識する。モディファイヤの状態にかかわらず remap を発動させるためにはワイルドカード「*」を付けなければならない。正解はこうだ。

 *Space:: Send {Blind}{Space}
 Space & sc028 :: Send {Blind}{PgDn}

罠 (1-5) 他のモディファイヤを押しながらプレフィクスキーを押して離したときの処理

この罠からはまだ抜け出せていない。次の日に続く

*1:研究室マシンを含む複数マシンをまたがった相互運用性

*2:Alt+PrintScreen=SysRq や Ctrl+Pause=Break

*3:テンキー

*4:無変換 や ひらがな

*5:半角/全角

*6:半角/全角