sdl_input_source.cpp (36898B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "sdl_input_source.h" 5 #include "input_manager.h" 6 7 #include "core/host.h" 8 #include "core/settings.h" 9 10 #include "common/assert.h" 11 #include "common/bitutils.h" 12 #include "common/file_system.h" 13 #include "common/log.h" 14 #include "common/path.h" 15 #include "common/string_util.h" 16 17 #include "IconsPromptFont.h" 18 19 #include <cmath> 20 21 #ifdef __APPLE__ 22 #include <dispatch/dispatch.h> 23 #endif 24 25 Log_SetChannel(SDLInputSource); 26 27 static constexpr const char* CONTROLLER_DB_FILENAME = "gamecontrollerdb.txt"; 28 29 static constexpr const char* s_sdl_axis_names[] = { 30 "LeftX", // SDL_CONTROLLER_AXIS_LEFTX 31 "LeftY", // SDL_CONTROLLER_AXIS_LEFTY 32 "RightX", // SDL_CONTROLLER_AXIS_RIGHTX 33 "RightY", // SDL_CONTROLLER_AXIS_RIGHTY 34 "LeftTrigger", // SDL_CONTROLLER_AXIS_TRIGGERLEFT 35 "RightTrigger", // SDL_CONTROLLER_AXIS_TRIGGERRIGHT 36 }; 37 static constexpr const char* s_sdl_axis_icons[][2] = { 38 {ICON_PF_LEFT_ANALOG_LEFT, ICON_PF_LEFT_ANALOG_RIGHT}, // SDL_CONTROLLER_AXIS_LEFTX 39 {ICON_PF_LEFT_ANALOG_UP, ICON_PF_LEFT_ANALOG_DOWN}, // SDL_CONTROLLER_AXIS_LEFTY 40 {ICON_PF_RIGHT_ANALOG_LEFT, ICON_PF_RIGHT_ANALOG_RIGHT}, // SDL_CONTROLLER_AXIS_RIGHTX 41 {ICON_PF_RIGHT_ANALOG_UP, ICON_PF_RIGHT_ANALOG_DOWN}, // SDL_CONTROLLER_AXIS_RIGHTY 42 {nullptr, ICON_PF_LEFT_TRIGGER_PULL}, // SDL_CONTROLLER_AXIS_TRIGGERLEFT 43 {nullptr, ICON_PF_RIGHT_TRIGGER_PULL}, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT 44 }; 45 static constexpr const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = { 46 {GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // SDL_CONTROLLER_AXIS_LEFTX 47 {GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // SDL_CONTROLLER_AXIS_LEFTY 48 {GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight}, // SDL_CONTROLLER_AXIS_RIGHTX 49 {GenericInputBinding::RightStickUp, GenericInputBinding::RightStickDown}, // SDL_CONTROLLER_AXIS_RIGHTY 50 {GenericInputBinding::Unknown, GenericInputBinding::L2}, // SDL_CONTROLLER_AXIS_TRIGGERLEFT 51 {GenericInputBinding::Unknown, GenericInputBinding::R2}, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT 52 }; 53 54 static constexpr const char* s_sdl_button_names[] = { 55 "A", // SDL_CONTROLLER_BUTTON_A 56 "B", // SDL_CONTROLLER_BUTTON_B 57 "X", // SDL_CONTROLLER_BUTTON_X 58 "Y", // SDL_CONTROLLER_BUTTON_Y 59 "Back", // SDL_CONTROLLER_BUTTON_BACK 60 "Guide", // SDL_CONTROLLER_BUTTON_GUIDE 61 "Start", // SDL_CONTROLLER_BUTTON_START 62 "LeftStick", // SDL_CONTROLLER_BUTTON_LEFTSTICK 63 "RightStick", // SDL_CONTROLLER_BUTTON_RIGHTSTICK 64 "LeftShoulder", // SDL_CONTROLLER_BUTTON_LEFTSHOULDER 65 "RightShoulder", // SDL_CONTROLLER_BUTTON_RIGHTSHOULDER 66 "DPadUp", // SDL_CONTROLLER_BUTTON_DPAD_UP 67 "DPadDown", // SDL_CONTROLLER_BUTTON_DPAD_DOWN 68 "DPadLeft", // SDL_CONTROLLER_BUTTON_DPAD_LEFT 69 "DPadRight", // SDL_CONTROLLER_BUTTON_DPAD_RIGHT 70 "Misc1", // SDL_CONTROLLER_BUTTON_MISC1 71 "Paddle1", // SDL_CONTROLLER_BUTTON_PADDLE1 72 "Paddle2", // SDL_CONTROLLER_BUTTON_PADDLE2 73 "Paddle3", // SDL_CONTROLLER_BUTTON_PADDLE3 74 "Paddle4", // SDL_CONTROLLER_BUTTON_PADDLE4 75 "Touchpad", // SDL_CONTROLLER_BUTTON_TOUCHPAD 76 }; 77 static constexpr const char* s_sdl_button_icons[] = { 78 ICON_PF_BUTTON_A, // SDL_CONTROLLER_BUTTON_A 79 ICON_PF_BUTTON_B, // SDL_CONTROLLER_BUTTON_B 80 ICON_PF_BUTTON_X, // SDL_CONTROLLER_BUTTON_X 81 ICON_PF_BUTTON_Y, // SDL_CONTROLLER_BUTTON_Y 82 ICON_PF_SHARE_CAPTURE, // SDL_CONTROLLER_BUTTON_BACK 83 ICON_PF_XBOX, // SDL_CONTROLLER_BUTTON_GUIDE 84 ICON_PF_BURGER_MENU, // SDL_CONTROLLER_BUTTON_START 85 ICON_PF_LEFT_ANALOG_CLICK, // SDL_CONTROLLER_BUTTON_LEFTSTICK 86 ICON_PF_RIGHT_ANALOG_CLICK, // SDL_CONTROLLER_BUTTON_RIGHTSTICK 87 ICON_PF_LEFT_SHOULDER_LB, // SDL_CONTROLLER_BUTTON_LEFTSHOULDER 88 ICON_PF_RIGHT_SHOULDER_RB, // SDL_CONTROLLER_BUTTON_RIGHTSHOULDER 89 ICON_PF_XBOX_DPAD_UP, // SDL_CONTROLLER_BUTTON_DPAD_UP 90 ICON_PF_XBOX_DPAD_DOWN, // SDL_CONTROLLER_BUTTON_DPAD_DOWN 91 ICON_PF_XBOX_DPAD_LEFT, // SDL_CONTROLLER_BUTTON_DPAD_LEFT 92 ICON_PF_XBOX_DPAD_RIGHT, // SDL_CONTROLLER_BUTTON_DPAD_RIGHT 93 }; 94 static constexpr const GenericInputBinding s_sdl_generic_binding_button_mapping[] = { 95 GenericInputBinding::Cross, // SDL_CONTROLLER_BUTTON_A 96 GenericInputBinding::Circle, // SDL_CONTROLLER_BUTTON_B 97 GenericInputBinding::Square, // SDL_CONTROLLER_BUTTON_X 98 GenericInputBinding::Triangle, // SDL_CONTROLLER_BUTTON_Y 99 GenericInputBinding::Select, // SDL_CONTROLLER_BUTTON_BACK 100 GenericInputBinding::System, // SDL_CONTROLLER_BUTTON_GUIDE 101 GenericInputBinding::Start, // SDL_CONTROLLER_BUTTON_START 102 GenericInputBinding::L3, // SDL_CONTROLLER_BUTTON_LEFTSTICK 103 GenericInputBinding::R3, // SDL_CONTROLLER_BUTTON_RIGHTSTICK 104 GenericInputBinding::L1, // SDL_CONTROLLER_BUTTON_LEFTSHOULDER 105 GenericInputBinding::R1, // SDL_CONTROLLER_BUTTON_RIGHTSHOULDER 106 GenericInputBinding::DPadUp, // SDL_CONTROLLER_BUTTON_DPAD_UP 107 GenericInputBinding::DPadDown, // SDL_CONTROLLER_BUTTON_DPAD_DOWN 108 GenericInputBinding::DPadLeft, // SDL_CONTROLLER_BUTTON_DPAD_LEFT 109 GenericInputBinding::DPadRight, // SDL_CONTROLLER_BUTTON_DPAD_RIGHT 110 GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_MISC1 111 GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE1 112 GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE2 113 GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE3 114 GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE4 115 GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_TOUCHPAD 116 }; 117 118 static constexpr const char* s_sdl_hat_direction_names[] = { 119 // clang-format off 120 "North", 121 "East", 122 "South", 123 "West", 124 // clang-format on 125 }; 126 127 static constexpr const char* s_sdl_default_led_colors[] = { 128 "0000ff", // SDL-0 129 "ff0000", // SDL-1 130 "00ff00", // SDL-2 131 "ffff00", // SDL-3 132 }; 133 134 static void SetControllerRGBLED(SDL_GameController* gc, u32 color) 135 { 136 SDL_GameControllerSetLED(gc, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); 137 } 138 139 static void SDLLogCallback(void* userdata, int category, SDL_LogPriority priority, const char* message) 140 { 141 static constexpr LOGLEVEL priority_map[SDL_NUM_LOG_PRIORITIES] = { 142 LOGLEVEL_DEBUG, 143 LOGLEVEL_DEBUG, // SDL_LOG_PRIORITY_VERBOSE 144 LOGLEVEL_DEBUG, // SDL_LOG_PRIORITY_DEBUG 145 LOGLEVEL_INFO, // SDL_LOG_PRIORITY_INFO 146 LOGLEVEL_WARNING, // SDL_LOG_PRIORITY_WARN 147 LOGLEVEL_ERROR, // SDL_LOG_PRIORITY_ERROR 148 LOGLEVEL_ERROR, // SDL_LOG_PRIORITY_CRITICAL 149 }; 150 151 Log::Write("SDL", "SDL", priority_map[priority], message); 152 } 153 154 bool SDLInputSource::ALLOW_EVENT_POLLING = true; 155 156 SDLInputSource::SDLInputSource() = default; 157 158 SDLInputSource::~SDLInputSource() 159 { 160 Assert(m_controllers.empty()); 161 } 162 163 bool SDLInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) 164 { 165 LoadSettings(si); 166 settings_lock.unlock(); 167 SetHints(); 168 bool result = InitializeSubsystem(); 169 settings_lock.lock(); 170 return result; 171 } 172 173 void SDLInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) 174 { 175 const bool old_controller_enhanced_mode = m_controller_enhanced_mode; 176 const bool old_controller_ps5_player_led = m_controller_ps5_player_led; 177 178 #ifdef __APPLE__ 179 const bool old_enable_iokit_driver = m_enable_iokit_driver; 180 const bool old_enable_mfi_driver = m_enable_mfi_driver; 181 #endif 182 183 LoadSettings(si); 184 185 #ifdef __APPLE__ 186 const bool drivers_changed = 187 (m_enable_iokit_driver != old_enable_iokit_driver || m_enable_mfi_driver != old_enable_mfi_driver); 188 #else 189 constexpr bool drivers_changed = false; 190 #endif 191 192 if (m_controller_enhanced_mode != old_controller_enhanced_mode || 193 m_controller_ps5_player_led != old_controller_ps5_player_led || 194 drivers_changed) 195 { 196 settings_lock.unlock(); 197 ShutdownSubsystem(); 198 SetHints(); 199 InitializeSubsystem(); 200 settings_lock.lock(); 201 } 202 } 203 204 bool SDLInputSource::ReloadDevices() 205 { 206 // We'll get a GC added/removed event here. 207 PollEvents(); 208 return false; 209 } 210 211 void SDLInputSource::Shutdown() 212 { 213 ShutdownSubsystem(); 214 } 215 216 void SDLInputSource::LoadSettings(SettingsInterface& si) 217 { 218 for (u32 i = 0; i < MAX_LED_COLORS; i++) 219 { 220 const u32 color = GetRGBForPlayerId(si, i); 221 if (m_led_colors[i] == color) 222 continue; 223 224 m_led_colors[i] = color; 225 226 const auto it = GetControllerDataForPlayerId(i); 227 if (it == m_controllers.end() || !it->game_controller || !SDL_GameControllerHasLED(it->game_controller)) 228 continue; 229 230 SetControllerRGBLED(it->game_controller, color); 231 } 232 233 m_controller_enhanced_mode = si.GetBoolValue("InputSources", "SDLControllerEnhancedMode", false); 234 m_controller_ps5_player_led = si.GetBoolValue("InputSources", "SDLPS5PlayerLED", false); 235 m_sdl_hints = si.GetKeyValueList("SDLHints"); 236 237 #ifdef __APPLE__ 238 m_enable_iokit_driver = si.GetBoolValue("InputSources", "SDLIOKitDriver", true); 239 m_enable_mfi_driver = si.GetBoolValue("InputSources", "SDLMFIDriver", true); 240 #endif 241 } 242 243 u32 SDLInputSource::GetRGBForPlayerId(SettingsInterface& si, u32 player_id) 244 { 245 return ParseRGBForPlayerId( 246 si.GetStringValue("SDLExtra", fmt::format("Player{}LED", player_id).c_str(), s_sdl_default_led_colors[player_id]), 247 player_id); 248 } 249 250 u32 SDLInputSource::ParseRGBForPlayerId(std::string_view str, u32 player_id) 251 { 252 if (player_id >= MAX_LED_COLORS) 253 return 0; 254 255 const u32 default_color = StringUtil::FromChars<u32>(s_sdl_default_led_colors[player_id], 16).value_or(0); 256 const u32 color = StringUtil::FromChars<u32>(str, 16).value_or(default_color); 257 258 return color; 259 } 260 261 void SDLInputSource::SetHints() 262 { 263 if (const std::string upath = Path::Combine(EmuFolders::DataRoot, CONTROLLER_DB_FILENAME); 264 FileSystem::FileExists(upath.c_str())) 265 { 266 INFO_LOG("Using Controller DB from user directory: '{}'", upath); 267 SDL_SetHint(SDL_HINT_GAMECONTROLLERCONFIG_FILE, upath.c_str()); 268 } 269 else if (const std::string rpath = EmuFolders::GetOverridableResourcePath(CONTROLLER_DB_FILENAME); 270 FileSystem::FileExists(rpath.c_str())) 271 { 272 INFO_LOG("Using Controller DB from resources."); 273 SDL_SetHint(SDL_HINT_GAMECONTROLLERCONFIG_FILE, rpath.c_str()); 274 } 275 else 276 { 277 ERROR_LOG("Controller DB not found, it should be named '{}'", CONTROLLER_DB_FILENAME); 278 } 279 280 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, m_controller_enhanced_mode ? "1" : "0"); 281 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, m_controller_enhanced_mode ? "1" : "0"); 282 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, m_controller_ps5_player_led ? "1" : "0"); 283 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1"); 284 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS3, "1"); 285 286 #ifdef __APPLE__ 287 INFO_LOG("IOKit is {}, MFI is {}.", m_enable_iokit_driver ? "enabled" : "disabled", 288 m_enable_mfi_driver ? "enabled" : "disabled"); 289 SDL_SetHint(SDL_HINT_JOYSTICK_IOKIT, m_enable_iokit_driver ? "1" : "0"); 290 SDL_SetHint(SDL_HINT_JOYSTICK_MFI, m_enable_mfi_driver ? "1" : "0"); 291 #endif 292 293 for (const std::pair<std::string, std::string>& hint : m_sdl_hints) 294 SDL_SetHint(hint.first.c_str(), hint.second.c_str()); 295 } 296 297 bool SDLInputSource::InitializeSubsystem() 298 { 299 if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0) 300 { 301 ERROR_LOG("SDL_InitSubSystem(SDL_INIT_JOYSTICK |SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) failed"); 302 return false; 303 } 304 305 SDL_LogSetOutputFunction(SDLLogCallback, nullptr); 306 #ifdef _DEBUG 307 SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); 308 #else 309 SDL_LogSetAllPriority(SDL_LOG_PRIORITY_INFO); 310 #endif 311 312 // we should open the controllers as the connected events come in, so no need to do any more here 313 m_sdl_subsystem_initialized = true; 314 INFO_LOG("{} controller mappings are loaded.", SDL_GameControllerNumMappings()); 315 return true; 316 } 317 318 void SDLInputSource::ShutdownSubsystem() 319 { 320 while (!m_controllers.empty()) 321 CloseDevice(m_controllers.begin()->joystick_id); 322 323 if (m_sdl_subsystem_initialized) 324 { 325 SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC); 326 m_sdl_subsystem_initialized = false; 327 } 328 } 329 330 void SDLInputSource::PollEvents() 331 { 332 if (!ALLOW_EVENT_POLLING) 333 return; 334 335 for (;;) 336 { 337 SDL_Event ev; 338 if (SDL_PollEvent(&ev)) 339 ProcessSDLEvent(&ev); 340 else 341 break; 342 } 343 } 344 345 std::vector<std::pair<std::string, std::string>> SDLInputSource::EnumerateDevices() 346 { 347 std::vector<std::pair<std::string, std::string>> ret; 348 349 for (const ControllerData& cd : m_controllers) 350 { 351 std::string id = fmt::format("SDL-{}", cd.player_id); 352 353 const char* name = cd.game_controller ? SDL_GameControllerName(cd.game_controller) : SDL_JoystickName(cd.joystick); 354 if (name) 355 ret.emplace_back(std::move(id), name); 356 else 357 ret.emplace_back(std::move(id), "Unknown Device"); 358 } 359 360 return ret; 361 } 362 363 std::optional<InputBindingKey> SDLInputSource::ParseKeyString(std::string_view device, std::string_view binding) 364 { 365 if (!device.starts_with("SDL-") || binding.empty()) 366 return std::nullopt; 367 368 const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(4)); 369 if (!player_id.has_value() || player_id.value() < 0) 370 return std::nullopt; 371 372 InputBindingKey key = {}; 373 key.source_type = InputSourceType::SDL; 374 key.source_index = static_cast<u32>(player_id.value()); 375 376 if (binding.ends_with("Motor")) 377 { 378 key.source_subtype = InputSubclass::ControllerMotor; 379 if (binding == "LargeMotor") 380 { 381 key.data = 0; 382 return key; 383 } 384 else if (binding == "SmallMotor") 385 { 386 key.data = 1; 387 return key; 388 } 389 else 390 { 391 return std::nullopt; 392 } 393 } 394 else if (binding.ends_with("Haptic")) 395 { 396 key.source_subtype = InputSubclass::ControllerHaptic; 397 key.data = 0; 398 return key; 399 } 400 else if (binding[0] == '+' || binding[0] == '-') 401 { 402 // likely an axis 403 const std::string_view axis_name(binding.substr(1)); 404 405 if (axis_name.starts_with("Axis")) 406 { 407 std::string_view end; 408 if (auto value = StringUtil::FromChars<u32>(axis_name.substr(4), 10, &end)) 409 { 410 key.source_subtype = InputSubclass::ControllerAxis; 411 key.data = *value + static_cast<u32>(std::size(s_sdl_axis_names)); 412 key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None; 413 key.invert = (end == "~"); 414 return key; 415 } 416 } 417 for (u32 i = 0; i < std::size(s_sdl_axis_names); i++) 418 { 419 if (axis_name == s_sdl_axis_names[i]) 420 { 421 // found an axis! 422 key.source_subtype = InputSubclass::ControllerAxis; 423 key.data = i; 424 key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None; 425 return key; 426 } 427 } 428 } 429 else if (binding.starts_with("FullAxis")) 430 { 431 std::string_view end; 432 if (auto value = StringUtil::FromChars<u32>(binding.substr(8), 10, &end)) 433 { 434 key.source_subtype = InputSubclass::ControllerAxis; 435 key.data = *value + static_cast<u32>(std::size(s_sdl_axis_names)); 436 key.modifier = InputModifier::FullAxis; 437 key.invert = (end == "~"); 438 return key; 439 } 440 } 441 else if (binding.starts_with("Hat")) 442 { 443 std::string_view hat_dir; 444 if (auto value = StringUtil::FromChars<u32>(binding.substr(3), 10, &hat_dir); value.has_value() && !hat_dir.empty()) 445 { 446 for (u8 dir = 0; dir < static_cast<u8>(std::size(s_sdl_hat_direction_names)); dir++) 447 { 448 if (hat_dir == s_sdl_hat_direction_names[dir]) 449 { 450 key.source_subtype = InputSubclass::ControllerHat; 451 key.data = value.value() * static_cast<u32>(std::size(s_sdl_hat_direction_names)) + dir; 452 return key; 453 } 454 } 455 } 456 } 457 else 458 { 459 // must be a button 460 if (binding.starts_with("Button")) 461 { 462 if (auto value = StringUtil::FromChars<u32>(binding.substr(6))) 463 { 464 key.source_subtype = InputSubclass::ControllerButton; 465 key.data = *value + static_cast<u32>(std::size(s_sdl_button_names)); 466 return key; 467 } 468 } 469 for (u32 i = 0; i < std::size(s_sdl_button_names); i++) 470 { 471 if (binding == s_sdl_button_names[i]) 472 { 473 key.source_subtype = InputSubclass::ControllerButton; 474 key.data = i; 475 return key; 476 } 477 } 478 } 479 480 // unknown axis/button 481 return std::nullopt; 482 } 483 484 TinyString SDLInputSource::ConvertKeyToString(InputBindingKey key) 485 { 486 TinyString ret; 487 488 if (key.source_type == InputSourceType::SDL) 489 { 490 if (key.source_subtype == InputSubclass::ControllerAxis) 491 { 492 const char* modifier = 493 (key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+")); 494 if (key.data < std::size(s_sdl_axis_names)) 495 { 496 ret.format("SDL-{}/{}{}", static_cast<u32>(key.source_index), modifier, s_sdl_axis_names[key.data]); 497 } 498 else 499 { 500 ret.format("SDL-{}/{}Axis{}{}", static_cast<u32>(key.source_index), modifier, 501 key.data - static_cast<u32>(std::size(s_sdl_axis_names)), key.invert ? "~" : ""); 502 } 503 } 504 else if (key.source_subtype == InputSubclass::ControllerButton) 505 { 506 if (key.data < std::size(s_sdl_button_names)) 507 { 508 ret.format("SDL-{}/{}", static_cast<u32>(key.source_index), s_sdl_button_names[key.data]); 509 } 510 else 511 { 512 ret.format("SDL-{}/Button{}", static_cast<u32>(key.source_index), 513 key.data - static_cast<u32>(std::size(s_sdl_button_names))); 514 } 515 } 516 else if (key.source_subtype == InputSubclass::ControllerHat) 517 { 518 const u32 hat_index = key.data / static_cast<u32>(std::size(s_sdl_hat_direction_names)); 519 const u32 hat_direction = key.data % static_cast<u32>(std::size(s_sdl_hat_direction_names)); 520 ret.format("SDL-{}/Hat{}{}", static_cast<u32>(key.source_index), hat_index, 521 s_sdl_hat_direction_names[hat_direction]); 522 } 523 else if (key.source_subtype == InputSubclass::ControllerMotor) 524 { 525 ret.format("SDL-{}/{}Motor", static_cast<u32>(key.source_index), key.data ? "Large" : "Small"); 526 } 527 else if (key.source_subtype == InputSubclass::ControllerHaptic) 528 { 529 ret.format("SDL-{}/Haptic", static_cast<u32>(key.source_index)); 530 } 531 } 532 533 return ret; 534 } 535 536 TinyString SDLInputSource::ConvertKeyToIcon(InputBindingKey key) 537 { 538 TinyString ret; 539 540 if (key.source_type == InputSourceType::SDL) 541 { 542 if (key.source_subtype == InputSubclass::ControllerAxis) 543 { 544 if (key.data < std::size(s_sdl_axis_icons) && key.modifier != InputModifier::FullAxis) 545 { 546 ret.format("SDL-{} {}", static_cast<u32>(key.source_index), 547 s_sdl_axis_icons[key.data][key.modifier == InputModifier::None]); 548 } 549 } 550 else if (key.source_subtype == InputSubclass::ControllerButton) 551 { 552 if (key.data < std::size(s_sdl_button_icons)) 553 ret.format("SDL-{} {}", static_cast<u32>(key.source_index), s_sdl_button_icons[key.data]); 554 } 555 } 556 557 return ret; 558 } 559 560 bool SDLInputSource::IsHandledInputEvent(const SDL_Event* ev) 561 { 562 switch (ev->type) 563 { 564 case SDL_CONTROLLERDEVICEADDED: 565 case SDL_CONTROLLERDEVICEREMOVED: 566 case SDL_JOYDEVICEADDED: 567 case SDL_JOYDEVICEREMOVED: 568 case SDL_CONTROLLERAXISMOTION: 569 case SDL_CONTROLLERBUTTONDOWN: 570 case SDL_CONTROLLERBUTTONUP: 571 case SDL_JOYAXISMOTION: 572 case SDL_JOYBUTTONDOWN: 573 case SDL_JOYBUTTONUP: 574 case SDL_JOYHATMOTION: 575 return true; 576 577 default: 578 return false; 579 } 580 } 581 582 bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event) 583 { 584 switch (event->type) 585 { 586 case SDL_CONTROLLERDEVICEADDED: 587 { 588 INFO_LOG("Controller {} inserted", event->cdevice.which); 589 OpenDevice(event->cdevice.which, true); 590 return true; 591 } 592 593 case SDL_CONTROLLERDEVICEREMOVED: 594 { 595 INFO_LOG("Controller {} removed", event->cdevice.which); 596 CloseDevice(event->cdevice.which); 597 return true; 598 } 599 600 case SDL_JOYDEVICEADDED: 601 { 602 // Let game controller handle.. well.. game controllers. 603 if (SDL_IsGameController(event->jdevice.which)) 604 return false; 605 606 INFO_LOG("Joystick {} inserted", event->jdevice.which); 607 OpenDevice(event->cdevice.which, false); 608 return true; 609 } 610 break; 611 612 case SDL_JOYDEVICEREMOVED: 613 { 614 if (auto it = GetControllerDataForJoystickId(event->cdevice.which); 615 it != m_controllers.end() && it->game_controller) 616 return false; 617 618 INFO_LOG("Joystick {} removed", event->jdevice.which); 619 CloseDevice(event->cdevice.which); 620 return true; 621 } 622 623 case SDL_CONTROLLERAXISMOTION: 624 return HandleControllerAxisEvent(&event->caxis); 625 626 case SDL_CONTROLLERBUTTONDOWN: 627 case SDL_CONTROLLERBUTTONUP: 628 return HandleControllerButtonEvent(&event->cbutton); 629 630 case SDL_JOYAXISMOTION: 631 return HandleJoystickAxisEvent(&event->jaxis); 632 633 case SDL_JOYBUTTONDOWN: 634 case SDL_JOYBUTTONUP: 635 return HandleJoystickButtonEvent(&event->jbutton); 636 637 case SDL_JOYHATMOTION: 638 return HandleJoystickHatEvent(&event->jhat); 639 640 default: 641 return false; 642 } 643 } 644 645 SDL_Joystick* SDLInputSource::GetJoystickForDevice(std::string_view device) 646 { 647 if (!device.starts_with("SDL-")) 648 return nullptr; 649 650 const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(4)); 651 if (!player_id.has_value() || player_id.value() < 0) 652 return nullptr; 653 654 auto it = GetControllerDataForPlayerId(player_id.value()); 655 if (it == m_controllers.end()) 656 return nullptr; 657 658 return it->joystick; 659 } 660 661 SDLInputSource::ControllerDataVector::iterator SDLInputSource::GetControllerDataForJoystickId(int id) 662 { 663 return std::find_if(m_controllers.begin(), m_controllers.end(), 664 [id](const ControllerData& cd) { return cd.joystick_id == id; }); 665 } 666 667 SDLInputSource::ControllerDataVector::iterator SDLInputSource::GetControllerDataForPlayerId(int id) 668 { 669 return std::find_if(m_controllers.begin(), m_controllers.end(), 670 [id](const ControllerData& cd) { return cd.player_id == id; }); 671 } 672 673 int SDLInputSource::GetFreePlayerId() const 674 { 675 for (int player_id = 0;; player_id++) 676 { 677 size_t i; 678 for (i = 0; i < m_controllers.size(); i++) 679 { 680 if (m_controllers[i].player_id == player_id) 681 break; 682 } 683 if (i == m_controllers.size()) 684 return player_id; 685 } 686 687 return 0; 688 } 689 690 bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller) 691 { 692 SDL_GameController* gcontroller; 693 SDL_Joystick* joystick; 694 695 if (is_gamecontroller) 696 { 697 gcontroller = SDL_GameControllerOpen(index); 698 joystick = gcontroller ? SDL_GameControllerGetJoystick(gcontroller) : nullptr; 699 } 700 else 701 { 702 gcontroller = nullptr; 703 joystick = SDL_JoystickOpen(index); 704 } 705 706 if (!gcontroller && !joystick) 707 { 708 ERROR_LOG("Failed to open controller {}", index); 709 if (gcontroller) 710 SDL_GameControllerClose(gcontroller); 711 712 return false; 713 } 714 715 const int joystick_id = SDL_JoystickInstanceID(joystick); 716 int player_id = gcontroller ? SDL_GameControllerGetPlayerIndex(gcontroller) : SDL_JoystickGetPlayerIndex(joystick); 717 if (player_id < 0 || GetControllerDataForPlayerId(player_id) != m_controllers.end()) 718 { 719 const int free_player_id = GetFreePlayerId(); 720 WARNING_LOG("Controller {} (joystick {}) returned player ID {}, which is invalid or in use. Using ID {} instead.", 721 index, joystick_id, player_id, free_player_id); 722 player_id = free_player_id; 723 } 724 725 const char* name = gcontroller ? SDL_GameControllerName(gcontroller) : SDL_JoystickName(joystick); 726 if (!name) 727 name = "Unknown Device"; 728 729 VERBOSE_LOG("Opened {} {} (instance id {}, player id {}): {}", is_gamecontroller ? "game controller" : "joystick", 730 index, joystick_id, player_id, name); 731 732 ControllerData cd = {}; 733 cd.player_id = player_id; 734 cd.joystick_id = joystick_id; 735 cd.haptic_left_right_effect = -1; 736 cd.game_controller = gcontroller; 737 cd.joystick = joystick; 738 739 if (gcontroller) 740 { 741 const int num_axes = SDL_JoystickNumAxes(joystick); 742 const int num_buttons = SDL_JoystickNumButtons(joystick); 743 cd.joy_axis_used_in_gc.resize(num_axes, false); 744 cd.joy_button_used_in_gc.resize(num_buttons, false); 745 auto mark_bind = [&](SDL_GameControllerButtonBind bind) { 746 if (bind.bindType == SDL_CONTROLLER_BINDTYPE_AXIS && bind.value.axis < num_axes) 747 cd.joy_axis_used_in_gc[bind.value.axis] = true; 748 if (bind.bindType == SDL_CONTROLLER_BINDTYPE_BUTTON && bind.value.button < num_buttons) 749 cd.joy_button_used_in_gc[bind.value.button] = true; 750 }; 751 for (size_t i = 0; i < std::size(s_sdl_axis_names); i++) 752 mark_bind(SDL_GameControllerGetBindForAxis(gcontroller, static_cast<SDL_GameControllerAxis>(i))); 753 for (size_t i = 0; i < std::size(s_sdl_button_names); i++) 754 mark_bind(SDL_GameControllerGetBindForButton(gcontroller, static_cast<SDL_GameControllerButton>(i))); 755 756 VERBOSE_LOG("Controller {} has {} axes and {} buttons", player_id, num_axes, num_buttons); 757 } 758 else 759 { 760 // GC doesn't have the concept of hats, so we only need to do this for joysticks. 761 const int num_hats = SDL_JoystickNumHats(joystick); 762 if (num_hats > 0) 763 cd.last_hat_state.resize(static_cast<size_t>(num_hats), u8(0)); 764 765 VERBOSE_LOG("Joystick {} has {} axes, {} buttons and {} hats", player_id, SDL_JoystickNumAxes(joystick), 766 SDL_JoystickNumButtons(joystick), num_hats); 767 } 768 769 cd.use_game_controller_rumble = (gcontroller && SDL_GameControllerRumble(gcontroller, 0, 0, 0) == 0); 770 if (cd.use_game_controller_rumble) 771 { 772 VERBOSE_LOG("Rumble is supported on '{}' via gamecontroller", name); 773 } 774 else 775 { 776 SDL_Haptic* haptic = SDL_HapticOpenFromJoystick(joystick); 777 if (haptic) 778 { 779 SDL_HapticEffect ef = {}; 780 ef.leftright.type = SDL_HAPTIC_LEFTRIGHT; 781 ef.leftright.length = 1000; 782 783 int ef_id = SDL_HapticNewEffect(haptic, &ef); 784 if (ef_id >= 0) 785 { 786 cd.haptic = haptic; 787 cd.haptic_left_right_effect = ef_id; 788 } 789 else 790 { 791 ERROR_LOG("Failed to create haptic left/right effect: {}", SDL_GetError()); 792 if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) != 0) 793 { 794 cd.haptic = haptic; 795 } 796 else 797 { 798 ERROR_LOG("No haptic rumble supported: {}", SDL_GetError()); 799 SDL_HapticClose(haptic); 800 } 801 } 802 } 803 804 if (cd.haptic) 805 VERBOSE_LOG("Rumble is supported on '{}' via haptic", name); 806 } 807 808 if (!cd.haptic && !cd.use_game_controller_rumble) 809 VERBOSE_LOG("Rumble is not supported on '{}'", name); 810 811 if (player_id >= 0 && static_cast<u32>(player_id) < MAX_LED_COLORS && gcontroller && 812 SDL_GameControllerHasLED(gcontroller)) 813 { 814 SetControllerRGBLED(gcontroller, m_led_colors[player_id]); 815 } 816 817 m_controllers.push_back(std::move(cd)); 818 819 InputManager::OnInputDeviceConnected(fmt::format("SDL-{}", player_id), name); 820 return true; 821 } 822 823 bool SDLInputSource::CloseDevice(int joystick_index) 824 { 825 auto it = GetControllerDataForJoystickId(joystick_index); 826 if (it == m_controllers.end()) 827 return false; 828 829 InputManager::OnInputDeviceDisconnected( 830 InputBindingKey{{.source_type = InputSourceType::SDL, .source_index = static_cast<u32>(it->player_id)}}, 831 fmt::format("SDL-{}", it->player_id)); 832 833 if (it->haptic) 834 SDL_HapticClose(it->haptic); 835 836 if (it->game_controller) 837 SDL_GameControllerClose(it->game_controller); 838 else 839 SDL_JoystickClose(it->joystick); 840 841 m_controllers.erase(it); 842 return true; 843 } 844 845 static float NormalizeS16(s16 value) 846 { 847 return static_cast<float>(value) / (value < 0 ? 32768.0f : 32767.0f); 848 } 849 850 bool SDLInputSource::HandleControllerAxisEvent(const SDL_ControllerAxisEvent* ev) 851 { 852 auto it = GetControllerDataForJoystickId(ev->which); 853 if (it == m_controllers.end()) 854 return false; 855 856 const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, ev->axis)); 857 InputManager::InvokeEvents(key, NormalizeS16(ev->value)); 858 return true; 859 } 860 861 bool SDLInputSource::HandleControllerButtonEvent(const SDL_ControllerButtonEvent* ev) 862 { 863 auto it = GetControllerDataForJoystickId(ev->which); 864 if (it == m_controllers.end()) 865 return false; 866 867 const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, ev->button)); 868 const GenericInputBinding generic_key = (ev->button < std::size(s_sdl_generic_binding_button_mapping)) ? 869 s_sdl_generic_binding_button_mapping[ev->button] : 870 GenericInputBinding::Unknown; 871 InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f, generic_key); 872 return true; 873 } 874 875 bool SDLInputSource::HandleJoystickAxisEvent(const SDL_JoyAxisEvent* ev) 876 { 877 auto it = GetControllerDataForJoystickId(ev->which); 878 if (it == m_controllers.end()) 879 return false; 880 if (ev->axis < it->joy_axis_used_in_gc.size() && it->joy_axis_used_in_gc[ev->axis]) 881 return false; // Will get handled by GC event 882 const u32 axis = ev->axis + static_cast<u32>(std::size(s_sdl_axis_names)); // Ensure we don't conflict with GC axes 883 const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, axis)); 884 InputManager::InvokeEvents(key, NormalizeS16(ev->value)); 885 return true; 886 } 887 888 bool SDLInputSource::HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev) 889 { 890 auto it = GetControllerDataForJoystickId(ev->which); 891 if (it == m_controllers.end()) 892 return false; 893 if (ev->button < it->joy_button_used_in_gc.size() && it->joy_button_used_in_gc[ev->button]) 894 return false; // Will get handled by GC event 895 const u32 button = 896 ev->button + static_cast<u32>(std::size(s_sdl_button_names)); // Ensure we don't conflict with GC buttons 897 const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, button)); 898 InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f); 899 return true; 900 } 901 902 bool SDLInputSource::HandleJoystickHatEvent(const SDL_JoyHatEvent* ev) 903 { 904 auto it = GetControllerDataForJoystickId(ev->which); 905 if (it == m_controllers.end() || ev->hat >= it->last_hat_state.size()) 906 return false; 907 908 const unsigned long last_direction = it->last_hat_state[ev->hat]; 909 it->last_hat_state[ev->hat] = ev->value; 910 911 unsigned long changed_direction = last_direction ^ ev->value; 912 while (changed_direction != 0) 913 { 914 const u32 pos = CountTrailingZeros(changed_direction); 915 916 const unsigned long mask = (1u << pos); 917 changed_direction &= ~mask; 918 919 const InputBindingKey key(MakeGenericControllerHatKey(InputSourceType::SDL, it->player_id, ev->hat, 920 static_cast<u8>(pos), 921 static_cast<u32>(std::size(s_sdl_hat_direction_names)))); 922 InputManager::InvokeEvents(key, (last_direction & mask) ? 0.0f : 1.0f); 923 } 924 925 return true; 926 } 927 928 std::vector<InputBindingKey> SDLInputSource::EnumerateMotors() 929 { 930 std::vector<InputBindingKey> ret; 931 932 InputBindingKey key = {}; 933 key.source_type = InputSourceType::SDL; 934 935 for (ControllerData& cd : m_controllers) 936 { 937 key.source_index = cd.player_id; 938 939 if (cd.use_game_controller_rumble || cd.haptic_left_right_effect) 940 { 941 // two motors 942 key.source_subtype = InputSubclass::ControllerMotor; 943 key.data = 0; 944 ret.push_back(key); 945 key.data = 1; 946 ret.push_back(key); 947 } 948 else if (cd.haptic) 949 { 950 // haptic effect 951 key.source_subtype = InputSubclass::ControllerHaptic; 952 key.data = 0; 953 ret.push_back(key); 954 } 955 } 956 957 return ret; 958 } 959 960 bool SDLInputSource::GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping) 961 { 962 if (!device.starts_with("SDL-")) 963 return false; 964 965 const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(4)); 966 if (!player_id.has_value() || player_id.value() < 0) 967 return false; 968 969 ControllerDataVector::iterator it = GetControllerDataForPlayerId(player_id.value()); 970 if (it == m_controllers.end()) 971 return false; 972 973 if (it->game_controller) 974 { 975 // assume all buttons are present. 976 const s32 pid = player_id.value(); 977 for (u32 i = 0; i < std::size(s_sdl_generic_binding_axis_mapping); i++) 978 { 979 const GenericInputBinding negative = s_sdl_generic_binding_axis_mapping[i][0]; 980 const GenericInputBinding positive = s_sdl_generic_binding_axis_mapping[i][1]; 981 if (negative != GenericInputBinding::Unknown) 982 mapping->emplace_back(negative, fmt::format("SDL-{}/-{}", pid, s_sdl_axis_names[i])); 983 984 if (positive != GenericInputBinding::Unknown) 985 mapping->emplace_back(positive, fmt::format("SDL-{}/+{}", pid, s_sdl_axis_names[i])); 986 } 987 for (u32 i = 0; i < std::size(s_sdl_generic_binding_button_mapping); i++) 988 { 989 const GenericInputBinding binding = s_sdl_generic_binding_button_mapping[i]; 990 if (binding != GenericInputBinding::Unknown) 991 mapping->emplace_back(binding, fmt::format("SDL-{}/{}", pid, s_sdl_button_names[i])); 992 } 993 994 if (it->use_game_controller_rumble || it->haptic_left_right_effect) 995 { 996 mapping->emplace_back(GenericInputBinding::SmallMotor, fmt::format("SDL-{}/SmallMotor", pid)); 997 mapping->emplace_back(GenericInputBinding::LargeMotor, fmt::format("SDL-{}/LargeMotor", pid)); 998 } 999 else 1000 { 1001 mapping->emplace_back(GenericInputBinding::SmallMotor, fmt::format("SDL-{}/Haptic", pid)); 1002 mapping->emplace_back(GenericInputBinding::LargeMotor, fmt::format("SDL-{}/Haptic", pid)); 1003 } 1004 1005 return true; 1006 } 1007 else 1008 { 1009 // joysticks have arbitrary axis numbers, so automapping isn't going to work here. 1010 return false; 1011 } 1012 } 1013 1014 void SDLInputSource::UpdateMotorState(InputBindingKey key, float intensity) 1015 { 1016 if (key.source_subtype != InputSubclass::ControllerMotor && key.source_subtype != InputSubclass::ControllerHaptic) 1017 return; 1018 1019 auto it = GetControllerDataForPlayerId(key.source_index); 1020 if (it == m_controllers.end()) 1021 return; 1022 1023 it->rumble_intensity[key.data] = static_cast<u16>(intensity * 65535.0f); 1024 SendRumbleUpdate(&(*it)); 1025 } 1026 1027 void SDLInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, 1028 float small_intensity) 1029 { 1030 if (large_key.source_index != small_key.source_index || large_key.source_subtype != InputSubclass::ControllerMotor || 1031 small_key.source_subtype != InputSubclass::ControllerMotor) 1032 { 1033 // bonkers config where they're mapped to different controllers... who would do such a thing? 1034 UpdateMotorState(large_key, large_intensity); 1035 UpdateMotorState(small_key, small_intensity); 1036 return; 1037 } 1038 1039 auto it = GetControllerDataForPlayerId(large_key.source_index); 1040 if (it == m_controllers.end()) 1041 return; 1042 1043 it->rumble_intensity[large_key.data] = static_cast<u16>(large_intensity * 65535.0f); 1044 it->rumble_intensity[small_key.data] = static_cast<u16>(small_intensity * 65535.0f); 1045 SendRumbleUpdate(&(*it)); 1046 } 1047 1048 void SDLInputSource::SendRumbleUpdate(ControllerData* cd) 1049 { 1050 // we'll update before this duration is elapsed 1051 static constexpr u32 DURATION = 65535; // SDL_MAX_RUMBLE_DURATION_MS 1052 1053 if (cd->use_game_controller_rumble) 1054 { 1055 SDL_GameControllerRumble(cd->game_controller, cd->rumble_intensity[0], cd->rumble_intensity[1], DURATION); 1056 return; 1057 } 1058 1059 if (cd->haptic_left_right_effect >= 0) 1060 { 1061 if ((static_cast<u32>(cd->rumble_intensity[0]) + static_cast<u32>(cd->rumble_intensity[1])) > 0) 1062 { 1063 SDL_HapticEffect ef; 1064 ef.type = SDL_HAPTIC_LEFTRIGHT; 1065 ef.leftright.large_magnitude = cd->rumble_intensity[0]; 1066 ef.leftright.small_magnitude = cd->rumble_intensity[1]; 1067 ef.leftright.length = DURATION; 1068 SDL_HapticUpdateEffect(cd->haptic, cd->haptic_left_right_effect, &ef); 1069 SDL_HapticRunEffect(cd->haptic, cd->haptic_left_right_effect, SDL_HAPTIC_INFINITY); 1070 } 1071 else 1072 { 1073 SDL_HapticStopEffect(cd->haptic, cd->haptic_left_right_effect); 1074 } 1075 } 1076 else 1077 { 1078 const float strength = 1079 static_cast<float>(std::max(cd->rumble_intensity[0], cd->rumble_intensity[1])) * (1.0f / 65535.0f); 1080 if (strength > 0.0f) 1081 SDL_HapticRumblePlay(cd->haptic, strength, DURATION); 1082 else 1083 SDL_HapticRumbleStop(cd->haptic); 1084 } 1085 } 1086 1087 std::unique_ptr<InputSource> InputSource::CreateSDLSource() 1088 { 1089 return std::make_unique<SDLInputSource>(); 1090 }