web-dev-qa-db-ja.com

アプリケーションがフォーカスを盗むのを防ぐ

アプリケーションがアクティブウィンドウからフォーカスを盗むのを防ぐための解決策はありますか?

これは、私がアプリケーションを起動し、他のことを実行するように切り替えると、特に面倒です。新しいアプリケーションは、半文のテキストを受け取り始めます。

190
svandragt

これは、Windowsの内部構造を広範囲に操作しなければ不可能であり、それを乗り越える必要があります。

それはあなたがオペレーティングシステムがあなたが別のことをすることを可能にする前にあなたが1つの行動をとることが本当に重要であるとき日常的なコンピュータの使用に瞬間があります。そのためには、特定のウィンドウに焦点を絞る必要があります。 Windowsでは、この動作の制御は、使用する個々のプログラムの開発者に大部分が任されています。

このトピックに関しては、すべての開発者が正しい判断を下すわけではありません。

私はこれが非常にイライラすることと迷惑であることを知っています、しかしあなたはあなたのケーキを持ってそれを食べることもできません。日常生活の中で、フォーカスが特定のUI要素に移動したり、フォーカスがロックされたままであることを要求しているアプリケーションに問題がなくなったりしている場合は、おそらく多くの場合があります。しかし、たいていのアプリケーションは、現在誰が主導権を握っているかを判断することになるとやや同等であり、システムは決して完璧ではあり得ません。

しばらく前に、私はこの問題を解決するための徹底的な調査を一度もしました(そして失敗しました)。私の研究の結果は 迷惑プロジェクトページ で見つけることができます。

このプロジェクトには、次のことを繰り返してフォーカスを取得しようとするアプリケーションも含まれています。

switch( message ) {
  case WM_TIMER:
    if( hWnd != NULL ) {
      // Start off easy
      // SetForegroundWindow will not move the window to the foreground,
      // but it will invoke FlashWindow internally and, thus, show the
      // taskbar.
      SetForegroundWindow( hWnd );

      // Our application is awesome! It must have your focus!
      SetActiveWindow( hWnd );

      // Flash that button!
      FlashWindow( hWnd, TRUE );
    }
    break;

このスニペットからわかるように、私の研究では、ユーザーインターフェイスの動作の他の側面にも焦点が当てられていませんでした。

私がこれを解決しようとしたのは、すべての新しいプロセスにDLLをロードし、別のウィンドウをアクティブにする原因となるAPI呼び出しをフックすることでした。
最後の部分は簡単な部分です。素晴らしいAPIがそこにライブラリをフックしているためです。私はとても素晴らしい mhookライブラリ を使いました。

#include "stdafx.h"
#include "mhook-2.2/mhook-lib/mhook.h"

typedef NTSTATUS( WINAPI* PNT_QUERY_SYSTEM_INFORMATION ) ( 
  __in       SYSTEM_INFORMATION_CLASS SystemInformationClass,     
  __inout    PVOID SystemInformation, 
  __in       ULONG SystemInformationLength, 
  __out_opt  PULONG ReturnLength    
);

// Originals
PNT_QUERY_SYSTEM_INFORMATION OriginalFlashWindow   = 
  (PNT_QUERY_SYSTEM_INFORMATION)::GetProcAddress( 
  ::GetModuleHandle( L"user32" ), "FlashWindow" );

PNT_QUERY_SYSTEM_INFORMATION OriginalFlashWindowEx = 
  (PNT_QUERY_SYSTEM_INFORMATION)::GetProcAddress( 
  ::GetModuleHandle( L"user32" ), "FlashWindowEx" );

PNT_QUERY_SYSTEM_INFORMATION OriginalSetForegroundWindow = 
  (PNT_QUERY_SYSTEM_INFORMATION)::GetProcAddress( 
  ::GetModuleHandle( L"user32" ), "SetForegroundWindow" );

// Hooks
BOOL WINAPI
HookedFlashWindow(
  __in  HWND hWnd,
  __in  BOOL bInvert
  ) {
  return 0;
}

BOOL WINAPI 
HookedFlashWindowEx(
  __in  PFLASHWINFO pfwi
  ) {
  return 0;
}

BOOL WINAPI 
HookedSetForegroundWindow(
  __in  HWND hWnd
  ) {
  // Pretend window was brought to foreground
  return 1;
}


BOOL APIENTRY 
DllMain( 
  HMODULE hModule,
  DWORD   ul_reason_for_call,
  LPVOID  lpReserved
  ) {
  switch( ul_reason_for_call ) {
    case DLL_PROCESS_ATTACH:
      Mhook_SetHook( (PVOID*)&OriginalFlashWindow,         HookedFlashWindow );
      Mhook_SetHook( (PVOID*)&OriginalFlashWindowEx,       HookedFlashWindowEx );
      Mhook_SetHook( (PVOID*)&OriginalSetForegroundWindow, HookedSetForegroundWindow );
      break;

    case DLL_PROCESS_DETACH:
      Mhook_Unhook( (PVOID*)&OriginalFlashWindow );
      Mhook_Unhook( (PVOID*)&OriginalFlashWindowEx );
      Mhook_Unhook( (PVOID*)&OriginalSetForegroundWindow );
      break;
  }
  return TRUE;
}

当時の私のテストでは、これはうまくいきました。すべての新しいプロセスにDLLをロードする部分を除いて。ご想像のとおり、それは軽すぎることではありません。私は当時 AppInit_DLLs アプローチを使用しました(これは単に十分ではありません)。

基本的に、これはうまくいきます。しかし、正しく新しいプロセスに自分のDLLを挿入するようなものを書く時間がありませんでした。そしてこれに費やされた時間は、焦点を盗むことが私に引き起こす煩わしさを大いに覆い隠しています。

DLLインジェクションの問題に加えて、私がGoogle Codeの実装でカバーしていなかったフォーカススチール方法もあります。同僚が実際にいくつかの追加の調査を行い、その方法をカバーしました。問題はSOで議論されました: https://stackoverflow.com/questions/7430864/windows-7-prevent-application-from-losing-focus

51
Der Hochstapler

Windows 7では、ForegroundLockTimeoutレジストリエントリはチェックされなくなりました。プロセスモニタでこれを確認できます。実際、Windows 7では、フォアグラウンドウィンドウを変更することはできません。その詳細については を読んでください 、それはWindows 2000以来そこにもありました。

しかし、 ドキュメントは吸い込まれ、彼らはお互いを追いかけてその周りの方法を見つけます

そのため、 SetForegroundWindow 、または同様のAPI関数でバグが発生しています...

実際にこれを正しく行う唯一の方法は、定期的に LockSetForegroundWindow を呼び出す小さなアプリケーションを作成することです。これにより、バグのあるAPI関数の呼び出しは実質的に無効になります。

それでも足りない場合(別のバグのあるAPI呼び出し?)、さらに進んで APIモニタリング を実行して状況を確認してから、 すべてのAPI呼び出しをフックするだけです。 process の後に any を取り除くことで、前景をめちゃくちゃにすることができます。しかし皮肉なことに、これはマイクロソフトによって推奨されていません...

23
Tamara Wijsman

これを行う TweakUI にオプションがあります。これは、ソフトウェア開発者が自分のアプリに集中するために採用している疑わしいトリックのほとんどを防止します。

それは進行中の武器戦争です、それでそれがすべてのためにうまくいくかどうか私は知りません。

更新EndangeredMassa によると、TweakUIはWindows 7では動作しません。

18
Simon P Stevens

「フォーカスを盗む」には2つの方法があるので、いくらか混乱があると思います。(1)ウィンドウが前面に表示されること、および(2)ウィンドウがキーストロークを受け取ることです。

ここで言及されている問題はおそらく2番目の問題で、そこではウィンドウはユーザーの要求や許可なしに自分自身を前面に持ってくることによってフォーカスを主張します。

ここでの議論はXPと7の間で分割する必要があります。

Windows XP

XPには、アプリケーションがフォーカスを盗むのを防ぐためにXPをWindows 7と同じように機能させるレジストリハックがあります。

  1. Regeditを使用してHKEY_CURRENT_USER\Control Panel\Desktopに移動してください。
  2. ForegroundLockTimeoutをダブルクリックして、その値を16進数で30d40に設定します。
  3. OKを押してregeditを終了します。
  4. 変更を有効にするためにPCを再起動します。

Windows 7

(以下の説明は、ほとんどXPにも当てはまります。)

Windowsがアプリケーションの焦点を盗むことを完全に阻止し、機能し続けることができる方法はないことを理解してください。たとえば、ファイルコピー中にアンチウイルスが脅威の可能性を検出し、実行するように求めるウィンドウをポップアップ表示したい場合、このウィンドウがブロックされていると、コピーが終了しない理由がわかりません。

Windows 7では、Windows自体の動作に対して唯一可能な変更があります。それは、 MS-Windowsフォーカスフォローフォローマウスレジストリ を使用することです。カーソルのあるウィンドウに常に移動します。アプリケーションがデスクトップ全体にポップアップ表示されるのを防ぐために、遅延を追加することができます。
この記事を参照してください。 Windows 7 - マウスホバーはウィンドウをアクティブにします - を有効にします。

そうでなければ、有罪プログラムを検出し、無効にする必要があります。これが常に焦点を当てているのと同じアプリケーションである場合、このアプリケーションは焦点を当てるようにプログラムされています。この動作を回避するには、そのアプリケーションによって提供される設定を使用してください。

VB Codeに含まれているVBSスクリプトを使用して、だれがフォーカスを盗んでいるのかを識別できます 。作者はこれを原因としてプリンタソフトウェアの「Call Home」アップデータとして識別しました。

他のすべてが失敗したとき、そしてあなたがこのひどくプログラムされたアプリケーションを識別したならば、絶望的な対策はそれを最小にし、それからそれがそれ自身を前面に持ってこないことを願うことです。より強力な形式の最小化は、 Best Free Application Minimizer にリストされている無料の製品の1つを使ってトレイを作成することです。

絶望的な順序での最後のアイデアは、 DesktopsDexpot のような製品を使って仮想的にあなたのデスクトップを壊すことです。デフォルト以外のデスクトップ.

[編集]

マイクロソフトがアーカイブギャラリーを廃止したため、上記のVBコードが再現されました。

Declare Auto Function GetForegroundWindow Lib "user32.dll" () As Integer
Declare Auto Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As Integer, ByRef procid As Integer) As UInteger

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Me.RichTextBox1.AppendText("Starting up at " & Now & vbCrLf)
    End Sub

    Private Sub GoingAway(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Deactivate, Me.LostFocus

        Dim hwnd As Integer = GetForegroundWindow()
        ' Note that process_id will be used as a ByRef argument
        ' and will be changed by GetWindowThreadProcessId
        Dim process_id As Integer = 1
        GetWindowThreadProcessId(hwnd, process_id)

        If (process_id <> 1) Then
            Dim appExePath As String = Process.GetProcessById(process_id).MainModule.FileName() 
            Me.RichTextBox1.AppendText("Lost focus at " & Now & " due to " & appExePath & vbCrLf)
        Else
            Me.RichTextBox1.AppendText("Lost focus due to unknown cause.")
        End If

    End Sub
14
harrymc

Der Hochstaplerの答え に触発されて、私はDLLインジェクタを書くことにしました。 Windows 7以降に焦点を当てる: https://blade.sk/stay-focused/

動作する方法は、新しく作成されたウィンドウを監視し(SetWinEventHookを使用)、Der Hochstaplerのウィンドウと非常によく似たDLLをウィンドウのプロセスに挿入します。 DLLをアンロードし、終了時に元の機能を復元します。

私のテストでは、これまでのところ非常にうまく機能しています。しかし、この問題は単にSetForegroundWindowを呼び出すアプリケーションよりもさらに深くなるようです。たとえば、新しいウィンドウが作成されると、自動的に最前面に表示されます。これも、ユーザーが別のウィンドウに入力するのを妨げます。

他のフォーカス盗用方法に対処するには、さらにテストが必要であり、それが発生しているシナリオについてのフィードバックをいただければ幸いです。

2
blade

Ghacksは 可能な解決策を持っています:

ポップアップによってアクティブウィンドウのフォーカスを奪うアプリケーションもあります。これは、ファイルを抽出したときや転送が完了したときなど、さまざまな理由で発生します。このような状況が発生してもほとんど問題にはなりませんが、記事を書いているときもあります。言葉をもう一度入力する必要があるだけでなく、集中力を失い、クリックしてフォーカスを取り戻す必要があります。

Pro Reviewer のWebサイトに、これを防ぐ方法についてのヒントがあります。フォーカスの盗用を防ぐ最も簡単な方法は、「アプリケーションがフォーカスを盗むのを防ぐ」という設定のTweak UIを使用することです。このオプションをチェックすると、他のアプリケーションが突然ポップアップして、現在作業中のウィンドウのフォーカスを奪うことを防ぎます。

これは、アプリケーションが以前に最小化されている場合にのみ機能します。フォーカスを盗むのではなく、 Tweak UI の同じメニューで定義できる回数だけ点滅します。 Tweak UIを使用したくない場合は、Windowsレジストリで設定を変更できます。

レジストリキーHKEY_CURRENT_USER>コントロールパネル>デスクトップに移動して、ForegroundLockTimeout値を30d40(16進数)または200000(10進数)に変更します。 ForeGroundFlashCountキーはユーザーに警告するウィンドウの点滅量を定義します。0は無制限を意味します。

2
Ivo Flipse

プログラムでアクティブにし、最大化して、そのプロセスのメインウィンドウを別のプロセスからフォーカスした後に、タスクバーが新しくアクティブになったターゲットウィンドウを点滅させないようにする方法を考え出しました。まず第一に、この操作が許可されるかどうかについて多くの制限があります。

msgstr "" "システムはフォアグラウンドウィンドウを設定できるプロセスを制限します。プロセスはフォアグラウンドウィンドウを設定することができるのは、次の条件のうちの1つが当てはまる場合に限ります。

  • プロセスはフォアグラウンドプロセスです。
  • プロセスはフォアグラウンドプロセスによって開始されました。
  • プロセスは最後の入力イベントを受け取りました。
  • 前景プロセスはありません。
  • フォアグラウンドプロセスはデバッグ中です。
  • 前景はロックされていません(LockSetForegroundWindowを参照)。
  • 前景ロックのタイムアウトが期限切れになった(SystemParametersInfoのSPI_GETFOREGROUNDLOCKTIMEOUTを参照)。
  • アクティブなメニューはありません。

https://docs.Microsoft.com/ja-jp/windows/desktop/api/winuser/nf-winuser-allowsetforegroundwindow

したがって、制御プロセスがフォアグラウンドにある場合、一時的に他のプロセスを有効にすることができます。ターゲットプロセスのプロセスIDを指定して AllowSetForegroundWindow を呼び出して、フォアグラウンドを完全にスチールする。その後、ターゲットプロセスは自身のウィンドウハンドルを使用して SetForegroundWindow 自体を呼び出すことができ、動作します。

明らかに、これは2つのプロセス間の調整を必要とします、しかしそれはうまくいきます、そしてあなたがすべてのエクスプローラクリックをリダイレクトするシングルインスタンスアプリを実装するためにこれをしているなら既存のアプリインスタンスを起動すると、とにかく物事を調整するための名前付きパイプが既に存在します。

0
Glenn Slayden