wgi_data_fetcher_win.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "device/gamepad/wgi_data_fetcher_win.h"
  5. #include <stdint.h>
  6. #include <wrl/event.h>
  7. #include <algorithm>
  8. #include <memory>
  9. #include <string>
  10. #include "base/containers/cxx20_erase.h"
  11. #include "base/containers/flat_map.h"
  12. #include "base/sequence_checker.h"
  13. #include "base/strings/string_util_win.h"
  14. #include "base/strings/utf_string_conversions.h"
  15. #include "base/threading/sequenced_task_runner_handle.h"
  16. #include "base/win/core_winrt_util.h"
  17. #include "base/win/hstring_reference.h"
  18. #include "base/win/windows_version.h"
  19. #include "device/base/event_utils_winrt.h"
  20. #include "device/gamepad/dualshock4_controller.h"
  21. #include "device/gamepad/gamepad_id_list.h"
  22. #include "device/gamepad/gamepad_standard_mappings.h"
  23. #include "device/gamepad/nintendo_controller.h"
  24. #include "device/gamepad/wgi_gamepad_device.h"
  25. namespace device {
  26. namespace {
  27. Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IRawGameController>
  28. GetRawGameController(ABI::Windows::Gaming::Input::IGamepad* gamepad,
  29. WgiDataFetcherWin::GetActivationFactoryFunction
  30. get_activation_factory_function_) {
  31. base::win::ScopedHString raw_game_controller_string =
  32. base::win::ScopedHString::Create(
  33. RuntimeClass_Windows_Gaming_Input_RawGameController);
  34. Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IRawGameControllerStatics>
  35. raw_game_controller_statics;
  36. HRESULT hr = get_activation_factory_function_(
  37. raw_game_controller_string.get(),
  38. IID_PPV_ARGS(&raw_game_controller_statics));
  39. if (FAILED(hr))
  40. return nullptr;
  41. Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGameController>
  42. game_controller;
  43. hr = gamepad->QueryInterface(IID_PPV_ARGS(&game_controller));
  44. if (FAILED(hr))
  45. return nullptr;
  46. Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IRawGameController>
  47. raw_game_controller;
  48. hr = raw_game_controller_statics->FromGameController(game_controller.Get(),
  49. &raw_game_controller);
  50. if (FAILED(hr))
  51. return nullptr;
  52. return raw_game_controller;
  53. }
  54. GamepadId GetGamepadId(const std::u16string& product_name,
  55. ABI::Windows::Gaming::Input::IGamepad* gamepad,
  56. WgiDataFetcherWin::GetActivationFactoryFunction
  57. get_activation_factory_function) {
  58. std::string product_name_string = base::UTF16ToUTF8(product_name);
  59. HRESULT hr = S_OK;
  60. Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IRawGameController>
  61. raw_game_controller =
  62. GetRawGameController(gamepad, get_activation_factory_function);
  63. if (!raw_game_controller) {
  64. return GamepadId::kUnknownGamepad;
  65. }
  66. uint16_t vendor_id;
  67. hr = raw_game_controller->get_HardwareVendorId(&vendor_id);
  68. if (FAILED(hr)) {
  69. return GamepadId::kUnknownGamepad;
  70. }
  71. uint16_t product_id;
  72. hr = raw_game_controller->get_HardwareProductId(&product_id);
  73. if (FAILED(hr)) {
  74. return GamepadId::kUnknownGamepad;
  75. }
  76. return GamepadIdList::Get().GetGamepadId(product_name_string, vendor_id,
  77. product_id);
  78. }
  79. // Check if the gamepad should be added by Windows.Gaming.Input. In the
  80. // situation that a Nintendo or Dualshock4 gamepad is connected, there are
  81. // dedicated data fetchers designed for these gamepads.
  82. // We want to let those data fetchers handle the gamepad input instead.
  83. bool ShouldEnumerateGamepad(GamepadId gamepad_id) {
  84. if (gamepad_id == GamepadId::kUnknownGamepad) {
  85. return false;
  86. }
  87. if (NintendoController::IsNintendoController(gamepad_id)) {
  88. // Nintendo devices are handled by the Nintendo data fetcher.
  89. return false;
  90. }
  91. if (Dualshock4Controller::IsDualshock4(gamepad_id)) {
  92. // Dualshock4 devices are handled by the RawInput data fetcher.
  93. return false;
  94. }
  95. return true;
  96. }
  97. // Checks if the provided gamepad has paddles and returns the available
  98. // quantity. If the Windows version is less then WIN10_RS1 (WIN10.0.14393.0),
  99. // this function returns 0, since IGamepad2 interface will not be available.
  100. uint32_t GetPaddleNumber(
  101. const Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad>&
  102. gamepad) {
  103. uint32_t num_paddles = 0;
  104. if (base::win::GetVersion() < base::win::Version::WIN10_RS1) {
  105. return num_paddles;
  106. }
  107. static constexpr ABI::Windows::Gaming::Input::GamepadButtons kPaddles[] = {
  108. ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_Paddle1,
  109. ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_Paddle2,
  110. ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_Paddle3,
  111. ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_Paddle4};
  112. Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad2> gamepad2;
  113. HRESULT hr = gamepad->QueryInterface(IID_PPV_ARGS(&gamepad2));
  114. if (hr == S_OK) {
  115. ABI::Windows::Gaming::Input::GameControllerButtonLabel button_label;
  116. for (const auto& paddle : kPaddles) {
  117. hr = gamepad2->GetButtonLabel(paddle, &button_label);
  118. if (hr == S_OK &&
  119. button_label !=
  120. ABI::Windows::Gaming::Input::GameControllerButtonLabel::
  121. GameControllerButtonLabel_None) {
  122. ++num_paddles;
  123. }
  124. }
  125. }
  126. return num_paddles;
  127. }
  128. bool HasTriggerRumbleSupport(GamepadId gamepad_id) {
  129. // Xbox One USB
  130. return gamepad_id == GamepadId::kMicrosoftProduct02d1 ||
  131. // Xbox One USB 2015 Firmware
  132. gamepad_id == GamepadId::kMicrosoftProduct02dd ||
  133. // Xbox One S Bluetooth 2016 Firmware
  134. gamepad_id == GamepadId::kMicrosoftProduct02fd ||
  135. // Xbox One S Bluetooth 2021 Firmware
  136. gamepad_id == GamepadId::kMicrosoftProduct0b20 ||
  137. // Xbox One S USB
  138. gamepad_id == GamepadId::kMicrosoftProduct02ea ||
  139. // Xbox One S Bluetooth
  140. gamepad_id == GamepadId::kMicrosoftProduct02e0 ||
  141. // Xbox Series X USB
  142. gamepad_id == GamepadId::kMicrosoftProduct0b12 ||
  143. // Xbox Series X Bluetooth
  144. gamepad_id == GamepadId::kMicrosoftProduct0b13 ||
  145. // Xbox One Elite USB
  146. gamepad_id == GamepadId::kMicrosoftProduct02e3 ||
  147. // Xbox One Elite Series 2 USB
  148. gamepad_id == GamepadId::kMicrosoftProduct0b00 ||
  149. // Xbox One Elite Series 2 Bluetooth
  150. gamepad_id == GamepadId::kMicrosoftProduct0b05 ||
  151. // Xbox Elite Series 2 Bluetooth 2021 Firmware
  152. gamepad_id == GamepadId::kMicrosoftProduct0b22;
  153. }
  154. } // namespace
  155. WgiDataFetcherWin::WgiDataFetcherWin() {
  156. DETACH_FROM_SEQUENCE(sequence_checker_);
  157. // If a callback has been overridden previously, we'll give preference to it.
  158. if (GetActivationFactoryFunctionCallback()) {
  159. get_activation_factory_function_ =
  160. GetActivationFactoryFunctionCallback().Run();
  161. } else {
  162. get_activation_factory_function_ = &base::win::RoGetActivationFactory;
  163. }
  164. }
  165. WgiDataFetcherWin::~WgiDataFetcherWin() {
  166. UnregisterEventHandlers();
  167. for (auto& map_entry : devices_) {
  168. if (map_entry.second) {
  169. map_entry.second->Shutdown();
  170. }
  171. }
  172. }
  173. GamepadSource WgiDataFetcherWin::source() {
  174. return Factory::static_source();
  175. }
  176. void WgiDataFetcherWin::OnAddedToProvider() {
  177. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  178. if (!base::win::HStringReference::ResolveCoreWinRTStringDelayload()) {
  179. initialization_state_ =
  180. InitializationState::kCoreWinrtStringDelayLoadFailed;
  181. return;
  182. }
  183. HRESULT hr = get_activation_factory_function_(
  184. base::win::HStringReference(RuntimeClass_Windows_Gaming_Input_Gamepad)
  185. .Get(),
  186. IID_PPV_ARGS(&gamepad_statics_));
  187. if (FAILED(hr)) {
  188. initialization_state_ = InitializationState::kRoGetActivationFactoryFailed;
  189. return;
  190. }
  191. // Create a Windows::Foundation::IEventHandler that runs a
  192. // base::RepeatingCallback() on the gamepad polling thread when a gamepad
  193. // is added or removed. This callback stores the current sequence task runner
  194. // and weak pointer, so those two objects would remain active until the
  195. // callback returns.
  196. added_event_token_ = AddEventHandler(
  197. gamepad_statics_.Get(),
  198. &ABI::Windows::Gaming::Input::IGamepadStatics::add_GamepadAdded,
  199. base::BindRepeating(&WgiDataFetcherWin::OnGamepadAdded,
  200. weak_factory_.GetWeakPtr()));
  201. if (!added_event_token_) {
  202. initialization_state_ = InitializationState::kAddGamepadAddedFailed;
  203. UnregisterEventHandlers();
  204. return;
  205. }
  206. removed_event_token_ = AddEventHandler(
  207. gamepad_statics_.Get(),
  208. &ABI::Windows::Gaming::Input::IGamepadStatics::add_GamepadRemoved,
  209. base::BindRepeating(&WgiDataFetcherWin::OnGamepadRemoved,
  210. weak_factory_.GetWeakPtr()));
  211. if (!removed_event_token_) {
  212. initialization_state_ = InitializationState::kAddGamepadRemovedFailed;
  213. UnregisterEventHandlers();
  214. return;
  215. }
  216. initialization_state_ = InitializationState::kInitialized;
  217. }
  218. void WgiDataFetcherWin::OnGamepadAdded(
  219. IInspectable* /* sender */,
  220. ABI::Windows::Gaming::Input::IGamepad* gamepad) {
  221. // While base::win::AddEventHandler stores the sequence_task_runner in the
  222. // callback function object, it post the task back to the same sequence - the
  223. // gamepad polling thread when the callback is returned on a different thread
  224. // from the IGamepadStatics COM API. Thus `OnGamepadAdded` is also running on
  225. // gamepad polling thread, it is the only thread that is able to access the
  226. // `devices_` object, making it thread-safe.
  227. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  228. if (initialization_state_ != InitializationState::kInitialized)
  229. return;
  230. const std::u16string display_name = GetGamepadDisplayName(gamepad);
  231. GamepadId gamepad_id =
  232. GetGamepadId(display_name, gamepad, get_activation_factory_function_);
  233. if (!ShouldEnumerateGamepad(gamepad_id)) {
  234. return;
  235. }
  236. int source_id = next_source_id_++;
  237. PadState* state = GetPadState(source_id);
  238. if (!state)
  239. return;
  240. state->is_initialized = true;
  241. Gamepad& pad = state->data;
  242. pad.SetID(display_name);
  243. pad.connected = true;
  244. if (HasTriggerRumbleSupport(gamepad_id)) {
  245. pad.vibration_actuator.type = GamepadHapticActuatorType::kTriggerRumble;
  246. } else {
  247. pad.vibration_actuator.type = GamepadHapticActuatorType::kDualRumble;
  248. }
  249. pad.vibration_actuator.not_null = true;
  250. pad.mapping = GamepadMapping::kStandard;
  251. devices_[source_id] = std::make_unique<WgiGamepadDevice>(gamepad);
  252. }
  253. void WgiDataFetcherWin::OnGamepadRemoved(
  254. IInspectable* /* sender */,
  255. ABI::Windows::Gaming::Input::IGamepad* gamepad) {
  256. // While ::device::AddEventHandler stores the sequence_task_runner in the
  257. // callback function object, it post the task back to the same sequence - the
  258. // gamepad polling thread when the callback is returned on a different thread
  259. // from the IGamepadStatics COM API. Thus `OnGamepadRemoved` is also running
  260. // on gamepad polling thread, it is the only thread that is able to access the
  261. // `devices_` object, making it thread-safe.
  262. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  263. DCHECK_EQ(initialization_state_, InitializationState::kInitialized);
  264. base::EraseIf(devices_, [=](const auto& map_entry) {
  265. if (map_entry.second->GetGamepad().Get() == gamepad) {
  266. map_entry.second->Shutdown();
  267. return true;
  268. }
  269. return false;
  270. });
  271. }
  272. void WgiDataFetcherWin::GetGamepadData(bool devices_changed_hint) {
  273. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  274. for (const auto& map_entry : devices_) {
  275. PadState* state = GetPadState(map_entry.first);
  276. if (!state)
  277. continue;
  278. ABI::Windows::Gaming::Input::GamepadReading reading;
  279. Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad> gamepad =
  280. map_entry.second->GetGamepad();
  281. if (FAILED(gamepad->GetCurrentReading(&reading)))
  282. continue;
  283. Gamepad& pad = state->data;
  284. pad.timestamp = CurrentTimeInMicroseconds();
  285. const uint32_t num_paddles = GetPaddleNumber(gamepad);
  286. if (num_paddles == 0) {
  287. pad.buttons_length = BUTTON_INDEX_COUNT - 1; // No meta.
  288. } else {
  289. pad.buttons_length = BUTTON_INDEX_COUNT + num_paddles;
  290. }
  291. static constexpr struct {
  292. int button_index;
  293. ABI::Windows::Gaming::Input::GamepadButtons wgi_button_mask;
  294. } kButtonMappings[] = {
  295. {BUTTON_INDEX_PRIMARY, ABI::Windows::Gaming::Input::GamepadButtons_A},
  296. {BUTTON_INDEX_SECONDARY, ABI::Windows::Gaming::Input::GamepadButtons_B},
  297. {BUTTON_INDEX_TERTIARY, ABI::Windows::Gaming::Input::GamepadButtons_X},
  298. {BUTTON_INDEX_QUATERNARY,
  299. ABI::Windows::Gaming::Input::GamepadButtons_Y},
  300. {BUTTON_INDEX_LEFT_SHOULDER,
  301. ABI::Windows::Gaming::Input::GamepadButtons_LeftShoulder},
  302. {BUTTON_INDEX_RIGHT_SHOULDER,
  303. ABI::Windows::Gaming::Input::GamepadButtons_RightShoulder},
  304. {BUTTON_INDEX_BACK_SELECT,
  305. ABI::Windows::Gaming::Input::GamepadButtons_View},
  306. {BUTTON_INDEX_START, ABI::Windows::Gaming::Input::GamepadButtons_Menu},
  307. {BUTTON_INDEX_LEFT_THUMBSTICK,
  308. ABI::Windows::Gaming::Input::GamepadButtons_LeftThumbstick},
  309. {BUTTON_INDEX_RIGHT_THUMBSTICK,
  310. ABI::Windows::Gaming::Input::GamepadButtons_RightThumbstick},
  311. {BUTTON_INDEX_DPAD_UP,
  312. ABI::Windows::Gaming::Input::GamepadButtons_DPadUp},
  313. {BUTTON_INDEX_DPAD_DOWN,
  314. ABI::Windows::Gaming::Input::GamepadButtons_DPadDown},
  315. {BUTTON_INDEX_DPAD_LEFT,
  316. ABI::Windows::Gaming::Input::GamepadButtons_DPadLeft},
  317. {BUTTON_INDEX_DPAD_RIGHT,
  318. ABI::Windows::Gaming::Input::GamepadButtons_DPadRight},
  319. {BUTTON_INDEX_META + 1,
  320. ABI::Windows::Gaming::Input::GamepadButtons_Paddle1},
  321. {BUTTON_INDEX_META + 2,
  322. ABI::Windows::Gaming::Input::GamepadButtons_Paddle2},
  323. {BUTTON_INDEX_META + 3,
  324. ABI::Windows::Gaming::Input::GamepadButtons_Paddle3},
  325. {BUTTON_INDEX_META + 4,
  326. ABI::Windows::Gaming::Input::GamepadButtons_Paddle4},
  327. };
  328. for (const auto& button : kButtonMappings) {
  329. if (reading.Buttons & button.wgi_button_mask) {
  330. pad.buttons[button.button_index].pressed = true;
  331. pad.buttons[button.button_index].value = 1.0f;
  332. } else {
  333. pad.buttons[button.button_index].pressed = false;
  334. pad.buttons[button.button_index].value = 0.0f;
  335. }
  336. }
  337. pad.buttons[BUTTON_INDEX_LEFT_TRIGGER].pressed =
  338. reading.LeftTrigger > GamepadButton::kDefaultButtonPressedThreshold;
  339. pad.buttons[BUTTON_INDEX_LEFT_TRIGGER].value = reading.LeftTrigger;
  340. pad.buttons[BUTTON_INDEX_RIGHT_TRIGGER].pressed =
  341. reading.RightTrigger > GamepadButton::kDefaultButtonPressedThreshold;
  342. pad.buttons[BUTTON_INDEX_RIGHT_TRIGGER].value = reading.RightTrigger;
  343. pad.axes_length = AXIS_INDEX_COUNT;
  344. // Invert the Y thumbstick axes to match the Standard Gamepad. WGI
  345. // thumbstick axes use +up/+right but the Standard Gamepad uses
  346. // +down/+right.
  347. pad.axes[AXIS_INDEX_LEFT_STICK_X] = reading.LeftThumbstickX;
  348. pad.axes[AXIS_INDEX_LEFT_STICK_Y] = reading.LeftThumbstickY * -1.0f;
  349. pad.axes[AXIS_INDEX_RIGHT_STICK_X] = reading.RightThumbstickX;
  350. pad.axes[AXIS_INDEX_RIGHT_STICK_Y] = reading.RightThumbstickY * -1.0f;
  351. }
  352. }
  353. void WgiDataFetcherWin::PlayEffect(
  354. int source_id,
  355. mojom::GamepadHapticEffectType type,
  356. mojom::GamepadEffectParametersPtr params,
  357. mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback callback,
  358. scoped_refptr<base::SequencedTaskRunner> callback_runner) {
  359. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  360. auto map_entry = devices_.find(source_id);
  361. if (map_entry == devices_.end()) {
  362. RunVibrationCallback(
  363. std::move(callback), std::move(callback_runner),
  364. mojom::GamepadHapticsResult::GamepadHapticsResultNotSupported);
  365. return;
  366. }
  367. map_entry->second->PlayEffect(type, std::move(params), std::move(callback),
  368. std::move(callback_runner));
  369. }
  370. void WgiDataFetcherWin::ResetVibration(
  371. int source_id,
  372. mojom::GamepadHapticsManager::ResetVibrationActuatorCallback callback,
  373. scoped_refptr<base::SequencedTaskRunner> callback_runner) {
  374. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  375. auto map_entry = devices_.find(source_id);
  376. if (map_entry == devices_.end()) {
  377. RunVibrationCallback(
  378. std::move(callback), std::move(callback_runner),
  379. mojom::GamepadHapticsResult::GamepadHapticsResultNotSupported);
  380. return;
  381. }
  382. map_entry->second->ResetVibration(std::move(callback),
  383. std::move(callback_runner));
  384. }
  385. // static
  386. void WgiDataFetcherWin::OverrideActivationFactoryFunctionForTesting(
  387. WgiDataFetcherWin::ActivationFactoryFunctionCallback callback) {
  388. GetActivationFactoryFunctionCallback() = callback;
  389. }
  390. // static
  391. WgiDataFetcherWin::ActivationFactoryFunctionCallback&
  392. WgiDataFetcherWin::GetActivationFactoryFunctionCallback() {
  393. static base::NoDestructor<
  394. WgiDataFetcherWin::ActivationFactoryFunctionCallback>
  395. instance;
  396. return *instance;
  397. }
  398. WgiDataFetcherWin::InitializationState
  399. WgiDataFetcherWin::GetInitializationState() const {
  400. return initialization_state_;
  401. }
  402. std::u16string WgiDataFetcherWin::GetGamepadDisplayName(
  403. ABI::Windows::Gaming::Input::IGamepad* gamepad) {
  404. static constexpr char16_t kDefaultDisplayName[] = u"Unknown Gamepad";
  405. Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IRawGameController>
  406. raw_game_controller = ::device::GetRawGameController(
  407. gamepad, get_activation_factory_function_);
  408. if (!raw_game_controller) {
  409. return kDefaultDisplayName;
  410. }
  411. Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IRawGameController2>
  412. raw_game_controller2;
  413. if (FAILED(raw_game_controller.As(&raw_game_controller2))) {
  414. return kDefaultDisplayName;
  415. }
  416. HSTRING display_name;
  417. if (FAILED(raw_game_controller2->get_DisplayName(&display_name))) {
  418. return kDefaultDisplayName;
  419. }
  420. base::win::ScopedHString scoped_display_name(display_name);
  421. return base::AsString16(scoped_display_name.Get());
  422. }
  423. void WgiDataFetcherWin::UnregisterEventHandlers() {
  424. if (added_event_token_) {
  425. HRESULT hr =
  426. gamepad_statics_->remove_GamepadAdded(added_event_token_.value());
  427. if (FAILED(hr)) {
  428. DLOG(ERROR) << "Removing GamepadAdded Handler failed: "
  429. << logging::SystemErrorCodeToString(hr);
  430. }
  431. }
  432. if (removed_event_token_) {
  433. HRESULT hr =
  434. gamepad_statics_->remove_GamepadRemoved(removed_event_token_.value());
  435. if (FAILED(hr)) {
  436. DLOG(ERROR) << "Removing GamepadRemoved Handler failed: "
  437. << logging::SystemErrorCodeToString(hr);
  438. }
  439. }
  440. }
  441. } // namespace device