// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "device/gamepad/raw_input_data_fetcher_win.h" #include #include #include "base/bind.h" #include "base/strings/sys_string_conversions.h" #include "base/trace_event/trace_event.h" #include "device/gamepad/gamepad_standard_mappings.h" #include "device/gamepad/gamepad_uma.h" #include "device/gamepad/nintendo_controller.h" namespace device { namespace { const uint16_t DeviceUsages[] = { RawInputGamepadDeviceWin::kGenericDesktopJoystick, RawInputGamepadDeviceWin::kGenericDesktopGamePad, RawInputGamepadDeviceWin::kGenericDesktopMultiAxisController, }; } // namespace RawInputDataFetcher::RawInputDataFetcher() = default; RawInputDataFetcher::~RawInputDataFetcher() { StopMonitor(); ClearControllers(); DCHECK(!window_); DCHECK(!events_monitored_); } GamepadSource RawInputDataFetcher::source() { return Factory::static_source(); } RAWINPUTDEVICE* RawInputDataFetcher::GetRawInputDevices(DWORD flags) { size_t usage_count = std::size(DeviceUsages); std::unique_ptr devices(new RAWINPUTDEVICE[usage_count]); for (size_t i = 0; i < usage_count; ++i) { devices[i].dwFlags = flags; devices[i].usUsagePage = 1; devices[i].usUsage = DeviceUsages[i]; devices[i].hwndTarget = (flags & RIDEV_REMOVE) ? 0 : window_->hwnd(); } return devices.release(); } void RawInputDataFetcher::PauseHint(bool pause) { if (pause) StopMonitor(); else StartMonitor(); } void RawInputDataFetcher::StartMonitor() { if (events_monitored_) return; if (!window_) { window_ = std::make_unique(); if (!window_->Create(base::BindRepeating( &RawInputDataFetcher::HandleMessage, base::Unretained(this)))) { PLOG(ERROR) << "Failed to create the raw input window"; window_.reset(); return; } } // Register to receive raw HID input. std::unique_ptr devices( GetRawInputDevices(RIDEV_INPUTSINK)); if (!::RegisterRawInputDevices(devices.get(), std::size(DeviceUsages), sizeof(RAWINPUTDEVICE))) { PLOG(ERROR) << "RegisterRawInputDevices() failed for RIDEV_INPUTSINK"; window_.reset(); return; } events_monitored_ = true; } void RawInputDataFetcher::StopMonitor() { if (!events_monitored_) return; // Stop receiving raw input. DCHECK(window_); std::unique_ptr devices(GetRawInputDevices(RIDEV_REMOVE)); if (!::RegisterRawInputDevices(devices.get(), std::size(DeviceUsages), sizeof(RAWINPUTDEVICE))) { PLOG(INFO) << "RegisterRawInputDevices() failed for RIDEV_REMOVE"; } events_monitored_ = false; window_.reset(); } bool RawInputDataFetcher::DisconnectUnrecognizedGamepad(int source_id) { for (auto it = controllers_.begin(); it != controllers_.end(); ++it) { if (it->second->GetSourceId() == source_id) { it->second->Shutdown(); controllers_.erase(it); return true; } } return false; } void RawInputDataFetcher::ClearControllers() { for (const auto& entry : controllers_) entry.second->Shutdown(); controllers_.clear(); } void RawInputDataFetcher::GetGamepadData(bool devices_changed_hint) { if (devices_changed_hint) EnumerateDevices(); for (const auto& entry : controllers_) { const RawInputGamepadDeviceWin* device = entry.second.get(); PadState* state = GetPadState(device->GetSourceId()); if (!state) continue; device->ReadPadState(&state->data); } } void RawInputDataFetcher::EnumerateDevices() { UINT count = 0; UINT result = ::GetRawInputDeviceList(nullptr, &count, sizeof(RAWINPUTDEVICELIST)); if (result == static_cast(-1)) { PLOG(ERROR) << "GetRawInputDeviceList() failed"; return; } DCHECK_EQ(0u, result); std::unique_ptr device_list( new RAWINPUTDEVICELIST[count]); result = ::GetRawInputDeviceList(device_list.get(), &count, sizeof(RAWINPUTDEVICELIST)); if (result == static_cast(-1)) { PLOG(ERROR) << "GetRawInputDeviceList() failed"; return; } DCHECK_EQ(count, result); std::unordered_set enumerated_device_handles; for (UINT i = 0; i < count; ++i) { if (device_list[i].dwType == RIM_TYPEHID) { HANDLE device_handle = device_list[i].hDevice; auto controller_it = controllers_.find(device_handle); RawInputGamepadDeviceWin* device; if (controller_it != controllers_.end()) { device = controller_it->second.get(); } else { int source_id = ++last_source_id_; auto new_device = std::make_unique( device_handle, source_id); if (!new_device->IsValid()) { new_device->Shutdown(); continue; } const int vendor_int = new_device->GetVendorId(); const int product_int = new_device->GetProductId(); const int version_number = new_device->GetVersionNumber(); const std::wstring product_wstring = new_device->GetProductString(); const std::string product_string = base::SysWideToUTF8(product_wstring); const GamepadId gamepad_id = GamepadIdList::Get().GetGamepadId( product_string, vendor_int, product_int); if (NintendoController::IsNintendoController(gamepad_id)) { // Nintendo devices are handled by the Nintendo data fetcher. new_device->Shutdown(); continue; } bool is_recognized = GamepadId::kUnknownGamepad != gamepad_id; // Record gamepad metrics before excluding XInput devices. This allows // us to recognize XInput devices even though the XInput API masks // the vendor and product IDs. RecordConnectedGamepad(gamepad_id); // The presence of "IG_" in the device name indicates that this is an // XInput Gamepad. Skip enumerating these devices and let the XInput // path handle it. // http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014.aspx const std::wstring device_name = new_device->GetDeviceName(); if (filter_xinput_ && device_name.find(L"IG_") != std::wstring::npos) { new_device->Shutdown(); continue; } PadState* state = GetPadState(source_id, is_recognized); if (!state) { new_device->Shutdown(); continue; // No slot available for this gamepad. } auto emplace_result = controllers_.emplace(device_handle, std::move(new_device)); device = emplace_result.first->second.get(); Gamepad& pad = state->data; pad.connected = true; pad.vibration_actuator.type = GamepadHapticActuatorType::kDualRumble; pad.vibration_actuator.not_null = device->SupportsVibration(); state->mapper = GetGamepadStandardMappingFunction( product_string, vendor_int, product_int, /*hid_specification_version=*/0, version_number, GAMEPAD_BUS_UNKNOWN); state->axis_mask = 0; state->button_mask = 0; UpdateGamepadStrings(product_string, vendor_int, product_int, state->mapper != nullptr, pad); } enumerated_device_handles.insert(device_handle); } } // Clear out old controllers that weren't part of this enumeration pass. auto controller_it = controllers_.begin(); while (controller_it != controllers_.end()) { if (enumerated_device_handles.find(controller_it->first) == enumerated_device_handles.end()) { controller_it->second->Shutdown(); controller_it = controllers_.erase(controller_it); } else { ++controller_it; } } } RawInputGamepadDeviceWin* RawInputDataFetcher::DeviceFromSourceId( int source_id) { for (const auto& entry : controllers_) { if (entry.second->GetSourceId() == source_id) return entry.second.get(); } return nullptr; } void RawInputDataFetcher::PlayEffect( int source_id, mojom::GamepadHapticEffectType type, mojom::GamepadEffectParametersPtr params, mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback callback, scoped_refptr callback_runner) { RawInputGamepadDeviceWin* device = DeviceFromSourceId(source_id); if (!device) { RunVibrationCallback( std::move(callback), std::move(callback_runner), mojom::GamepadHapticsResult::GamepadHapticsResultError); return; } if (!device->SupportsVibration()) { RunVibrationCallback( std::move(callback), std::move(callback_runner), mojom::GamepadHapticsResult::GamepadHapticsResultNotSupported); return; } device->PlayEffect(type, std::move(params), std::move(callback), std::move(callback_runner)); } void RawInputDataFetcher::ResetVibration( int source_id, mojom::GamepadHapticsManager::ResetVibrationActuatorCallback callback, scoped_refptr callback_runner) { RawInputGamepadDeviceWin* device = DeviceFromSourceId(source_id); if (!device) { RunVibrationCallback( std::move(callback), std::move(callback_runner), mojom::GamepadHapticsResult::GamepadHapticsResultError); return; } if (!device->SupportsVibration()) { RunVibrationCallback( std::move(callback), std::move(callback_runner), mojom::GamepadHapticsResult::GamepadHapticsResultNotSupported); return; } device->ResetVibration(std::move(callback), std::move(callback_runner)); } LRESULT RawInputDataFetcher::OnInput(HRAWINPUT input_handle) { // Get the size of the input record. UINT size = 0; UINT result = ::GetRawInputData(input_handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)); if (result == static_cast(-1)) { PLOG(ERROR) << "GetRawInputData() failed"; return 0; } DCHECK_EQ(0u, result); // Retrieve the input record. std::unique_ptr buffer(new uint8_t[size]); RAWINPUT* input = reinterpret_cast(buffer.get()); result = ::GetRawInputData(input_handle, RID_INPUT, buffer.get(), &size, sizeof(RAWINPUTHEADER)); if (result == static_cast(-1)) { PLOG(ERROR) << "GetRawInputData() failed"; return 0; } DCHECK_EQ(size, result); // Notify the observer about events generated locally. if (input->header.dwType == RIM_TYPEHID && input->header.hDevice != NULL) { auto it = controllers_.find(input->header.hDevice); if (it != controllers_.end()) it->second->UpdateGamepad(input); } return ::DefRawInputProc(&input, 1, sizeof(RAWINPUTHEADER)); } bool RawInputDataFetcher::HandleMessage(UINT message, WPARAM wparam, LPARAM lparam, LRESULT* result) { switch (message) { case WM_INPUT: *result = OnInput(reinterpret_cast(lparam)); return true; default: return false; } } } // namespace device