OSVR Framework (Internal Development Docs)  0.6-1962-g59773924
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
USBSerialDevInfo_Windows.h
Go to the documentation of this file.
1 
12 // Copyright 2015 Sensics, Inc.
13 //
14 // Licensed under the Apache License, Version 2.0 (the "License");
15 // you may not use this file except in compliance with the License.
16 // You may obtain a copy of the License at
17 //
18 // http://www.apache.org/licenses/LICENSE-2.0
19 //
20 // Unless required by applicable law or agreed to in writing, software
21 // distributed under the License is distributed on an "AS IS" BASIS,
22 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 // See the License for the specific language governing permissions and
24 // limitations under the License.
25 
26 // Internal Includes
27 #include "USBSerialDevInfo.h"
28 #include <osvr/Util/WideToUTF8.h>
29 #include <osvr/Util/StdInt.h>
30 
31 // Library/third-party includes
32 #define _WIN32_DCOM
33 #include <string>
34 #include <comdef.h>
35 #include <Wbemidl.h>
36 #include <tchar.h>
37 #include <windows.h>
38 
39 #include <comutils/ComVariant.h>
40 #include <intrusive_ptr_COM.h>
41 
42 #include <boost/intrusive_ptr.hpp>
43 #include <boost/noncopyable.hpp>
44 
45 // Standard includes
46 #include <regex>
47 #include <iostream>
48 #include <sstream>
49 #include <iomanip>
50 #include <algorithm> // for std::transform
51 #include <string>
52 
53 namespace osvr {
54 namespace usbserial {
55 
56  namespace {
58  class ComRAII : boost::noncopyable {
59  public:
60  ComRAII() {
61  auto result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
62  if (FAILED(result)) {
63  m_failed = true;
64  }
65  }
66  ~ComRAII() {
67  // Must call, even if it failed.
68  CoUninitialize();
69  }
70  bool failed() const { return m_failed; }
71 
72  private:
73  bool m_failed = false;
74  };
75 
80  class USBVidPidExtractor {
81  public:
82  USBVidPidExtractor()
83  : m_vidPidRegex(
84  "USB.VID_([[:xdigit:]]{4})&PID_([[:xdigit:]]{4})") {}
85 
86  boost::optional<std::pair<uint16_t, uint16_t>>
87  getVidPid(std::string const &hardwareId) {
88  std::smatch m;
89  if (!std::regex_search(hardwareId, m, m_vidPidRegex)) {
90  // Couldn't find it - that's weird.
91  // Might not be a USB serial port (could be a "regular" one)
92  return boost::none;
93  }
94  // first submatch is vid, second submatch is pid.
95  return std::make_pair(m_hexToInt(m.str(1)),
96  m_hexToInt(m.str(2)));
97  }
98 
99  private:
102  uint16_t m_hexToInt(std::string const &str) {
103  m_ss.clear();
104  uint16_t ret;
105  m_ss << std::hex << str;
106  m_ss >> ret;
107  return ret;
108  }
109 
111  std::regex m_vidPidRegex;
112 
114  std::stringstream m_ss;
115  };
116  } // namespace
117 
118  inline std::string
119  getPNPDeviceIdSearchString(boost::optional<uint16_t> const &vendorID,
120  boost::optional<uint16_t> const &productID) {
123  std::ostringstream searchTerms;
124  if (vendorID) {
125  searchTerms << "VID_" << std::setfill('0') << std::setw(4)
126  << std::hex << *vendorID;
127  }
128  if (vendorID && productID) {
129  searchTerms << "&";
130  }
131  if (productID) {
132  searchTerms << "PID_" << std::setfill('0') << std::setw(4)
133  << std::hex << *productID;
134  }
135  // Convert to all upper-case, in place.
136  std::string ret{searchTerms.str()};
137  std::transform(begin(ret), end(ret), begin(ret),
138  [](char c) { return ::toupper(c); });
139 
140  return ret;
141  }
142 
143  std::vector<USBSerialDevice>
144  getSerialDeviceList(boost::optional<uint16_t> vendorID,
145  boost::optional<uint16_t> productID) {
146  using boost::intrusive_ptr;
147 
148  auto searchString = getPNPDeviceIdSearchString(vendorID, productID);
149 
150  HRESULT result;
151  std::vector<USBSerialDevice> devices;
152 
153  // initialize COM to make lib calls, otherwise we can't proceed
154  ComRAII comSetup;
155  if (comSetup.failed()) {
156  return devices;
157  }
158 
159  // Set COM security should be done here. It's automatically
160  // called by COM when interface is marshaled/unmarshaled with default
161  // settings
162 
163  // Obtain the initial locator to WMI
164  intrusive_ptr<IWbemLocator> locator;
165 
166  result =
167  CoCreateInstance(CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER,
168  IID_IWbemLocator, AttachPtr(locator));
169 
170  if (FAILED(result)) {
171  return devices;
172  }
173 
174  // Connect to WMI through the IWbemLocator::ConnectServer method
175 
176  intrusive_ptr<IWbemServices> wbemServices;
177 
178  // Connect to the root\cimv2 namespace with
179  // the current user and obtain pointer pSvc
180  // to make IWbemServices calls.
181  result = locator->ConnectServer(
182  bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
183  nullptr, // User name. nullptr = current user
184  nullptr, // User password. nullptr = current
185  nullptr, // Locale. nullptr indicates current
186  WBEM_FLAG_CONNECT_USE_MAX_WAIT, // Security flags - here, requesting
187  // a timeout.
188  0, // Authority (for example, Kerberos)
189  nullptr, // Context object
190  AttachPtr(wbemServices) // pointer to IWbemServices proxy
191  );
192 
193  if (FAILED(result)) {
194  return devices;
195  }
196 
197  // Set security levels on the proxy
198 
199  result =
200  CoSetProxyBlanket(wbemServices.get(), RPC_C_AUTHN_WINNT,
201  RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL,
202  RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE);
203 
204  if (FAILED(result)) {
205  return devices;
206  }
207 
208  // Use the IWbemServices pointer to make requests of WMI
209 
210  // Get a list of serial devices
211  intrusive_ptr<IEnumWbemClassObject> devEnum;
212  result = wbemServices->ExecQuery(
213  bstr_t(L"WQL"), bstr_t(L"SELECT * FROM Win32_SerialPort"),
214  WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, nullptr,
215  AttachPtr(devEnum));
216 
217  if (FAILED(result)) {
218  return devices;
219  }
220 
221  USBVidPidExtractor vidPidExtractor;
222 
223  intrusive_ptr<IWbemClassObject> wbemClassObj;
224  ULONG numObjRet = 0;
225  while (devEnum) {
226  HRESULT hr = devEnum->Next(WBEM_INFINITE, 1,
227  AttachPtr(wbemClassObj), &numObjRet);
228 
229  if (FAILED(hr) || numObjRet == 0) {
230  break;
231  }
232 
233  using comutils::Variant;
234  using comutils::get;
235 
237  Variant varPort;
238  hr = wbemClassObj->Get(L"DeviceID", 0, AttachVariant(varPort),
239  nullptr, nullptr);
240  std::string port =
241  util::wideToUTF8String(get<std::wstring>(varPort));
242 
245  Variant varHardwareID;
246  hr = wbemClassObj->Get(L"PNPDeviceID", 0,
247  AttachVariant(varHardwareID), nullptr,
248  nullptr);
249  std::string hardwareID =
250  util::wideToUTF8String(get<std::wstring>(varHardwareID));
251 
252  if (!searchString.empty()) {
253  if (hardwareID.find(searchString) == std::string::npos) {
256  continue;
257  }
258  }
259 
260  auto vidPid = vidPidExtractor.getVidPid(hardwareID);
261  if (!vidPid) {
263  continue;
264  }
265 
268  std::string path = "\\\\.\\" + port;
269 
270  devices.emplace_back(vidPid->first, vidPid->second, path, port);
271  }
272 
273  return devices;
274  }
275 
276 } // namespace usbserial
277 } // namespace osvr
std::stringstream m_ss
String stream used to convert the hex string to an int.
Header wrapping the C99 standard stdint header.
t_< detail::transform_< List, Fun >> transform
Definition: Transform.h:54
Configured header for internal UTF16 or Windows TCHAR to UTF8 conversion. Using this header requires ...