WM_APPCOMMAND and Enhanced Input Devices

Microsoft Corporation

June 24, 1999

1.0 Introduction

To create a better user experience, hardware vendors are now adding additional keys and buttons to standard input devices (such as keyboards and mice) that open and control software applications. These additional input avenues can open applications, control audio and media applications, and open and control Internet browsers. Previous to the release of Microsoft Windows® 2000, Windows operating systems required hardware manufacturers to develop their own standards for these enhancements.

The Windows 2000 operating system Beta 3 provides native support for 15 new multimedia events, in the form of a new Windows API named WM_APPCOMMAND. This newly defined message notifies an application with focus when one of these new multimedia events occurs.

2.0 WM_APPCOMMAND-Enabled Applications on Native Operating Systems

The WM_APPCOMMAND API message, introduced in Windows 2000 Beta 3, defines a straightforward way for applications to receive notifications of an enhanced key-press event. The application can then initiate any predefined functionality associated with the particular key event. See Section 5.0 for a link to the list of defined messages. For example, an MDI application could use BACKWARD and FORWARD events to cycle through document windows. Adding a WM_APPCOMMAND handler enables your application to work with the new enhanced keyboards, mice, and any other new HID device that supports this extended functionality.

When a supported key or button is pressed, a series of events occurs that results in a WM_APPCOMMAND message being sent to the window with input focus. If this window does not handle the WM_APPCOMMAND message, the operating system will "bubble" this message to the window’s parent. This is repeated until the top-level window is reached. The process ends if one of the windows handles the message and returns TRUE. If the top-level window does not handle the message, the WM_APPCOMMAND message is passed on to the shell hook.

2.1 Using DefWindowProc for Message Cleanup

WM_APPCOMMAND generation depends upon an application correctly disposing of unused Windows messages. Developers should strive to always send unused keyboard, mouse, and WM_APPCOMMAND messages to DefWindowProc. Not cleaning up unused messages in this manner will break WM_APPCOMMAND generation.

2.2 Handling WM_APPCOMMAND vs. Virtual Key Codes

For most programmers, the natural way to handle keyboard key events is through the use of virtual keycodes (VKs) delivered through the WM_KEYDOWN and WM_KEYUP messages. In order to support the new multimedia keyboard keys and mouse buttons, it is highly recommended that VKs not be used. There are two main reasons for this. First, by using WM_APPCOMMAND, you will be building in support for all HID devices that can generate this message (such as keyboards and mice). As more support for enhanced devices of all types is added to Windows 2000, there will be a growing list of devices that will not generate VKs but will generate WM_APPCOMMANDs. Secondly, WM_KEYDOWN or WM_KEYUP messages are sent to the window with input focus and are not bubbled up to the parent window. This can allow controls to "eat" these key messages and thereby break WM_APPCOMMAND generation (see Section 3.0).

Figure 1. WM_APPCOMMAND on Windows 2000 (subject to change)

3.0 Troubleshooting

Problem: Your WM_APPCOMMAND-enabled application does not respond to WM_APPCOMMAND messages.

Possible cause: Not returning the correct Return value.

Returning the correct value at each stage of the process is very important. When a WM_KEYDOWN message is handled, the return value should be FALSE (or 0). When a WM_XBUTTON or WM_APPCOMMAND message is handled, the return value should be TRUE (or 1). See Figure 1 for more details and Appendix A for an example application.

Possible cause: Not forwarding unhandled messages to DefWindowProc.

The most common mistake made when adding WM_APPCOMMAND support to an application is not calling DefWindowProc for message cleanup (that is, sending unhandled messages to DefWindowProc). In Windows 2000, WM_APPCOMMAND messages are generated by DefWindowProc when known virtual key codes or XButton messages arrive there. See Figure 1 for more details and Appendix A for an example application.

Possible cause: Controls are eating messages.

Note that WM_KEYDOWN messages are sent to the window with focus (and not the foreground window) and are not "bubbled" up to the parent window. This means that buttons and other controls can receive these messages and can "eat" them. It is very important to use DefWindowProc for message cleanup in all application message handlers. See Figure 1 for more details and Appendix A for an example application.

4.0 WM_APPCOMMAND Requirements

Using Microsoft IntelliType Pro 1.0 or IntelliPoint 3.0 software enhances the native WM_APPCOMMAND support found in Windows 2000. IntelliType Pro and IntelliPoint emulate this enhanced WM_APPCOMMAND support on legacy Microsoft operating systems.

With Microsoft IntelliType Pro:

Windows 2000: Version 1.0 enhanced support for Beta 3. Version 1.1 enhanced support for final version of Windows 2000.
Windows NT4.0: Service Pack 3 and later support provided by IntelliType Pro 1.0+.
Windows 95/98: Support provided by IntelliType Pro 1.0+.
Windows CE: Unsupported.

With Microsoft IntelliPoint:

Windows 2000: Version 3.0 enhanced support for Beta 3. Version 3.01 enhanced support for final version of Windows 2000.
Windows NT4.0: Service Pack 3 and later support provided by IntelliPoint 3.0+.
Windows 95/98: Support provided by IntelliPoint 3.0+.
Windows CE: Unsupported.

5.0 WM_APPCOMMAND Specifics

See WM_APPCOMMAND in the Platform SDK.

6.0 How to Handle the WM_APPCOMMAND Message

An application would process this message in a similar way to how it would handle a menu or toolbar command. For example, menu commands might be handled in a window procedure like this:

   case WM_COMMAND:
      switch (wParam)
      {
      case IDC_PLAYPAUSE:
OnPlayPause(hwnd);
         return FALSE;

      case IDC_STOP:
         OnStop(hwnd);
         return FALSE;

      case IDC_PREV:
         OnPrev(hwnd);
         return FALSE;

      case IDC_NEXT:
         OnNext(hwnd);
         return FALSE;
      
[other commands…]
}
[…]

   return DefWindowProc(hwnd, message, wParam, lParam);

Commands from the keyboard would be handled in a similar fashion. An important difference is that a WM_APPCOMMAND handler must return 1 if the message is handled, rather than the more common return code of 0. It is also important to pass unused codes to DefWindowProc(), so that other applications will have the chance to handle them.

   case WM_APPCOMMAND:
      switch (GET_APPCOMMAND_LPARAM(lParam))
      {
      case APPCOMMAND_MEDIA_PLAY_PAUSE:
      lr = OnPlayPause(hwnd);
      if (lr==NOERROR) return TRUE;
      break;

   case APPCOMMAND_MEDIA_STOP:
      lr = OnStop(hwnd);
      if (lr==NOERROR) return TRUE;
      break;

   case APPCOMMAND_MEDIA_PREVIOUSTRACK:
      lr = OnPrev(hwnd);
      if (lr==NOERROR) return TRUE;
      break;

   case APPCOMMAND_MEDIA_NEXTTRACK:
      lr = OnNext(hwnd);
      if (lr==NOERROR) return TRUE;
      break;
      }

      return DefWindowProc(hwnd, message, wParam, lParam);

6.1 A Sample WM_APPCOMMAND-Enabled Application

Appendix A contains code for a simple media player that handles all the WM_APPCOMMAND media events.

7.0 Microsoft’s IntelliType Pro Software for Enhanced Keyboards

In 1999, Microsoft’s Hardware division introduced three new enhanced keyboards. New keyboard keys (Hot Keys) allow users to control their Internet browsers, media players, and mail clients, and to launch applications without using the mouse. On Windows 2000, the IntelliType Pro software enhances the existing native operating system support. On older, legacy Microsoft operating systems, IntelliType Pro adds complete support for the new keys.

7.1 WM_APPCOMMAND with Microsoft IntelliType Pro on Windows 2000

Running Microsoft IntelliType Pro on Windows 2000 enhances native WM_APPCOMMAND support by allowing non-foreground applications to be queried with a subset of the WM_APPCOMMAND messages (all media messages and "Home"). If you are running IntelliType Pro and the foreground window does not handle the WM_APPCOMMAND-generated message (and use DefWindowProc to clean up this unused message), then IntelliType Pro forwards the WM_APPCOMMAND message to each of the main windows existing in the z-order until either a window uses the message or all windows ignore it.

7.2 WM_APPCOMMAND with Microsoft IntelliType Pro on Legacy Operating Systems

To ensure consistent application functionality across operating systems, IntelliType Pro emulates Windows 2000 native WM_APPCOMMAND support on previously released Microsoft operating systems. IntelliType Pro also provides the enhanced functionality described in Section 7.1 above. Win32® applications running under Windows 95, Windows 98, or Windows NT 4.0 (SP3 and later) will receive the WM_APPCOMMAND message upon enhanced keyboard key press if the IntelliType Pro software is installed. WM_APPCOMMAND-enabled applications will function as if they were running on a native operating system.

7.3 Adding Media Players to the IntelliType Pro Media Menu

Pressing the Media key brings up a menu of known multimedia players currently loaded on the computer. The menu should only list players with native WM_APPCOMMAND support. It is possible to add new players to this list, but do not add your player unless it works with all the media keys on the keyboard. When the Media button is first pressed, a search for known media players is initiated. A list of supported media players then appears in the registry under the key:

HKEY_CURRENT_USER\Software\Microsoft\Keyboard

To add a media player to this list, all you need is the path to the .exe file of the new player and the name you want to have displayed in the Media Menu.

7.3.1 When to add a new player

A new player being installed onto a computer system should add itself to the IntelliType Pro media menu through the registry at installation time. This process only needs to be performed once. The nature of the registry does not allow multiple copies of the same key. While all applications added in this way will appear in the media menu, only those which are WM_APPCOMMAND-enabled or supported by IntelliType Pro software will function using the enhanced keyboard buttons.

7.3.2 When will the new player be listed in the media menu?

The newly added player should be visible in the media menu immediately after it is added to the registry and the "Media" key is pressed. Only legitimate .exe files are listed. On each Media key, press the IntelliType Pro software verifies that the .exe file exists on the listed path before it is listed in the Media Menu.

7.3.3 Steps to add a player to the media menu:

  1. Under the registry key
    HKEY_CURRENT_USER\Software\Microsoft\Keyboard
    check for a key titled "Native Media Players." If it does not exist, create it. (See Appendix B for an example of how to create a key.)

  2. Under the key
    HKCU\Software\Microsoft\Keyboard\Native Media Players
    create a key which will have two string values. The name of this key should be descriptive of the new device being added.

  3. Inside the newly created key, add a string value titled "AppName." The data of this value should be the name you wish to appear in the media menu (for example, "NewPlayer").

  4. Inside the newly created key, add a string value titled "ExePath." The data of this value should be the exact path to the player’s .exe file (for example, c:\program files\newplayer\newplayer.exe).

7.3.4 Sample code

Appendix B contains sample code that could be used to add a media player to the IntelliType Pro media menu.

8.0 WM_APPCOMMAND and Microsoft Mice

Microsoft has launched its new five-button IntelliMouse® Explorer. The new buttons navigate forward and back by default, the same as Forward and Back buttons in Internet browsers. Windows 2000 has message support built in for these buttons. On older, legacy Microsoft operating systems, the new IntelliPoint 3.0 software adds complete support for the new buttons. IntelliPoint 3.0 also allows this functionality to be assigned to any button on all of Microsoft’s mouse products.

8.1 New Messages on Windows 2000

Windows 2000 added new messages for the new mouse buttons. These are the XButton messages (see Section 9.0 for links to full descriptions of these messages). They are similar to the messages for the three existing buttons, with the primary difference being that a single message is used for both buttons. A flag is set in the wParam to specify which button performed the action.

If one is writing an application with Forward and Back functionality, it is preferable to handle the WM_APPCOMMAND message. This will allow the Forward/Back functionality regardless of whether a mouse, keyboard, or some other future device is connected.

If instead one desires to use the button for a mouse-related action, such as an alternate tool in a CAD application, the preferred method is to respond to the XButton messages themselves. When acting on the XButton messages, it is important to return TRUE, so as to prevent the WM_APPCOMMAND message from being generated. If an application doesn’t use the WM_APPCOMMAND message itself, IntelliPoint will attempt to perform a Forward/Back action in an application with an unhandled WM_APPCOMMAND message.

8.2 WM_APPCOMMAND with Microsoft IntelliPoint on Legacy Operating Systems

To ensure consistent application functionality across operating systems, IntelliPoint 3.0 emulates Windows 2000 native WM_APPCOMMAND and XButton support on previously released Microsoft operating systems. When IntelliPoint 3.0 is installed, Win32 applications running under Windows 95, Windows 98, or Windows NT 4.0 will receive the XButton and WM_APPCOMMAND messages as if they were running on a native operating system.

9.0 New Windows Messages to Support Five-Button Mice

The following are the new messages that are added to support the new five-button mice. Their definitions can be found in winuser.h in the Windows 2000 SDK.

WM_XBUTTONDOWN

WM_XBUTTONUP

WM_XBUTTONDBLCLK

WM_NCXBUTTONDOWN

WM_NCXBUTTONUP

WM_NCXBUTTONDBLCLK

Appendix A—Sample Media Player Using WM_APPCOMMAND

This sample is a simple media player and handles all the WM_APPCOMMAND media events from an enhanced keyboard. Adding enhanced mouse support would simply entail adding message handlers for the XButton messages.

#include <Windows.h>
#include "resource.h"


LPARAM WINAPI MainWndProc( HWND,UINT,WPARAM,LPARAM );
BOOL InitDialog( HWND );
BOOL OnClose(HWND hwnd);
BOOL OnTimer(HWND hwnd);

BOOL OnPlayPause(HWND hwnd);
BOOL OnStop(HWND hwnd);
BOOL OnPrev(HWND hwnd);
BOOL OnNext(HWND hwnd);


//
//The supported WM_APPCOMMAND events
//
#ifndef WM_APPCOMMAND

#define WM_APPCOMMAND               0x319
#define APPCOMMAND_MEDIA_NEXTTRACK         11
#define APPCOMMAND_MEDIA_PREVIOUSTRACK      12
#define APPCOMMAND_MEDIA_STOP            13
#define APPCOMMAND_MEDIA_PLAY_PAUSE         14
#define FAPPCOMMAND_MASK  0x8000
#define GET_APPCOMMAND_LPARAM(lParam) ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK))

#endif


//
//Player constants
//
const UINT DISPLAY_TIMER_ID = 100;
const UINT TIMER_INTERVAL = 1000;
const UINT NUM_TRACKS = 10;

HINSTANCE hInstance = 0;

UINT trackIndex;
UINT trackTime;

CHAR szTrackIndex[3];
CHAR szTrackTime[5];

BOOL isPlaying;
BOOL isBetweenTracks;

//------------------------------------------------------------
//  WinMain()
//  
//   Main windows routine. All the usual stuff.
//
//-------------------------------------------------------------
INT PASCAL WinMain( HINSTANCE hInstance, 
                    HINSTANCE hPrevInstance, 
                    LPSTR  lpCmdLine, 
                    int    nCmdShow )
{
    ::hInstance = hInstance;

//
// Register the main window class
//
WNDCLASS  wc;

wc.style = CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = (WNDPROC)MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = DLGWINDOWEXTRA;
wc.hInstance = hInstance;       
wc.hIcon        = 0;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground= CreateSolidBrush(GetSysColor(COLOR_BACKGROUND));
wc.lpszMenuName =  NULL;
wc.lpszClassName= "APP_CMD";

if(!RegisterClass(&wc))
return FALSE;


// Create the main window as dialog. 
//
HWND hwndMain = CreateDialog(hInstance, "APP_CMD", 0, NULL);
   ShowWindow(hwndMain,SW_SHOWNORMAL);

   //initialize
   InitDialog(hwndMain);

MSG msg;
   while (GetMessage(&msg, NULL,0,0)) 
   {
              TranslateMessage(&msg);
              DispatchMessage(&msg);
   }
   return (msg.wParam);
} // end WinMain()




///*------------------------------------------------------------
//
//   MainWndProc() - Main window callback procedure.
//  
// -------------------------------------------------------------

LPARAM WINAPI MainWndProc( HWND hwnd, 
                           UINT msg,
                           WPARAM wParam,
                           LPARAM lParam )
{
   switch (msg){

   //Handle the WM_APPCOMMAND messages here. Return TRUE if we handle the message.
//
   case WM_APPCOMMAND:
      switch (GET_APPCOMMAND_LPARAM(lParam))
      {
      case APPCOMMAND_MEDIA_PLAY_PAUSE:
         OnPlayPause(hwnd);
         return TRUE;

      case APPCOMMAND_MEDIA_STOP:
         OnStop(hwnd);
         return TRUE;

      case APPCOMMAND_MEDIA_PREVIOUSTRACK:
         OnPrev(hwnd);
         return TRUE;

      case APPCOMMAND_MEDIA_NEXTTRACK:
         OnNext(hwnd);
         return TRUE;
      }
      break;

   case WM_CLOSE:
      return OnClose(hwnd);

   case WM_TIMER:
      return OnTimer(hwnd);

   //Handle the interface messages here. Return FALSE if we handle the message
   //
   case WM_COMMAND:
      switch (wParam)
      {
      case IDC_PLAY:
      case IDC_PAUSE:
         OnPlayPause(hwnd);
         return FALSE;

      case IDC_STOP:
         OnStop(hwnd);
         return FALSE;

      case IDC_PREV:
         OnPrev(hwnd);
         return FALSE;

      case IDC_NEXT:
         OnNext(hwnd);
         return FALSE;
      }
      break;
   }
   //Clean up any unused messages by calling DefWindowProc
   //
   return DefWindowProc(hwnd,msg,wParam,lParam);
}


//
//Initialize the player UI
//
BOOL InitDialog(HWND hwnd)
{
   // start on first track
   trackIndex = 0;

   // start at beginning of track
   trackTime = 0;

   // don't play until the user tells us to
   isPlaying = FALSE;


   // set the icon for the "play" button
   HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PLAY));
   SendDlgItemMessage(hwnd,IDC_PLAY,BM_SETIMAGE,IMAGE_ICON,(LPARAM)hIcon);

   // set the icon for the "pause" button
   hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PAUSE));
   SendDlgItemMessage(hwnd,IDC_PAUSE,BM_SETIMAGE,IMAGE_ICON,(LPARAM)hIcon);

   // set the icon for the "stop" button
   hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_STOP));
   SendDlgItemMessage(hwnd,IDC_STOP,BM_SETIMAGE,IMAGE_ICON,(LPARAM)hIcon);

   // set the icon for the "previous" button
   hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PREV));
   SendDlgItemMessage(hwnd,IDC_PREV,BM_SETIMAGE,IMAGE_ICON,(LPARAM)hIcon);

   // set the icon for the "next" button
   hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_NEXT));
   SendDlgItemMessage(hwnd,IDC_NEXT,BM_SETIMAGE,IMAGE_ICON,(LPARAM)hIcon);


   // start a timer to update the track time display
   SetTimer(hwnd, DISPLAY_TIMER_ID, TIMER_INTERVAL, NULL);

   return TRUE;
}

//
//Close the Application
//
BOOL OnClose(HWND hwnd)
{
   KillTimer(hwnd, DISPLAY_TIMER_ID);
   DestroyWindow(hwnd);
   PostQuitMessage(0);
   return TRUE;
}

//
//Update Track UI
//
void UpdateTrackInfo(HWND hwnd)
{
   wsprintf(szTrackIndex, "%d", trackIndex+1);
   SetDlgItemText(hwnd, IDC_TRACKINDEX, szTrackIndex);

   wsprintf(szTrackTime, "0:%02d", trackTime);
   SetDlgItemText(hwnd, IDC_TRACKTIME, szTrackTime);
}

//
//Timer controls how fast the UI "time" display changes
//
BOOL OnTimer(HWND hwnd)
{
   if (isPlaying)
   {
      // make sure the display is visible
      UpdateTrackInfo(hwnd);
      HWND hwndDisplay = GetDlgItem(hwnd, IDC_TRACKTIME);
      ShowWindow(hwndDisplay, SW_SHOW);

      // advance the counter
      ++trackTime;

      // every track is 60 seconds
      if (trackTime >= 60)
         OnNext(hwnd);   // advance to the beginning of the next track
   }
   else
   {
      // get the track time window
      HWND hwndDisplay = GetDlgItem(hwnd, IDC_TRACKTIME);

      // flash the display
      if (IsWindowVisible(hwndDisplay))
         ShowWindow(hwndDisplay, SW_HIDE);
      else
         ShowWindow(hwndDisplay, SW_SHOW);
   }

   UpdateTrackInfo(hwnd);
   return TRUE;
}

//
//Start/Pause the UI’s "time" index
//
BOOL OnPlayPause(HWND hwnd)
{
   // toggle between play and pause
   isPlaying = !isPlaying;

   UpdateTrackInfo(hwnd);
   return TRUE;
}

//
//Stop the UI’s "time" index
//
BOOL OnStop(HWND hwnd)
{
   // stop playing and move to beginning of track
   isPlaying = FALSE;
   trackTime = 0;

   UpdateTrackInfo(hwnd);
   return TRUE;
}

//
//Goto the previous track
//
BOOL OnPrev(HWND hwnd)
{
   // if the user is at the beginning of a track
   if (trackTime<1)
   {
      // set the track index to the previous track
      if (trackIndex==0)
         trackIndex = NUM_TRACKS-1;
      else
         --trackIndex %= NUM_TRACKS;
   }

   // go back the beginning of the track
   trackTime = 0;

   UpdateTrackInfo(hwnd);
   return TRUE;
}

//
//Goto the next track
//
BOOL OnNext(HWND hwnd)
{
   // go to the beginninf of the next track
   ++trackIndex %= NUM_TRACKS;
   trackTime = 0;

   UpdateTrackInfo(hwnd);
   return TRUE;
}

Appendix B—Sample Code for Adding Media Players to IntelliType Pro Media Menu

#define MSKB_KEY "Software\\Microsoft\\Keyboard\\Native Media Players"

BOOL AddPlayerToRegistry ( LPSTR szDisplayString, LPSTR szExePath )
{
   //open Microsoft Keyboard Native Media key OR if it does not exist, create it 
   HKEY hKey;
   DWORD dwDisposition;
   if ( ERROR_SUCCESS != RegCreateKeyEx( 
      
            HKEY_CURRENT_USER, 
            MSKB_KEY, 
            0, 0, 
            REG_OPTION_NON_VOLATILE, 
            KEY_ALL_ACCESS, 
            0, 
            &hKey, 
            &dwDisposition 
            ) 
   ) return 0;


   //create a key for new media player (using DisplayString as key name)
   HKEY hSubKey;
   if ( ERROR_SUCCESS != RegCreateKeyEx( 
      
            hKey, 
            szDisplayString, 
            0, 0, 
            REG_OPTION_NON_VOLATILE, 
            KEY_ALL_ACCESS, 
            0, 
            &hSubKey, 
            &dwDisposition 
            ) 
   ) return 0;


   //Add AppName string value to new key and copy in the DisplayString
   if ( ERROR_SUCCESS != RegSetValueEx( 
      
            hSubKey, 
            "AppName", 
            0, 
            REG_SZ, 
             (const BYTE*)szDisplayString, 
            strlen(szDisplayString) 
            ) 
   ) return 0;


   //Add ExePath string value to new key and copy in the ExePath
   if ( ERROR_SUCCESS != RegSetValueEx( 
      
            hSubKey, 
            "ExePath", 
            0, 
            REG_SZ, 
             (const BYTE*)szExePath, 
            strlen(szExePath) 
            ) 
   ) return 0;


   //close reg keys
   if ( ERROR_SUCCESS != RegCloseKey(hKey) ) return 0;
   if ( ERROR_SUCCESS != RegCloseKey(hSubKey) ) return 0;

   return 1;
}