From 264e9ccf8ec3501240315167a86f380cfe139795 Mon Sep 17 00:00:00 2001 From: danprice142 Date: Thu, 30 Oct 2025 21:31:49 +0000 Subject: [PATCH 01/12] SDL3 Add DSU joystick driver Rebased on latest upstream --- CMakeLists.txt | 19 + include/SDL3/SDL_hints.h | 50 ++ include/build_config/SDL_build_config.h.cmake | 1 + .../build_config/SDL_build_config_windows.h | 1 + src/joystick/SDL_joystick.c | 7 + src/joystick/SDL_sysjoystick.h | 1 + src/joystick/dsu/SDL_dsujoystick.c | 550 ++++++++++++ src/joystick/dsu/SDL_dsujoystick_c.h | 165 ++++ src/joystick/dsu/SDL_dsujoystick_driver.c | 836 ++++++++++++++++++ src/joystick/dsu/SDL_dsuprotocol.h | 201 +++++ 10 files changed, 1831 insertions(+) create mode 100644 src/joystick/dsu/SDL_dsujoystick.c create mode 100644 src/joystick/dsu/SDL_dsujoystick_c.h create mode 100644 src/joystick/dsu/SDL_dsujoystick_driver.c create mode 100644 src/joystick/dsu/SDL_dsuprotocol.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7472137a2d006..79999826e2c41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -380,6 +380,7 @@ dep_option(SDL_HIDAPI_LIBUSB "Use libusb for low level joystick drivers" O dep_option(SDL_HIDAPI_LIBUSB_SHARED "Dynamically load libusb support" ON "SDL_HIDAPI_LIBUSB;SDL_DEPS_SHARED" OFF) dep_option(SDL_HIDAPI_JOYSTICK "Use HIDAPI for low level joystick drivers" ON SDL_HIDAPI OFF) dep_option(SDL_VIRTUAL_JOYSTICK "Enable the virtual-joystick driver" ON SDL_HIDAPI OFF) +option(SDL_DSU_JOYSTICK "Enable DSU client joystick support" ON) set_option(SDL_LIBUDEV "Enable libudev support" ON) set_option(SDL_ASAN "Use AddressSanitizer to detect memory errors" OFF) set_option(SDL_CCACHE "Use Ccache to speed up build" OFF) @@ -1374,6 +1375,24 @@ if(SDL_JOYSTICK) "${SDL3_SOURCE_DIR}/src/joystick/virtual/*.h" ) endif() + + # DSU (DualShock UDP) client support + if(SDL_DSU_JOYSTICK) + set(SDL_JOYSTICK_DSU 1) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.h" + ) + + # DSU requires network libraries + if(WIN32) + list(APPEND SDL3_EXTRA_LIBS ws2_32) + elseif(UNIX AND NOT APPLE AND NOT ANDROID AND NOT HAIKU) + # Unix systems typically have sockets built-in + elseif(HAIKU) + list(APPEND SDL3_EXTRA_LIBS network) + endif() + endif() endif() if(SDL_VIDEO) diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 972e0b7e782a1..fb7295d928022 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -2319,6 +2319,56 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_WGI "SDL_JOYSTICK_WGI" +/** + * A variable controlling whether the DSU (DualShock UDP) joystick driver should be used. + * + * This variable can be set to the following values: + * + * - "0": DSU driver is disabled + * - "1": DSU driver is enabled (default) + * + * The DSU driver allows SDL to connect to DSU servers (DS4Windows, BetterJoy, etc.) + * to receive controller data over UDP, including motion sensors and touchpad data. + * + * This hint should be set before SDL is initialized. + * + * \since This hint is available since SDL 3.2.0. + */ +#define SDL_HINT_JOYSTICK_DSU "SDL_JOYSTICK_DSU" + +/** + * A variable controlling the DSU server address. + * + * The default value is "127.0.0.1" + * + * This hint should be set before SDL is initialized. + * + * \since This hint is available since SDL 3.2.0. + */ +#define SDL_HINT_DSU_SERVER "SDL_DSU_SERVER" + +/** + * A variable controlling the DSU server port. + * + * The default value is "26760" + * + * This hint should be set before SDL is initialized. + * + * \since This hint is available since SDL 3.2.0. + */ +#define SDL_HINT_DSU_SERVER_PORT "SDL_DSU_SERVER_PORT" + +/** + * A variable controlling the DSU client port. + * + * The default value is "0" (auto-select) + * + * This hint should be set before SDL is initialized. + * + * \since This hint is available since SDL 3.2.0. + */ +#define SDL_HINT_DSU_CLIENT_PORT "SDL_DSU_CLIENT_PORT" + /** * A variable containing a list of wheel style controllers. * diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 35560da940507..7a8ad4cacea56 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -313,6 +313,7 @@ #cmakedefine SDL_JOYSTICK_RAWINPUT 1 #cmakedefine SDL_JOYSTICK_USBHID 1 #cmakedefine SDL_JOYSTICK_VIRTUAL 1 +#cmakedefine SDL_JOYSTICK_DSU 1 #cmakedefine SDL_JOYSTICK_VITA 1 #cmakedefine SDL_JOYSTICK_WGI 1 #cmakedefine SDL_JOYSTICK_XINPUT 1 diff --git a/include/build_config/SDL_build_config_windows.h b/include/build_config/SDL_build_config_windows.h index 7872d2a92869c..62e646934263c 100644 --- a/include/build_config/SDL_build_config_windows.h +++ b/include/build_config/SDL_build_config_windows.h @@ -231,6 +231,7 @@ typedef unsigned int uintptr_t; #define SDL_JOYSTICK_HIDAPI 1 #define SDL_JOYSTICK_RAWINPUT 1 #define SDL_JOYSTICK_VIRTUAL 1 +#define SDL_JOYSTICK_DSU 1 #ifdef HAVE_WINDOWS_GAMING_INPUT_H #define SDL_JOYSTICK_WGI 1 #endif diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index 1841341831ef1..fcf0777ff247a 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -48,6 +48,10 @@ #include "./virtual/SDL_virtualjoystick_c.h" #endif +#ifdef SDL_JOYSTICK_DSU +#include "./dsu/SDL_dsujoystick_c.h" +#endif + static SDL_JoystickDriver *SDL_joystick_drivers[] = { #ifdef SDL_JOYSTICK_HIDAPI // Highest priority driver for supported devices &SDL_HIDAPI_JoystickDriver, @@ -100,6 +104,9 @@ static SDL_JoystickDriver *SDL_joystick_drivers[] = { #ifdef SDL_JOYSTICK_VIRTUAL &SDL_VIRTUAL_JoystickDriver, #endif +#ifdef SDL_JOYSTICK_DSU + &SDL_DSU_JoystickDriver, +#endif #ifdef SDL_JOYSTICK_VITA &SDL_VITA_JoystickDriver, #endif diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h index 041ebc3b50903..450b8454b7107 100644 --- a/src/joystick/SDL_sysjoystick.h +++ b/src/joystick/SDL_sysjoystick.h @@ -255,6 +255,7 @@ extern SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver; extern SDL_JoystickDriver SDL_IOS_JoystickDriver; extern SDL_JoystickDriver SDL_LINUX_JoystickDriver; extern SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver; +extern SDL_JoystickDriver SDL_DSU_JoystickDriver; extern SDL_JoystickDriver SDL_WGI_JoystickDriver; extern SDL_JoystickDriver SDL_WINDOWS_JoystickDriver; extern SDL_JoystickDriver SDL_WINMM_JoystickDriver; diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c new file mode 100644 index 0000000000000..ff7e1f451ae8b --- /dev/null +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -0,0 +1,550 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_DSU + +/* DSU (DualShock UDP) client joystick driver - Main Implementation */ + +/* Include system joystick headers */ +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" + +/* Define this before including our header to get internal definitions */ +#define IN_JOYSTICK_DSU_ + +/* Include our header to get structure definitions */ +#include "SDL_dsujoystick_c.h" + +/* Additional Windows headers */ +#ifdef _WIN32 +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +/* Define socklen_t for MinGW/other Windows compilers if needed */ +#ifndef _MSC_VER +#ifndef socklen_t +typedef int socklen_t; +#endif +#endif +#endif + +/* Ensure timer prototypes are visible */ +#include + +/* Platform-specific socket includes */ +#ifndef _WIN32 + #include + #include + #include + #include + #include + #include + #define closesocket close +#endif + +#include + +/* Constants */ +#define SERVER_REREGISTER_INTERVAL 1000 /* ms */ +#define SERVER_TIMEOUT_INTERVAL 2000 /* ms */ +#define GRAVITY_ACCELERATION 9.80665f /* m/s² */ + +/* Global DSU context is defined in SDL_dsujoystick_driver.c */ +extern DSU_Context *s_dsu_ctx; + +/* Use the DSU_Context type from the shared header */ + +/* Forward declarations */ +void DSU_RequestControllerInfo(DSU_Context *ctx, Uint8 slot); +void DSU_RequestControllerData(DSU_Context *ctx, Uint8 slot); + +/* Socket helpers implementation */ +int DSU_InitSockets(void) +{ +#ifdef _WIN32 + WSADATA wsaData; + return WSAStartup(MAKEWORD(2, 2), &wsaData); +#else + return 0; +#endif +} + +void DSU_QuitSockets(void) +{ +#ifdef _WIN32 + WSACleanup(); +#endif +} + +dsu_socket_t DSU_CreateSocket(Uint16 port) +{ + dsu_socket_t sock; + struct sockaddr_in addr; + int reuse = 1; +#ifdef _WIN32 + u_long mode = 1; +#else + int flags; +#endif + + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == DSU_INVALID_SOCKET) { + return DSU_INVALID_SOCKET; + } + + /* Allow address reuse */ + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)); + + /* Set socket to non-blocking */ +#ifdef _WIN32 + ioctlsocket(sock, FIONBIO, &mode); +#else + flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); +#endif + + /* Bind to client port if specified */ + if (port != 0) { + SDL_memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + /* Bind failure is not fatal, continue anyway */ + } + } + + return sock; +} + +void DSU_CloseSocket(dsu_socket_t socket) +{ + if (socket != DSU_INVALID_SOCKET) { + closesocket(socket); + } +} + +/* Complete CRC32 table */ +static const Uint32 crc32_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +Uint32 DSU_CalculateCRC32(const Uint8 *data, size_t length) +{ + Uint32 crc = 0xFFFFFFFF; + size_t i; + + for (i = 0; i < length; i++) { + crc = crc32_table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8); + } + + return crc ^ 0xFFFFFFFF; +} + +/* Send a packet to the DSU server */ +static int DSU_SendPacket(DSU_Context *ctx, void *packet, size_t size) +{ + DSU_Header *header; + struct sockaddr_in server; + int result; + + header = (DSU_Header *)packet; + + /* Fill header */ + SDL_memcpy(header->magic, DSU_MAGIC_CLIENT, 4); + header->version = SDL_Swap16LE(DSU_PROTOCOL_VERSION); + header->length = SDL_Swap16LE((Uint16)(size - sizeof(DSU_Header))); + header->client_id = SDL_Swap32LE(ctx->client_id); + header->crc32 = 0; + + /* Calculate and store CRC32 */ + header->crc32 = SDL_Swap32LE(DSU_CalculateCRC32((const Uint8 *)packet, size)); + + /* Send to server */ + SDL_memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_port = DSU_htons(ctx->server_port); + server.sin_addr.s_addr = DSU_ipv4_addr(ctx->server_address); + + result = (sendto)(ctx->socket, (const char*)packet, (int)size, 0, + (struct sockaddr *)&server, (int)sizeof(server)); + + if (result < 0) { +#ifdef _WIN32 + int err = WSAGetLastError(); + SDL_Log("DSU: sendto failed with error %d\n", err); +#else + SDL_Log("DSU: sendto failed with errno %d\n", errno); +#endif + } + + return result; +} + +/* Request controller information */ +void DSU_RequestControllerInfo(DSU_Context *ctx, Uint8 slot) +{ + DSU_PortRequest request; + + SDL_memset(&request, 0, sizeof(request)); + request.header.message_type = SDL_Swap32LE(DSU_MSG_PORTS_INFO); + request.flags = 0; + request.slot_id = slot; /* 0xFF for all slots */ + /* MAC is zeros for all controllers */ + + SDL_Log("DSU: Requesting controller info for slot %d\n", slot); + DSU_SendPacket(ctx, &request, sizeof(request)); +} + +/* Request controller data */ +void DSU_RequestControllerData(DSU_Context *ctx, Uint8 slot) +{ + DSU_PortRequest request; + + SDL_memset(&request, 0, sizeof(request)); + request.header.message_type = SDL_Swap32LE(DSU_MSG_DATA); + request.flags = 0; /* Subscribe to data */ + request.slot_id = slot; + + SDL_Log("DSU: Subscribing to data for slot %d\n", slot); + DSU_SendPacket(ctx, &request, sizeof(request)); +} + +/* Process incoming controller data */ +static void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data) +{ + DSU_ControllerSlot *slot; + int slot_id; + bool was_connected; + + /* Validate context */ + if (!ctx || !ctx->slots_mutex) { + SDL_Log("DSU: Invalid context or mutex in ProcessControllerData\n"); + return; + } + + /* Get slot ID */ + slot_id = data->info.slot; + SDL_Log("DSU: Raw slot_id from packet: %d (max=%d)\n", slot_id, DSU_MAX_SLOTS); + if (slot_id >= DSU_MAX_SLOTS) { + SDL_Log("DSU: Invalid slot_id %d in data packet\n", slot_id); + return; + } + + SDL_Log("DSU: Processing data for slot %d\n", slot_id); + + if (!ctx->slots_mutex) { + SDL_Log("DSU: ERROR - slots_mutex is NULL!\n"); + return; + } + + SDL_LockMutex(ctx->slots_mutex); + SDL_Log("DSU: Mutex locked, accessing slot %d\n", slot_id); + slot = &ctx->slots[slot_id]; + SDL_Log("DSU: Got slot pointer for slot %d\n", slot_id); + + SDL_Log("DSU: Slot %d state: detected=%d connected=%d instance_id=%d\n", + slot_id, slot->detected, slot->connected, (int)slot->instance_id); + + /* If already connected to SDL, just update data without changing state */ + if (slot->connected) { + SDL_Log("DSU: Slot %d already connected, updating data only\n", slot_id); + was_connected = true; + } else { + /* Update connection state */ + was_connected = slot->detected; + slot->detected = (data->info.slot_state == DSU_STATE_CONNECTED); + SDL_Log("DSU: Slot %d state updated: was_connected=%d detected=%d\n", + slot_id, was_connected, slot->detected); + } + + if (slot->detected || slot->connected) { + /* Update controller info */ + SDL_memcpy(slot->mac, data->info.mac, 6); + slot->battery = data->info.battery; + slot->model = data->info.device_model; + slot->connection = data->info.connection_type; + slot->slot_id = slot_id; + + /* Generate name */ + SDL_snprintf(slot->name, sizeof(slot->name), "DSUClient/%d", slot_id); + + /* Update button states */ + slot->buttons = 0; + + /* Map DSU buttons to SDL buttons */ + if (data->button_states_2 & DSU_BUTTON_CROSS) slot->buttons |= (1 << 0); + if (data->button_states_2 & DSU_BUTTON_CIRCLE) slot->buttons |= (1 << 1); + if (data->button_states_2 & DSU_BUTTON_SQUARE) slot->buttons |= (1 << 2); + if (data->button_states_2 & DSU_BUTTON_TRIANGLE) slot->buttons |= (1 << 3); + if (data->button_states_2 & DSU_BUTTON_L1) slot->buttons |= (1 << 4); + if (data->button_states_2 & DSU_BUTTON_R1) slot->buttons |= (1 << 5); + if (data->button_states_1 & DSU_BUTTON_SHARE) slot->buttons |= (1 << 6); + if (data->button_states_1 & DSU_BUTTON_OPTIONS) slot->buttons |= (1 << 7); + if (data->button_states_1 & DSU_BUTTON_L3) slot->buttons |= (1 << 8); + if (data->button_states_1 & DSU_BUTTON_R3) slot->buttons |= (1 << 9); + if (data->button_ps) slot->buttons |= (1 << 10); + if (data->button_touch) slot->buttons |= (1 << 11); + + /* Update analog sticks */ + slot->axes[0] = ((Sint16)data->left_stick_x - 128) * 257; + slot->axes[1] = ((Sint16)data->left_stick_y - 128) * -257; + slot->axes[2] = ((Sint16)data->right_stick_x - 128) * 257; + slot->axes[3] = ((Sint16)data->right_stick_y - 128) * -257; + + /* Triggers */ + slot->axes[4] = ((Sint16)data->analog_trigger_l2) * 128; + slot->axes[5] = ((Sint16)data->analog_trigger_r2) * 128; + + /* D-Pad as hat */ + slot->hat = SDL_HAT_CENTERED; + if (data->button_states_1 & DSU_BUTTON_DPAD_UP) slot->hat |= SDL_HAT_UP; + if (data->button_states_1 & DSU_BUTTON_DPAD_DOWN) slot->hat |= SDL_HAT_DOWN; + if (data->button_states_1 & DSU_BUTTON_DPAD_LEFT) slot->hat |= SDL_HAT_LEFT; + if (data->button_states_1 & DSU_BUTTON_DPAD_RIGHT) slot->hat |= SDL_HAT_RIGHT; + + /* Motion data */ + if (data->motion_timestamp != 0) { + slot->has_gyro = true; + slot->has_accel = true; + slot->motion_timestamp = SDL_Swap64LE(data->motion_timestamp); + + /* Convert gyro from deg/s to rad/s (handling endianness) */ + slot->gyro[0] = SDL_SwapFloatLE(data->gyro_pitch) * (SDL_PI_F / 180.0f); + slot->gyro[1] = SDL_SwapFloatLE(data->gyro_yaw) * (SDL_PI_F / 180.0f); + slot->gyro[2] = SDL_SwapFloatLE(data->gyro_roll) * (SDL_PI_F / 180.0f); + + /* Convert accel from g to m/s² (handling endianness) */ + slot->accel[0] = SDL_SwapFloatLE(data->accel_x) * GRAVITY_ACCELERATION; + slot->accel[1] = SDL_SwapFloatLE(data->accel_y) * GRAVITY_ACCELERATION; + slot->accel[2] = SDL_SwapFloatLE(data->accel_z) * GRAVITY_ACCELERATION; + } + + /* Update last packet time */ + slot->last_packet_time = SDL_GetTicks(); + + /* Touch data */ + slot->has_touchpad = true; + slot->touch1_active = data->touch1_active; + slot->touch2_active = data->touch2_active; + slot->touch1_id = data->touch1_id; + slot->touch2_id = data->touch2_id; + slot->touch1_x = SDL_Swap16LE(data->touch1_x); + slot->touch1_y = SDL_Swap16LE(data->touch1_y); + slot->touch2_x = SDL_Swap16LE(data->touch2_x); + slot->touch2_y = SDL_Swap16LE(data->touch2_y); + + /* Update timing */ + slot->last_packet_time = SDL_GetTicks(); + slot->packet_number = SDL_Swap32LE(data->packet_number); + } + + /* Handle connection state changes (before unlock) */ + if (!was_connected && slot->detected) { + Uint16 vendor; + Uint16 product; + + /* New controller connected */ + slot->instance_id = SDL_GetNextObjectID(); + SDL_Log("DSU: Generated new instance_id %d for slot %d\n", (int)slot->instance_id, slot_id); + + /* Update controller ID for SDL */ + vendor = 0x054C; /* Sony vendor ID */ + product = 0x05C4; /* DS4 product ID by default */ + if (slot->model == DSU_MODEL_FULL_GYRO) { + product = 0x09CC; + } + slot->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, 0, + NULL, slot->name, 'd', 0); + + /* Mark for deferred add (SDL_PrivateJoystickAdded requires joystick lock) */ + slot->pending_add = true; + + SDL_Log("DSU: Controller connected on slot %d, instance %d, will notify SDL\n", + slot_id, (int)slot->instance_id); + } + + SDL_Log("DSU: About to unlock mutex for slot %d\n", slot_id); + SDL_UnlockMutex(ctx->slots_mutex); + SDL_Log("DSU: Unlocked mutex for slot %d\n", slot_id); + + /* Subscribe to controller data updates if just detected */ + if (!was_connected && slot->detected) { + DSU_RequestControllerData(ctx, slot_id); + } + SDL_Log("DSU: Finished processing data for slot %d\n", slot_id); +} + +/* Receiver thread implementation */ +int SDLCALL DSU_ReceiverThread(void *data) +{ + DSU_Context *ctx = (DSU_Context *)data; + Uint8 buffer[1024]; + struct sockaddr_in sender; + socklen_t sender_len = sizeof(sender); + DSU_Header *header; + int received; + + SDL_Log("DSU: Receiver thread starting\n"); + SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH); + + /* Main receive loop */ + while (SDL_GetAtomicInt(&ctx->running)) { + /* Double-check context is still valid */ + if (!ctx->slots_mutex) { + SDL_Log("DSU: Receiver thread exiting - mutex destroyed\n"); + break; + } + received = recvfrom(ctx->socket, (char*)buffer, sizeof(buffer), 0, + (struct sockaddr *)&sender, &sender_len); + + if (received > (int)sizeof(DSU_Header)) { + header = (DSU_Header *)buffer; + + /* Validate magic */ + if (SDL_memcmp(header->magic, DSU_MAGIC_SERVER, 4) == 0) { + Uint32 received_crc; + Uint32 calculated_crc; + + /* Validate CRC32 */ + received_crc = SDL_Swap32LE(header->crc32); + header->crc32 = 0; + calculated_crc = DSU_CalculateCRC32(buffer, received); + + if (received_crc == calculated_crc) { + Uint32 msg_type = SDL_Swap32LE(header->message_type); + + switch (msg_type) { + case DSU_MSG_VERSION: + /* Version info received */ + break; + + case DSU_MSG_PORTS_INFO: { + /* Port info response - tells us which slots have controllers */ + if (received >= (int)(sizeof(DSU_Header) + 4)) { + Uint8 *data_ptr; + Uint8 slot_id; + Uint8 slot_state; + + /* Parse port info */ + data_ptr = buffer + sizeof(DSU_Header); + slot_id = data_ptr[0]; + slot_state = data_ptr[1]; + /* Skip device_model = data_ptr[2] and connection_type = data_ptr[3] - not used */ + + SDL_Log("DSU: Port info - slot %d state %d\n", slot_id, slot_state); + + /* If controller is connected in this slot, request data */ + if (slot_state == DSU_STATE_CONNECTED && slot_id < DSU_MAX_SLOTS) { + DSU_RequestControllerData(ctx, slot_id); + } + } + break; + } + + case DSU_MSG_DATA: + /* Controller data */ + if (received >= (int)sizeof(DSU_ControllerData)) { + DSU_ControllerData *data = (DSU_ControllerData *)buffer; + SDL_Log("DSU: Data packet received for slot %d\n", data->info.slot); + DSU_ProcessControllerData(ctx, data); + } + break; + + default: + /* Unknown message type */ + break; + } + }; + } + } else if (received < 0) { + /* Check for real errors (not just EWOULDBLOCK) */ +#ifdef _WIN32 + int error = WSAGetLastError(); + if (error == WSAENOTSOCK || error == WSAEBADF) { + /* Socket closed, exit gracefully */ + SDL_Log("DSU: Socket closed, receiver thread exiting\n"); + break; + } + if (error != WSAEWOULDBLOCK && error != WSAEINTR && error != WSAECONNRESET) { + SDL_Log("DSU: recvfrom error %d\n", error); + SDL_Delay(100); /* Back off on errors */ + } +#else + if (errno == EBADF) { + /* Socket closed, exit gracefully */ + SDL_Log("DSU: Socket closed, receiver thread exiting\n"); + break; + } + if (errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) { + SDL_Log("DSU: recvfrom errno %d\n", errno); + SDL_Delay(100); + } +#endif + } + + /* Small delay to prevent CPU spinning */ + SDL_Delay(1); + } + + SDL_Log("DSU: Receiver thread exiting cleanly\n"); + return 0; +} + +#endif /* SDL_JOYSTICK_DSU */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/joystick/dsu/SDL_dsujoystick_c.h b/src/joystick/dsu/SDL_dsujoystick_c.h new file mode 100644 index 0000000000000..abb84bb91415e --- /dev/null +++ b/src/joystick/dsu/SDL_dsujoystick_c.h @@ -0,0 +1,165 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_dsujoystick_c_h_ +#define SDL_dsujoystick_c_h_ + +#include "SDL_internal.h" + +#ifdef SDL_JOYSTICK_DSU + +#include +#include +#include +#include +#include "../SDL_sysjoystick.h" +#include "SDL_dsuprotocol.h" + +/* DSU Joystick driver */ +extern SDL_JoystickDriver SDL_DSU_JoystickDriver; + +/* Socket type definitions - move these out to ensure visibility */ +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +typedef SOCKET dsu_socket_t; +#else +typedef int dsu_socket_t; +#endif + +/* Internal structures - always visible */ +typedef struct DSU_ControllerSlot { + /* Connection state */ + bool detected; /* Controller detected by DSU but not yet added to SDL */ + bool connected; /* Controller added to SDL and visible */ + SDL_JoystickID instance_id; + SDL_GUID guid; + char name[128]; + + /* DSU protocol data */ + Uint8 slot_id; + Uint8 mac[6]; + Uint8 battery; + DSU_DeviceModel model; + DSU_ConnectionType connection; + + /* Controller state */ + Uint16 buttons; + Sint16 axes[6]; /* LX, LY, RX, RY, L2, R2 */ + Uint8 hat; + + /* Motion data */ + bool has_gyro; + bool has_accel; + float gyro[3]; /* Pitch, Yaw, Roll in rad/s */ + float accel[3]; /* X, Y, Z in m/s² */ + Uint64 motion_timestamp; + + /* Touch data */ + bool has_touchpad; + bool touch1_active; + bool touch2_active; + Uint8 touch1_id; + Uint8 touch2_id; + Uint16 touch1_x, touch1_y; + Uint16 touch2_x, touch2_y; + + /* Timing */ + Uint64 last_packet_time; + Uint32 packet_number; + + /* State change flags for deferred notifications */ + bool pending_add; +} DSU_ControllerSlot; + +typedef struct DSU_Context_t { + /* Network */ + dsu_socket_t socket; + SDL_Thread *receiver_thread; + SDL_AtomicInt running; + + /* Server configuration */ + char server_address[256]; + Uint16 server_port; + Uint16 client_port; + Uint32 client_id; + + /* Controller slots (4 max per DSU protocol) */ + DSU_ControllerSlot slots[DSU_MAX_SLOTS]; + SDL_Mutex *slots_mutex; + + /* Timing for periodic updates */ + Uint64 last_request_time; +} DSU_Context; + +/* Global DSU context */ +extern DSU_Context *s_dsu_ctx; + +/* Socket helpers - only available when implementing */ +#ifdef IN_JOYSTICK_DSU_ + +#ifdef __cplusplus +extern "C" { +#endif + +static SDL_FORCE_INLINE Uint16 DSU_htons(Uint16 x) { return SDL_Swap16BE(x); } +static SDL_FORCE_INLINE Uint32 DSU_htonl(Uint32 x) { return SDL_Swap32BE(x); } +static SDL_FORCE_INLINE Uint32 DSU_ipv4_addr(const char *ip) +{ + unsigned int a, b, c, d; + if (SDL_sscanf(ip, "%u.%u.%u.%u", &a, &b, &c, &d) == 4) { + Uint32 v = ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) | (d & 0xFF); + return DSU_htonl(v); + } + /* Fallback to 127.0.0.1 */ + return DSU_htonl(0x7F000001u); +} + +#ifdef __cplusplus +} +#endif + +#ifdef _WIN32 +#define DSU_SOCKET_ERROR SOCKET_ERROR +#define DSU_INVALID_SOCKET INVALID_SOCKET +#else +#define DSU_SOCKET_ERROR -1 +#define DSU_INVALID_SOCKET -1 +#endif + +/* Socket helpers */ +int DSU_InitSockets(void); +void DSU_QuitSockets(void); +dsu_socket_t DSU_CreateSocket(Uint16 port); +void DSU_CloseSocket(dsu_socket_t socket); + +/* CRC32 calculation */ +Uint32 DSU_CalculateCRC32(const Uint8 *data, size_t length); + +#endif /* IN_JOYSTICK_DSU_ */ + +#endif /* SDL_JOYSTICK_DSU */ + +#endif /* SDL_dsujoystick_c_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/joystick/dsu/SDL_dsujoystick_driver.c b/src/joystick/dsu/SDL_dsujoystick_driver.c new file mode 100644 index 0000000000000..b35af5428a290 --- /dev/null +++ b/src/joystick/dsu/SDL_dsujoystick_driver.c @@ -0,0 +1,836 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* Include Windows socket headers before SDL to avoid macro conflicts */ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +#endif + +#include "SDL_internal.h" + +/* Define this before including our header to get internal definitions */ +#define IN_JOYSTICK_DSU_ + +/* Include protocol definitions and shared DSU header for types */ +#include "SDL_dsuprotocol.h" +#include "SDL_dsujoystick_c.h" + +#ifdef SDL_JOYSTICK_DSU + +/* Forward declare the driver */ +extern SDL_JoystickDriver SDL_DSU_JoystickDriver; + +/* Then include system joystick headers */ +#include "../SDL_sysjoystick.h" +#include "../SDL_joystick_c.h" +/* Ensure timer prototypes are visible for SDL_GetTicks64 */ +#include + +/* Additional Windows headers already included at the top */ +#ifdef _WIN32 +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif + +/* Global DSU context pointer */ +struct DSU_Context_t *s_dsu_ctx = NULL; + +/* Helper function to work around macro issues */ +static SDL_INLINE SDL_Mutex* GetDSUMutex(void) { + struct DSU_Context_t *ctx = s_dsu_ctx; + return ctx ? ctx->slots_mutex : NULL; +} + +/* Forward declarations */ +extern int SDLCALL DSU_ReceiverThread(void *data); +extern void DSU_RequestControllerInfo(struct DSU_Context_t *ctx, Uint8 slot); +extern void DSU_RequestControllerData(struct DSU_Context_t *ctx, Uint8 slot); + +/* Socket helpers */ +extern int DSU_InitSockets(void); +extern void DSU_QuitSockets(void); +extern dsu_socket_t DSU_CreateSocket(Uint16 port); +extern void DSU_CloseSocket(dsu_socket_t socket); +extern Uint32 DSU_CalculateCRC32(const Uint8 *data, size_t length); + +/* Driver functions */ +static bool DSU_JoystickInit(void) +{ + const char *enabled; + const char *server; + const char *server_port; + const char *client_port; + struct DSU_Context_t *ctx; + + /* Check if DSU is enabled */ + enabled = SDL_GetHint(SDL_HINT_JOYSTICK_DSU); + if (enabled && SDL_atoi(enabled) == 0) { + return true; /* DSU disabled */ + } + + /* Allocate context */ + ctx = (struct DSU_Context_t *)SDL_calloc(1, sizeof(struct DSU_Context_t)); + if (!ctx) { + SDL_OutOfMemory(); + return false; + } + + /* Get configuration from hints with fallbacks */ + server = SDL_GetHint(SDL_HINT_DSU_SERVER); + if (!server || !*server) { + server = DSU_SERVER_ADDRESS_DEFAULT; + } + SDL_strlcpy(ctx->server_address, server, + sizeof(ctx->server_address)); + + server_port = SDL_GetHint(SDL_HINT_DSU_SERVER_PORT); + if (server_port && *server_port) { + ctx->server_port = SDL_atoi(server_port); + } else { + ctx->server_port = DSU_SERVER_PORT_DEFAULT; + } + + client_port = SDL_GetHint(SDL_HINT_DSU_CLIENT_PORT); + if (client_port && *client_port) { + ctx->client_port = SDL_atoi(client_port); + } else { + ctx->client_port = DSU_CLIENT_PORT_DEFAULT; + } + + ctx->client_id = (Uint32)SDL_GetTicks(); + + /* Initialize sockets */ + if (DSU_InitSockets() != 0) { + SDL_free(ctx); + return false; + } + + /* Create UDP socket */ + ctx->socket = DSU_CreateSocket(ctx->client_port); + if (ctx->socket == DSU_INVALID_SOCKET) { + DSU_QuitSockets(); + SDL_free(ctx); + return false; + } + + /* Create mutex */ + ctx->slots_mutex = SDL_CreateMutex(); + if (!ctx->slots_mutex) { + DSU_CloseSocket(ctx->socket); + DSU_QuitSockets(); + SDL_free(ctx); + SDL_OutOfMemory(); + return false; + } + + /* Start receiver thread */ + SDL_SetAtomicInt(&ctx->running, 1); + ctx->receiver_thread = SDL_CreateThread( + DSU_ReceiverThread, "DSU_Receiver", ctx); + if (!ctx->receiver_thread) { + SDL_DestroyMutex(ctx->slots_mutex); + DSU_CloseSocket(ctx->socket); + DSU_QuitSockets(); + SDL_free(ctx); + SDL_SetError("Failed to create DSU receiver thread"); + return false; + } + + /* Store context globally */ + s_dsu_ctx = ctx; + + /* Request controller info from all slots */ + DSU_RequestControllerInfo(ctx, 0xFF); + + return true; +} + +static int DSU_JoystickGetCount(void) +{ + int count = 0; + int i; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + ctx = s_dsu_ctx; + if (!ctx) { + return 0; + } + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].connected) { + count++; + } + } + SDL_UnlockMutex(mutex); + + return count; +} + +static void DSU_JoystickDetect(void) +{ + Uint64 now; + int i; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + ctx = s_dsu_ctx; + if (!ctx) { + return; + } + + /* Periodically request controller info and re-subscribe to data */ + now = SDL_GetTicks(); + if (now - ctx->last_request_time >= 500) { /* Request more frequently */ + DSU_RequestControllerInfo(ctx, 0xFF); + + /* Re-subscribe to data for detected controllers */ + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].detected || ctx->slots[i].connected) { + DSU_RequestControllerData(ctx, i); + } + } + + ctx->last_request_time = now; + } + + /* Process pending joystick additions (SDL holds joystick lock during Detect) */ + /* First, collect which controllers need to be added while holding the mutex */ + struct { + SDL_JoystickID instance_id; + int slot; + } pending_adds[DSU_MAX_SLOTS]; + int num_pending = 0; + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].pending_add && ctx->slots[i].detected) { + SDL_Log("DSU: Found pending add for slot %d, instance %d\n", + i, (int)ctx->slots[i].instance_id); + ctx->slots[i].pending_add = false; + /* DON'T mark as connected yet - wait until SDL accepts it */ + pending_adds[num_pending].instance_id = ctx->slots[i].instance_id; + pending_adds[num_pending].slot = i; + num_pending++; + } + } + SDL_UnlockMutex(mutex); + + /* Now notify SDL about the new controllers without holding the mutex */ + for (i = 0; i < num_pending; i++) { + SDL_Log("DSU: About to call SDL_PrivateJoystickAdded for instance %d\n", (int)pending_adds[i].instance_id); + SDL_Log("DSU: Current joystick count = %d\n", DSU_JoystickGetCount()); + SDL_PrivateJoystickAdded(pending_adds[i].instance_id); + SDL_Log("DSU: SDL_PrivateJoystickAdded returned for instance %d\n", (int)pending_adds[i].instance_id); + + /* NOW mark it as connected since SDL accepted it */ + SDL_LockMutex(mutex); + ctx->slots[pending_adds[i].slot].connected = true; + SDL_UnlockMutex(mutex); + + SDL_Log("DSU: New joystick count = %d\n", DSU_JoystickGetCount()); + } + + /* Check for timeouts */ + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if ((ctx->slots[i].detected || ctx->slots[i].connected) && + now - ctx->slots[i].last_packet_time > 5000) { /* Increased timeout */ + /* Controller timed out */ + SDL_Log("DSU: Controller timeout on slot %d (instance %d)\n", i, (int)ctx->slots[i].instance_id); + + /* Notify SDL if it was connected */ + if (ctx->slots[i].connected && ctx->slots[i].instance_id != 0) { + SDL_PrivateJoystickRemoved(ctx->slots[i].instance_id); + } + + /* Clear all state flags */ + ctx->slots[i].detected = false; + ctx->slots[i].connected = false; + ctx->slots[i].pending_add = false; + ctx->slots[i].instance_id = 0; + } + } + SDL_UnlockMutex(mutex); +} + +static const char *DSU_JoystickGetDeviceName(int device_index) +{ + int i, count = 0; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + ctx = s_dsu_ctx; + if (!ctx) { + return NULL; + } + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].connected) { + if (count == device_index) { + SDL_UnlockMutex(mutex); + return ctx->slots[i].name; + } + count++; + } + } + SDL_UnlockMutex(mutex); + + return NULL; +} + +static bool DSU_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + /* DSU devices are network-based, not USB, so we don't match by VID/PID */ + return false; +} + +static const char *DSU_JoystickGetDevicePath(int device_index) +{ + return NULL; /* No path for network devices */ +} + +static int DSU_JoystickGetDevicePlayerIndex(int device_index) +{ + int i, count = 0; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + ctx = s_dsu_ctx; + if (!ctx) { + return -1; + } + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].connected) { + if (count == device_index) { + SDL_UnlockMutex(mutex); + return i; /* Return slot ID as player index */ + } + count++; + } + } + SDL_UnlockMutex(mutex); + + return -1; +} + +static void DSU_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ + /* DSU controllers have fixed slots, can't change */ +} + +static SDL_GUID DSU_JoystickGetDeviceGUID(int device_index) +{ + SDL_GUID guid; + int i, count = 0; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + SDL_zero(guid); + + ctx = s_dsu_ctx; + if (!ctx) { + return guid; + } + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].connected) { + if (count == device_index) { + guid = ctx->slots[i].guid; + SDL_UnlockMutex(mutex); + return guid; + } + count++; + } + } + SDL_UnlockMutex(mutex); + + return guid; +} + +static SDL_JoystickID DSU_JoystickGetDeviceInstanceID(int device_index) +{ + int i, count = 0; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + ctx = s_dsu_ctx; + if (!ctx) { + return -1; + } + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].connected) { + if (count == device_index) { + SDL_JoystickID id = ctx->slots[i].instance_id; + SDL_UnlockMutex(mutex); + return id; + } + count++; + } + } + SDL_UnlockMutex(mutex); + + return -1; +} + +static bool DSU_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + DSU_ControllerSlot *slot = NULL; + int i, count = 0; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + SDL_Log("DSU: JoystickOpen called for device_index %d\n", device_index); + + ctx = s_dsu_ctx; + if (!ctx) { + SDL_SetError("DSU not initialized"); + SDL_Log("DSU: JoystickOpen failed - not initialized\n"); + return false; + } + SDL_Log("DSU: JoystickOpen - context valid\n"); + + if (!joystick) { + SDL_SetError("DSU: NULL joystick pointer"); + SDL_Log("DSU: JoystickOpen failed - NULL joystick\n"); + return false; + } + + /* Find the slot for this device - check detected controllers */ + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + /* Look for detected controllers that are about to be connected */ + if (ctx->slots[i].detected && ctx->slots[i].instance_id != 0) { + if (count == device_index) { + slot = &ctx->slots[i]; + SDL_Log("DSU: JoystickOpen found slot %d for device_index %d\n", i, device_index); + break; + } + count++; + } + } + SDL_UnlockMutex(mutex); + + if (!slot) { + SDL_SetError("Invalid DSU device index"); + return false; + } + + joystick->instance_id = slot->instance_id; + joystick->hwdata = (struct joystick_hwdata *)slot; + joystick->nbuttons = 12; /* Standard PS4 buttons */ + joystick->naxes = 6; /* LX, LY, RX, RY, L2, R2 */ + joystick->nhats = 1; /* D-Pad */ + + /* Set up touchpad if available */ + if (slot->has_touchpad) { + joystick->ntouchpads = 1; + joystick->touchpads = (SDL_JoystickTouchpadInfo *)SDL_calloc(1, sizeof(SDL_JoystickTouchpadInfo)); + if (joystick->touchpads) { + joystick->touchpads[0].nfingers = 2; /* DSU supports 2 fingers */ + } else { + joystick->ntouchpads = 0; /* Failed to allocate, disable touchpad */ + } + } + + /* Register sensors if available */ + SDL_Log("DSU: JoystickOpen - About to register sensors\n"); + if (slot->has_gyro || (slot->model == DSU_MODEL_FULL_GYRO) || (slot->model == DSU_MODEL_PARTIAL_GYRO)) { + /* DSU reports gyro at varying rates, but typically 250-1000Hz for DS4/DS5 */ + SDL_Log("DSU: Adding GYRO sensor\n"); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); + slot->has_gyro = true; + } + if (slot->has_accel || (slot->model == DSU_MODEL_FULL_GYRO)) { + /* DSU reports accelerometer at same rate as gyro */ + SDL_Log("DSU: Adding ACCEL sensor\n"); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + slot->has_accel = true; + } + + SDL_Log("DSU: JoystickOpen completed successfully for slot %d, hwdata=%p\n", slot->slot_id, slot); + return true; +} + +static bool DSU_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + DSU_ControllerSlot *slot = (DSU_ControllerSlot *)joystick->hwdata; + DSU_RumblePacket packet; + struct sockaddr_in server; + struct DSU_Context_t *ctx; + + ctx = s_dsu_ctx; + if (!ctx || !slot || !slot->connected) { + SDL_SetError("DSU controller not available"); + return false; + } + + /* Build rumble packet */ + SDL_memset(&packet, 0, sizeof(packet)); + SDL_memcpy(packet.header.magic, DSU_MAGIC_CLIENT, 4); + packet.header.version = SDL_Swap16LE(DSU_PROTOCOL_VERSION); + packet.header.length = SDL_Swap16LE((Uint16)(sizeof(packet) - sizeof(DSU_Header))); + packet.header.client_id = SDL_Swap32LE(ctx->client_id); + packet.header.message_type = SDL_Swap32LE(DSU_MSG_RUMBLE); + + /* Set rumble values */ + packet.slot = slot->slot_id; + packet.motor_left = (Uint8)(low_frequency_rumble >> 8); /* Convert from 16-bit to 8-bit */ + packet.motor_right = (Uint8)(high_frequency_rumble >> 8); + + /* Calculate CRC32 */ + packet.header.crc32 = 0; + packet.header.crc32 = SDL_Swap32LE(DSU_CalculateCRC32((Uint8*)&packet, sizeof(packet))); + + /* Send to server */ + SDL_memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_port = DSU_htons(ctx->server_port); + server.sin_addr.s_addr = DSU_ipv4_addr(ctx->server_address); + if ((sendto)(ctx->socket, (const char*)&packet, (int)sizeof(packet), 0, + (struct sockaddr *)&server, (int)sizeof(server)) < 0) { + SDL_SetError("Failed to send rumble packet"); + return false; + } + + return true; +} + +static bool DSU_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + SDL_Unsupported(); + return false; +} + +static bool DSU_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + SDL_Unsupported(); + return false; +} + +static bool DSU_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + SDL_Unsupported(); + return false; +} + +static bool DSU_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + DSU_ControllerSlot *slot = (DSU_ControllerSlot *)joystick->hwdata; + + /* Sensors are always enabled if available */ + if (!(slot->has_gyro || slot->has_accel)) { + SDL_Unsupported(); + return false; + } + return true; +} + +static void DSU_JoystickUpdate(SDL_Joystick *joystick) +{ + DSU_ControllerSlot *slot = (DSU_ControllerSlot *)joystick->hwdata; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + Uint64 timestamp; + int i; + + if (!slot) { + SDL_Log("DSU: JoystickUpdate called with NULL slot\n"); + return; + } + + if (!slot->connected) { + SDL_Log("DSU: JoystickUpdate called for disconnected slot %d\n", slot->slot_id); + return; + } + + ctx = s_dsu_ctx; + if (!ctx || !ctx->slots_mutex) { + SDL_Log("DSU: JoystickUpdate called but context invalid\n"); + return; + } + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + + /* Get current timestamp */ + timestamp = SDL_GetTicks(); + + /* Log current input state */ + SDL_Log("DSU UPDATE: Slot %d buttons=0x%04x", slot->slot_id, slot->buttons); + + /* Update buttons with names */ + const char* button_names[] = { + "Cross/A", "Circle/B", "Square/X", "Triangle/Y", + "L1/LB", "R1/RB", "Share/Back", "Options/Start", + "L3/LSClick", "R3/RSClick", "PS/Home", "Touchpad" + }; + for (i = 0; i < 12; i++) { + bool pressed = (slot->buttons & (1 << i)) ? true : false; + if (pressed) { + SDL_Log(" Button %d (%s): PRESSED", i, button_names[i]); + } + SDL_SendJoystickButton(timestamp, joystick, i, pressed); + } + + /* Update axes with detailed logging */ + SDL_Log("DSU UPDATE: Axes - LX=%d LY=%d RX=%d RY=%d L2=%d R2=%d", + slot->axes[0], slot->axes[1], slot->axes[2], + slot->axes[3], slot->axes[4], slot->axes[5]); + for (i = 0; i < 6; i++) { + SDL_SendJoystickAxis(timestamp, joystick, i, slot->axes[i]); + } + + /* Update hat (D-Pad) */ + const char* hat_str = "CENTERED"; + if (slot->hat & SDL_HAT_UP) { + if (slot->hat & SDL_HAT_LEFT) hat_str = "UP-LEFT"; + else if (slot->hat & SDL_HAT_RIGHT) hat_str = "UP-RIGHT"; + else hat_str = "UP"; + } else if (slot->hat & SDL_HAT_DOWN) { + if (slot->hat & SDL_HAT_LEFT) hat_str = "DOWN-LEFT"; + else if (slot->hat & SDL_HAT_RIGHT) hat_str = "DOWN-RIGHT"; + else hat_str = "DOWN"; + } else if (slot->hat & SDL_HAT_LEFT) { + hat_str = "LEFT"; + } else if (slot->hat & SDL_HAT_RIGHT) { + hat_str = "RIGHT"; + } + if (slot->hat != SDL_HAT_CENTERED) { + SDL_Log(" D-Pad: %s (0x%02x)", hat_str, slot->hat); + } + SDL_SendJoystickHat(timestamp, joystick, 0, slot->hat); + + /* Update touchpad if available */ + if (slot->has_touchpad && joystick->ntouchpads > 0) { + /* DS4/DS5 touchpad resolution is typically 1920x943 */ + const float TOUCHPAD_WIDTH = 1920.0f; + const float TOUCHPAD_HEIGHT = 943.0f; + + /* First touch point */ + bool touchpad_down = slot->touch1_active; + float touchpad_x = (float)slot->touch1_x / TOUCHPAD_WIDTH; + float touchpad_y = (float)slot->touch1_y / TOUCHPAD_HEIGHT; + + if (touchpad_down) { + SDL_Log(" TOUCHPAD[0]: Active X=%d Y=%d (%.2f, %.2f)", + slot->touch1_x, slot->touch1_y, touchpad_x, touchpad_y); + } + + /* Clamp to valid range */ + if (touchpad_x < 0.0f) touchpad_x = 0.0f; + if (touchpad_x > 1.0f) touchpad_x = 1.0f; + if (touchpad_y < 0.0f) touchpad_y = 0.0f; + if (touchpad_y > 1.0f) touchpad_y = 1.0f; + + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, + touchpad_x, touchpad_y, + touchpad_down ? 1.0f : 0.0f); + + /* Second touch point */ + touchpad_down = slot->touch2_active; + touchpad_x = (float)slot->touch2_x / TOUCHPAD_WIDTH; + touchpad_y = (float)slot->touch2_y / TOUCHPAD_HEIGHT; + + if (touchpad_down) { + SDL_Log(" TOUCHPAD[1]: Active X=%d Y=%d (%.2f, %.2f)", + slot->touch2_x, slot->touch2_y, touchpad_x, touchpad_y); + } + + /* Clamp to valid range */ + if (touchpad_x < 0.0f) touchpad_x = 0.0f; + if (touchpad_x > 1.0f) touchpad_x = 1.0f; + if (touchpad_y < 0.0f) touchpad_y = 0.0f; + if (touchpad_y > 1.0f) touchpad_y = 1.0f; + + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, + touchpad_x, touchpad_y, + touchpad_down ? 1.0f : 0.0f); + } + + /* Update battery level */ + const char* battery_str = "Unknown"; + switch (slot->battery) { + case DSU_BATTERY_DYING: + battery_str = "Dying (0-10%)"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 10); + break; + case DSU_BATTERY_LOW: + battery_str = "Low (10-40%)"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 25); + break; + case DSU_BATTERY_MEDIUM: + battery_str = "Medium (40-70%)"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 55); + break; + case DSU_BATTERY_HIGH: + battery_str = "High (70-100%)"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 85); + break; + case DSU_BATTERY_FULL: + battery_str = "Full (100%)"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 100); + break; + case DSU_BATTERY_CHARGING: + battery_str = "Charging"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_CHARGING, -1); + break; + case DSU_BATTERY_CHARGED: + battery_str = "Charged"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_CHARGING, 100); + break; + default: + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_UNKNOWN, -1); + break; + } + static Uint8 last_battery = 0xFF; + if (slot->battery != last_battery) { + SDL_Log(" BATTERY: %s (0x%02x)", battery_str, slot->battery); + last_battery = slot->battery; + } + + /* Update sensors if available */ + if (slot->has_gyro) { + SDL_Log(" GYRO: X=%.3f Y=%.3f Z=%.3f rad/s", + slot->gyro[0], slot->gyro[1], slot->gyro[2]); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, + slot->motion_timestamp, slot->gyro, 3); + } + if (slot->has_accel) { + SDL_Log(" ACCEL: X=%.3f Y=%.3f Z=%.3f m/s²", + slot->accel[0], slot->accel[1], slot->accel[2]); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, + slot->motion_timestamp, slot->accel, 3); + } + + SDL_UnlockMutex(mutex); +} + +static void DSU_JoystickClose(SDL_Joystick *joystick) +{ + /* Free touchpad info if allocated */ + if (joystick->touchpads) { + SDL_free(joystick->touchpads); + joystick->touchpads = NULL; + joystick->ntouchpads = 0; + } + + joystick->hwdata = NULL; +} + +static void DSU_JoystickQuit(void) +{ + struct DSU_Context_t *ctx; + + SDL_Log("DSU: JoystickQuit called\n"); + + ctx = s_dsu_ctx; + if (!ctx) { + return; + } + + /* Clear the global pointer first to prevent access during shutdown */ + s_dsu_ctx = NULL; + + /* Stop receiver thread */ + if (SDL_GetAtomicInt(&ctx->running) != 0) { + SDL_SetAtomicInt(&ctx->running, 0); + } + + /* Close socket to interrupt any blocking recvfrom */ + if (ctx->socket != DSU_INVALID_SOCKET) { + DSU_CloseSocket(ctx->socket); + ctx->socket = DSU_INVALID_SOCKET; + } + + /* Now wait for thread to finish */ + if (ctx->receiver_thread) { + SDL_Log("DSU: Waiting for receiver thread to finish...\n"); + SDL_WaitThread(ctx->receiver_thread, NULL); + ctx->receiver_thread = NULL; + SDL_Log("DSU: Receiver thread finished\n"); + } + + /* Clean up sockets */ + DSU_QuitSockets(); + + /* Clean up mutex */ + if (ctx->slots_mutex) { + SDL_DestroyMutex(ctx->slots_mutex); + } + + /* Free context */ + SDL_free(ctx); + SDL_Log("DSU: Quit complete\n"); +} + +static bool DSU_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + /* DSU controllers map well to standard gamepad layout */ + return false; /* Use default mapping */ +} + +/* Export the driver */ +SDL_JoystickDriver SDL_DSU_JoystickDriver = { + DSU_JoystickInit, + DSU_JoystickGetCount, + DSU_JoystickDetect, + DSU_JoystickIsDevicePresent, + DSU_JoystickGetDeviceName, + DSU_JoystickGetDevicePath, + NULL, /* GetDeviceSteamVirtualGamepadSlot */ + DSU_JoystickGetDevicePlayerIndex, + DSU_JoystickSetDevicePlayerIndex, + DSU_JoystickGetDeviceGUID, + DSU_JoystickGetDeviceInstanceID, + DSU_JoystickOpen, + DSU_JoystickRumble, + DSU_JoystickRumbleTriggers, + DSU_JoystickSetLED, + DSU_JoystickSendEffect, + DSU_JoystickSetSensorsEnabled, + DSU_JoystickUpdate, + DSU_JoystickClose, + DSU_JoystickQuit, + DSU_JoystickGetGamepadMapping +}; + +#endif /* SDL_JOYSTICK_DSU */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/joystick/dsu/SDL_dsuprotocol.h b/src/joystick/dsu/SDL_dsuprotocol.h new file mode 100644 index 0000000000000..952deaa8e06e7 --- /dev/null +++ b/src/joystick/dsu/SDL_dsuprotocol.h @@ -0,0 +1,201 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_dsuprotocol_h_ +#define SDL_dsuprotocol_h_ + +#include "SDL_internal.h" + +/* DSU (DualShock UDP) Protocol Constants - Based on CemuHook */ + +#define DSU_PROTOCOL_VERSION 1001 +#define DSU_SERVER_PORT_DEFAULT 26760 +#define DSU_CLIENT_PORT_DEFAULT 26761 +#define DSU_SERVER_ADDRESS_DEFAULT "127.0.0.1" + +/* Magic strings */ +#define DSU_MAGIC_CLIENT "DSUC" +#define DSU_MAGIC_SERVER "DSUS" + +/* Message types */ +typedef enum { + DSU_MSG_VERSION = 0x100000, + DSU_MSG_PORTS_INFO = 0x100001, + DSU_MSG_DATA = 0x100002, + DSU_MSG_RUMBLE_INFO = 0x110001, /* Unofficial */ + DSU_MSG_RUMBLE = 0x110002 /* Unofficial */ +} DSU_MessageType; + +/* Controller states */ +typedef enum { + DSU_STATE_DISCONNECTED = 0, + DSU_STATE_RESERVED = 1, + DSU_STATE_CONNECTED = 2 +} DSU_SlotState; + +/* Device models */ +typedef enum { + DSU_MODEL_NONE = 0, + DSU_MODEL_PARTIAL_GYRO = 1, + DSU_MODEL_FULL_GYRO = 2, /* DS4, DS5 */ + DSU_MODEL_NO_GYRO = 3 +} DSU_DeviceModel; + +/* Connection types */ +typedef enum { + DSU_CONN_NONE = 0, + DSU_CONN_USB = 1, + DSU_CONN_BLUETOOTH = 2 +} DSU_ConnectionType; + +/* Battery states */ +typedef enum { + DSU_BATTERY_NONE = 0x00, + DSU_BATTERY_DYING = 0x01, /* 0-10% */ + DSU_BATTERY_LOW = 0x02, /* 10-40% */ + DSU_BATTERY_MEDIUM = 0x03, /* 40-70% */ + DSU_BATTERY_HIGH = 0x04, /* 70-100% */ + DSU_BATTERY_FULL = 0x05, /* 100% */ + DSU_BATTERY_CHARGING = 0xEE, + DSU_BATTERY_CHARGED = 0xEF +} DSU_BatteryState; + +/* Packet structures */ +#pragma pack(push, 1) + +typedef struct { + char magic[4]; /* DSUC or DSUS */ + Uint16 version; /* Protocol version (1001) */ + Uint16 length; /* Packet length after header */ + Uint32 crc32; /* CRC32 of packet (with this field zeroed) */ + Uint32 client_id; /* Random client ID */ + Uint32 message_type; /* Message type enum */ +} DSU_Header; + +typedef struct { + DSU_Header header; + Uint8 flags; /* Slot registration flags */ + Uint8 slot_id; /* 0-3 for specific slot, 0xFF for all */ + Uint8 mac[6]; /* MAC address filter (zeros for all) */ +} DSU_PortRequest; + +typedef struct { + Uint8 slot; /* Controller slot 0-3 */ + Uint8 slot_state; /* DSU_SlotState */ + Uint8 device_model; /* DSU_DeviceModel */ + Uint8 connection_type; /* DSU_ConnectionType */ + Uint8 mac[6]; /* Controller MAC address */ + Uint8 battery; /* DSU_BatteryState */ + Uint8 is_active; /* 0 or 1 */ +} DSU_ControllerInfo; + +typedef struct { + DSU_Header header; + Uint8 slot; /* Controller slot 0-3 */ + Uint8 motor_left; /* Left/Low frequency motor intensity (0-255) */ + Uint8 motor_right; /* Right/High frequency motor intensity (0-255) */ +} DSU_RumblePacket; + +typedef struct { + DSU_Header header; + DSU_ControllerInfo info; + + /* Controller data */ + Uint32 packet_number; /* Incremental counter */ + + /* Digital buttons */ + Uint8 button_states_1; /* Share, L3, R3, Options, DPad */ + Uint8 button_states_2; /* L2, R2, L1, R1, Triangle, Circle, Cross, Square */ + Uint8 button_ps; /* PS/Home button */ + Uint8 button_touch; /* Touchpad button */ + + /* Analog sticks (0-255, 128=center) */ + Uint8 left_stick_x; + Uint8 left_stick_y; + Uint8 right_stick_x; + Uint8 right_stick_y; + + /* Analog buttons (0-255, pressure sensitive) */ + Uint8 analog_dpad_left; + Uint8 analog_dpad_down; + Uint8 analog_dpad_right; + Uint8 analog_dpad_up; + Uint8 analog_button_square; + Uint8 analog_button_cross; + Uint8 analog_button_circle; + Uint8 analog_button_triangle; + Uint8 analog_button_r1; + Uint8 analog_button_l1; + Uint8 analog_trigger_r2; + Uint8 analog_trigger_l2; + + /* Touch data (2 points max) */ + Uint8 touch1_active; + Uint8 touch1_id; + Uint16 touch1_x; + Uint16 touch1_y; + + Uint8 touch2_active; + Uint8 touch2_id; + Uint16 touch2_x; + Uint16 touch2_y; + + /* Motion data (optional) */ + Uint64 motion_timestamp; /* Microseconds */ + float accel_x; /* In g units */ + float accel_y; + float accel_z; + float gyro_pitch; /* In degrees/second */ + float gyro_yaw; + float gyro_roll; +} DSU_ControllerData; + +#pragma pack(pop) + +/* Button masks for button_states_1 */ +#define DSU_BUTTON_SHARE 0x01 +#define DSU_BUTTON_L3 0x02 +#define DSU_BUTTON_R3 0x04 +#define DSU_BUTTON_OPTIONS 0x08 +#define DSU_BUTTON_DPAD_UP 0x10 +#define DSU_BUTTON_DPAD_RIGHT 0x20 +#define DSU_BUTTON_DPAD_DOWN 0x40 +#define DSU_BUTTON_DPAD_LEFT 0x80 + +/* Button masks for button_states_2 */ +#define DSU_BUTTON_L2 0x01 +#define DSU_BUTTON_R2 0x02 +#define DSU_BUTTON_L1 0x04 +#define DSU_BUTTON_R1 0x08 +#define DSU_BUTTON_TRIANGLE 0x10 +#define DSU_BUTTON_CIRCLE 0x20 +#define DSU_BUTTON_CROSS 0x40 +#define DSU_BUTTON_SQUARE 0x80 + +/* Maximum number of DSU slots per server */ +#define DSU_MAX_SLOTS 4 + +/* We can support up to 8 controllers by using 2 server connections */ +#define DSU_MAX_CONTROLLERS 8 + +#endif /* SDL_dsuprotocol_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */ From b2f6615124e580bd9148bf16201448541eaf82aa Mon Sep 17 00:00:00 2001 From: danprice142 Date: Thu, 30 Oct 2025 21:32:45 +0000 Subject: [PATCH 02/12] Improve DSU joystick cross-platform socket support Disabled DSU joystick on Emscripten due to lack of UDP socket support. Updated socket initialization to use ioctl with FIONBIO for better compatibility on Unix-like systems. Refactored header includes and comments for clarity and platform correctness. Fixed variable naming in DSU data packet handling and removed unnecessary 'static' from inline functions in header. --- CMakeLists.txt | 36 +++++++++++++++-------- src/joystick/dsu/SDL_dsujoystick.c | 21 +++++++++---- src/joystick/dsu/SDL_dsujoystick_c.h | 6 ++-- src/joystick/dsu/SDL_dsujoystick_driver.c | 9 +++++- 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 79999826e2c41..2eb60a2421c22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1378,19 +1378,29 @@ if(SDL_JOYSTICK) # DSU (DualShock UDP) client support if(SDL_DSU_JOYSTICK) - set(SDL_JOYSTICK_DSU 1) - sdl_glob_sources( - "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.c" - "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.h" - ) - - # DSU requires network libraries - if(WIN32) - list(APPEND SDL3_EXTRA_LIBS ws2_32) - elseif(UNIX AND NOT APPLE AND NOT ANDROID AND NOT HAIKU) - # Unix systems typically have sockets built-in - elseif(HAIKU) - list(APPEND SDL3_EXTRA_LIBS network) + # Disable DSU for platforms that don't support UDP sockets + if(EMSCRIPTEN) + message(STATUS "DSU joystick disabled on Emscripten (no UDP socket support)") + set(SDL_DSU_JOYSTICK OFF) + else() + set(SDL_JOYSTICK_DSU 1) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.h" + ) + + # DSU requires network libraries + if(WIN32) + sdl_link_dependency(dsu LIBS ws2_32) + elseif(HAIKU) + sdl_link_dependency(dsu LIBS network) + elseif(APPLE) + # macOS/iOS have sockets built-in, no extra linking needed + elseif(ANDROID) + # Android has sockets built-in, no extra linking needed + elseif(UNIX) + # Other Unix systems typically have sockets built-in + endif() endif() endif() endif() diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index ff7e1f451ae8b..da4e0eb31273e 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -61,6 +61,10 @@ typedef int socklen_t; #include #include #include + #include /* Required for ioctl on Unix-like systems including Haiku */ + #ifdef __sun + #include /* FIONBIO on Solaris */ + #endif #define closesocket close #endif @@ -121,8 +125,15 @@ dsu_socket_t DSU_CreateSocket(Uint16 port) #ifdef _WIN32 ioctlsocket(sock, FIONBIO, &mode); #else - flags = fcntl(sock, F_GETFL, 0); - fcntl(sock, F_SETFL, flags | O_NONBLOCK); + /* Use ioctl with FIONBIO for better compatibility across Unix-like systems */ + #ifdef FIONBIO + int mode_unix = 1; + ioctl(sock, FIONBIO, &mode_unix); + #else + /* Fallback to fcntl if FIONBIO is not available */ + flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); + #endif #endif /* Bind to client port if specified */ @@ -499,9 +510,9 @@ int SDLCALL DSU_ReceiverThread(void *data) case DSU_MSG_DATA: /* Controller data */ if (received >= (int)sizeof(DSU_ControllerData)) { - DSU_ControllerData *data = (DSU_ControllerData *)buffer; - SDL_Log("DSU: Data packet received for slot %d\n", data->info.slot); - DSU_ProcessControllerData(ctx, data); + DSU_ControllerData *packet = (DSU_ControllerData *)buffer; + SDL_Log("DSU: Data packet received for slot %d\n", packet->info.slot); + DSU_ProcessControllerData(ctx, packet); } break; diff --git a/src/joystick/dsu/SDL_dsujoystick_c.h b/src/joystick/dsu/SDL_dsujoystick_c.h index abb84bb91415e..a1f30ecab7e0b 100644 --- a/src/joystick/dsu/SDL_dsujoystick_c.h +++ b/src/joystick/dsu/SDL_dsujoystick_c.h @@ -122,9 +122,9 @@ extern DSU_Context *s_dsu_ctx; extern "C" { #endif -static SDL_FORCE_INLINE Uint16 DSU_htons(Uint16 x) { return SDL_Swap16BE(x); } -static SDL_FORCE_INLINE Uint32 DSU_htonl(Uint32 x) { return SDL_Swap32BE(x); } -static SDL_FORCE_INLINE Uint32 DSU_ipv4_addr(const char *ip) + SDL_FORCE_INLINE Uint16 DSU_htons(Uint16 x) { return SDL_Swap16BE(x); } + SDL_FORCE_INLINE Uint32 DSU_htonl(Uint32 x) { return SDL_Swap32BE(x); } + SDL_FORCE_INLINE Uint32 DSU_ipv4_addr(const char *ip) { unsigned int a, b, c, d; if (SDL_sscanf(ip, "%u.%u.%u.%u", &a, &b, &c, &d) == 4) { diff --git a/src/joystick/dsu/SDL_dsujoystick_driver.c b/src/joystick/dsu/SDL_dsujoystick_driver.c index b35af5428a290..9573fdb567a64 100644 --- a/src/joystick/dsu/SDL_dsujoystick_driver.c +++ b/src/joystick/dsu/SDL_dsujoystick_driver.c @@ -19,7 +19,7 @@ 3. This notice may not be removed or altered from any source distribution. */ -/* Include Windows socket headers before SDL to avoid macro conflicts */ +/* Include socket headers before SDL to avoid macro conflicts */ #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -27,6 +27,13 @@ #ifdef _MSC_VER #pragma comment(lib, "ws2_32.lib") #endif +#else +/* Unix-like systems including Haiku */ +#include +#include +#include +#include +#include #endif #include "SDL_internal.h" From 08814d1c1a25e4260120ea332aa967eb944b177a Mon Sep 17 00:00:00 2001 From: danprice142 Date: Fri, 14 Nov 2025 19:33:11 +0000 Subject: [PATCH 03/12] Improve DSU joystick socket portability and linking Updated CMakeLists.txt to only enable DSU joystick on non-Emscripten targets and use sdl_link_dependency for required network libraries. Enhanced DSU socket creation for better non-blocking support and portability, including FIONBIO and ioctl handling. Added missing socket-related includes for non-Windows platforms in SDL_dsujoystick_driver.c. --- CMakeLists.txt | 38 ++++++++++-------------------- src/joystick/dsu/SDL_dsujoystick.c | 25 +++++++++++++------- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2eb60a2421c22..7dba1ad3b40c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1375,32 +1375,20 @@ if(SDL_JOYSTICK) "${SDL3_SOURCE_DIR}/src/joystick/virtual/*.h" ) endif() - + # DSU (DualShock UDP) client support - if(SDL_DSU_JOYSTICK) - # Disable DSU for platforms that don't support UDP sockets - if(EMSCRIPTEN) - message(STATUS "DSU joystick disabled on Emscripten (no UDP socket support)") - set(SDL_DSU_JOYSTICK OFF) - else() - set(SDL_JOYSTICK_DSU 1) - sdl_glob_sources( - "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.c" - "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.h" - ) - - # DSU requires network libraries - if(WIN32) - sdl_link_dependency(dsu LIBS ws2_32) - elseif(HAIKU) - sdl_link_dependency(dsu LIBS network) - elseif(APPLE) - # macOS/iOS have sockets built-in, no extra linking needed - elseif(ANDROID) - # Android has sockets built-in, no extra linking needed - elseif(UNIX) - # Other Unix systems typically have sockets built-in - endif() + if(SDL_DSU_JOYSTICK AND NOT EMSCRIPTEN) + set(SDL_JOYSTICK_DSU 1) + sdl_glob_sources( + "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.c" + "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.h" + ) + + # DSU requires network libraries + if(WIN32) + sdl_link_dependency(dsu LIBS ws2_32) + elseif(HAIKU) + sdl_link_dependency(dsu LIBS network) endif() endif() endif() diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index da4e0eb31273e..977904e6c9cf7 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -61,9 +61,9 @@ typedef int socklen_t; #include #include #include - #include /* Required for ioctl on Unix-like systems including Haiku */ + #include #ifdef __sun - #include /* FIONBIO on Solaris */ + #include #endif #define closesocket close #endif @@ -111,6 +111,9 @@ dsu_socket_t DSU_CreateSocket(Uint16 port) u_long mode = 1; #else int flags; +#if defined(FIONBIO) + int nonblock = 1; +#endif #endif sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); @@ -125,15 +128,19 @@ dsu_socket_t DSU_CreateSocket(Uint16 port) #ifdef _WIN32 ioctlsocket(sock, FIONBIO, &mode); #else - /* Use ioctl with FIONBIO for better compatibility across Unix-like systems */ - #ifdef FIONBIO - int mode_unix = 1; - ioctl(sock, FIONBIO, &mode_unix); - #else - /* Fallback to fcntl if FIONBIO is not available */ +#if defined(FIONBIO) + if (ioctl(sock, FIONBIO, &nonblock) < 0) { flags = fcntl(sock, F_GETFL, 0); + if (flags != -1) { + fcntl(sock, F_SETFL, flags | O_NONBLOCK); + } + } +#else + flags = fcntl(sock, F_GETFL, 0); + if (flags != -1) { fcntl(sock, F_SETFL, flags | O_NONBLOCK); - #endif + } +#endif #endif /* Bind to client port if specified */ From 0979ad52bec80c878a42287330b2b95cc831655a Mon Sep 17 00:00:00 2001 From: danprice142 Date: Fri, 14 Nov 2025 21:58:45 +0000 Subject: [PATCH 04/12] Restrict DSU joystick support to specific platforms Updated CMakeLists.txt to enable DSU joystick support only on Windows, Linux, Android, Haiku, FreeBSD, NetBSD, OpenBSD, and macOS. Added conditional inclusion of in SDL_dsujoystick.c. Removed unused GetDSUMutex helper from SDL_dsujoystick_driver.c. --- CMakeLists.txt | 2 +- src/joystick/dsu/SDL_dsujoystick.c | 4 +++- src/joystick/dsu/SDL_dsujoystick_driver.c | 6 ------ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dba1ad3b40c7..6bf827978f631 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1377,7 +1377,7 @@ if(SDL_JOYSTICK) endif() # DSU (DualShock UDP) client support - if(SDL_DSU_JOYSTICK AND NOT EMSCRIPTEN) + if(SDL_DSU_JOYSTICK AND NOT EMSCRIPTEN AND (WINDOWS OR LINUX OR ANDROID OR HAIKU OR FREEBSD OR NETBSD OR OPENBSD OR MACOS)) set(SDL_JOYSTICK_DSU 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.c" diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index 977904e6c9cf7..ecd6ead78b8ff 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -61,7 +61,9 @@ typedef int socklen_t; #include #include #include - #include + #ifdef HAVE_SYS_IOCTL_H + #include + #endif #ifdef __sun #include #endif diff --git a/src/joystick/dsu/SDL_dsujoystick_driver.c b/src/joystick/dsu/SDL_dsujoystick_driver.c index 9573fdb567a64..147e0a71b1260 100644 --- a/src/joystick/dsu/SDL_dsujoystick_driver.c +++ b/src/joystick/dsu/SDL_dsujoystick_driver.c @@ -64,12 +64,6 @@ extern SDL_JoystickDriver SDL_DSU_JoystickDriver; /* Global DSU context pointer */ struct DSU_Context_t *s_dsu_ctx = NULL; -/* Helper function to work around macro issues */ -static SDL_INLINE SDL_Mutex* GetDSUMutex(void) { - struct DSU_Context_t *ctx = s_dsu_ctx; - return ctx ? ctx->slots_mutex : NULL; -} - /* Forward declarations */ extern int SDLCALL DSU_ReceiverThread(void *data); extern void DSU_RequestControllerInfo(struct DSU_Context_t *ctx, Uint8 slot); From 648976738396351ced17f3437941ab45556a0b5f Mon Sep 17 00:00:00 2001 From: danprice142 Date: Fri, 14 Nov 2025 22:16:56 +0000 Subject: [PATCH 05/12] VisualC: add DSU DSU joystick driver sources --- VisualC/SDL/SDL.vcxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index 7af7132b29a66..ed7c5e53a2aca 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -389,6 +389,8 @@ + + @@ -637,6 +639,8 @@ + + From 81c4ef31e142e3105d2eebce39cce554c7e024e2 Mon Sep 17 00:00:00 2001 From: danprice142 Date: Fri, 14 Nov 2025 23:16:02 +0000 Subject: [PATCH 06/12] DSU: fix MSVC warnings and socket types --- src/joystick/dsu/SDL_dsujoystick.c | 4 ++-- src/joystick/dsu/SDL_dsujoystick_driver.c | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index ecd6ead78b8ff..231a3c3f091d1 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -338,7 +338,7 @@ static void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data slot->battery = data->info.battery; slot->model = data->info.device_model; slot->connection = data->info.connection_type; - slot->slot_id = slot_id; + slot->slot_id = (Uint8)slot_id; /* Generate name */ SDL_snprintf(slot->name, sizeof(slot->name), "DSUClient/%d", slot_id); @@ -444,7 +444,7 @@ static void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data /* Subscribe to controller data updates if just detected */ if (!was_connected && slot->detected) { - DSU_RequestControllerData(ctx, slot_id); + DSU_RequestControllerData(ctx, (Uint8)slot_id); } SDL_Log("DSU: Finished processing data for slot %d\n", slot_id); } diff --git a/src/joystick/dsu/SDL_dsujoystick_driver.c b/src/joystick/dsu/SDL_dsujoystick_driver.c index 147e0a71b1260..965dc2ed52f7a 100644 --- a/src/joystick/dsu/SDL_dsujoystick_driver.c +++ b/src/joystick/dsu/SDL_dsujoystick_driver.c @@ -21,7 +21,6 @@ /* Include socket headers before SDL to avoid macro conflicts */ #ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN #include #include #ifdef _MSC_VER @@ -108,14 +107,14 @@ static bool DSU_JoystickInit(void) server_port = SDL_GetHint(SDL_HINT_DSU_SERVER_PORT); if (server_port && *server_port) { - ctx->server_port = SDL_atoi(server_port); + ctx->server_port = (Uint16)SDL_atoi(server_port); } else { ctx->server_port = DSU_SERVER_PORT_DEFAULT; } client_port = SDL_GetHint(SDL_HINT_DSU_CLIENT_PORT); if (client_port && *client_port) { - ctx->client_port = SDL_atoi(client_port); + ctx->client_port = (Uint16)SDL_atoi(client_port); } else { ctx->client_port = DSU_CLIENT_PORT_DEFAULT; } @@ -207,12 +206,12 @@ static void DSU_JoystickDetect(void) /* Periodically request controller info and re-subscribe to data */ now = SDL_GetTicks(); if (now - ctx->last_request_time >= 500) { /* Request more frequently */ - DSU_RequestControllerInfo(ctx, 0xFF); + DSU_RequestControllerInfo(ctx, (Uint8)0xFF); /* Re-subscribe to data for detected controllers */ for (i = 0; i < DSU_MAX_SLOTS; i++) { if (ctx->slots[i].detected || ctx->slots[i].connected) { - DSU_RequestControllerData(ctx, i); + DSU_RequestControllerData(ctx, (Uint8)i); } } @@ -389,7 +388,7 @@ static SDL_JoystickID DSU_JoystickGetDeviceInstanceID(int device_index) ctx = s_dsu_ctx; if (!ctx) { - return -1; + return 0; } mutex = ctx->slots_mutex; @@ -607,7 +606,7 @@ static void DSU_JoystickUpdate(SDL_Joystick *joystick) if (pressed) { SDL_Log(" Button %d (%s): PRESSED", i, button_names[i]); } - SDL_SendJoystickButton(timestamp, joystick, i, pressed); + SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, pressed); } /* Update axes with detailed logging */ @@ -615,7 +614,7 @@ static void DSU_JoystickUpdate(SDL_Joystick *joystick) slot->axes[0], slot->axes[1], slot->axes[2], slot->axes[3], slot->axes[4], slot->axes[5]); for (i = 0; i < 6; i++) { - SDL_SendJoystickAxis(timestamp, joystick, i, slot->axes[i]); + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, slot->axes[i]); } /* Update hat (D-Pad) */ From fe8c232ff2e182ef0242f195875c34cc7d10a447 Mon Sep 17 00:00:00 2001 From: danprice142 Date: Sat, 15 Nov 2025 07:31:33 +0000 Subject: [PATCH 07/12] DSU: fix SDL_JoystickID warning on MSVC --- src/joystick/dsu/SDL_dsujoystick_driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/joystick/dsu/SDL_dsujoystick_driver.c b/src/joystick/dsu/SDL_dsujoystick_driver.c index 965dc2ed52f7a..7b8800f1f0b79 100644 --- a/src/joystick/dsu/SDL_dsujoystick_driver.c +++ b/src/joystick/dsu/SDL_dsujoystick_driver.c @@ -405,7 +405,7 @@ static SDL_JoystickID DSU_JoystickGetDeviceInstanceID(int device_index) } SDL_UnlockMutex(mutex); - return -1; + return 0; } static bool DSU_JoystickOpen(SDL_Joystick *joystick, int device_index) From bc413bf483656fddcb60b61da5de2611c00f47c8 Mon Sep 17 00:00:00 2001 From: danprice142 Date: Sat, 15 Nov 2025 23:10:37 +0000 Subject: [PATCH 08/12] Merge DSU joystick driver into SDL_dsujoystick.c Moved all DSU joystick driver logic from SDL_dsujoystick_driver.c into SDL_dsujoystick.c, removing the separate driver file. Updated SDL_dsujoystick_c.h to remove internal driver declarations and macros now handled in the implementation. Updated VisualC project file to remove the deleted driver source. --- VisualC/SDL/SDL.vcxproj | 1 - src/joystick/dsu/SDL_dsujoystick.c | 782 +++++++++++++++++++- src/joystick/dsu/SDL_dsujoystick_c.h | 66 +- src/joystick/dsu/SDL_dsujoystick_driver.c | 836 ---------------------- 4 files changed, 787 insertions(+), 898 deletions(-) delete mode 100644 src/joystick/dsu/SDL_dsujoystick_driver.c diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index ed7c5e53a2aca..5d6484c3ecf4e 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -640,7 +640,6 @@ - diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index 231a3c3f091d1..6cc16fec0ec5e 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -77,8 +77,23 @@ typedef int socklen_t; #define SERVER_TIMEOUT_INTERVAL 2000 /* ms */ #define GRAVITY_ACCELERATION 9.80665f /* m/s² */ -/* Global DSU context is defined in SDL_dsujoystick_driver.c */ -extern DSU_Context *s_dsu_ctx; +/* Internal DSU helper macros */ +#ifdef _WIN32 + #define DSU_htons(x) htons(x) + #define DSU_htonl(x) htonl(x) + #define DSU_ipv4_addr(str) inet_addr(str) + #define DSU_SOCKET_ERROR SOCKET_ERROR + #define DSU_INVALID_SOCKET INVALID_SOCKET +#else + #define DSU_htons(x) htons(x) + #define DSU_htonl(x) htonl(x) + #define DSU_ipv4_addr(str) inet_addr(str) + #define DSU_SOCKET_ERROR (-1) + #define DSU_INVALID_SOCKET (-1) +#endif + +/* Global DSU context pointer */ +struct DSU_Context_t *s_dsu_ctx = NULL; /* Use the DSU_Context type from the shared header */ @@ -166,7 +181,7 @@ void DSU_CloseSocket(dsu_socket_t socket) closesocket(socket); } } - +#if 0 /* Legacy CRC32 implementation replaced by SDL_crc32() */ /* Complete CRC32 table */ static const Uint32 crc32_table[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, @@ -214,6 +229,7 @@ Uint32 DSU_CalculateCRC32(const Uint8 *data, size_t length) return crc ^ 0xFFFFFFFF; } +#endif /* Send a packet to the DSU server */ static int DSU_SendPacket(DSU_Context *ctx, void *packet, size_t size) @@ -232,7 +248,7 @@ static int DSU_SendPacket(DSU_Context *ctx, void *packet, size_t size) header->crc32 = 0; /* Calculate and store CRC32 */ - header->crc32 = SDL_Swap32LE(DSU_CalculateCRC32((const Uint8 *)packet, size)); + header->crc32 = SDL_Swap32LE(SDL_crc32(0, packet, size)); /* Send to server */ SDL_memset(&server, 0, sizeof(server)); @@ -483,7 +499,7 @@ int SDLCALL DSU_ReceiverThread(void *data) /* Validate CRC32 */ received_crc = SDL_Swap32LE(header->crc32); header->crc32 = 0; - calculated_crc = DSU_CalculateCRC32(buffer, received); + calculated_crc = SDL_crc32(0, buffer, (size_t)received); if (received_crc == calculated_crc) { Uint32 msg_type = SDL_Swap32LE(header->message_type); @@ -565,6 +581,762 @@ int SDLCALL DSU_ReceiverThread(void *data) return 0; } +/* Driver functions - merged from SDL_dsujoystick_driver.c */ +static bool DSU_JoystickInit(void) +{ + const char *enabled; + const char *server; + const char *server_port; + const char *client_port; + struct DSU_Context_t *ctx; + + /* Check if DSU is enabled */ + enabled = SDL_GetHint(SDL_HINT_JOYSTICK_DSU); + if (enabled && SDL_atoi(enabled) == 0) { + return true; /* DSU disabled */ + } + + /* Allocate context */ + ctx = (struct DSU_Context_t *)SDL_calloc(1, sizeof(struct DSU_Context_t)); + if (!ctx) { + SDL_OutOfMemory(); + return false; + } + + /* Get configuration from hints with fallbacks */ + server = SDL_GetHint(SDL_HINT_DSU_SERVER); + if (!server || !*server) { + server = DSU_SERVER_ADDRESS_DEFAULT; + } + SDL_strlcpy(ctx->server_address, server, + sizeof(ctx->server_address)); + + server_port = SDL_GetHint(SDL_HINT_DSU_SERVER_PORT); + if (server_port && *server_port) { + ctx->server_port = (Uint16)SDL_atoi(server_port); + } else { + ctx->server_port = DSU_SERVER_PORT_DEFAULT; + } + + client_port = SDL_GetHint(SDL_HINT_DSU_CLIENT_PORT); + if (client_port && *client_port) { + ctx->client_port = (Uint16)SDL_atoi(client_port); + } else { + ctx->client_port = DSU_CLIENT_PORT_DEFAULT; + } + + ctx->client_id = (Uint32)SDL_GetTicks(); + + /* Initialize sockets */ + if (DSU_InitSockets() != 0) { + SDL_free(ctx); + return false; + } + + /* Create UDP socket */ + ctx->socket = DSU_CreateSocket(ctx->client_port); + if (ctx->socket == DSU_INVALID_SOCKET) { + DSU_QuitSockets(); + SDL_free(ctx); + return false; + } + + /* Create mutex */ + ctx->slots_mutex = SDL_CreateMutex(); + if (!ctx->slots_mutex) { + DSU_CloseSocket(ctx->socket); + DSU_QuitSockets(); + SDL_free(ctx); + SDL_OutOfMemory(); + return false; + } + + /* Start receiver thread */ + SDL_SetAtomicInt(&ctx->running, 1); + ctx->receiver_thread = SDL_CreateThread( + DSU_ReceiverThread, "DSU_Receiver", ctx); + if (!ctx->receiver_thread) { + SDL_DestroyMutex(ctx->slots_mutex); + DSU_CloseSocket(ctx->socket); + DSU_QuitSockets(); + SDL_free(ctx); + SDL_SetError("Failed to create DSU receiver thread"); + return false; + } + + /* Store context globally */ + s_dsu_ctx = ctx; + + /* Request controller info from all slots */ + DSU_RequestControllerInfo(ctx, 0xFF); + + return true; +} + +static int DSU_JoystickGetCount(void) +{ + int count = 0; + int i; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + ctx = s_dsu_ctx; + if (!ctx) { + return 0; + } + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].connected) { + count++; + } + } + SDL_UnlockMutex(mutex); + + return count; +} + +static void DSU_JoystickDetect(void) +{ + Uint64 now; + int i; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + ctx = s_dsu_ctx; + if (!ctx) { + return; + } + + /* Periodically request controller info and re-subscribe to data */ + now = SDL_GetTicks(); + if (now - ctx->last_request_time >= 500) { /* Request more frequently */ + DSU_RequestControllerInfo(ctx, (Uint8)0xFF); + + /* Re-subscribe to data for detected controllers */ + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].detected || ctx->slots[i].connected) { + DSU_RequestControllerData(ctx, (Uint8)i); + } + } + + ctx->last_request_time = now; + } + + /* Process pending joystick additions (SDL holds joystick lock during Detect) */ + /* First, collect which controllers need to be added while holding the mutex */ + struct { + SDL_JoystickID instance_id; + int slot; + } pending_adds[DSU_MAX_SLOTS]; + int num_pending = 0; + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].pending_add && ctx->slots[i].detected) { + SDL_Log("DSU: Found pending add for slot %d, instance %d\n", + i, (int)ctx->slots[i].instance_id); + ctx->slots[i].pending_add = false; + /* DON'T mark as connected yet - wait until SDL accepts it */ + pending_adds[num_pending].instance_id = ctx->slots[i].instance_id; + pending_adds[num_pending].slot = i; + num_pending++; + } + } + SDL_UnlockMutex(mutex); + + /* Now notify SDL about the new controllers without holding the mutex */ + for (i = 0; i < num_pending; i++) { + SDL_Log("DSU: About to call SDL_PrivateJoystickAdded for instance %d\n", (int)pending_adds[i].instance_id); + SDL_Log("DSU: Current joystick count = %d\n", DSU_JoystickGetCount()); + SDL_PrivateJoystickAdded(pending_adds[i].instance_id); + SDL_Log("DSU: SDL_PrivateJoystickAdded returned for instance %d\n", (int)pending_adds[i].instance_id); + + /* NOW mark it as connected since SDL accepted it */ + SDL_LockMutex(mutex); + ctx->slots[pending_adds[i].slot].connected = true; + SDL_UnlockMutex(mutex); + + SDL_Log("DSU: New joystick count = %d\n", DSU_JoystickGetCount()); + } + + /* Check for timeouts */ + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if ((ctx->slots[i].detected || ctx->slots[i].connected) && + now - ctx->slots[i].last_packet_time > 5000) { /* Increased timeout */ + /* Controller timed out */ + SDL_Log("DSU: Controller timeout on slot %d (instance %d)\n", i, (int)ctx->slots[i].instance_id); + + /* Notify SDL if it was connected */ + if (ctx->slots[i].connected && ctx->slots[i].instance_id != 0) { + SDL_PrivateJoystickRemoved(ctx->slots[i].instance_id); + } + + /* Clear all state flags */ + ctx->slots[i].detected = false; + ctx->slots[i].connected = false; + ctx->slots[i].pending_add = false; + ctx->slots[i].instance_id = 0; + } + } + SDL_UnlockMutex(mutex); +} + +static const char *DSU_JoystickGetDeviceName(int device_index) +{ + int i, count = 0; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + ctx = s_dsu_ctx; + if (!ctx) { + return NULL; + } + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].connected) { + if (count == device_index) { + SDL_UnlockMutex(mutex); + return ctx->slots[i].name; + } + count++; + } + } + SDL_UnlockMutex(mutex); + + return NULL; +} + +static bool DSU_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) +{ + /* DSU devices are network-based, not USB, so we don't match by VID/PID */ + return false; +} + +static const char *DSU_JoystickGetDevicePath(int device_index) +{ + return NULL; /* No path for network devices */ +} + +static int DSU_JoystickGetDevicePlayerIndex(int device_index) +{ + int i, count = 0; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + ctx = s_dsu_ctx; + if (!ctx) { + return -1; + } + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].connected) { + if (count == device_index) { + SDL_UnlockMutex(mutex); + return i; /* Return slot ID as player index */ + } + count++; + } + } + SDL_UnlockMutex(mutex); + + return -1; +} + +static void DSU_JoystickSetDevicePlayerIndex(int device_index, int player_index) +{ + /* DSU controllers have fixed slots, can't change */ +} + +static SDL_GUID DSU_JoystickGetDeviceGUID(int device_index) +{ + SDL_GUID guid; + int i, count = 0; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + SDL_zero(guid); + + ctx = s_dsu_ctx; + if (!ctx) { + return guid; + } + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].connected) { + if (count == device_index) { + guid = ctx->slots[i].guid; + SDL_UnlockMutex(mutex); + return guid; + } + count++; + } + } + SDL_UnlockMutex(mutex); + + return guid; +} + +static SDL_JoystickID DSU_JoystickGetDeviceInstanceID(int device_index) +{ + int i, count = 0; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + ctx = s_dsu_ctx; + if (!ctx) { + return 0; + } + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->slots[i].connected) { + if (count == device_index) { + SDL_JoystickID id = ctx->slots[i].instance_id; + SDL_UnlockMutex(mutex); + return id; + } + count++; + } + } + SDL_UnlockMutex(mutex); + + return 0; +} + +static bool DSU_JoystickOpen(SDL_Joystick *joystick, int device_index) +{ + DSU_ControllerSlot *slot = NULL; + int i, count = 0; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + + SDL_Log("DSU: JoystickOpen called for device_index %d\n", device_index); + + ctx = s_dsu_ctx; + if (!ctx) { + SDL_SetError("DSU not initialized"); + SDL_Log("DSU: JoystickOpen failed - not initialized\n"); + return false; + } + SDL_Log("DSU: JoystickOpen - context valid\n"); + + if (!joystick) { + SDL_SetError("DSU: NULL joystick pointer"); + SDL_Log("DSU: JoystickOpen failed - NULL joystick\n"); + return false; + } + + /* Find the slot for this device - check detected controllers */ + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (i = 0; i < DSU_MAX_SLOTS; i++) { + /* Look for detected controllers that are about to be connected */ + if (ctx->slots[i].detected && ctx->slots[i].instance_id != 0) { + if (count == device_index) { + slot = &ctx->slots[i]; + SDL_Log("DSU: JoystickOpen found slot %d for device_index %d\n", i, device_index); + break; + } + count++; + } + } + SDL_UnlockMutex(mutex); + + if (!slot) { + SDL_SetError("Invalid DSU device index"); + return false; + } + + joystick->instance_id = slot->instance_id; + joystick->hwdata = (struct joystick_hwdata *)slot; + joystick->nbuttons = 12; /* Standard PS4 buttons */ + joystick->naxes = 6; /* LX, LY, RX, RY, L2, R2 */ + joystick->nhats = 1; /* D-Pad */ + + /* Set up touchpad if available */ + if (slot->has_touchpad) { + joystick->ntouchpads = 1; + joystick->touchpads = (SDL_JoystickTouchpadInfo *)SDL_calloc(1, sizeof(SDL_JoystickTouchpadInfo)); + if (joystick->touchpads) { + joystick->touchpads[0].nfingers = 2; /* DSU supports 2 fingers */ + } else { + joystick->ntouchpads = 0; /* Failed to allocate, disable touchpad */ + } + } + + /* Register sensors if available */ + SDL_Log("DSU: JoystickOpen - About to register sensors\n"); + if (slot->has_gyro || (slot->model == DSU_MODEL_FULL_GYRO) || (slot->model == DSU_MODEL_PARTIAL_GYRO)) { + /* DSU reports gyro at varying rates, but typically 250-1000Hz for DS4/DS5 */ + SDL_Log("DSU: Adding GYRO sensor\n"); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); + slot->has_gyro = true; + } + if (slot->has_accel || (slot->model == DSU_MODEL_FULL_GYRO)) { + /* DSU reports accelerometer at same rate as gyro */ + SDL_Log("DSU: Adding ACCEL sensor\n"); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + slot->has_accel = true; + } + + SDL_Log("DSU: JoystickOpen completed successfully for slot %d, hwdata=%p\n", slot->slot_id, slot); + return true; +} + +static bool DSU_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + DSU_ControllerSlot *slot = (DSU_ControllerSlot *)joystick->hwdata; + DSU_RumblePacket packet; + struct sockaddr_in server; + struct DSU_Context_t *ctx; + + ctx = s_dsu_ctx; + if (!ctx || !slot || !slot->connected) { + SDL_SetError("DSU controller not available"); + return false; + } + + /* Build rumble packet */ + SDL_memset(&packet, 0, sizeof(packet)); + SDL_memcpy(packet.header.magic, DSU_MAGIC_CLIENT, 4); + packet.header.version = SDL_Swap16LE(DSU_PROTOCOL_VERSION); + packet.header.length = SDL_Swap16LE((Uint16)(sizeof(packet) - sizeof(DSU_Header))); + packet.header.client_id = SDL_Swap32LE(ctx->client_id); + packet.header.message_type = SDL_Swap32LE(DSU_MSG_RUMBLE); + + /* Set rumble values */ + packet.slot = slot->slot_id; + packet.motor_left = (Uint8)(low_frequency_rumble >> 8); /* Convert from 16-bit to 8-bit */ + packet.motor_right = (Uint8)(high_frequency_rumble >> 8); + + /* Calculate CRC32 */ + packet.header.crc32 = 0; + packet.header.crc32 = SDL_Swap32LE(SDL_crc32(0, &packet, sizeof(packet))); + + /* Send to server */ + SDL_memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_port = DSU_htons(ctx->server_port); + server.sin_addr.s_addr = DSU_ipv4_addr(ctx->server_address); + if ((sendto)(ctx->socket, (const char*)&packet, (int)sizeof(packet), 0, + (struct sockaddr *)&server, (int)sizeof(server)) < 0) { + SDL_SetError("Failed to send rumble packet"); + return false; + } + + return true; +} + +static bool DSU_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + SDL_Unsupported(); + return false; +} + +static bool DSU_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + SDL_Unsupported(); + return false; +} + +static bool DSU_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) +{ + SDL_Unsupported(); + return false; +} + +static bool DSU_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) +{ + DSU_ControllerSlot *slot = (DSU_ControllerSlot *)joystick->hwdata; + + /* Sensors are always enabled if available */ + if (!(slot->has_gyro || slot->has_accel)) { + SDL_Unsupported(); + return false; + } + return true; +} + +static void DSU_JoystickUpdate(SDL_Joystick *joystick) +{ + DSU_ControllerSlot *slot = (DSU_ControllerSlot *)joystick->hwdata; + struct DSU_Context_t *ctx; + SDL_Mutex *mutex; + Uint64 timestamp; + int i; + + if (!slot) { + SDL_Log("DSU: JoystickUpdate called with NULL slot\n"); + return; + } + + if (!slot->connected) { + SDL_Log("DSU: JoystickUpdate called for disconnected slot %d\n", slot->slot_id); + return; + } + + ctx = s_dsu_ctx; + if (!ctx || !ctx->slots_mutex) { + SDL_Log("DSU: JoystickUpdate called but context invalid\n"); + return; + } + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + + /* Get current timestamp */ + timestamp = SDL_GetTicks(); + + /* Log current input state */ + SDL_Log("DSU UPDATE: Slot %d buttons=0x%04x", slot->slot_id, slot->buttons); + + /* Update buttons with names */ + const char* button_names[] = { + "Cross/A", "Circle/B", "Square/X", "Triangle/Y", + "L1/LB", "R1/RB", "Share/Back", "Options/Start", + "L3/LSClick", "R3/RSClick", "PS/Home", "Touchpad" + }; + for (i = 0; i < 12; i++) { + bool pressed = (slot->buttons & (1 << i)) ? true : false; + if (pressed) { + SDL_Log(" Button %d (%s): PRESSED", i, button_names[i]); + } + SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, pressed); + } + + /* Update axes with detailed logging */ + SDL_Log("DSU UPDATE: Axes - LX=%d LY=%d RX=%d RY=%d L2=%d R2=%d", + slot->axes[0], slot->axes[1], slot->axes[2], + slot->axes[3], slot->axes[4], slot->axes[5]); + for (i = 0; i < 6; i++) { + SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, slot->axes[i]); + } + + /* Update hat (D-Pad) */ + const char* hat_str = "CENTERED"; + if (slot->hat & SDL_HAT_UP) { + if (slot->hat & SDL_HAT_LEFT) hat_str = "UP-LEFT"; + else if (slot->hat & SDL_HAT_RIGHT) hat_str = "UP-RIGHT"; + else hat_str = "UP"; + } else if (slot->hat & SDL_HAT_DOWN) { + if (slot->hat & SDL_HAT_LEFT) hat_str = "DOWN-LEFT"; + else if (slot->hat & SDL_HAT_RIGHT) hat_str = "DOWN-RIGHT"; + else hat_str = "DOWN"; + } else if (slot->hat & SDL_HAT_LEFT) { + hat_str = "LEFT"; + } else if (slot->hat & SDL_HAT_RIGHT) { + hat_str = "RIGHT"; + } + if (slot->hat != SDL_HAT_CENTERED) { + SDL_Log(" D-Pad: %s (0x%02x)", hat_str, slot->hat); + } + SDL_SendJoystickHat(timestamp, joystick, 0, slot->hat); + + /* Update touchpad if available */ + if (slot->has_touchpad && joystick->ntouchpads > 0) { + /* DS4/DS5 touchpad resolution is typically 1920x943 */ + const float TOUCHPAD_WIDTH = 1920.0f; + const float TOUCHPAD_HEIGHT = 943.0f; + + /* First touch point */ + bool touchpad_down = slot->touch1_active; + float touchpad_x = (float)slot->touch1_x / TOUCHPAD_WIDTH; + float touchpad_y = (float)slot->touch1_y / TOUCHPAD_HEIGHT; + + if (touchpad_down) { + SDL_Log(" TOUCHPAD[0]: Active X=%d Y=%d (%.2f, %.2f)", + slot->touch1_x, slot->touch1_y, touchpad_x, touchpad_y); + } + + /* Clamp to valid range */ + if (touchpad_x < 0.0f) touchpad_x = 0.0f; + if (touchpad_x > 1.0f) touchpad_x = 1.0f; + if (touchpad_y < 0.0f) touchpad_y = 0.0f; + if (touchpad_y > 1.0f) touchpad_y = 1.0f; + + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, + touchpad_x, touchpad_y, + touchpad_down ? 1.0f : 0.0f); + + /* Second touch point */ + touchpad_down = slot->touch2_active; + touchpad_x = (float)slot->touch2_x / TOUCHPAD_WIDTH; + touchpad_y = (float)slot->touch2_y / TOUCHPAD_HEIGHT; + + if (touchpad_down) { + SDL_Log(" TOUCHPAD[1]: Active X=%d Y=%d (%.2f, %.2f)", + slot->touch2_x, slot->touch2_y, touchpad_x, touchpad_y); + } + + /* Clamp to valid range */ + if (touchpad_x < 0.0f) touchpad_x = 0.0f; + if (touchpad_x > 1.0f) touchpad_x = 1.0f; + if (touchpad_y < 0.0f) touchpad_y = 0.0f; + if (touchpad_y > 1.0f) touchpad_y = 1.0f; + + SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, + touchpad_x, touchpad_y, + touchpad_down ? 1.0f : 0.0f); + } + + /* Update battery level */ + const char* battery_str = "Unknown"; + switch (slot->battery) { + case DSU_BATTERY_DYING: + battery_str = "Dying (0-10%)"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 10); + break; + case DSU_BATTERY_LOW: + battery_str = "Low (10-40%)"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 25); + break; + case DSU_BATTERY_MEDIUM: + battery_str = "Medium (40-70%)"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 55); + break; + case DSU_BATTERY_HIGH: + battery_str = "High (70-100%)"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 85); + break; + case DSU_BATTERY_FULL: + battery_str = "Full (100%)"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 100); + break; + case DSU_BATTERY_CHARGING: + battery_str = "Charging"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_CHARGING, -1); + break; + case DSU_BATTERY_CHARGED: + battery_str = "Charged"; + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_CHARGING, 100); + break; + default: + SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_UNKNOWN, -1); + break; + } + static Uint8 last_battery = 0xFF; + if (slot->battery != last_battery) { + SDL_Log(" BATTERY: %s (0x%02x)", battery_str, slot->battery); + last_battery = slot->battery; + } + + /* Update sensors if available */ + if (slot->has_gyro) { + SDL_Log(" GYRO: X=%.3f Y=%.3f Z=%.3f rad/s", + slot->gyro[0], slot->gyro[1], slot->gyro[2]); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, + slot->motion_timestamp, slot->gyro, 3); + } + if (slot->has_accel) { + SDL_Log(" ACCEL: X=%.3f Y=%.3f Z=%.3f m/s²", + slot->accel[0], slot->accel[1], slot->accel[2]); + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, + slot->motion_timestamp, slot->accel, 3); + } + + SDL_UnlockMutex(mutex); +} + +static void DSU_JoystickClose(SDL_Joystick *joystick) +{ + /* Free touchpad info if allocated */ + if (joystick->touchpads) { + SDL_free(joystick->touchpads); + joystick->touchpads = NULL; + joystick->ntouchpads = 0; + } + + joystick->hwdata = NULL; +} + +static void DSU_JoystickQuit(void) +{ + struct DSU_Context_t *ctx; + + SDL_Log("DSU: JoystickQuit called\n"); + + ctx = s_dsu_ctx; + if (!ctx) { + return; + } + + /* Clear the global pointer first to prevent access during shutdown */ + s_dsu_ctx = NULL; + + /* Stop receiver thread */ + if (SDL_GetAtomicInt(&ctx->running) != 0) { + SDL_SetAtomicInt(&ctx->running, 0); + } + + /* Close socket to interrupt any blocking recvfrom */ + if (ctx->socket != DSU_INVALID_SOCKET) { + DSU_CloseSocket(ctx->socket); + ctx->socket = DSU_INVALID_SOCKET; + } + + /* Now wait for thread to finish */ + if (ctx->receiver_thread) { + SDL_Log("DSU: Waiting for receiver thread to finish...\n"); + SDL_WaitThread(ctx->receiver_thread, NULL); + ctx->receiver_thread = NULL; + SDL_Log("DSU: Receiver thread finished\n"); + } + + /* Clean up sockets */ + DSU_QuitSockets(); + + /* Clean up mutex */ + if (ctx->slots_mutex) { + SDL_DestroyMutex(ctx->slots_mutex); + } + + /* Free context */ + SDL_free(ctx); + SDL_Log("DSU: Quit complete\n"); +} + +static bool DSU_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) +{ + /* DSU controllers map well to standard gamepad layout */ + return false; /* Use default mapping */ +} + +/* Export the driver */ +SDL_JoystickDriver SDL_DSU_JoystickDriver = { + DSU_JoystickInit, + DSU_JoystickGetCount, + DSU_JoystickDetect, + DSU_JoystickIsDevicePresent, + DSU_JoystickGetDeviceName, + DSU_JoystickGetDevicePath, + NULL, /* GetDeviceSteamVirtualGamepadSlot */ + DSU_JoystickGetDevicePlayerIndex, + DSU_JoystickSetDevicePlayerIndex, + DSU_JoystickGetDeviceGUID, + DSU_JoystickGetDeviceInstanceID, + DSU_JoystickOpen, + DSU_JoystickRumble, + DSU_JoystickRumbleTriggers, + DSU_JoystickSetLED, + DSU_JoystickSendEffect, + DSU_JoystickSetSensorsEnabled, + DSU_JoystickUpdate, + DSU_JoystickClose, + DSU_JoystickQuit, + DSU_JoystickGetGamepadMapping +}; + #endif /* SDL_JOYSTICK_DSU */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/joystick/dsu/SDL_dsujoystick_c.h b/src/joystick/dsu/SDL_dsujoystick_c.h index a1f30ecab7e0b..b41b682408b30 100644 --- a/src/joystick/dsu/SDL_dsujoystick_c.h +++ b/src/joystick/dsu/SDL_dsujoystick_c.h @@ -43,7 +43,7 @@ extern SDL_JoystickDriver SDL_DSU_JoystickDriver; #endif #include typedef SOCKET dsu_socket_t; -#else +#else typedef int dsu_socket_t; #endif @@ -55,26 +55,26 @@ typedef struct DSU_ControllerSlot { SDL_JoystickID instance_id; SDL_GUID guid; char name[128]; - + /* DSU protocol data */ Uint8 slot_id; Uint8 mac[6]; Uint8 battery; DSU_DeviceModel model; DSU_ConnectionType connection; - + /* Controller state */ Uint16 buttons; Sint16 axes[6]; /* LX, LY, RX, RY, L2, R2 */ Uint8 hat; - + /* Motion data */ bool has_gyro; bool has_accel; float gyro[3]; /* Pitch, Yaw, Roll in rad/s */ float accel[3]; /* X, Y, Z in m/s² */ Uint64 motion_timestamp; - + /* Touch data */ bool has_touchpad; bool touch1_active; @@ -83,11 +83,11 @@ typedef struct DSU_ControllerSlot { Uint8 touch2_id; Uint16 touch1_x, touch1_y; Uint16 touch2_x, touch2_y; - + /* Timing */ Uint64 last_packet_time; Uint32 packet_number; - + /* State change flags for deferred notifications */ bool pending_add; } DSU_ControllerSlot; @@ -97,67 +97,21 @@ typedef struct DSU_Context_t { dsu_socket_t socket; SDL_Thread *receiver_thread; SDL_AtomicInt running; - + /* Server configuration */ char server_address[256]; Uint16 server_port; Uint16 client_port; Uint32 client_id; - + /* Controller slots (4 max per DSU protocol) */ DSU_ControllerSlot slots[DSU_MAX_SLOTS]; SDL_Mutex *slots_mutex; - + /* Timing for periodic updates */ Uint64 last_request_time; } DSU_Context; -/* Global DSU context */ -extern DSU_Context *s_dsu_ctx; - -/* Socket helpers - only available when implementing */ -#ifdef IN_JOYSTICK_DSU_ - -#ifdef __cplusplus -extern "C" { -#endif - - SDL_FORCE_INLINE Uint16 DSU_htons(Uint16 x) { return SDL_Swap16BE(x); } - SDL_FORCE_INLINE Uint32 DSU_htonl(Uint32 x) { return SDL_Swap32BE(x); } - SDL_FORCE_INLINE Uint32 DSU_ipv4_addr(const char *ip) -{ - unsigned int a, b, c, d; - if (SDL_sscanf(ip, "%u.%u.%u.%u", &a, &b, &c, &d) == 4) { - Uint32 v = ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) | (d & 0xFF); - return DSU_htonl(v); - } - /* Fallback to 127.0.0.1 */ - return DSU_htonl(0x7F000001u); -} - -#ifdef __cplusplus -} -#endif - -#ifdef _WIN32 -#define DSU_SOCKET_ERROR SOCKET_ERROR -#define DSU_INVALID_SOCKET INVALID_SOCKET -#else -#define DSU_SOCKET_ERROR -1 -#define DSU_INVALID_SOCKET -1 -#endif - -/* Socket helpers */ -int DSU_InitSockets(void); -void DSU_QuitSockets(void); -dsu_socket_t DSU_CreateSocket(Uint16 port); -void DSU_CloseSocket(dsu_socket_t socket); - -/* CRC32 calculation */ -Uint32 DSU_CalculateCRC32(const Uint8 *data, size_t length); - -#endif /* IN_JOYSTICK_DSU_ */ - #endif /* SDL_JOYSTICK_DSU */ #endif /* SDL_dsujoystick_c_h_ */ diff --git a/src/joystick/dsu/SDL_dsujoystick_driver.c b/src/joystick/dsu/SDL_dsujoystick_driver.c deleted file mode 100644 index 7b8800f1f0b79..0000000000000 --- a/src/joystick/dsu/SDL_dsujoystick_driver.c +++ /dev/null @@ -1,836 +0,0 @@ -/* - Simple DirectMedia Layer - Copyright (C) 1997-2025 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -*/ - -/* Include socket headers before SDL to avoid macro conflicts */ -#ifdef _WIN32 -#include -#include -#ifdef _MSC_VER -#pragma comment(lib, "ws2_32.lib") -#endif -#else -/* Unix-like systems including Haiku */ -#include -#include -#include -#include -#include -#endif - -#include "SDL_internal.h" - -/* Define this before including our header to get internal definitions */ -#define IN_JOYSTICK_DSU_ - -/* Include protocol definitions and shared DSU header for types */ -#include "SDL_dsuprotocol.h" -#include "SDL_dsujoystick_c.h" - -#ifdef SDL_JOYSTICK_DSU - -/* Forward declare the driver */ -extern SDL_JoystickDriver SDL_DSU_JoystickDriver; - -/* Then include system joystick headers */ -#include "../SDL_sysjoystick.h" -#include "../SDL_joystick_c.h" -/* Ensure timer prototypes are visible for SDL_GetTicks64 */ -#include - -/* Additional Windows headers already included at the top */ -#ifdef _WIN32 -#define _WINSOCK_DEPRECATED_NO_WARNINGS -#endif - -/* Global DSU context pointer */ -struct DSU_Context_t *s_dsu_ctx = NULL; - -/* Forward declarations */ -extern int SDLCALL DSU_ReceiverThread(void *data); -extern void DSU_RequestControllerInfo(struct DSU_Context_t *ctx, Uint8 slot); -extern void DSU_RequestControllerData(struct DSU_Context_t *ctx, Uint8 slot); - -/* Socket helpers */ -extern int DSU_InitSockets(void); -extern void DSU_QuitSockets(void); -extern dsu_socket_t DSU_CreateSocket(Uint16 port); -extern void DSU_CloseSocket(dsu_socket_t socket); -extern Uint32 DSU_CalculateCRC32(const Uint8 *data, size_t length); - -/* Driver functions */ -static bool DSU_JoystickInit(void) -{ - const char *enabled; - const char *server; - const char *server_port; - const char *client_port; - struct DSU_Context_t *ctx; - - /* Check if DSU is enabled */ - enabled = SDL_GetHint(SDL_HINT_JOYSTICK_DSU); - if (enabled && SDL_atoi(enabled) == 0) { - return true; /* DSU disabled */ - } - - /* Allocate context */ - ctx = (struct DSU_Context_t *)SDL_calloc(1, sizeof(struct DSU_Context_t)); - if (!ctx) { - SDL_OutOfMemory(); - return false; - } - - /* Get configuration from hints with fallbacks */ - server = SDL_GetHint(SDL_HINT_DSU_SERVER); - if (!server || !*server) { - server = DSU_SERVER_ADDRESS_DEFAULT; - } - SDL_strlcpy(ctx->server_address, server, - sizeof(ctx->server_address)); - - server_port = SDL_GetHint(SDL_HINT_DSU_SERVER_PORT); - if (server_port && *server_port) { - ctx->server_port = (Uint16)SDL_atoi(server_port); - } else { - ctx->server_port = DSU_SERVER_PORT_DEFAULT; - } - - client_port = SDL_GetHint(SDL_HINT_DSU_CLIENT_PORT); - if (client_port && *client_port) { - ctx->client_port = (Uint16)SDL_atoi(client_port); - } else { - ctx->client_port = DSU_CLIENT_PORT_DEFAULT; - } - - ctx->client_id = (Uint32)SDL_GetTicks(); - - /* Initialize sockets */ - if (DSU_InitSockets() != 0) { - SDL_free(ctx); - return false; - } - - /* Create UDP socket */ - ctx->socket = DSU_CreateSocket(ctx->client_port); - if (ctx->socket == DSU_INVALID_SOCKET) { - DSU_QuitSockets(); - SDL_free(ctx); - return false; - } - - /* Create mutex */ - ctx->slots_mutex = SDL_CreateMutex(); - if (!ctx->slots_mutex) { - DSU_CloseSocket(ctx->socket); - DSU_QuitSockets(); - SDL_free(ctx); - SDL_OutOfMemory(); - return false; - } - - /* Start receiver thread */ - SDL_SetAtomicInt(&ctx->running, 1); - ctx->receiver_thread = SDL_CreateThread( - DSU_ReceiverThread, "DSU_Receiver", ctx); - if (!ctx->receiver_thread) { - SDL_DestroyMutex(ctx->slots_mutex); - DSU_CloseSocket(ctx->socket); - DSU_QuitSockets(); - SDL_free(ctx); - SDL_SetError("Failed to create DSU receiver thread"); - return false; - } - - /* Store context globally */ - s_dsu_ctx = ctx; - - /* Request controller info from all slots */ - DSU_RequestControllerInfo(ctx, 0xFF); - - return true; -} - -static int DSU_JoystickGetCount(void) -{ - int count = 0; - int i; - struct DSU_Context_t *ctx; - SDL_Mutex *mutex; - - ctx = s_dsu_ctx; - if (!ctx) { - return 0; - } - - mutex = ctx->slots_mutex; - SDL_LockMutex(mutex); - for (i = 0; i < DSU_MAX_SLOTS; i++) { - if (ctx->slots[i].connected) { - count++; - } - } - SDL_UnlockMutex(mutex); - - return count; -} - -static void DSU_JoystickDetect(void) -{ - Uint64 now; - int i; - struct DSU_Context_t *ctx; - SDL_Mutex *mutex; - - ctx = s_dsu_ctx; - if (!ctx) { - return; - } - - /* Periodically request controller info and re-subscribe to data */ - now = SDL_GetTicks(); - if (now - ctx->last_request_time >= 500) { /* Request more frequently */ - DSU_RequestControllerInfo(ctx, (Uint8)0xFF); - - /* Re-subscribe to data for detected controllers */ - for (i = 0; i < DSU_MAX_SLOTS; i++) { - if (ctx->slots[i].detected || ctx->slots[i].connected) { - DSU_RequestControllerData(ctx, (Uint8)i); - } - } - - ctx->last_request_time = now; - } - - /* Process pending joystick additions (SDL holds joystick lock during Detect) */ - /* First, collect which controllers need to be added while holding the mutex */ - struct { - SDL_JoystickID instance_id; - int slot; - } pending_adds[DSU_MAX_SLOTS]; - int num_pending = 0; - - mutex = ctx->slots_mutex; - SDL_LockMutex(mutex); - for (i = 0; i < DSU_MAX_SLOTS; i++) { - if (ctx->slots[i].pending_add && ctx->slots[i].detected) { - SDL_Log("DSU: Found pending add for slot %d, instance %d\n", - i, (int)ctx->slots[i].instance_id); - ctx->slots[i].pending_add = false; - /* DON'T mark as connected yet - wait until SDL accepts it */ - pending_adds[num_pending].instance_id = ctx->slots[i].instance_id; - pending_adds[num_pending].slot = i; - num_pending++; - } - } - SDL_UnlockMutex(mutex); - - /* Now notify SDL about the new controllers without holding the mutex */ - for (i = 0; i < num_pending; i++) { - SDL_Log("DSU: About to call SDL_PrivateJoystickAdded for instance %d\n", (int)pending_adds[i].instance_id); - SDL_Log("DSU: Current joystick count = %d\n", DSU_JoystickGetCount()); - SDL_PrivateJoystickAdded(pending_adds[i].instance_id); - SDL_Log("DSU: SDL_PrivateJoystickAdded returned for instance %d\n", (int)pending_adds[i].instance_id); - - /* NOW mark it as connected since SDL accepted it */ - SDL_LockMutex(mutex); - ctx->slots[pending_adds[i].slot].connected = true; - SDL_UnlockMutex(mutex); - - SDL_Log("DSU: New joystick count = %d\n", DSU_JoystickGetCount()); - } - - /* Check for timeouts */ - SDL_LockMutex(mutex); - for (i = 0; i < DSU_MAX_SLOTS; i++) { - if ((ctx->slots[i].detected || ctx->slots[i].connected) && - now - ctx->slots[i].last_packet_time > 5000) { /* Increased timeout */ - /* Controller timed out */ - SDL_Log("DSU: Controller timeout on slot %d (instance %d)\n", i, (int)ctx->slots[i].instance_id); - - /* Notify SDL if it was connected */ - if (ctx->slots[i].connected && ctx->slots[i].instance_id != 0) { - SDL_PrivateJoystickRemoved(ctx->slots[i].instance_id); - } - - /* Clear all state flags */ - ctx->slots[i].detected = false; - ctx->slots[i].connected = false; - ctx->slots[i].pending_add = false; - ctx->slots[i].instance_id = 0; - } - } - SDL_UnlockMutex(mutex); -} - -static const char *DSU_JoystickGetDeviceName(int device_index) -{ - int i, count = 0; - struct DSU_Context_t *ctx; - SDL_Mutex *mutex; - - ctx = s_dsu_ctx; - if (!ctx) { - return NULL; - } - - mutex = ctx->slots_mutex; - SDL_LockMutex(mutex); - for (i = 0; i < DSU_MAX_SLOTS; i++) { - if (ctx->slots[i].connected) { - if (count == device_index) { - SDL_UnlockMutex(mutex); - return ctx->slots[i].name; - } - count++; - } - } - SDL_UnlockMutex(mutex); - - return NULL; -} - -static bool DSU_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) -{ - /* DSU devices are network-based, not USB, so we don't match by VID/PID */ - return false; -} - -static const char *DSU_JoystickGetDevicePath(int device_index) -{ - return NULL; /* No path for network devices */ -} - -static int DSU_JoystickGetDevicePlayerIndex(int device_index) -{ - int i, count = 0; - struct DSU_Context_t *ctx; - SDL_Mutex *mutex; - - ctx = s_dsu_ctx; - if (!ctx) { - return -1; - } - - mutex = ctx->slots_mutex; - SDL_LockMutex(mutex); - for (i = 0; i < DSU_MAX_SLOTS; i++) { - if (ctx->slots[i].connected) { - if (count == device_index) { - SDL_UnlockMutex(mutex); - return i; /* Return slot ID as player index */ - } - count++; - } - } - SDL_UnlockMutex(mutex); - - return -1; -} - -static void DSU_JoystickSetDevicePlayerIndex(int device_index, int player_index) -{ - /* DSU controllers have fixed slots, can't change */ -} - -static SDL_GUID DSU_JoystickGetDeviceGUID(int device_index) -{ - SDL_GUID guid; - int i, count = 0; - struct DSU_Context_t *ctx; - SDL_Mutex *mutex; - - SDL_zero(guid); - - ctx = s_dsu_ctx; - if (!ctx) { - return guid; - } - - mutex = ctx->slots_mutex; - SDL_LockMutex(mutex); - for (i = 0; i < DSU_MAX_SLOTS; i++) { - if (ctx->slots[i].connected) { - if (count == device_index) { - guid = ctx->slots[i].guid; - SDL_UnlockMutex(mutex); - return guid; - } - count++; - } - } - SDL_UnlockMutex(mutex); - - return guid; -} - -static SDL_JoystickID DSU_JoystickGetDeviceInstanceID(int device_index) -{ - int i, count = 0; - struct DSU_Context_t *ctx; - SDL_Mutex *mutex; - - ctx = s_dsu_ctx; - if (!ctx) { - return 0; - } - - mutex = ctx->slots_mutex; - SDL_LockMutex(mutex); - for (i = 0; i < DSU_MAX_SLOTS; i++) { - if (ctx->slots[i].connected) { - if (count == device_index) { - SDL_JoystickID id = ctx->slots[i].instance_id; - SDL_UnlockMutex(mutex); - return id; - } - count++; - } - } - SDL_UnlockMutex(mutex); - - return 0; -} - -static bool DSU_JoystickOpen(SDL_Joystick *joystick, int device_index) -{ - DSU_ControllerSlot *slot = NULL; - int i, count = 0; - struct DSU_Context_t *ctx; - SDL_Mutex *mutex; - - SDL_Log("DSU: JoystickOpen called for device_index %d\n", device_index); - - ctx = s_dsu_ctx; - if (!ctx) { - SDL_SetError("DSU not initialized"); - SDL_Log("DSU: JoystickOpen failed - not initialized\n"); - return false; - } - SDL_Log("DSU: JoystickOpen - context valid\n"); - - if (!joystick) { - SDL_SetError("DSU: NULL joystick pointer"); - SDL_Log("DSU: JoystickOpen failed - NULL joystick\n"); - return false; - } - - /* Find the slot for this device - check detected controllers */ - mutex = ctx->slots_mutex; - SDL_LockMutex(mutex); - for (i = 0; i < DSU_MAX_SLOTS; i++) { - /* Look for detected controllers that are about to be connected */ - if (ctx->slots[i].detected && ctx->slots[i].instance_id != 0) { - if (count == device_index) { - slot = &ctx->slots[i]; - SDL_Log("DSU: JoystickOpen found slot %d for device_index %d\n", i, device_index); - break; - } - count++; - } - } - SDL_UnlockMutex(mutex); - - if (!slot) { - SDL_SetError("Invalid DSU device index"); - return false; - } - - joystick->instance_id = slot->instance_id; - joystick->hwdata = (struct joystick_hwdata *)slot; - joystick->nbuttons = 12; /* Standard PS4 buttons */ - joystick->naxes = 6; /* LX, LY, RX, RY, L2, R2 */ - joystick->nhats = 1; /* D-Pad */ - - /* Set up touchpad if available */ - if (slot->has_touchpad) { - joystick->ntouchpads = 1; - joystick->touchpads = (SDL_JoystickTouchpadInfo *)SDL_calloc(1, sizeof(SDL_JoystickTouchpadInfo)); - if (joystick->touchpads) { - joystick->touchpads[0].nfingers = 2; /* DSU supports 2 fingers */ - } else { - joystick->ntouchpads = 0; /* Failed to allocate, disable touchpad */ - } - } - - /* Register sensors if available */ - SDL_Log("DSU: JoystickOpen - About to register sensors\n"); - if (slot->has_gyro || (slot->model == DSU_MODEL_FULL_GYRO) || (slot->model == DSU_MODEL_PARTIAL_GYRO)) { - /* DSU reports gyro at varying rates, but typically 250-1000Hz for DS4/DS5 */ - SDL_Log("DSU: Adding GYRO sensor\n"); - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); - slot->has_gyro = true; - } - if (slot->has_accel || (slot->model == DSU_MODEL_FULL_GYRO)) { - /* DSU reports accelerometer at same rate as gyro */ - SDL_Log("DSU: Adding ACCEL sensor\n"); - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); - slot->has_accel = true; - } - - SDL_Log("DSU: JoystickOpen completed successfully for slot %d, hwdata=%p\n", slot->slot_id, slot); - return true; -} - -static bool DSU_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) -{ - DSU_ControllerSlot *slot = (DSU_ControllerSlot *)joystick->hwdata; - DSU_RumblePacket packet; - struct sockaddr_in server; - struct DSU_Context_t *ctx; - - ctx = s_dsu_ctx; - if (!ctx || !slot || !slot->connected) { - SDL_SetError("DSU controller not available"); - return false; - } - - /* Build rumble packet */ - SDL_memset(&packet, 0, sizeof(packet)); - SDL_memcpy(packet.header.magic, DSU_MAGIC_CLIENT, 4); - packet.header.version = SDL_Swap16LE(DSU_PROTOCOL_VERSION); - packet.header.length = SDL_Swap16LE((Uint16)(sizeof(packet) - sizeof(DSU_Header))); - packet.header.client_id = SDL_Swap32LE(ctx->client_id); - packet.header.message_type = SDL_Swap32LE(DSU_MSG_RUMBLE); - - /* Set rumble values */ - packet.slot = slot->slot_id; - packet.motor_left = (Uint8)(low_frequency_rumble >> 8); /* Convert from 16-bit to 8-bit */ - packet.motor_right = (Uint8)(high_frequency_rumble >> 8); - - /* Calculate CRC32 */ - packet.header.crc32 = 0; - packet.header.crc32 = SDL_Swap32LE(DSU_CalculateCRC32((Uint8*)&packet, sizeof(packet))); - - /* Send to server */ - SDL_memset(&server, 0, sizeof(server)); - server.sin_family = AF_INET; - server.sin_port = DSU_htons(ctx->server_port); - server.sin_addr.s_addr = DSU_ipv4_addr(ctx->server_address); - if ((sendto)(ctx->socket, (const char*)&packet, (int)sizeof(packet), 0, - (struct sockaddr *)&server, (int)sizeof(server)) < 0) { - SDL_SetError("Failed to send rumble packet"); - return false; - } - - return true; -} - -static bool DSU_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) -{ - SDL_Unsupported(); - return false; -} - -static bool DSU_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) -{ - SDL_Unsupported(); - return false; -} - -static bool DSU_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) -{ - SDL_Unsupported(); - return false; -} - -static bool DSU_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) -{ - DSU_ControllerSlot *slot = (DSU_ControllerSlot *)joystick->hwdata; - - /* Sensors are always enabled if available */ - if (!(slot->has_gyro || slot->has_accel)) { - SDL_Unsupported(); - return false; - } - return true; -} - -static void DSU_JoystickUpdate(SDL_Joystick *joystick) -{ - DSU_ControllerSlot *slot = (DSU_ControllerSlot *)joystick->hwdata; - struct DSU_Context_t *ctx; - SDL_Mutex *mutex; - Uint64 timestamp; - int i; - - if (!slot) { - SDL_Log("DSU: JoystickUpdate called with NULL slot\n"); - return; - } - - if (!slot->connected) { - SDL_Log("DSU: JoystickUpdate called for disconnected slot %d\n", slot->slot_id); - return; - } - - ctx = s_dsu_ctx; - if (!ctx || !ctx->slots_mutex) { - SDL_Log("DSU: JoystickUpdate called but context invalid\n"); - return; - } - - mutex = ctx->slots_mutex; - SDL_LockMutex(mutex); - - /* Get current timestamp */ - timestamp = SDL_GetTicks(); - - /* Log current input state */ - SDL_Log("DSU UPDATE: Slot %d buttons=0x%04x", slot->slot_id, slot->buttons); - - /* Update buttons with names */ - const char* button_names[] = { - "Cross/A", "Circle/B", "Square/X", "Triangle/Y", - "L1/LB", "R1/RB", "Share/Back", "Options/Start", - "L3/LSClick", "R3/RSClick", "PS/Home", "Touchpad" - }; - for (i = 0; i < 12; i++) { - bool pressed = (slot->buttons & (1 << i)) ? true : false; - if (pressed) { - SDL_Log(" Button %d (%s): PRESSED", i, button_names[i]); - } - SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, pressed); - } - - /* Update axes with detailed logging */ - SDL_Log("DSU UPDATE: Axes - LX=%d LY=%d RX=%d RY=%d L2=%d R2=%d", - slot->axes[0], slot->axes[1], slot->axes[2], - slot->axes[3], slot->axes[4], slot->axes[5]); - for (i = 0; i < 6; i++) { - SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, slot->axes[i]); - } - - /* Update hat (D-Pad) */ - const char* hat_str = "CENTERED"; - if (slot->hat & SDL_HAT_UP) { - if (slot->hat & SDL_HAT_LEFT) hat_str = "UP-LEFT"; - else if (slot->hat & SDL_HAT_RIGHT) hat_str = "UP-RIGHT"; - else hat_str = "UP"; - } else if (slot->hat & SDL_HAT_DOWN) { - if (slot->hat & SDL_HAT_LEFT) hat_str = "DOWN-LEFT"; - else if (slot->hat & SDL_HAT_RIGHT) hat_str = "DOWN-RIGHT"; - else hat_str = "DOWN"; - } else if (slot->hat & SDL_HAT_LEFT) { - hat_str = "LEFT"; - } else if (slot->hat & SDL_HAT_RIGHT) { - hat_str = "RIGHT"; - } - if (slot->hat != SDL_HAT_CENTERED) { - SDL_Log(" D-Pad: %s (0x%02x)", hat_str, slot->hat); - } - SDL_SendJoystickHat(timestamp, joystick, 0, slot->hat); - - /* Update touchpad if available */ - if (slot->has_touchpad && joystick->ntouchpads > 0) { - /* DS4/DS5 touchpad resolution is typically 1920x943 */ - const float TOUCHPAD_WIDTH = 1920.0f; - const float TOUCHPAD_HEIGHT = 943.0f; - - /* First touch point */ - bool touchpad_down = slot->touch1_active; - float touchpad_x = (float)slot->touch1_x / TOUCHPAD_WIDTH; - float touchpad_y = (float)slot->touch1_y / TOUCHPAD_HEIGHT; - - if (touchpad_down) { - SDL_Log(" TOUCHPAD[0]: Active X=%d Y=%d (%.2f, %.2f)", - slot->touch1_x, slot->touch1_y, touchpad_x, touchpad_y); - } - - /* Clamp to valid range */ - if (touchpad_x < 0.0f) touchpad_x = 0.0f; - if (touchpad_x > 1.0f) touchpad_x = 1.0f; - if (touchpad_y < 0.0f) touchpad_y = 0.0f; - if (touchpad_y > 1.0f) touchpad_y = 1.0f; - - SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, - touchpad_x, touchpad_y, - touchpad_down ? 1.0f : 0.0f); - - /* Second touch point */ - touchpad_down = slot->touch2_active; - touchpad_x = (float)slot->touch2_x / TOUCHPAD_WIDTH; - touchpad_y = (float)slot->touch2_y / TOUCHPAD_HEIGHT; - - if (touchpad_down) { - SDL_Log(" TOUCHPAD[1]: Active X=%d Y=%d (%.2f, %.2f)", - slot->touch2_x, slot->touch2_y, touchpad_x, touchpad_y); - } - - /* Clamp to valid range */ - if (touchpad_x < 0.0f) touchpad_x = 0.0f; - if (touchpad_x > 1.0f) touchpad_x = 1.0f; - if (touchpad_y < 0.0f) touchpad_y = 0.0f; - if (touchpad_y > 1.0f) touchpad_y = 1.0f; - - SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, - touchpad_x, touchpad_y, - touchpad_down ? 1.0f : 0.0f); - } - - /* Update battery level */ - const char* battery_str = "Unknown"; - switch (slot->battery) { - case DSU_BATTERY_DYING: - battery_str = "Dying (0-10%)"; - SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 10); - break; - case DSU_BATTERY_LOW: - battery_str = "Low (10-40%)"; - SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 25); - break; - case DSU_BATTERY_MEDIUM: - battery_str = "Medium (40-70%)"; - SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 55); - break; - case DSU_BATTERY_HIGH: - battery_str = "High (70-100%)"; - SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 85); - break; - case DSU_BATTERY_FULL: - battery_str = "Full (100%)"; - SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_ON_BATTERY, 100); - break; - case DSU_BATTERY_CHARGING: - battery_str = "Charging"; - SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_CHARGING, -1); - break; - case DSU_BATTERY_CHARGED: - battery_str = "Charged"; - SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_CHARGING, 100); - break; - default: - SDL_SendJoystickPowerInfo(joystick, SDL_POWERSTATE_UNKNOWN, -1); - break; - } - static Uint8 last_battery = 0xFF; - if (slot->battery != last_battery) { - SDL_Log(" BATTERY: %s (0x%02x)", battery_str, slot->battery); - last_battery = slot->battery; - } - - /* Update sensors if available */ - if (slot->has_gyro) { - SDL_Log(" GYRO: X=%.3f Y=%.3f Z=%.3f rad/s", - slot->gyro[0], slot->gyro[1], slot->gyro[2]); - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, - slot->motion_timestamp, slot->gyro, 3); - } - if (slot->has_accel) { - SDL_Log(" ACCEL: X=%.3f Y=%.3f Z=%.3f m/s²", - slot->accel[0], slot->accel[1], slot->accel[2]); - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, - slot->motion_timestamp, slot->accel, 3); - } - - SDL_UnlockMutex(mutex); -} - -static void DSU_JoystickClose(SDL_Joystick *joystick) -{ - /* Free touchpad info if allocated */ - if (joystick->touchpads) { - SDL_free(joystick->touchpads); - joystick->touchpads = NULL; - joystick->ntouchpads = 0; - } - - joystick->hwdata = NULL; -} - -static void DSU_JoystickQuit(void) -{ - struct DSU_Context_t *ctx; - - SDL_Log("DSU: JoystickQuit called\n"); - - ctx = s_dsu_ctx; - if (!ctx) { - return; - } - - /* Clear the global pointer first to prevent access during shutdown */ - s_dsu_ctx = NULL; - - /* Stop receiver thread */ - if (SDL_GetAtomicInt(&ctx->running) != 0) { - SDL_SetAtomicInt(&ctx->running, 0); - } - - /* Close socket to interrupt any blocking recvfrom */ - if (ctx->socket != DSU_INVALID_SOCKET) { - DSU_CloseSocket(ctx->socket); - ctx->socket = DSU_INVALID_SOCKET; - } - - /* Now wait for thread to finish */ - if (ctx->receiver_thread) { - SDL_Log("DSU: Waiting for receiver thread to finish...\n"); - SDL_WaitThread(ctx->receiver_thread, NULL); - ctx->receiver_thread = NULL; - SDL_Log("DSU: Receiver thread finished\n"); - } - - /* Clean up sockets */ - DSU_QuitSockets(); - - /* Clean up mutex */ - if (ctx->slots_mutex) { - SDL_DestroyMutex(ctx->slots_mutex); - } - - /* Free context */ - SDL_free(ctx); - SDL_Log("DSU: Quit complete\n"); -} - -static bool DSU_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) -{ - /* DSU controllers map well to standard gamepad layout */ - return false; /* Use default mapping */ -} - -/* Export the driver */ -SDL_JoystickDriver SDL_DSU_JoystickDriver = { - DSU_JoystickInit, - DSU_JoystickGetCount, - DSU_JoystickDetect, - DSU_JoystickIsDevicePresent, - DSU_JoystickGetDeviceName, - DSU_JoystickGetDevicePath, - NULL, /* GetDeviceSteamVirtualGamepadSlot */ - DSU_JoystickGetDevicePlayerIndex, - DSU_JoystickSetDevicePlayerIndex, - DSU_JoystickGetDeviceGUID, - DSU_JoystickGetDeviceInstanceID, - DSU_JoystickOpen, - DSU_JoystickRumble, - DSU_JoystickRumbleTriggers, - DSU_JoystickSetLED, - DSU_JoystickSendEffect, - DSU_JoystickSetSensorsEnabled, - DSU_JoystickUpdate, - DSU_JoystickClose, - DSU_JoystickQuit, - DSU_JoystickGetGamepadMapping -}; - -#endif /* SDL_JOYSTICK_DSU */ - -/* vi: set ts=4 sw=4 expandtab: */ From 345b9614937ba080f5ad2db4bf93bd540b0995e6 Mon Sep 17 00:00:00 2001 From: danprice142 Date: Sat, 15 Nov 2025 23:35:04 +0000 Subject: [PATCH 09/12] DSU: Fix deprecated inet_addr warning on Windows ARM64 Replace inet_addr() with InetPtonA() on Windows and inet_pton() on Unix-like systems to fix C4996 warnings on Windows ARM64 builds where warnings are treated as errors. --- src/joystick/dsu/SDL_dsujoystick.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index 6cc16fec0ec5e..532441c4b4044 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -37,7 +37,6 @@ /* Additional Windows headers */ #ifdef _WIN32 -#define _WINSOCK_DEPRECATED_NO_WARNINGS #include #ifdef _MSC_VER #pragma comment(lib, "ws2_32.lib") @@ -77,21 +76,42 @@ typedef int socklen_t; #define SERVER_TIMEOUT_INTERVAL 2000 /* ms */ #define GRAVITY_ACCELERATION 9.80665f /* m/s² */ -/* Internal DSU helper macros */ +/* Internal DSU helper macros and functions */ #ifdef _WIN32 #define DSU_htons(x) htons(x) #define DSU_htonl(x) htonl(x) - #define DSU_ipv4_addr(str) inet_addr(str) #define DSU_SOCKET_ERROR SOCKET_ERROR #define DSU_INVALID_SOCKET INVALID_SOCKET #else #define DSU_htons(x) htons(x) #define DSU_htonl(x) htonl(x) - #define DSU_ipv4_addr(str) inet_addr(str) #define DSU_SOCKET_ERROR (-1) #define DSU_INVALID_SOCKET (-1) #endif +/* Helper function to convert IP address string to network byte order */ +static unsigned long DSU_ipv4_addr(const char *str) +{ + struct sockaddr_in addr; + SDL_memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + +#ifdef _WIN32 + /* Use InetPton on Windows to avoid deprecated API warning */ + if (InetPtonA(AF_INET, str, &addr.sin_addr) == 1) { + return addr.sin_addr.s_addr; + } +#else + /* Use inet_pton on Unix-like systems */ + if (inet_pton(AF_INET, str, &addr.sin_addr) == 1) { + return addr.sin_addr.s_addr; + } +#endif + + /* Return INADDR_NONE on error (same as inet_addr would) */ + return (unsigned long)(-1); +} + /* Global DSU context pointer */ struct DSU_Context_t *s_dsu_ctx = NULL; From db33ef9e10a3f682c47cd85d207610d22efbe728 Mon Sep 17 00:00:00 2001 From: danprice142 Date: Sun, 16 Nov 2025 00:29:10 +0000 Subject: [PATCH 10/12] Add DSU support for more platforms Extended DSU support to additional platforms including QNX, RISC OS, PlayStation Vita, PSP, PS2, and Nintendo 3DS. Updated CMakeLists.txt to handle platform-specific dependencies and added platform-specific socket handling, initialization, and cleanup in SDL_dsujoystick.c. Introduced wrappers for network functions and improved error handling for these platforms. --- CMakeLists.txt | 32 ++++- src/joystick/dsu/SDL_dsujoystick.c | 195 +++++++++++++++++++++++++---- 2 files changed, 201 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bf827978f631..74a03cdbe6b8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1377,7 +1377,13 @@ if(SDL_JOYSTICK) endif() # DSU (DualShock UDP) client support - if(SDL_DSU_JOYSTICK AND NOT EMSCRIPTEN AND (WINDOWS OR LINUX OR ANDROID OR HAIKU OR FREEBSD OR NETBSD OR OPENBSD OR MACOS)) + # Supported on platforms with UDP socket support (BSD sockets or WinSock) + # Note: Some of these platforms haven't been fully tested yet but should work in theory + if(SDL_DSU_JOYSTICK AND NOT EMSCRIPTEN AND + (WINDOWS OR LINUX OR ANDROID OR HAIKU OR FREEBSD OR NETBSD OR OPENBSD OR + MACOS OR IOS OR TVOS OR VISIONOS OR WATCHOS OR + QNX OR RISCOS OR + VITA OR PSP OR PS2 OR N3DS)) set(SDL_JOYSTICK_DSU 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.c" @@ -1389,6 +1395,30 @@ if(SDL_JOYSTICK) sdl_link_dependency(dsu LIBS ws2_32) elseif(HAIKU) sdl_link_dependency(dsu LIBS network) + elseif(QNX) + # QNX needs socket library + sdl_link_dependency(dsu LIBS socket) + elseif(VITA) + # PlayStation Vita networking libraries + sdl_link_dependency(dsu LIBS + SceNet_stub + SceNetCtl_stub + ) + elseif(PSP) + # PSP networking libraries + sdl_link_dependency(dsu LIBS + pspnet + pspnet_inet + pspnet_resolver + ) + elseif(PS2) + # PlayStation 2 uses ps2sdk's lwIP + sdl_link_dependency(dsu LIBS + ps2ip + ) + elseif(N3DS) + # Nintendo 3DS networking libraries (if available) + sdl_link_dependency(dsu LIBS ctru) endif() endif() endif() diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index 532441c4b4044..cd73695890bde 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -54,19 +54,84 @@ typedef int socklen_t; /* Platform-specific socket includes */ #ifndef _WIN32 - #include - #include - #include - #include - #include - #include - #ifdef HAVE_SYS_IOCTL_H + /* iOS/tvOS/watchOS/visionOS - Same as macOS */ + #if defined(__APPLE__) + #include + #include + #include + #include + #include + #include #include + /* QNX */ + #elif defined(__QNXNTO__) + #include + #include + #include + #include + #include + #include + #include + /* PlayStation Vita */ + #elif defined(__VITA__) + #include + #include + #include + #define socklen_t unsigned int + #define closesocket sceNetSocketClose + #define EAGAIN SCE_NET_EAGAIN + #define EWOULDBLOCK SCE_NET_EWOULDBLOCK + /* PlayStation Portable */ + #elif defined(__PSP__) + #include + #include + #include + #include + #define socklen_t unsigned int + #define closesocket sceNetInetClose + /* Nintendo 3DS */ + #elif defined(__3DS__) + #include <3ds.h> + #include + #include + #include + #define closesocket closesocket /* 3DS uses closesocket like Windows */ + /* PlayStation 2 */ + #elif defined(__PS2__) + /* PS2 networking support - requires ps2sdk */ + #include + #include + #define socklen_t unsigned int + #define closesocket lwip_close + /* RISC OS */ + #elif defined(__riscos__) + #include + #include + #include + #include + #include + #include + /* Standard Unix/Linux */ + #else + #include + #include + #include + #include + #include + #include + #ifdef HAVE_SYS_IOCTL_H + #include + #endif + #ifdef __sun + #include + #endif + #define closesocket close #endif - #ifdef __sun - #include + + /* Default closesocket if not defined */ + #ifndef closesocket + #define closesocket close #endif - #define closesocket close #endif #include @@ -121,21 +186,65 @@ struct DSU_Context_t *s_dsu_ctx = NULL; void DSU_RequestControllerInfo(DSU_Context *ctx, Uint8 slot); void DSU_RequestControllerData(DSU_Context *ctx, Uint8 slot); +/* Platform-specific network function wrappers */ +#if defined(__VITA__) + #define DSU_sendto(sock, buf, len, flags, addr, addrlen) \ + sceNetSendto((sock), (buf), (len), (flags), (addr), (addrlen)) + #define DSU_recvfrom(sock, buf, len, flags, addr, addrlen) \ + sceNetRecvfrom((sock), (buf), (len), (flags), (addr), (addrlen)) + #define DSU_GetLastError() sce_net_errno +#elif defined(__PSP__) + #define DSU_sendto(sock, buf, len, flags, addr, addrlen) \ + sceNetInetSendto((sock), (buf), (len), (flags), (addr), (addrlen)) + #define DSU_recvfrom(sock, buf, len, flags, addr, addrlen) \ + sceNetInetRecvfrom((sock), (buf), (len), (flags), (addr), (addrlen)) + #define DSU_GetLastError() sceNetInetGetErrno() +#elif defined(__PS2__) + /* PS2 uses lwIP */ + #define DSU_sendto(sock, buf, len, flags, addr, addrlen) \ + lwip_sendto((sock), (buf), (len), (flags), (addr), (addrlen)) + #define DSU_recvfrom(sock, buf, len, flags, addr, addrlen) \ + lwip_recvfrom((sock), (buf), (len), (flags), (addr), (addrlen)) + #define DSU_GetLastError() errno +#else + /* Standard sendto/recvfrom */ + #define DSU_sendto sendto + #define DSU_recvfrom recvfrom + #define DSU_GetLastError() errno +#endif + /* Socket helpers implementation */ int DSU_InitSockets(void) { #ifdef _WIN32 WSADATA wsaData; return WSAStartup(MAKEWORD(2, 2), &wsaData); +#elif defined(__VITA__) + /* PS Vita network initialization is handled by SDL main */ + return 0; +#elif defined(__PSP__) + /* PSP network initialization is handled by SDL main */ + return 0; +#elif defined(__PS2__) + /* PS2 network initialization is handled by SDL main */ + return 0; +#elif defined(__3DS__) + /* 3DS network initialization is handled by SDL main */ + return 0; #else + /* Unix/Linux - no initialization needed */ return 0; #endif } -void DSU_QuitSockets(void) +void DSU_CleanupSockets(void) { #ifdef _WIN32 WSACleanup(); +#elif defined(__VITA__) || defined(__PSP__) || defined(__PS2__) || defined(__3DS__) + /* Console platforms handle cleanup elsewhere */ +#else + /* Unix/Linux - no cleanup needed */ #endif } @@ -146,6 +255,17 @@ dsu_socket_t DSU_CreateSocket(Uint16 port) int reuse = 1; #ifdef _WIN32 u_long mode = 1; +#elif defined(__VITA__) + /* PS Vita uses different socket creation */ + int nonblock = 1; +#elif defined(__PSP__) + /* PSP socket creation */ +#elif defined(__PS2__) + /* PS2 socket creation */ + int nb = 1; +#elif defined(__3DS__) + /* 3DS socket creation */ + int nonblock = 1; #else int flags; #if defined(FIONBIO) @@ -153,17 +273,41 @@ dsu_socket_t DSU_CreateSocket(Uint16 port) #endif #endif +#if defined(__VITA__) + sock = sceNetSocket("DSU_Socket", AF_INET, SOCK_DGRAM, 0); +#elif defined(__PSP__) + sock = sceNetInetSocket(AF_INET, SOCK_DGRAM, 0); +#elif defined(__PS2__) + sock = lwip_socket(AF_INET, SOCK_DGRAM, 0); +#elif defined(__3DS__) sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); +#else + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); +#endif if (sock == DSU_INVALID_SOCKET) { return DSU_INVALID_SOCKET; } /* Allow address reuse */ +#if !defined(__VITA__) && !defined(__PSP__) && !defined(__PS2__) /* These platforms may not support SO_REUSEADDR */ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)); +#endif /* Set socket to non-blocking */ #ifdef _WIN32 ioctlsocket(sock, FIONBIO, &mode); +#elif defined(__VITA__) + sceNetSetSockOpt(sock, SCE_NET_SOL_SOCKET, SCE_NET_SO_NBIO, &nonblock, sizeof(nonblock)); +#elif defined(__PSP__) + /* PSP: Set non-blocking mode differently */ + sceNetInetSetNonblock(sock, 1); +#elif defined(__PS2__) + /* PS2: lwIP non-blocking mode */ + int nb = 1; + lwip_ioctl(sock, FIONBIO, &nb); +#elif defined(__3DS__) + /* 3DS: Use fcntl for non-blocking */ + fcntl(sock, F_SETFL, O_NONBLOCK); #else #if defined(FIONBIO) if (ioctl(sock, FIONBIO, &nonblock) < 0) { @@ -276,15 +420,15 @@ static int DSU_SendPacket(DSU_Context *ctx, void *packet, size_t size) server.sin_port = DSU_htons(ctx->server_port); server.sin_addr.s_addr = DSU_ipv4_addr(ctx->server_address); - result = (sendto)(ctx->socket, (const char*)packet, (int)size, 0, - (struct sockaddr *)&server, (int)sizeof(server)); + result = DSU_sendto(ctx->socket, (const char*)packet, (int)size, 0, + (struct sockaddr *)&server, (int)sizeof(server)); if (result < 0) { #ifdef _WIN32 int err = WSAGetLastError(); SDL_Log("DSU: sendto failed with error %d\n", err); #else - SDL_Log("DSU: sendto failed with errno %d\n", errno); + SDL_Log("DSU: sendto failed with errno %d\n", DSU_GetLastError()); #endif } @@ -505,8 +649,8 @@ int SDLCALL DSU_ReceiverThread(void *data) SDL_Log("DSU: Receiver thread exiting - mutex destroyed\n"); break; } - received = recvfrom(ctx->socket, (char*)buffer, sizeof(buffer), 0, - (struct sockaddr *)&sender, &sender_len); + received = DSU_recvfrom(ctx->socket, (char*)buffer, sizeof(buffer), 0, + (struct sockaddr *)&sender, &sender_len); if (received > (int)sizeof(DSU_Header)) { header = (DSU_Header *)buffer; @@ -581,13 +725,14 @@ int SDLCALL DSU_ReceiverThread(void *data) SDL_Delay(100); /* Back off on errors */ } #else - if (errno == EBADF) { + int err = DSU_GetLastError(); + if (err == EBADF) { /* Socket closed, exit gracefully */ SDL_Log("DSU: Socket closed, receiver thread exiting\n"); break; } - if (errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) { - SDL_Log("DSU: recvfrom errno %d\n", errno); + if (err != EWOULDBLOCK && err != EAGAIN && err != EINTR) { + SDL_Log("DSU: recvfrom errno %d\n", err); SDL_Delay(100); } #endif @@ -656,7 +801,7 @@ static bool DSU_JoystickInit(void) /* Create UDP socket */ ctx->socket = DSU_CreateSocket(ctx->client_port); if (ctx->socket == DSU_INVALID_SOCKET) { - DSU_QuitSockets(); + DSU_CleanupSockets(); SDL_free(ctx); return false; } @@ -665,7 +810,7 @@ static bool DSU_JoystickInit(void) ctx->slots_mutex = SDL_CreateMutex(); if (!ctx->slots_mutex) { DSU_CloseSocket(ctx->socket); - DSU_QuitSockets(); + DSU_CleanupSockets(); SDL_free(ctx); SDL_OutOfMemory(); return false; @@ -678,7 +823,7 @@ static bool DSU_JoystickInit(void) if (!ctx->receiver_thread) { SDL_DestroyMutex(ctx->slots_mutex); DSU_CloseSocket(ctx->socket); - DSU_QuitSockets(); + DSU_CleanupSockets(); SDL_free(ctx); SDL_SetError("Failed to create DSU receiver thread"); return false; @@ -1049,8 +1194,8 @@ static bool DSU_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumb server.sin_family = AF_INET; server.sin_port = DSU_htons(ctx->server_port); server.sin_addr.s_addr = DSU_ipv4_addr(ctx->server_address); - if ((sendto)(ctx->socket, (const char*)&packet, (int)sizeof(packet), 0, - (struct sockaddr *)&server, (int)sizeof(server)) < 0) { + if (DSU_sendto(ctx->socket, (const char*)&packet, (int)sizeof(packet), 0, + (struct sockaddr *)&server, (int)sizeof(server)) < 0) { SDL_SetError("Failed to send rumble packet"); return false; } @@ -1314,7 +1459,7 @@ static void DSU_JoystickQuit(void) } /* Clean up sockets */ - DSU_QuitSockets(); + DSU_CleanupSockets(); /* Clean up mutex */ if (ctx->slots_mutex) { From 1e4cb5dae25dcfc72c8c251b4fef72d3fd7a8217 Mon Sep 17 00:00:00 2001 From: danprice142 Date: Sun, 16 Nov 2025 00:50:26 +0000 Subject: [PATCH 11/12] Remove DSU joystick support for unsupported consoles Dropped DSU support for PlayStation Vita, PSP, PS2, and Nintendo 3DS platforms I dont have a way to test anyway --- CMakeLists.txt | 29 +------- src/joystick/dsu/SDL_dsujoystick.c | 109 ++--------------------------- 2 files changed, 6 insertions(+), 132 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 74a03cdbe6b8f..80fa3463ec3a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1378,12 +1378,8 @@ if(SDL_JOYSTICK) # DSU (DualShock UDP) client support # Supported on platforms with UDP socket support (BSD sockets or WinSock) - # Note: Some of these platforms haven't been fully tested yet but should work in theory - if(SDL_DSU_JOYSTICK AND NOT EMSCRIPTEN AND - (WINDOWS OR LINUX OR ANDROID OR HAIKU OR FREEBSD OR NETBSD OR OPENBSD OR - MACOS OR IOS OR TVOS OR VISIONOS OR WATCHOS OR - QNX OR RISCOS OR - VITA OR PSP OR PS2 OR N3DS)) + # Disabled on platforms without UDP support or where not yet implemented + if(SDL_DSU_JOYSTICK AND NOT (EMSCRIPTEN OR VITA OR PSP OR PS2 OR N3DS)) set(SDL_JOYSTICK_DSU 1) sdl_glob_sources( "${SDL3_SOURCE_DIR}/src/joystick/dsu/*.c" @@ -1398,27 +1394,6 @@ if(SDL_JOYSTICK) elseif(QNX) # QNX needs socket library sdl_link_dependency(dsu LIBS socket) - elseif(VITA) - # PlayStation Vita networking libraries - sdl_link_dependency(dsu LIBS - SceNet_stub - SceNetCtl_stub - ) - elseif(PSP) - # PSP networking libraries - sdl_link_dependency(dsu LIBS - pspnet - pspnet_inet - pspnet_resolver - ) - elseif(PS2) - # PlayStation 2 uses ps2sdk's lwIP - sdl_link_dependency(dsu LIBS - ps2ip - ) - elseif(N3DS) - # Nintendo 3DS networking libraries (if available) - sdl_link_dependency(dsu LIBS ctru) endif() endif() endif() diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index cd73695890bde..44242461cb756 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -72,37 +72,6 @@ typedef int socklen_t; #include #include #include - /* PlayStation Vita */ - #elif defined(__VITA__) - #include - #include - #include - #define socklen_t unsigned int - #define closesocket sceNetSocketClose - #define EAGAIN SCE_NET_EAGAIN - #define EWOULDBLOCK SCE_NET_EWOULDBLOCK - /* PlayStation Portable */ - #elif defined(__PSP__) - #include - #include - #include - #include - #define socklen_t unsigned int - #define closesocket sceNetInetClose - /* Nintendo 3DS */ - #elif defined(__3DS__) - #include <3ds.h> - #include - #include - #include - #define closesocket closesocket /* 3DS uses closesocket like Windows */ - /* PlayStation 2 */ - #elif defined(__PS2__) - /* PS2 networking support - requires ps2sdk */ - #include - #include - #define socklen_t unsigned int - #define closesocket lwip_close /* RISC OS */ #elif defined(__riscos__) #include @@ -187,31 +156,10 @@ void DSU_RequestControllerInfo(DSU_Context *ctx, Uint8 slot); void DSU_RequestControllerData(DSU_Context *ctx, Uint8 slot); /* Platform-specific network function wrappers */ -#if defined(__VITA__) - #define DSU_sendto(sock, buf, len, flags, addr, addrlen) \ - sceNetSendto((sock), (buf), (len), (flags), (addr), (addrlen)) - #define DSU_recvfrom(sock, buf, len, flags, addr, addrlen) \ - sceNetRecvfrom((sock), (buf), (len), (flags), (addr), (addrlen)) - #define DSU_GetLastError() sce_net_errno -#elif defined(__PSP__) - #define DSU_sendto(sock, buf, len, flags, addr, addrlen) \ - sceNetInetSendto((sock), (buf), (len), (flags), (addr), (addrlen)) - #define DSU_recvfrom(sock, buf, len, flags, addr, addrlen) \ - sceNetInetRecvfrom((sock), (buf), (len), (flags), (addr), (addrlen)) - #define DSU_GetLastError() sceNetInetGetErrno() -#elif defined(__PS2__) - /* PS2 uses lwIP */ - #define DSU_sendto(sock, buf, len, flags, addr, addrlen) \ - lwip_sendto((sock), (buf), (len), (flags), (addr), (addrlen)) - #define DSU_recvfrom(sock, buf, len, flags, addr, addrlen) \ - lwip_recvfrom((sock), (buf), (len), (flags), (addr), (addrlen)) - #define DSU_GetLastError() errno -#else - /* Standard sendto/recvfrom */ - #define DSU_sendto sendto - #define DSU_recvfrom recvfrom - #define DSU_GetLastError() errno -#endif +/* Standard sendto/recvfrom for all supported platforms */ +#define DSU_sendto sendto +#define DSU_recvfrom recvfrom +#define DSU_GetLastError() errno /* Socket helpers implementation */ int DSU_InitSockets(void) @@ -219,18 +167,6 @@ int DSU_InitSockets(void) #ifdef _WIN32 WSADATA wsaData; return WSAStartup(MAKEWORD(2, 2), &wsaData); -#elif defined(__VITA__) - /* PS Vita network initialization is handled by SDL main */ - return 0; -#elif defined(__PSP__) - /* PSP network initialization is handled by SDL main */ - return 0; -#elif defined(__PS2__) - /* PS2 network initialization is handled by SDL main */ - return 0; -#elif defined(__3DS__) - /* 3DS network initialization is handled by SDL main */ - return 0; #else /* Unix/Linux - no initialization needed */ return 0; @@ -241,8 +177,6 @@ void DSU_CleanupSockets(void) { #ifdef _WIN32 WSACleanup(); -#elif defined(__VITA__) || defined(__PSP__) || defined(__PS2__) || defined(__3DS__) - /* Console platforms handle cleanup elsewhere */ #else /* Unix/Linux - no cleanup needed */ #endif @@ -255,17 +189,6 @@ dsu_socket_t DSU_CreateSocket(Uint16 port) int reuse = 1; #ifdef _WIN32 u_long mode = 1; -#elif defined(__VITA__) - /* PS Vita uses different socket creation */ - int nonblock = 1; -#elif defined(__PSP__) - /* PSP socket creation */ -#elif defined(__PS2__) - /* PS2 socket creation */ - int nb = 1; -#elif defined(__3DS__) - /* 3DS socket creation */ - int nonblock = 1; #else int flags; #if defined(FIONBIO) @@ -273,41 +196,17 @@ dsu_socket_t DSU_CreateSocket(Uint16 port) #endif #endif -#if defined(__VITA__) - sock = sceNetSocket("DSU_Socket", AF_INET, SOCK_DGRAM, 0); -#elif defined(__PSP__) - sock = sceNetInetSocket(AF_INET, SOCK_DGRAM, 0); -#elif defined(__PS2__) - sock = lwip_socket(AF_INET, SOCK_DGRAM, 0); -#elif defined(__3DS__) sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); -#else - sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); -#endif if (sock == DSU_INVALID_SOCKET) { return DSU_INVALID_SOCKET; } /* Allow address reuse */ -#if !defined(__VITA__) && !defined(__PSP__) && !defined(__PS2__) /* These platforms may not support SO_REUSEADDR */ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)); -#endif /* Set socket to non-blocking */ #ifdef _WIN32 ioctlsocket(sock, FIONBIO, &mode); -#elif defined(__VITA__) - sceNetSetSockOpt(sock, SCE_NET_SOL_SOCKET, SCE_NET_SO_NBIO, &nonblock, sizeof(nonblock)); -#elif defined(__PSP__) - /* PSP: Set non-blocking mode differently */ - sceNetInetSetNonblock(sock, 1); -#elif defined(__PS2__) - /* PS2: lwIP non-blocking mode */ - int nb = 1; - lwip_ioctl(sock, FIONBIO, &nb); -#elif defined(__3DS__) - /* 3DS: Use fcntl for non-blocking */ - fcntl(sock, F_SETFL, O_NONBLOCK); #else #if defined(FIONBIO) if (ioctl(sock, FIONBIO, &nonblock) < 0) { From 9af697ef5ab7ff8f020d10e42c545081d4d39588 Mon Sep 17 00:00:00 2001 From: danprice142 Date: Mon, 17 Nov 2025 17:16:41 +0000 Subject: [PATCH 12/12] Improve IPv4 address parsing on Windows Replaces use of InetPtonA with getaddrinfo for IPv4 address parsing on Windows to avoid deprecated APIs and maintain compatibility with older systems like Windows XP --- src/joystick/dsu/SDL_dsujoystick.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index 44242461cb756..0c874167023eb 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -131,9 +131,24 @@ static unsigned long DSU_ipv4_addr(const char *str) addr.sin_family = AF_INET; #ifdef _WIN32 - /* Use InetPton on Windows to avoid deprecated API warning */ - if (InetPtonA(AF_INET, str, &addr.sin_addr) == 1) { - return addr.sin_addr.s_addr; + /* Use getaddrinfo on Windows keeping compatibility with older systems (e.g. Windows XP). */ + { + struct addrinfo hints; + struct addrinfo *result = NULL; + + SDL_memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + + if (getaddrinfo(str, NULL, &hints, &result) == 0 && result && result->ai_addr) { + const struct sockaddr_in *ipv4 = (const struct sockaddr_in *)result->ai_addr; + addr.sin_addr = ipv4->sin_addr; + freeaddrinfo(result); + return addr.sin_addr.s_addr; + } + + if (result) { + freeaddrinfo(result); + } } #else /* Use inet_pton on Unix-like systems */