Using Multimedia Keyboard Keys in Your Own Program

Environment: VS.NET (2003), Windows 2000, Windows XP

When I bought my current computer, it came with a multimedia keyboard that has a lot more keys than usual. There are keys for controlling a media player and some browser keys, such as forward, back, and so forth.

The Windows media player can be controlled by these keys, even if it is in the background; this is a very convenient feature. My version of Winamp sadly does not make use of the keys.

When I looked for a MP3/Ogg player with an integrated media library and available source code (so I could change things if neccesary), I stumbled over Musik. This is a Linux/Windows MP3/Ogg player using wxwindows2 for the GUI and sqlite as the database. It is quite fast, at least faster than mediaplayer and musicmatch jukebox, concerning the startup time and media library browsing. It is available at http://musik.sf.net.

Musik also didn’t make use of the special keys, which I got accustomed to, so I started to add this feature for Windows. After reading some MSDN, I found out that you have to handle the WM_APPCOMMAND message, which is available to your program code, if you define _WIN32_WINNT=0x0500 (or higher) in your project. WM_APPCOMMAND can be found in winuser.h, if you have the right SDK headers. VS.NET already has these. For VC 6, I am not quite sure.

Your window proc of your window that should handle the WM_APPCOMMAND message has to do something like the following code does:


if(message == WM_APPCOMMAND)
{
switch(GET_APPCOMMAND_LPARAM(lParam))
{
case APPCOMMAND_MEDIA_NEXTTRACK:
// do something, which skips the track
return 1;
case APPCOMMAND_MEDIA_PREVIOUSTRACK:
// do something…
return 1;
case APPCOMMAND_MEDIA_STOP:
// do something, which stops playing
return 1;
case APPCOMMAND_MEDIA_PLAY_PAUSE:
// toggle between play and pause
return 1;
}
}
// handle other messages or call default window proc.

This shows how to use the playing control keys, but you can use all other keys, too, if you use the corresponding APPCOMMAND_XXX code. (See MSDN or winuser.h for more.)

Now, by handling the WM_APPCOMMAND, your program will react to these keys, but only if the window with this windows proc is the active one, or one of its parent windows handles the message. If your program is not active, it will not get the message. So, what to do? The answer is that you have to write a DLL containg a shell hook. Windows will call a shell hook with the hook code equal to HSHELL_APPCOMMAND if the active program doesn’t handle the WM_APPCOMMAND message. This hook has to be in a DLL because only then can other processes than your own be hooked.

I have written such a hook DLL. The hook code just sends a WM_APPCOMMAND message every time the hook sees the HSHELL_APPCOMMAND code to a window that has installed the hook, by calling the SetMMShellHook(HWND hWnd) function of the hook DLL. To unregister, UnSetMMShellHook() has to be called. Call SetMMShellHook after creating your window and UnSetMMShellHook before it gets destroyed.


// Hook procedure for Shell hook

LRESULT CALLBACK ShellProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// Do we have to handle this?
if (nCode == HSHELL_APPCOMMAND)
{
// Process the hook if the hNotifyWnd window handle is valid
if (hNotifyWnd != NULL)
{
short AppCommand = GET_APPCOMMAND_LPARAM(lParam);
switch (AppCommand)
{
case APPCOMMAND_MEDIA_NEXTTRACK:
case APPCOMMAND_MEDIA_PLAY_PAUSE:
case APPCOMMAND_MEDIA_PREVIOUSTRACK:
case APPCOMMAND_MEDIA_STOP:
::PostMessage(hNotifyWnd,WM_APPCOMMAND,wParam,lParam);
return 1; // Don’t call CallNextHookEx; instead,
// return non-zero, because we have handled
// the message (see MSDN doc)

}
}
}

// Call the next handler in the chain
return CallNextHookEx (hShellHook, nCode, wParam, lParam);
}

This code only delegates some APPCOMMAD messages to the windows. You can add other ones, if you want, or delegate all, by removing the “switch case” and calling PostMessage every time.

Together with the window proc code above, your program reacts to the keys, even if it is in the background, but only if no application that handles the WM_APPCOMMAND is active. Then the keys will be handled by this application.

Because the hook has to be installed for different processes, the hook DLL makes use of a shared segment.


#pragma data_seg(“.shared”)
HWND hNotifyWnd = NULL;
HHOOK hShellHook = NULL; // Handle to the Shell hook
#pragma data_seg( )

This code declares a segment called “.shared”. To let this segment really become shared, you have to add a line in the .def file of the DLL. The linker has to learn of the def file by the /DEF: option.


SECTIONS
.shared read write shared

Note: The .sln and vcproject files are for VS.NET 2003, but you can use them with VS.NET if you use a text editor to change the version to 7.00 in these files.

Have fun!

Downloads


Download source code – 5 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read