it-swarm.dev

Evitando que las aplicaciones roben el foco

¿Hay alguna solución para evitar que las aplicaciones roben el foco de la ventana activa?

Esto es especialmente molesto cuando estoy iniciando una aplicación, cambie para hacer otra cosa y la nueva aplicación comienza a recibir la mitad de una oración de texto.

190
svandragt

Esto no es posible sin una manipulación extensa de los elementos internos de Windows y necesita superarlo.

Hay momentos en el uso diario de la computadora cuando es realmente importante que realice una acción antes de que el sistema operativo le permita hacer otra. Para hacer eso, necesita bloquear su enfoque en ciertas ventanas. En Windows, el control sobre este comportamiento se deja en gran parte a los desarrolladores de los programas individuales que utiliza.

No todos los desarrolladores toman las decisiones correctas cuando se trata de este tema.

Sé que esto es muy frustrante y molesto, pero no puedes tener tu pastel y comerlo también. Probablemente hay muchos casos a lo largo de su vida diaria en los que está perfectamente bien con el enfoque que se está moviendo a un determinado elemento de la interfaz de usuario o una aplicación que solicita que el enfoque permanezca bloqueado en él. Pero la mayoría de las aplicaciones son iguales cuando se trata de decidir quién es el líder en este momento y el sistema nunca puede ser perfecto.

Hace un tiempo realicé una extensa investigación para resolver este problema de una vez por todas (y fracasé). El resultado de mi investigación se puede encontrar en la página del proyecto de molestia .

El proyecto también incluye una aplicación que intenta repetidamente llamar la atención llamando a:

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;

Como podemos ver en este fragmento, mi investigación también se centró en otros aspectos del comportamiento de la interfaz de usuario que no me gustan.

La forma en que intenté resolver esto fue cargar un DLL en cada nuevo proceso y conectar las llamadas a la API que hacen que se activen otras ventanas.
La última parte es la más fácil, gracias a las asombrosas API que enlazan las bibliotecas. Utilicé la gran biblioteca 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;
}

Desde mis pruebas en aquel entonces, esto funcionó muy bien. Excepto por la parte de cargar el DLL en cada nuevo proceso. Como uno puede imaginar, eso no es nada para tomarse a la ligera. Utilicé el enfoque AppInit_DLLs en aquel entonces (que simplemente no es suficiente).

Básicamente, esto funciona muy bien. Pero nunca encontré el tiempo para escribir algo que correctamente inyecte mi DLL en nuevos procesos. Y el tiempo invertido en esto en gran medida eclipsa la molestia que me causa el robo de enfoque.

Además del DLL problema de inyección, también hay un método de robo de enfoque que no cubrí en la implementación de Google Code. Un compañero de trabajo hizo una investigación adicional y cubrió ese método. El problema se discutió en SO: https://stackoverflow.com/questions/7430864/windows-7-prevent-application-from-losing-focus

51
Der Hochstapler

En Windows 7, la entrada de registro ForegroundLockTimeout ya no está marcada, puede verificar esto con Process Monitor. De hecho, en Windows 7 no le permiten cambiar la ventana de primer plano. Vaya y lea sobre sus detalles , incluso ha estado allí desde Windows 2000.

Sin embargo, la documentación apesta y se persiguen entre sí y encuentran formas de evitar eso .

Entonces, hay algo defectuoso con SetForegroundWindow , o funciones de API similares ...

La única manera de hacer esto correctamente es hacer una pequeña aplicación que periódicamente llame LockSetForegroundWindow , deshabilitando virtualmente cualquier llamada a nuestra función de API con errores.

Si eso no es suficiente (¿otra llamada de API con errores?), Puede ir aún más lejos y hacer un poco de monitoreo de API para ver qué está pasando, y luego simplemente enganchar las llamadas a la API en cada proceso después de lo cual puede deshacerse de cualquier llamadas que arruinen el primer plano. Sin embargo, irónicamente, esto es desalentado por Microsoft ...

23
Tamara Wijsman

Hay una opción en TweakUI que hace esto. Evita la mayoría de los trucos habituales que los desarrolladores de software dudosos emplean para forzar el enfoque en su aplicación.

Sin embargo, es una guerra de armas en curso, así que no sé si funciona para todo.

Actualización : Según EndangeredMassa , TweakUI no funciona en Windows 7.

18
Simon P Stevens

Creo que puede existir cierta confusión, ya que hay dos formas de "robar el foco": (1) una ventana que viene al primer plano y (2) la ventana que recibe las pulsaciones de teclas.

El problema al que se hace referencia aquí es probablemente el segundo, donde una ventana reclama el enfoque al ponerse en primer plano, sin la solicitud ni el permiso del usuario.

La discusión debe dividirse aquí entre XP y 7.

Windows XP

En XP hay un hack del registro que hace que XP funcione de la misma manera que Windows 7 para evitar que las aplicaciones roben el foco:

  1. Utilice regedit para ir a: HKEY_CURRENT_USER\Control Panel\Desktop.
  2. Haga doble clic en ForegroundLockTimeout y establezca su valor en hexadecimal en 30d40.
  3. Pulse OK y salga de regedit.
  4. Reinicie su PC para que los cambios surtan efecto.

Windows 7

(La discusión a continuación se aplica principalmente a XP también.)

Comprenda que no hay forma en que Windows pueda bloquear totalmente las aplicaciones para que no roben el foco y permanezcan funcionales. Por ejemplo, si durante una copia de archivo, su antivirus detectó una posible amenaza y desea abrir una ventana que le solicita la acción, si esta ventana está bloqueada, nunca entenderá por qué la copia nunca termina.

En Windows 7 solo hay una modificación posible para el comportamiento de Windows, que es usar MS-Windows foco-sigue-mouse El registro hacks , donde el foco y/o la activación va siempre a las ventanas debajo del cursor. Se puede agregar un retraso para evitar que aparezcan aplicaciones en todo el escritorio.
Consulte este artículo: Windows 7 - La función de desplazamiento del mouse hace que la ventana esté activa: habilitar .

De lo contrario, uno debe detectar y neutralizar el programa culpable: si esta es siempre la misma aplicación que se está enfocando, entonces esta aplicación está programada para tomar el enfoque y evitar que esto se haga deshabilitando el inicio de la computadora, o use alguna configuración proporcionada por esa aplicación para evitar este comportamiento.

Podría usar el script VBS incluido en VB Código que identifica quién está robando el foco , que el autor usó para identificar al culpable como un actualizador de "llamada a casa" para un software de impresora.

Una medida desesperada cuando todo lo demás falla, y si ha identificado esta aplicación mal programada, es minimizarla y esperar que luego no se presente. Una forma más fuerte de minimización es la bandeja mediante el uso de uno de los productos gratuitos enumerados en Mejor minimizador de aplicaciones gratuitas .

La última idea en el orden de la desesperación es fracturar su escritorio virtualmente usando un producto como Desktops or Dexpot , y hacer su trabajo en otro escritorio que el predeterminado.

[EDITAR]

Como Microsoft ha retirado la Galería de archivos, aquí está el código VB reproducido arriba:

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

Inspirado por la respuesta de Der Hochstapler , decidí escribir un DLL inyector, que funciona con procesos de 64 y 32 bits y evita enfoque de robo en Windows 7 o más reciente: https://blade.sk/stay-focused/

La forma en que funciona es que observa las ventanas recién creadas (usando SetWinEventHook) e inyecta DLL muy similar a la de Der Hochstapler en el proceso de la ventana, si no está ya presente. Descarga los archivos DLL y restaura la funcionalidad original en la salida.

De mis pruebas, funciona muy bien hasta ahora. Sin embargo, el problema parece ir más allá de las aplicaciones que llaman SetForegroundWindow. Por ejemplo, cuando se crea una nueva ventana, se pone automáticamente en primer plano, lo que también interfiere con un usuario que escribe en otra ventana.

Para lidiar con otros métodos de robo de enfoque, se requieren más pruebas y agradecería cualquier comentario sobre los escenarios en los que está sucediendo.

2
blade

Ghacks tiene una posible solución:

Sucede varias veces al día cuando algunas aplicaciones roban el foco de la ventana activa al aparecer. Esto puede suceder por varios motivos, por ejemplo, cuando extraigo archivos o finaliza una transferencia. No importa la mayor parte del tiempo cuando esto sucede, pero a veces escribo un artículo y no solo significa que debo escribir algunas palabras de nuevo, sino que también perdí la concentración y tengo que hacer clic para recuperar el enfoque.

El Pro Reviewer sitio web tiene una sugerencia sobre cómo evitar que esto suceda. La forma más fácil de prevenir el robo de foco es usar Tweak UI, que tiene una configuración que se llama "Evitar que las aplicaciones roben el foco". Marcar esta opción evita que otras aplicaciones se abran repentinamente y roben el foco de la ventana en la que está trabajando actualmente.

Esto solo funciona cuando la aplicación ha sido minimizada anteriormente. En lugar de robar el enfoque, parpadeará varias veces, que se pueden definir en el mismo menú en Tweak UI . Si no desea utilizar Tweak UI, puede cambiar la configuración en el Registro de Windows.

Navegue hasta la clave de registro HKEY_CURRENT_USER> Panel de control> Escritorio y cambie el valor de ForegroundLockTimeout a 30d40 (hexadecimal) o 200000 (decimal). La clave ForeGroundFlashCount define la cantidad de destellos de una ventana para alertar al usuario donde 0 significa ilimitado.

2
Ivo Flipse

Descubrí cómo evitar que la barra de tareas destelle una ventana de destino recién activada después de que actives, maximices y enfocas programáticamente la ventana principal de ese proceso desde otro proceso. En primer lugar, hay muchas restricciones sobre si se permitirá esta operación.

"El sistema restringe los procesos que pueden establecer la ventana de primer plano. Un proceso puede establecer la ventana de primer plano solo si una de las siguientes condiciones es verdadera:

  • El proceso es el proceso de primer plano.
  • El proceso fue iniciado por el proceso de primer plano.
  • El proceso recibió el último evento de entrada.
  • No hay proceso de primer plano.
  • El proceso de primer plano se está depurando.
  • El primer plano no está bloqueado (consulte LockSetForegroundWindow).
  • El tiempo de espera de bloqueo de primer plano ha caducado (consulte SPI_GETFOREGROUNDLOCKTIMEOUT en SystemParametersInfo).
  • No hay menús activos.

https://docs.Microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-allowsetforegroundwindow

Entonces, si el que controla proceso está en primer plano, puede habilitar temporalmenteotro proceso para robe el primer plano llamando AllowSetForegroundWindow con el identificador de proceso del proceso de destino. Luego, después de eso, el proceso de destino puede llamar SetForegroundWindow por sí mismo, utilizando su propio identificador de ventana, y funcionará.

Obviamente, esto requiere algo de coordinación entre los dos procesos, pero funciona, y si lo hace para implementar una aplicación de instancia única que redirige todos los lanzamientos con el clic del Explorador en la instancia de la aplicación existente, entonces ya tendrá una (por ejemplo) canalización para coordinar las cosas de todos modos.

0
Glenn Slayden