Detect keyboard presence in Windows 8 desktop program
Asked Answered
D

3

2

For Metro apps there's Windows.Devices.Input.KeyboardCapabilities.KeyboardPresent. Is there a way for Windows 8 desktop programs to detect whether a physical keyboard is present?

Dearr answered 16/8, 2012 at 18:57 Comment(8)
Duplicate of Win32 determining when keyboard is connected/disconnectedSandor
@JamesMcNellis, that question is about connecting/disconnecting a keyboard while this one is about whether there's a keyboard available in the first place. Not quite an exact duplicate, even if the answer is totally relevant.Partridge
Use WMI, Win32_Keyboard class.Zymolysis
@James: That's certainly interesting, thanks. However, I found that GetRawInputDeviceList() reports a keyboard on a tablet computer which clearly doesn't have a (physical) one.Dearr
I would expect KeyboardPresent also to return true then. Does it?Sandor
@James: I use Delphi so I can't write a Metro app to try that. However, I noticed that Device Manager shows a PS/2 keyboard (on a tablet!). GetRawInputDeviceList() reports that nicely....Dearr
Did you get anything working for a windows 8 desktop program? as i have same issue, and cant get to 'keyboardPresent' as that's on a metro or phone specific class.Cobweb
My experience with the Win32_Keyboard class is it always returns that there is a keyboard device present even when the keyboard is unplugged (certainly for Surface Pro devices anyway).Homemade
H
0

It's a bit fiddly and I don't know whether the approach I'm proposing will work in all cases, but this is the approach I ended up using:

  1. Use SetupDiGetClassDevs to find all the keyboard devices.
  2. Use SetupDiGetDeviceRegistryProperty to read some keyboard device properties to ignore PS/2 keyboards
  3. Check for touch support since Win 8 touch devices always appear to have an additional HID Keyboard device.

One of the problems with PS/2 ports is that they show up as always being a keyboard device, even if nothing is plugged in. I just managed the problem by assuming no one will ever use a PS/2 keyboard and I filtered them out. I've include two separate checks to try and figure if a keyboard is PS/2 or not. I don't know how reliable either one is, but both individually seem to work okay for the machines I've tested.

The other problem (#3) is that when Windows 8 machines have touch support they have an extra HID keyboard device which you need to ignore.

PS: Something I just realised, my code is using a "buffer" class for the property queries. I left it out to keep just the relevant code, but you'll need to replace that with some appropriate buffer/memory management.

#include <algorithm>
#include <cfgmgr32.h>
#include <Setupapi.h>
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>

struct KeyboardState
{
    KeyboardState() : isPS2Keyboard(false)
    {   }

    std::wstring deviceName; // The name of the keyboard device.
    bool isPS2Keyboard;      // Whether the keyboard is a PS/2 keyboard.
};

void GetKeyboardState(std::vector<KeyboardState>& result)
{
    LPCWSTR PS2ServiceName = L"i8042prt";
    LPCWSTR PS2CompatibleId = L"*PNP0303";
    const GUID KEYBOARD_CLASS_GUID = { 0x4D36E96B, 0xE325,  0x11CE,  { 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 } };

    // Query for all the keyboard devices.
    HDEVINFO hDevInfo = SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, NULL, NULL, DIGCF_PRESENT);
    if (hDevInfo == INVALID_HANDLE_VALUE)
    {
        return;
    }

    //
    // Enumerate all the keyboards and figure out if any are PS/2 keyboards.
    //
    bool hasKeyboard = false;
    for (int i = 0;; ++i)
    {
        SP_DEVINFO_DATA deviceInfoData = { 0 };
        deviceInfoData.cbSize = sizeof (deviceInfoData);
        if (!SetupDiEnumDeviceInfo(hDevInfo, i, &deviceInfoData))
        {
            break;
        }

        KeyboardState currentKeyboard;

        // Get the device ID
        WCHAR szDeviceID[MAX_DEVICE_ID_LEN];
        CONFIGRET status = CM_Get_Device_ID(deviceInfoData.DevInst, szDeviceID, MAX_DEVICE_ID_LEN, 0);
        if (status == CR_SUCCESS)
        {
            currentKeyboard.deviceName = szDeviceID;
        }

        //
        // 1) First check the service name. If we find this device has the PS/2 service name then it is a PS/2
        //    keyboard.
        //
        DWORD size = 0;
        if (!SetupDiGetDeviceRegistryProperty(hDevInfo, &deviceInfoData, SPDRP_SERVICE, NULL, NULL, NULL, &size))
        {
            try
            {
                buffer buf(size);
                if (SetupDiGetDeviceRegistryProperty(hDevInfo, &deviceInfoData, SPDRP_SERVICE, NULL, buf.get(), buf.size(), &size))
                {
                    LPCWSTR serviceName = (LPCWSTR)buf.get();
                    if (boost::iequals(serviceName, PS2ServiceName))
                    {
                        currentKeyboard.isPS2Keyboard = true;
                    }
                }
            }
            catch (std::bad_alloc)
            {
            }
        }

        //
        // 2) Fallback check for a PS/2 keyboard, if CompatibleIDs contains *PNP0303 then the keyboard is a PS/2 keyboard.
        //
        size = 0;
        if (!currentKeyboard.isPS2Keyboard && !SetupDiGetDeviceRegistryProperty(hDevInfo, &deviceInfoData, SPDRP_COMPATIBLEIDS, NULL, NULL, NULL, &size))
        {
            try
            {
                buffer buf(size);
                if (SetupDiGetDeviceRegistryProperty(hDevInfo, &deviceInfoData, SPDRP_COMPATIBLEIDS, NULL, buf.get(), buf.size(), &size))
                {
                    std::wstring value = (LPCWSTR)buf.get();

                    // Split the REG_MULTI_SZ values into separate strings.
                    boost::char_separator<wchar_t> sep(L"\0");
                    typedef boost::tokenizer< boost::char_separator<wchar_t>, std::wstring::const_iterator, std::wstring > WStringTokenzier;
                    WStringTokenzier tokens(value, sep);

                    // Look for the "*PNP0303" ID that indicates a PS2 keyboard device.
                    for (WStringTokenzier::iterator itr = tokens.begin(); itr != tokens.end(); ++itr)
                    {
                        if (boost::iequals(itr->c_str(), PS2CompatibleId))
                        {
                            currentKeyboard.isPS2Keyboard = true;
                            break;
                        }
                    }
                }
            }
            catch (std::bad_alloc)
            {
            }
        }

        result.push_back(currentKeyboard);
    }
}

bool IsNonPS2Keyboard(const KeyboardState& keyboard)
{
    return !keyboard.isPS2Keyboard;
}

bool HasKeyboard()
{
    std::vector<KeyboardState> keyboards;
    GetKeyboardState(keyboards);

    int countOfNonPs2Keyboards = std::count_if(keyboards.begin(), keyboards.end(), IsNonPS2Keyboard);

    // Win 8 with touch support appear to always have an extra HID keyboard device which we
    // want to ignore.
    if ((NID_INTEGRATED_TOUCH & GetSystemMetrics(SM_DIGITIZER)) == NID_INTEGRATED_TOUCH)
    {
        return countOfNonPs2Keyboards > 1;
    }
    else
    {
        return countOfNonPs2Keyboards > 0;
    }
}
Homemade answered 18/9, 2014 at 3:43 Comment(0)
B
0

On Windows 10 this API is part of the UWP API and can be called from Desktop applications just fine.

To call it from C# (or other .NET languages) you need to add a few references to the project files:

<Reference Include="System.Runtime.WindowsRuntime" />
<Reference Include="Windows.Foundation.FoundationContract">
  <HintPath>C:\Program Files (x86)\Windows Kits\10\References\10.0.16299.0\Windows.Foundation.FoundationContract\3.0.0.0\Windows.Foundation.FoundationContract.winmd</HintPath>
  <Private>False</Private>
</Reference>
<Reference Include="Windows.Foundation.UniversalApiContract">
  <HintPath>C:\Program Files (x86)\Windows Kits\10\References\10.0.16299.0\Windows.Foundation.UniversalApiContract\5.0.0.0\Windows.Foundation.UniversalApiContract.winmd</HintPath>
  <Private>False</Private>
</Reference>

The first reference will be resolved against the GAC, the other two are in your VS installation (you can chose a different version if you want). Setting Private to False means to not deploy a Local Copy of those assemblies.

Console.WriteLine(new Windows.Devices.Input.KeyboardCapabilities().KeyboardPresent != 0 ? "keyboard available" : "no keyboard");

In C++ you can do it as follows:

#include <roapi.h>
#include <wrl.h>
#include <windows.devices.input.h>

#pragma comment(lib, "runtimeobject")

int APIENTRY wWinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR lpCmdLine,
    _In_ int nCmdShow)
{
    RoInitialize(RO_INIT_MULTITHREADED);
    {
        INT32 isKeyboardAvailable;
        Microsoft::WRL::ComPtr<ABI::Windows::Devices::Input::IKeyboardCapabilities> pKeyboardCapabilities;
        Microsoft::WRL::Wrappers::HStringReference KeyboardClass(RuntimeClass_Windows_Devices_Input_KeyboardCapabilities);
        if (SUCCEEDED(RoActivateInstance(KeyboardClass.Get(), &pKeyboardCapabilities)) &&
            SUCCEEDED(pKeyboardCapabilities->get_KeyboardPresent(&isKeyboardAvailable)))
        {
            OutputDebugStringW(isKeyboardAvailable ? L"keyboard available\n" : L"no keyboard\n");
        }
    }
    RoUninitialize();
}
Boilermaker answered 28/6, 2019 at 10:33 Comment(0)
A
-1

Simple : Look into HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\kbdclass

Abercromby answered 12/2, 2015 at 23:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.