diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 5991c747f..8cd0c78a2 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -591,7 +591,6 @@ struct Ngs2WaveformFormat { }; struct Ngs2WaveformBlock { - // PS5 grew the offset/size pair to 64 bits: the record is 40 bytes, not the 32-byte PS4 layout. std::uint64_t data_offset; std::uint64_t data_size; std::uint32_t num_repeats; diff --git a/core/libs/prx/libScePad/Export.cpp b/core/libs/prx/libScePad/Export.cpp index 87e2dbc79..ec4d13144 100644 --- a/core/libs/prx/libScePad/Export.cpp +++ b/core/libs/prx/libScePad/Export.cpp @@ -22,7 +22,6 @@ int APS5_VABI scePadClose_nid_postfix(int handle) { int APS5_VABI scePadDeviceClassGetExtendedInformation(int handle, PadDeviceClassExtendedInformation* info) { if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; if (info == nullptr) return PAD_ERROR_INVALID_ARG; - // Standard digital pad: device class only, every class-specific field zero. std::memset(info, 0, sizeof(*info)); info->deviceClass = PAD_DEVICE_CLASS_STANDARD; return PAD_OK; @@ -33,7 +32,7 @@ int APS5_VABI scePadDeviceClassParseData(int handle, const PadData* data, PadDev if (data == nullptr || class_data == nullptr) return PAD_ERROR_INVALID_ARG; std::memset(class_data, 0, sizeof(*class_data)); class_data->deviceClass = PAD_DEVICE_CLASS_STANDARD; - class_data->dataValid = data->connected; // no steering/guitar/drum payload for a standard pad + class_data->dataValid = data->connected; return PAD_OK; } @@ -65,13 +64,12 @@ int APS5_VABI scePadGetHandle(int user_id, int type, int index) { return 0; } -// Port 0 holds the virtual DualSense while the host has no pad open; fields per the PadInfo layout. int APS5_VABI scePadGetInfo_nid_postfix(PadInfo* info) { if (info == nullptr) return PAD_ERROR_INVALID_ARG; std::memset(info, 0, sizeof(*info)); info->maxConnectCount = 4; - info->connectedCount[0] = 1; // standard ports - info->connectedCount[1] = 0; // special ports + info->connectedCount[0] = 1; + info->connectedCount[1] = 0; PadInfo::PadTypeInfo& slot = info->padTypeInfo[0]; slot.connectPort = 0; slot.status = 1; @@ -84,7 +82,6 @@ int APS5_VABI scePadGetInfo_nid_postfix(PadInfo* info) { int APS5_VABI scePadGetTriggerEffectState(int handle, PadTriggerEffectStateInformation* info) { if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; if (info == nullptr) return PAD_ERROR_INVALID_ARG; - // Both triggers report the neutral "no feedback engaged" state; the host pad has no trigger readback. std::memset(info, 0, sizeof(*info)); return PAD_OK; } @@ -92,7 +89,6 @@ int APS5_VABI scePadGetTriggerEffectState(int handle, PadTriggerEffectStateInfor int APS5_VABI scePadGetExtControllerInformation(int handle, void* info) { if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; if (info == nullptr) return PAD_ERROR_INVALID_ARG; - // ScePadExtendedControllerInformation: base information followed by the class extension (all zero for a standard pad). std::memset(info, 0, 0x2c); PadControllerInformation* base = static_cast(info); base->touchPadInfo.pixelDensity = 44.86f; @@ -173,7 +169,6 @@ int APS5_VABI scePadSetAngularVelocityDeadbandState(int handle, bool enable) { return 0; } -// ScePadLightBarParam: u8 r, g, b at +0..2. int APS5_VABI scePadSetLightBar(int handle, const PadLightBarParam* param) { if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; if (param == nullptr) return PAD_ERROR_INVALID_ARG; @@ -194,10 +189,6 @@ int APS5_VABI scePadSetTiltCorrectionState(int handle, bool enabled) { return 0; } -// ScePadTriggerEffectParam: u8 triggerMask (bit 0 = L2, bit 1 = R2), u8 padding[7], ScePadTriggerEffectCommandData command[2] (56 bytes each). -// Not one of the extended-info NIDs the audit called out by name, but wired to a real implementation here -// because it is the guest entry point for the adaptive-trigger plumbing added alongside it (Pad::SetTriggerCommand); -// leaving it NotImplemented would make the trigger effect state/output machinery unreachable from the guest. int APS5_VABI scePadSetTriggerEffect(int handle, const void* param) { if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; if (param == nullptr) return PAD_ERROR_INVALID_ARG; @@ -208,7 +199,6 @@ int APS5_VABI scePadSetTriggerEffect(int handle, const void* param) { } std::uint32_t mode[2] = {}; for (int trigger = 0; trigger < 2; ++trigger) std::memcpy(&mode[trigger], bytes + 8 + 56 * trigger, 4); - // Modes 0..6 are the SDK ScePadTriggerEffectMode values; anything else means the struct was misread. if (mode[0] > 6 || mode[1] > 6) { return PAD_ERROR_INVALID_ARG; } @@ -218,10 +208,6 @@ int APS5_VABI scePadSetTriggerEffect(int handle, const void* param) { return PAD_OK; } -// ScePadVibrationParam: u8 largeMotor at +0, u8 smallMotor at +1 (vibration-mode motors only; the trigger -// motors are driven through scePadSetTriggerEffect). -// Also not in the audit's named list, but wired here for the same reason as scePadSetTriggerEffect: it is -// the only guest entry point for Pad::SetVibration, which the rumble plumbing added alongside it needs. int APS5_VABI scePadSetVibration(int handle, const PadVibrationParam* param) { if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; if (param == nullptr) return PAD_ERROR_INVALID_ARG; @@ -229,8 +215,6 @@ int APS5_VABI scePadSetVibration(int handle, const PadVibrationParam* param) { return PAD_OK; } -// mode: 0 = desktop (USB) vibration layout, 1 = embedded controller layout. Both drive the same host rumble. -// Same rationale as scePadSetVibration: the only guest entry point for Pad::SetVibrationMode. int APS5_VABI scePadSetVibrationMode(int handle, int mode) { if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; if (mode != 0 && mode != 1) return PAD_ERROR_INVALID_ARG; diff --git a/core/libs/prx/libScePad/include/Pad.hpp b/core/libs/prx/libScePad/include/Pad.hpp index 6484c286e..2b4e32951 100644 --- a/core/libs/prx/libScePad/include/Pad.hpp +++ b/core/libs/prx/libScePad/include/Pad.hpp @@ -16,36 +16,30 @@ constexpr int PAD_HANDLE = 1; constexpr int PAD_CONNECTION_TYPE_LOCAL = 0; constexpr int PAD_DEVICE_CLASS_STANDARD = 0; -// ScePadDeviceType values reconstructed from DualSense HID telemetry; no current title queries them, -// they only describe the virtual device. constexpr int PAD_DEVICE_TYPE_NONE = 0; constexpr int PAD_DEVICE_TYPE_NAVIGATOR = 1; constexpr int PAD_DEVICE_TYPE_DUAL_SHOCK_4 = 2; constexpr int PAD_DEVICE_TYPE_DUAL_SENSE = 3; constexpr int PAD_DEVICE_TYPE_DUAL_SENSE_EDGE = 4; -// ScePadConnectType values from the PS4/PS5 SDK. constexpr int PAD_CONNECT_TYPE_UNREGISTERED = 0; constexpr int PAD_CONNECT_TYPE_USB = 1; constexpr int PAD_CONNECT_TYPE_BLUETOOTH = 2; -// Bluetooth public address of the virtual pad; DC:8C:37 is the Sony OUI a retail DualSense uses. inline constexpr std::uint8_t PAD_BD_ADDRESS[6] = {0xDC, 0x8C, 0x37, 0x51, 0x01, 0xF4}; -// Mirrors scePadGetInfo's ScePadInfo: per-port slot table of the pad service. The exact SDK field names -// are not public; this is the layout every out-param filler in this library writes and documents. struct PadInfo { - std::uint8_t maxConnectCount; // 0x00 - std::uint8_t connectedCount[2]; // 0x01 per personal port type (standard, special) - std::uint8_t reserved0[2]; // 0x03 + std::uint8_t maxConnectCount; + std::uint8_t connectedCount[2]; + std::uint8_t reserved0[2]; struct PadTypeInfo { - std::uint8_t connectPort; // index within the port type - std::uint8_t status; // 0 free, 1 connected - std::uint8_t deviceType; // PAD_DEVICE_TYPE_* - std::uint8_t connectType; // PAD_CONNECT_TYPE_* - std::uint8_t bluetoothMacAddress[6]; // empty unless status == 1 - } padTypeInfo[8]; // 0x05, 10 bytes per slot - std::uint8_t reserved1[4]; // 0x55 + std::uint8_t connectPort; + std::uint8_t status; + std::uint8_t deviceType; + std::uint8_t connectType; + std::uint8_t bluetoothMacAddress[6]; + } padTypeInfo[8]; + std::uint8_t reserved1[4]; }; static_assert(sizeof(PadInfo) == 0x59); diff --git a/core/libs/prx/libScePad/include/PadState.hpp b/core/libs/prx/libScePad/include/PadState.hpp index 090948961..e8c87359f 100644 --- a/core/libs/prx/libScePad/include/PadState.hpp +++ b/core/libs/prx/libScePad/include/PadState.hpp @@ -8,8 +8,8 @@ struct PadTouchPoint { bool active = false; - std::uint16_t x = 0; // 0..1919 - std::uint16_t y = 0; // 0..942 + std::uint16_t x = 0; + std::uint16_t y = 0; std::uint8_t id = 0; bool operator==(const PadTouchPoint&) const = default; @@ -22,32 +22,30 @@ struct PadInputState { std::uint8_t analogButtonsR2 = 0; bool touchLeft = false; bool touchRight = false; - // Host gamepad extras (all neutral when no gamepad is present). - bool hasMotion = false; // host pad delivers accelerometer + gyroscope - std::array accel{0.0f, 9.80665f, 0.0f}; // m/s^2, +Y up when the pad lies flat - std::array gyro{0.0f, 0.0f, 0.0f}; // rad/s - std::array touch{}; // host touchpad fingers - std::uint8_t deviceKind = 0; // 0 none, 1 DualSense, 2 DualShock 4, 3 other pad + bool hasMotion = false; + std::array accel{0.0f, 9.80665f, 0.0f}; + std::array gyro{0.0f, 0.0f, 0.0f}; + std::array touch{}; + std::uint8_t deviceKind = 0; }; -// Output requests issued by the guest (rumble, light bar, adaptive triggers), applied to the host pad by the window thread. struct PadTriggerRequest { - bool valid = false; // an effect other than "off" is engaged - std::uint8_t effect[11]{}; // DualSense native trigger effect block - std::uint8_t fallback = 0; // 0..255 rumble strength for pads without adaptive triggers - std::uint32_t mode = 0; // guest ScePadTriggerEffectMode + bool valid = false; + std::uint8_t effect[11]{}; + std::uint8_t fallback = 0; + std::uint32_t mode = 0; }; struct PadOutputState { - std::uint32_t sequence = 0; // bumped on every change + std::uint32_t sequence = 0; std::uint8_t vibrationLarge = 0; std::uint8_t vibrationSmall = 0; - bool lightBarValid = false; // false: default colour + bool lightBarValid = false; std::uint8_t lightBar[3]{}; - PadTriggerRequest trigger[2]; // 0 = L2, 1 = R2 - bool triggerTouched = false; // an effect struct was received at least once + PadTriggerRequest trigger[2]; + bool triggerTouched = false; bool motionEnabled = true; - int vibrationMode = 0; // last ScePadSetVibrationMode argument (0/r0 = desktop, 1/r1 = embedded) + int vibrationMode = 0; }; namespace Pad { @@ -56,7 +54,6 @@ PadData ReadState(); void SetVibration(std::uint8_t large, std::uint8_t small); void SetVibrationMode(int mode); void SetLightBar(bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b); -// trigger: 0 = L2, 1 = R2; command is a ScePadTriggerEffectCommandData (56 bytes: u32 mode, padding, command data at +8). void SetTriggerCommand(int trigger, const std::uint8_t* command); void ResetOrientation(); void SetMotionEnabled(bool enabled); @@ -64,7 +61,6 @@ void SetMotionEnabled(bool enabled); extern "C" void PadPublishInput_nid_postfix(const PadInputState& input); extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error); -// Copies the current output request when it differs from *seenSequence, updating it; returns true when a copy was made. extern "C" bool PadFetchOutput_nid_postfix(std::uint32_t* seenSequence, PadOutputState* out); #endif diff --git a/core/libs/prx/libScePad/src/PadState.cpp b/core/libs/prx/libScePad/src/PadState.cpp index f64b60723..0c3c9cc27 100644 --- a/core/libs/prx/libScePad/src/PadState.cpp +++ b/core/libs/prx/libScePad/src/PadState.cpp @@ -15,8 +15,6 @@ namespace { std::exception_ptr failure; bool initialized = false; - // Orientation fusion: gyro integration with accelerometer tilt correction (Mahony filter). The identity - // quaternion is the pad lying flat and face up, which is also what a pad without sensors reports. struct Quat { float w = 1.0f, x = 0.0f, y = 0.0f, z = 0.0f; }; Quat orientation; std::uint64_t lastFuseTime = 0; @@ -25,18 +23,17 @@ namespace { bool prevTouchActive[2] = {false, false}; std::uint8_t touchIds[2] = {0, 0}; - void Normalize(Quat& q) { + void normalize(Quat& q) { const float n = std::sqrt(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z); if (n < 1e-6f) { q = Quat{}; return; } q.w /= n; q.x /= n; q.y /= n; q.z /= n; } - void Fuse(float dt, const std::array& accel, const std::array& gyro) { + void fuse(float dt, const std::array& accel, const std::array& gyro) { float gx = gyro[0], gy = gyro[1], gz = gyro[2]; const float an = std::sqrt(accel[0] * accel[0] + accel[1] * accel[1] + accel[2] * accel[2]); if (an > 1.0f) { const float ax = accel[0] / an, ay = accel[1] / an, az = accel[2] / an; - // Gravity direction predicted by the current orientation, in the body frame (rest pose: +Y). const float vx = 2.0f * (orientation.x * orientation.y + orientation.w * orientation.z); const float vy = orientation.w * orientation.w - orientation.x * orientation.x + orientation.y * orientation.y - orientation.z * orientation.z; const float vz = 2.0f * (orientation.y * orientation.z - orientation.w * orientation.x); @@ -58,12 +55,10 @@ namespace { orientation.x = q.x + q.w * hx + q.y * hz - q.z * hy; orientation.y = q.y + q.w * hy - q.x * hz + q.z * hx; orientation.z = q.z + q.w * hz + q.x * hy - q.y * hx; - Normalize(orientation); + normalize(orientation); } - // DualSense native effect block: byte 0 = mode, [1..2] = active zone mask, [3..6] = 3-bit strength per zone, - // [9] = frequency (vibration modes). Mode 0x05 means "no effect". - void EncodeZones(std::uint8_t* dst, std::uint8_t mode, const std::uint8_t* strengths, int count, std::uint8_t frequency) { + void encodeZones(std::uint8_t* dst, std::uint8_t mode, const std::uint8_t* strengths, int count, std::uint8_t frequency) { std::uint16_t zones = 0; std::uint32_t packed = 0; for (int i = 0; i < count && i < 10; ++i) { @@ -80,13 +75,13 @@ namespace { dst[9] = frequency; } - std::uint8_t MaxOf(const std::uint8_t* v, int n) { + std::uint8_t maxOf(const std::uint8_t* v, int n) { std::uint8_t m = 0; for (int i = 0; i < n; ++i) m = std::max(m, v[i]); return m; } - std::uint8_t ScaleStrength(std::uint8_t s) { + std::uint8_t scaleStrength(std::uint8_t s) { return s == 0 ? 0 : static_cast(std::min(255, 32 * std::min(s, 8))); } } @@ -115,8 +110,6 @@ PadData Pad::ReadState() { data.analog_buttons_l2 = std::max(state.analogButtonsL2, (state.buttons & 0x100) != 0 ? 255 : 0); data.analog_buttons_r2 = std::max(state.analogButtonsR2, (state.buttons & 0x200) != 0 ? 255 : 0); - // Motion: real sensor data when the host pad has it and the guest enabled the sensors, otherwise the pad at rest - // (gravity on +Y, no rotation, identity orientation). const bool live = state.hasMotion && output.motionEnabled; const std::array rest{0.0f, 9.80665f, 0.0f}; const std::array& accel = live ? state.accel : rest; @@ -124,7 +117,7 @@ PadData Pad::ReadState() { if (live) { float dt = lastFuseTime != 0 && now > lastFuseTime ? static_cast(now - lastFuseTime) * 1e-6f : 0.0f; dt = std::min(dt, 0.1f); - Fuse(dt, accel, gyro); + fuse(dt, accel, gyro); } else { orientation = Quat{}; biasIntegral[0] = biasIntegral[1] = biasIntegral[2] = 0.0f; @@ -134,8 +127,6 @@ PadData Pad::ReadState() { data.orientation_y = orientation.y; data.orientation_z = orientation.z; data.orientation_w = orientation.w; - // ScePadData reports acceleration in units of g (rest = 1.0 on the "up" axis), not m/s^2; the fusion - // math above stays in m/s^2 because Fuse()'s free-fall gate assumes that scale. constexpr float kGravity = 9.80665f; data.acceleration_x = accel[0] / kGravity; data.acceleration_y = accel[1] / kGravity; @@ -148,7 +139,6 @@ PadData Pad::ReadState() { data.connected_count = 1; data.timestamp = timestamp; - // Touch: host touchpad fingers, plus the keyboard emulation (BACKSPACE / TAB) when no finger is down. std::uint8_t touchNum = 0; for (int i = 0; i < 2; ++i) { const PadTouchPoint& tp = state.touch[i]; @@ -206,14 +196,14 @@ void Pad::SetTriggerCommand(int trigger, const std::uint8_t* command) { req.mode = mode; req.effect[0] = 0x05; switch (mode) { - case 1: { // feedback { position, strength } + case 1: { std::uint8_t strengths[10] = {}; if (p[0] <= 9 && p[1] >= 1 && p[1] <= 8) for (int i = p[0]; i < 10; ++i) strengths[i] = p[1]; - EncodeZones(req.effect, 0x21, strengths, 10, 0); - req.fallback = ScaleStrength(p[1]); + encodeZones(req.effect, 0x21, strengths, 10, 0); + req.fallback = scaleStrength(p[1]); break; } - case 2: { // weapon { startPosition, endPosition, strength } + case 2: { if (p[0] >= 2 && p[0] <= 7 && p[1] > p[0] && p[1] <= 8 && p[2] >= 1 && p[2] <= 8) { const std::uint16_t zones = static_cast((1u << p[0]) | (1u << p[1])); req.effect[0] = 0x25; @@ -221,36 +211,36 @@ void Pad::SetTriggerCommand(int trigger, const std::uint8_t* command) { req.effect[2] = static_cast(zones >> 8); req.effect[3] = static_cast(p[2] - 1); } - req.fallback = ScaleStrength(p[2]); + req.fallback = scaleStrength(p[2]); break; } - case 3: { // vibration { position, amplitude, frequency } + case 3: { std::uint8_t strengths[10] = {}; if (p[0] <= 9 && p[1] >= 1 && p[1] <= 8) for (int i = p[0]; i < 10; ++i) strengths[i] = p[1]; - EncodeZones(req.effect, 0x26, strengths, 10, p[2]); - req.fallback = ScaleStrength(p[1]); + encodeZones(req.effect, 0x26, strengths, 10, p[2]); + req.fallback = scaleStrength(p[1]); break; } - case 4: // multi-position feedback { strength[10] } - EncodeZones(req.effect, 0x21, p, 10, 0); - req.fallback = ScaleStrength(MaxOf(p, 10)); + case 4: + encodeZones(req.effect, 0x21, p, 10, 0); + req.fallback = scaleStrength(maxOf(p, 10)); break; - case 5: { // slope feedback { startPosition, endPosition, startStrength, endStrength } + case 5: { std::uint8_t strengths[10] = {}; if (p[0] <= 8 && p[1] > p[0] && p[1] <= 9 && p[2] >= 1 && p[2] <= 8 && p[3] >= 1 && p[3] <= 8) { const int dist = p[1] - p[0]; for (int i = p[0]; i < 10; ++i) strengths[i] = i <= p[1] ? static_cast(std::lround(p[2] + (p[3] - p[2]) * (i - p[0]) / static_cast(dist))) : p[3]; } - EncodeZones(req.effect, 0x21, strengths, 10, 0); - req.fallback = ScaleStrength(std::max(p[2], p[3])); + encodeZones(req.effect, 0x21, strengths, 10, 0); + req.fallback = scaleStrength(std::max(p[2], p[3])); break; } - case 6: // multi-position vibration { frequency, amplitude[10] } - EncodeZones(req.effect, 0x26, p + 1, 10, p[0]); - req.fallback = p[0] == 0 ? 0 : ScaleStrength(MaxOf(p + 1, 10)); + case 6: + encodeZones(req.effect, 0x26, p + 1, 10, p[0]); + req.fallback = p[0] == 0 ? 0 : scaleStrength(maxOf(p + 1, 10)); break; - default: break; // 0 = off + default: break; } req.valid = req.effect[0] != 0x05; if (!req.valid) req.fallback = 0; @@ -276,9 +266,6 @@ void Pad::SetMotionEnabled(bool enabled) { extern "C" void PadPublishInput_nid_postfix(const PadInputState& input) { std::lock_guard lock(stateMutex); if (failure) std::rethrow_exception(failure); - // Change-detection kept from upstream, extended to the motion/touch fields: a pad with live sensors - // changes on essentially every call (so this is a no-op there), while a pad without one still only - // bumps the reported timestamp when something actually changed. if (state.buttons == input.buttons && state.sticks == input.sticks && state.analogButtonsL2 == input.analogButtonsL2 && state.analogButtonsR2 == input.analogButtonsR2 && state.touchLeft == input.touchLeft && state.touchRight == input.touchRight && diff --git a/core/libs/prx/libSceVideoOut/include/PadInput.hpp b/core/libs/prx/libSceVideoOut/include/PadInput.hpp index 8947e36a1..f0cbea89c 100644 --- a/core/libs/prx/libSceVideoOut/include/PadInput.hpp +++ b/core/libs/prx/libSceVideoOut/include/PadInput.hpp @@ -34,10 +34,8 @@ private: std::array mouseStick{128, 128}; std::chrono::steady_clock::time_point nextMousePoll{}; bool mouseEnabled = false; - // Host gamepad (Xbox / DualShock / DualSense / Switch Pro ... through SDL's GameController mapping database), merged with keyboard and mouse. SDL_GameController* controller = nullptr; PadInputState controllerState{}; - // Guest output requests (rumble, light bar, adaptive triggers) mirrored onto the host pad. std::uint32_t outputSequence = 0; PadOutputState outputState{}; bool outputPending = false; diff --git a/core/libs/prx/libSceVideoOut/src/PadInput.cpp b/core/libs/prx/libSceVideoOut/src/PadInput.cpp index bfd41de1e..7bf045fa0 100644 --- a/core/libs/prx/libSceVideoOut/src/PadInput.cpp +++ b/core/libs/prx/libSceVideoOut/src/PadInput.cpp @@ -24,13 +24,11 @@ PadInput::~PadInput() { void PadInput::openFirstAvailableController() { if (controller != nullptr) return; - // Read continuously (not just when the SDL hint is checked at subsystem init), so this is set every - // time regardless of whether the video driver already brought SDL_INIT_GAMECONTROLLER up. - SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); // keep reading the pad while the window is unfocused + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "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; // no controller support: keyboard and mouse keep working + return; } } for (int deviceIndex = 0; deviceIndex < SDL_NumJoysticks(); ++deviceIndex) { @@ -53,7 +51,7 @@ void PadInput::openController(int deviceIndex) { 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; // re-apply the current guest output request to the newly opened pad + outputPending = true; } void PadInput::enableSensors() { @@ -70,7 +68,6 @@ void PadInput::closeController() { controllerState = {}; } -// Pushes the rumble / light bar / trigger requests of the guest to the host pad. Runs on the window thread that owns the SDL joystick. void PadInput::applyOutput() { PadOutputState fetched; if (PadFetchOutput_nid_postfix(&outputSequence, &fetched)) { @@ -85,7 +82,6 @@ void PadInput::applyOutput() { const bool triggerRumble = outputState.trigger[0].fallback != 0 || outputState.trigger[1].fallback != 0; const bool isPs5 = SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_PS5; if (!outputPending) { - // Rumble requests expire host-side after a fixed time; keep them alive while the guest holds them. if ((rumbling || (triggerRumble && !isPs5)) && now >= nextRumbleRefresh) outputPending = true; else return; } @@ -99,7 +95,6 @@ void PadInput::applyOutput() { } if (outputState.triggerTouched) { if (isPs5) { - // DS5EffectsState: enable bits 0x04 (right) / 0x08 (left) at byte 0, right effect block at +10, left effect block at +21 (47 bytes). Uint8 effect[47] = {}; effect[0] = 0x04 | 0x08; std::memcpy(effect + 10, outputState.trigger[1].effect, 11); @@ -121,14 +116,9 @@ void PadInput::setMouseMode(bool enabled) { nextMousePoll = std::chrono::steady_clock::now() + std::chrono::milliseconds(Pad::MousePollIntervalMs); } -// SDL reports buttons by physical position (A = bottom face button), which on a PlayStation layout is Cross, so the mapping below is the -// PlayStation one for every pad type; a Switch-style layout is normalised by SDL's own mapping database. PadInputState PadInput::sampleController() const { PadInputState result; if (controller == nullptr) return result; - // Button/trigger/stick mapping and scaling kept identical to the pre-existing gamepad support - // (SDL_GameControllerGetAttached block previously inlined in publish()); only motion, touchpad - // finger tracking, and device-kind detection are new here. const auto readButton = [this](SDL_GameControllerButton button) { return SDL_GameControllerGetButton(controller, button) != 0; }; @@ -267,7 +257,6 @@ void PadInput::Update() { if (controller != nullptr) SDL_GameControllerUpdate(); applyOutput(); if (controller != nullptr) { - // Sensors change on every sample, so a pad with motion republishes continuously. controllerState = sampleController(); publish(); } @@ -304,8 +293,6 @@ void PadInput::Update() { } void PadInput::publish() { - // Gamepad is the base layer (like a title reading a real DualSense); keyboard and mouse bindings are - // applied on top and win when they conflict with the gamepad, matching the pre-existing keyboard/mouse behaviour. PadInputState state; state.buttons = controllerState.buttons; state.sticks = controllerState.sticks;