How can I detect whether a keyboard is attached to the computer?


Today's Little Program tells you whether a keyboard is attached to the computer. The short answer is "Enumerate the raw input devices and see if any of them is a keyboard."

Remember: Little Programs don't worry about silly things like race conditions.

#include <windows.h>
#include <iostream>
#include <vector>
#include <algorithm>

bool IsKeyboardPresent()
{
 UINT numDevices = 0;
  if (GetRawInputDeviceList(nullptr, &numDevices,
                            sizeof(RAWINPUTDEVICELIST)) != 0) {
   throw GetLastError();
 }

 std::vector<RAWINPUTDEVICELIST> devices(numDevices);

 if (GetRawInputDeviceList(&devices[0], &numDevices,
                           sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) {
  throw GetLastError();
 }

 return std::find_if(devices.begin(), devices.end(),
    [](RAWINPUTDEVICELIST& device)
    { return device.dwType == RIM_TYPEKEYBOARD; }) != devices.end();
}

int __cdecl main(int, char**)
{
 std::cout << IsKeyboardPresent() << std::endl;
 return 0;
}

There is a race condition in this code if the number of devices changes between the two calls to Get­Raw­Input­Device­List. I will leave you to fix it before incorporating this code into your program.

Comments (24)
  1. Brian_EE says:

    What will this report for devices connected to the PC that aren't keyboards, but identify themselves as such? Two things that come to mind are some types of barcode scanners and some prank devices that you can buy from ThinkGeek.

    Seems like an unreliable endeavor from the get go.

    [If it identifies itself as a keyboard, then it's a keyboard. There is no second-guessing. -Raymond]
  2. anonymouscommenter says:

    To expand on what Brian EE asked, what if there is a touch screen that can bring up a touch screen keyboard?

    [A touch screen does not report itself as a keyboard. -Raymond]
  3. anonymouscommenter says:

    If it walks like a keyboard and quacks like a keyboard, it's a keyboard. You could probably blacklist known device ids.

  4. Jimmy Queue says:

    What this is really answering is, is there a device attached that has a type of RIM_TYPEKEYBOARD?

  5. anonymouscommenter says:

    This runs into problems on convertible systems. A keyboard device may be present, but the hardware can be in a configuration that doesn't make it accessible.

    But Raymond's post is technically correct – it tells you if there is a keyboard attached, not if there is a usable keyboard attached.

    PowerDeterminePlatformRoleEx tells you which configuration a system is in, and should match up with the logic Windows uses for determining when it should automatically use the touch keyboard in Universal apps. A convertible system should go back and forth between Mobile and Slate, unless it is plugged into a fixed docking station, where it may report itself as a desktop. There is an API for determining if a system is docked, but that can be misleading – I think a Surface will behave differently if docked to it's mobile keyboard case or to a fixed dock designed to support external monitors.

    [Convertibles typically disconnect the keyboard when placed in tablet mode, so no keyboard will be enumerated. -Raymond]
  6. skSdnW says:

    GetRawInputDeviceList is a nicely designed function because it allows you to retrieve the total number of items even when you pass in a insufficient non-null buffer:

    std::vector<RAWINPUTDEVICELIST> devices;

    UINT numDevices = 2, retr = !numDevices, gle;

    for (;retr != numDevices;)

    {

    devices.resize(numDevices);

    retr = GetRawInputDeviceList(&devices[0], &numDevices, sizeof(RAWINPUTDEVICELIST));

    if (-1 == retr && ERROR_INSUFFICIENT_BUFFER != (gle = GetLastError())) throw gle;

    }

    In this example it is probably not that helpful because you will probably allocate a insufficient buffer the first time around for anything except simple desktop machines.

    Switching to a query first style loop is also easy:

    std::vector<RAWINPUTDEVICELIST> devices;

    UINT numDevices = 0, retr = !numDevices, gle;

    for (;retr != numDevices;)

    {

    devices.resize(numDevices);

    retr = GetRawInputDeviceList(numDevices ? &devices[0] : nullptr, &numDevices, sizeof(RAWINPUTDEVICELIST));

    if (-1 == retr && ERROR_INSUFFICIENT_BUFFER != (gle = GetLastError())) throw gle;

    }

  7. anonymouscommenter says:

    I'm going to guess that what this actually tells you is whether there is a keyboard in your current session. If you connect via RDP to a remote computer, it would tell you that your own local computer has a keyboard rather than the remote computer has a keyboard attached to it.

  8. anonymouscommenter says:

    Wow nitpickers everywhere.

    Thanks for the small program Raymond, I enjoy these!

  9. anonymouscommenter says:

    "[Convertibles typically disconnect the keyboard when placed in tablet mode, so no keyboard will be enumerated. -Raymond]"

    That's what I thought too, until I tried it out. I needed a quick-and-dirty way to know if my Surface was docked or not for some tests (nothing ever to be used near production) I was doing, and discovered that it still reported a keyboard. I was in C# Universal App land, so used Windows.Devices.Input.KeyboardCapabilities to detect it. KeyboardPresent was true, even when undocked! My assumption was that the actual USB device is integrated into the Surface, not the keyboard case, but I didn't look into it further because it didn't really matter. The convertible device guidelines for OEMs don't mention physically disconnecting the keyboard, just the details of how to get the GPIO driver for the laptop/slate indicator working, and how the OS responds to it. Unfortunately, there doesn't seem to be a way for a Universal app to query the platform role.

  10. anonymouscommenter says:

    @skSdnW: Put a simple `numDevices += 2` just before std::vector<RAWINPUTDEVICELIST> devices(numDevices);

    `numDevices` gets overwritten on second call to `GetRawInputDeviceList`

  11. skSdnW says:

    @wqw: Why are you talking to me but quoting Raymond's code? My code uses a loop to avoid the race condition and having GetRawInputDeviceList overwrite numDevices is sort of the point. Doing a += 2 might not be enough, a docking station might provide more than two devices etc…

  12. anonymouscommenter says:

    Mil $ qeustion : why does anyone ever want to know if there is a keyboard?

  13. anonymouscommenter says:

    @skSdnW: I'm commenting your over-engineered code. Again: a simple bicrement would be enough. Now you have to figure out why.

  14. anonymouscommenter says:

    @Amit: We don't want to show keyboard key hints over our buttons in a game if the MS Surface doesn't have a keyboard attached at the moment.

  15. anonymouscommenter says:

    @wqw

    > a simple bicrement would be enough

    No it won't. Stop misleading other readers, please.

  16. anonymouscommenter says:

    Here's my little program to detect whether a keyboard is attached, but with all the error-checking removed, as is standard for little programs:

    @echo off

    pause

    @skSdnW: Why the for (;condition;) loop instead of a while (condition) loop?

  17. skSdnW says:

    @Neil: My initial code was for(;;) and a break (while(true) can cause warnings). You could probably remove the ?: condition from the second example as well but I try to not edit the code too much in the comment reply field.

  18. anonymouscommenter says:

    @skSdnW Does while(true) give a warning in any recent compiler?

    I noticed the other "trick" of putting the constant first to avoid = being allowed when you meant ==, but it's not 1995 anymore and it makes it harder to write descriptive code for those of us who read left to right. A good compiler should pick up on that.

    Hiding the assignment inside the if also hurts readability as is putting the if and throw on the same line.

    You're using different techniques for preventing errors but then using a style that makes it easy to make mistakes it can't warn against. i.e. always use {} even for single line if's, don't hide variable definitions on one line etc. Anytime I see a ? operator my spider sense activates, thankfully you can breakpoint them these days but it's still a much more tiresome task than splitting the code up.

    I know you are probably happy with it because it's your style, but someone else will either have to slow down when skim reading your code or they'll miss important details (some developers do this on purpose to deter anyone else touching their code).

    If I was asked to fix a bug in this code I'd just rewrite it rather than try to figure out what it does compared to what it would actually need to do.

    This comes across as arrogant and negative, but I just thought that maybe nobody ever questioned how you structure your code and I thought it might actually help if you re-evaluate it.

  19. skSdnW says:

    @boogaloo: I don't want to start a style debate here. I agree that the "if" is a bit iffy and the throw belongs on the next line but I was not going for readability, I was trying to keep it short because it is throwaway code posted as a comment…

  20. anonymouscommenter says:

    I have a function that asks the user to press 'Y' or 'N', when asked "Is there a keyboard present".  But, if there is no keyboard present, the user can not press 'N', so my program stops.  This is a Denial Of Service bug.  Please fix it, thanks.

  21. anonymouscommenter says:

    The race condition is in

    | return std::find_if(devices.begin(), devices.end(),

    |    [](RAWINPUTDEVICELIST& device)

    |    { return device.dwType == RIM_TYPEKEYBOARD; }) != devices.end(); }

    which might search uninitialized entries in devices. To fix it one should call

    | devices.resize(numDevices);

    before the `std::find_if`.

  22. anonymouscommenter says:

    If we're going to turn Win32 errors into unhandled exceptions, I think I'd rather throw HRESULT_FROM_WIN32(GetLastError()) than the raw Win32 error value.

  23. anonymouscommenter says:

    Why does the MSDN documentation for GetRawInputDeviceList have the same race condition in it's example as you have in yours?  I would think that MS wouldn't want to promote bad code by providing examples that have bugs.  It is not even mentioned that the example has a race condition so the unwary learner will end up writing bad code because the example provided by an authority has bugs.

  24. skSdnW says:

    @Query: A getter function like this will always have some type of a race. To keep it simple they could change it and check the return value against >= 0 in the second call. After all, something could be added/removed at any point after a successful call even if you use a loop.

    What happens to the handles in the array when something is unplugged is another question, perhaps they are not real handles?

Comments are closed.

Skip to main content