Simulating media controller buttons like Play and Pause

Today's Little Program simulates pressing the Play/Pause button on your fancy keyboard. This might be useful if you want to write a program that converts some other input (say, gesture detection) into media controller events.

One way of doing this is to take advantage of the Def­Window­Proc function, since the default behavior for the WM_APP­COMMAND message is to pass the message up the parent chain, and if it still can't find a handler, it hands the message to the shell for global processing.

Remember, don't fumble around. If you want to send a message to a window, then send a message to a window. Don't broadcast a message to every window in the system (resulting in mass chaos).

Take the scratch program and make this simple addition:

void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
 if (ch == ' ') {
  SendMessage(hwnd, WM_APPCOMMAND, (WPARAM)hwnd,

 HANDLE_MSG(hwnd, WM_CHAR, OnChar);

When you press the space bar in the scratch application, it pretends that you instead pressed the Play/Pause button on your fancy keyboard with no shift modifiers.

The scratch program doesn't do anything with the key, so it ends up falling through to Def­Window­Proc, which eventually hands the key to the shell and any other registered shell hooks. If you have a program like Windows Media Player which registers for shell events, it will see the notification and pause/resume playback.

Of course, this assumes that the program you want to talk to listens globally for the keypress. If you want to make the current foreground program respond as if you had pressed the Play/Pause, you can just inject the keypress.

int __cdecl main(int, char**)
 INPUT inputs[2] = {};
 inputs[0].type = INPUT_KEYBOARD;
 inputs[0].ki.wVk = VK_MEDIA_PLAY_PAUSE;
 inputs[0].ki.wScan = 0x22;
 inputs[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
 inputs[1].type = INPUT_KEYBOARD;
 inputs[1].ki.wVk = VK_MEDIA_PLAY_PAUSE;
 inputs[1].ki.wScan = 0x22;
 SendInput(2, inputs, sizeof(INPUT));
 return 0;

Note, however, that since we didn't do anything about the state of modifier keys, if the user happens to have the shift key down at the time you injected the message, the application is going to be told, "Hey, do your play/pause thing, and if you change behavior when the shift key is down, here's your chance."

But what did you expect from a Little Program?

Comments (18)
  1. Henke37 says:

    Last array index is wrong, it should be 1 instead of 0.

  2. SimonRev says:

    Out of curiosity — how do I register for the shell events if I want to globally receive the play/pause notification?

  3. Frank says:

    @SimonRev – RegisterShellHookWindow

  4. SimonRev says:

    Well that seems a bit more palatable than registering for WH_SHELLHOOK which I would assume that in addition to a 32 bit DLL to inject you would also need a stub 64-bit DLL to receive WM_APPCOMMANDS that went to 64 bit processes.

  5. rsola says:

    Thanks we have…/gg463446.aspx

    MSDN continues erasing old but very valuable content.

  6. Rick C says:

    Looks like the documentations still around if you are willing to do a bit of searching.  "keyboard scan codes" took me to this page:…/ff542326(v=vs.85).aspx which has a link at the bottom to this…/p pdf, which has several-page table showing what looks like all the scan codes.

  7. nobugz says:

    You'll to the world a great, great favor by deleting that second snippet.  Please.

  8. Then there's the approach I took, which was to write a custom HID driver.

  9. Yuhong Bao says:

    "The scratch program doesn't do anything with the key, so it ends up falling through to Def­Window­Proc, which eventually hands the key to the shell and any other registered shell hooks."

    Is there a reason not to just call DefWindowProc directly?

  10. Yuhong Bao says:

    Ideally, there would be a function that is separate from DefWindowProc that allows the WM_SYSCOMMAND/WM_APPCOMMAND/etc. functions to be invoked.

  11. @Yuhong Bao says:

    There is. Its called SendMessage. insert not spam here.

    [You want to use SendMessage so that the other infrastructure (e.g. message hooks) are invoked. Calling DefWindowProc directly means that somebody who subclassed the window will not see the message. -Raymond]
  12. Entegy says:

    @Maurits Why on Earth would you do that?

  13. Erm, Mr. Chen? Why don't you have a Little Program article about one that only works in the latest version of Windows and demonstrates one of its nifty features or secrets?

  14. Joshua says:

    From the linked article:  [With the source code, people would be more likely to break the rules, because they can see which rules they can get away with breaking.] -Raymond Chen

    This one cuts both ways. Because the source to Windows Forms is available, I was able to quickly locate a bug within an hour of encountering it and actually produce a fix. Unfortunately the only viable fix is poking directly into the variables of the WinForms code. Previous attempts at filing bugs, results in *none* of them ever getting fixed, and merely closed out 3 years later. (Yes I can watch this on MS Connect.) Seriously, what do you expect people to do?

    As for don't use WinForms, you're talking 3 man years to move away now.

    For reference, here is the entirety of my fix (SetPrivateField is a helper function for the reflection API):

           'System.Windows.Forms BUG: Check Box size is not set correctly for high DPI.

           SetPrivateField(Me, "idealCheckSize", SystemInformation.MenuCheckSize.Height)

    I guess I'm willing to take the bet that if bugs aren't getting fixed that MS just isn't maintaining WinForms anymore and so the hitpoint is never gonna change.

  15. Joshua says:

    @Fleet Command: The Old New Thing, not The New New Thing.

  16. @Joshua: Not sure I follow you. IMHO, "The Old" here is a term of endearment, as in "Hello, old boy!" So, it is practically The Sweet New Thing.

  17. Mark S says:

    @Fleet Command

    Disagree; see for example…/407234.aspx ("Predictions for the future") particularly vs. "Windows history"

  18. remoter says:

    Doesn't work when in a RDP session. Fix it. (keys should OBVIOUSLY go to the local computer)

Comments are closed.

Skip to main content