Alt tab event

Started by
13 comments, last by Evil Steve 15 years, 3 months ago
Until a week or so ago I had completely forgot about the 'alt tab' event (when a player 'alt tabs' out of the game and back in). I've decided to make this my priority. Long story short I've tried a couple of tutorials and I've had some issues. I was hoping someone would have a great tutorial (great as in, I'm a total idiot and have no idea what I'm doing) on how to handle the player 'alt tabbing' out of, and into, the game. If it matters I have my window's message pump in a separate thread.
Advertisement
It's extremely difficult to give you an answer with no knowledge of what version of the SDK you are using..

In the case of using Direct3D9, you must handle the device which might be in the lost state. You should then check whether or not the state of the device is in a state where the device can be reset and then reset it.

The SDK Documentation on this subject is all you need
Quote:Original post by Gamer Pro
If it matters I have my window's message pump in a separate thread.
Seperate from what? D3D calls really need to be made from the same thread as the window (And the window needs to be in the same thread as your window pump), or you're in for a massive amount of pain and reduced performance.
Wall of freaking text warning!!!!!! The important part is waaaaayyyy down at the end.

Okay, sorry about that guys, I had like a page and a half written down but decided that it wasn't needed as I was just asking for a tutorial on how to handle the 'alt tab' event. So here it is:

I'm using Microsoft Visual Studio 9 (2008 edition of C++) for my coding and I'm using the August 2008 version of directX9 SDK.

Insofar, I have simple opening credits that render when my game starts (actually that's all my game is at the moment). I have three credits that fade in to the background music (my game is a learning experience and I'm not going to 'release' it so the legal stuff doesn't apply), the game title is 'typed' (like a typewriter) onto screen, the subtitle 'pops' into existence and the background music fades out. At any given moment, the player can press the escape key to skip the credits. It was just as I was finishing up that I remembered about being able to 'alt tab' out.

I looked online for some tutorials and found the 'best' one at:
http://books.google.ca/books?id=1-NfBElV97IC&pg=PA82&lpg=PA82&dq=the+%27alt+tab%27+problem+%2B+programming&source=web&ots=SOojdpm_lC&sig=4qDZfZ-VylGByNp0xmO4Bn9pSq0&hl=en&sa=X&oi=book_result&resnum=3&ct=result#PPA83,M1

However, when I try to implement this method I have a major problem: It doesn't work, here's what happens: When my first credit fades in I try to alt tab (to see if it works). I have several desktop items come on on my screen (such as the window's start button in the bottom left of the screen, the taskbar in the lower right and the thumbnails of the varies open programs in the center of the screen) and these items stay up while my game continues to render my credit. It's like the desktop is fighting my game for control of the screen. Not only that, but the desktop items that have popped up flicker from being on screen to black (at a fast rate (I'm guessing 60Hz, my screen refresh speed)). I can still press the escape key to skip the credits despite having pressed alt tab and supposedly changing which window has control of the screen. This is exactly what happened BEFORE I tried the method in the e-book so using the wait event and WA_ACTIVE/WA_INACTIVE (or whatever it is) did nothing.

So I was hoping to get a link to a tutorial that will teach me how to handle this from a beginner point of view (as I basically am concerning this).

So for more info:

My fade in function basically acts like this:
while (total time rendering < rendering time for the credit) {
Update my game controller (determines how much time the previous loop took and if my game state needs to be updated).
Check if the player wants to skip the credit.
if my game state needs to be updated (determined by my game controller) then increases the alpha value variable.
draw my credit with the alpha value variable.
}

This is before I was worried about the alt tab event (I know I have to use Test Cooperative level to see if the device has been lost, call device lost and then device reset, ditto for the directX font and whatnot).

And for the message pump is separate thing. I read on: http://www.gamedev.net/reference/articles/article1249.asp about separating the message pump (I won't be surprised if I messed that up as well).

So what happens is my game begins execution, it creates a thread (I'll call this my game thread), sets a global variable controlling my message pump to true and enters the message pump (which keeps looping until the variable changes to false).
My game thread retrieves the system setting (such as screen height/width) from an XML file, creates the game window, my direct graphics object and my direct input object. Then the game thread calls a function that runs my opening credits, after they have been rendered, the variable controlling the message pump turns false and the window is destroyed.

Here's my code (in case this is confusing):
NOTE: the 'gameUpdateController' object is basically an encapsulation of the method outlines in: http://gafferongames.wordpress.com/game-physics/fix-your-timestep/

------------------------------------------------------------------------------

bool OpeningCredit::renderFadeIn() const{
/* Renders the credit fading in, taking as long as the stored amount of time (renderTime_). The
* calculated change in alpha is the 'idle' alpha change; that is, if all computer instructions
* were instantaneous, then the final alpha value will equal the stored alpha value. */

// Alpha variables:
const float alphaChange = ((float)this->fontAlpha_ / this->renderTime_)
* gameUpdateController::OPENING_CREDITS;
float currentAlpha = 0;

// Controller variables:
GameUpdateController guc(gameUpdateController::OPENING_CREDITS * 1000);

// Direct graphics variables:
D3DCOLOR color = D3DCOLOR_ARGB(0,
this->fontRed_,
this->fontGreen_,
this->fontBlue_);
IDirect3DDevice9* graphics = this->directGraphics_->getDevice();

// Font variables:
const int blue = this->fontBlue_;
const char* credit = this->caption_.c_str();
const int green = this->fontGreen_;
const int length = this->caption_.length();
const int red = this->fontRed_;
RECT textbox = this->location_;

// Keyboard variables:
int keyStroke;

// Time variables:
const float renderTime = this->renderTime_ * 1000;

// Fades the credit in.
while (guc.getTotalTime() < renderTime) {

// Updates the game controller.
guc.update();

// Checks if the player choose to skip the credit by pressing the escape key.
keyStroke = this->directInput_->detectKeystroke();
if (keyStroke == DIK_ESCAPE)
return false;

// Updates the game data if needed.
while(guc.shouldGameUpdate() == true) {

// Updates the current alpha value.
currentAlpha += alphaChange;
color = D3DCOLOR_ARGB((int)currentAlpha,
red,
green,
blue);
}

// Renders the credit with the current alpha value.
graphics->BeginScene();
this->font_->DrawText(NULL,
credit,
length,
&textbox,
DT_TOP | DT_LEFT | DT_NOCLIP,
color);
graphics->EndScene();
graphics->Present(NULL, NULL, NULL, NULL);
}

return true;
}


-------------------------------------------------------------------------------


DWORD WINAPI GameThread(LPVOID startingParameter) {
// This is the game thread. All the code for running the game goes here.

// Local variables:
int height;
string name;
int width;
const string settingsFile = "settings\\settings.xml";

// Retrieves the system settings.
getSystemSetttings(settingsFile, &name, &height, &width);

// Creates the game's resources (window, directX, etc).
GameWindow window(name.c_str(), width, height);
window.create();
DirectGraphics graphics(&window);
DirectInput input(window.getHandle());

if (testing::testMode == true) {

// Creates & displays the opening credits.
displayOpeningCredits(&graphics, &input);
}

// Clean up.
main::messagePumpActive = false;
DestroyWindow(window.getHandle());

return 0;
}



int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
/* This is were the game begins its execution. The game thread is created (handles all the code
* for the game) and the message pump is handled here. */

// Error variables:
const int error = -1;
const string errorTitle = "Error Occurance";
const string threadCreateFailed = "Unable to create the game thread";

// Local variables:
HANDLE gameThreadHandle;
MSG message;
DWORD gameThreadId;

// Creates the game thread.
gameThreadHandle = CreateThread(0, 0, &GameThread, 0, 0, &gameThreadId);
if (gameThreadHandle == NULL) {
MessageBox(NULL, threadCreateFailed.c_str(), errorTitle.c_str(), MB_ICONERROR | MB_OK);
exit(error);
}

// Activates the message pump.
main::messagePumpActive = true;

// Enters the message pump.
while (main::messagePumpActive == true) {

// Checks to see if there's a message waiting in the queue
if (PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {

// Translates keystroke messages into the correct format.
TranslateMessage(&message);

// Sends the message to the window's WindowProc function
DispatchMessage(&message);
}
}

return 0;
}


-------------------------------------------------------------------------------


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! THE IMPORTANT BIT: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Just to make things easier on all of us, I'm simply looking for a tutorial that will teach me how to hand the alt tab event, from beginning to end (from the window procedure (detecting the game window losing focus and whatnot) to resetting the directX device and font). This way you aren't getting confused by my code and we don't play 'message tag' by going 'did you do this?' and me answering.

Oh and a quick question: If I have windowed = false (in the direct graphics initialization (<variable>.Windowed = false;) will that have an effect on the alt tab thing? I ask because until I can get alt tab working, I have it set to true, which lets me switch windows as the game runs so I can stop it or use breakpoints or to do something else (obviously I change it to false when I test the alt tab) but I don't know if this has an effect.

[Edited by - Gamer Pro on January 7, 2009 8:27:26 PM]
while( msg.message != WM_QUIT ){	if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )	{		TranslateMessage( &msg );		DispatchMessage( &msg );	}	else	{		// If we're in a lost device state, stop processing input		// and sleep until our app window has regained focus		if ( g_bLostDevice )		{			// Test the current state of the devuce			HRESULT hr = g_pD3DDevice->TestCooperativeLevel();			// If we're ready, reset the device			if ( hr == D3DERR_DEVICENOTRESET )			{				OnLostDevice();				if ( FAILED( OnResetDevice() ) )				{					MessageBox( g_hWnd, TEXT("Error resetting device"), TEXT("D3D9 Sprite Tutorial"), MB_OK);					DestroyWindow( g_hWnd );				}				else					g_bLostDevice = FALSE;			}			else				//Otherwise, sleep				Sleep ( 15 );		}		// If we're not in a lost device state, just render 		// everything normally		else		{			FLOAT fDT = GetDT();			// Render the scene			HRESULT hr = Render( fDT );			if ( hr == D3DERR_DEVICELOST )				g_bLostDevice = TRUE;			else if ( FAILED( hr ) )			{				MessageBox( g_hWnd, TEXT("Error rendering scene"), TEXT("D3D9 Sprite Tutorial"), MB_OK);				DestroyWindow( g_hWnd );			}			// Get user input and update the scene			HandleInput ( fDT );		}	}}


Render() is where you'd call BeginScene/EndScene and do your drawing. The HRESULT returned is the return value of IDirect3DDevice9::Present.

In OnLostDevice, you would Release any buffers/surfaces in D3DPOOL_DEFAULT and also call OnLostDevice on any D3DX interfaces (ID3DXEffect, ID3DXSprite, etc.).

In OnResetDevice, you would call IDirect3DDevice9::Reset with your present parameters. You would also re-create any resources in D3DPOOL_DEFAULT, and call OnResetDevice for D3DX interfaces.
Okay, that's more or less what I've seen/read/tried to do. So, unfortunately, I think I'm going to have to go through this step by step (well, to a degree).

First off, I'm new to directX. What I've learned is from http://www.directxtutorial.com/ (the horror, I know, I know). But I've supplemented this with: http://www.drunkenhyena.com/cgi-bin/directx.pl. So my code does do error checking at the very lest.

So, step one: Getting the game to pause while it's not in focus.
Just to iterate (I've posted this in my second post, but who want to read all that?) when I try to alt tab out of the game while my credit is fading in, I have a couple of parts of the desktop pop up on screen but the game continues to render and I can even press the escape button to skip the credits (so, when I alt tab the window DOESN'T lose control of the screen). The parts of the desktop also flicker from being on screen to being black (like a strobe effect).

A part of my window procedure:
-------------------------------------------------------------------------------
// The message handler.
switch (message) {

// Checks if the window has been 'activated' or 'deactivated'.
case WM_ACTIVATE:
switch (wParam) {

// Checks if the window has been 'activated'.
case WA_ACTIVE:
SetEvent(altTab::wakeUpEvent);
return 0;

// Checks if the window has been 'deactivated'.
case WA_INACTIVE:
ResetEvent(altTab::wakeUpEvent);
return 0;

default:
return 0;
}
return 0;
-------------------------------------------------------------------------------

My fade in code:
-------------------------------------------------------------------------------
bool OpeningCredit::renderFadeIn() const{
/* Renders the credit fading in, taking as long as the stored amount of time (renderTime_). The
* calculated change in alpha is the 'idle' alpha change; that is, if all computer instructions
* were instantaneous, then the final alpha value will equal the stored alpha value. */

// Alpha variables:
const float alphaChange = ((float)this->fontAlpha_ / this->renderTime_)
* gameUpdateController::OPENING_CREDITS;
float currentAlpha = 0;

// Controller variables:
GameUpdateController guc(gameUpdateController::OPENING_CREDITS * 1000);

// Direct graphics variables:
D3DCOLOR color = D3DCOLOR_ARGB(0,
this->fontRed_,
this->fontGreen_,
this->fontBlue_);
IDirect3DDevice9* graphics = this->directGraphics_->getDevice();

// Font variables:
const int blue = this->fontBlue_;
const char* credit = this->caption_.c_str();
const int green = this->fontGreen_;
const int length = this->caption_.length();
const int red = this->fontRed_;
RECT textbox = this->location_;

// Keyboard variables:
int keyStroke;

// Time variables:
const float renderTime = this->renderTime_ * 1000;

// Fades the credit in.
while (guc.getTotalTime() < renderTime) {

// Updates the game controller.
guc.update();

// Updates the game data if needed.
while(guc.shouldGameUpdate() == true) {

// Updates the current alpha value.
currentAlpha += alphaChange;
color = D3DCOLOR_ARGB((int)currentAlpha,
red,
green,
blue);
}

// Renders the credit with the current alpha value.
graphics->BeginScene();
this->font_->DrawText(NULL,
credit,
length,
&textbox,
DT_TOP | DT_LEFT | DT_NOCLIP,
color);
graphics->EndScene();
graphics->Present(NULL, NULL, NULL, NULL);

// Checks if the player chooses to skip the credit by pressing the escape key.
keyStroke = this->directInput_->detectKeystroke();
if (keyStroke == DIK_ESCAPE)
return false;

// Checks if the player 'alt tabbed' out of the game.
WaitForSingleObject(altTab::wakeUpEvent, INFINITE);
}

return true;
}
-------------------------------------------------------------------------------

I know I don't check the cooperative level and reset my device/font. I'm not worried about that right now. Right now, I just want the game to pause when 'alt tabbed' out.

And for clarification my 'gameUpdateController' object basically determines how much time the previous loop took and adds that time to a time accumulator. The argument when the object is created is the desired game state update time (ie: every 1/60th of a second, the game state should be updated). When the 'shouldGameUpdate' function is called, it compares the time accumulated to the desired update rate. If there's enough time accumulated, the time accumulated is reduced by the update rate and returns true. I stole this idea from a website, but unfortunately, I forgot to keep the website.
It looks like you're making things really complicated with the whole event handling thing. If you only want to update and render when your window has focus, you can use GetForegroundWindow and then check with the result to see if you should update and render. However since you're using DirectInput for your keyboard input (which is a universally bad idea), you'll have to make sure your app isn't maintaining a hold on the keyboard (or mouse, if you're using DI for that too) while it doesn't have focus.

[Edited by - MJP on January 9, 2009 8:08:14 AM]
AAARRRGGGHHHHH.

Sorry, pent up aggression. After spending a few hours wandering around gamedev and the net in general, I decided to go with raw input for my input (as apposed to directInput). But WM_INPUT isn't 'firing'. (Slight tangent (I hope) to the topic. If it becomes to long of a tangent I'll leave the thread as is for now and start another one.)

Here's a portion of my code:
--------------------------------------------------------------------------------

// Error variables:
const int error = -1;
const string errorTitle = "Error Occurance";
const string registrationError = "A input device could not be registered.";

// Local variables:
RAWINPUTDEVICE device;
int deviceRegistered;

// Zeros the raw input device.
ZeroMemory(&device, sizeof(RAWINPUTDEVICE));

// Registers the keyboard.
device.dwFlags = 0;
device.hwndTarget = window.getHandle();
device.usUsage = 0x06;
device.usUsagePage = 0x01;

deviceRegistered = RegisterRawInputDevices(&device, 1, sizeof(RAWINPUTDEVICE));
if (deviceRegistered == FALSE) {
MessageBox(NULL, registrationError.c_str(), errorTitle.c_str(), MB_ICONERROR | MB_OK);
exit(error);
}

-------------------------------------------------------------------------------

// The message handler.
switch (message) {

// Checks if the player entered any input.
case WM_INPUT:
SetEvent(altTab::wakeUpEvent); // BREAK POINT HERE.
return 0;

-------------------------------------------------------------------------------

Registering the keyboard seems to work (if I change the test at the end to == TRUE (vs. the == FALSE)) the message box pops up, so I think it works. But I have a break point in my message handler where specified and when I press a keyboard button (or any key really) on my keyboard the break point isn't reached (I'll worry about the 'making my live more complicated by using events thing later, once I get the window procedure working).

I've looked at msdn.microsoft.com but I can't find anything related to my problem, I've include Windows.h and User32.lib (though if I don't include the User32 library, I don't get a compile error). And because I'm not getting a compile error I don't THINK I missed some other header file / library file that I need to include.

Using the 'search' bar in gamedev and given me a few threads with people with this error. About 2/3 of these just stop, so I'm thinking they must have figured it out (but they don't say so or what the problem was) and the other 1/3 or so is people forgetting to call 'RegisterRawInputDevices'.

So I'm thinking my 'RegisterRawInputDevices' ISN'T working as there are a few people that are using this method and a couple of them even say it works 'beautifully' (hence the pent up aggression).
Why on earth do you need raw input? Just use the window messages that are already being delivered to your window; WM_KEYDOWN and the like.

As far as I understand it, there's absolutely no reason to use raw input for the keyboard. For the mouse, maybe if you really really need high DPI input, but not the keyboard.
If you're trying to use the RawInput stuff and it won't compile, it's probably because you haven't defined the appropriate version macros before including windows.h. RawInput requires Windows XP, so you'll need to set your version at least that high.

This topic is closed to new replies.

Advertisement