mirror of
https://github.com/hedge-dev/UnleashedRecomp
synced 2026-05-24 15:21:16 -04:00
4a33883512
This makes controllers have their first input ignored until after detection, much like other PC ports. This probably won't end up being merged in though, as other Sonic PC ports don't seem to work this way, but I'll push it anyway just for the sake of having this somewhere.
386 lines
10 KiB
C++
386 lines
10 KiB
C++
#include <stdafx.h>
|
|
#include <SDL.h>
|
|
#include <user/config.h>
|
|
#include <hid/hid.h>
|
|
#include <os/logger.h>
|
|
#include <ui/game_window.h>
|
|
#include <kernel/xdm.h>
|
|
#include <app.h>
|
|
|
|
#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_GameControllerTypeForIndex(index);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
return hid::EInputDevice::Xbox;
|
|
}
|
|
|
|
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<Controller, 4> 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;
|
|
|
|
// Signal that we've changed input device to block first input.
|
|
if (hid::g_inputDevice == hid::EInputDevice::Keyboard)
|
|
hid::g_hasChangedInputDevice = true;
|
|
|
|
hid::g_inputDevice = controller->GetInputDevice();
|
|
hid::g_inputDevicePad = hid::g_inputDevice;
|
|
hid::g_inputDevicePadExplicit = (hid::EInputDeviceExplicit)controller->GetControllerType();
|
|
|
|
if (hid::g_hasChangedInputDevice)
|
|
LOGFN("Input Device: {}", hid::GetInputDeviceName());
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (!hid::g_hasChangedInputDevice)
|
|
controller->PollAxis();
|
|
}
|
|
else
|
|
{
|
|
SDL_ShowCursor(SDL_DISABLE);
|
|
SetControllerInputDevice(controller);
|
|
|
|
if (!hid::g_hasChangedInputDevice)
|
|
controller->Poll();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SDL_KEYDOWN:
|
|
case SDL_KEYUP:
|
|
{
|
|
if (hid::g_inputDevice != hid::EInputDevice::Keyboard)
|
|
{
|
|
hid::g_inputDevice = hid::EInputDevice::Keyboard;
|
|
hid::g_hasChangedInputDevice = true;
|
|
|
|
LOGN("Input Device: 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_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;
|
|
|
|
if (hid::g_hasChangedInputDevice)
|
|
{
|
|
hid::g_hasChangedInputDevice = false;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
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;
|
|
}
|