#include #include #include #include #include #include #include #include #define TRANSLATE_INPUT(S, X) SDL_GameControllerGetButton(controller, S) << FirstBitLow(X) #define VIBRATION_TIMEOUT_MS 5000 class Controller { public: SDL_GameController* controller{}; SDL_Joystick* joystick{}; SDL_JoystickID id{ -1 }; XAMINPUT_GAMEPAD state{}; XAMINPUT_VIBRATION vibration{ 0, 0 }; int index{}; Controller() = default; explicit Controller(int index) : Controller(SDL_GameControllerOpen(index)) { this->index = index; } Controller(SDL_GameController* controller) : controller(controller) { if (!controller) return; joystick = SDL_GameControllerGetJoystick(controller); id = SDL_JoystickInstanceID(joystick); } SDL_GameControllerType GetControllerType() const { return SDL_GameControllerGetType(controller); } hid::EInputDevice GetInputDevice() const { switch (GetControllerType()) { case SDL_CONTROLLER_TYPE_PS3: case SDL_CONTROLLER_TYPE_PS4: case SDL_CONTROLLER_TYPE_PS5: return hid::EInputDevice::PlayStation; case SDL_CONTROLLER_TYPE_XBOX360: case SDL_CONTROLLER_TYPE_XBOXONE: return hid::EInputDevice::Xbox; default: return hid::EInputDevice::Unknown; } } const char* GetControllerName() const { auto result = SDL_GameControllerName(controller); if (!result) return "Unknown Device"; return result; } void Close() { if (!controller) return; SDL_GameControllerClose(controller); controller = nullptr; joystick = nullptr; id = -1; } bool CanPoll() { return controller; } void PollAxis() { if (!CanPoll()) return; auto& pad = state; pad.sThumbLX = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); pad.sThumbLY = ~SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); pad.sThumbRX = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); pad.sThumbRY = ~SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); pad.bLeftTrigger = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) >> 7; pad.bRightTrigger = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) >> 7; } void Poll() { if (!CanPoll()) return; auto& pad = state; pad.wButtons = 0; pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_DPAD_UP, XAMINPUT_GAMEPAD_DPAD_UP); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_DPAD_DOWN, XAMINPUT_GAMEPAD_DPAD_DOWN); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_DPAD_LEFT, XAMINPUT_GAMEPAD_DPAD_LEFT); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_DPAD_RIGHT, XAMINPUT_GAMEPAD_DPAD_RIGHT); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_START, XAMINPUT_GAMEPAD_START); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_BACK, XAMINPUT_GAMEPAD_BACK); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_TOUCHPAD, XAMINPUT_GAMEPAD_BACK); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_LEFTSTICK, XAMINPUT_GAMEPAD_LEFT_THUMB); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_RIGHTSTICK, XAMINPUT_GAMEPAD_RIGHT_THUMB); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_LEFTSHOULDER, XAMINPUT_GAMEPAD_LEFT_SHOULDER); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, XAMINPUT_GAMEPAD_RIGHT_SHOULDER); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_A, XAMINPUT_GAMEPAD_A); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_B, XAMINPUT_GAMEPAD_B); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_X, XAMINPUT_GAMEPAD_X); pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_Y, XAMINPUT_GAMEPAD_Y); } void SetVibration(const XAMINPUT_VIBRATION& vibration) { if (!CanPoll()) return; this->vibration = vibration; SDL_GameControllerRumble(controller, vibration.wLeftMotorSpeed * 256, vibration.wRightMotorSpeed * 256, VIBRATION_TIMEOUT_MS); } void SetLED(const uint8_t r, const uint8_t g, const uint8_t b) const { SDL_GameControllerSetLED(controller, r, g, b); } }; std::array g_controllers; Controller* g_activeController; inline Controller* EnsureController(uint32_t dwUserIndex) { if (!g_controllers[dwUserIndex].controller) return nullptr; return &g_controllers[dwUserIndex]; } inline size_t FindFreeController() { for (size_t i = 0; i < g_controllers.size(); i++) { if (!g_controllers[i].controller) return i; } return -1; } inline Controller* FindController(int which) { for (auto& controller : g_controllers) { if (controller.id == which) return &controller; } return nullptr; } static void SetControllerInputDevice(Controller* controller) { g_activeController = controller; if (App::s_isLoading) return; hid::g_inputDevice = controller->GetInputDevice(); hid::g_inputDeviceController = hid::g_inputDevice; auto controllerType = (hid::EInputDeviceExplicit)controller->GetControllerType(); auto controllerName = controller->GetControllerName(); // Only proceed if the controller type changes. if (hid::g_inputDeviceExplicit != controllerType) { hid::g_inputDeviceExplicit = controllerType; if (controllerType == hid::EInputDeviceExplicit::Unknown) { LOGFN("Detected controller: {} (Unknown Controller Type)", controllerName); } else { LOGFN("Detected controller: {}", controllerName); } } } static void SetControllerTimeOfDayLED(Controller& controller, bool isNight) { auto r = isNight ? 22 : 0; auto g = isNight ? 0 : 37; auto b = isNight ? 101 : 184; controller.SetLED(r, g, b); } int HID_OnSDLEvent(void*, SDL_Event* event) { switch (event->type) { case SDL_CONTROLLERDEVICEADDED: { const auto freeIndex = FindFreeController(); if (freeIndex != -1) { auto controller = Controller(event->cdevice.which); g_controllers[freeIndex] = controller; SetControllerTimeOfDayLED(controller, App::s_isWerehog); } break; } case SDL_CONTROLLERDEVICEREMOVED: { auto* controller = FindController(event->cdevice.which); if (controller) controller->Close(); break; } case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: case SDL_CONTROLLERAXISMOTION: case SDL_CONTROLLERTOUCHPADDOWN: { auto* controller = FindController(event->cdevice.which); if (!controller) break; if (event->type == SDL_CONTROLLERAXISMOTION) { if (abs(event->caxis.value) > 8000) { SDL_ShowCursor(SDL_DISABLE); SetControllerInputDevice(controller); } controller->PollAxis(); } else { SDL_ShowCursor(SDL_DISABLE); SetControllerInputDevice(controller); controller->Poll(); } break; } case SDL_KEYDOWN: case SDL_KEYUP: hid::g_inputDevice = hid::EInputDevice::Keyboard; break; case SDL_MOUSEMOTION: case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: { if (!GameWindow::IsFullscreen() || GameWindow::s_isFullscreenCursorVisible) SDL_ShowCursor(SDL_ENABLE); hid::g_inputDevice = hid::EInputDevice::Mouse; break; } case SDL_WINDOWEVENT: { if (event->window.event == SDL_WINDOWEVENT_FOCUS_LOST) { // Stop vibrating controllers on focus lost. for (auto& controller : g_controllers) controller.SetVibration({ 0, 0 }); } break; } case SDL_USER_EVILSONIC: { for (auto& controller : g_controllers) SetControllerTimeOfDayLED(controller, event->user.code); break; } } return 0; } void hid::Init() { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS3, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, "1"); SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "1"); SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); // Uses Button Labels. This hint is disabled for Nintendo Controllers. SDL_InitSubSystem(SDL_INIT_EVENTS); SDL_AddEventWatch(HID_OnSDLEvent, nullptr); SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); } uint32_t hid::GetState(uint32_t dwUserIndex, XAMINPUT_STATE* pState) { static uint32_t packet; if (!pState) return ERROR_BAD_ARGUMENTS; memset(pState, 0, sizeof(*pState)); pState->dwPacketNumber = packet++; if (!g_activeController) return ERROR_DEVICE_NOT_CONNECTED; pState->Gamepad = g_activeController->state; return ERROR_SUCCESS; } uint32_t hid::SetState(uint32_t dwUserIndex, XAMINPUT_VIBRATION* pVibration) { if (!pVibration) return ERROR_BAD_ARGUMENTS; if (!g_activeController) return ERROR_DEVICE_NOT_CONNECTED; g_activeController->SetVibration(*pVibration); return ERROR_SUCCESS; } uint32_t hid::GetCapabilities(uint32_t dwUserIndex, XAMINPUT_CAPABILITIES* pCaps) { if (!pCaps) return ERROR_BAD_ARGUMENTS; if (!g_activeController) return ERROR_DEVICE_NOT_CONNECTED; memset(pCaps, 0, sizeof(*pCaps)); pCaps->Type = XAMINPUT_DEVTYPE_GAMEPAD; pCaps->SubType = XAMINPUT_DEVSUBTYPE_GAMEPAD; // TODO: other types? pCaps->Flags = 0; pCaps->Gamepad = g_activeController->state; pCaps->Vibration = g_activeController->vibration; return ERROR_SUCCESS; }