AutoHotkeyのススメ

## はじめに

AutoHotkey歴2年にして、それなりに便利な使い方がわかってきました。
どうやら独自の運用方法をしているようですし、布教のためにもと思い、紹介しておくことにしました。

※初心の方はまずこちらからご覧ください。
[【連載】なぜAutoHotkeyを使うのか](https://ashiatoblog.com/posts/pc/autohotkey/ahk1/)

※最新のオリジナルAutoHotkeyスクリプトはこちら。
https://github.com/ryoheiszk/PortableApps/tree/master/ProgramFiles/AutoHotkey

## 環境構築

※ 下の記事の要約です。
[【連載】AutoHotkeyのインストール・環境構築](https://ashiatoblog.com/posts/pc/autohotkey/ahk2/)

以下のツールを用います。

* AutoHotkey(Unicode) - Portable
* VSCode
* AutoHotkey Plus(VSCode拡張)
* Git

### AutoHotkey(Unicode) - Portable

かつてはShift-JIS版が主流でしたが、現在は**UTF-8 with BOM**版でOKです。

Portable版にした理由は環境依存性を極力除きたかったためで、持ち出したときに適応しやすくなります。

### VSCode  / AutoHotkey Plus

使い始めた頃はメモ帳でコーディングしていましたが、どう考えても不便なのでモダンなエディタに移行しました。

個人的にはこの組み合わせが無難なのではないかと思っています。

デバッガが便利で、`MsgBox`でデバッグせずに済むので助かります。

`launch.json`は次のように設定するとよいでしょう。

```json:launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "ahk",
      "request": "launch",
      "name": "AutoHotkey Debugger",
      "program": "${workspaceRoot}\\AutoHotkey.ahk",
      "stopOnEntry": true
    }
  ]
}
```

※`program`を自動生成のものから書き換えています。
後述のディレクトリ構成の場合、実行すべきは常に`AutoHotkey.ahk`であるためです。
ただし、こちらの設定は`F5`(既定値)によるデバッグに関するものであり、AutoHotkey Plusの`Debug Script`の場合は常にアクティブがファイルが実行されるため、上記の設定にして差し支えることはないと思われます。

### Git

バージョン管理したいお年頃です。

## おすすめのスクリプト

下記のディレクトリ構成であることを前提とします。

`※`部分に関して、重要な順に説明します。

```bash
root\
  ├ AutoHotkey.exe
  ├ ※AutoHotkey.ahk
  ├ PluginList.ahk
  ├ Variables.ahk
  └ Plugins\
       ├ ※main.ahk
       ├ ※edit.ahk
       ├ ※numpad.ahk
       ├ ※fn.ahk
       ├ ※hotstring.ahk
       ├ ※others.ahk
       ├ ※git_alias.ahk
       ├ ※toggle.ahk
       ├ ※keybd_mouse.ahk
       ├ ※temp.ahk
       ├ ※IME.ahk
       ├ launcher_data.ini
        └ bat\
          └ batch_file_1.bat
```

### AutoHotkey.ahk

`AutoHotkey.ahk`は全スクリプトの核となるファイルですが、こちらは以下の役割としています。

1. スクリプト全体の設定
2. グローバル変数の定義
3. プラグインの自動取り込み
4. 共通サブルーチンの定義

#### 1.スクリプト全体の設定

流派は様々ありますが、私は次のような設定にしています。

```autohotkey:AutoHotkey.ahk
#Persistent
#SingleInstance, Force
#NoEnv
#UseHook
#InstallKeybdHook
#InstallMouseHook
#HotkeyInterval, 2000
#MaxHotkeysPerInterval, 200
Process, Priority,, Realtime
SendMode, Input
SetWorkingDir %A_ScriptDir%
SetTitleMatchMode, 2
```

|コマンド|説明|
|---|---|
|#Persistent|スクリプトを明示的に常駐させる|
|#SingleInstance, Force|同じスクリプトを再実行した際は、確認なくリロードする|
|#NoEnv|変数の処理においてWindows環境変数の探索をしない(処理速度向上のため)|
|#UseHook|RegisterHotkeyを介さず、フックを使用してホットキーを定義する(処理速度向上のため)|
|#InstallKeybdHook|キーボードフックを有効化する(デバッグのため)|
|#InstallMouseHook|マウスフックを有効化する(デバッグのため)|
|#HotkeyInterval, 2000|無限ループの検出間隔を設定|
|#MaxHotkeysPerInterval, 200|この回数以上のホットキーが↑の間隔で行われた場合、無限ループとして警告する|
|Process, Priority,, Realtime|プロセス優先度を最高にする|
|SendMode, Input|SendコマンドのモードをInputにする(処理速度向上のため)|
|SetWorkingDir %A_ScriptDir%|作業フォルダをAutoHotkey.ahkを含むフォルダとする
|SetTitleMatchMode, 2|WindowTitleは部分一致検索にする|

#### 2.グローバル変数の定義

グローバル変数はAuto-executeセクションで定義する必要があります。

切り分けたスクリプトで定義する方法がないわけではありませんが、こちらの方が手軽であると思い採用しています。

```autohotkey:AutoHotkey.ahk
; 変数の初期化
#Include, %A_ScriptDir%\Variables.ahk
```

ちなみに、`Variables.ahk`は次のようになっています。
(`AutoHotkey.ahk`、`keybd_mouse.ahk`、`toggle.ahk`で利用します。)

```autohotkey:Variables.ahk
keys_all := "0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,vkBB,vkBA,vkBC,Space,Tab,Enter,BS,vkF3,vkF4,vk1D,vkF2,vkF0"

toggle := false
```

#### 3. プラグインの自動取り込み

プラグイン(=切り分けたスクリプトファイル)を、Auto-executeセクションにおいていちいち`Include`するのは、DRY的にもよろしくないため、`Plugins`ディレクトリ直下のスクリプトファイルを自動的に`Include`できるようにしておきました。

スクリプト実行時に`PluginList.ahk`に一覧が書き込まれ、最後にまとめて`Include`されます。

```autohotkey:AutoHotkey.ahk
; プラグインの検出・取り込み

If (search_plugins()) {
  Reload
}

search_plugins() {
  ; Pluginsフォルダ内のAHKスクリプト名を整形してplugin_filesに格納
  plugin_files := ""
  Loop, %A_ScriptDir%\Plugins\*.ahk {
    plugin_files .= "#" . "Include *i %A_ScriptDir%\Plugins\" . A_LoopFileName . "`n"
  }
  If (plugin_files == "") {
    Return 0
  }
  ; Pluginsの変更点を認識
  file := FileOpen(A_ScriptDir . "\PluginList.ahk", "r `n", "utf-8")
  If (file) {
    plugin_list_old := file.Read(file.Length)
    file.Close
    If (plugin_list_old == plugin_files) {
      Return 0
    }
  }
  ; plugin_list_oldをplugin_filesに書き換える
  file := FileOpen(A_ScriptDir . "\PluginList.ahk", "w `n", "utf-8")
  If (!file) {
    Return 0
  }
  file.Write(plugin_files)
  file.Close
  Return 1
}

; (Auto-exexute Sectionここまで)

#Include, %A_ScriptDir%\PluginList.ahk

```

#### 4. 共通サブルーチンの定義

切り分けたスクリプト群で自由に使いたい関数はこちらにまとめておきます。

##### ツールチップ関係

```autohotkey:AutoHotkey.ahk
my_tooltip_function(str, delay) {
  ToolTip, %str%
  SetTimer, remove_tooltip, -%delay%
}

remove_tooltip:
  ToolTip
Return

remove_tooltip_all:
  SetTimer, remove_tooltip, Off
  Loop, 20
  ToolTip, , , , % A_Index
Return
```

##### カレントディレクトリ取得
```autohotkey:AutoHotkey.ahk
get_current_dir() {
  explorerHwnd := WinActive("ahk_class CabinetWClass")
  If (explorerHwnd) {
    for window in ComObjCreate("Shell.Application").Windows {
      If (window.hwnd==explorerHwnd)
        Return window.Document.Folder.Self.Path
    }
  }
}
```

##### 動的なホットキー定義

```autohotkey:AutoHotkey.ahk
hotkeys_define(keys, label, OnOff) {
  Loop, PARSE, keys, `,
    Hotkey, %A_LoopField%, %label%, %OnOff%
  Return
}

; キー無効化用ラベル(Hotkeyコマンドでラベルとして指定する)
disable_keys:
Return
```

次のようにして動的にホットキーを定義できます。

```autohotkey
; 変数で指定したキーを無効化する

keys_all := "0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,vkBB,vkBA,vkBC,Space,Tab,Enter,BS,vkF3,vkF4,vk1D,vkF2,vkF0"

hotkeys_define(keys_all, "disable_keys", "On")
```

##### 改行コード除去

```autohotkey:AutoHotkey.ahk
rm_crlf(str) {
  str := RegExReplace(str, "\n", "")
  str := RegExReplace(str, "\r", "")
  Return str
}
```

### main.ahk

主要なスクリプトのうち、ファイルを切り分ける程の量でない場合、こちらにまとめます。

#### 英数キー(CapsLock)無効

`Ctrl`と入れ替える人は多いようですが、Lock系キーの動作が不安定であるため、AHKでは対応できません。
レジストリの書き換えをすればよい訳ですが、それではレジストリを書き換えられる環境のみで実現できる、環境依存性のあるキー配列となってしまうため、それはしないことにしています。

```autohotkey:main.ahk
vkF0::Return
```
#### デスクトップフォルダを開く

クイックアクセスよりデスクトップフォルダを開きたいことが多いため、Windowsの既定動作を上書きしています。

```autohotkey:main.ahk
#e::Run, %A_Desktop%
```

#### 音量関連

`Fn`キー頼りもまた機種依存的であるため、別途用意しました。

```autohotkey:main.ahk
; 単体押下の動作指定
AppsKey::Send,{AppsKey}

; ボリュームを上げる
AppsKey & Up::Send,{Volume_Up 1}

; ボリュームを下げる
AppsKey & Down::Send,{Volume_Down 1}

; ミュート
AppsKey & Left::Send,{Volume_Mute}
```

#### ツールチップ表示

本当にコピーできたか、上書き保存できたか、不安なことがよくあるため、ツールチップを表示します。

```autohotkey:main.ahk
; コピーした時

OnClipboardChange:
  my_tooltip_function("コピー", 300)
Return

; 上書き保存した時

^s::
  Send, ^s
  my_tooltip_function("上書き保存", 300)
Return
```

#### カレントディレクトリのパスを取得

関数は`AutoHotkey.ahk`(前述)で定義しています。

```autohotkey:main.ahk
^+!p::Clipboard := get_current_dir()
```

#### 日付入力

Excelの機能ですが、通常利用できるようにしました。

```autohotkey:main.ahk
^vkBB::
  FormatTime, dateStr, , yyyy-MM-dd
  Send, {vkF2}{vkF3}%dateStr%
Return
```

#### 最初のテキストフィールドに移動[Chromium]

Cent Browserに標準装備されている機能ですが、これが便利なのでAHKでポータブル化しました。

ポータブル化が目的であるため、意地でもブックマークレットは使いません。
アドレスバーに`javascript:`として、かんたんなjsコードを流し込みます。

正直使っておらず自己満ではありますが、技術的には面白いため掲載しました。

```autohotkey:main.ahk
#If, WinActive("ahk_exe chrome.exe")
  ^+u::
    ; ホールド対策
    KeyWait, Ctrl, Up
    KeyWait, Shift, Up
    stash := ClipBoardAll
    ClipBoard := "const element=document.querySelector(""input[type='text'],input[type='search']"");element.select();element.scrollIntoView();"
    ClipWait, 1
    Send, ^l
    ; "javascript:"は貼り付けできない
    Send, {vkF2}{vkF3}javascript:
    Sleep, 100
    Send, ^v
    Sleep, 100
    Send, {Enter}
    Sleep, 10
    ClipBoard := stash
    stash := ""
  Return
#If
```

### edit.ahk

AHKから離れられない理由の1つです。
超高速文字編集を可能にします。(当社比)
私の性格が反映されている配列になっています。

```autohotkey:edit.ahk
; ↑
vk1D & i::Send, {Blind}{Up}

; ↓
vk1D & k::Send, {Blind}{Down}

; ←
vk1D & j::Send, {Blind}{Left}

; →
vk1D & l::Send, {Blind}{Right}

; Home
vk1D & h::Send, {Blind}{Home}

; End
vk1D & vkBB::Send, {Blind}{End}

; ↑↑↑↑
vk1D & u::Send, {Blind}{Up 4}

; ↓↓↓↓
vk1D & ,::Send, {Blind}{Down 4}

; →→→→
vk1D & .::Send, {Blind}{Right 4}

; ←←←←
vk1D & m::Send, {Blind}{Left 4}

; Enter
vk1D & Space::Send, {Blind}{Enter}

; Backspace
vk1D & n::Send, {Blind}{Backspace}

; Delete
vk1D & /::Send,{Blind}{Delete}

; 行挿入(Ctrl押下状況により、挿入位置を前後で選択)
vk1D & Enter::
  If (GetKeyState("Ctrl", "P")) {
    Send, {Up}{End}{Enter}
  } Else {
    Send, {End}{Enter}
  }
Return

; 半角英数
vk1D & vkF2::Send, {vkF2}{vkF3}

```

### numpad.ahk

#### 出先PCで常に半角数字を入力する

AHKを持ち出したときに、利用するPCの設定によって数字の半角/全角が違ってしまうことを防ぐ。
既定値は大抵、テンキー入力の場合は日本語入力でも半角であることを利用

```autohotkey:numpad.ahk
1::Send, {Numpad1}
2::Send, {Numpad2}
3::Send, {Numpad3}
4::Send, {Numpad4}
5::Send, {Numpad5}
6::Send, {Numpad6}
7::Send, {Numpad7}
8::Send, {Numpad8}
9::Send, {Numpad9}
0::Send, {Numpad0}
```

#### 左手テンキー

`無変換`+`左ホームポジション`でテンキー(っぽい何か)を扱えます。
とても便利です。
テンキー付きキーボードに完全勝利しました。

```autohotkey:numpad.ahk
; 修飾キーと無変換の組み合わせのトリガーを有効にする
#If, !(GetKeyState("Ctrl", "P")
  || GetKeyState("Shift", "P")
    || GetKeyState("Alt", "P")
      || GetKeyState("LWin", "P"))

  vk1D & z::Send, 0
  vk1D & x::Send, 1
  vk1D & c::Send, 2
  vk1D & v::Send, 3
  vk1D & s::Send, 4
  vk1D & d::Send, 5
  vk1D & f::Send, 6
  vk1D & w::Send, 7
  vk1D & e::Send, 8
  vk1D & r::Send, 9
  vk1D & b::Send, =
  vk1D & a::Send, *
  vk1D & q::Send, {NumpadDiv}
  vk1D & g::Send, {+}
  vk1D & t::Send, -
  vk1D & Tab::Send, {Enter}
  vk1D & LWin::Send, .

#If
```

頭に何やら書いていますが、不便を感じるまでは内部のスッキリしている部分のみでOKです。

拡張性の都合により、条件を付与しています。

#### テンキーのマクロボード化(未設定)

左手テンキーを実現したため、テンキーをマクロボードとしたいところです。

まだ内容を考えていないため、とりあえず無効化してテンプレとしています。

```autohotkey:numpad.ahk
Numpad1::Return
Numpad2::Return
Numpad3::Return
Numpad4::Return
Numpad5::Return
Numpad6::Return
Numpad7::Return
Numpad8::Return
Numpad9::Return
NumpadDot::Return
NumpadAdd::Return
NumpadSub::Return
NumpadMult::Return
NumpadDiv::Return
NumpadEnter::Return
```

### fn.ahk

HHKB的に`Fn`キーを近づけます。
副次的に、`Fn`キーが全て空くためマクロキーとして活用できます。

`Alt`+`F4`
↓
`無変換`+`Alt`+`4`

```autohotkey:fn.ahk
vk1D & vkF3::Esc
vk1D & vkF4::Esc
vk1D & 1::F1
vk1D & 2::F2
vk1D & 3::F3
vk1D & 4::F4
vk1D & 5::F5
vk1D & 6::F6
vk1D & 7::F7
vk1D & 8::F8
vk1D & 9::F9
vk1D & 0::F10
vk1D & -::F11
vk1D & ^::F12
vk1D & \::F13
```

### hotstring.ahk

型だけ用意しました。

Hotstringは頻繁に抜けるため、あまり使いたくありません。
AHKがシングルスレッド動作(疑似マルチスレッド)であることが関係していると、勝手に予想しています。

```autohotkey:hotstring.ahk
#Hotstring *C Z
; C: 大文字小文字を区別する
; *: 終了文字無しで発動する
; Z : 発動時点でキー入力のバッファをクリアする
; R : 特殊キーをそのまま入力する(個別設定)

; 設定はここから
::teststring::
  Send, It's test.
Return
```

### others.ahk

個別のターゲットに対する、細々とした処理を記述します。

#### Netflix

Netflixで早送りする際、矢印を連打するとサムネイルだけ先に進むようになるのがうっとうしいので(伝われ)、その対策としてこちらを用意しました。

```autohotkey:others.ahk
#If, WinActive("Netflix")
  ~Right::
  ~Left::
    Send, {Space 2}
  Return
#If
```

### git_alias.ahk

複数のPCで同じエイリアスを使いたいため、GitのエイリアスはAHKに統合した方が都合がいい気がします。
(他にいい方法があればお教えください。)

```autohotkey:git_alias.ahk
#Hotstring C O Z
; C : 大文字小文字の区別
; O : 最後に終了文字を入力しない
; Z : 発動時点でキー入力のバッファをクリアする
; R : 特殊キーをそのまま入力する(個別設定)

; 基本
::gia::git add .
::giac::git commit -a -m ""{Left}
::gibr::git branch{Space}
::gibrd::git branch -d{Space}
::gic::git commit -m ""{Left}
::gica::git commit --amend -m ""{Left}
::gican::git commit --amend --no-edit
::gico::git checkout{Space}
::gicob::git checkout -b{Space}
::gicon::git config --global{Space}
::gist::git stash save --include-untracked ""{Left}
::gistl::git stash list
:R:gistp::git stash pop stash@{}
::gists::git stash show
::gireb::git rebase{Space}
::girebi::git rebase -i HEAD~
::giref::git reflog -7
::giress::git reset --soft HEAD
::giresh::git reset --hard HEAD
::gil::git log --oneline -5
::gim::git merge --no-ff{Space}
::gis::git status{Space}

; 応用
::gishbr::git show-branch | grep '*'
; :R:gishbr::git show-branch | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -1 | awk -F'[]~^[]' '{print $2}'
::gigr::git log --graph --date=short --decorate=short --pretty=format:'%Cgreen%h %Creset%cd %Cblue%cn %Cred%d %Creset%s'
```

### toggle.ahk

自分のスクリプトの中で、1,2を争う程便利だと思っています。

VSCodeの`Ctrl`+`K`→`何か`という2段階のショートカットのアイデアを得て、それをAHKで実装しました。

具体的には例えば、
* `変換`+`G`→`G`で選択文字列をGoogle検索
* `変換`+`G`→`L`でランチャーが起動して、`launcher_data.ini`に登録した動作を実行する
などがあります。

しかし、特大ボリュームで説明が大変なので、説明は省きます。
個人ブログの方で連載が追いついたら、こちらにリンクを張っておきます。

(以下、`toggle.ahk`を細分化して扱っています。)

#### 修飾キー単体動作の定義

```autohotkey:toggle.ahk
vk1C::Return
```

押下時間によって、変換キーと無効を切り替えてもいいかもしれません。
コンビネーションキーとして利用する場合は、比較的長時間押下していることになるためです。

ホットキーが発火していない場合は組み込み変数`A_ThisHotkey`に変化がないことから、変換キーを`Down`したときと`Up`したときの`A_ThisHotkey`が一致していれば変換キーとして動作する、としてもよさそうです。

今のところ変換キーを使いたいことはあまりないため、放置しています。

#### プライマリキーの設定

冗長で気に入らないので、何かいい案があればお教えください。

```autohotkey:toggle.ahk
#If, GetKeyState("vk1C", "P") == true
  a::
  b::
  c::
  d::
  e::
  f::
  g::
  h::
  i::
  j::
  k::
  l::
  m::
  n::
  o::
  p::
  q::
  r::
  s::
  t::
  u::
  v::
  w::
  x::
  y::
  z::
    toggle := A_ThisHotkey
    toggle_activation(toggle)
    SetTimer, watch_hotkey_done, 50

    hotkeys_define(keys_all, "disable_keys", "On")
  Return
#If
```

#### セカンダリキーの設定

例えば`変換`→`G`としてから、やっぱりやめたいとなったときに`ESC`で入力を破棄できるようにします。

```autohotkey:toggle.ahk
#If, toggle != false
  Esc::SetTimer, toggle_deactivation, Off
#If
```

##### AHK関連

`変換`+`A`の世界線です。(以下同様)

例えば`変換`+`A`→`D`ならAHKの公式ドキュメントをブラウザで開きます。

```autohotkey:toggle.ahk
#If, toggle == "a"
  ; コンパイラを開く
  c::Run, %A_ScriptDir%/Compiler/Ahk2Exe.exe

  ; Documentation
  d::Run, https://www.autohotkey.com/docs/AutoHotkey.htm

  ; KeyList(日本語Wiki)
  k::Run, http://ahkwiki.net/KeyList

  ; keybd_mouse.ahk
  m::
    Gosub, toggle_deactivation
    Gosub, keybd_mouse
  Return

  ; リロード
  r::Reload

  ; 実行ファイルのフォルダを開く
  o::Run, %A_ScriptDir%

  ; Wiki(日本語)を開く
  h::Run, http://ahkwiki.net/Top

  ; WindowSpy
  w::Run, %A_ScriptDir%\AutoHotkey.exe %A_ScriptDir%\WindowSpy.ahk
```

##### 文字編集

正直使っていないですが、拡張性が無限大なので書いておきました。

例えば、「上の行の偶数番目の文字を削除する」のような設定も自由にできます。

```autohotkey:toggle.ahk
#If, toggle == "e"
  ; 1行コピー
  c::Send, {Home}+{End}^c{Right}

  ; 1行削除
  e::
    Send, {End}+{Home}{Delete}
    Goto, toggle_deactivation
  Return

  ; 1行削除(キャレット～末尾)
  Delete::Send, +{End}{Delete}

  ; 1行削除(先頭～キャレット)
  Backspace::Send, +{Home}{Backspace}
```

##### GUI関連

かなり便利です。

```autohotkey:toggle.ahk
#If, toggle == "g"
```

###### DeepL検索

```autohotkey:toggle.ahk
  d::
    ; 多重起動防止
    If (WinExist("ahk_class AutoHotkeyGUI")) {
      Return
    }
    stash := ClipboardAll
    Clipboard :=
    Send, ^c
    ClipWait, 0.05
    clip := Clipboard
    Clipboard := stash
    clip := rm_crlf(clip)
    Gui, Add, Text, , 日本語→英語
    Gui, Add, Text, , (Shift)英語→日本語
    Gui, Add, Edit, v_str_deepl w380, %clip%
    Gui, Add, Button, Default, Translate
    Gui, Show, Center w400, DeepL
    Send, {vkF2}
    clip := ""
    Return
    ButtonTranslate:
      Gui, Submit
      ; Shift押下時はja->en
      If GetKeyState("Shift", "P") {
        Run, https://www.deepl.com/translator#ja/en/%_str_deepl%
      ; 直打ちならばen->ja
      } Else {
        Run, https://www.deepl.com/translator#en/ja/%_str_deepl%
      }
    4GuiEscape:
    4GuiClose:
      Gui, Destroy
  Return
```

###### 新しいブランクファイルを作成

超便利です。

通常はWindowsエクスプローラ上で`新規作成`→`テキストファイル`などとして、`.txt`を削除するのだろうと思いますが、この方法なら
`変換`+`G`
↓
`L`
↓
`(ファイル名)`
↓
`Enter`

で自由なファイルを作成できます。

```autohotkey:toggle.ahk
  f::
    ; エクスプローラがアクティブでなければ中断
    If (!WinActive("ahk_class CabinetWClass")) {
      MsgBox, エクスプローラがアクティブではありません
      Return
    }
    ; 現在表示中のディレクトリ
    current_dir := get_current_dir()
    ; ファイルを生成(重複しない名前)
    Gui, Add, Edit, v_str_filename w380
    Gui, Add, Button, Default, Append
    Gui, Show, Center w400, ファイル名
    Send, {vkF2}{vkF3}
    Return
    ButtonAppend:
    Gui, Submit
    FileAppend, , %current_dir%\%_str_filename%
    3GuiEscape:
    3GuiClose:
      Gui, Destroy
  Return
```

###### Google検索

```autohotkey:toggle.ahk
  g::
    ; プライマリキーとセカンダリキーが同じ場合に必要
    Gosub, toggle_deactivation

    ; 多重起動防止
    If (WinExist("ahk_class AutoHotkeyGUI")) {
      Return
    }
    stash := ClipboardAll
    Clipboard :=
    Send, ^c
    ClipWait, 0.05
    clip := Clipboard
    Clipboard := stash
    clip := rm_crlf(clip)
    Gui, Add, Edit, v_str_google w380, %clip%
    Gui, Add, Button, Default, Search
    Gui, Show, Center w400, Google
    Send, {vkF2}
    clip := ""
    Return
    ButtonSearch:
      Gui, Submit
      Run, https://www.google.co.jp/search?q=%_str_google%
    2GuiEscape:
    2GuiClose:
      Gui, Destroy
  Return
```

###### ランチャーを起動

`/Plugins/launcher_data.ini`の記述を`Run`できます。

`Run`はWebページを開いたり、ファイルを実行したりできる便利なコマンドです。
もちろんPowerShellスクリプト等も実行できるため、つまるところAHKの守備範囲を超えたすべての動作を定義できます。

```autohotkey:toggle.ahk
  ; def:launcher
  l::
    ; 多重起動防止
    If (WinExist("ahk_class AutoHotkeyGUI")) {
      Return
    }
    launcher_head:
    Gui, Add, Edit, v_str_launcher Lowercase
    Gui, Add, Button, Default, Launch
    Gui, Show, Center AutoSize, Launcher
    Send, {vkF2}{vkF3}
    Return
    ButtonLaunch:
      Gui, Submit
      IniRead, val, %A_ScriptDir%\Plugins\launcher_data.ini, Scripts, %_str_launcher%
      ; 一致するレコードがなければval="ERROR"となることを利用する
      If (val != "ERROR") {
        Run, %val%
      } Else {
        GoSub, GuiClose
        Goto, launcher_head
      }
    GuiEscape:
    GuiClose:
      Gui, Destroy
  Return
```

```autohotkey:launcher_data.ini
[Scripts]
codeahk = F:\Desktop\Tools\Source\AutoHotkey\ahk.code-workspace
fstartup = shell:startup
notion = https://www.notion.so
```

###### 「GUI関連」を閉じる

```autohotkey:toggle.ahk
#If
```

#### 使用するサブルーチン

```autohotkey:toggle.ahk
; セカンダリキー入力待ちにし、タイムアウトをSetTimerする
toggle_activation(toggle) {
  time_limitation := 2000
  SetTimer, toggle_deactivation, -%time_limitation%
  my_tooltip_function("vk1C + " . toggle . " -> ?", time_limitation)
}

toggle_deactivation:
  toggle := false
  my_tooltip_function("toggle == false", 1000)
  hotkeys_define(keys_all, "disable_keys", "Off")
  SetTimer, toggle_deactivation, Off
  SetTimer, watch_hotkey_done, Off
Return

; セカンダリキーの入力があった場合、toggleをfalseにし、SetTimerしたタイムアウト設定を解除する
watch_hotkey_done:
  new_ThisHotkey := A_ThisHotkey

  ; toggleにはプライマリキー送信時のA_ThisHotkeyが格納されている
  ; 何らかのホットキー(つまりセカンダリキー)が実行されたとき、A_ThisHotkeyが書き換わることを利用する
  If (new_ThisHotkey != toggle)
    Goto, toggle_deactivation
Return
```


### keybd_mouse.ahk

キーボードでマウスを操作します。
なくてもいいですが、たまに便利な場面があります。
(文字入力中にマウスカーソルが邪魔な場合や、無線キーボードしか手元にない場合など)

`MouseMove`ではキー長押しのレスポンスが遅いため、`While`で強引に処理しています。
しかし、AHKがシングルスレッド動作であるせいでカクつくのが悩みです。

`Critical`で処理の乗っ取りを防ぐために、切り分けてある関数群を`While`内にベタ打ちする方法も検討中です。

こちらも特大ボリュームなので、ここでの解説は避けます。

```autohotkey:keybd_mouse.ahk
keybd_mouse:
  ; キー設定///////////////////////////////////
  exit_this       := "r"      ; keybd_mouse終了

  mouse_up        := "e"      ; ↑
  mouse_down      := "d"      ; ↓
  mouse_left      := "s"      ; ←
  mouse_right     := "f"      ; →
  mouse_LB        := "j"      ; 左クリック
  mouse_RB        := "l"      ; 右クリック
  mouse_MB        := "vkBC"   ; 中クリック
  scroll_up       := "i"      ; 上スクロール
  scroll_down     := "k"      ; 下スクロール

  accel_key       := "o"      ; カーソル加速
  decel_key       := "vkBB"   ; カーソル減速

  default_speed   := 4        ; 規定のカーソル移動速度
  accel_vol       := 16       ; accelKey押下時のカーソル移動速度の増加量
  slow_vol        := 1        ; decel_key押下時のカーソル移動速度
  move_ratio      := 16/9     ; 縦横移動量倍率

  ;////////////////////////////////////////////

  hotkeys_define(keys_all, "disable_keys", "On")
  Hotkey, %exit_this%, toggle_keybd_mouse

  Gosub, toggle_keybd_mouse

  SetTimer, mouse_button_checker, 100

  While (toggle_keybd_mouse == true) {
    ; 速度設定///////////////////////////
    speed := default_speed
    move_X := 0
    move_Y := 0

    If (GetKeyState(accel_key, "P"))
      speed += accel_vol
    If (GetKeyState(decel_key, "P"))
      speed := slow_vol
    ;////////////////////////////////////

    ; 移動方向設定///////////////////////
    If (GetKeyState(mouse_up, "P"))
      move_Y += speed
    If (GetKeyState(mouse_down, "P"))
      move_Y += -speed
    If (GetKeyState(mouse_left, "P"))
      move_X += -speed * move_ratio
    If (GetKeyState(mouse_right, "P"))
      move_X += speed * move_ratio
    ;////////////////////////////////////

    ; 移動///////////////////////////////
    MouseMove, move_X, -move_Y, 0, R
    ; ///////////////////////////////////
  }

  SetTimer, mouse_button_checker, Off

  hotkeys_define(keys_all, "disable_keys", "Off")
  Hotkey, %exit_this%, toggle_keybd_mouse, Off
Return


;////////////////////////////////////////////
;サブルーチン////////////////////////////////
;////////////////////////////////////////////

; トグル/////////////////////////////////////
toggle_keybd_mouse:
  toggle_keybd_mouse := !toggle_keybd_mouse

  my_tooltip_function("マウスモード: " . (toggle_keybd_mouse == true ? "ON" : "OFF"), 1000)

  ; タスクバーの高さを取得
  WinGetPos, , , , taskbarHeight, ahk_class Shell_TrayWnd

  ; 右下にツールチップ
  CoordMode, ToolTip, Screen
  ToolTip, % "マウスモード: " . (toggle_keybd_mouse == true ? "ON" : "OFF")
      ,A_ScreenWidth, A_ScreenHeight - (taskbarHeight + 21), 2  ; ツールチップの高さ: 20
  CoordMode, ToolTip, Relative

  ; OFFにするならそのツールチップは指定時間後に削除
  If (toggle_keybd_mouse == false)
    SetTimer, remove_tooltip_all, -500
Return
;////////////////////////////////////////////


; マウス/////////////////////////////////////
mouse_button_checker:
  ; マウス/////////////////////////////
  keybd_mouse_click(mouse_LB, "L")
  keybd_mouse_click(mouse_MB, "M")
  keybd_mouse_click(mouse_RB, "R")
  keybd_mouse_scroll(scroll_up, "Up", accel_key, decel_key, accel_vol)
  keybd_mouse_scroll(scroll_down, "Down", accel_key, decel_key, accel_vol)
  ;////////////////////////////////////
Return


keybd_mouse_click(key, button) {
  ;引数について////////////////////////
  ;// key:= "j", button:= "L" の場合 //
  ;// j を押すと LButtonが送信される //
  ;////////////////////////////////////

  ; 変数%button%B_downの値を引き継ぐ
  ; %button%B_downは直前に押したか離したかを記録
  Global

  ; 上記変数を用いた連打対策///////////
  ; 押すとき
  If (GetKeyState(key, "P") == true) {
    If (%button%B_down != true) {
      Send, {Blind}{%button%Button Down}
      %button%B_down := true

      ; 押下したら一瞬カーソルを止める
      Sleep, 150
    }
  ; 離すとき
  } Else {
    If (%button%B_down == true) {
      Send, {Blind}{%button%Button Up}
      %button%B_down := false
    }
  }
  ;////////////////////////////////////
}


keybd_mouse_scroll(key, scroll, accel_key, decel_key, accel_vol) {
  While (GetKeyState(key, "P")) {
    ; スクロール中はカーソルを固定
    If (GetKeyState(key, "P"))
      Send, {Blind}{Wheel%scroll%}

    ; スクロール速度の設定
    scroll_wait := 100
    If (GetKeyState(accel_key, "P"))
      scroll_wait -= accel_vol * 5
    If (GetKeyState(decel_key, "P"))
      scroll_wait := 200

    Sleep, scroll_wait
  }
}
;////////////////////////////////////////////

```

### temp.ahk

思いついたコードのテストをする場所です。

ちょっと試したいだけなら`AutoHotkey Plus`を活かしつつ別AHKファイルに書く方が便利ですが、短期間継続的に試したいコードがあれば、`temp.ahk`に書くのが便利だろうと思います。

### IME.ahk

`IME.ahk`は有志が作成したプラグインです。
あまり依存関係を作りたくないため使用は避けていますが、いざというときのために配備しておきました。

https://github.com/karakaram/alt-ime-ahk


## 管理者権限の付与

タスクマネージャその他、管理者権限を必要とするプログラム上でAHKを動作させるためには、AHK自体に管理者権限を付与する必要があります。

下記を参照されるとよいでしょう。

<https://qiita.com/ryoheiszk/items/97618dc67360c0a1d963>

## まとめ

ここまでカスタマイズしてしまうと、Linuxアレルギーな副作用が生じますので、あまりおすすめできません。

Qiitaでは、備忘録、またエンジニア向けというつもりで説明を省いていますが、個人ブログの方ではしっかり説明しようと思っています。

遊びに来てください。

[【連載】なぜAutoHotkeyを使うのか](https://ashiatoblog.com/posts/pc/autohotkey/ahk1/)

---

別のWindowsマシンで即時にAHKその他常駐ソフト環境を展開する方法があります。

Linuxにおけるdotfiles相当の手法で、そのPCの環境を汚すことなく、30秒程度で設定可能です。

文章による解説が困難であったため動画となっていますが、視聴後試されることをおすすめします。

<https://www.youtube.com/watch?v=IAKbhnj8J14>