Originally posted May 14, 2009 at MSDN.
A Button-Mashers Guide to Input
Previous post: Twisted Pixels #3 Memory Mysteries
Windows Mobile 6.5 is the next version of Windows Mobile, and there are a few changes to how you program for the Windows Mobile platform. One of these changes was the subject of a recent blog post: Just say no to GAPI – What you need to know about AllKeys and input management.
Since this post is about input, go ahead and read that post, then come back and read the rest of this post about how input works in Windows Mobile, and how easy it can be to code for.
Note: Just a reminder, this series is currently looking at the unmanaged APIs exposed by Windows Mobile, and how those APIs can be used by game developers.
I was asked a few weeks ago to write a sample application that demonstrated the use of “AllKeys”. Since AllKeys is such a simple interface, I decided to make the task more challenging by writing an application that would display all key-press information, and would allow the user to see the difference in messages when AllKeys is turned on and when it is turned off.
This program allows you to confirm the behavior of all the keys on your device, which is always helpful. Different devices have different keys and buttons, and the mapping and behavior of these keys is not always obvious. I hope it is a useful addition to your toolbox! You can download the .exe as well as the source at: http://code.msdn.microsoft.com/tpix
Input Management
Input handling in Windows Mobile is not that different from the Windows desktop, and uses the same system of messages, but that does not help you if you have never programmed for Windows. Fortunately, handling input is one of the simpler parts of Windows programming, and the other parts (such as working with controls and child windows) aren’t usually needed for native game development. There are plenty of references that describe how Windows processes messages, so I will not go into details here (Charles Petzold’s books are a long time favorite of mine – See Programming Windows).
The bottom line is that input is sent to an application in the form of a message, and every Windows program has what is called a message loop to process these and other messages named WndProc. If you are building an application in Visual Studio and use one of the starter Templates such as “Win32 Smart Device Project”, this message loop will be created for you along with other bits of code needed to create a basic Windows Mobile application. Here is an example:
The Message Loop
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
static SHACTIVATEINFO s_sai;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_HELP_ABOUT:
DialogBox(g_hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, About);
break;
case IDM_OK:
SendMessage (hWnd, WM_CLOSE, 0, 0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_CREATE:
SHMENUBARINFO mbi;
memset(&mbi, 0, sizeof(SHMENUBARINFO));
mbi.cbSize = sizeof(SHMENUBARINFO);
mbi.hwndParent = hWnd;
mbi.nToolBarId = IDR_MENU;
mbi.hInstRes = g_hInst;
if (!SHCreateMenuBar(&mbi))
{
g_hWndMenuBar = NULL;
}
else
{
g_hWndMenuBar = mbi.hwndMB;
}
// Initialize the shell activate info structure
memset(&s_sai, 0, sizeof (s_sai));
s_sai.cbSize = sizeof (s_sai);
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here…
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
CommandBar_Destroy(g_hWndMenuBar);
PostQuitMessage(0);
break;
case WM_ACTIVATE:
// Notify shell of our activate message
SHHandleWMActivate(hWnd, wParam, lParam, &s_sai, FALSE);
break;
case WM_SETTINGCHANGE:
SHHandleWMSettingChange(hWnd, wParam, lParam, &s_sai);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
This is the default message handler created by Visual Studio in our starter project. Note that it has no input handlers (yet!).
Many applications have more than one message loop. In fact, message loops are associated with the creation of a window, and so each window can (in theory) have a message loop. On the other hand, some windows such as dialog boxes use the message loop of their parent for message handling. You can see an example of that in the code above – look for “case IDM_HELP_ABOUT:” which processes messages from the “About” dialog box.
Key Messages in the Message Loop
Since we are only interested in processing input at the moment, I’ll ignore the rest of these messages and focus only on keyboard and button presses. The messages that we are interested in are:
WM_KEYDOWN:
WM_KEYUP:
WM_SYSKEYDOWN:
WM_SYSKEYUP:
WM_CHAR:
WM_SYSCHAR:
WM_DEADCHAR:
WM_SYSDEADCHAR:
In actual practice, the only messages that Windows Mobile applications are usually interested in are:
WM_KEYDOWN:
WM_KEYUP:
WM_CHAR:
Input is really pretty simple – when a button or key is pressed, WM_Keydown message is sent to the application. When the key or button is released, WM_Keyup is sent. You can tell which button has been pressed by looking at the data that is included with the message.
In order to support different languages with a single hardware device, the OS is able to look at this VK key code and map it to one of many languages (which ever language is installed on your phone). This translation usually follows the WM_KEYDOWN message, and is called WM_CHAR. In the data for WM_CHAR, there is a field that contains the actual value of the character pressed. If your program is compiled to use Unicode, this will be the format of the character. For English devices, the WM_CHAR data is usually the same value as the WM_KEYDOWN value, but this can not be assumed for any other language, and might even change from version to version as language mappings are refined.
Working with Message Data
Message data is received by the message loop in the form of the cryptically named wParam and lParam parameters. These are two integer parameters that are passed into the message loop at the same time as the message.
Message data varies according to message type. In the example above, For the WM_COMMAND message, the data is mapped as follows:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
The message data for the keypress messages listed above includes the virtual key code, which is mapped like this:
long nVirtKey = wParam;
You can compare the value of the virtual key code with the predefined VK_ identifiers to determine which key was pressed.
Unlike message handling, the reference documentation for virtual key codes on MSDN is quite thorough. See the topic Using Virtual Key Codes, and it’s child topic Virtual Key Codes for the latest, most accurate information. More general information can be found in the Keyboard section of the documentation.
Character Mapping
In order to support different languages with a single hardware device, the OS is able to look at this VK key code and map it (depending on the language your phone is set to) to a specific character. This character is then sent to your application as part of a WM_CHAR message.
WM_CHAR usually follows the WM_KEYDOWN message, and is most often followed by the WM_KEYUP message. In the data for WM_CHAR, there is a field that contains the actual, language dependent character value of the key pressed. If your program is compiled to use Unicode (which is the default), this will be the format of the character. For English devices, the WM_CHAR data is usually the same value as the WM_KEYDOWN value, but this can not be assumed for any other language, and might even change from version to version as language mappings are refined.
Most Windows Mobile applications expect keyboard input to represent text, and so these applications pay attention to the WM_CHAR message, since this represents the value of the keypress in the local language. For game developers, needs may be different – a button or key may be mapped to a specific game action that has no relation to the local language – W,A,S,D,X may be mapped to direction keys, for example. In this case, it would be better to respond to the WM_KEYDOWN message, since the VK_ code from that message will be consistent across different languages.
Capturing Button Presses
The non-character buttons (such as the d-pad on many phones) are handled in a similar way, but there is a slight twist. Many of these buttons are tied to specific tasks in the operating system, and if you press them, the OS may intercept the message and it will never arrive in your application. Instead, (for example) you may find your application sent to the background as the phone dialer starts up.
There are times when game developers want to have access to these buttons, and if we stay alert and get out of the way when a call comes in, it is ok to ask the OS to send these messages to our own application for processing. In this way a game programmer can get the messages from the d-pad at the bottom of many devices, and can also get messages from the action button in the center of the d-pad. For an example of these buttons on a virtual device, look at the emulator image above.
You ask the OS to send your application all button press information is through the API call AllKeys. AllKeys (TRUE) tells the OS to send all button presses to the application, and AllKeys (FALSE) resets things so that the OS can do it’s own processing of these keys.
The best way (currently) to learn about AllKeys is to read up on it on this post: Just say no to GAPI – What you need to know about AllKeys and input management, and then download the source to the sample app mentioned in this article – look at the code, and run it on your device and see what happens when you start mashing buttons!
Note: You have to be especially sensitive when programming for a mobile device due to the fact that the device is – first and foremost – a phone, and you need to write code that allows your applications to get out of the way when a phone call comes in. In fact, that sounds like a good topic for a separate blog article, so lets leave it at that.