{"id":106303,"date":"2022-03-02T07:00:00","date_gmt":"2022-03-02T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106303"},"modified":"2022-03-02T06:41:12","modified_gmt":"2022-03-02T14:41:12","slug":"20220302-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220302-00\/?p=106303","title":{"rendered":"How can I detect whether the system has a keyboard attached? On the GetRawInputDeviceList function"},"content":{"rendered":"<p>Last time, we saw <a title=\"How does Windows decide what instructions to provide for unlocking the PC?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220301-00\/?p=106300\"> that the instructions for unlocking the PC<\/a> vary depending on whether a keyboard is attached. So how do you detect whether a keyboard is attached?<\/p>\n<p>The <code>Get\u00adRaw\u00adInput\u00adDevice\u00adList<\/code> function gives you the raw input devices that are registered with the input system. Let&#8217;s write a little program to count how many of them are keyboards.<\/p>\n<pre>#include &lt;windows.h&gt;\r\n#include &lt;stdio.h&gt; \/\/ Horrors! Mixing C and C++!\r\n#include &lt;vector&gt;\r\n\r\n[[noreturn]] void throw_win32_error(DWORD error)\r\n{\r\n    \/\/ replace with your desired win32 exception object\r\n    std::terminate();\r\n}\r\n\r\nstd::vector&lt;RAWINPUTDEVICELIST&gt; GetRawInputDevices()\r\n{\r\n  UINT deviceCount = 0;\r\n  if (GetRawInputDeviceList(nullptr, &amp;deviceCount,\r\n            sizeof(devices[0]) != 0) {\r\n    throw_win32_error(GetLastError());\r\n  }\r\n\r\n  std::vector&lt;RAWINPUTDEVICELIST&gt; devices(deviceCount);\r\n  while (deviceCount != 0) {\r\n    UINT actualDeviceCount = GetRawInputDeviceList(\r\n            devices.data(), &amp;deviceCount,\r\n            sizeof(devices[0]);\r\n    if (actualDeviceCount != (UINT)-1) {\r\n        devices.resize(actualDeviceCount);\r\n        return devices;\r\n    }\r\n    DWORD error = GetLastError();\r\n    if (error != ERROR_INSUFFICIENT_BUFFER) {\r\n      throw_win32_error(error);\r\n    }\r\n    devices.resize(deviceCount);\r\n  }\r\n}\r\n<\/pre>\n<p>The behavior of the <code>Get\u00adRaw\u00adInput\u00adDevice\u00adList()<\/code> is very strange. Here it is in a table:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Scenario<\/th>\n<th>Buffer pointer<\/th>\n<th>Size on entry<\/th>\n<th>Size on return<\/th>\n<th>Return value<\/th>\n<\/tr>\n<tr>\n<td>Query number of devices<\/td>\n<td><code>nullptr<\/code><\/td>\n<td>ignored<\/td>\n<td>required size<\/td>\n<td style=\"text-align: center;\">0<\/td>\n<\/tr>\n<tr>\n<td>Request data (failed)<\/td>\n<td>Non-<code>nullptr<\/code><\/td>\n<td>provided size<\/td>\n<td>required size<\/td>\n<td style=\"text-align: center;\"><code>0xFFFFFFFF<\/code><\/td>\n<\/tr>\n<tr>\n<td>Request data (succeeded)<\/td>\n<td>Non-<code>nullptr<\/code><\/td>\n<td>provided size<\/td>\n<td>unchanged<\/td>\n<td style=\"text-align: center;\">actual size<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The <code>Get\u00adRaw\u00adInput\u00adDevices()<\/code> function starts by using the <i>Query number of devices<\/i> pattern to get an initial guess as to the number of devices, and then goes into the usual loop of calling a function (in this case <code>Get\u00adRaw\u00adInput\u00adDevice\u00adList()<\/code>) to fill a buffer, resizing the buffer based on the result from the previous query, until you finally get what you want. There is a weird edge case where there are no input devices: A vector resized to zero is permitted to return <code>data() == nullptr<\/code>, but that would cause our <i>Request data<\/i> to be misinterpreted as a <i>Query number of devices<\/i>, so we skip the loop if the device count is zero.<\/p>\n<p>Most people don&#8217;t write the loop. They just call the function twice, once to get the count, and once to fill the buffer. But it means that if a device is attached between the two calls, the code fails because the count changed. You need to make it a loop so you can adapt to changes that occur behind your back.<\/p>\n<p>Other common oversights when using the <code>Get\u00adRaw\u00adInput\u00adDevice\u00adList()<\/code> function are missing the case where there are no devices, or the case where a device is removed during the loop, so that the <i>Request data<\/i> succeeds but returns a value smaller than the provided size.<\/p>\n<p>If we start with a nonzero initial guess, we can get rid of the preliminary portion of the function and go straight to the loop, thereby avoiding the weird &#8220;I&#8217;m going to return success even though you didn&#8217;t get anything&#8221; first row of the above table.<\/p>\n<pre>std::vector&lt;RAWINPUTDEVICELIST&gt; GetRawInputDevices()\r\n{\r\n  <span style=\"color: blue;\">UINT deviceCount = 10; \/\/ initial guess, must be nonzero<\/span>\r\n  std::vector&lt;RAWINPUTDEVICELIST&gt; devices(deviceCount);\r\n  while (deviceCount != 0) {\r\n    UINT actualDeviceCount = GetRawInputDeviceList(\r\n            devices.data(), &amp;deviceCount,\r\n            sizeof(devices[0]);\r\n    if (actualDeviceCount != (UINT)-1) {\r\n        devices.resize(actualDeviceCount);\r\n        return devices;\r\n    }\r\n    DWORD error = GetLastError();\r\n    if (error != ERROR_INSUFFICIENT_BUFFER) {\r\n      std::terminate(); \/\/ throw something\r\n    }\r\n    devices.resize(deviceCount);\r\n  }\r\n}\r\n<\/pre>\n<p>Now we get to use this function to study the raw input devices.<\/p>\n<pre>int main(int argc, char** argv)\r\n{\r\n  auto devices = GetRawInputDevices();\r\n  int mouseCount = 0;\r\n  int keyboardCount = 0;\r\n  int otherCount = 0;\r\n  for (auto const&amp; device : devices) {\r\n    switch (device.dwType)\r\n    {\r\n    case RIM_TYPEKEYBOARD: keyboardCount++; break;\r\n    case RIM_TYPEMOUSE: mouseCount++; break;\r\n    default: otherCount++; break;\r\n    }\r\n  }\r\n  printf(\"There are %d keyboards, %d mice, and %d other things\\n\",\r\n         keyboardCount, mouseCount, otherCount);\r\n  return 0;\r\n}\r\n<\/pre>\n<p>We walk through the list and tally up how many devices there are of each kind.<\/p>\n<p>If you just want a &#8220;yes or no&#8221; answer about whether there is a keyboard attached, you can ask <a href=\"https:\/\/docs.microsoft.com\/uwp\/api\/Windows.Devices.Input.KeyboardCapabilities.KeyboardPresent\"> <code>KeyboardCapabilities.<wbr \/>KeyboardPresent<\/code><\/a>:<\/p>\n<pre>#include &lt;stdio.h&gt; \/\/ Horrors! Mixing C and C++!\r\n#include &lt;winrt\/Windows.Devices.Input.h&gt;\r\n\r\nint main(int argc, char** argv)\r\n{\r\n  winrt::init_apartment();\r\n  winrt::Windows::Devices::Input::KeyboardCapabilities capabilities;\r\n  printf(\"KeyboardPresent = %d\\n\", capabilities.KeyboardPresent());\r\n  return 0;\r\n}\r\n<\/pre>\n<p>The <code>Keyboard\u00adPresent<\/code> property identifies keyboards by\u2026 enumerating the raw input devices and seeing if any of them is a keyboard.<\/p>\n<p>This all sounds good, except that a lot of devices report themselves as keyboards, even though they aren&#8217;t keyboards in the usual sense. Next time, we&#8217;ll see if we can filter those guys out.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You can enumerate the input devices and see if there&#8217;s a keyboard.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-106303","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You can enumerate the input devices and see if there&#8217;s a keyboard.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106303","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=106303"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106303\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=106303"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106303"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106303"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}