CapsLockが最強レイヤーキーに変身！ホームポジション崩さずVimライクに爆速タイピング

## はじめに

> Mac版はこちらの記事からどうぞ → [英数キーが最強レイヤーキーに変身！Karabiner-Elementsでホームポジション崩さず爆速タイピング](https://qiita.com/y__h/items/fda87bec491e0dde500d)
> 

普段のタイピングで、**矢印キー**や**Backspaceキー**を押すたびに**ホームポジション**から手を動かしていませんか？

また、あまり使わない**CapsLockキー**が重要な位置にあってもったいないと感じたことはないでしょうか。

この記事では、**AutoHotKey**を使って**CapsLockキー**を“**レイヤーキー**”として活用し、**ホームポジション**を崩さずに**矢印キー**や**Backspace**、**Enter**などを使えるようにする方法をご紹介します。

**AutoHotKey公式サイト↓**

https://www.autohotkey.com/

## キーボード配列の問題点

- **矢印キー・Backspaceが端にある**：**ホームポジション**から手を離さないと操作できず、タイピング中の手の移動が頻発する
- **CapsLockキーが“死に”キー**：あまり使わないのに重要な位置にあるため、別用途に再割り当てしたい

## CapsLockレイヤー化の概要

1. **CapsLock**がオンになっている間、`ESDF`周辺のキーを**矢印キー**や**Backspace**、**Enter**、**Del**などに置き換える
2. 必要に応じて**Ctrl／Alt**や**変換／無変換キー**も割り当て
3. **CapsLock**のオン／オフ状態を視覚的にわかりやすく画面中央に**オーバーレイ表示**

## AutoHotKeyの使い方

AutoHotKey（AHK）は、Windows上で動作するフリーのマクロ／自動化ツールです。以下の手順で導入し、スクリプトを実行することができます。

1. **インストール**
    - [公式サイト](https://www.autohotkey.com/)から最新の**インストーラ**をダウンロードし、ウィザードに従って**インストール**します。
    - **v1系**と**v2系**があるため、互換性のあるスクリプトを使う場合は**バージョン**を確認してください。(こだわりが無ければ**v2**をダウンロードすることをおすすめします)
2. **スクリプトの作成**
    - 任意のテキストエディタで拡張子 **`.ahk`** のファイルを作成します。
    - ファイル冒頭で **`#Requires AutoHotkey v2.0`**を記述します。(なくても良い)
3. **スクリプトの実行／再読み込み**
    - **`.ahk`** ファイルをダブルクリックすると、タスクトレイに**AHKアイコン**が常駐し、スクリプトがバックグラウンドで動作します。
    - スクリプトを編集したら、タスクトレイアイコンを右クリックして **「Reload Script」** を選びます。
![バックグラウンド.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3638255/8707b529-f771-4310-bafc-86253a5290a1.png)

4. **自動起動設定（スタートアップ登録）**
    1. スクリプトの**ショートカット**を作成し、名前を分かりやすくリネームしておきます。
    2. **`Win + R`** で「ファイル名を指定して実行」を開き、 **`shell:startup`** と入力してEnter。
    3. 開いた**スタートアップフォルダ**に先ほどのショートカットを貼り付けると、Windows起動時にスクリプトが自動で実行されるようになります。
![windowsR.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3638255/7e494550-e4db-4430-a7f0-ff4e3ea2624c.png)

これらの基本操作を押さえておくことで、**CapsLockレイヤー化**スクリプトのカスタマイズやトラブルシュートがスムーズになります。

## 1. キーマップ変更スクリプト (**AutoHotKey v2**)

以下のスクリプトは、**CapsLock**がオンになっている間だけキーの機能を入れ替える「**レイヤー**」動作を実現します。ホームポジションから指を大きく動かさずに、矢印移動や編集操作が可能になります。

```
#HotIf GetKeyState("CapsLock", "T")
  ; Esc / Tab / Backspace / Enter / Delete
  $q::Esc
  $a::Tab
  $w::Backspace
  $r::Enter
  $t::Del

  ; 矢印キー
  $e::Up
  $s::Left
  $d::Down
  $f::Right

  ; Home / Space / End
  $x::Home
  $c::Space
  $v::End

  ; Ctrl / Alt / 変換 / 無変換
  $n::Ctrl
  $m::Alt
  $u::vk1C  ; 変換キー
  $o::vk1D  ; 無変換キー
#HotIf
```

### キーマップ一覧

| 元キー | 置換先機能 |
| --- | --- |
| Q | Esc |
| A | Tab |
| W | Backspace |
| R | Enter |
| T | Del |
| E/S/D/F | Up/Left/Down/Right |
| X/C/V | Home/Space/End |
| N/M | Ctrl/Alt |
| U/O | 変換/無変換 |

スクリプト中のキー名（例：`Tab`, `vk1C`など）は、公式ドキュメントで一覧化されています。ほかのキーをリマップしたい場合は以下のURLを参考に、キー名を自由に変更してください。

https://www.autohotkey.com/docs/v2/KeyList.htm

## 2. CapsLockオンオフを表示するインディケータ (`AutoHotKey v2`)

こちらは**CapsLock**の状態を中央画面に**オーバーレイ**表示するスクリプトです。トグルごと画像の **「NORMAL MODE」**／**「INSERT MODE」** を **フェードイン／アウト**で表示します。
![insertMode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3638255/8184472b-c6f5-4f23-a6bb-9301884252d0.png)

![normalMode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3638255/a8cdb8be-090c-4570-9fb5-83278214f3f7.png)

```
#Requires AutoHotkey v2.0

; 定数定義
OVERLAY_WIDTH := 240       ; オーバーレイの幅
OVERLAY_HEIGHT := 60       ; オーバーレイの高さ
OVERLAY_BG_NORMAL := "303030"  ; 通常モード時の背景色（暗めのグレー）
OVERLAY_BG_INSERT := "304030"  ; 挿入モード時の背景色（暗めの緑グレー）
OVERLAY_OPACITY := 180      ; 透明度 (0-255, 0が完全不透明)
OVERLAY_FONT := "s16 bold" ; オーバーレイのフォントスタイル
OVERLAY_FONT_FACE := "Arial" ; オーバーレイのフォント
OVERLAY_TEXT_COLOR := "FFFFFF" ; テキストの色（白）
DISPLAY_TIME := 1000       ; オーバーレイの表示時間（ミリ秒）
FADE_STEP := 20           ; フェードアウト時の透明度減少ステップ
FADE_INTERVAL := 5       ; フェードアウトの更新間隔（ミリ秒）
INSERT_MODE_TEXT := "INSERT MODE" ; 挿入モード時の表示テキスト
NORMAL_MODE_TEXT := "NORMAL MODE" ; 通常モード時の表示テキスト

; Caps Lockの標準機能を無効化
SetCapsLockState "AlwaysOff"

; オーバーレイウィンドウの初期化
class ModeOverlay {
    static gui := ""
    static textControl := ""
    static displayTimer := 0
    static fadeTimer := 0
    static currentOpacity := OVERLAY_OPACITY
    
    ; オーバーレイウィンドウの初期化
    static Init() {
        this.gui := Gui("+AlwaysOnTop -Caption +ToolWindow +E0x20")
        this.gui.BackColor := OVERLAY_BG_NORMAL
        ; フォントのパディングを設定（垂直方向のオフセットを追加）
        this.gui.SetFont(OVERLAY_FONT " c" OVERLAY_TEXT_COLOR, OVERLAY_FONT_FACE)
        ; テキストコントロールをGUI全体に広げて中央揃えにする
        ; 0x1: SS_CENTER - テキストを水平方向に中央揃え
        ; 0x200: SS_CENTERIMAGE - テキストを垂直方向に中央揃え
        ; 合計: 0x201 - 完全に中央揃え
        ; y2に設定して少し下方向に調整
        this.textControl := this.gui.Add("Text", "x0 y2 w" OVERLAY_WIDTH " h" OVERLAY_HEIGHT-4 " +0x201 Center", "")
        
        ; ウィンドウの透明度を設定
        WinSetTransparent(OVERLAY_OPACITY, this.gui)
    }
    
    ; オーバーレイを表示
    static Show(text, isInsertMode := false) {
        ; 透明度を初期値に戻す
        this.currentOpacity := OVERLAY_OPACITY
        
        ; フェードタイマーが動いていれば停止
        if this.fadeTimer
            SetTimer(this.fadeTimer, 0)
            
        ; 既存の表示タイマーをクリア
        if this.displayTimer
            SetTimer(this.displayTimer, 0)
        
        ; モードに応じて背景色を変更
        this.gui.BackColor := isInsertMode ? OVERLAY_BG_INSERT : OVERLAY_BG_NORMAL
        
        ; アクティブなウィンドウが属するモニターの中央に表示
        monitorRect := this.GetActiveMonitorRect()
        
        ; モニターの中央座標を計算
        x := monitorRect.left + (monitorRect.right - monitorRect.left) // 2 - OVERLAY_WIDTH // 2
        y := monitorRect.top + (monitorRect.bottom - monitorRect.top) // 2 - OVERLAY_HEIGHT // 2
        
        ; テキスト設定（上下中央に表示されるように垂直センタリングを追加）
        this.textControl.Value := text
        
        ; オーバーレイを表示
        this.gui.Show("x" x " y" y " w" OVERLAY_WIDTH " h" OVERLAY_HEIGHT " NoActivate")
        
        ; 透明度を再設定（色が変わった後にも透明度を維持するため）
        WinSetTransparent(OVERLAY_OPACITY, this.gui)
        
        ; 指定時間後にフェードアウト開始
        this.displayTimer := ObjBindMethod(this, "StartFadeOut")
        SetTimer(this.displayTimer, -DISPLAY_TIME)
    }
    
    ; フェードアウト開始
    static StartFadeOut() {
        this.fadeTimer := ObjBindMethod(this, "FadeStep")
        SetTimer(this.fadeTimer, FADE_INTERVAL)
    }
    
    ; フェードアウトのステップ処理
    static FadeStep() {
        ; 透明度を減少
        this.currentOpacity -= FADE_STEP
        
        ; 透明度が0以下になったら非表示にして終了
        if (this.currentOpacity <= 0) {
            SetTimer(this.fadeTimer, 0)
            this.gui.Hide()
            this.currentOpacity := OVERLAY_OPACITY
            return
        }
        
        ; 透明度を適用
        WinSetTransparent(this.currentOpacity, this.gui)
    }
    
    ; オーバーレイを即時非表示（フェードなし）
    static Hide() {
        ; フェードタイマーが動いていれば停止
        if this.fadeTimer
            SetTimer(this.fadeTimer, 0)
            
        this.gui.Hide()
        this.currentOpacity := OVERLAY_OPACITY
    }
    
    ; アクティブなモニターの領域を取得
    static GetActiveMonitorRect() {
        ; アクティブなウィンドウの情報を取得
        activeWin := WinGetID("A")
        
        ; アクティブなウィンドウが属するモニターのハンドルを取得
        ; MONITOR_DEFAULTTONEAREST = 2
        activeMonitor := DllCall("MonitorFromWindow", "Ptr", activeWin, "UInt", 2)
        
        ; MONITORINFO構造体を作成（40バイト: サイズ(4) + RECT(16) + RECT(16) + フラグ(4)）
        monitorInfo := Buffer(40, 0)
        NumPut("UInt", 40, monitorInfo, 0)  ; 構造体のサイズを設定
        
        ; モニター情報を取得
        DllCall("GetMonitorInfo", "Ptr", activeMonitor, "Ptr", monitorInfo)
        
        ; モニターの作業領域（タスクバーを除いた領域）を取得
        return {
            left: NumGet(monitorInfo, 20, "Int"),
            top: NumGet(monitorInfo, 24, "Int"),
            right: NumGet(monitorInfo, 28, "Int"),
            bottom: NumGet(monitorInfo, 32, "Int")
        }
    }
}

; オーバーレイを初期化
ModeOverlay.Init()

; モード管理クラス
class InputModeManager {
    static isInsertMode := true
    
    ; モードを切り替え
    static ToggleMode() {
        this.isInsertMode := !this.isInsertMode
        
        if this.isInsertMode {
            ModeOverlay.Show(INSERT_MODE_TEXT, false)
            SetCapsLockState "off"
        } else {
            ModeOverlay.Show(NORMAL_MODE_TEXT, true)
            SetCapsLockState "on"
        }
    }
}

; Caps Lockキーのホットキー設定
CapsLock::InputModeManager.ToggleMode()
```

### インディケータの主要パラメーター説明

| 変数名 | 用途 | デフォルト値 | 調整例 |
| --- | --- | --- | --- |
| `OVERLAY_WIDTH` | オーバーレイウィンドウの横幅 (px) | `240` | `300` にすれば横長にできます |
| `OVERLAY_HEIGHT` | オーバーレイウィンドウの高さ (px) | `60` | `80` にすれば縦に大きく表示 |
| `OVERLAY_FONT` | フォントサイズとスタイル設定 | `"s16 bold"` | `"s20 bold"` → 文字をさらに大きく、`"s14"` → 少し小さめに |
| `OVERLAY_FONT_FACE` | フォントフェミリー名 | `"Arial"` | `"Meiryo UI"` に変更して和文フォントに |
| `OVERLAY_TEXT_COLOR` | 表示文字の色 (16進RGB) | `"FFFFFF"` (白) | `"FF0000"` → 赤文字に、`"00FF00"` → 緑文字に |
| `OVERLAY_BG_NORMAL` | 通常モード時の背景色 (16進RGB) | `"303030"` (グレー) | `"202020"` → さらに暗いグレー、`"505050"` → 明るめのグレー |
| `OVERLAY_BG_INSERT` | 挿入モード時の背景色 (16進RGB) | `"304030"` (緑寄り) | `"203020"` → 暗い緑、`"407040"` → 明るい緑 |
| `OVERLAY_OPACITY` | ウィンドウの不透明度 (0～255, 高いほど不透明) | `180` | `200` にすればより不透明に、`120` にすればより透けて見えます |
| `DISPLAY_TIME` | オーバーレイを表示する時間 (ミリ秒) | `1000` | `2000` にすると2秒間表示され、`500` にすると半秒でフェードアウト開始 |
| `FADE_STEP` | フェードアウト時に一度に減少する透明度 | `20` | `10` にするとゆっくりフェードアウト、`30` にすると速くフェードアウト |
| `FADE_INTERVAL` | フェードアウト更新間隔 (ミリ秒) | `5` | `10` にするとフェードの刻みが粗く、`2` にするとより滑らか |
| `INSERT_MODE_TEXT` | 文字入力モード表示文字 | `"INSERT MODE"` | `"Vim Off"` などお好みの文言に変更可能 |
| `NORMAL_MODE_TEXT` | 装飾モード表示文字 | `"NORMAL MODE"` | `"Vim On"` などお好みの文言に変更可能 |

> Tip 1:
> 
> 
> これらの変数はスクリプト冒頭で定義しています。自分の環境や好みに合わせて数値やカラーコード、テキストを自由に変更してください。
> 

> Tip 2:
> 
> 
> Vimのモード名似合わせているため、通常モードを”INSERT”に、特殊モードを”NORMAL”に命名しています。
> 

## 3. 日本語／英語入力切替キーの設定方法

Windows標準キーボードには、Macのような専用のかな／英数キーがありません。以下の方法で切り替えを割り当てておくと便利です。

- **Google 日本語入力（Mozc）用のかなキー設定**
    
https://qiita.com/suupia/items/567ebcd4bc8b9890b82a
    
- **Windows IME用のかな入力切替キー設定**
    
https://miraisouzou.jp/kana_key_windows/
    

これらのURLを参考に、 **`vk15`** （無変換）や `vk1C` （変換）などをAutoHotKeyで再マップしましょう。

## まとめ

- **CapsLock**を**レイヤーキー化**することで、**ホームポジション**を崩さずに**矢印キー**や**Backspace**、**Enter**などを操作可能に
- **AutoHotKey v1/v2**それぞれの**スクリプト例**を掲載
- **オーバーレイ表示**で**CapsLock**の状態が一目瞭然に
- **日本語／英語入力切替キー**も併せて設定すれば、Windowsでのタイピングがさらに快適に！

ぜひご自身のキーボードでも**試してみてください**！