Evitar que las aplicaciones roben el foco

191

¿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, cambio a hacer otra cosa y la nueva aplicación comienza a recibir media frase de texto.

svandragt
fuente
99
@Ivo Windows 7 en mi caso, pero creo que para SuperUser todas las versiones de Windows serían relevantes
svandragt
3
El moderador fusionó esta pregunta: superuser.com/questions/199821/… con la actual. Esto está mal, la respuesta a la pregunta actual no se aplica a Windows 7, por lo que no debe fusionarse. Hasta ahora no pude encontrar una solución a este problema en Windows 7
Alex Angelico
17
Esta es una de mis manías favoritas con cada GUI que he usado. Estás escribiendo y blam, un cuadro de diálogo de bleeping roba el foco y la mitad de las pulsaciones de teclas van a otro lado. Se podría pensar que los implementadores de los sistemas de ventanas habrían descubierto esto hace décadas. Si hay actividad en una ventana, retrase la exposición de la nueva ventana. Por ejemplo, no muestre nada en la GUI hasta tres o cuatro segundos desde el último clic del botón o pulsación de tecla en la ventana actualmente enfocada. Doh!
Kaz
24
This is especially annoying when I'm starting an application, switch to do something else and the new application starts receiving half a sentence of text.Es aún más molesto cuando aparece un cuadro de diálogo y lo descarta involuntariamente sin siquiera ver el mensaje porque presionó Spaceo Entermientras escribía una oración.
Synetech
3
En realidad, esto es mucho más que molesto, diría que es un riesgo de seguridad. No hay nada que impida que aparezca una aplicación cuando estás escribiendo una contraseña y obteniendo tu entrada.
Chris Peacock

Respuestas:

51

Esto no es posible sin una manipulación extensiva de los componentes internos de Windows y debe 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 medida 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 foco movido a un determinado elemento de la interfaz de usuario o una aplicación que solicita que el foco permanezca fijo en él. Pero la mayoría de las aplicaciones son algo 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 hice una extensa investigación para resolver este problema de una vez por todas (y fallé). El resultado de mi investigación se puede encontrar en la página del proyecto molesto .

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

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 traté de resolver esto fue cargar una DLL en cada proceso nuevo y conectar las llamadas a la API que hacen que se activen otras ventanas.
La última parte es fácil, gracias a las impresionantes bibliotecas de enganche de API que existen. Usé 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 ese entonces, esto funcionó muy bien. Excepto por la parte de cargar la DLL en cada proceso nuevo. Como uno podría imaginar, eso no es nada que tomar demasiado a la ligera. Solía los AppInit_DLLs acercan entonces (que simplemente no es suficiente).

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

Además del problema de inyección de DLL, también hay un método de robo de foco que no cubrí en la implementación en Google Code. Un compañero de trabajo en realidad 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

Der Hochstapler
fuente
¿Crees que esta solución tuya podría ser portada a Java? Estuve buscando y haciendo preguntas pero no encontré nada. ¿Tal vez podría importar la biblioteca de ganchos en Java usando jne?
Tomáš Zato
@ TomášZato: No tengo idea. Yo no estoy usando este código activamente.
Der Hochstapler
Estoy tratando de compilarlo como C ++ al menos (y luego inyectar / eliminar la DLL compilada de Java). Pero eso tampoco va demasiado bien. No quiero discutirlo aquí en los comentarios, pero si realmente pudieran ayudarme a hacer que funcione, ¡sería muy amable! Creé
Tomáš Zato
23

En Windows 7, la ForegroundLockTimeoutentrada del registro 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 y encuentran formas de evitarlo .

Entonces, hay algo con errores SetForegroundWindowo funciones API similares ...

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

Si eso no es suficiente (¿otra llamada API defectuosa?) Puede ir aún más lejos y hacer un seguimiento de la API para ver qué está sucediendo, y luego simplemente enganchar las llamadas API en cada proceso, después de lo cual puede deshacerse de las llamadas que se equivocan el primer plano. Sin embargo, irónicamente, Microsoft desaconseja esto ...

Tamara Wijsman
fuente
3
¿Alguien tiene un caso de uso reproducible de esto en Windows 7? Dado que las personas experimentan lo contrario (por ejemplo, a menudo encuentro que Windows exigente se oculte detrás de mi ventana actual) y que todavía no veo que esto suceda en Windows 7, sería bastante molesto escribir una aplicación pero no poder Pruébalo. Además, como Microsoft afirma que esto ya no debería suceder con Windows 7. En el mejor de los casos, la gente descubrió que solo podía cambiar el foco del teclado por accidente, esta llamada a la API lo solucionaría, pero no sé cómo probar si realmente funciona ... .
Tamara Wijsman
1
El instalador (basado en InnoSetup) inicia otros procesos y otras posibles configuraciones (ocultas), pero no sé en qué creador de configuración se basan.
Daniel Beck
66
@TomWijsman: Abra regedit, busque texto aleatorio que no se encuentre. Entra en otra aplicación y comienza a escribir. Cuando finalice la búsqueda, regedit robará el foco.
endolito
1
@endolith: No es reproducible, aunque utilizo Windows 8 Replase Preview aquí. ¿Qué sistema operativo estás usando? En mi caso, solo resalta la aplicación en la parte inferior, pero no interrumpe mi navegación en absoluto ...
Tamara Wijsman
21
Sí, Win7 Pro de 64 bits. Y el robo de foco es aún peor para procesos elevados, ya que capturan su presión <Enter> cuando no deberían, y le dice que mangue su sistema accidentalmente. Nada debe nunca ser capaz de robar el foco.
endolito
18

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.

Simon P Stevens
fuente
2
¿Tweakui es compatible con Windows 7?
frankster
@frankster. No tengo idea, lo siento, sospecho que probablemente no lo sea. Descárgalo y pruébalo. Informe si lo hace, todo el mundo lo sabe.
Simon P Stevens
55
Incluso usar la configuración de registro que TweakUI establece no funciona en Win7.
EndangeredMassa
@EndangeredMassa, ¿qué clave de registro es esa?
n611x007
2
La clave de registro es HKEY_CURRENT_USER \ Control Panel \ Desktop \ ForegroundLockTimeout (en milisegundos). Y sí, ya no funciona en Windows 7.
foo
14

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

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

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

Windows XP

En XP hay un hack de registro que hace que XP funcione igual 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 ForegroundLockTimeouty establezca su valor en hexadecimal en 30d40.
  3. Presione 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).

Por favor, comprenda que no hay forma en que Windows pueda bloquear totalmente las aplicaciones de robar el foco y permanecer funcional. Por ejemplo, si durante una copia de un archivo, su antivirus detectó una posible amenaza y le gustaría abrir una ventana emergente pidiéndole que tome medidas, si esta ventana está bloqueada, nunca entenderá por qué la copia nunca termina.

En Windows 7, solo hay una modificación posible en el comportamiento del propio Windows, que es utilizar los hacks de registro MS-Windows focus-sigue-mouse , donde el foco y / o activación siempre se dirige 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: el desplazamiento del mouse activa la ventana: Habilitar .

De lo contrario, uno debe detectar y neutralizar el programa de culpabilidad: si esta es siempre la misma aplicación que está recibiendo el foco, entonces esta aplicación está programada para enfocar y evitar que esto se pueda hacer deshabilitando que se inicie con la computadora, o use alguna configuración proporcionada por esa aplicación para evitar este comportamiento.

Podría usar el script VBS incluido en el Código VB 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 no se presente al frente. Una forma más fuerte de minimización es la bandeja usando uno de los productos gratuitos enumerados en Best Free Application Minimizer .

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

[EDITAR]

Como Microsoft retiró la Galería de archivos, aquí se reproduce el código VB anterior:

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
harrymc
fuente
48
"si esta ventana está bloqueada, nunca entenderías por qué la copia nunca termina" Eso no es cierto. El comportamiento correcto es notificar al usuario con un icono de barra de tareas parpadeante (o tal vez una ventana emergente de globo o una notificación de tostadora o algo así). Interrumpir al usuario con una ventana que intercepta sus pulsaciones de teclas significa que le dicen al software antivirus que tome una acción u otra al azar. Definitivamente no es una buena manera de hacer las cosas.
endolito
1
"si esta ventana está bloqueada, nunca entenderías por qué la copia nunca termina" Eso no es cierto. El comportamiento correcto es notificar al usuario con un icono de barra de tareas parpadeante ... Ha habido ocasiones en las que hice clic en un botón o algo en un programa en ejecución que hace que se cree un nuevo diálogo modal (por ejemplo, abrir un archivo ), pero luego Me cambio a otro programa antes de que se cree el diálogo. Como resultado, el diálogo está oculto y el otro programa no se puede cambiar y el diálogo no se puede descartar. Ni su botón de la barra de tareas ni Alt-Tabfunciona; solo forzando el diálogo al frente.
Synetech
1
@Synetech: a veces la única solución para el diálogo no frontal es matar la tarea. Los algoritmos de enfoque en Windows son realmente pésimos.
harrymc
2
@harrymc, nunca tengo que recurrir a matar una de las aplicaciones. Simplemente ejecuto mi programa de manipulación de ventanas ( WinSpy ++ hace el truco simplemente genial) y oculto la ventana al frente, luego puedo cerrar el cuadro de diálogo atascado y luego volver a mostrar la ventana oculta. No es conveniente, pero es mejor que matar cualquiera de los procesos.
Synetech
1
@harrymc, no realmente; matar una aplicación y perder cosas solo genera más vapor, y si se trata de un cuadro de diálogo modal (que bloquea la ventana principal y no tiene botón de barra de tareas), no aparecerá en la Alt+Tablista y, en mi experiencia, una ventana que tiene un diálogo modal abierto, no siempre (¿nunca?) muestra el diálogo modal con Alt+Tab, especialmente si el diálogo nunca tuvo un cambio para enfocarse. :-|
Synetech
2

Ghacks tiene una posible solución:

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

El sitio web de Pro Reviewer tiene un consejo 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 foco". Marcar esta opción evita que otras aplicaciones aparezcan repentinamente y roben el foco de la ventana en la que está trabajando actualmente.

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

Vaya a 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 parpadeos de una ventana para alertar al usuario donde 0 significa ilimitado.

Ivo Flipse
fuente
20
Esto no funciona en ningún sistema operativo después de XP. Ese valor de registro ya está establecido en eso (por defecto, creo) y no funciona de todos modos.
EndangeredMassa
1
Solo en segundo lugar, estoy en Windows 7 (64 bits), experimentando robo de foco (VS 2012 cuando finalmente está activo, por ejemplo), y la sugerencia de registro anterior ya está en su lugar. Confirmación técnica en esta respuesta: superuser.com/a/403554/972
Michael Paulukonis
2

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

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

Según mis pruebas, hasta ahora funciona muy bien. Sin embargo, el problema parece ir más allá de las llamadas de aplicaciones 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 foco, se requieren más pruebas y agradecería cualquier comentario sobre los escenarios donde está sucediendo.

espada
fuente
0

Descubrí cómo evitar que la Barra de tareas muestre una ventana de destino recién activada después de activar, maximizar y enfocar 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 qué procesos pueden establecer la ventana de primer plano. Un proceso puede establecer la ventana de primer plano solo si se cumple una de las siguientes condiciones:

  • El proceso es el proceso en 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 en primer plano se está depurando.
  • El primer plano no está bloqueado (ver 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 proceso de control está en primer plano, puede habilitar temporalmente otro proceso para robar completamente el primer plano llamando a AllowSetForegroundWindow con la identificación del proceso del proceso de destino. Luego, después de eso, el proceso de destino puede llamar a SetForegroundWindow en sí, utilizando su propio identificador de ventana, y funcionará.

Obviamente, esto requiere cierta coordinación entre los dos procesos, pero funciona, y si está haciendo esto para implementar una aplicación de instancia única que redirige todos los lanzamientos de Explorer-click en la instancia de aplicación existente, entonces ya estará tener una (por ejemplo) tubería con nombre para coordinar las cosas de todos modos.

Glenn Slayden
fuente