AutoHotkeyの「OnClipBoardChange」誤検知?対処方法

AutoHotkeyに搭載されている、クリップボード更新検知サブルーチン「OnClipBoardChange」の誤検知?対処方法について、私自身の覚書を兼ねて公開します。

最初に「OnClipBoardChange」の仕組みを確認しておきましょう。

以下、AutoHotkey Wikiからの引用です。

OnClipboardChange という名前のラベルを作成しておくと、何らかのアプリケーションによってWindowsのクリップボードが変更されたときにそのラベルが実行されるようになる。

つまり、「OnClipBoardChange」を使うことで、AutoHotkeyが自動的にクリップボードを監視してくれる、というわけです。

使い方としては、クリップボードが変更されたときに実行させたい処理(クリップボードの履歴を取るなど)を「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\ZTZ`e-‘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
<広告>

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です


*


このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください