ホットキーというか、マウスジェスチャとかカーソルジェスチャ寄りの話。
非アクティブウィンドウのクリックで発動するやつ。
動機
- Chromeでプレイするブラウザゲーで、右クリックをリロードに設定している
- 対象ウィンドウがアクティブでないときも、そのウィンドウを右クリックしたらリロードしてほしい
実装
上記記事で触れたように、MouseGetPos
コマンドでマウスカーソル直下のウィンドウ情報を取得し、#If
ディレクティブで評価することにより、マウスカーソル位置に依存したホットキーを実装することができる。
MouseGetPos
コマンドで取得できる情報は、ウィンドウハンドルおよびコントロールのClassNNである。
しかし、Chromeはブラウザゲーだけに使うわけではないし、Chromium系アプリなども同値を返してくるので、ここはウィンドウタイトルで判定したい。
ウィンドウハンドルをウィンドウタイトルに結びつけるには、WinGetTitle
コマンドで、対象ウィンドウをMouseGetPos
で取得したウィンドウハンドルから求めればよい。
以下は、マウスカーソルがグランブルーファンタジーを開いているChrome上にあるかどうかを評価する関数である。
MouseIsOverGBF() {
GBFTitle = グランブルーファンタジー - Google Chrome
MouseGetPos, , ,
MouseGetPos, , , MouseOverHundle, , 1 ; カーソル下のウィンドウハンドルを取得
WinGetTitle, MouseOverTitle, ahk_id %MouseOverHundle% ; ウィンドウハンドルからタイトルを取得
Return, (MouseOverTitle = GBFTitle) ; 変数を比較し真偽値を関数の返り値とする
}
これで、以下のような指定をすれば、アクティブウィンドウに依存せず、カーソル下のウィンドウのみに依存したホットキーを実装できるが…
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
ディレクティブの下にある場合、式が評価され、式に関数が含まれればコールされる。
ならば、その関数にウィンドウ切り替えを仕込めばいい。
; カーソルがグラブルのウィンドウ上にあるかを判定する関数
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 Remarks参照)
言い換えれば、ある条件で発動するホットキーにおける共通の処理は、条件式を構成する関数に仕込んでおけばよいということになる。
これで、ホットキーラベルを増やしても、いちいちアクティベート処理を書く必要はない。
カーソル下のウィンドウ情報判定後に別途アクティベートする
問題点としては、やはりGUI的な操作を入れると動作が重くなってしまう。
先のコードだと、右クリックするときはいつでも全部WinActivate
が走ってしまうのがいけない。
それは、カーソルがグラブルのウィンドウ上にあり、かつアクティブでない場合のみでよい。
とすれば、判定に用いる関数を分ければいい。
; カーソルがグラブルのウィンドウ上にあるかを判定する関数
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
をまとめてしまうという手も考えられる。
; カーソルがグラブルのウィンドウ上にあるかを判定する関数
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
と大差なく行える。