xinput_source.cpp (18829B)
1 // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "xinput_source.h" 5 #include "input_manager.h" 6 7 #include "common/assert.h" 8 #include "common/log.h" 9 #include "common/string_util.h" 10 11 #include "IconsPromptFont.h" 12 13 #include <cmath> 14 15 Log_SetChannel(XInputSource); 16 17 static const char* s_axis_names[XInputSource::NUM_AXES] = { 18 "LeftX", // AXIS_LEFTX 19 "LeftY", // AXIS_LEFTY 20 "RightX", // AXIS_RIGHTX 21 "RightY", // AXIS_RIGHTY 22 "LeftTrigger", // AXIS_TRIGGERLEFT 23 "RightTrigger", // AXIS_TRIGGERRIGHT 24 }; 25 static constexpr const char* s_axis_icons[][2] = { 26 {ICON_PF_LEFT_ANALOG_LEFT, ICON_PF_LEFT_ANALOG_RIGHT}, // AXIS_LEFTX 27 {ICON_PF_LEFT_ANALOG_UP, ICON_PF_LEFT_ANALOG_DOWN}, // AXIS_LEFTY 28 {ICON_PF_RIGHT_ANALOG_LEFT, ICON_PF_RIGHT_ANALOG_RIGHT}, // AXIS_RIGHTX 29 {ICON_PF_RIGHT_ANALOG_UP, ICON_PF_RIGHT_ANALOG_DOWN}, // AXIS_RIGHTY 30 {nullptr, ICON_PF_LEFT_TRIGGER_PULL}, // AXIS_TRIGGERLEFT 31 {nullptr, ICON_PF_RIGHT_TRIGGER_PULL}, // AXIS_TRIGGERRIGHT 32 }; 33 static const GenericInputBinding s_xinput_generic_binding_axis_mapping[][2] = { 34 {GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // AXIS_LEFTX 35 {GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // AXIS_LEFTY 36 {GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight}, // AXIS_RIGHTX 37 {GenericInputBinding::RightStickUp, GenericInputBinding::RightStickDown}, // AXIS_RIGHTY 38 {GenericInputBinding::Unknown, GenericInputBinding::L2}, // AXIS_TRIGGERLEFT 39 {GenericInputBinding::Unknown, GenericInputBinding::R2}, // AXIS_TRIGGERRIGHT 40 }; 41 42 static const char* s_button_names[XInputSource::NUM_BUTTONS] = { 43 "DPadUp", // XINPUT_GAMEPAD_DPAD_UP 44 "DPadDown", // XINPUT_GAMEPAD_DPAD_DOWN 45 "DPadLeft", // XINPUT_GAMEPAD_DPAD_LEFT 46 "DPadRight", // XINPUT_GAMEPAD_DPAD_RIGHT 47 "Start", // XINPUT_GAMEPAD_START 48 "Back", // XINPUT_GAMEPAD_BACK 49 "LeftStick", // XINPUT_GAMEPAD_LEFT_THUMB 50 "RightStick", // XINPUT_GAMEPAD_RIGHT_THUMB 51 "LeftShoulder", // XINPUT_GAMEPAD_LEFT_SHOULDER 52 "RightShoulder", // XINPUT_GAMEPAD_RIGHT_SHOULDER 53 "A", // XINPUT_GAMEPAD_A 54 "B", // XINPUT_GAMEPAD_B 55 "X", // XINPUT_GAMEPAD_X 56 "Y", // XINPUT_GAMEPAD_Y 57 "Guide", // XINPUT_GAMEPAD_GUIDE 58 }; 59 static const u16 s_button_masks[XInputSource::NUM_BUTTONS] = { 60 XINPUT_GAMEPAD_DPAD_UP, 61 XINPUT_GAMEPAD_DPAD_DOWN, 62 XINPUT_GAMEPAD_DPAD_LEFT, 63 XINPUT_GAMEPAD_DPAD_RIGHT, 64 XINPUT_GAMEPAD_START, 65 XINPUT_GAMEPAD_BACK, 66 XINPUT_GAMEPAD_LEFT_THUMB, 67 XINPUT_GAMEPAD_RIGHT_THUMB, 68 XINPUT_GAMEPAD_LEFT_SHOULDER, 69 XINPUT_GAMEPAD_RIGHT_SHOULDER, 70 XINPUT_GAMEPAD_A, 71 XINPUT_GAMEPAD_B, 72 XINPUT_GAMEPAD_X, 73 XINPUT_GAMEPAD_Y, 74 0x400, // XINPUT_GAMEPAD_GUIDE 75 }; 76 static constexpr const char* s_button_icons[] = { 77 ICON_PF_XBOX_DPAD_UP, // XINPUT_GAMEPAD_DPAD_UP 78 ICON_PF_XBOX_DPAD_DOWN, // XINPUT_GAMEPAD_DPAD_DOWN 79 ICON_PF_XBOX_DPAD_LEFT, // XINPUT_GAMEPAD_DPAD_LEFT 80 ICON_PF_XBOX_DPAD_RIGHT, // XINPUT_GAMEPAD_DPAD_RIGHT 81 ICON_PF_BURGER_MENU, // XINPUT_GAMEPAD_START 82 ICON_PF_SHARE_CAPTURE, // XINPUT_GAMEPAD_BACK 83 ICON_PF_LEFT_ANALOG_CLICK, // XINPUT_GAMEPAD_LEFT_THUMB 84 ICON_PF_RIGHT_ANALOG_CLICK, // XINPUT_GAMEPAD_RIGHT_THUMB 85 ICON_PF_LEFT_SHOULDER_LB, // XINPUT_GAMEPAD_LEFT_SHOULDER 86 ICON_PF_RIGHT_SHOULDER_RB, // XINPUT_GAMEPAD_RIGHT_SHOULDER 87 ICON_PF_BUTTON_A, // XINPUT_GAMEPAD_A 88 ICON_PF_BUTTON_B, // XINPUT_GAMEPAD_B 89 ICON_PF_BUTTON_X, // XINPUT_GAMEPAD_X 90 ICON_PF_BUTTON_Y, // XINPUT_GAMEPAD_Y 91 ICON_PF_XBOX, // XINPUT_GAMEPAD_GUIDE 92 }; 93 static const GenericInputBinding s_xinput_generic_binding_button_mapping[] = { 94 GenericInputBinding::DPadUp, // XINPUT_GAMEPAD_DPAD_UP 95 GenericInputBinding::DPadDown, // XINPUT_GAMEPAD_DPAD_DOWN 96 GenericInputBinding::DPadLeft, // XINPUT_GAMEPAD_DPAD_LEFT 97 GenericInputBinding::DPadRight, // XINPUT_GAMEPAD_DPAD_RIGHT 98 GenericInputBinding::Start, // XINPUT_GAMEPAD_START 99 GenericInputBinding::Select, // XINPUT_GAMEPAD_BACK 100 GenericInputBinding::L3, // XINPUT_GAMEPAD_LEFT_THUMB 101 GenericInputBinding::R3, // XINPUT_GAMEPAD_RIGHT_THUMB 102 GenericInputBinding::L1, // XINPUT_GAMEPAD_LEFT_SHOULDER 103 GenericInputBinding::R1, // XINPUT_GAMEPAD_RIGHT_SHOULDER 104 GenericInputBinding::Cross, // XINPUT_GAMEPAD_A 105 GenericInputBinding::Circle, // XINPUT_GAMEPAD_B 106 GenericInputBinding::Square, // XINPUT_GAMEPAD_X 107 GenericInputBinding::Triangle, // XINPUT_GAMEPAD_Y 108 GenericInputBinding::System, // XINPUT_GAMEPAD_GUIDE 109 }; 110 111 XInputSource::XInputSource() = default; 112 113 XInputSource::~XInputSource() = default; 114 115 bool XInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) 116 { 117 // xinput1_3.dll is flawed and obsolete, but it's also commonly used by wrappers. 118 // For this reason, try to load it *only* from the application directory, and not system32. 119 m_xinput_module = LoadLibraryExW(L"xinput1_3", nullptr, LOAD_LIBRARY_SEARCH_APPLICATION_DIR); 120 if (!m_xinput_module) 121 { 122 m_xinput_module = LoadLibraryW(L"xinput1_4"); 123 } 124 if (!m_xinput_module) 125 { 126 m_xinput_module = LoadLibraryW(L"xinput9_1_0"); 127 } 128 if (!m_xinput_module) 129 { 130 ERROR_LOG("Failed to load XInput module."); 131 return false; 132 } 133 134 // Try the hidden version of XInputGetState(), which lets us query the guide button. 135 m_xinput_get_state = 136 reinterpret_cast<decltype(m_xinput_get_state)>(GetProcAddress(m_xinput_module, reinterpret_cast<LPCSTR>(100))); 137 if (!m_xinput_get_state) 138 { 139 m_xinput_get_state = 140 reinterpret_cast<decltype(m_xinput_get_state)>(GetProcAddress(m_xinput_module, "XInputGetState")); 141 } 142 m_xinput_set_state = 143 reinterpret_cast<decltype(m_xinput_set_state)>(GetProcAddress(m_xinput_module, "XInputSetState")); 144 m_xinput_get_capabilities = 145 reinterpret_cast<decltype(m_xinput_get_capabilities)>(GetProcAddress(m_xinput_module, "XInputGetCapabilities")); 146 147 if (!m_xinput_get_state || !m_xinput_set_state || !m_xinput_get_capabilities) 148 { 149 ERROR_LOG("Failed to get XInput function pointers."); 150 return false; 151 } 152 153 ReloadDevices(); 154 return true; 155 } 156 157 void XInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) 158 { 159 } 160 161 bool XInputSource::ReloadDevices() 162 { 163 bool changed = false; 164 for (u32 i = 0; i < NUM_CONTROLLERS; i++) 165 { 166 XINPUT_STATE new_state; 167 DWORD result = m_xinput_get_state(i, &new_state); 168 169 if (result == ERROR_SUCCESS) 170 { 171 if (m_controllers[i].connected) 172 continue; 173 174 HandleControllerConnection(i); 175 changed = true; 176 } 177 else if (result == ERROR_DEVICE_NOT_CONNECTED) 178 { 179 if (!m_controllers[i].connected) 180 continue; 181 182 HandleControllerDisconnection(i); 183 changed = true; 184 } 185 } 186 187 return changed; 188 } 189 190 void XInputSource::Shutdown() 191 { 192 for (u32 i = 0; i < NUM_CONTROLLERS; i++) 193 { 194 if (m_controllers[i].connected) 195 HandleControllerDisconnection(i); 196 } 197 198 if (m_xinput_module) 199 { 200 FreeLibrary(m_xinput_module); 201 m_xinput_module = nullptr; 202 } 203 204 m_xinput_get_state = nullptr; 205 m_xinput_set_state = nullptr; 206 m_xinput_get_capabilities = nullptr; 207 } 208 209 void XInputSource::PollEvents() 210 { 211 for (u32 i = 0; i < NUM_CONTROLLERS; i++) 212 { 213 const bool was_connected = m_controllers[i].connected; 214 if (!was_connected) 215 continue; 216 217 XINPUT_STATE new_state; 218 DWORD result = m_xinput_get_state(i, &new_state); 219 220 if (result == ERROR_SUCCESS) 221 { 222 if (!was_connected) 223 HandleControllerConnection(i); 224 225 CheckForStateChanges(i, new_state); 226 } 227 else 228 { 229 if (result != ERROR_DEVICE_NOT_CONNECTED) 230 WARNING_LOG("XInputGetState({}) failed: 0x{:08X} / 0x{:08X}", i, result, GetLastError()); 231 232 if (was_connected) 233 HandleControllerDisconnection(i); 234 } 235 } 236 } 237 238 std::vector<std::pair<std::string, std::string>> XInputSource::EnumerateDevices() 239 { 240 std::vector<std::pair<std::string, std::string>> ret; 241 242 for (u32 i = 0; i < NUM_CONTROLLERS; i++) 243 { 244 if (!m_controllers[i].connected) 245 continue; 246 247 ret.emplace_back(fmt::format("XInput-{}", i), fmt::format("XInput Controller {}", i)); 248 } 249 250 return ret; 251 } 252 253 std::optional<InputBindingKey> XInputSource::ParseKeyString(std::string_view device, std::string_view binding) 254 { 255 if (!device.starts_with("XInput-") || binding.empty()) 256 return std::nullopt; 257 258 const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(7)); 259 if (!player_id.has_value() || player_id.value() < 0) 260 return std::nullopt; 261 262 InputBindingKey key = {}; 263 key.source_type = InputSourceType::XInput; 264 key.source_index = static_cast<u32>(player_id.value()); 265 266 if (binding.ends_with("Motor")) 267 { 268 key.source_subtype = InputSubclass::ControllerMotor; 269 if (binding == "LargeMotor") 270 { 271 key.data = 0; 272 return key; 273 } 274 else if (binding == "SmallMotor") 275 { 276 key.data = 1; 277 return key; 278 } 279 else 280 { 281 return std::nullopt; 282 } 283 } 284 else if (binding[0] == '+' || binding[0] == '-') 285 { 286 // likely an axis 287 const std::string_view axis_name(binding.substr(1)); 288 for (u32 i = 0; i < std::size(s_axis_names); i++) 289 { 290 if (axis_name == s_axis_names[i]) 291 { 292 // found an axis! 293 key.source_subtype = InputSubclass::ControllerAxis; 294 key.data = i; 295 key.modifier = binding[0] == '-' ? InputModifier::Negate : InputModifier::None; 296 return key; 297 } 298 } 299 } 300 else 301 { 302 // must be a button 303 for (u32 i = 0; i < std::size(s_button_names); i++) 304 { 305 if (binding == s_button_names[i]) 306 { 307 key.source_subtype = InputSubclass::ControllerButton; 308 key.data = i; 309 return key; 310 } 311 } 312 } 313 314 // unknown axis/button 315 return std::nullopt; 316 } 317 318 TinyString XInputSource::ConvertKeyToString(InputBindingKey key) 319 { 320 TinyString ret; 321 322 if (key.source_type == InputSourceType::XInput) 323 { 324 if (key.source_subtype == InputSubclass::ControllerAxis && key.data < std::size(s_axis_names)) 325 { 326 const char modifier = key.modifier == InputModifier::Negate ? '-' : '+'; 327 ret.format("XInput-{}/{}{}", static_cast<u32>(key.source_index), modifier, s_axis_names[key.data]); 328 } 329 else if (key.source_subtype == InputSubclass::ControllerButton && key.data < std::size(s_button_names)) 330 { 331 ret.format("XInput-{}/{}", static_cast<u32>(key.source_index), s_button_names[key.data]); 332 } 333 else if (key.source_subtype == InputSubclass::ControllerMotor) 334 { 335 ret.format("XInput-{}/{}Motor", static_cast<u32>(key.source_index), key.data ? "Large" : "Small"); 336 } 337 } 338 339 return ret; 340 } 341 342 TinyString XInputSource::ConvertKeyToIcon(InputBindingKey key) 343 { 344 TinyString ret; 345 346 if (key.source_type == InputSourceType::SDL) 347 { 348 if (key.source_subtype == InputSubclass::ControllerAxis) 349 { 350 if (key.data < std::size(s_axis_icons) && key.modifier != InputModifier::FullAxis) 351 { 352 ret.format("XInput-{} {}", static_cast<u32>(key.source_index), 353 s_axis_icons[key.data][key.modifier == InputModifier::None]); 354 } 355 } 356 else if (key.source_subtype == InputSubclass::ControllerButton) 357 { 358 if (key.data < std::size(s_button_icons)) 359 ret.format("XInput-{} {}", static_cast<u32>(key.source_index), s_button_icons[key.data]); 360 } 361 } 362 363 return ret; 364 } 365 366 std::vector<InputBindingKey> XInputSource::EnumerateMotors() 367 { 368 std::vector<InputBindingKey> ret; 369 370 for (u32 i = 0; i < NUM_CONTROLLERS; i++) 371 { 372 const ControllerData& cd = m_controllers[i]; 373 if (!cd.connected) 374 continue; 375 376 if (cd.has_large_motor) 377 ret.push_back(MakeGenericControllerMotorKey(InputSourceType::XInput, i, 0)); 378 379 if (cd.has_small_motor) 380 ret.push_back(MakeGenericControllerMotorKey(InputSourceType::XInput, i, 1)); 381 } 382 383 return ret; 384 } 385 386 bool XInputSource::GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping) 387 { 388 if (!device.starts_with("XInput-")) 389 return false; 390 391 const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(7)); 392 if (!player_id.has_value() || player_id.value() < 0) 393 return false; 394 395 if (player_id.value() < 0 || player_id.value() >= static_cast<s32>(XUSER_MAX_COUNT)) 396 return false; 397 398 // assume all buttons are present. 399 const s32 pid = player_id.value(); 400 for (u32 i = 0; i < std::size(s_xinput_generic_binding_axis_mapping); i++) 401 { 402 const GenericInputBinding negative = s_xinput_generic_binding_axis_mapping[i][0]; 403 const GenericInputBinding positive = s_xinput_generic_binding_axis_mapping[i][1]; 404 if (negative != GenericInputBinding::Unknown) 405 mapping->emplace_back(negative, fmt::format("XInput-{}/-{}", pid, s_axis_names[i])); 406 407 if (positive != GenericInputBinding::Unknown) 408 mapping->emplace_back(positive, fmt::format("XInput-{}/+{}", pid, s_axis_names[i])); 409 } 410 for (u32 i = 0; i < std::size(s_xinput_generic_binding_button_mapping); i++) 411 { 412 const GenericInputBinding binding = s_xinput_generic_binding_button_mapping[i]; 413 if (binding != GenericInputBinding::Unknown) 414 mapping->emplace_back(binding, fmt::format("XInput-{}/{}", pid, s_button_names[i])); 415 } 416 417 if (m_controllers[pid].has_small_motor) 418 mapping->emplace_back(GenericInputBinding::SmallMotor, fmt::format("XInput-{}/SmallMotor", pid)); 419 if (m_controllers[pid].has_large_motor) 420 mapping->emplace_back(GenericInputBinding::LargeMotor, fmt::format("XInput-{}/LargeMotor", pid)); 421 422 return true; 423 } 424 425 void XInputSource::HandleControllerConnection(u32 index) 426 { 427 INFO_LOG("XInput controller {} connected.", index); 428 429 XINPUT_CAPABILITIES caps = {}; 430 if (m_xinput_get_capabilities(index, 0, &caps) != ERROR_SUCCESS) 431 WARNING_LOG("Failed to get XInput capabilities for controller {}", index); 432 433 ControllerData& cd = m_controllers[index]; 434 cd.connected = true; 435 cd.has_large_motor = caps.Vibration.wLeftMotorSpeed != 0; 436 cd.has_small_motor = caps.Vibration.wRightMotorSpeed != 0; 437 cd.last_state = {}; 438 439 InputManager::OnInputDeviceConnected(fmt::format("XInput-{}", index), fmt::format("XInput Controller {}", index)); 440 } 441 442 void XInputSource::HandleControllerDisconnection(u32 index) 443 { 444 INFO_LOG("XInput controller {} disconnected.", index); 445 446 InputManager::OnInputDeviceDisconnected({{ 447 .source_type = InputSourceType::XInput, 448 .source_index = index, 449 }}, 450 fmt::format("XInput-{}", index)); 451 m_controllers[index] = {}; 452 } 453 454 void XInputSource::CheckForStateChanges(u32 index, const XINPUT_STATE& new_state) 455 { 456 ControllerData& cd = m_controllers[index]; 457 if (new_state.dwPacketNumber == cd.last_state.dwPacketNumber) 458 return; 459 460 XINPUT_GAMEPAD& ogp = cd.last_state.Gamepad; 461 const XINPUT_GAMEPAD& ngp = new_state.Gamepad; 462 463 #define CHECK_AXIS(field, axis, min_value, max_value) \ 464 if (ogp.field != ngp.field) \ 465 { \ 466 InputManager::InvokeEvents(MakeGenericControllerAxisKey(InputSourceType::XInput, index, axis), \ 467 static_cast<float>(ngp.field) / ((ngp.field < 0) ? min_value : max_value), \ 468 GenericInputBinding::Unknown); \ 469 } 470 471 // Y axes is inverted in XInput when compared to SDL. 472 CHECK_AXIS(sThumbLX, AXIS_LEFTX, 32768, 32767); 473 CHECK_AXIS(sThumbLY, AXIS_LEFTY, -32768, -32767); 474 CHECK_AXIS(sThumbRX, AXIS_RIGHTX, 32768, 32767); 475 CHECK_AXIS(sThumbRY, AXIS_RIGHTY, -32768, -32767); 476 CHECK_AXIS(bLeftTrigger, AXIS_LEFTTRIGGER, 0, 255); 477 CHECK_AXIS(bRightTrigger, AXIS_RIGHTTRIGGER, 0, 255); 478 479 #undef CHECK_AXIS 480 481 const u16 old_button_bits = ogp.wButtons; 482 const u16 new_button_bits = ngp.wButtons; 483 if (old_button_bits != new_button_bits) 484 { 485 for (u32 button = 0; button < NUM_BUTTONS; button++) 486 { 487 const u16 button_mask = s_button_masks[button]; 488 if ((old_button_bits & button_mask) != (new_button_bits & button_mask)) 489 { 490 const GenericInputBinding generic_key = (button < std::size(s_xinput_generic_binding_button_mapping)) ? 491 s_xinput_generic_binding_button_mapping[button] : 492 GenericInputBinding::Unknown; 493 const float value = ((new_button_bits & button_mask) != 0) ? 1.0f : 0.0f; 494 InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::XInput, index, button), value, 495 generic_key); 496 } 497 } 498 } 499 500 cd.last_state = new_state; 501 } 502 503 void XInputSource::UpdateMotorState(InputBindingKey key, float intensity) 504 { 505 if (key.source_subtype != InputSubclass::ControllerMotor || key.source_index >= NUM_CONTROLLERS) 506 return; 507 508 ControllerData& cd = m_controllers[key.source_index]; 509 if (!cd.connected) 510 return; 511 512 const u16 i_intensity = static_cast<u16>(intensity * 65535.0f); 513 if (key.data != 0) 514 cd.last_vibration.wRightMotorSpeed = i_intensity; 515 else 516 cd.last_vibration.wLeftMotorSpeed = i_intensity; 517 518 m_xinput_set_state(key.source_index, &cd.last_vibration); 519 } 520 521 void XInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, 522 float small_intensity) 523 { 524 if (large_key.source_index != small_key.source_index || large_key.source_subtype != InputSubclass::ControllerMotor || 525 small_key.source_subtype != InputSubclass::ControllerMotor) 526 { 527 // bonkers config where they're mapped to different controllers... who would do such a thing? 528 UpdateMotorState(large_key, large_intensity); 529 UpdateMotorState(small_key, small_intensity); 530 return; 531 } 532 533 ControllerData& cd = m_controllers[large_key.source_index]; 534 if (!cd.connected) 535 return; 536 537 cd.last_vibration.wLeftMotorSpeed = static_cast<u16>(large_intensity * 65535.0f); 538 cd.last_vibration.wRightMotorSpeed = static_cast<u16>(small_intensity * 65535.0f); 539 m_xinput_set_state(large_key.source_index, &cd.last_vibration); 540 } 541 542 std::unique_ptr<InputSource> InputSource::CreateXInputSource() 543 { 544 return std::make_unique<XInputSource>(); 545 }