AutoHotkeyの「OnClipBoardChange」誤検知?対処方法
AutoHotkeyに搭載されている、クリップボード更新検知サブルーチン「OnClipBoardChange」の誤検知?対処方法について、私自身の覚書を兼ねて公開します。
最初に「OnClipBoardChange」の仕組みを確認しておきましょう。
以下、AutoHotkey Wikiからの引用です。
OnClipboardChange という名前のラベルを作成しておくと、何らかのアプリケーションによってWindowsのクリップボードが変更されたときにそのラベルが実行されるようになる。
つまり、「OnClipBoardChange」を使うことで、AutoHotkeyが自動的にクリップボードを監視してくれる、というわけです。
使い方としては、クリップボードが変更されたときに実行させたい処理(クリップボードの履歴を取るなど)を「OnClipBoardChange」ラベルの中に記述することになります。
OnClipBoardChangeの使用方法
OnClipBoardChange:
[ここにクリップボードが変更されたときの処理を記述する]
Return
こうすることで、クリップボードが変更されるたびに、「OnClipBoardChange」内に記述した処理を実行してくれるわけです。
「OnClipBoardChange」の誤検知?とは
ところが、この「OnClipBoardChange」には、誤検知なのか何なのか、ともかく不思議な挙動があります。
それは、何らかのアプリケーションを起動または終了した時に、クリップボードは変更されていないにもかかわらず、変更ありと判定されて「OnClipBoardChange」が実行されてしまうという内容です。
私が確認できたものは以下のとおり。
誤検知を確認したもの
- タスクスケジューラーなどの管理ツール起動時
- MassiGra起動時
- ID Manager終了時
- OpenOffice Calcの起動および終了時
- TeraPad起動時
何度もスクリプトを確認しましたが、「OnClipBoardChange」の使用方法に誤りはありませんでした。
正直、誤検知なのか、アプリの行儀が悪いのか判断しかねるところですが、ともかくクリップボードを変更してないのにもかかわらず「OnClipBoardChange」が実行されるケースがあるのです。
これは困りますね。
ということで、次のように対処しました。
対処1:クリップボードのシーケンス番号をチェックする
クリップボードのシーケンス番号とは、クリップボードが変更されるたびに更新される一連番号のことです。
色々検証した結果「OnClipBoardChange」の誤検知と思わしき場合には、シーケンス番号は変化してしませんでした。
そのため「OnClipBoardChange」内で、シーケンス番号の内容をチェックすれば、誤検知の回避が可能です。
対処2:ウィンドウ数の変化を検知する
しかし、対処1をすり抜けるアプリが存在するので、別の方法も合わせて実施する必要があります。
具体的には、OpenOffice Calcの画面内で何らかのテキストをコピーし、貼り付けないまま終了すると、シーケンス番号が更新されてしまうため、対処1をすり抜けてしまうのです。
これを回避するために「OnClipBoardChange」が起動した時点で、ウィンドウ数が変化していないか確認することにしました。
ただし「ウィンドウ数が変化していたら誤検知」とすると、ユーザーが実際にウインドウを閉じた後にテキストをコピーした場合も誤検知と判断してしまいます。
ですから、ウィンドウ数が変化して500ミリ秒未満にシーケンス番号が更新された場合には誤検知である、とみなすことにしました。
「何らかのウィンドウを閉じる→他のウィンドウでテキストをコピーする」という操作を、手作業で0.5秒以内に完了させるのは無理だろうという想定です。
スクリプトの実装
上記対処1,2を実装したのが以下のスクリプトです。
スクリプト例
#Persistent
;===== ウィンドウ数が変化したら変化した日時を取得する
;https://sites.google.com/site/agkh6mze/howto/winevent のスクリプトを拝借
;
myFunc := RegisterCallback(“WinActivateHandler”)
myHook := DllCall(“SetWinEventHook”
, “UInt”, 0x00000003 ; eventMin : EVENT_SYSTEM_FOREGROUND
, “UInt”, 0x00000003 ; eventMax : EVENT_SYSTEM_FOREGROUND
, “UInt”, 0 ; hModule : self
, “UInt”, myFunc ; hWinEventProc :
, “UInt”, 0 ; idProcess : All process
, “UInt”, 0 ; idThread : All threads
, “UInt”, 0x0003 ; dwFlags : WINEVENT_SKIPOWNTHREAD | WINEVENT_SKIPOWNPROCESS
, “UInt”)
Global NowWinCnt := 0
Global PrvWinCnt := 0
Global ActWinChgTime := 0
WinGet NowWinCnt,Count,,,5eZg~G+X9)JAIofIJBgcIe\ZTZe-'DpmO ;---------- アクティブウィンドウが変化したときに起動する関数 WinActivateHandler(hWinEventHook, event, hwnd, idObject, idChild, thread, time) { ;---------- ウィンドウ数を退避 PrvWinCnt := NowWinCnt ;---------- 現在開いているウィンドウ数を取得 WinGet NowWinCnt,Count,,,5eZg~G+X9)JAIofIJBgcIe\ZTZ
e-‘DpmO
;———- ウィンドウ数が変化していたら日時を取得する
If (PrvWinCnt == NowWinCnt) {
ActWinChgTime := 0
}Else{
ActWinChgTime = %A_Now%%A_MSec%
}
}
;===== クリップボードのシーケンス番号を取得する
BefClipSN := DllCall(“GetClipboardSequenceNumber”)
;===== AutoExec終了
Return
;===== クリップボードが変化した時の処理
OnClipboardChange:
;—– クリップボードのシーケンス番号を取得する
CurClipSN := DllCall(“GetClipboardSequenceNumber”)
;—– ウィンドウ数が変化しているか?
If (ActWinChgTime > 0) {
;—– ウィンドウ数が変化しているときは変化した日時とクリップボードが変化した日時の差を求める
ClipChgTime = %A_Now%%A_MSec%
DelayTime := ClipChgTime – ActWinChgTime
}Else{
DelayTime := 1000
}
;—– クリップボードがテキスト形式で内容が更新されたときだけ処理する
If (A_EventInfo == 1 && CurClipSN > BefClipSN && DelayTime > 500) {
;ここにクリップボードが変化した時の処理を記述する
}
Return