Evitar que aplicativos roubem o foco

189

Existe alguma solução para impedir que aplicativos roubem o foco da janela ativa?

Isso é especialmente irritante quando estou iniciando um aplicativo, mudo para fazer outra coisa e o novo aplicativo começa a receber meia frase de texto.

    
por svandragt 05.08.2009 / 10:48

7 respostas

50

Isto não é possível sem a extensa manipulação dos componentes internos do Windows e você precisa passar por isso.

Há momentos no uso diário do computador quando é realmente importante que você faça uma ação antes que o sistema operacional permita que você faça outra. Para fazer isso, é necessário bloquear seu foco em determinadas janelas. No Windows, o controle sobre esse comportamento é largamente deixado para os desenvolvedores dos programas individuais que você usa.

Nem todo desenvolvedor toma as decisões certas quando se trata deste tópico.

Eu sei que isso é muito frustrante e chato, mas você não pode ter seu bolo e comê-lo também. Provavelmente, há muitos casos em sua vida diária em que você está perfeitamente bem com o foco sendo movido para um determinado elemento da interface do usuário ou um aplicativo solicitando que o foco permaneça bloqueado. Mas a maioria dos aplicativos é um pouco igual quando se trata de decidir quem é o líder agora e o sistema nunca pode ser perfeito.

Há algum tempo, fiz uma extensa pesquisa sobre como resolver esse problema de uma vez por todas (e falhei). O resultado da minha pesquisa pode ser encontrado na página do projeto de aborrecimento .

O projeto também inclui um aplicativo que tenta repetidamente chamar o foco chamando:

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 neste trecho, minha pesquisa também se concentrou em outros aspectos do comportamento da interface do usuário dos quais não gosto.

A maneira como tentei resolver isso foi carregar uma DLL em cada novo processo e ligar as chamadas de API que fazem com que outras janelas sejam ativadas.
A última parte é a mais fácil, graças a incríveis bibliotecas de hooking de APIs por aí. Eu usei a grande 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;
}

De meus testes naquela época, isso funcionou muito bem. Exceto pela parte de carregar a DLL em todo novo processo. Como se pode imaginar, isso não é nada demais. Usei a abordagem AppInit_DLLs (que simplesmente não é suficiente).

Basicamente, isso funciona muito bem. Mas eu nunca encontrei tempo para escrever algo que propriamente injeta minha DLL em novos processos. E o tempo investido nisso, em grande parte, ofusca o aborrecimento que o foco roubado me causa.

Além do problema de injeção de DLL, há também um método de roubo de foco que não abordei na implementação do Google Code. Um colega de trabalho realmente fez algumas pesquisas adicionais e cobriu esse método. O problema foi discutido no SO: link

    
por 24.03.2012 / 10:56
23

No Windows 7, a entrada de registro ForegroundLockTimeout não está mais marcada, você pode verificar isso com o Process Monitor. Na verdade, no Windows 7 eles não permitem que você altere a janela de primeiro plano. Acesse o site sobre os detalhes . até lá desde o Windows 2000.

No entanto, a documentação é chata e eles se perseguem e encontram maneiras de contornar isso .

Então, há algo de bugs acontecendo com SetForegroundWindow ou funções de API semelhantes ...

A única maneira de realmente fazer isso corretamente é fazer um pequeno aplicativo que periodicamente chama LockSetForegroundWindow , praticamente desativando todas as chamadas para a nossa função de API com bugs.

Se isso não for suficiente (outra chamada de API com bugs), você pode ir ainda mais longe e fazer alguns monitoramento de API para ver o que está acontecendo em, e então você simplesmente conecta as chamadas da API em cada processo , após o qual você pode se livrar de qualquer chamadas que estraga o primeiro plano. No entanto, ironicamente, isso é desencorajado pela Microsoft ...

    
por 22.03.2012 / 10:52
18

Existe uma opção em TweakUI que faz isso. Isso evita que a maioria dos truques comuns usados pelos desenvolvedores de software duvidosos force o foco em seu aplicativo.

É uma guerra armamentista contínua, então não sei se funciona para tudo.

Atualização : De acordo com EndangeredMassa , o TweakUI não funciona no Windows 7 .

    
por 05.08.2009 / 11:12
14

Acredito que pode haver alguma confusão, pois há duas maneiras de "roubar o foco": (1) uma janela chegando ao primeiro plano e (2) a janela recebendo as teclas digitadas.

O problema referido aqui é provavelmente o segundo, onde um O Windows reivindica o foco colocando-se em primeiro plano - sem o pedido ou a permissão do usuário.

A discussão deve dividir aqui entre XP e 7.

Windows XP

No XP existe um hack de registro que faz o XP funcionar da mesma forma que o Windows 7, impedindo que os aplicativos roubem o foco:

  1. Use o regedit para ir para: HKEY_CURRENT_USER\Control Panel\Desktop .
  2. Clique duas vezes em ForegroundLockTimeout e defina seu valor em hexadecimal como 30d40 .
  3. Pressione OK e saia do regedit.
  4. Reinicie o seu PC para que as alterações entrem em vigor.

Windows 7

(A discussão abaixo se aplica principalmente ao XP também.)

Por favor, entenda que não há nenhuma maneira em que o Windows pode bloquear totalmente os aplicativos de roubar o foco e permanecer funcional. Por exemplo, se durante uma cópia de arquivo seu antivírus detectou uma possível ameaça e gostaria de abrir uma janela solicitando a ação, se esta janela estiver bloqueada então você nunca entenderia porque a cópia nunca termina.

No Windows 7, só há uma modificação possível no comportamento do próprio Windows, que é para usar os hacks de MS-Windows focus-follows-mouse , onde o foco e / ou ativação vai sempre para as janelas sob o cursor. Um atraso pode ser adicionado para evitar que os aplicativos sejam exibidos em toda a área de trabalho. Veja este artigo: Windows 7 - Mouse Hover Torna a Janela Ativa - Habilita .

Caso contrário, é preciso detectar e neutralizar o programa culpado: Se este é sempre o mesmo aplicativo que está recebendo o foco, esse aplicativo é programado para tirar o foco e impedir que isso seja feito, seja desativando-o desde o início com o computador, ou usando alguma configuração fornecida por esse aplicativo para evitar esse comportamento.

Você pode usar o script VBS incluído no Código do VB que identifica quem está roubando o foco , que o autor usou para identificar o culpado como um atualizador de "call home" para um software de impressora.

Uma medida desesperada quando tudo mais falha, e se você identificou esta aplicação mal programada, é minimizá-lo e espero que não seja levado para a frente. Uma forma mais strong de minimização é a bandeja usando um dos produtos gratuitos listados Melhor minimizador de aplicativos gratuito .

A última ideia na ordem de desespero é fraturar sua área de trabalho virtualmente usando um produto como Desktops ou Dexpot , e faça seu trabalho em outro desktop que o padrão.

[EDITAR]

Como a Microsoft retirou a Galeria de arquivos, aqui está o código VB acima reproduzido:

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
    
por 09.06.2012 / 11:22
2

Ghacks tem uma solução possível:

It happens several times a day that some applications steal the focus of the active window by popping up. This can happen for a number of reasons, when I extract files or a transfer finishes for instance. It does not matter most of the time when this happens but sometimes I’m writing an article and it does not only mean that I have to type some words again but also that I lost concentration and have to click to regain focus.

The Pro Reviewer website has a tip on how to prevent this from happening. The easiest way of preventing focus stealing is to use Tweak UI which has a setting that is called “Prevent applications from stealing focus”. Checking this option prevents that other applications pop up suddenly and steal the focus of the window you are currently working in.

This only works when the application has been minimized before. Instead of stealing the focus it will flash a number of times which can be defined in the same menu in Tweak UI. If you do not want to use Tweak UI you can change the setting in the Windows Registry.

Navigate to the Registry key HKEY_CURRENT_USER > Control Panel > Desktop and change the ForegroundLockTimeout value to 30d40 (Hexadecimal) or 200000 (Decimal). The key ForeGroundFlashCount defines the amount of flashes of a window to alert the user where 0 means unlimited.

    
por 05.08.2009 / 11:13
2

Inspirado pela resposta do Der Hochstapler , decidi escrever um injetor de DLL, que funciona tanto com 64 quanto com 32 bits processa e impede o roubo de foco no Windows 7 ou mais recente: link

A maneira como funciona é que ele assiste a janelas recém-criadas (usando SetWinEventHook ) e injeta DLL muito semelhante à do Der Hochstapler no processo da janela, se não estiver presente. Ele descarrega as DLLs e restaura a funcionalidade original ao sair.

Do meu teste, funciona muito bem até agora. No entanto, o problema parece ser mais profundo do que apenas aplicativos chamando SetForegroundWindow . Por exemplo, quando uma nova janela é criada, ela é automaticamente colocada em primeiro plano, o que também interfere com o usuário digitando em outra janela.

Para lidar com outros métodos de roubo de foco, mais testes são necessários e agradecemos qualquer feedback sobre os cenários em que isso está acontecendo.

    
por 29.11.2018 / 13:06
0

Descobri como impedir que o TaskBar exibisse uma janela de destino recém-ativada depois que você ativasse, maximizasse e focasse, de forma programática, a janela principal desse processo em outro processo. Primeiro de tudo, há muitas restrições sobre se essa operação será permitida.

"The system restricts which processes can set the foreground window. A process can set the foreground window only if one of the following conditions is true:

  • The process is the foreground process.
  • The process was started by the foreground process.
  • The process received the last input event.
  • There is no foreground process.
  • The foreground process is being debugged.
  • The foreground is not locked (see LockSetForegroundWindow).
  • The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo).
  • No menus are active.

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

Portanto, se o processo de controle estiver em primeiro plano, ele poderá ativar temporariamente outro processo para roubar totalmente o primeiro plano chamando AllowSetForegroundWindow com o ID do processo do destino processo. Depois disso, o processo de segmentação pode chamar SetForegroundWindow , usando seu próprio identificador de janela, e isso funcionará.

Obviamente, isso requer alguma coordenação entre os dois processos, mas funciona, e se você estiver fazendo isso para implementar um aplicativo de instância única que redireciona todos os lançamentos de clique do Explorer para a instância do aplicativo existente, você já terá um canal nomeado (por exemplo) para coordenar as coisas de qualquer maneira.

    
por 26.03.2019 / 08:52