2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-10

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

動機

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

実装

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

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

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

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

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

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

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ディレクティブの下にある場合、式が評価され、式に関数が含まれればコールされる。

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

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 Remarks参照)
言い換えれば、ある条件で発動するホットキーにおける共通の処理は、条件式を構成する関数に仕込んでおけばよいということになる。

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

カーソル下のウィンドウ情報判定後に別途アクティベートする

問題点としては、やはりGUI的な操作を入れると動作が重くなってしまう。

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

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

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をまとめてしまうという手も考えられる。

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と大差なく行える。

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?