5

I am trying to detect Alt+Tab in a Win Event Hook using SetWinEventHook. However the callback for the hook is never triggering for Alt+Tab, even though I have a message pump (I am on windows 10)

I came across this idea to process Alt+Tab like this when I stumbled across this series of posts:

Alt Tab overlay Win32 identificator

SetWinEventHook does not catch any event

Which describe using a WinEventHook to get the Alt+Tab message (which I don't know if it is even efficient or not to use in the modern day). I found an article by Raymond Chen detailing it (however it doesn't work anymore and I don't know why, but it should.)

How do I get the Alt+Tab message in my event callback?

EDIT: Here is my example to mimic what I was doing on a large scale (the small program doesn't work) and to remove doubt from the comments. Here is the entire program plus compile script that shows what I am trying to do but doesn't work at all (what am I doing wrong? the above Raymond Chen Program seems to be effected by it). I am on Windows 10

(Edit, I found EVENT_MIN and EVENT_MAX give me events in the hook, but never the EVENT_SYSTEM_SWITCHSTART and EVENT_SYSTEM_SWITCHEND events)

Compile.bat:

@echo off
if not defined DevEnvDir (
    call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
)

if "%Platform%" neq "x64" (
    echo ERROR: Platform is not "x64" - previous bat call failed.
    exit /b 1
)

set FILES=main.cpp

set RELEASEFLAGS=/O2 /DMAINDEBUG=0
set DEBUGFLAGS=/Zi /DMAINDEBUG=1

set LIBS=kernel32.lib user32.lib gdi32.lib Winmm.lib

::Release
cl /nologo /W3 /GS- /Gs999999 %RELEASEFLAGS% %FILES% /Fe: AnnoyingSound.exe %LIBS% /link /incremental:no /opt:icf /opt:ref /subsystem:windows

::Debug
cl /nologo /W3 /GS- /Gs999999 %DEBUGFLAGS% %FILES% /FC /Fe: AnnoyingSoundDebug.exe %LIBS% /link /incremental:no /opt:icf /opt:ref /subsystem:console

main.cpp:

#define STRICT
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>

HINSTANCE hinst; 
HWND hwndMain; 

LRESULT CALLBACK
WndProc(
    HWND Window,   
    UINT Message,  
    WPARAM WParam, 
    LPARAM LParam) 
{
  if( Message == WM_CLOSE ) 
  {
    PostQuitMessage(0);
  } 
  else
  {
    LRESULT Result  = DefWindowProc( Window, Message, WParam, LParam );
    return Result;
  }
  return 0;
}


HWND g_hwndAltTab = nullptr;
void CALLBACK WinEventProc(
    HWINEVENTHOOK hWinEventHook,
    DWORD event,
    HWND hwnd,
    LONG idObject,
    LONG idChild,
    DWORD dwEventThread,
    DWORD dwmsEventTime
)
{
  printf("Here!\n"); //This never gets called when Alt+Tab pressed
  PCTSTR pszSound = nullptr;
  switch (event) 
  {
    case EVENT_SYSTEM_SWITCHSTART:
      if (!g_hwndAltTab) 
      {
        g_hwndAltTab = hwnd;
        pszSound = "C:\\Windows\\Media\\Speech On.wav";
      }
      break;
    case EVENT_SYSTEM_SWITCHEND:
      if (g_hwndAltTab) 
      {
        g_hwndAltTab = nullptr;
        pszSound = "C:\\Windows\\Media\\Speech Sleep.wav";
      }
      break;
  }
  if (pszSound) {
    PlaySound(pszSound, nullptr, SND_FILENAME | SND_ASYNC);
  }

}

#if MAINDEBUG
int main()
#else
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
#endif
{ 
#if !MAINDEBUG
  UNREFERENCED_PARAMETER(lpCmdLine); 
#endif
  { 
    WNDCLASS wc;
    wc.style = 0; 
    wc.lpfnWndProc = (WNDPROC) WndProc; 
    wc.cbClsExtra = 0; 
    wc.cbWndExtra = 0; 
    wc.hInstance = GetModuleHandle( NULL );
    wc.hIcon = LoadIcon((HINSTANCE) NULL, 
        IDI_APPLICATION); 
    wc.hCursor = LoadCursor((HINSTANCE) NULL, 
        IDC_ARROW); 
    wc.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH); 
    wc.lpszMenuName =  "MainMenu"; 
    wc.lpszClassName = "MainWndClass"; 
    
    if (!RegisterClass(&wc)) 
        return FALSE; 
  } 
  hinst = GetModuleHandle( NULL );  // save instance handle 

  HWINEVENTHOOK hWinEventHook = SetWinEventHook(
     EVENT_SYSTEM_SWITCHSTART, EVENT_SYSTEM_SWITCHEND,
     nullptr, WinEventProc, 0, 0,
     WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);

    hwndMain = CreateWindow("MainWndClass", "Sample", 
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 
        CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL, 
        (HMENU) NULL, hinst, (LPVOID) NULL); 
 
    // If the main window cannot be created, terminate 
    // the application. 
 
  if (!hwndMain) 
      return FALSE; 

  if (hWinEventHook)
  {
    ShowWindow(hwndMain, SW_SHOW); 
    UpdateWindow(hwndMain); 
    MSG msg;
    while(GetMessage( &msg, NULL, 0, 0 ))
    {
      if( msg.message == WM_QUIT ) 
      {
        PostQuitMessage(0);
      } 
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }

    UnhookWinEvent(hWinEventHook);
  }
  return 0;
}

For context of what I am doing:

I am making my own rendering engine in DirectX12, and I am doing all the Win32 stuff manually. One thing I want to do is minimize the window on Alt+Tab when in fullscreen. (I heard DirectX renders on top of the window, so that the window is minimized, but DirectX keeps rendering on top of where the window was, so I need to process Alt+Tab and then stop the rendering (I don't know if that I true, just saw it in a forum post)). So I need a way of detecting Alt+Tab and processing it. (if this doesn't work, I may look into accelerators, but I don't know if that will work with Alt + Tab)

Going through old StackOverflow stuff, I found these two links, but all the examples are either dead or irrelevant:

Handle Alt Tab in fullscreen OpenGL application properly

Handling minimisation in Fullscreen Win32 OpenGL

yosmo78
  • 489
  • 4
  • 13
  • many messages not returned from `PeekMessage` but posted direct to windows procedure. you of course never got `WM_KEYUP` or `WM_KEYDOWN`, `WM_INPUT`, .. without window – RbMm May 01 '22 at 09:22
  • 1
    Please read about the [XY Problem](https://xyproblem.info) and verify that this is or isn't. – IInspectable May 01 '22 at 10:14
  • @RbMm I did get `WM_INPUT` outside the window though... – yosmo78 May 01 '22 at 17:28
  • @IInspectable even if it is the XY problem in the long run (which I don't believe it is) for full screen minimization for DirectX. It still would be good to at least have a deeper understanding of the API in use, because the old tutorials are outdated. Even this [answer](https://stackoverflow.com/questions/972299/best-practices-for-alt-tab-support-in-a-directx-app) has an answer talking about forcing minimization when an alt tab is detected. And the old Raymond Chen article tells me that this is the way to detect Alt+Tab – yosmo78 May 01 '22 at 17:34
  • 1
    You must handle messages inside WindowProc , but not after PeekMessage, because same messages never returned from PeekMessage but direct sent to WindowProc – RbMm May 01 '22 at 21:28
  • @RbMm I updated my example to have the message pump do exactly what you say and it still doesn't work – yosmo78 May 02 '22 at 01:41
  • Can't you just detect that your window has been minimized? What's significant about knowing it was minimized via Alt+Tab? – Jonathan Potter May 03 '22 at 12:26
  • `EVENT_SYSTEM_SWITCHSTART` and `EVENT_SYSTEM_SWITCHEND` generated only if nobody register *ALT+TAB* handler and system default handler (in csrss kernel) post it. but by default *explorer.exe* register self handler and this events not posted. `EVENT_SYSTEM_FOREGROUND` for `ForegroundStaging` class window will be – RbMm May 04 '22 at 11:45
  • 3
    It appears that this problem had been around for a while and the way they handled it was to listen on the keys strokes and assume the switch - https://stackoverflow.com/questions/49588438/the-system-events-event-system-switchstart-and-event-system-switchend-works-in-w – ODgn May 06 '22 at 20:25

0 Answers0