英語配列キーボードで日本語入力と英語入力を切り替えやすくしたい (2) AHK実践編

# はじめに

英語配列キーボードで日本語入力と英語入力を切り替えやすくしたい 方法をいくつか考えて、AutoHotKey で `[左Alt]` `[右Alt]` に `[無変換]` `[変換]` が良さそう、というところからの続きです。


# AutoHotKey とは

ホットキーのカスタマイズや、マクロ作成ができるフリーソフトです。 [AutoHotkey](https://www.autohotkey.com/) からダウンロードできます。

詳細は [AutoHotkey \- Wikipedia](https://ja.wikipedia.org/wiki/AutoHotkey) を。

[AutoHotkey Wiki](http://ahkwiki.net/Top) が詳しいです。

Wiki は少し古いです。当時から変わっていることは大きく2点。

* AutoHotKey_L と AutoHotKey を昔は区別していましたけれども今は同じ
* エディターは VS Code に AutoHotKey 拡張を入れるのがお勧め


# スクリプト作成

`[Alt]` を押して離す前に他のキー `[A]` を押したときに、どのように出力したいかを整理します。

|Case|入力|出力|
|---|---|---|
|A.|`[左Alt↓]` `[左Alt↑]` `[A↓]` `[A↑]`|`[無変換]` `[A]`|
|B.|`[左Alt↓]` `[A↓]` `[左Alt↑]` `[A↑]`|`[無変換]` `[A]`|
|C.|`[左Alt↓]` `[A↓]` `[A↑]` `[左Alt↑]`|`[Alt]+[A]`|

A. は空打ち、C. はアクセスキーとしての動作です。普通です。

厄介なのは B のように入れ違いになる場合。 A., C., どちら側に寄せるのもありでしょうけれど、アクセスキーとしてふるまうと意図せずにメニューが実行されて何が起きるかわかりませんから、間違った文字入力になる A. 側に寄せる方が良いと思います。 

<del>C. 側に寄せるなら、alt-ime-ahk を使いましょう、でこのエントリーは終わります。</del>

キーを押したタイミングだけでなく、離したタイミングまで考えないて制御するのが困りものです。

また、IME.ahk を使わないという縛りを入れます。今回は AutoHotKey で行いますけれども、将来的に自作キーボードで同じようなことをしたくなるかもしれませんから。

参考:
* [karakaram/alt\-ime\-ahk](https://github.com/karakaram/alt-ime-ahk)
* [IME制御 \- eamat @Cabinet \- atwiki（アットウィキ）](https://w.atwiki.jp/eamat/pages/17.html) IME.ahk


## 方針1. Alt を押したときに無変換・変換を押したことにする

通常のキー処理の裏で、`[無変換]` `[変換]` 相当のキーも送ります。とてもかんたんです。

```
#NoEnv

SendMode Input
SetKeyDelay, -1

LAltPressing := False
RAltPressing := False

~LAlt::
    If Not LAltPressing {
        Send {vk1D} ; Muhenkan
        LAltPressing := True
        KeyWait, LAlt
        LAltPressing := False
    }
    Return

~RAlt::
    If Not RAltPressing {
        Send {vk1C} ; Henkan
        RAltPressing := True
        KeyWait, RAlt
        RAltPressing := False
    }
    Return
```

* Pros.
  * 実装がシンプル
  * Case A, B, C すべて満たせる
  * 応答が早い
* Cons.
  * アクセスキーなどで `[Alt]` を使いたいときも `[無変換]` `[変換]` が発動してしまう
  * Google 日本語入力の IME モード切替ガイドがすぐ消える
    * モード切替後に `[Alt]` キーが押される相当のため
    * IME.ahk を使うなら、 Win32API で IME 状態を取り、切り替えが必要な時に `[Alt]+[~]` = `[半角/全角]` を送ることで解決できます


## 方針2. Alt を押したあと 0.2 秒後のキー状態を見て無変換・変換を押したことにする

```
#NoEnv

; Options
LongPressAltEnabled := False


SendMode Input

LAltState := "Off" ; Off | Tapping | Pressing
RAltState := "Off"
QueuedKey := ""

#If (LAltState = "Tapping") || (RAltState = "Tapping")
    !a::QueuedKey := QueuedKey . "a"
    !b::QueuedKey := QueuedKey . "b"
    ; 中略
    !z::QueuedKey := QueuedKey . "z"
#If

~LAlt::
    If (LAltState = "Off") {
        Send {Blind}{F14}
        LAltState := "Tapping"
        SetTimer, OnTimeoutLAltTapping, -200
    }
    Return
*~LAlt Up::
    SetTimer, OnTimeoutLAltTapping, Off
    If (LAltState = "Tapping") {
        SetKeyDelay, -1
        Send, {Blind}{vk1D}%QueuedKey%
    } Else If (LongPressAltEnabled && LAltState = "Pressing" && A_Priorkey == "LAlt") {
        Send, {LAlt}
    }
    LAltState := "Off"
    QueuedKey := ""
    Return
OnTimeoutLAltTapping:
    If (LAltState = "Tapping") {
        LAltState := "Pressing"
        If (QueuedKey != "") {
            SetKeyDelay, -1
            Send, {Blind}%QueuedKey%
            QueuedKey := ""
        }
    }
    Return

~RAlt::
    If (RAltState = "Off") {
        Send {Blind}{F14}
        RAltState := "Tapping"
        SetTimer, OnTimeoutRAltTapping, -200
    }
    Return
*~RAlt Up::
    SetTimer, OnTimeoutRAltTapping, Off
    If (RAltState = "Tapping") {
        SetKeyDelay, -1
        Send, {Blind}{vk1C}%QueuedKey%
    } Else If (LongPressAltEnabled && RAltState = "Pressing" && A_Priorkey == "RAlt") {
        Send, {RAlt}
    } 
    RAltState := "Off"
    QueuedKey := ""
    Return
OnTimeoutRAltTapping:
    If (RAltState = "Tapping") {
        RAltState := "Pressing"
        If (QueuedKey != "") {
            SetKeyDelay, -1
            Send, {Blind}%QueuedKey%
            QueuedKey := ""
        }
    }
    Return
```

`[Alt ↓]` `[F↓]` `[Alt ↑]` `[F ↑]` `[A ↓]` `[A ↑]` みたいな入力のときに `[F↓]` の瞬間に `[F]` を押したいのか `[Alt]+[F↓]` したいのか分かりません。そこで、`QueuedKey` でいったん `F` を覚えて、200ms 以内に `[Alt]` を離した `Tapping` か、200ms 後も `[Alt]` を押し続けている `Pressing` かで違う出力をしています。

* Pros.
  * Case A, B, C すべて満たせる
  * Google 日本語入力の IME モード切替ガイドが正しく表示される
  * `[Alt]` 長押しを `[Alt]` 単独入力とすることもできる
    * `[Alt]+[F14]` 後に `[Alt]` 単独押しとするので、アプリによってはうまく動かない
* Cons.
  * `[Alt]+[A]` などのアクセスキーの入力が `[Alt]` を押した時から 0.2秒後になる
    * `[A↓]` を検知したときには、`[A]` を入力したいのか `[Alt]+[A]` したいのか分からないため
    * `[A↑]` も検知すれば良さそうですが、スレッドが複数になると難しいです
  * メニューを出さないように `[Alt]+[F14]` をいつも押したことにするのが微妙
  * キューに溜めるキーを A-Z の分だけ行うのが微妙

無理やりですが、これで1時間に1回くらいやってしまっていたメニュー誤発動から開放されて、幸せです。


## 方針3. Alt 発動を最大 0.2 秒間止める

```
#NoEnv

; Options
LongPressAltEnabled := True


SendMode Input
SetKeyDelay, -1

LAltState := "Off" ; Off | Tapping | Pressing
RAltState := "Off"
QueuedKey := ""

#If (LAltState = "Tapping") || (RAltState = "Tapping")
    a::QueuedKey := QueuedKey . "a"
    b::QueuedKey := QueuedKey . "b"
    ; 中略
    z::QueuedKey := QueuedKey . "z"
#If

LAlt::
    If (LAltState = "Off") {
        LAltState := "Tapping"
        KeyWait, LAlt, T0.2
        If ErrorLevel {
            LAltState := "Pressing"
            If (QueuedKey == "" && Not LongPressAltEnabled)
                Send, {Blind}{LAlt DownR}{F14}
            Else
                Send, {Blind}{LAlt DownR}%QueuedKey%
            KeyWait, LAlt
            Send, {LAlt up}
        } Else
            Send, {Blind}{vk1D}%QueuedKey%
        QueuedKey := ""
        LAltState := "Off"
    }
    Return

RAlt::
    If (RAltState = "Off") {
        RAltState := "Tapping"
        KeyWait, RAlt, T0.2
        If ErrorLevel {
            RAltState := "Pressing"
            If (QueuedKey == "" && Not LongPressAltEnabled)
                Send, {Blind}{RAlt DownR}{F14}
            Else
                Send, {Blind}{RAlt DownR}%QueuedKey%
            KeyWait, RAlt
            Send, {RAlt up}
        } Else
            Send, {Blind}{vk1C}%QueuedKey%
        QueuedKey := ""
        RAltState := "Off"
    }

```

方針1, 2 では `~LAlt::` で `[LAlt]` のキー入力はそのまま通しながら、裏で別のキー処理をしていました。
方針3 では 0.2秒間 `[LAlt]` 入力を止めます。

* Pros.
  * Case A, B, C すべて満たせる
  * Google 日本語入力の IME モード切替ガイドが正しく表示される
  * メニューを出さない `[Alt]+[F14]` の使用を最小限にできる
  * `[Alt]` 長押しを `[Alt]` 単独入力とすることもできる
    * 方針 2. の不自然さはない
* Cons.
  * `[Alt]+[A]` などのアクセスキーの入力が `[Alt]` を押した時から 0.2秒後になる
  * キューに溜めるキーを A-Z の分だけ行うのが微妙
  * `[Alt]+[PrintScreen]` などのアクセスキーの入力が `[Alt]` を押しはじめてから 0.2秒経つまでは `[PrintScreen]` になる
    * カーソルなどのすべてのキーに対して `[Alt]` を押している最中は別の処理をするようなスクリプトを書けばよいはず
    * ただでさえ長いスクリプトがさらに長く

方針2 と比べると、良くない点が多いかと思います。

もしかすると AutoHotKey `InputHook()` 関数を使うと、この方針でも進められるかもしれません。時間切れしました。


## 結論: 方針2

[hossy3/alt\-convert\-key](https://github.com/hossy3/alt-convert-key) GitHub に置きました。


# TL;DR (再々掲)

* よほど英語配列にしたい場合以外は、日本語配列キーボードがまずおすすめ。`[無変換]` `[変換]` キーはとても便利。これを捨てるのは大きな犠牲。
* 英語配列キーボードを使うなら、定番の [karakaram/alt\-ime\-ahk](https://github.com/karakaram/alt-ime-ahk) がおすすめ。
* それ以外を目指すなら、ようこそ茨の道へ。

英語配列キーボードに `[無変換]` `[変換]` を割り当てようと実装して苦労した、というのがこれだけでも分かるかと思います。

[QMK Firmware で Raise/Lower と変換/無変換を同じキーに割り当てる \- Okapies' Archive](https://okapies.hateblo.jp/entry/2019/02/02/133953) というエントリを見て、ファームウェア側でもやっぱり大変そうだと思いました。レイヤーキーなら `[Alt]` に比べると組み合わせの相性が良さそうです。キーボードを自作するならこちらを試したいところです。


# ついでに F13 を Fn キーに割り当てる

もうちょっとだけ続きます。