AutoHotkeyでSandS

<!-- vi: se tw=1000 et sw=2 ts=2 sts=2 ft=markdown wrap nolbr : -->


# 概要
AutoHotkey を用いて、SandSしてみようという試みです。

前の記事から切り出しました。
[AutoHotkeyでどこでもVimっぽくカーソル移動したい（モードレス版）](https://qiita.com/azuwai2/items/6c4d065aefc18d2c4085)


# 環境
 * windows10 Home 1909
 * AutHotkey v.1.1.33.02


# SandS とは

スペースバーにスペースバー以外の働きを持たせようという考え方です。

スペースバーを単体で押して離した場合は、単なるスペースバーとして作用するようにしておくため、スペースを入力することができなくなってしまうわけではありません。

スペースバーを押し下げている間に、他のキーを同時に押した場合、異なった作用をさせよう。という趣旨です。
元々は SandS (Space and Shift) の名の通り、スペースバーにシフトキー（小文字→大文字）の機能を持たせる物です。

具体的に説明をすると・・・

 +  キーボードの「a」を打つと画面には「a」と出る。コレは当たり前です。。

 +  シフトキーを押し下げても何も起こりません。
    シフトキーを押したまま、キー「a」を打つと、画面には「A」と出る。
    その後、シフトキーを離しても何も起きない。コレもまだ当たり前です。

 +  キーボードのスペースバーを押し下げると、空白文字がキーリピートによりドバーと入り始めます。
    スペースバーを押したまま、キー「a」を打つと、空白文字のリピートが止まり、画面には「a」と出ます。
    その後、スペースバーを離しても何も起きません。コレもまだまだ当たり前です。


ここまでが、いつもの当たり前の世界です。
それが、SandS では以下のようになります。


 +  スペースバーを押し下げても何も起こりません。**（普通じゃない！）**<br>
    スペースバーを押したまま「a」を打つと、「A」と出る。**（何それ便利！？）**<br>
    ちなみに、何もせずにスペースバーを離したときには**1文字分だけの**空白文字が画面に出力されます。

代償として、空白文字のキーリピートによる連続入力が行えなくなります。

SKK、とか、新JISカナ入力、とか、英文タイプといった方面などでは、重宝する人もいるとかいないとか。
ちなみに、元SKK使いですが、確かに便利でした。さらに、元新JISカナ使いでもありますが、確かに便利でした。

日本語入力をしている上では、スペースバーは仮名漢字変換の機能に用いることが多いと思います。
スペースバーを押し下げて変換を開始、そのまま次の文章の入力を続けたつもりが、スペースバーから指が離れる前に文字の入力をしてしまっていて、変なところに大文字アルファベットが？とかいうこともあると聞き及んでいます。

個人的には、「、」「。」を入力するときに、その直前の文章を変換した時のスペースが残ってしまって「＞」や「＜」が入力される事がありました。



## AutoHotkey で SandS

修飾キーと同時にスペースが状態で押された場合は、素直に「修飾キー＋スペース」として働いてほしいため、`*Space::`ではなく、`Space::`を対象に実装しています。


```Sands.ahk
  #InstallKeybdHook
  $Space::
      if SandS_guard = True           ;スペースキーガード
          return
      SandS_guard = True              ;スペースキーにガードをかける
      Send,{Shift Down}               ;シフトキーを仮想的に押し下げる
      ifNotEqual SandS_key            ;既に入力済みの場合は抜ける
          return
      SandS_key=
      Input,SandS_key,L1 V            ;1文字入力を受け付け（入力有無判定用）
      return

  $Space up::                         ;スペース解放時
      input                           ;既存のInputコマンドの終了
      if SandS_guard = False          ;ガードがかかってなかった場合（修飾キー＋Spaceのリリース）
          return
      SandS_guard = False             ;スペースキーガードを外す
      Send,{Shift Up}                 ;シフトキー解放
      ifEqual SandS_key               ;SandS文字入力なし
          Send,{Space}                ;スペースを発射
      SandS_key=
      return
```


## 解説

### 修飾キー同時の押しについて

修飾キーを優先させたいため、あえて`*Space::`ではなく`Space::`を使用しています。
そのため Shif+Space によるスペースの入力はキーリピートが効くという利点（？）もあります。

また、`<^Space::`や`>+Space::`等のように、個別に定義しなおすこともできます。

## 以下、失敗例

### 修飾キー同時押し時も含めて SandS にしたいとき（失敗例）

修飾キーと Space 同時押しした時にも、Space を Shift キーとして使いたい、という場合―――あまり考えられない状況ですが、Shift 以外の修飾キー、例えば Ctrl+Space を Ctrl+Shift として扱いたい、と言う感じでしょうか？―――もし、上記スクリプトを単純に`*Space::`に置き換えてしまうと、ALT+Space や Shift+Space や Ctrl+Space Win+Space 等もすべて該当ルーチン内で処理されてしまい、シフト押し下げ相当の事しかしてくれなくなってしまいます。

コントロールメニューにフォーカスを移すことも、Excel で行選択や列選択をすることも、できなくなってしまいます。
（Win+Space による IME の切り替えもできなくなりますが、それはあまり使わないので問題ない。）

そこで出てくるのが Send コマンドの 「{Blind}」というキーワードです。
通常、Send コマンドでは、修飾キーは仮想的に離された状態として処理されますが、「{Blind}」をキー名の前につけることで、修飾キーの自動復元を行ってくれます。

一応書いてみると、こんな感じになりますか。

ついでに、`up::`時の`input`の空打ちがないと、`%SandS_key%` に半角空白が入ってきてしまうので、`%A_Space%`との比較が必要になってしまうという例も盛り込んでおきます。

```Sands_Blind.ahk
  #InstallKeybdHook

  $*Space::                       ;スペース押下時

      if SandS_guard = True       ;スペースキーガード
          Return
      SandS_guard = True          ;スペースキーにガードをかける

      Send,{Shift Down}           ;シフトキーを仮想的に押し下げる

      ifNotEqual SandS_key        ;文字入力済みの場合は終わり
          return
      SandS_key=
      Input,SandS_key,L1 V        ;1文字入力を受け付け（入力有無判定用）


      Return


  $*Space up::                    ;スペース解放時
      ;input                       ;既存のInputコマンドの終了
      SandS_guard = False         ;スペースキーガードを外す
      Send,{Shift Up}             ;シフトキー解放

      ifEqual SandS_key           ;SandS文字入力なし
          Send,{Blind}{Space}     ;修飾を復元しながらスペースを発射
      ifEqual SandS_key,%A_Space% ;空白文字（Inputに、Sendした{Space}が詰まるため）
          Send,{Blind}{Space}     ;修飾を復元しながらスペースを発射

      SandS_key=
      Return
```

Shift+Space を入力してもリピートもされないのは良いとしても、Ctrl+Space を入力すると、私の環境ではなぜか、IME の切り替えが暴発したりしますね。謎です。

IME の切り替えが暴発する時点でお話になりませんが、もう一つ、使い物にならない理由があります。

IME が全角入力になっている最中に、 Shift → Space の順で押下します。その後、Shift → Space という順番で指を離してみてください。半角空白を期待していたのに、全角空白が入ってしまうのです。

Shift → Space と押された時点で`*Space::`が動き出しますが、Space を離すまで空白文字はまだ入力されません。Shift → Space と離された時点で`*Space up::`が動き出しますが、この時点ではもうShiftは押されておらず、Send コマンドの Blind 指定もむなしく、IME の状態に従って全角空白が入力されてしまうのです。嗚呼、残念無念。

### 修飾キー同時押し時も含めて SandS にしたいとき（押下、解放の順序問題へ対応）
上記の失敗を回避するために、`*Space::`の中で、Space が押された瞬間に、どの修飾キーが押されていたのか覚えておけば良いじゃん！というアプローチもあります。

もはや、あまりする必要の無い苦労ですが、どうしても Shift+Space がリピートしてしまうのが嫌だ！ とか、Ctrl+Space+a の場合には Ctrl+A として振舞ってほしいんだ！ いう場合にはもう少し迷走してみるのもよいでしょう。

こうなりました。

```SandS_Modif.ahk
  #InstallKeybdHook

  $*Space::                           ;スペース押下時
      ;tooltip,"%SandS_mod%" "%SandS_key%" %SandS_guard%  ;debug

      if SandS_guard = True           ;スペースキーガード
          Return
      SandS_guard = True              ;スペースキーにガードをかける

      Send,{Shift Down}               ;シフトキーを仮想的に押し下げる

      SandS_mod=                      ;装飾キー退避
      GetKeyState, state, Shift,P
      if state=D
          SandS_mod=%SandS_mod%+
      GetKeyState, state, Ctrl,P
      if state=D
          SandS_mod=%SandS_mod%^
      GetKeyState, state, Alt,P
      if state=D
          SandS_mod=%SandS_mod%!

      GetKeyState, state, LWin,P
      if state=D
          SandS_mod=%SandS_mod%#
      GetKeyState, state, RWin,P
      if state=D
          SandS_mod=%SandS_mod%#

      ifNotEqual SandS_key            ;文字入力済みの場合は終わり
          return
      SandS_key=
      Input,SandS_key,L1 V            ;1文字入力を受け付け（入力有無判定用）



      Return


  $*Space up::                        ;スペース解放時
      ;tooltip,"%SandS_mod%" "%SandS_key%" %SandS_guard%  ;debug

      input                           ;既存のInputコマンドの終了
      SandS_guard = False             ;スペースキーガードを外す
      Send,{Shift Up}                 ;シフトキー解放

      ifNotEqual SandS_mod            ;修飾キーありの場合
          Send,%SandS_mod%{Space}     ;自前で修飾しながらスペースを発射！
      else                            ;修飾キー同時押しではなかった場合
          ifEqual SandS_key           ;SandS文字入力なし
              Send,{Space}            ;単打のスペースを発射

      SandS_key=
      Return
```

Shift+Spaceを長押ししても、Spaceを離すまで何も入力されず、キーリピートも抑止されています。

問題だった、Shift → Space の順で押し下げて、Shift → Space の順で離してみても、全角空白は出ませんね！成功です。

でも 私の端末では、相変わらず Ctrl 同時押し時の IME 切り替えが暴発します。