テンキー N を押すと直前に押したキーを N 回連打する AutoHotkey スクリプト

テンキーを持て余しているので有効活用したいと思い、AutoHotkey の練習も兼ねて書いてみました。

# 前提
[AutoHotkey](https://autohotkey.com/) について簡単に紹介しておきます。

AutoHotkey とは Windows 用のスクリプトエンジンです。ahk ファイルという独自文法で書いたスクリプトを AutoHotkey.exe に食わせることで動作します。元はその名の通りキーボード制御（リマップやらホットキーやら）用ソフトウェアでしたが、今は色んな機能が搭載されてスクリプト言語にまで成長しました。

AutoHotkey には複数のバージョンがありますが、本記事では [AutoHotkey_L(Version 1.1.x)](https://github.com/Lexikos/AutoHotkey_L) を前提とします。

# 背景
スペース、Backspace、カーソルキーなどを連打する機会はちらほらありますが、連打するのがだるい時があります。楽できないかなと考えて、**直前に押したキーを N 回連打させる** のはどうか、とひらめきました。

# つくったもの
**テンキー N を押すと、直前に押したキーを N 回連打する** という AutoHotkey スクリプトをつくりました。

## 動作イメージ
たとえばスペースを押した後にテンキー8を押すと、スペースが8回連打されます。また「→」を押した後にテンキー4を押すと、「→」が4回連打されます。

![strokelone_demo](https://user-images.githubusercontent.com/23325839/40335913-3461357e-5da1-11e8-8a34-7f9f986a0b33.gif)

上記画像では「A」とスペースの連打を試していますが、Enter やらカーソルキーやらにも使えます。

## スクリプト

```ahk
#SingleInstance force
#Persistent
#KeyHistory 20
#InstallKeybdHook

priorkey := A_PriorKey

Numpad1::
  push_priorkey(1)
Return

Numpad2::
  push_priorkey(2)
Return

Numpad3::
  push_priorkey(3)
Return

Numpad4::
  push_priorkey(4)
Return

Numpad5::
  push_priorkey(5)
Return

Numpad6::
  push_priorkey(6)
Return

Numpad7::
  push_priorkey(7)
Return

Numpad8::
  push_priorkey(8)
Return

Numpad9::
  push_priorkey(9)
Return

push_priorkey(count){
  global priorkey
  old_priorkey := priorkey
  priorkey := A_PriorKey

  is_numpad_found := InStr(priorkey, "Numpad")
  if(is_numpad_found){
    priorkey := old_priorkey
  }

  Loop %count% {
    SendEvent {%priorkey% down}
    Sleep 1
  }
  SendEvent {%priorkey% up}
}
```

## GitHub
[strokelone](https://github.com/stakiran/strokelone) という名前で GitHub にアップしてみました。

# スクリプト解説
以下 AutoHotkey の話題が続きます。

上記スクリプトの中身について解説していきます。

## (1) 直前に押したキーの連打
本スクリプトの根幹となる処理をどうやって実現したかという話です。

まず必要となるパーツは以下三つ。

パーツ1、テンキー入力時に別の処理を実行する。

```
Numpad1::
  ; テンキー1を押した時に実行する処理をココに書く
  ; ...
  ; ...
Return
```

パーツ2、直前に入力されたキー内容を取得する。

```
; 直前に押されたキー内容が入っているシステム変数
%A_PriorKey%
```

パーツ3、任意のキー入力を送信する。

```
; Spaceを入力する
SendEvent {Space}
```

これらを組み合わせれば実現できそうです。

が、一点厄介な罠があります。 **一度テンキーを入力すると、A_PriorKey の中身がテンキーになってしまう** ことです。これのせいで、たとえば space → テンキー8 → テンキー8 と押しても space が16回入力されません（1回目のテンキー8を押した時点で A_PriorKey がテンキー8 になってしまう）。

どころか内部的には「テンキー8を押したら A_PriorKey(=テンキー8) を Send する」という **循環再帰呼び出し** みたいな挙動になってしまい、スクリプトがエラーを出してしまいます。

これを回避するために、以下のように対処しました。

- A_PriorKey の値を別のグローバル変数にもたせておく(例: priorkey)
- 二つ前に入力されたキーも保持しておく(例: old_priorkey)
- SendEvent を行う前に priorkey の中身を見て、 **もしテンキー系が入っていたら場合は priorkey の中身を old_priorkey にする**

つまり「テンキーを A_PriorKey の対象から外す」処理を強引に実現しました。

## (2) A_PriorKey キーの送信方法について
キー送信は Send 命令にて行いますが、この Send は設定や使い方が非常に多岐に渡ります。

- 参考
  - [Send / SendRaw / SendEvent / SendInput / SendPlay - AutoHotkey Wiki](http://ahkwiki.net/Send)
  - [SendMode - AutoHotkey Wiki](http://ahkwiki.net/SendMode)

どんな方法を使おうかという話ですが、以下のようにしてみました。

```ahk
Loop %count% {
  SendEvent {%priorkey% down}
  Sleep 1
}
SendEvent {%priorkey% up}
```

- ここでの結論
  - (1) Send 命令として `SendEvent` を使った
  - (2) キー送信はまず down(押し下げ) を送信回数分送った後、最後に up(押し上げ) を送信した

まず (1) ですが、SendPlay や SendInput は動作が安定しなかった（やたら動きがカクカクしたり送信が早すぎてエディタ側で N 回全部を捕捉できなかったりしました :sweat: ）ため SendEvent としました。

続いて (2) です。最初は以下のようにしていたのですが、

```ahk
; 最初はこうしていた
Loop %count% {
  SendEvent {%priorkey%}
}
```

これだと **テンキーを押しっぱなしにした時** に動作しなくなる、という問題がありました。AutoHotkey  において押しっぱなし時も動作させたい場合、上記のように down(押し下げ) のみを繰り返し送信させるのが一種のおまじないのようです。（この辺、詳しい仕組みがわかってないので知りたいところではあります :sweat: ）

## (3) その他必要な設定について
スクリプトの序盤で定義しているやつらです。

```ahk
#SingleInstance force
```

これは多重起動を防止するオプションです。厳密に言うと「後から起動された場合、前に起動してた分を **強制的に(Forcely)** 終了させる」という挙動になります。

```ahk
#Persistent
```

これは「（スクリプトが最後の行まで実行されたら終了するのではなく）常駐してね」というオプションです。

```ahk
#KeyHistory 20
```

これは「入力されたキー情報をどこまで記録するか」という値です。A_PriorKey 変数は、この key history 機能が有効じゃないと使えませんので、こうして明示的に有効にします。

この key history ですが、keydown(キーを押し込む) と keyup(キーを離す) をそれぞれ 1 件として記録するので、「一つ前のキー」情報がほしければ最低でも 2 が必要となる……と思えますが、なぜか 2 だと動作しませんでした。ここではテキトーに 20 くらいにしてます :smirk:

```ahk
#InstallKeybdHook
```

これは「キーボードフック」という機能を有効にするオプションです。AutoHotkey ではキーボード絡みで色々複雑なことをしており、そのために「キーボードフック」なる専門的な処理が必要になることが多いのですが、このオプションはその「専門的な処理を使ってください」と指定しています。ざっくり言うと、おなじないみたいなものです。

# おわりに
AutoHotkey を使うと、このようなカスタマイズを（ある程度プログラミングに慣れてるなら）サクっとこなせるので便利だなと改めて思いました。

ですが（サクっと書いただけの本スクリプトは）まだ挙動が完全ではなさそうです。特に Send 部分など「おまじないだから」で済ましている部分がちらほらあります :sweat: もっと精進が必要ですね。

以上、AutoHotkey ネタとしてテンキーで直前入力キーを連打する例について取り上げてみました。何かの参考になれば幸いです。