Impedindo que os aplicativos roubem o foco


191

Existem soluções para impedir que os aplicativos roubem o foco da janela ativa?

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


9
@ Ivo Windows 7 no meu caso, mas eu acho que para SuperUser todas as janelas versões seriam relevantes
svandragt

3
O moderador mesclou esta pergunta: superuser.com/questions/199821/… com a atual. Isso está errado, a resposta para a pergunta atual não se aplica ao Windows 7, portanto não deve ser mesclada. Até agora não consegui encontrar uma solução para esse problema no Windows 7
Alex Angelico

17
Este é um dos meus ódios de estimação número um em todas as GUI que já usei. Você está digitando e culpando, alguma caixa de diálogo apita rouba o foco e metade das teclas pressionadas vai para outro lugar. Você pensaria que os implementadores de sistemas de janelas teriam descoberto isso décadas atrás. Se houver atividade em uma janela, adie a exposição da nova janela. Por exemplo, não apareça nada na GUI até três ou quatro segundos desde o último clique ou pressionamento de tecla na janela atualmente focada. 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.É ainda mais irritante quando uma caixa de diálogo é exibida e você acidentalmente a dispensa sem nem mesmo ver a mensagem porque pressionou Spaceou Enterdigitou uma frase.
Synetech

3
Na verdade, isso é muito mais do que irritante, eu diria que é um risco à segurança. Não há nada para impedir que um aplicativo apareça quando você está digitando uma senha e capturando sua entrada.
Chris Peacock

Respostas:


51

Isso não é possível sem a manipulação extensiva de componentes internos do Windows e você precisa superá-lo.

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

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

Sei que isso é muito frustrante e irritante, mas você não pode comer o seu bolo e também comê-lo. Provavelmente, existem muitos casos ao longo da 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 das aplicações é 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 a solução desse 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 recuperar o foco repetidamente 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 foi focada em outros aspectos do comportamento da interface do usuário que eu não gosto.

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

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

Basicamente, isso funciona muito bem. Mas nunca encontrei tempo para escrever algo que injete corretamente minha DLL em novos processos. E o tempo investido nisso obscurece amplamente o aborrecimento que o foco roubado me causa.

Além do problema de injeção de DLL, também há um método de roubo de foco que não cobri na implementação no Google Code. Um colega de trabalho realmente fez algumas pesquisas adicionais e cobriu esse método. O problema foi discutido no SO: https://stackoverflow.com/questions/7430864/windows-7-prevent-application-from-losing-focus


Você acha que essa solução pode ser portada para Java? Estive pesquisando e fazendo perguntas, mas não encontrei nada. Talvez eu possa importar a própria biblioteca de ganchos em java usando jne?
Tomáš Zato

@ TomášZato: Não faço ideia. Não estou ativamente usando esse código.
Der Hochstapler /

Estou tentando compilá-lo como C ++ pelo menos (e depois injetar / remover a DLL compilada do Java). Mas isso também não vai muito bem. Não quero discutir isso aqui nos comentários, mas se você realmente puder me ajudar a fazê-lo funcionar, eu seria muito gentil! Eu criei uma sala de bate-papo, se eu conseguir que isso funcione, postarei um comentário sobre como fazê-lo aqui: chat.stackexchange.com/rooms/21637/…
Tomáš Zato

23

No Windows 7, a ForegroundLockTimeoutentrada do registro não está mais marcada, você pode verificar isso com o Process Monitor. De fato, no Windows 7, eles não permitem que você altere a janela do primeiro plano. Vá e leia sobre seus detalhes , ele existe desde o Windows 2000.

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

Portanto, há algo de buggy acontecendo SetForegroundWindowou funções similares da API ...

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

Se isso não for suficiente (outra chamada de API de buggy?), Você pode ir ainda mais longe e fazer algum monitoramento da API para ver o que está acontecendo. Depois, basta conectar as chamadas da API em todos os processos e depois eliminar as chamadas que atrapalharem. primeiro plano. No entanto, ironicamente, isso é desencorajado pela Microsoft ...


3
Alguém tem um caso de uso reproduzível disso no Windows 7? Dado que as pessoas experimentam o oposto (por exemplo, muitas vezes acho que o Windows está oculto atrás da minha janela atual) e ainda estou vendo isso acontecer no Windows 7, seria muito chato escrever um aplicativo, mas não consegui teste-o. Além disso, como a Microsoft afirma que isso não deveria mais acontecer com o Windows 7. Na melhor das hipóteses, as pessoas descobriram que ele só podia mudar o foco do teclado por acidente, essa chamada de API corrigia isso, mas eu não sei como testar se ele realmente funciona. .
Tamara Wijsman

1
O instalador (baseado no InnoSetup) inicia outros processos e possíveis outras configurações (ocultas), mas não sei em que criador da instalação eles se baseiam.
Daniel Beck

6
@ TomWijsman: Abra o regedit, procure por algum texto aleatório que não será encontrado. Entre em outro aplicativo e comece a digitar. Quando a pesquisa terminar, o regedit roubará o foco.
endolith 6/06/12

1
@ endolith: Não reproduzível, usando o Windows 8 Replase Preview aqui. Qual sistema operacional você está usando? No meu caso, apenas destaca a aplicação na parte inferior, mas não interrompe a minha navegação em tudo ...
Tamara Wijsman

21
Sim, Win7 Pro de 64 bits. E o roubo de foco é ainda pior para processos elevados, pois eles capturam seu pressionamento <Enter> quando não deveriam, e você diz para mangueira seu sistema acidentalmente. Nada deve sempre ser capaz de roubar foco.
endolith 6/06/12

18

Há uma opção no TweakUI que faz isso. Isso evita a maioria dos truques usuais que desenvolvedores de software duvidosos empregam para forçar o foco em seu aplicativo.

É uma guerra de armas em andamento, portanto, não sei se funciona para tudo.

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


2
tweakui é compatível com o windows 7?
frankster

@frankster. Não faço ideia, desculpe, suspeito que provavelmente não seja. Faça o download e experimente. Relate se fizer isso, todo mundo sabe.
Simon P Stevens

5
Mesmo usando a configuração de registro que o TweakUI define não funciona no Win7.
EndangeredMassa

@EndangeredMassa qual chave do registro é essa?
N611x007

2
A chave do registro é HKEY_CURRENT_USER \ Control Panel \ Desktop \ ForegroundLockTimeout (em milissegundos). E sim, ele não funciona mais no Windows 7.
foo

14

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

O problema mencionado aqui é provavelmente o segundo, em que uma janela reivindica o foco, trazendo-se para o primeiro plano - sem a solicitação ou permissão do usuário.

A discussão deve ser dividida aqui entre XP e 7.

Windows XP

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

  1. Use regedit ir em: HKEY_CURRENT_USER\Control Panel\Desktop.
  2. Clique duas vezes em ForegroundLockTimeoute defina seu valor em hexadecimal para 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á como o Windows impedir 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 a ser tomada, se essa janela estiver bloqueada, você nunca entenderá por que a cópia nunca termina.

No Windows 7, existe apenas uma modificação possível para o comportamento do Windows, que é usar os hacks de registro do MS-Windows focus-segue-mouse , nos quais o foco e / ou a ativação vão sempre para as janelas sob o cursor. Um atraso pode ser adicionado para evitar que aplicativos apareçam em toda a área de trabalho.
Consulte este artigo: Windows 7 - Passar o mouse ativa a janela - Ativar .

Caso contrário, é preciso detectar e neutralizar o programa culpado: se esse é sempre o mesmo aplicativo que está recebendo o foco, esse aplicativo é programado para ter o foco e impedir que isso possa ser feito, desativando a inicialização do computador ou use alguma configuração fornecida por esse aplicativo para evitar esse comportamento.

Você pode usar o script VBS incluído no Código 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 esse aplicativo mal programado, é minimizá-lo e torcer para que ele não se destaque. Uma forma mais forte de minimização é a bandeja, usando um dos produtos gratuitos listados no Melhor Minimizador de Aplicativo Gratuito .

A última idéia na ordem do desespero é fraturar sua área de trabalho virtualmente usando um produto como Desktops ou Dexpot e fazer seu trabalho em outra área de trabalho que não o padrão.

[EDITAR]

Como a Microsoft retirou a Galeria de Arquivos, eis 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

48
"se esta janela estiver bloqueada, você nunca entenderá por que a cópia nunca termina" Isso não é verdade. O comportamento correto é notificar o usuário com um ícone piscando na barra de tarefas (ou talvez um balão pop-up ou uma notificação da torradeira ou algo assim). Interromper o usuário com uma janela que intercepta as teclas digitadas significa que ele instrui o software antivírus a executar uma ação ou outra aleatoriamente. Definitivamente, não é uma boa maneira de fazer as coisas.
Endolith

1
"se esta janela estiver bloqueada, você nunca entenderá por que a cópia nunca termina" Isso não é verdade. O comportamento correto é notificar o usuário com um ícone piscando na barra de tarefas ... Houve momentos em que cliquei em um botão ou em algo em um programa em execução que faz com que uma nova caixa de diálogo modal seja criada (por exemplo, abrir arquivo ), mas depois Eu mudo para outro programa antes da caixa de diálogo ser criada. Como resultado, a caixa de diálogo está oculta e o outro programa não pode ser alternado e a caixa de diálogo não pode ser descartada. Nem o botão da barra de tarefas nem Alt-Tabfunciona; forçando apenas o diálogo para a frente.
Synetech

1
@ Synetech: Às vezes, a única solução para o diálogo não frontal é matar a tarefa. Os algoritmos de foco no Windows são realmente ruins.
21812 harrymc

2
@ harrymc, nunca preciso recorrer a matar um dos aplicativos. Acabei de executar o meu programa de manipulação de janelas (o WinSpy ++ é ótimo) e oculto a janela na frente, então posso descartar a caixa de diálogo bloqueada e exibir novamente a janela oculta. Não é conveniente, mas é melhor do que matar qualquer um dos processos.
Synetech

1
@harrymc, na verdade não; matar um aplicativo e perder coisas gera mais força e, se for uma caixa de diálogo modal (que bloqueia a janela pai e não possui botão da barra de tarefas), ela não aparecerá na Alt+Tablista e, na minha experiência, uma janela que tem uma caixa de diálogo modal aberta nem sempre (nunca?) mostra a caixa de diálogo modal com Alt+Tab, especialmente se a caixa de diálogo nunca teve uma alteração para obter foco. :-|
Synetech

2

Ghacks tem uma solução possível:

Acontece várias vezes ao dia que alguns aplicativos roubam o foco da janela ativa aparecendo. Isso pode acontecer por vários motivos, quando eu extraio arquivos ou uma transferência é concluída, por exemplo. Não importa na maioria das vezes quando isso acontece, mas às vezes estou escrevendo um artigo e isso não significa apenas que tenho que digitar algumas palavras novamente, mas também que perdi a concentração e tenho que clicar para recuperar o foco.

O site Pro Reviewer tem uma dica sobre como impedir que isso aconteça. A maneira mais fácil de impedir o roubo de foco é usar o Tweak UI, que possui uma configuração chamada “Impedir que aplicativos roubem o foco”. A seleção dessa opção impede que outros aplicativos apareçam repentinamente e roubem o foco da janela em que você está trabalhando.

Isso funciona apenas quando o aplicativo já foi minimizado antes. Em vez de roubar o foco, ele pisca várias vezes, o que pode ser definido no mesmo menu na interface do usuário do Tweak . Se você não quiser usar o Tweak UI, poderá alterar a configuração no Registro do Windows.

Navegue até a chave do Registro HKEY_CURRENT_USER> Painel de Controle> Área de Trabalho e altere o valor ForegroundLockTimeout para 30d40 (Hexadecimal) ou 200000 (Decimal). A chave ForeGroundFlashCount define a quantidade de flashes de uma janela para alertar o usuário em que 0 significa ilimitado.


20
Isso não funciona em qualquer sistema operacional após o XP. Esse valor do registro já está definido para isso (por padrão, acredito) e não funciona de qualquer maneira.
EndangeredMassa

1
Apenas para dizer que estou no Windows 7 (64 bits), estou enfrentando roubo de foco (VS 2012 quando finalmente ativo, por exemplo), e a sugestão de registro acima já está em vigor. Confirmação técnica nesta resposta: superuser.com/a/403554/972
Michael Paulukonis

2

Inspirado pela resposta de Der Hochstapler , decidi escrever um injetor de DLL, que funciona com processos de 64 e 32 bits e evita o roubo de foco no Windows 7 ou mais recente: https://blade.sk/stay-focused/

A maneira como funciona é que ele observa as janelas criadas recentemente (usando SetWinEventHook) e injeta DLL muito semelhante à do Der Hochstapler no processo da janela, se ainda não estiver presente. Ele descarrega as DLLs e restaura a funcionalidade original na saída.

Dos meus testes, ele 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, são necessários mais testes e eu gostaria de receber qualquer feedback sobre os cenários em que isso está acontecendo.


0

Eu descobri como parar a Barra de Tarefas de exibir uma janela de destino recém-ativada depois que você programaticamente ativa, maximiza e concentra a janela principal desse processo em outro processo. Primeiro de tudo, há muitas restrições sobre se essa operação será permitida.

"O sistema restringe quais processos podem definir a janela do primeiro plano. Um processo pode definir a janela do primeiro plano apenas se uma das seguintes condições for verdadeira:

  • O processo é o processo em primeiro plano.
  • O processo foi iniciado pelo processo em primeiro plano.
  • O processo recebeu o último evento de entrada.
  • Não há processo em primeiro plano.
  • O processo de primeiro plano está sendo depurado.
  • O primeiro plano não está bloqueado (consulte LockSetForegroundWindow).
  • O tempo limite do bloqueio do primeiro plano expirou (consulte SPI_GETFOREGROUNDLOCKTIMEOUT em SystemParametersInfo).
  • Nenhum menu está ativo.

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á permitir temporariamente que outro processo roube totalmente o primeiro plano, chamando AllowSetForegroundWindow com o ID do processo de destino. Depois disso, o processo de destino pode chamar o próprio SetForegroundWindow , usando seu próprio identificador de janela, e 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 redirecione todas as ativações do Explorer-click para a instância existente do aplicativo, você já tenha um (por exemplo) pipe nomeado para coordenar as coisas de qualquer maneira.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.