I am creating a windowed application, and I am currently working on a Windows platform layer in C. I want the application update loop to be suspended when its window is not in focus, and not suspended otherwise.
The way I currently handle events works fine for handling the [X]
(close) icon and the maximize icon (i.e. resizing the window). However, whenever I click the [-]
(minimize) icon at the top right of the application window that Windows spawns, the window disappears forever, and I cannot get it to reappear or get focus back to it to quit via key-bindings; I have to kill the application from the command line instead. The same happens any time the window changes focus to the background; it disappears and cannot be brought into focus again.
All my other event code that is unrelated to window focus changing works fine: key events, mouse events, window close/resize, etc.
Minimal reproducible example:
#include <stdio.h>
#include <string.h>
#include <windows.h>
typedef _Bool bool;
#define true ( ( bool ) 1 )
#define false ( ( bool ) 0 )
bool suspended;
bool running;
void
platform_update
( void )
{
MSG message;
while ( PeekMessageA ( &message , 0 , 0 , 0 , PM_REMOVE ) )
{
TranslateMessage ( &message );
DispatchMessageA ( &message );
}
}
LRESULT CALLBACK
platform_process_message
( HWND window
, UINT message
, WPARAM w_param
, LPARAM l_param
)
{
switch ( message )
{
case WM_CLOSE:
{
printf ( "Quitting application.\n" );
running = false;
return 0;
}
case WM_DESTROY:
{
PostQuitMessage ( 0 );
return 0;
}
case WM_ACTIVATE:
{
if ( running )
{
if ( LOWORD ( w_param ) == WA_INACTIVE )
{
printf ( "Suspending application.\n" );
suspended = true;
}
else
{
printf ( "Resuming application.\n" );
suspended = false;
}
}
}
break;
case WM_SIZE:
{
printf ( "Resized application window.\n" );
}
break;
}
return DefWindowProcA ( window , message , w_param , l_param );
}
int
main
( void )
{
HINSTANCE h_instance = GetModuleHandleA ( 0 );
// Register window class.
WNDCLASSA class;
memset ( &class , 0 , sizeof ( class ) );
class.style = CS_DBLCLKS;
class.lpfnWndProc = platform_process_message;
class.cbClsExtra = 0;
class.cbWndExtra = 0;
class.hInstance = h_instance;
class.hIcon = LoadIcon ( h_instance, IDI_APPLICATION );
class.hCursor = LoadCursor ( 0 , IDC_ARROW );
class.hbrBackground = 0;
class.lpszClassName = "test_window";
if ( !RegisterClassA ( &class ) )
{
printf ( "Error: RegisterClassA failed.\n" );
return 1;
}
// Initialize window dimensions.
int window_x = 100;
int window_y = 100;
int window_width = 200;
int window_height = 200;
// Initialize window style.
int window_style = WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION
| WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_THICKFRAME
;
int window_ex_style = WS_EX_APPWINDOW;
RECT border = { 0 , 0 , 0 , 0 };
AdjustWindowRectEx ( &border , window_style , 0 , window_ex_style );
window_x += border.left;
window_y += border.top;
window_width += border.right - border.left;
window_height += border.bottom - border.top;
// Create the window.
HWND window = CreateWindowExA ( window_ex_style
, class.lpszClassName
, "Test window"
, window_style
, window_x
, window_y
, window_width
, window_height
, 0
, 0
, h_instance
, 0
);
if ( !window )
{
printf ( "Error: CreateWindowExA failed.\n" );
return 2;
}
// Display the window.
ShowWindow ( window , SW_SHOW );
// Update loop.
running = true;
suspended = false;
while ( running )
{
if ( suspended )
{
continue;
}
platform_update ();
Sleep ( 16 );// Simulates other stuff being done here.
}
running = false;
// Destroy window.
if ( !DestroyWindow ( window ) )
{
printf ( "Error: DestroyWindow failed." );
return 3;
}
return 0;
}
When the window becomes inactive, you set suspended
to true
, which stops your main loop from calling platform_update()
ever again, thus your window stops receiving messages and can't behave properly from that point on.
You need to continue processing window messages, just suspend the rest of your code instead, eg:
while ( running )
{
platform_update (); // <-- move here
if ( suspended )
{
continue;
}
Sleep ( 16 );// Simulates other stuff being done here.
}
Do note, however, that if there are no messages to process, and no work to perform, your loop will become unyielding and eat up CPU cycles like crazy. You should put the thread to sleep when there is nothing to do. For instance, if platform_update()
doesn't receive a message, and suspended
is true, then call Sleep(0)
to yield the CPU before going to the next loop iteration, eg:
bool
platform_update
( void )
{
MSG message;
if ( !PeekMessageA ( &message , 0 , 0 , 0 , PM_REMOVE ) )
{
return false;
}
do
{
TranslateMessage ( &message );
DispatchMessageA ( &message );
}
while ( PeekMessageA ( &message , 0 , 0 , 0 , PM_REMOVE ) );
return true;
}
...
while ( running )
{
bool got_msg = platform_update ();
if ( suspended )
{
if ( !got_msg )
{
Sleep(0); // <-- add this
}
continue;
}
Sleep ( 16 );// Simulates other stuff being done here.
}