Today we’ll begin spelunking through the internals of the Panic Button Control Panel, which I’ll probably just be calling “the program” from here on out, so get with it. “It” being the program. Great start!

The first thing that strikes me when looking at the source code is that a surprising amount of it is dedicated to the seemingly simple user interface. Maybe that’s not so surprising to a jaded Windows programmer, but there’s so much material there that I’m just going to postpone discussing it until next time.

My second reaction is to the code that communicates with the Panic Button: it’s complicated! So complicated that it deserves an entire article defending my actions to the world. Let’s commence opening statements with how the Panic Button actually works.

The Panic Button is a USB keyboard. Yeah, I didn’t see that coming either! It’s just a keyboard! A fairly crappy keyboard, what with the one button and all, but still, it’s just a keyboard! Turns out it’s no big deal to have two keyboards plugged into your computer. Who knew? Pushing the button causes the device to generate the following keyboard sequence:

SHIFT + ALT + P

It’s kinda anticlimactic, but there’s a subtle engineering genius to this. I’ve never seen the real Panic Button software but my guess is that it simply registers the system-wide hotkey SHIFT + ALT + P with Windows to receive notifications when the button is pushed. No need for drivers, no need for USB programming, no need to invent a message protocol, no need to detect device connection and removal, it could just call RegisterHotKey(), wait for WM_HOTKEY messages, and let Windows handle the rest. I imagine the operation that produced the Panic Button to be on the cheaper end of things so avoiding all that complexity, and let’s face it, responsibility, is a wise move on its part. It would probably also simplify the hardware if an existing keyboard component could be used. I’m just guessing though, I haven’t taken mine apart.

When I figured this out I was disappointed. I was hoping to reverse engineer a tiny protocol and do a little USB programming, not call RegisterHotKey() and take a nap. So I looked for shortcomings to this approach, something that would justify a bit more work. All I could come up with was that the hotkey method isn’t enough to determine if the Panic Button was plugged in or not, and that it can’t discern between a legitimate Panic Button press and simply pressing SHIFT + ALT + P on the keyboard. The first issue is important to me because hardware programs should be able to tell you if the hardware they know so bloomin’ much about is even plugged in. Plus, that part is fun to write. The second issue is just a matter of principle. Being able to Panic Button without a Panic Button is lame, and it cheapens something that’s already dangerously cheap as it is. With those minor grievances in mind…

The Panic Button program works by using the Raw Input API introduced in Windows XP to detect when the Panic Button has been pushed. It does this by calling RegisterRawInputDevices() in order to be notified about all keyboard input system-wide. Windows in turn sends a WM_INPUT message for every keyboard event that occurs and the program inspects each message to see if the event is the ‘P’ in the Panic Button key sequence. What makes WM_INPUT king is that it includes a field indicating the device that caused the event, and so the program only acts upon genuine Panic Button presses.

Registering for raw input notifications from all keyboards goes a little something exactly like this:

BOOL RegisterForRawInputFromKeyboards(HWND hwnd)
{
    RAWINPUTDEVICE rid;
    rid.usUsagePage = 0x01;     // Magic values for
    rid.usUsage     = 0x06;     //  HID Keyboards.
    rid.dwFlags     = RIDEV_INPUTSINK;
    rid.hwndTarget  = hwnd;

    return RegisterRawInputDevices(&rid, 1, sizeof(rid));
}

The Raw Input API can be used for any input device, but we only care about keyboards and indicate that through the usage values. The RIDEV_INPUTSINK flag causes the program to receive notifications even when it’s not in the foreground, which is perfect since it’s going to spend most of its life minimized in the system tray. This code and the rest that deals directly with the button lives in PanicButton.cpp, encapsulated in wordy functions with simple interfaces. Case in point, the following is called in response to WM_INPUT:

BOOL IsPanicButtonPushed(HANDLE hPanicButton, HRAWINPUT hRawInput)
{
    // get size of input data
    UINT bufferSize = 0;
    if (GetRawInputData(hRawInput, RID_INPUT, NULL, &bufferSize, sizeof(RAWINPUTHEADER)) != 0)
        return FALSE;

    // allocate buffer for input data
    PRAWINPUT rawInput = (PRAWINPUT)malloc(bufferSize);

    // get input data and see if it's a panic button push
    BOOL isPushed = FALSE;
    if (GetRawInputData(hRawInput, RID_INPUT, rawInput, &bufferSize, sizeof(RAWINPUTHEADER)) == bufferSize)
    {
        // is this keyboard input from the panic button?
        if (rawInput->header.hDevice == hPanicButton && rawInput->header.dwType == RIM_TYPEKEYBOARD)
        {
            // is this a WM_SYSKEYUP of the P key?
            //  that's the message from the panic button
            //  that's only sent once per push.
            UINT message = rawInput->data.keyboard.Message;
            USHORT vkey = rawInput->data.keyboard.VKey;
            isPushed = (message == WM_SYSKEYUP && vkey == 'P');
        }
    }

    free(rawInput);
    return isPushed;
}

There’s a lot of API noise in there but the goal of IsPanicButtonPushed() is simple; it answers the question “Was this WM_INPUT caused by the Panic Button releasing the P key?” Most of the noise is due to a fundamental difference between WM_INPUT and traditional window messages. Rather than directly include an address to the message’s data structure with the message, WM_INPUT instead provides a numeric handle which the message recipient can use to retrieve the RAWINPUTHEADER structure through GetRawInputData(). This means that allocating memory for the structure and determining the necessary size is the responsibility of the recipient, and it’s that paperwork that balloons this otherwise simple test of the structure’s contents. The first part of said test is to compare the source device handle to the Panic Button’s device handle to see if they match, which would be straightforward except that I haven’t shown where that handle comes from yet. So instead let’s look at the second part, the WM_SYSKEYUP / P test. The comment claims that this is a good event to test for because it only occurs once per Panic Button push. That may be true, but this was written in 2008 and frankly I claimed a lot of things in 2008 so we better investigate further.

Here’s the full list of window messages generated by a Panic Button press:

Window Message Virtual Key or Character
WM_KEYDOWN VK_SHIFT
WM_SYSKEYDOWN VK_MENU
WM_SYSKEYDOWN P
WM_SYSCHAR P
WM_SYSKEYUP P
WM_KEYUP VK_MENU
WM_KEYUP VK_SHIFT

First some mild deciphering: VK_MENU is Windows-speak for the ALT key, and the WM_SYS* versions of keyboard messages are only sent when the ALT key is down. Once that’s understood it all makes symmetric sense: a rapid build-up of conditions causing the character message followed by an immediate and equal tear-down. I begrudgingly admit that the WM_SYSKEYUP / P combination is only sent once per button press, but that looks to be the case for each message. They’re all unique! There’s nothing at all special about WM_SYSKEYUP / P; testing for any step in the sequence would work just as well. I seem to recall choosing WM_SYSKEYUP / P out of paranoia just in case some kind of defect or interfering auto-repeat rate caused multiple keydown and character messages to be sent, whereas a keyup message could only be sent once by definition. (Exercise for home: hold down a key and try to release it twice.) Unlikely, I know, maybe even impossible, but everything else being equal that’s the pony I bet on. If you’d like to learn more about Windows keyboard messages you can visit here, but Google and I recommend here.

That covers detecting button presses, which leaves detecting button presence. The Raw Input API has us covered there too:

HANDLE GetPanicButtonInputHandle()
{
    // get device count
    UINT deviceCount = 0;
    if (GetRawInputDeviceList(NULL, &deviceCount, sizeof(RAWINPUTDEVICELIST)) == -1)
        return NULL;

    // allocate buffer for device list
    UINT bufferSize = sizeof(RAWINPUTDEVICELIST) * deviceCount;
    PRAWINPUTDEVICELIST buffer = (PRAWINPUTDEVICELIST)malloc(bufferSize);

    // get device list
    HANDLE hPanicButton = NULL;
    if (GetRawInputDeviceList(buffer, &deviceCount, sizeof(RAWINPUTDEVICELIST)) != -1)
    {
        // loop through devices
        for (UINT i=0; i < deviceCount; i++)
        {
            // get device name and check against panic button name using a substring comparison
            TCHAR deviceName[256];
            bufferSize = sizeof(deviceName);
            if (GetRawInputDeviceInfo(buffer[i].hDevice, RIDI_DEVICENAME, deviceName, &bufferSize) != -1)
            {
                TCHAR panicButtonMagicString[] = _T("vid_04f3&pid_04a0");
                CharLowerBuff(deviceName, lstrlen(deviceName));
                if (_tcsstr(deviceName, panicButtonMagicString) != NULL)
                {
                    hPanicButton = buffer[i].hDevice;
                    break;
                }
            }
        }
    }

    free(buffer);
    return hPanicButton;
}

GetPanicButtonInputHandle() simply walks the list of connected input devices looking for a device whose name contains the Panic Button’s unique signature. This signature, “vid_04f3&pid_04a0″, is made up of the Panic Button’s USB Vendor ID and Product ID, 04F3 and 04A0 respectively. USB requires these values to be unique to a device, and so this otherwise shady substring comparison can be forgiven. GetPanicButtonInputHandle() not only tells us if the button is connected (no handle means no button), but its result can be used to identify the source of WM_INPUT messages. This explains the hPanicButton parameter we saw in IsPanicButtonPushed() earlier. The program calls GetPanicButtonInputHandle() upon loading and stores the result for future input testing via IsPanicButtonPushed(). Actually, if the stored handle is NULL then the program doesn’t even bother calling IsPanicButtonPushed() since it already knows that there’s no button attached, but you get the idea. Or not. At this point I’m way too invested in this explanation to start over with a clearer one so here’s hoping.

The last piece to Panic Button communication is keeping track of device connections and disconnections as they happen. That’s not only necessary for displaying the button’s status, but also for keeping the stored Panic Button handle up to date. So… I apologize, but here’s where it starts getting weird. If you peruse the Raw Input API reference you’ll come across WM_INPUT_DEVICE_CHANGE, a message which you can arrange to have sent every time an input device is attached or removed from the system. That sounds perfect, but if you check the fine-print you’ll see that this message wasn’t added to Windows until Vista, disqualifying its use if the PanicĀ Button program is to work on Windows XP, which as an XP user I’d prefer that it does.

With WM_INPUT_DEVICE_CHANGE off the table the next candidate is the original WM_DEVICECHANGE message which has been around forever. Well, “forever” if you consider the mid-nineties to be the beginning of time, which with attention spans these days you just might. WM_DEVICECHANGE and WM_INPUT_DEVICE_CHANGE share the same model, you register for notifications and you receive them whenever a device gets attached or removed. As the name suggests, WM_DEVICECHANGE is more general and is used for all devices, not just inputs. The other difference is that registering for notifications is a bit more complicated:

HDEVNOTIFY RegisterForHumanInterfaceDeviceNotifications(HWND hwnd)
{
    // this is the last hid.dll dependancy in the app.
    //  to remove the dependency on the Windows DDK, remove
    //  the call to HidD_GetHidGuid() and use the flag
    //  DEVICE_NOTIFY_ALL_INTERFACE_CLASSES which will cause
    //  notificationFilter.dbcc_classguid to be ignored.
    //  the only consequence will be that more WM_DEVICECHANGE
    //  messages will be sent by Windows.
    GUID hid;
    HidD_GetHidGuid(&hid);

    DEV_BROADCAST_DEVICEINTERFACE notificationFilter;
    ZeroMemory(&notificationFilter, sizeof(notificationFilter));
    notificationFilter.dbcc_size = sizeof(notificationFilter);
    notificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
    notificationFilter.dbcc_classguid = hid;

    return RegisterDeviceNotification(
        hwnd,                         // events recipient
        &notificationFilter,          // type of device
        DEVICE_NOTIFY_WINDOW_HANDLE   // type of recipient handle
        );
}

Way to spoil the punchline, well-commented code! Registering for device notifications about keyboards isn’t complicated because of the code, it’s complicated because of the dependency incurred by calling HidD_GetHidGuid(). HidD_GetHidGuid() isn’t defined in the Platform SDK headers and libraries, it’s instead found in the Driver Development Kit. Normally that would mean that to compile this silly program you’d have to download a 600MB CD-ROM image, install 1.4GB of components, and then void your citizenship and be labeled an enemy combatant by configuring Visual Studio to compile against the DDK. Yeah… I’m not liking the sound of that either, especially since as the comment says, HidD_GetHidGuid() is the only thing keeping the DDK in the picture and it’s optional! HidD_GetHidGuid() gives us the Globally Unique Identifier for Human Interface Devices, and we use that identifier to tell RegisterDeviceNotification() that we’re only interested in that type of device, but we could also simply skip the filter by asking for notifications for all devices. That would eliminate the need for HidD_GetHidGuid(), but it would also make the Panic Button program a pretty bad Windows citizen.

The way a program detects if its device is connected is by waiting for a WM_DEVICECHANGE message from Windows and then scanning the list of connected devices to see if its guy is there. Now, the message doesn’t specifically say “Your device is connected!”, it just says “Something happened somewhere to someone!” and you’re on your own to investigate. Imagine that you’ve got multiple programs running in the background, each dealing with a different piece of hardware. (If you’re on a laptop you don’t have to imagine at all, I’ll just tell you: you have eighteen.) Now imagine that you plug in a mouse. Every one of those programs could potentially be sent a WM_DEVICECHANGE message and they’re all going to immediately do the same thing: scan Windows to see if their device is connected or disconnected… at the same time. It’s in everyone’s best interest to keep that burst of processing to a minimum, and if you experience a sudden halt when you plug in or unplug your USB gizmos then you recognize the truths I be spittin’. With that in mind, I’d like to help curb the tragedy of the commons and ensure that the Panic Button program receives a minimum number of WM_DEVICECHANGE messages so it looks like HidD_GetHidGuid() is here to stay.

Wait, let’s take a step back for a moment. Something bothers me about a function named “HidD_GetHidGuid”, and not just its unpronounceability. Get me the Globally Unique Identifier?

Here, I’ll save us both some time: it’s {4D1E55B2-F16F-11CF-88CB-001111000030}!

Now the DDK dependency is starting to look a little ridiculous; is all this complexity really because we’re intent on calling a function in the same vein as GetTheNumberFive()? Hard-coding the GUID into the Panic Button program eliminates the HidD_GetHidGuid() call and the DDK dependency and it absolutely works… but man it feels sleazy. I really just wanted to explore my options. This is a terrible solution, even though it would work… for now. There must be a reason that the HID GUID wasn’t defined as a constant and that this layer of indirection exists. The only reason could be that on some systems, either now or in the future, the HID GUID is different. Fair enough, but that kinda defeats the point of using a Globally Unique Identifier in the first place. So no, we won’t be hard-coding the result. It’s the high road for us.

Well, maybe not that high. What I ended up doing was ripping the few relavant files out of the DDK and packaging them with my program’s source code. That’s what’s up with the “DDK” subdirectory. This way I can share the code in a self-contained way that doesn’t force others to jump through unnecessary hoops in order to compile. These files are from the old Windows 2003 DDK (3790.1830) which is what I originally developed the program against. This was the last version to be called DDK, afterwards software schizophrenia set in and it was rebranded as the Windows Driver Kit. The Panic Button program compiles just fine against the latest WDK but its header files are less portable so I included the older ones. If it were any harder than this I would have just cheated and used GetProcAddress() to avoid linking to the DDK at all. This is just a guess, but I wouldn’t be surprised if this exact headache was one of the motivations behind the creation of WM_INPUT_DEVICE_CHANGE.

It was a lot of work, but by using the Raw Input API and flirting with the DDK the Panic Button program was able to avoid using hotkeys. Except that it wasn’t:

static BOOL OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
    ...

    // Register the Panic Button sequence of Shift + Alt + P.
    //  The only reason I do this is to prevent the P from reaching
    //  the active window. The app doesn't use WM_HOTKEY to be notified
    //  of a Panic Button push, it uses WM_INPUT from the Raw Input API.
    RegisterHotKey(hwnd, HotKeyID, MOD_SHIFT | MOD_ALT, 'P');

    ...
}

Oh, Come On! After all that it was still necessary to register the hotkey! The Raw Input API is great for notifying about keyboard events but it doesn’t let you filter them out. The SHIFT + ALT + P key sequence still gets sent to the active window, which depending on the window could mean menus popping up, buttons being pushed, who knows? Hotkeys, on the other hand, are filtered by Windows and don’t reach their normal targets. That’s the behavior I was looking for, and so the program registers the hotkey solely for that purpose.

Sweet sassy molassy, are we ever in need of a recap. The Panic Button program works by…

  • Registering for WM_DEVICECHANGE messages for Human Interface Devices.
  • Registering for WM_INPUT messages for HID keyboards.
  • Registering the SHIFT + ALT + P hotkey to prevent it from reaching applications.
  • Scanning connected input devices for the Panic Button and storing the resulting handle.
  • Re-scanning when receiving WM_DEVICECHANGE and updating the stored handle.
  • Testing each WM_INPUT to see if it’s a WM_SYSKEYUP / P whose source matches the stored handle.

Curse you bullet points, always belittling my work. The end result is that the program displays whether or not the button is connected, only responds to legitimate button presses, and prevents menus that start with ‘P’ from awkwardly springing up. What can I say, buttons that play Table Dance don’t program themselves.

That’s it for today’s episode of Clarissa Explains It All. Next time Sam and I will talk about Pearl Jam, parents, and stupid user interface tricks. Until then, enjoy the source code to the Panic Button program. The project/solution files are for Visual Studio 2005 but they’ll also load just fine in 2008. And… is that Minicrt in there too?

Na Na na-na-na….

PanicButton.exe
PanicButton.zip (source code)