[AutoHotKey]マウスカーソルが非アクティブな指定ウィンドウ上にある場合のホットキー


ホットキーというか、マウスジェスチャとかカーソルジェスチャ寄りの話。
非アクティブウィンドウのクリックで発動するやつ。

## 動機
- Chromeでプレイするブラウザゲーで、右クリックをリロードに設定している
- 対象ウィンドウがアクティブでないときも、そのウィンドウを右クリックしたらリロードしてほしい

## 実装
- [\[AutoHotKey\]マウスカーソル位置に基づく文脈依存ホットキー \- Qiita](https://qiita.com/catfist/items/64ead24e81e65e680b56)

上記記事で触れたように、`MouseGetPos`コマンドでマウスカーソル直下のウィンドウ情報を取得し、`#If`ディレクティブで評価することにより、マウスカーソル位置に依存したホットキーを実装することができる。

- [MouseGetPos \- AutoHotkey Wiki](http://ahkwiki.net/MouseGetPos)

`MouseGetPos`コマンドで取得できる情報は、ウィンドウハンドルおよびコントロールのClassNNである。
しかし、Chromeはブラウザゲーだけに使うわけではないし、Chromium系アプリなども同値を返してくるので、ここはウィンドウタイトルで判定したい。

ウィンドウハンドルをウィンドウタイトルに結びつけるには、`WinGetTitle`コマンドで、対象ウィンドウを`MouseGetPos`で取得したウィンドウハンドルから求めればよい。

以下は、マウスカーソルが**グランブルーファンタジーを開いているChrome**上にあるかどうかを評価する関数である。

```txt:sample.ahk
MouseIsOverGBF() {
	GBFTitle = グランブルーファンタジー - Google Chrome
	MouseGetPos, , ,
	MouseGetPos, , , MouseOverHundle, , 1 ; カーソル下のウィンドウハンドルを取得
	WinGetTitle, MouseOverTitle, ahk_id %MouseOverHundle% ; ウィンドウハンドルからタイトルを取得
	Return, (MouseOverTitle = GBFTitle) ; 変数を比較し真偽値を関数の返り値とする
}
```

これで、以下のような指定をすれば、アクティブウィンドウに依存せず、カーソル下のウィンドウのみに依存したホットキーを実装できるが…

```txt:sample.ahk
MouseIsOverGBF() {
	GBFTitle = グランブルーファンタジー - Google Chrome
	MouseGetPos, , , MouseOverHundle, , 1 ; カーソル下のウィンドウハンドルを取得
	WinGetTitle, MouseOverTitle, ahk_id %MouseOverHundle% ; ウィンドウハンドルからタイトルを取得
	Return, (MouseOverTitle = GBFTitle) ; 変数を比較し真偽値を関数の返り値とする
}

#If MouseIsOverGBF()
{RButton}::
	Send, {F5}
	Return
```

しかし、`Send`コマンドによるキーコードはアクティブウィンドウに対して送られてしまう。
`ControlSend`コマンドも試したが、Chromeの対象ウィンドウがアクティブでないと動作しなかった。

よって、`WinActivate`（あるいは`ControlFocus`）コマンドでアクティブウィンドウを切り替える必要があるが、問題は、`WinActivate`をどこで実行するか。
ホットキーラベルの中に仕込めばよさそうだが、そうすると全てのホットキーラベルに同じことを書く必要が出てくる。

どうするか。

### 判定に用いる関数でウィンドウ切り替えを行う
ホットキーを入力して、それが`#If`ディレクティブの下にある場合、式が評価され、式に関数が含まれればコールされる。

ならば、その関数にウィンドウ切り替えを仕込めばいい。

```txt:sample.ahk
; カーソルがグラブルのウィンドウ上にあるかを判定する関数
MouseIsOverGBF() {
	GBFTitle = グランブルーファンタジー - Google Chrome	MouseGetPos, , ,
	ChromeControl = Chrome_RenderWidgetHostHWND1 ; Chromeのタイトルバー等を除いた表示部分
	MouseGetPos, , , MouseOverHundle, MouseOverControl, 1 ; カーソル下のウィンドウハンドルおよびコントロール名を取得
	WinGetTitle, MouseOverTitle, ahk_id %MouseOverHundle% ; ウィンドウハンドルからタイトルを取得
    IfWinNotActive, ahk_id %MouseOverHundle% ; カーソル下のウィンドウがアクティブでない場合
	{
		WinActivate, ahk_id %MouseOverHundle% ; カーソル下のウィンドウをアクティベート
		WinWaitActive, ahk_id %MouseOverHundle% ; アクティベート完了待ち
	}
	Return, (MouseOverControl = ChromeControl and MouseOverTitle = GBFTitle) ; 変数を比較し真偽値を関数の返り値とする
}

#If MouseIsOverGBF() ; 返り値が真である場合
RButton::F5
```

カーソルが、グラブルを開いているChromeのレンダラ部分にある場合のみホットキーが動作する。

関数コールが終了してからホットキーラベルの処理が行われるため、**現在の**アクティブウィンドウに対してキーコードが送られることはない。
`WinWaitActive`を入れると確実だが、若干遅くなるので、なんならなくてもいいと思う。
グラブルを開いているChromeのウィンドウが複数ある場合でも、ウィンドウハンドルは各ウィンドウごとにユニークであるため期待通り動作する。

ただし、**ドロップダウンリストにはコントロール名がない**ため、マウスがドロップダウンリストにある場合は動作しない。具体的にはショップ交換等での数量選択。

また、`#If`ディレクティブでの関数コールは、その下に記述されたホットキー（ないしホットストリング）が入力された場合にのみ行われる。
よって、GUI的な操作やファイル操作などを仕込んでも、スクリプト実行時やスクリプトリロード時に暴発することはない。
（[\#If \- AutoHotkey Wiki](http://ahkwiki.net/-If)　Remarks参照）
言い換えれば、**ある条件で発動するホットキーにおける共通の処理は、条件式を構成する関数に仕込んでおけばよい**ということになる。

これで、ホットキーラベルを増やしても、いちいちアクティベート処理を書く必要はない。

### カーソル下のウィンドウ情報判定後に別途アクティベートする
問題点としては、やはりGUI的な操作を入れると動作が重くなってしまう。

先のコードだと、右クリックするときはいつでも全部`WinActivate`が走ってしまうのがいけない。
それは、カーソルがグラブルのウィンドウ上にあり、かつアクティブでない場合のみでよい。

とすれば、**判定に用いる関数を分ければいい。**

```txt:sample.ahk
; カーソルがグラブルのウィンドウ上にあるかを判定する関数
MouseIsOverGBF() {
	Global ; 関数内の変数を全てグローバル変数として宣言
	GBFTitle = グランブルーファンタジー - Google Chrome	MouseGetPos, , ,
	ChromeControl = Chrome_RenderWidgetHostHWND1 ; Chromeのタイトルバー等を除いた表示部分
	MouseGetPos, , , MouseOverHundle, MouseOverControl, 1 ; カーソル下のウィンドウハンドルおよびコントロール名を取得
	WinGetTitle, MouseOverTitle, ahk_id %MouseOverHundle% ; ウィンドウハンドルからタイトルを取得
	Return, (MouseOverControl = ChromeControl and MouseOverTitle = GBFTitle) ; 変数を比較し真偽値を関数の返り値とする
}
; カーソル下のウィンドウが非アクティブである場合にアクティベートする関数
UnderActivate()
{
	Global ; 関数内の変数を全てグローバル変数として宣言
    IfWinNotActive, ahk_id %MouseOverHundle% ; カーソル下のウィンドウがアクティブでない場合
	{
		WinActivate, ahk_id %MouseOverHundle% ; カーソル下のウィンドウをアクティベート
	}
	Return, 1 ; 真を返す
}

#If MouseIsOverGBF() and UnderActivate()
RButton::F5
```

ポイントは、先にカーソル位置による判定を行って、対象外ウィンドウを除外してから、アクティベートを行うこと。
`and`を含む式においては、左側が真である場合のみ右側の処理が行われるため、グラブル以外のウィンドウを右クリックした場合には、`UnderActivate()`はコールされない。

`UnderActivate()`は常に真を返すので、`MouseIsOverGBF()`が真であればホットキーは動作する。

`UnderActivate()`ではウィンドウハンドルを取得していないが、`MouseIsOverGBF()`でグローバル変数に格納してあるので参照できる。

これで、必要な場合のみ、自動的にウィンドウ切り替えを行えるようになった。

### ウィンドウ切り替え＋Sendを関数化
記述をシンプルにする方法としては、ウィンドウ切り替えと`Send`をまとめてしまうという手も考えられる。

```txt:sample.ahk
; カーソルがグラブルのウィンドウ上にあるかを判定する関数
MouseIsOverGBF() {
	Global ; 関数内の変数を全てグローバル変数として宣言
	GBFTitle = グランブルーファンタジー - Google Chrome	MouseGetPos, , ,
	ChromeControl = Chrome_RenderWidgetHostHWND1 ; Chromeのタイトルバー等を除いた表示部分
	MouseGetPos, , , MouseOverHundle, MouseOverControl, 1 ; カーソル下のウィンドウハンドルおよびコントロール名を取得
	WinGetTitle, MouseOverTitle, ahk_id %MouseOverHundle% ; ウィンドウハンドルからタイトルを取得
	Return, (MouseOverControl = ChromeControl and MouseOverTitle = GBFTitle) ; 変数を比較し真偽値を関数の返り値とする
}
; カーソル下のウィンドウが非アクティブである場合にアクティベートしてからSendする関数
SendForce(Keys)
{
	Global ; 関数内の変数を全てグローバル変数として宣言
    IfWinNotActive, ahk_id %MouseOverHundle% ; カーソル下のウィンドウがアクティブでない場合
	{
		WinActivate, ahk_id %MouseOverHundle% ; カーソル下のウィンドウをアクティベート
	}
	Send, %Keys%
}

#If MouseIsOverGBF()
RButton::SendForce("F5")
```

これなら、記述・修正も通常の`Send`と大差なく行える。