dinput_source.cpp (15974B)
1 // SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #define INITGUID 5 6 #include "dinput_source.h" 7 #include "input_manager.h" 8 #include "platform_misc.h" 9 10 #include "common/assert.h" 11 #include "common/log.h" 12 #include "common/string_util.h" 13 14 #include "fmt/format.h" 15 16 #include <cmath> 17 #include <limits> 18 Log_SetChannel(DInputSource); 19 20 using PFNDIRECTINPUT8CREATE = HRESULT(WINAPI*)(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID* ppvOut, 21 LPUNKNOWN punkOuter); 22 using PFNGETDFDIJOYSTICK = LPCDIDATAFORMAT(WINAPI*)(); 23 24 DInputSource::DInputSource() = default; 25 26 DInputSource::~DInputSource() 27 { 28 m_controllers.clear(); 29 m_dinput.Reset(); 30 if (m_dinput_module) 31 FreeLibrary(m_dinput_module); 32 } 33 34 std::array<bool, DInputSource::NUM_HAT_DIRECTIONS> DInputSource::GetHatButtons(DWORD hat) 35 { 36 std::array<bool, NUM_HAT_DIRECTIONS> buttons = {}; 37 38 const WORD hv = LOWORD(hat); 39 if (hv != 0xFFFF) 40 { 41 if ((hv >= 0 && hv < 9000) || hv >= 31500) 42 buttons[HAT_DIRECTION_UP] = true; 43 if (hv >= 4500 && hv < 18000) 44 buttons[HAT_DIRECTION_RIGHT] = true; 45 if (hv >= 13500 && hv < 27000) 46 buttons[HAT_DIRECTION_DOWN] = true; 47 if (hv >= 22500) 48 buttons[HAT_DIRECTION_LEFT] = true; 49 } 50 51 return buttons; 52 } 53 54 std::string DInputSource::GetDeviceIdentifier(u32 index) 55 { 56 return fmt::format("DInput-{}", index); 57 } 58 59 static constexpr std::array<const char*, DInputSource::NUM_HAT_DIRECTIONS> s_hat_directions = { 60 {"Up", "Down", "Left", "Right"}}; 61 62 bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) 63 { 64 m_dinput_module = LoadLibraryW(L"dinput8"); 65 if (!m_dinput_module) 66 { 67 ERROR_LOG("Failed to load DInput module."); 68 return false; 69 } 70 71 PFNDIRECTINPUT8CREATE create = 72 reinterpret_cast<PFNDIRECTINPUT8CREATE>(GetProcAddress(m_dinput_module, "DirectInput8Create")); 73 PFNGETDFDIJOYSTICK get_joystick_data_format = 74 reinterpret_cast<PFNGETDFDIJOYSTICK>(GetProcAddress(m_dinput_module, "GetdfDIJoystick")); 75 if (!create || !get_joystick_data_format) 76 { 77 ERROR_LOG("Failed to get DInput function pointers."); 78 return false; 79 } 80 81 HRESULT hr = create(GetModuleHandleA(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8W, 82 reinterpret_cast<LPVOID*>(m_dinput.GetAddressOf()), nullptr); 83 m_joystick_data_format = get_joystick_data_format(); 84 if (FAILED(hr) || !m_joystick_data_format) 85 { 86 ERROR_LOG("DirectInput8Create() failed: {}", static_cast<unsigned>(hr)); 87 return false; 88 } 89 90 // need to release the lock while we're enumerating, because we call winId(). 91 settings_lock.unlock(); 92 const std::optional<WindowInfo> toplevel_wi(Host::GetTopLevelWindowInfo()); 93 settings_lock.lock(); 94 95 if (!toplevel_wi.has_value() || toplevel_wi->type != WindowInfo::Type::Win32) 96 { 97 ERROR_LOG("Missing top level window, cannot add DInput devices."); 98 return false; 99 } 100 101 m_toplevel_window = static_cast<HWND>(toplevel_wi->window_handle); 102 ReloadDevices(); 103 return true; 104 } 105 106 void DInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) 107 { 108 // noop 109 } 110 111 static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCEW lpddi, LPVOID pvRef) 112 { 113 static_cast<std::vector<DIDEVICEINSTANCEW>*>(pvRef)->push_back(*lpddi); 114 return DIENUM_CONTINUE; 115 } 116 117 bool DInputSource::ReloadDevices() 118 { 119 // detect any removals 120 PollEvents(); 121 122 // look for new devices 123 std::vector<DIDEVICEINSTANCEW> devices; 124 m_dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumCallback, &devices, DIEDFL_ATTACHEDONLY); 125 126 VERBOSE_LOG("Enumerated {} devices", devices.size()); 127 128 bool changed = false; 129 for (DIDEVICEINSTANCEW inst : devices) 130 { 131 // do we already have this one? 132 if (std::any_of(m_controllers.begin(), m_controllers.end(), 133 [&inst](const ControllerData& cd) { return inst.guidInstance == cd.guid; })) 134 { 135 // yup, so skip it 136 continue; 137 } 138 139 ControllerData cd; 140 cd.guid = inst.guidInstance; 141 HRESULT hr = m_dinput->CreateDevice(inst.guidInstance, cd.device.GetAddressOf(), nullptr); 142 if (FAILED(hr)) [[unlikely]] 143 { 144 WARNING_LOG("Failed to create instance of device [{}, {}]", 145 StringUtil::WideStringToUTF8String(inst.tszProductName), 146 StringUtil::WideStringToUTF8String(inst.tszInstanceName)); 147 continue; 148 } 149 150 const std::string name(StringUtil::WideStringToUTF8String(inst.tszProductName)); 151 if (AddDevice(cd, name)) 152 { 153 const u32 index = static_cast<u32>(m_controllers.size()); 154 m_controllers.push_back(std::move(cd)); 155 InputManager::OnInputDeviceConnected(GetDeviceIdentifier(index), name); 156 changed = true; 157 } 158 } 159 160 return changed; 161 } 162 163 void DInputSource::Shutdown() 164 { 165 while (!m_controllers.empty()) 166 { 167 const u32 index = static_cast<u32>(m_controllers.size() - 1); 168 InputManager::OnInputDeviceDisconnected( 169 InputBindingKey{{.source_type = InputSourceType::DInput, .source_index = index}}, 170 GetDeviceIdentifier(static_cast<u32>(m_controllers.size() - 1))); 171 m_controllers.pop_back(); 172 } 173 } 174 175 bool DInputSource::AddDevice(ControllerData& cd, const std::string& name) 176 { 177 HRESULT hr = cd.device->SetCooperativeLevel(m_toplevel_window, DISCL_BACKGROUND | DISCL_EXCLUSIVE); 178 if (FAILED(hr)) 179 { 180 hr = cd.device->SetCooperativeLevel(m_toplevel_window, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE); 181 if (FAILED(hr)) 182 { 183 ERROR_LOG("Failed to set cooperative level for '{}'", name); 184 return false; 185 } 186 187 WARNING_LOG("Failed to set exclusive mode for '{}'", name); 188 } 189 190 hr = cd.device->SetDataFormat(m_joystick_data_format); 191 if (FAILED(hr)) 192 { 193 ERROR_LOG("Failed to set data format for '{}'", name); 194 return false; 195 } 196 197 hr = cd.device->Acquire(); 198 if (FAILED(hr)) 199 { 200 ERROR_LOG("Failed to acquire device '{}'", name); 201 return false; 202 } 203 204 DIDEVCAPS caps = {}; 205 caps.dwSize = sizeof(caps); 206 hr = cd.device->GetCapabilities(&caps); 207 if (FAILED(hr)) 208 { 209 ERROR_LOG("Failed to get capabilities for '{}'", name); 210 return false; 211 } 212 213 cd.num_buttons = caps.dwButtons; 214 215 static constexpr const u32 axis_offsets[] = {OFFSETOF(DIJOYSTATE, lX), OFFSETOF(DIJOYSTATE, lY), 216 OFFSETOF(DIJOYSTATE, lZ), OFFSETOF(DIJOYSTATE, lRz), 217 OFFSETOF(DIJOYSTATE, lRx), OFFSETOF(DIJOYSTATE, lRy), 218 OFFSETOF(DIJOYSTATE, rglSlider[0]), OFFSETOF(DIJOYSTATE, rglSlider[1])}; 219 for (const u32 offset : axis_offsets) 220 { 221 // ask for 16 bits of axis range 222 DIPROPRANGE range = {}; 223 range.diph.dwSize = sizeof(range); 224 range.diph.dwHeaderSize = sizeof(range.diph); 225 range.diph.dwHow = DIPH_BYOFFSET; 226 range.diph.dwObj = static_cast<DWORD>(offset); 227 range.lMin = std::numeric_limits<s16>::min(); 228 range.lMax = std::numeric_limits<s16>::max(); 229 hr = cd.device->SetProperty(DIPROP_RANGE, &range.diph); 230 231 // did it apply? 232 if (SUCCEEDED(cd.device->GetProperty(DIPROP_RANGE, &range.diph))) 233 cd.axis_offsets.push_back(offset); 234 } 235 236 cd.num_buttons = std::min(static_cast<u32>(caps.dwButtons), static_cast<u32>(std::size(cd.last_state.rgbButtons))); 237 cd.num_hats = std::min(static_cast<u32>(caps.dwPOVs), static_cast<u32>(std::size(cd.last_state.rgdwPOV))); 238 239 hr = cd.device->Poll(); 240 if (hr == DI_NOEFFECT) 241 cd.needs_poll = false; 242 else if (hr != DI_OK) 243 WARNING_LOG("Polling device '{}' failed: {:08X}", name, static_cast<unsigned>(hr)); 244 245 hr = cd.device->GetDeviceState(sizeof(cd.last_state), &cd.last_state); 246 if (hr != DI_OK) 247 WARNING_LOG("GetDeviceState() for '{}' failed: {:08X}", name, static_cast<unsigned>(hr)); 248 249 INFO_LOG("{} has {} buttons, {} axes, {} hats", name, cd.num_buttons, static_cast<u32>(cd.axis_offsets.size()), 250 cd.num_hats); 251 252 return (cd.num_buttons > 0 || !cd.axis_offsets.empty() || cd.num_hats > 0); 253 } 254 255 void DInputSource::PollEvents() 256 { 257 for (size_t i = 0; i < m_controllers.size();) 258 { 259 ControllerData& cd = m_controllers[i]; 260 if (cd.needs_poll) 261 cd.device->Poll(); 262 263 DIJOYSTATE js; 264 HRESULT hr = cd.device->GetDeviceState(sizeof(js), &js); 265 if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) 266 { 267 hr = cd.device->Acquire(); 268 if (hr == DI_OK) 269 hr = cd.device->GetDeviceState(sizeof(js), &js); 270 271 if (hr != DI_OK) 272 { 273 InputManager::OnInputDeviceDisconnected( 274 InputBindingKey{{.source_type = InputSourceType::DInput, .source_index = static_cast<u32>(i)}}, 275 GetDeviceIdentifier(static_cast<u32>(i))); 276 m_controllers.erase(m_controllers.begin() + i); 277 continue; 278 } 279 } 280 else if (hr != DI_OK) 281 { 282 WARNING_LOG("GetDeviceState() failed: {:08X}", static_cast<unsigned>(hr)); 283 i++; 284 continue; 285 } 286 287 CheckForStateChanges(i, js); 288 i++; 289 } 290 } 291 292 std::vector<std::pair<std::string, std::string>> DInputSource::EnumerateDevices() 293 { 294 std::vector<std::pair<std::string, std::string>> ret; 295 for (size_t i = 0; i < m_controllers.size(); i++) 296 { 297 DIDEVICEINSTANCEW dii; 298 dii.dwSize = sizeof(DIDEVICEINSTANCEW); 299 std::string name; 300 if (SUCCEEDED(m_controllers[i].device->GetDeviceInfo(&dii))) 301 name = StringUtil::WideStringToUTF8String(dii.tszProductName); 302 303 if (name.empty()) 304 name = "Unknown"; 305 306 ret.emplace_back(GetDeviceIdentifier(static_cast<u32>(i)), std::move(name)); 307 } 308 309 return ret; 310 } 311 312 std::vector<InputBindingKey> DInputSource::EnumerateMotors() 313 { 314 return {}; 315 } 316 317 bool DInputSource::GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping) 318 { 319 return {}; 320 } 321 322 void DInputSource::UpdateMotorState(InputBindingKey key, float intensity) 323 { 324 // not supported 325 } 326 327 void DInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, 328 float small_intensity) 329 { 330 // not supported 331 } 332 333 std::optional<InputBindingKey> DInputSource::ParseKeyString(std::string_view device, std::string_view binding) 334 { 335 if (!device.starts_with("DInput-") || binding.empty()) 336 return std::nullopt; 337 338 const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(7)); 339 if (!player_id.has_value() || player_id.value() < 0) 340 return std::nullopt; 341 342 InputBindingKey key = {}; 343 key.source_type = InputSourceType::DInput; 344 key.source_index = static_cast<u32>(player_id.value()); 345 346 if (binding.starts_with("+Axis") || binding.starts_with("-Axis")) 347 { 348 std::string_view end; 349 const std::optional<u32> axis_index = StringUtil::FromChars<u32>(binding.substr(5), 10, &end); 350 if (!axis_index.has_value()) 351 return std::nullopt; 352 353 key.source_subtype = InputSubclass::ControllerAxis; 354 key.data = axis_index.value(); 355 key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None; 356 key.invert = (end == "~"); 357 return key; 358 } 359 else if (binding.starts_with("FullAxis")) 360 { 361 std::string_view end; 362 const std::optional<u32> axis_index = StringUtil::FromChars<u32>(binding.substr(8), 10, &end); 363 if (!axis_index.has_value()) 364 return std::nullopt; 365 366 key.source_subtype = InputSubclass::ControllerAxis; 367 key.data = axis_index.value(); 368 key.modifier = InputModifier::FullAxis; 369 key.invert = (end == "~"); 370 return key; 371 } 372 else if (binding.starts_with("Hat")) 373 { 374 if (binding[3] < '0' || binding[3] > '9' || binding.length() < 5) 375 return std::nullopt; 376 377 const u32 hat_index = binding[3] - '0'; 378 const std::string_view hat_dir(binding.substr(4)); 379 for (u32 i = 0; i < NUM_HAT_DIRECTIONS; i++) 380 { 381 if (hat_dir == s_hat_directions[i]) 382 { 383 key.source_subtype = InputSubclass::ControllerButton; 384 key.data = MAX_NUM_BUTTONS + hat_index * NUM_HAT_DIRECTIONS + i; 385 return key; 386 } 387 } 388 389 // bad direction 390 return std::nullopt; 391 } 392 else if (binding.starts_with("Button")) 393 { 394 const std::optional<u32> button_index = StringUtil::FromChars<u32>(binding.substr(6)); 395 if (!button_index.has_value()) 396 return std::nullopt; 397 398 key.source_subtype = InputSubclass::ControllerButton; 399 key.data = button_index.value(); 400 return key; 401 } 402 403 // unknown axis/button 404 return std::nullopt; 405 } 406 407 TinyString DInputSource::ConvertKeyToString(InputBindingKey key) 408 { 409 TinyString ret; 410 411 if (key.source_type == InputSourceType::DInput) 412 { 413 if (key.source_subtype == InputSubclass::ControllerAxis) 414 { 415 const char* modifier = 416 (key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+")); 417 ret.format("DInput-{}/{}Axis{}{}", u32(key.source_index), modifier, u32(key.data), key.invert ? "~" : ""); 418 } 419 else if (key.source_subtype == InputSubclass::ControllerButton && key.data >= MAX_NUM_BUTTONS) 420 { 421 const u32 hat_num = (key.data - MAX_NUM_BUTTONS) / NUM_HAT_DIRECTIONS; 422 const u32 hat_dir = (key.data - MAX_NUM_BUTTONS) % NUM_HAT_DIRECTIONS; 423 ret.format("DInput-{}/Hat{}{}", u32(key.source_index), hat_num, s_hat_directions[hat_dir]); 424 } 425 else if (key.source_subtype == InputSubclass::ControllerButton) 426 { 427 ret.format("DInput-{}/Button{}", u32(key.source_index), u32(key.data)); 428 } 429 } 430 431 return ret; 432 } 433 434 TinyString DInputSource::ConvertKeyToIcon(InputBindingKey key) 435 { 436 return {}; 437 } 438 439 void DInputSource::CheckForStateChanges(size_t index, const DIJOYSTATE& new_state) 440 { 441 ControllerData& cd = m_controllers[index]; 442 DIJOYSTATE& last_state = cd.last_state; 443 444 for (size_t i = 0; i < cd.axis_offsets.size(); i++) 445 { 446 LONG new_value; 447 LONG old_value; 448 std::memcpy(&old_value, reinterpret_cast<const u8*>(&cd.last_state) + cd.axis_offsets[i], sizeof(old_value)); 449 std::memcpy(&new_value, reinterpret_cast<const u8*>(&new_state) + cd.axis_offsets[i], sizeof(new_value)); 450 if (old_value != new_value) 451 { 452 std::memcpy(reinterpret_cast<u8*>(&cd.last_state) + cd.axis_offsets[i], &new_value, sizeof(new_value)); 453 454 // TODO: Use the range from caps? 455 const float value = static_cast<float>(new_value) / (new_value < 0 ? 32768.0f : 32767.0f); 456 InputManager::InvokeEvents( 457 MakeGenericControllerAxisKey(InputSourceType::DInput, static_cast<u32>(index), static_cast<u32>(i)), value, 458 GenericInputBinding::Unknown); 459 } 460 } 461 462 for (u32 i = 0; i < cd.num_buttons; i++) 463 { 464 if (last_state.rgbButtons[i] != new_state.rgbButtons[i]) 465 { 466 last_state.rgbButtons[i] = new_state.rgbButtons[i]; 467 468 const float value = (new_state.rgbButtons[i] != 0) ? 1.0f : 0.0f; 469 InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::DInput, static_cast<u32>(index), i), 470 value, GenericInputBinding::Unknown); 471 } 472 } 473 474 for (u32 i = 0; i < cd.num_hats; i++) 475 { 476 if (last_state.rgdwPOV[i] != new_state.rgdwPOV[i]) 477 { 478 // map hats to the last buttons 479 const std::array<bool, NUM_HAT_DIRECTIONS> old_buttons(GetHatButtons(last_state.rgdwPOV[i])); 480 const std::array<bool, NUM_HAT_DIRECTIONS> new_buttons(GetHatButtons(new_state.rgdwPOV[i])); 481 last_state.rgdwPOV[i] = new_state.rgdwPOV[i]; 482 483 for (u32 j = 0; j < NUM_HAT_DIRECTIONS; j++) 484 { 485 if (old_buttons[j] != new_buttons[j]) 486 { 487 const float value = (new_buttons[j] ? 1.0f : 0.0f); 488 InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::DInput, static_cast<u32>(index), 489 cd.num_buttons + (i * NUM_HAT_DIRECTIONS) + j), 490 value, GenericInputBinding::Unknown); 491 } 492 } 493 } 494 } 495 } 496 497 std::unique_ptr<InputSource> InputSource::CreateDInputSource() 498 { 499 return std::make_unique<DInputSource>(); 500 }