AHKでキーボードライフをレベルアップさせてみた


# まえがき
キーボードショートカット、作りませんか?(気さくな挨拶)

改めましてこんにちは。エンジニアはもちろんのこと、誰もがショートカットに魅力を感じるものです。今回は、AutoHotkeyを主軸としながら、様々なソフトを駆使することで、あなたのキーボードライフを快適にする方法を紹介します。

# 実装した機能
新しく追加するキーボードショートカットは以下の通りです。

| ショートカットキー          |    動作          |
| ------------------ | ------------ |
| 変換 + i             | ↑(方向キー)      |
| 変換 + k             | ↓(方向キー)      |
| 変換 + j             | ←(方向キー)      |
| 変換 + l             | →(方向キー)      |
| 変換 + h             | home         |
| 変換 + ;             | end          |
| 無変換 + s            | Rapture起動    |
| 無変換 + j            | Orchis起動     |
| 無変換 + CapsLock     | 仮想デスクトップ切り替え |
| 無変換 + 変換           | 半角/全角切り替え    |
| CapsLock           | BackSpace    |


# 動作環境について
動作環境を紹介します。
- OS:  Windows 11 Home 22H2l
- キーボード:  Logicool Wireless Keyboard K270
- マウス: Logicool ERGO M575SP


（静音マウス、静音キーボードっていいよね）

では始めましょうか。
まずは必要なソフトのダウンロードからですね。

:::note alert
注意!
これから設定することは、全て自己責任であることを理解して行ってください。不安が残る方はWikiや他の記事を見て勉強して頂ければ幸いです。私が勉強に使ったサイトリンクは[参考リンク集](#参考リンク集)にまとめてあります。
:::



# 環境構築
## ソフトのインストール

### AutoHotkey 

https://www.autohotkey.com/

AutoHotkey（略はAHK）は言わずと知れたホットキー編集ソフトです。これがあればだいたいなんでもできます。


v1.1とv2.0の2種類あります(2024/11/07現在)が…今回はv2.0を採用します。
特に理由はないのでお好きな方を…と言いたいのですが
文法に差異があるので、スクリプトをコピペするつもりならv2.0をオススメします。

### AHK:VirtualDesktopAccessor

https://github.com/Ciantic/VirtualDesktopAccessor

ソフトではないですがインストール必須なのでこの章に書きます。こちらはWindowsの仮想デスクトップ機能をAHKから操作するための`.dll`ファイルです。DLL単体をGithubからダウンロードして、適当なフォルダに入れましょう。

### Change Key

https://forest.watch.impress.co.jp/library/software/changekey/

CapsLockキーを変更するために使用します。ダウンロードファイルは`.LHZ`なので、7Zip等で解凍してください。レジストリを書き換えるため、不安な方はバックアップ推奨です。CapsLock単体を書き換えるだけの機能のソフトもありますが、将来のカスタマイズ性を考えてこちらを採用しています。

### Orchis

https://www.eonet.ne.jp/~gorota/

https://forest.watch.impress.co.jp/library/software/orchis/

ランチャーソフトです。登録したソフトをリスト表示して、簡単に起動できます。Orchis内でもソフトにショートカットを割り当てられるので便利です。

### Rapture

https://freesoft-100.com/download/rapture/

スクリーンショットソフトです。Windows標準のスクリーンショットよりも便利なので愛用しています。スクショ機能に興味がない方は飛ばして頂いて構いません。

## AHKスクリプト設定
AHKでは`.ahk`ファイルにスクリプトを記述します。
`MyScript.ahk`を作成して
以下のスクリプトをコピーしましょう。注意点として
- 4行目は、`VirtualDesktopAccessor.dll`が入っているフォルダパスを入力してください([詳細](#仮想デスクトップ切り替え))
- 114行目にある、Raptureショートカットへのファイルパスは[こちら](#Rapture-1)で解説します
<details><summary>スクリプト</summary>

```MyScript.ahk
#Requires AutoHotkey v2.0

; ↓ 4行目
SetWorkingDir("ここにインストールしたDLLファイルのフォルダパスを入力")

VDA_PATH := A_WorkingDir . "\dll\VirtualDesktopAccessor.dll"
hVirtualDesktopAccessor := DllCall("LoadLibrary", "Str", VDA_PATH, "Ptr")

GetDesktopCountProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "GetDesktopCount", "Ptr")
GoToDesktopNumberProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "GoToDesktopNumber", "Ptr")
GetCurrentDesktopNumberProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "GetCurrentDesktopNumber", "Ptr")
MoveWindowToDesktopNumberProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "MoveWindowToDesktopNumber", "Ptr")
GetDesktopNameProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "GetDesktopName", "Ptr")
SetDesktopNameProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "SetDesktopName", "Ptr")

; On change listeners
RegisterPostMessageHookProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "RegisterPostMessageHook", "Ptr")
UnregisterPostMessageHookProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "UnregisterPostMessageHook", "Ptr")

GetDesktopCount() {
    global GetDesktopCountProc
    count := DllCall(GetDesktopCountProc, "Int")
    return count
}

MoveCurrentWindowToDesktop(number) {
    global MoveWindowToDesktopNumberProc, GoToDesktopNumberProc
    activeHwnd := WinGetID("A")
    DllCall(MoveWindowToDesktopNumberProc, "Ptr", activeHwnd, "Int", number, "Int")
    DllCall(GoToDesktopNumberProc, "Int", number, "Int")
}

GoToDesktopNumber(num) {
    global GoToDesktopNumberProc
    DllCall(GoToDesktopNumberProc, "Int", num, "Int")
    return
}

MoveOrGotoDesktopNumber(num) {
    ; If user is holding down Mouse left button, move the current window also
    if (GetKeyState("LButton")) {
        MoveCurrentWindowToDesktop(num)
    } else {
        GoToDesktopNumber(num)
    }
    return
}

GoToNextDesktop() {
    global GetCurrentDesktopNumberProc, GoToDesktopNumberProc
    current := DllCall(GetCurrentDesktopNumberProc, "Int")
    last_desktop := GetDesktopCount() - 1
    ; If current desktop is last, go to first desktop
    if (current = last_desktop) {
        MoveOrGotoDesktopNumber(0)
    } else {
        MoveOrGotoDesktopNumber(current + 1)
    }
    return
}

; ホットキー設定

; ホットキーについて
; SC07Bは無変換
; SC079は変換
; SC070はカタカナ/ひらがな/ローマ字
; SC029は半角/全角
; RAltは右Alt
; AppsKeyは右Altの隣のキーのこと


; [無変換] + F14(CapsLock)でデスクトップを移動する
SC07B & F14::GoToNextDesktop()

; [変換] + [j:←,l:→,i:↑,k:↓]カーソル移動
; {Blind}によって修飾キーが押された場合も出力に修飾キーが渡されるように設定している
SC079 & j::Send "{Blind}{Left}" 
SC079 & l::Send "{Blind}{Right}" 
SC079 & i::Send "{Blind}{Up}" 
SC079 & k::Send "{Blind}{Down}" 

; [変換] + [h:Home,;:End]行の先頭と末尾へジャンプする
SC079 & h::Send "{Blind}{Home}"
SC079 & `;::Send "{Blind}{End}"

; [無変換] + [変換]で半角/全角を切り替える
SC07B & SC079::Send "{SC029}"

; !CapsLockはChgKeyによってF14に変更されている
; F14(CapsLock)でBackSpace
F14::Send "{Blind}{BackSpace}"

; Shift + F14(CapsLock)で+Shift + CapsLockを送信
+F14::+CapsLock 

; Orchisを実行する
; OrchisはランチャーソフトゆえRunやWinActivateが使えない
; Orchis側でF15キーをショートカットに割り当て
; [無変換] + j キーで F15を送る
SC07B & j::Send "{F15}"

; [無変換] + s キーでRaptureを実行する
; 作業フォルダーを指定したショートカットを呼び出すことでrapture.iniを正常に読み込ませている
; ↓ 114行目
SC07B & s::Run "作成したRaptureショートカットのファイルパスを入力"
```
</details>




スクリプトを保存して、ファイルをダブルクリックすると起動しますので、動作確認にどうぞ。

内容が気になるのなら、コメントを見るか、[解説](#解説)を参照してください。

## その他の設定

先ほど作成した`MyScript.ahk`ファイルを、PC起動時に自動実行できると便利です。Wibdowsのスタートアップ機能を使います。
`Win + R`ショートカットから「ファイル名を指定して実行」が起動しますので

![rapture_20241108000919.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3807504/583f10fc-7f2f-c62e-d4a8-55d562ecfba4.png)

`shell:startup`と入力してOKをクリックします。
開かれたフォルダに、`MyScript.ahk`を入れます。これで、次回起動時からスクリプトが呼び出されます。

Orchisもスタートアップに設定しておくと便利です。ランチャーソフトなので、好きな時にサッと立ち上げたいですよね。

`orchis.exe`のショートカットを作成しましょう。作成したショートカットを、スクリプトと同様にスタートアップフォルダに保存してください。

![rapture_20241108162946.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3807504/84683e11-09fa-4126-08d5-c508e9462c34.jpeg)

次に、Orchisの設定をしましょう。`orchis.exe`を起動し、表示されるランチャーを右クリックして「共通設定」をクリックします。

![rapture_20241108174951.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3807504/499f7c88-95a1-9b74-5b44-57e57dbb854c.jpeg)

「表示方法 > キーボード操作」から、Orchis Launcherを選択します。
ここで設定したホットキーから、Orchisランチャーを呼び出せます。
今回は、`F15`を指定します。
`MyScript.ahk`をダブルクリックで実行した後、Orchis Launcherを選択して`[無変換] + j`で`F15`を送りましょう。画像のように何も設定されていないように見えますが、内部的にはちゃんと`F15`が登録されます。

これでOrchisの設定は終了です。

---

Raptureの設定をしましょう。ダウンロードした zipを解凍したら、中に入っている`Rapture.exe`のショートカットを作成します。
ショートカットを右クリック、プロパティを表示します。
「詳細 > 作業ディレクトリ」に、`Rapture.exe`のあるフォルダパスを入力します。

![rapture_20241108164134.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3807504/97ee3688-60e6-a368-942f-5d0641c040ee.jpeg)

そうしたら[スクリプト](#ahkスクリプト設定)の113行目のパスを書き換えてください。
```MyScript.ahk
; [無変換] + s キーでRaptureを実行する
; 作業フォルダーを指定したショートカットを呼び出すことでrapture.iniを正常に読み込ませている
SC07B & s::Run "作成したRaptureショートカットのファイルパスを入力"

```

さて、最後にChange Keyを設定します。`ChgKey.exe`を（右クリックから）管理者権限で起動します。GUI内のCapsLockキーをクリックすると、画像のように背景が緑色になりますので、GUI内のキーボード右上のScanCodeをクリックします。

![rapture_20241108165307.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3807504/f6285e62-b496-25a8-4305-63799f9f4509.jpeg)


ポップアップから、スキャンコード`0065`を入力します。
設定を保存し再起動してください。

![rapture_20241108165205.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3807504/e5e4b929-1b7d-2e77-826a-5d05d3e1d99b.jpeg)

:::note info 
スキャンコードについて
スキャンコードとは、キーボードの各ホットキーに割り当てられたコードのことです。今回のスキャンコード`0x0065`は、`F14`に相当します。[なぜF14なのかは後ほど説明します](#change-key-1)。
:::
## 解説

この章では、それぞれの設定で何を行っているかを説明します。


### 仮想デスクトップ切り替え

1~60行まで抜粋します。
<details><summary>スクリプト</summary>

```MyScript.ahk
#Requires AutoHotkey v2.0

; VirtualDesktopAccessorの関数宣言
SetWorkingDir("VirtualDesktopAccessor.dllのあるディレクトリ")

VDA_PATH := A_WorkingDir . "\dll\VirtualDesktopAccessor.dll"
hVirtualDesktopAccessor := DllCall("LoadLibrary", "Str", VDA_PATH, "Ptr")

GetDesktopCountProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "GetDesktopCount", "Ptr")
GoToDesktopNumberProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "GoToDesktopNumber", "Ptr")
GetCurrentDesktopNumberProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "GetCurrentDesktopNumber", "Ptr")
MoveWindowToDesktopNumberProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "MoveWindowToDesktopNumber", "Ptr")
GetDesktopNameProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "GetDesktopName", "Ptr")
SetDesktopNameProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "SetDesktopName", "Ptr")

; On change listeners
RegisterPostMessageHookProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "RegisterPostMessageHook", "Ptr")
UnregisterPostMessageHookProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "UnregisterPostMessageHook", "Ptr")

GetDesktopCount() {
    global GetDesktopCountProc
    count := DllCall(GetDesktopCountProc, "Int")
    return count
}

MoveCurrentWindowToDesktop(number) {
    global MoveWindowToDesktopNumberProc, GoToDesktopNumberProc
    activeHwnd := WinGetID("A")
    DllCall(MoveWindowToDesktopNumberProc, "Ptr", activeHwnd, "Int", number, "Int")
    DllCall(GoToDesktopNumberProc, "Int", number, "Int")
}

GoToDesktopNumber(num) {
    global GoToDesktopNumberProc
    DllCall(GoToDesktopNumberProc, "Int", num, "Int")
    return
}

MoveOrGotoDesktopNumber(num) {
    ; If user is holding down Mouse left button, move the current window also
    if (GetKeyState("LButton")) {
        MoveCurrentWindowToDesktop(num)
    } else {
        GoToDesktopNumber(num)
    }
    return
}

GoToNextDesktop() {
    global GetCurrentDesktopNumberProc, GoToDesktopNumberProc
    current := DllCall(GetCurrentDesktopNumberProc, "Int")
    last_desktop := GetDesktopCount() - 1
    ; If current desktop is last, go to first desktop
    if (current = last_desktop) {
        MoveOrGotoDesktopNumber(0)
    } else {
        MoveOrGotoDesktopNumber(current + 1)
    }
    return
}
```
</details>



こちらのスクリプトでは、仮想デスクトップ切り替えに必要な関数を定義しています。
このスクリプトは、

https://github.com/Ciantic/VirtualDesktopAccessor/blob/rust/example.ah2

より拝借しています。

このスクリプトで重要なのは
```MyScript.ahk
; ↓ 4行目
SetWorkingDir("VirtualDesktopAccessor.dllのあるディレクトリ")
```
ここです。
ただいま、このスクリプトファイルはスタートアップフォルダに保存しています。
そのため、`A_WorkingDir`もスタートアップフォルダに指定されているのですが、これでは問題があります。
`VirtualDesktopAccessor.dll`がないのです。かといってフォルダ内に入れてしまうと起動時に実行されてしまいます。そのため、`SetWorkingDir()`で変更しています。

---
では本題の仮想デスクトップの切り替えについて説明します。
Windowsではデフォルトで　`Win + Ctrl +←,→`から仮想デスクトップを切り替えることができます。しかし問題があって、ループしてくれないんですよね。詳しく説明します。

例えば、`1_desk`と`2_desk`という２つの仮想デスクトップがあるとします。今、`2_desk`にいるとして、`Win + Ctrl + →`を実行しても何も起きません。理想は`1_desk`に移動してほしい…。

というわけで、VirtualDesktopAccessorによる`GoToNextDesktop()`を利用します。スクリプトを読むとわかりやすいのですが、最後のデスクトップにいる場合には先頭のデスクトップに移動してくれます。まさに理想としていた動作です。

ホットキーの割り当てについては`CapsLock`が
絡んでいるので、[Change Keyの章](#change-key-1)で解説します。

### ホットキー割り当て
`[無変換] + jikl`で方向キーを割り当てています。よく`hjkl`を割り当てる構成が有名ですが、私は慣れがあったのと、`h`と`;`にhomeとendを割り当てたかった関係で、このような構成にしています。`{Blined}`をつけることによって、ShiftやCtrといった修飾キーの入力も一緒に通過できます。例えば、`Shift + [無変換] + l`で、右に進みながら範囲選択ができます。

また、`[無変換] + [変換]`で、半角全角を切り替えられるようにしています。



### Orchis
Orchisは、自動起動しないソフトなので、スタートアップにショートカットを追加することで解決しています。スタートアップでバックグラウンドに用意し、呼び出しキーでウィンドウが表示されます。
つまり、もともと`orchis.exe`は立ち上がっているため
AHKから単に`Run “Orchis.exe”`しても、ウィンドウ表示がされません。

対策は簡単です。Orchis内にショートカット割り当てができるようになっているので、Orchis側でF15キーを呼び出しショートカットに指定し、AHK側では`[変換] + j`でF15キーを送るように設定しています。

### Rapture
Raptureには`Rapture.ini`という設定ファイルがあります。これを設定しておくと、スクリーンショットのファイル名や自動保存の設定ができて便利です。ですが、AHKから`Rapture.exe`を`Run`しても、設定ファイルが読み込めませんでした。

`A_WorkingDir`を変更することも検討しましたが、VirtualDesktopAccessorで設定済みであること、わざわざ切り替えるのはメモリ消費に繋がりかねないのでやめました。

代わりに、ショートカットから作業ディレクトリを指定することで解決しています。

つまり

1. AHKからショートカットを呼び出す
2. ショートカットが作業ディレクトリを、`Rapture.ini`のあるフォルダに指定する
3. ショートカットが`Rapture.exe`を呼び出す

こういう仕組みで動いています。

### Change Key

こちらのQiita記事には丁寧な画像付きの解説もあります。

https://qiita.com/riekure/items/49b941fa5159f9948313

Change KeyではCapsLockをF14キーに割り当てています。F15でないのはただの気分です。わざわざBackSpaceを直接割り当てていないのは、`Shift + CapsLock`の動作は残したかったからですね。

Functionキーのスキャンコード(F13~F24)はこちらのサイトを参考にしました。

http://did2.blog64.fc2.com/blog-entry-349.html


```MyScript.ahk
F14::Send ”{Blind}{BackSpace}” 

Shift & F14::Send “{+CapsLock}”

SC07B & F14::GoToNextDesktop()
```
- 1行目では、F14にBackSpaceをAHKから割り当てます
- 2行目ではShift + F14を押された場合には、システムに`Shift + CapsLock`を伝えるようにしています(CapsLockの前についた`+`はShiftキーの意味です)
- 3行目では[仮想デスクトップ切り替え](#仮想デスクトップ切り替え)の`GoToNextDesktop()`を呼び出します

### 余談 ミドルボタン

AHKでは、マウスクリックにもスクリプトを利用することができます。
今回は、マウスのミドルボタンにデスクトップ切り替えを割り当てます。
ですが、Chromeでサイトを見ている場合はこの機能をオフにします。
理由は、ミドルボタンを押した際のスクロール移動が便利だからです。そもそも、`[変換] + CapsLock`に同じ機能があり、Chrome操作時はそっちを使うからですね。

というわけで、以下のスクリプトを追加します。62行目以降に追加してください。

仕組みは単純です。`WinActive(“ahk_exe chrome.exe”)`でChromeがアクティブであることを検知した場合、ただMbuttonを`Send`して終わります。それ以外では、`GotoNextDesktop()`を呼び出し仮想デスクトップを切り替えます。

```MyScript.ahk
; マウスミドルボタンで仮想デスクトップを切り替える。chrome使用時はオフになっている。
MButton::{
    If WinActive("ahk_exe chrome.exe"){
        Send "{Mbutton}"
        return
    }
    GoToNextDesktop()
}
```
# さいごに
お疲れ様でした。操作感には慣れが必要ですが、きっと大きな恩恵を得られるはずです。
特に、半角全角切り替えが思った以上に楽なので、気に入っています。

AutoHotkeyには他にも作業の自動化、コピペの強化ができるそうです…凄い。
私のAHK歴は短いので、これからも勉強していこうと思います。

誤字訂正は編集リクエストで、質問はコメントにてお願い致します。

あなたのキーボードライフに幸在らんことを…。

## 参考:リンク集

https://www.autohotkey.com/

https://ahkscript.github.io/ja/docs/v2/

https://github.com/Ciantic/VirtualDesktopAccessor

https://qiita.com/riekure/items/49b941fa5159f9948313

http://did2.blog64.fc2.com/blog-entry-349.html