#include #include #include #include #include #include #include #include #include "SDL.h" #include "prx/libSceVideoOut/include/PadInput.hpp" #include "prx/libSceMouse/include/MouseState.hpp" #include "prx/libSceVideoOut/include/DisplayWindow.hpp" #include "prx/libSceAgcDriver/Execution/include/Presentation.hpp" #include "prx/libScePad/include/PadState.hpp" #include "prx/libScePad/include/PadInputTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libSceIme/include/ImeInput.hpp" PadInput::PadInput() : PadInput(Pad::LoadInputMapping()) { } PadInput::PadInput(Pad::InputConfiguration configuration) : keyboardMouse(std::move(configuration)) { openFirstAvailableController(); } PadInput::~PadInput() { closeController(); } void PadInput::openFirstAvailableController() { if (controller != nullptr) return; SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); if ((SDL_WasInit(SDL_INIT_GAMECONTROLLER) & SDL_INIT_GAMECONTROLLER) == 0) { if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) { APS5_LOG_ERR("Pad: SDL game controller init failed: %s", SDL_GetError()); return; } } for (int deviceIndex = 0; deviceIndex < SDL_NumJoysticks(); ++deviceIndex) { if (!SDL_IsGameController(deviceIndex)) continue; openController(deviceIndex); if (controller != nullptr) return; } } void PadInput::openController(int deviceIndex) { if (controller != nullptr || !SDL_IsGameController(deviceIndex)) return; controller = SDL_GameControllerOpen(deviceIndex); if (controller == nullptr) { APS5_LOG_ERR("Pad: could not open game controller %d: %s", deviceIndex, SDL_GetError()); return; } const char* name = SDL_GameControllerName(controller); APS5_LOG_OUT("Pad: connected game controller: %s (type %d, sensors accel=%d gyro=%d, touchpads=%d, led=%d, trigger rumble=%d)", name != nullptr ? name : "unknown", static_cast(SDL_GameControllerGetType(controller)), SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE, SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE, SDL_GameControllerGetNumTouchpads(controller), SDL_GameControllerHasLED(controller) == SDL_TRUE, SDL_GameControllerHasRumbleTriggers(controller) == SDL_TRUE); enableSensors(); outputPending = true; } void PadInput::enableSensors() { if (controller == nullptr) return; const SDL_bool wanted = outputState.motionEnabled ? SDL_TRUE : SDL_FALSE; if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE) SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, wanted); if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE) SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, wanted); } void PadInput::closeController() { if (controller == nullptr) return; SDL_GameControllerClose(controller); controller = nullptr; controllerState = {}; } void PadInput::applyOutput() { PadOutputState fetched; if (PadFetchOutput_nid_postfix(&outputSequence, &fetched)) { const bool motionChanged = fetched.motionEnabled != outputState.motionEnabled; outputState = fetched; outputPending = true; if (motionChanged) enableSensors(); } if (controller == nullptr) return; const auto now = std::chrono::steady_clock::now(); const bool isPs5 = SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_PS5; const auto large = isPs5 ? outputState.vibrationLarge : std::max(outputState.vibrationLarge, outputState.audioVibrationLarge); const auto small = isPs5 ? outputState.vibrationSmall : std::max(outputState.vibrationSmall, outputState.audioVibrationSmall); const bool rumbling = large != 0 || small != 0; const bool triggerRumble = outputState.trigger[0].fallback != 0 || outputState.trigger[1].fallback != 0; if (!outputPending) { if ((rumbling || (triggerRumble && !isPs5)) && now >= nextRumbleRefresh) outputPending = true; else return; } outputPending = false; nextRumbleRefresh = now + std::chrono::milliseconds(700); constexpr Uint32 rumbleMs = 2000; SDL_GameControllerRumble(controller, static_cast(large * 257), static_cast(small * 257), rumbling ? rumbleMs : 0); if (SDL_GameControllerHasLED(controller) == SDL_TRUE) { if (outputState.lightBarValid) SDL_GameControllerSetLED(controller, outputState.lightBar[0], outputState.lightBar[1], outputState.lightBar[2]); else SDL_GameControllerSetLED(controller, 0, 64, 255); } if (outputState.triggerTouched) { if (isPs5) { Uint8 effect[47] = {}; effect[0] = 0x04 | 0x08; std::memcpy(effect + 10, outputState.trigger[1].effect, 11); std::memcpy(effect + 21, outputState.trigger[0].effect, 11); SDL_GameControllerSendEffect(controller, effect, sizeof(effect)); } else if (SDL_GameControllerHasRumbleTriggers(controller) == SDL_TRUE) { SDL_GameControllerRumbleTriggers(controller, static_cast(outputState.trigger[0].fallback * 257), static_cast(outputState.trigger[1].fallback * 257), triggerRumble ? rumbleMs : 0); } } } void PadInput::setMouseMode(bool enabled) { SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "0"); SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE, "0"); if (SDL_SetRelativeMouseMode(enabled ? SDL_TRUE : SDL_FALSE) != 0) throw std::runtime_error(std::string("Pad: relative mouse mode failed: ") + SDL_GetError()); keyboardMouse.SetMouseMode(enabled); } PadInputState PadInput::sampleController() const { PadInputState result; if (controller == nullptr) return result; const auto readButton = [this](SDL_GameControllerButton button) { return SDL_GameControllerGetButton(controller, button) != 0; }; const auto addButton = [&result, &readButton](SDL_GameControllerButton source, Pad::PadButton button) { if (readButton(source)) result.buttons |= static_cast(button); }; addButton(SDL_CONTROLLER_BUTTON_A, Pad::PadButton::Cross); addButton(SDL_CONTROLLER_BUTTON_B, Pad::PadButton::Circle); addButton(SDL_CONTROLLER_BUTTON_X, Pad::PadButton::Square); addButton(SDL_CONTROLLER_BUTTON_Y, Pad::PadButton::Triangle); addButton(SDL_CONTROLLER_BUTTON_LEFTSHOULDER, Pad::PadButton::L1); addButton(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, Pad::PadButton::R1); const auto type = SDL_GameControllerGetType(controller); const bool viewPressed = type != SDL_CONTROLLER_TYPE_PS4 && type != SDL_CONTROLLER_TYPE_PS5 && readButton(SDL_CONTROLLER_BUTTON_BACK); if (viewPressed) result.buttons |= static_cast(Pad::PadButton::TouchPad); addButton(SDL_CONTROLLER_BUTTON_START, Pad::PadButton::Options); addButton(SDL_CONTROLLER_BUTTON_LEFTSTICK, Pad::PadButton::L3); addButton(SDL_CONTROLLER_BUTTON_RIGHTSTICK, Pad::PadButton::R3); addButton(SDL_CONTROLLER_BUTTON_DPAD_UP, Pad::PadButton::Up); addButton(SDL_CONTROLLER_BUTTON_DPAD_RIGHT, Pad::PadButton::Right); addButton(SDL_CONTROLLER_BUTTON_DPAD_DOWN, Pad::PadButton::Down); addButton(SDL_CONTROLLER_BUTTON_DPAD_LEFT, Pad::PadButton::Left); addButton(SDL_CONTROLLER_BUTTON_TOUCHPAD, Pad::PadButton::TouchPad); const auto triggerValue = [this](SDL_GameControllerAxis axis) { const auto value = std::clamp(SDL_GameControllerGetAxis(controller, axis), 0, 32767); return static_cast((value * 255 + 16383) / 32767); }; result.analogButtonsL2 = triggerValue(SDL_CONTROLLER_AXIS_TRIGGERLEFT); result.analogButtonsR2 = triggerValue(SDL_CONTROLLER_AXIS_TRIGGERRIGHT); if (result.analogButtonsL2 != 0) result.buttons |= static_cast(Pad::PadButton::L2); if (result.analogButtonsR2 != 0) result.buttons |= static_cast(Pad::PadButton::R2); const auto stick = [this](SDL_GameControllerAxis xAxis, SDL_GameControllerAxis yAxis, int deadzonePercent) { return Pad::StickWithDeadzone(SDL_GameControllerGetAxis(controller, xAxis), SDL_GameControllerGetAxis(controller, yAxis), deadzonePercent); }; const auto left = stick(SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY, keyboardMouse.Configuration().settings.leftStickDeadzonePercent); const auto right = stick(SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY, keyboardMouse.Configuration().settings.rightStickDeadzonePercent); result.sticks = {left[0], left[1], right[0], right[1]}; switch (SDL_GameControllerGetType(controller)) { case SDL_CONTROLLER_TYPE_PS5: result.deviceKind = 1; break; case SDL_CONTROLLER_TYPE_PS4: result.deviceKind = 2; break; default: result.deviceKind = 3; break; } if (SDL_GameControllerIsSensorEnabled(controller, SDL_SENSOR_ACCEL) == SDL_TRUE && SDL_GameControllerIsSensorEnabled(controller, SDL_SENSOR_GYRO) == SDL_TRUE) { float accel[3]; float gyro[3]; if (SDL_GameControllerGetSensorData(controller, SDL_SENSOR_ACCEL, accel, 3) == 0 && SDL_GameControllerGetSensorData(controller, SDL_SENSOR_GYRO, gyro, 3) == 0) { result.hasMotion = true; for (int i = 0; i < 3; ++i) { result.accel[i] = accel[i]; result.gyro[i] = gyro[i]; } } } if (SDL_GameControllerGetNumTouchpads(controller) > 0) { for (int finger = 0; finger < 2; ++finger) { Uint8 down = 0; float x = 0.0f; float y = 0.0f; float pressure = 0.0f; if (SDL_GameControllerGetTouchpadFinger(controller, 0, finger, &down, &x, &y, &pressure) != 0 || down == 0) continue; result.touch[finger].active = true; result.touch[finger].x = static_cast(std::clamp(x, 0.0f, 1.0f) * 1919.0f); result.touch[finger].y = static_cast(std::clamp(y, 0.0f, 1.0f) * 942.0f); } } if (viewPressed && !result.touch[0].active && !result.touch[1].active) { result.touch[0] = {true, 960, 471, 0}; } return result; } void PadInput::HandleEvent(const SDL_Event& event, DisplayWindow& window) { if (event.type == SDL_CONTROLLERDEVICEADDED) { openController(event.cdevice.which); return; } if (event.type == SDL_CONTROLLERDEVICEREMOVED && controller != nullptr) { const auto instanceId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller)); if (instanceId == event.cdevice.which) { closeController(); openFirstAvailableController(); publish(); } return; } if (event.type == SDL_CONTROLLERBUTTONDOWN || event.type == SDL_CONTROLLERBUTTONUP || event.type == SDL_CONTROLLERAXISMOTION || event.type == SDL_CONTROLLERDEVICEREMAPPED) { publish(); return; } if (event.type == SDL_WINDOWEVENT) { if (keyboardMouse.HandleEvent(event, std::chrono::steady_clock::now()).releaseMouse) setMouseMode(false); publish(); return; } // APS5_NO_PAD_INPUT=1 keeps a measurement run from reacting to keys or mouse buttons that reach // its window (a stray press advances the title into another stage). static const bool ignoreInput = std::getenv("APS5_NO_PAD_INPUT") != nullptr; const bool input = event.type == SDL_MOUSEWHEEL || event.type == SDL_MOUSEMOTION || event.type == SDL_KEYDOWN || event.type == SDL_KEYUP || event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP; if (!input || ignoreInput) return; if (event.type == SDL_KEYDOWN && ImeCapturesKeyboard_nid_postfix()) return; keyboardMouse.SetTitleReadsMouse(MouseIsOpen_nid_no_patch()); const auto requests = keyboardMouse.HandleEvent(event, std::chrono::steady_clock::now()); if (requests.toggleFullscreen && window.Handle() != nullptr && event.key.windowID == SDL_GetWindowID(window.Handle())) window.ToggleFullscreen(); if (requests.togglePerformanceOverlay && window.Handle() != nullptr && event.key.windowID == SDL_GetWindowID(window.Handle())) AgcDriverTogglePerformanceOverlay_nid_postfix(); if (requests.toggleMouse) setMouseMode(!keyboardMouse.MouseMode()); if (event.type != SDL_MOUSEMOTION) publish(); } void PadInput::Update() { if (controller != nullptr) SDL_GameControllerUpdate(); applyOutput(); if (controller != nullptr) controllerState = sampleController(); if (keyboardMouse.MouseMode() && SDL_GetKeyboardFocus() == nullptr) { keyboardMouse.Release(); setMouseMode(false); publish(); return; } const bool changed = keyboardMouse.Advance(std::chrono::steady_clock::now()); if (controller != nullptr || keyboardMouse.MouseMode() || changed) publish(); } void PadInput::publish() { PadPublishInput_nid_postfix(keyboardMouse.Compose(controllerState)); }