From 264e9ccf8ec3501240315167a86f380cfe139795 Mon Sep 17 00:00:00 2001 From: danprice142 Date: Thu, 30 Oct 2025 21:31:49 +0000 Subject: [PATCH 01/16] 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 7472137a2d..79999826e2 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 972e0b7e78..fb7295d928 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 35560da940..7a8ad4cace 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 7872d2a928..62e6469342 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 1841341831..fcf0777ff2 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 041ebc3b50..450b8454b7 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 0000000000..ff7e1f451a --- /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 0000000000..abb84bb914 --- /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 0000000000..b35af5428a --- /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 0000000000..952deaa8e0 --- /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/16] 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 79999826e2..2eb60a2421 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 ff7e1f451a..da4e0eb312 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 abb84bb914..a1f30ecab7 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 b35af5428a..9573fdb567 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/16] 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 2eb60a2421..7dba1ad3b4 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 da4e0eb312..977904e6c9 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/16] 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 7dba1ad3b4..6bf827978f 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 977904e6c9..ecd6ead78b 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 9573fdb567..147e0a71b1 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/16] 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 7af7132b29..ed7c5e53a2 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/16] 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 ecd6ead78b..231a3c3f09 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 147e0a71b1..965dc2ed52 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/16] 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 965dc2ed52..7b8800f1f0 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/16] 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 ed7c5e53a2..5d6484c3ec 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 231a3c3f09..6cc16fec0e 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 a1f30ecab7..b41b682408 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 7b8800f1f0..0000000000 --- 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/16] 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 6cc16fec0e..532441c4b4 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/16] 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 6bf827978f..74a03cdbe6 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 532441c4b4..cd73695890 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/16] 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 74a03cdbe6..80fa3463ec 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 cd73695890..44242461cb 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/16] 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 44242461cb..0c87416702 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 */ From 41e99603c230f05ad2db95b4eeef53540abcd27a Mon Sep 17 00:00:00 2001 From: danprice142 Date: Fri, 21 Nov 2025 05:04:54 +0000 Subject: [PATCH 13/16] Remove legacy CRC32 implementation from DSU joystick Deleted the old CRC32 calculation code in SDL_dsujoystick.c, as it has been replaced by SDL_crc32(). Also updated SDL.vcxproj to include new DSU joystick source and header files. --- VisualC-GDK/SDL/SDL.vcxproj | 3 ++ src/joystick/dsu/SDL_dsujoystick.c | 49 ------------------------------ 2 files changed, 3 insertions(+), 49 deletions(-) diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index 5f7ac76679..d0bcb7ccbd 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -475,6 +475,8 @@ + + @@ -746,6 +748,7 @@ + diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index 0c87416702..a0e84d0c4e 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -259,55 +259,6 @@ 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, - 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; -} -#endif /* Send a packet to the DSU server */ static int DSU_SendPacket(DSU_Context *ctx, void *packet, size_t size) From 40855e0e526bfcef04a5ab17d0ec1098abaadd81 Mon Sep 17 00:00:00 2001 From: danprice142 Date: Wed, 17 Dec 2025 14:51:50 +0000 Subject: [PATCH 14/16] Remove verbose logging and deferred add logic from DSU joystick This commit removes extensive SDL_Log debug output and the 'pending_add' state/logic from the DSU joystick implementation. Joystick addition and removal now occur immediately with proper mutex and joystick lock handling. --- src/joystick/dsu/SDL_dsujoystick.c | 214 ++++++--------------------- src/joystick/dsu/SDL_dsujoystick_c.h | 3 - 2 files changed, 43 insertions(+), 174 deletions(-) diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index a0e84d0c4e..f88d615ad3 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -291,9 +291,9 @@ static int DSU_SendPacket(DSU_Context *ctx, void *packet, size_t size) if (result < 0) { #ifdef _WIN32 int err = WSAGetLastError(); - SDL_Log("DSU: sendto failed with error %d\n", err); + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "DSU: sendto failed with error %d", err); #else - SDL_Log("DSU: sendto failed with errno %d\n", DSU_GetLastError()); + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "DSU: sendto failed with errno %d", DSU_GetLastError()); #endif } @@ -311,7 +311,6 @@ void DSU_RequestControllerInfo(DSU_Context *ctx, Uint8 slot) 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)); } @@ -325,7 +324,6 @@ void DSU_RequestControllerData(DSU_Context *ctx, Uint8 slot) 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)); } @@ -338,43 +336,26 @@ static void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data /* 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"); + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "DSU: Invalid slot_id %d in data packet", slot_id); 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) { @@ -465,7 +446,6 @@ static void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data /* 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 */ @@ -476,22 +456,27 @@ static void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data 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; + /* Mark as connected before notifying SDL */ + slot->connected = true; - SDL_Log("DSU: Controller connected on slot %d, instance %d, will notify SDL\n", - slot_id, (int)slot->instance_id); + /* Unlock our mutex before taking the joystick lock to avoid deadlock */ + SDL_UnlockMutex(ctx->slots_mutex); + + /* Add the joystick with proper locking */ + SDL_LockJoysticks(); + SDL_PrivateJoystickAdded(slot->instance_id); + SDL_UnlockJoysticks(); + + /* Re-lock our mutex to continue */ + SDL_LockMutex(ctx->slots_mutex); } - 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, (Uint8)slot_id); } - SDL_Log("DSU: Finished processing data for slot %d\n", slot_id); } /* Receiver thread implementation */ @@ -504,14 +489,12 @@ int SDLCALL DSU_ReceiverThread(void *data) 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 = DSU_recvfrom(ctx->socket, (char*)buffer, sizeof(buffer), 0, @@ -551,7 +534,6 @@ int SDLCALL DSU_ReceiverThread(void *data) 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) { @@ -565,7 +547,6 @@ int SDLCALL DSU_ReceiverThread(void *data) /* Controller data */ if (received >= (int)sizeof(DSU_ControllerData)) { DSU_ControllerData *packet = (DSU_ControllerData *)buffer; - SDL_Log("DSU: Data packet received for slot %d\n", packet->info.slot); DSU_ProcessControllerData(ctx, packet); } break; @@ -582,22 +563,20 @@ int SDLCALL DSU_ReceiverThread(void *data) 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_LogWarn(SDL_LOG_CATEGORY_INPUT, "DSU: recvfrom error %d", error); SDL_Delay(100); /* Back off on errors */ } #else int err = DSU_GetLastError(); if (err == EBADF) { /* Socket closed, exit gracefully */ - SDL_Log("DSU: Socket closed, receiver thread exiting\n"); break; } if (err != EWOULDBLOCK && err != EAGAIN && err != EINTR) { - SDL_Log("DSU: recvfrom errno %d\n", err); + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "DSU: recvfrom errno %d", err); SDL_Delay(100); } #endif @@ -607,7 +586,6 @@ int SDLCALL DSU_ReceiverThread(void *data) SDL_Delay(1); } - SDL_Log("DSU: Receiver thread exiting cleanly\n"); return 0; } @@ -754,62 +732,36 @@ static void DSU_JoystickDetect(void) 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 */ + mutex = ctx->slots_mutex; 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 */ + /* Controller timed out - notify SDL if it was connected */ if (ctx->slots[i].connected && ctx->slots[i].instance_id != 0) { - SDL_PrivateJoystickRemoved(ctx->slots[i].instance_id); - } + SDL_JoystickID removed_id = 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; + /* Clear state before notifying to avoid race conditions */ + ctx->slots[i].detected = false; + ctx->slots[i].connected = false; + ctx->slots[i].instance_id = 0; + + /* Unlock our mutex before taking the joystick lock to avoid deadlock */ + SDL_UnlockMutex(mutex); + + SDL_LockJoysticks(); + SDL_PrivateJoystickRemoved(removed_id); + SDL_UnlockJoysticks(); + + /* Re-lock our mutex to continue the loop */ + SDL_LockMutex(mutex); + } else { + /* Clear all state flags */ + ctx->slots[i].detected = false; + ctx->slots[i].connected = false; + ctx->slots[i].instance_id = 0; + } } } SDL_UnlockMutex(mutex); @@ -951,19 +903,14 @@ static bool DSU_JoystickOpen(SDL_Joystick *joystick, int device_index) 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; } @@ -975,7 +922,6 @@ static bool DSU_JoystickOpen(SDL_Joystick *joystick, int device_index) 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++; @@ -1006,21 +952,17 @@ static bool DSU_JoystickOpen(SDL_Joystick *joystick, int device_index) } /* 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; } @@ -1106,19 +1048,12 @@ static void DSU_JoystickUpdate(SDL_Joystick *joystick) 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); + if (!slot || !slot->connected) { return; } ctx = s_dsu_ctx; if (!ctx || !ctx->slots_mutex) { - SDL_Log("DSU: JoystickUpdate called but context invalid\n"); return; } @@ -1128,49 +1063,18 @@ static void DSU_JoystickUpdate(SDL_Joystick *joystick) /* 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" - }; + /* Update buttons */ 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]); + /* Update axes */ 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 */ @@ -1184,11 +1088,6 @@ static void DSU_JoystickUpdate(SDL_Joystick *joystick) 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; @@ -1204,11 +1103,6 @@ static void DSU_JoystickUpdate(SDL_Joystick *joystick) 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; @@ -1221,56 +1115,39 @@ static void DSU_JoystickUpdate(SDL_Joystick *joystick) } /* 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); } @@ -1294,8 +1171,6 @@ static void DSU_JoystickQuit(void) { struct DSU_Context_t *ctx; - SDL_Log("DSU: JoystickQuit called\n"); - ctx = s_dsu_ctx; if (!ctx) { return; @@ -1317,10 +1192,8 @@ static void DSU_JoystickQuit(void) /* 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 */ @@ -1333,7 +1206,6 @@ static void DSU_JoystickQuit(void) /* Free context */ SDL_free(ctx); - SDL_Log("DSU: Quit complete\n"); } static bool DSU_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) diff --git a/src/joystick/dsu/SDL_dsujoystick_c.h b/src/joystick/dsu/SDL_dsujoystick_c.h index b41b682408..8c56c2f1ef 100644 --- a/src/joystick/dsu/SDL_dsujoystick_c.h +++ b/src/joystick/dsu/SDL_dsujoystick_c.h @@ -87,9 +87,6 @@ typedef struct DSU_ControllerSlot { /* Timing */ Uint64 last_packet_time; Uint32 packet_number; - - /* State change flags for deferred notifications */ - bool pending_add; } DSU_ControllerSlot; typedef struct DSU_Context_t { From db1245c6137de9a0a54696287509086727c1d727 Mon Sep 17 00:00:00 2001 From: danprice142 Date: Tue, 3 Feb 2026 15:32:44 +0000 Subject: [PATCH 15/16] DSU joystick: add multi-server support & refactor Refactor the DSU joystick driver to support multiple DSU servers and improve networking/lifecycle handling. Introduces a per-server DSU_ServerConnection type (DSU_MAX_SERVERS), moves per-server sockets, client IDs, threads and slots into that structure, and updates function signatures accordingly. Receiver thread now uses select() with a timeout, per-server sockets/threads are created, and sockets are closed/threads joined per-server on shutdown. Added server string parsing (address[:port]) and support for comma-separated server lists, improved timeout/detection logic to avoid SDL deadlocks by deferring SDL_PrivateJoystickAdded until DSU_JoystickDetect, and fixed rumble to send to the correct server/socket. Added sensor enable tracking, touchpad and sensor handling improvements, and a small CMake option change for DSU_JOYSTICK handling. Updated header to define new structs and fields. --- CMakeLists.txt | 2 +- src/joystick/dsu/SDL_dsujoystick.c | 631 ++++++++++++++++----------- src/joystick/dsu/SDL_dsujoystick_c.h | 30 +- 3 files changed, 413 insertions(+), 250 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 80fa3463ec..b3744e125e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -380,7 +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) +dep_option(SDL_DSU_JOYSTICK "Enable DSU client joystick support" ON SDL_JOYSTICK OFF) 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) diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index f88d615ad3..65e2354e17 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -167,8 +167,8 @@ struct DSU_Context_t *s_dsu_ctx = NULL; /* 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); +void DSU_RequestControllerInfo(DSU_ServerConnection *server, Uint8 slot); +void DSU_RequestControllerData(DSU_ServerConnection *server, Uint8 slot); /* Platform-specific network function wrappers */ /* Standard sendto/recvfrom for all supported platforms */ @@ -261,10 +261,10 @@ void DSU_CloseSocket(dsu_socket_t socket) } /* Send a packet to the DSU server */ -static int DSU_SendPacket(DSU_Context *ctx, void *packet, size_t size) +static int DSU_SendPacket(DSU_ServerConnection *conn, void *packet, size_t size) { DSU_Header *header; - struct sockaddr_in server; + struct sockaddr_in server_addr; int result; header = (DSU_Header *)packet; @@ -273,20 +273,20 @@ static int DSU_SendPacket(DSU_Context *ctx, void *packet, size_t size) 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->client_id = SDL_Swap32LE(conn->client_id); header->crc32 = 0; /* Calculate and store CRC32 */ header->crc32 = SDL_Swap32LE(SDL_crc32(0, 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); + SDL_memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = DSU_htons(conn->server_port); + server_addr.sin_addr.s_addr = DSU_ipv4_addr(conn->server_address); - result = DSU_sendto(ctx->socket, (const char*)packet, (int)size, 0, - (struct sockaddr *)&server, (int)sizeof(server)); + result = DSU_sendto(conn->socket, (const char*)packet, (int)size, 0, + (struct sockaddr *)&server_addr, (int)sizeof(server_addr)); if (result < 0) { #ifdef _WIN32 @@ -301,9 +301,10 @@ static int DSU_SendPacket(DSU_Context *ctx, void *packet, size_t size) } /* Request controller information */ -void DSU_RequestControllerInfo(DSU_Context *ctx, Uint8 slot) +void DSU_RequestControllerInfo(DSU_ServerConnection *conn, Uint8 slot) { DSU_PortRequest request; + int result; SDL_memset(&request, 0, sizeof(request)); request.header.message_type = SDL_Swap32LE(DSU_MSG_PORTS_INFO); @@ -311,11 +312,12 @@ void DSU_RequestControllerInfo(DSU_Context *ctx, Uint8 slot) request.slot_id = slot; /* 0xFF for all slots */ /* MAC is zeros for all controllers */ - DSU_SendPacket(ctx, &request, sizeof(request)); + result = DSU_SendPacket(conn, &request, sizeof(request)); + (void)result; } /* Request controller data */ -void DSU_RequestControllerData(DSU_Context *ctx, Uint8 slot) +void DSU_RequestControllerData(DSU_ServerConnection *conn, Uint8 slot) { DSU_PortRequest request; @@ -324,20 +326,22 @@ void DSU_RequestControllerData(DSU_Context *ctx, Uint8 slot) request.flags = 0; /* Subscribe to data */ request.slot_id = slot; - DSU_SendPacket(ctx, &request, sizeof(request)); + DSU_SendPacket(conn, &request, sizeof(request)); } /* Process incoming controller data */ -static void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data) +static void DSU_ProcessControllerData(DSU_ServerConnection *conn, DSU_ControllerData *data) { DSU_ControllerSlot *slot; + DSU_Context *ctx; int slot_id; bool was_connected; - /* Validate context */ - if (!ctx || !ctx->slots_mutex) { + /* Validate connection and parent context */ + if (!conn || !conn->parent || !conn->parent->slots_mutex) { return; } + ctx = conn->parent; /* Get slot ID */ slot_id = data->info.slot; @@ -347,7 +351,8 @@ static void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data } SDL_LockMutex(ctx->slots_mutex); - slot = &ctx->slots[slot_id]; + slot = &conn->slots[slot_id]; + /* If already connected to SDL, just update data without changing state */ if (slot->connected) { @@ -356,6 +361,7 @@ static void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data /* Update connection state */ was_connected = slot->detected; slot->detected = (data->info.slot_state == DSU_STATE_CONNECTED); + /* Debug logging removed */ } if (slot->detected || slot->connected) { @@ -365,9 +371,10 @@ static void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data slot->model = data->info.device_model; slot->connection = data->info.connection_type; slot->slot_id = (Uint8)slot_id; + slot->parent_conn = conn; /* Back-reference for rumble */ - /* Generate name */ - SDL_snprintf(slot->name, sizeof(slot->name), "DSUClient/%d", slot_id); + /* Generate name with server index for multi-server support */ + SDL_snprintf(slot->name, sizeof(slot->name), "DSUClient/%d/%d", conn->server_index, slot_id); /* Update button states */ slot->buttons = 0; @@ -444,6 +451,8 @@ static void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data Uint16 vendor; Uint16 product; + /* New controller detected */ + /* New controller connected */ slot->instance_id = SDL_GetNextObjectID(); @@ -456,48 +465,85 @@ static void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data slot->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, 0, NULL, slot->name, 'd', 0); - /* Mark as connected before notifying SDL */ - slot->connected = true; - - /* Unlock our mutex before taking the joystick lock to avoid deadlock */ - SDL_UnlockMutex(ctx->slots_mutex); - - /* Add the joystick with proper locking */ - SDL_LockJoysticks(); - SDL_PrivateJoystickAdded(slot->instance_id); - SDL_UnlockJoysticks(); - - /* Re-lock our mutex to continue */ - SDL_LockMutex(ctx->slots_mutex); + /* Mark slot as ready for detection - DSU_JoystickDetect will add it to SDL */ + /* Do NOT call SDL_PrivateJoystickAdded here - it would deadlock with main thread */ + /* Controller ready for JoystickDetect */ } SDL_UnlockMutex(ctx->slots_mutex); /* Subscribe to controller data updates if just detected */ if (!was_connected && slot->detected) { - DSU_RequestControllerData(ctx, (Uint8)slot_id); + DSU_RequestControllerData(conn, (Uint8)slot_id); } } -/* Receiver thread implementation */ +/* Receiver thread implementation - one per server connection */ int SDLCALL DSU_ReceiverThread(void *data) { - DSU_Context *ctx = (DSU_Context *)data; + DSU_ServerConnection *conn = (DSU_ServerConnection *)data; Uint8 buffer[1024]; struct sockaddr_in sender; socklen_t sender_len = sizeof(sender); DSU_Header *header; int received; + fd_set readfds; + struct timeval tv; + int select_result; SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH); /* Main receive loop */ - while (SDL_GetAtomicInt(&ctx->running)) { - /* Double-check context is still valid */ - if (!ctx->slots_mutex) { + while (SDL_GetAtomicInt(&conn->running)) { + /* Double-check parent context is still valid */ + if (!conn->parent || !conn->parent->slots_mutex) { break; } - received = DSU_recvfrom(ctx->socket, (char*)buffer, sizeof(buffer), 0, + + /* Use select() with timeout instead of spinning */ + FD_ZERO(&readfds); + FD_SET(conn->socket, &readfds); + tv.tv_sec = 0; + tv.tv_usec = 10000; /* 10ms timeout */ + +#ifdef _WIN32 + select_result = select(0, &readfds, NULL, NULL, &tv); +#else + select_result = select((int)conn->socket + 1, &readfds, NULL, NULL, &tv); +#endif + + if (select_result < 0) { + /* Select error - check if socket was closed */ +#ifdef _WIN32 + int error = WSAGetLastError(); + if (error == WSAENOTSOCK || error == WSAEBADF) { + break; + } + if (error != WSAEINTR) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "DSU: select error %d", error); + SDL_Delay(100); + } +#else + int err = DSU_GetLastError(); + if (err == EBADF) { + break; + } + if (err != EINTR) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "DSU: select errno %d", err); + SDL_Delay(100); + } +#endif + continue; + } + + if (select_result == 0) { + /* Timeout - no data available, loop continues */ + continue; + } + + /* Data available - receive it */ + sender_len = sizeof(sender); + received = DSU_recvfrom(conn->socket, (char*)buffer, sizeof(buffer), 0, (struct sockaddr *)&sender, &sender_len); if (received > (int)sizeof(DSU_Header)) { @@ -532,12 +578,10 @@ int SDLCALL DSU_ReceiverThread(void *data) 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 */ - /* 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); + DSU_RequestControllerData(conn, slot_id); } } break; @@ -546,8 +590,7 @@ int SDLCALL DSU_ReceiverThread(void *data) case DSU_MSG_DATA: /* Controller data */ if (received >= (int)sizeof(DSU_ControllerData)) { - DSU_ControllerData *packet = (DSU_ControllerData *)buffer; - DSU_ProcessControllerData(ctx, packet); + DSU_ProcessControllerData(conn, (DSU_ControllerData *)buffer); } break; @@ -555,48 +598,59 @@ int SDLCALL DSU_ReceiverThread(void *data) /* 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 */ break; } - if (error != WSAEWOULDBLOCK && error != WSAEINTR && error != WSAECONNRESET) { - SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "DSU: recvfrom error %d", error); - SDL_Delay(100); /* Back off on errors */ - } #else int err = DSU_GetLastError(); if (err == EBADF) { - /* Socket closed, exit gracefully */ break; } - if (err != EWOULDBLOCK && err != EAGAIN && err != EINTR) { - SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "DSU: recvfrom errno %d", err); - SDL_Delay(100); - } #endif } - - /* Small delay to prevent CPU spinning */ - SDL_Delay(1); } return 0; } +/* Helper to parse server string (format: "address" or "address:port") */ +static void DSU_ParseServerString(const char *str, char *address, size_t addr_size, Uint16 *port) +{ + const char *colon; + size_t addr_len; + + colon = SDL_strchr(str, ':'); + if (colon) { + addr_len = (size_t)(colon - str); + if (addr_len >= addr_size) { + addr_len = addr_size - 1; + } + SDL_memcpy(address, str, addr_len); + address[addr_len] = '\0'; + *port = (Uint16)SDL_atoi(colon + 1); + } else { + SDL_strlcpy(address, str, addr_size); + } +} + /* 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; + const char *servers_hint; + const char *client_port_hint; struct DSU_Context_t *ctx; + char server_list[1024]; + char *token; + char *saveptr; + int server_idx; + DSU_ServerConnection *conn; /* Check if DSU is enabled */ enabled = SDL_GetHint(SDL_HINT_JOYSTICK_DSU); @@ -611,80 +665,110 @@ static bool DSU_JoystickInit(void) 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); + /* Get client port */ + client_port_hint = SDL_GetHint(SDL_HINT_DSU_CLIENT_PORT); + if (client_port_hint && *client_port_hint) { + ctx->client_port = (Uint16)SDL_atoi(client_port_hint); } 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_CleanupSockets(); - SDL_free(ctx); - return false; - } - /* Create mutex */ ctx->slots_mutex = SDL_CreateMutex(); if (!ctx->slots_mutex) { - DSU_CloseSocket(ctx->socket); DSU_CleanupSockets(); 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) { + /* Parse server list (comma-separated: "127.0.0.1:26760,192.168.1.50:26761") */ + servers_hint = SDL_GetHint(SDL_HINT_DSU_SERVER); + if (!servers_hint || !*servers_hint) { + /* Default to single localhost server */ + SDL_snprintf(server_list, sizeof(server_list), "%s:%d", + DSU_SERVER_ADDRESS_DEFAULT, DSU_SERVER_PORT_DEFAULT); + } else { + SDL_strlcpy(server_list, servers_hint, sizeof(server_list)); + } + + /* Parse each server */ + server_idx = 0; + token = SDL_strtok_r(server_list, ",", &saveptr); + while (token && server_idx < DSU_MAX_SERVERS) { + /* Skip whitespace */ + while (*token == ' ') token++; + + conn = &ctx->servers[server_idx]; + conn->parent = ctx; + conn->server_index = server_idx; + conn->server_port = DSU_SERVER_PORT_DEFAULT; + + /* Parse address:port */ + DSU_ParseServerString(token, conn->server_address, + sizeof(conn->server_address), &conn->server_port); + + /* Create socket for this server */ + conn->socket = DSU_CreateSocket(ctx->client_port + (Uint16)server_idx); + if (conn->socket == DSU_INVALID_SOCKET) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "DSU: Failed to create socket for server %d", server_idx); + token = SDL_strtok_r(NULL, ",", &saveptr); + continue; + } + + /* Generate unique client ID */ + conn->client_id = (Uint32)SDL_GetTicks() + (Uint32)server_idx; + + /* Start receiver thread for this server */ + SDL_SetAtomicInt(&conn->running, 1); + { + char thread_name[32]; + SDL_snprintf(thread_name, sizeof(thread_name), "DSU_Recv_%d", server_idx); + conn->receiver_thread = SDL_CreateThread(DSU_ReceiverThread, thread_name, conn); + } + + if (!conn->receiver_thread) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "DSU: Failed to create thread for server %d", server_idx); + DSU_CloseSocket(conn->socket); + conn->socket = DSU_INVALID_SOCKET; + token = SDL_strtok_r(NULL, ",", &saveptr); + continue; + } + + server_idx++; + token = SDL_strtok_r(NULL, ",", &saveptr); + } + + ctx->server_count = server_idx; + + if (ctx->server_count == 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "DSU: No servers configured"); SDL_DestroyMutex(ctx->slots_mutex); - DSU_CloseSocket(ctx->socket); DSU_CleanupSockets(); SDL_free(ctx); - SDL_SetError("Failed to create DSU receiver thread"); - return false; + return true; /* Not an error, just no servers */ } /* Store context globally */ s_dsu_ctx = ctx; - /* Request controller info from all slots */ - DSU_RequestControllerInfo(ctx, 0xFF); + /* Request controller info from all servers */ + for (server_idx = 0; server_idx < ctx->server_count; server_idx++) { + DSU_RequestControllerInfo(&ctx->servers[server_idx], 0xFF); + } return true; } static int DSU_JoystickGetCount(void) { - int count = 0; - int i; + int s, i, count = 0; struct DSU_Context_t *ctx; SDL_Mutex *mutex; @@ -695,9 +779,11 @@ static int DSU_JoystickGetCount(void) mutex = ctx->slots_mutex; SDL_LockMutex(mutex); - for (i = 0; i < DSU_MAX_SLOTS; i++) { - if (ctx->slots[i].connected) { - count++; + for (s = 0; s < ctx->server_count; s++) { + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->servers[s].slots[i].connected) { + count++; + } } } SDL_UnlockMutex(mutex); @@ -708,59 +794,93 @@ static int DSU_JoystickGetCount(void) static void DSU_JoystickDetect(void) { Uint64 now; - int i; + int s, i; struct DSU_Context_t *ctx; SDL_Mutex *mutex; + DSU_ServerConnection *conn; 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); + /* For each server, periodically request controller info */ + for (s = 0; s < ctx->server_count; s++) { + conn = &ctx->servers[s]; + + if (now >= (conn->last_request_time + 500)) { /* Request every 500ms */ + DSU_RequestControllerInfo(conn, (Uint8)0xFF); + + /* Re-subscribe to data for detected controllers */ + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (conn->slots[i].detected || conn->slots[i].connected) { + DSU_RequestControllerData(conn, (Uint8)i); + } } - } - ctx->last_request_time = now; + conn->last_request_time = now; + } } - /* Check for timeouts */ - mutex = ctx->slots_mutex; + /* Check for newly detected controllers - notify SDL about them */ + /* Collect IDs to add, then notify SDL outside our mutex to avoid deadlock */ + { + SDL_JoystickID ids_to_add[DSU_MAX_SERVERS * DSU_MAX_SLOTS]; + int add_count = 0; + + mutex = ctx->slots_mutex; + SDL_LockMutex(mutex); + for (s = 0; s < ctx->server_count; s++) { + conn = &ctx->servers[s]; + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (conn->slots[i].detected && !conn->slots[i].connected && conn->slots[i].instance_id != 0) { + /* Mark connected BEFORE notifying so SDL can find it */ + conn->slots[i].connected = true; + ids_to_add[add_count++] = conn->slots[i].instance_id; + } + } + } + SDL_UnlockMutex(mutex); + + /* Now notify SDL - must be outside our mutex because SDL will call back into us */ + for (i = 0; i < add_count; i++) { + SDL_PrivateJoystickAdded(ids_to_add[i]); + } + } + + /* Check for timeouts on all servers */ 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 - notify SDL if it was connected */ - if (ctx->slots[i].connected && ctx->slots[i].instance_id != 0) { - SDL_JoystickID removed_id = ctx->slots[i].instance_id; + for (s = 0; s < ctx->server_count; s++) { + conn = &ctx->servers[s]; + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if ((conn->slots[i].detected || conn->slots[i].connected) && + now > (conn->slots[i].last_packet_time + 5000)) { /* 5 second timeout */ + /* Controller timed out - notify SDL if it was connected */ + if (conn->slots[i].connected && conn->slots[i].instance_id != 0) { + SDL_JoystickID removed_id = conn->slots[i].instance_id; - /* Clear state before notifying to avoid race conditions */ - ctx->slots[i].detected = false; - ctx->slots[i].connected = false; - ctx->slots[i].instance_id = 0; + /* Clear state before notifying to avoid race conditions */ + conn->slots[i].detected = false; + conn->slots[i].connected = false; + conn->slots[i].instance_id = 0; - /* Unlock our mutex before taking the joystick lock to avoid deadlock */ - SDL_UnlockMutex(mutex); + /* Unlock our mutex before taking the joystick lock to avoid deadlock */ + SDL_UnlockMutex(mutex); - SDL_LockJoysticks(); - SDL_PrivateJoystickRemoved(removed_id); - SDL_UnlockJoysticks(); + SDL_LockJoysticks(); + SDL_PrivateJoystickRemoved(removed_id); + SDL_UnlockJoysticks(); - /* Re-lock our mutex to continue the loop */ - SDL_LockMutex(mutex); - } else { - /* Clear all state flags */ - ctx->slots[i].detected = false; - ctx->slots[i].connected = false; - ctx->slots[i].instance_id = 0; + /* Re-lock our mutex to continue the loop */ + SDL_LockMutex(mutex); + } else { + /* Clear all state flags */ + conn->slots[i].detected = false; + conn->slots[i].connected = false; + conn->slots[i].instance_id = 0; + } } } } @@ -769,7 +889,7 @@ static void DSU_JoystickDetect(void) static const char *DSU_JoystickGetDeviceName(int device_index) { - int i, count = 0; + int s, i, count = 0; struct DSU_Context_t *ctx; SDL_Mutex *mutex; @@ -780,17 +900,19 @@ static const char *DSU_JoystickGetDeviceName(int device_index) 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; + for (s = 0; s < ctx->server_count; s++) { + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->servers[s].slots[i].connected) { + if (count == device_index) { + const char *name = ctx->servers[s].slots[i].name; + SDL_UnlockMutex(mutex); + return name; + } + count++; } - count++; } } SDL_UnlockMutex(mutex); - return NULL; } @@ -805,9 +927,14 @@ static const char *DSU_JoystickGetDevicePath(int device_index) return NULL; /* No path for network devices */ } +static int DSU_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) +{ + return -1; /* DSU controllers are not Steam virtual gamepads */ +} + static int DSU_JoystickGetDevicePlayerIndex(int device_index) { - int i, count = 0; + int s, i, count = 0; struct DSU_Context_t *ctx; SDL_Mutex *mutex; @@ -818,17 +945,19 @@ static int DSU_JoystickGetDevicePlayerIndex(int device_index) 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 */ + for (s = 0; s < ctx->server_count; s++) { + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->servers[s].slots[i].connected) { + if (count == device_index) { + int result = (s * DSU_MAX_SLOTS) + i; + SDL_UnlockMutex(mutex); + return result; + } + count++; } - count++; } } SDL_UnlockMutex(mutex); - return -1; } @@ -840,7 +969,7 @@ static void DSU_JoystickSetDevicePlayerIndex(int device_index, int player_index) static SDL_GUID DSU_JoystickGetDeviceGUID(int device_index) { SDL_GUID guid; - int i, count = 0; + int s, i, count = 0; struct DSU_Context_t *ctx; SDL_Mutex *mutex; @@ -853,24 +982,25 @@ static SDL_GUID DSU_JoystickGetDeviceGUID(int device_index) 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; + for (s = 0; s < ctx->server_count; s++) { + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->servers[s].slots[i].connected) { + if (count == device_index) { + guid = ctx->servers[s].slots[i].guid; + SDL_UnlockMutex(mutex); + return guid; + } + count++; } - count++; } } SDL_UnlockMutex(mutex); - return guid; } static SDL_JoystickID DSU_JoystickGetDeviceInstanceID(int device_index) { - int i, count = 0; + int s, i, count = 0; struct DSU_Context_t *ctx; SDL_Mutex *mutex; @@ -881,14 +1011,16 @@ static SDL_JoystickID DSU_JoystickGetDeviceInstanceID(int device_index) 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; + for (s = 0; s < ctx->server_count; s++) { + for (i = 0; i < DSU_MAX_SLOTS; i++) { + if (ctx->servers[s].slots[i].connected) { + if (count == device_index) { + SDL_JoystickID id = ctx->servers[s].slots[i].instance_id; + SDL_UnlockMutex(mutex); + return id; + } + count++; } - count++; } } SDL_UnlockMutex(mutex); @@ -899,9 +1031,10 @@ static SDL_JoystickID DSU_JoystickGetDeviceInstanceID(int device_index) static bool DSU_JoystickOpen(SDL_Joystick *joystick, int device_index) { DSU_ControllerSlot *slot = NULL; - int i, count = 0; + int s, i, count = 0; struct DSU_Context_t *ctx; SDL_Mutex *mutex; + bool found = false; ctx = s_dsu_ctx; if (!ctx) { @@ -914,17 +1047,20 @@ static bool DSU_JoystickOpen(SDL_Joystick *joystick, int device_index) return false; } - /* Find the slot for this device - check detected controllers */ + /* Find the slot for this device - check detected controllers across all servers */ 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]; - break; + for (s = 0; s < ctx->server_count && !found; s++) { + for (i = 0; i < DSU_MAX_SLOTS; i++) { + /* Look for detected controllers that are about to be connected */ + if (ctx->servers[s].slots[i].detected && ctx->servers[s].slots[i].instance_id != 0) { + if (count == device_index) { + slot = &ctx->servers[s].slots[i]; + found = true; + break; + } + count++; } - count++; } } SDL_UnlockMutex(mutex); @@ -940,27 +1076,31 @@ static bool DSU_JoystickOpen(SDL_Joystick *joystick, int device_index) joystick->naxes = 6; /* LX, LY, RX, RY, L2, R2 */ joystick->nhats = 1; /* D-Pad */ - /* Set up touchpad if available */ + /* Set up touchpad if available - use SDL's API to properly allocate */ 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 */ - } + SDL_PrivateJoystickAddTouchpad(joystick, 2); /* DSU supports 2 fingers */ } /* Register sensors if available */ 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 */ + /* DSU reports gyro at varying rates, typically 250-1000Hz for DS4/DS5 */ SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f); + /* Enable sensor directly - DSU always provides sensor data */ + if (joystick->nsensors > 0) { + joystick->sensors[joystick->nsensors - 1].enabled = true; + } slot->has_gyro = true; + slot->sensors_enabled = true; } if (slot->has_accel || (slot->model == DSU_MODEL_FULL_GYRO)) { /* DSU reports accelerometer at same rate as gyro */ SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f); + /* Enable sensor directly - DSU always provides sensor data */ + if (joystick->nsensors > 0) { + joystick->sensors[joystick->nsensors - 1].enabled = true; + } slot->has_accel = true; + slot->sensors_enabled = true; } return true; @@ -970,21 +1110,20 @@ static bool DSU_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumb { DSU_ControllerSlot *slot = (DSU_ControllerSlot *)joystick->hwdata; DSU_RumblePacket packet; - struct sockaddr_in server; - struct DSU_Context_t *ctx; + struct sockaddr_in server_addr; + DSU_ServerConnection *conn; - ctx = s_dsu_ctx; - if (!ctx || !slot || !slot->connected) { - SDL_SetError("DSU controller not available"); - return false; + if (!slot || !slot->connected || !slot->parent_conn) { + return SDL_SetError("DSU controller not available"); } + conn = slot->parent_conn; /* 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.client_id = SDL_Swap32LE(conn->client_id); packet.header.message_type = SDL_Swap32LE(DSU_MSG_RUMBLE); /* Set rumble values */ @@ -997,14 +1136,13 @@ static bool DSU_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumb 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 (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; + SDL_memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = DSU_htons(conn->server_port); + server_addr.sin_addr.s_addr = DSU_ipv4_addr(conn->server_address); + if (DSU_sendto(conn->socket, (const char*)&packet, (int)sizeof(packet), 0, + (struct sockaddr *)&server_addr, (int)sizeof(server_addr)) < 0) { + return SDL_SetError("Failed to send rumble packet"); } return true; @@ -1012,31 +1150,27 @@ static bool DSU_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumb static bool DSU_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) { - SDL_Unsupported(); - return false; + return SDL_Unsupported(); } static bool DSU_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) { - SDL_Unsupported(); - return false; + return SDL_Unsupported(); } static bool DSU_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) { - SDL_Unsupported(); - return false; + return SDL_Unsupported(); } 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 SDL_Unsupported(); } + slot->sensors_enabled = enabled; return true; } @@ -1064,13 +1198,13 @@ static void DSU_JoystickUpdate(SDL_Joystick *joystick) timestamp = SDL_GetTicks(); /* Update buttons */ - for (i = 0; i < 12; i++) { + for (i = 0; i < joystick->nbuttons && i < 12; i++) { bool pressed = (slot->buttons & (1 << i)) ? true : false; SDL_SendJoystickButton(timestamp, joystick, (Uint8)i, pressed); } /* Update axes */ - for (i = 0; i < 6; i++) { + for (i = 0; i < joystick->naxes && i < 6; i++) { SDL_SendJoystickAxis(timestamp, joystick, (Uint8)i, slot->axes[i]); } @@ -1142,14 +1276,16 @@ static void DSU_JoystickUpdate(SDL_Joystick *joystick) break; } - /* Update sensors if available */ - if (slot->has_gyro) { - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, - slot->motion_timestamp, slot->gyro, 3); - } - if (slot->has_accel) { - SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, - slot->motion_timestamp, slot->accel, 3); + /* Update sensors if enabled */ + if (slot->sensors_enabled) { + if (slot->has_gyro) { + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, + slot->motion_timestamp, slot->gyro, 3); + } + if (slot->has_accel) { + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, + slot->motion_timestamp, slot->accel, 3); + } } SDL_UnlockMutex(mutex); @@ -1157,19 +1293,15 @@ static void DSU_JoystickUpdate(SDL_Joystick *joystick) 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; - } - + /* SDL handles touchpad cleanup - we just clear hwdata */ joystick->hwdata = NULL; } static void DSU_JoystickQuit(void) { struct DSU_Context_t *ctx; + int s; + DSU_ServerConnection *conn; ctx = s_dsu_ctx; if (!ctx) { @@ -1179,21 +1311,30 @@ static void DSU_JoystickQuit(void) /* 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); + /* Stop all receiver threads */ + for (s = 0; s < ctx->server_count; s++) { + conn = &ctx->servers[s]; + if (SDL_GetAtomicInt(&conn->running) != 0) { + SDL_SetAtomicInt(&conn->running, 0); + } } - /* Close socket to interrupt any blocking recvfrom */ - if (ctx->socket != DSU_INVALID_SOCKET) { - DSU_CloseSocket(ctx->socket); - ctx->socket = DSU_INVALID_SOCKET; + /* Close all sockets to interrupt any blocking select/recvfrom */ + for (s = 0; s < ctx->server_count; s++) { + conn = &ctx->servers[s]; + if (conn->socket != DSU_INVALID_SOCKET) { + DSU_CloseSocket(conn->socket); + conn->socket = DSU_INVALID_SOCKET; + } } - /* Now wait for thread to finish */ - if (ctx->receiver_thread) { - SDL_WaitThread(ctx->receiver_thread, NULL); - ctx->receiver_thread = NULL; + /* Wait for all threads to finish */ + for (s = 0; s < ctx->server_count; s++) { + conn = &ctx->servers[s]; + if (conn->receiver_thread) { + SDL_WaitThread(conn->receiver_thread, NULL); + conn->receiver_thread = NULL; + } } /* Clean up sockets */ @@ -1222,7 +1363,7 @@ SDL_JoystickDriver SDL_DSU_JoystickDriver = { DSU_JoystickIsDevicePresent, DSU_JoystickGetDeviceName, DSU_JoystickGetDevicePath, - NULL, /* GetDeviceSteamVirtualGamepadSlot */ + DSU_JoystickGetDeviceSteamVirtualGamepadSlot, DSU_JoystickGetDevicePlayerIndex, DSU_JoystickSetDevicePlayerIndex, DSU_JoystickGetDeviceGUID, diff --git a/src/joystick/dsu/SDL_dsujoystick_c.h b/src/joystick/dsu/SDL_dsujoystick_c.h index 8c56c2f1ef..a8127dd69f 100644 --- a/src/joystick/dsu/SDL_dsujoystick_c.h +++ b/src/joystick/dsu/SDL_dsujoystick_c.h @@ -71,6 +71,7 @@ typedef struct DSU_ControllerSlot { /* Motion data */ bool has_gyro; bool has_accel; + bool sensors_enabled; /* Track if SDL enabled sensors */ float gyro[3]; /* Pitch, Yaw, Roll in rad/s */ float accel[3]; /* X, Y, Z in m/s² */ Uint64 motion_timestamp; @@ -87,9 +88,16 @@ typedef struct DSU_ControllerSlot { /* Timing */ Uint64 last_packet_time; Uint32 packet_number; + + /* Back-reference to parent server connection */ + struct DSU_ServerConnection *parent_conn; } DSU_ControllerSlot; -typedef struct DSU_Context_t { +/* Maximum number of DSU servers that can be connected simultaneously */ +#define DSU_MAX_SERVERS 4 + +/* Per-server connection state */ +typedef struct DSU_ServerConnection { /* Network */ dsu_socket_t socket; SDL_Thread *receiver_thread; @@ -98,15 +106,29 @@ typedef struct DSU_Context_t { /* Server configuration */ char server_address[256]; Uint16 server_port; - Uint16 client_port; Uint32 client_id; - /* Controller slots (4 max per DSU protocol) */ + /* Controller slots (4 max per DSU server) */ DSU_ControllerSlot slots[DSU_MAX_SLOTS]; - SDL_Mutex *slots_mutex; /* Timing for periodic updates */ Uint64 last_request_time; + + /* Back-reference to parent context */ + struct DSU_Context_t *parent; + int server_index; +} DSU_ServerConnection; + +typedef struct DSU_Context_t { + /* Server connections */ + DSU_ServerConnection servers[DSU_MAX_SERVERS]; + int server_count; + + /* Client port (shared across all servers) */ + Uint16 client_port; + + /* Shared mutex for all slots */ + SDL_Mutex *slots_mutex; } DSU_Context; #endif /* SDL_JOYSTICK_DSU */ From 6a44baad9b64249e2edb69dfbfc1798fae56b898 Mon Sep 17 00:00:00 2001 From: danprice142 Date: Thu, 5 Feb 2026 07:29:28 +0000 Subject: [PATCH 16/16] Fix DSU joystick build on Unix platforms Add missing sys/time.h for struct timeval and sys/select.h for select() function and fd_set types. These headers are required for socket operations on Unix-like platforms including Linux, BSD, and other POSIX systems. This resolves compilation errors: - 'storage size of tv isn't known' (struct timeval) - 'implicit declaration of function select' - 'fd_set' and related macros undeclared The headers are added to the standard Unix/Linux platform section after errno.h and before sys/ioctl.h to maintain proper ordering. --- src/joystick/dsu/SDL_dsujoystick.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/joystick/dsu/SDL_dsujoystick.c b/src/joystick/dsu/SDL_dsujoystick.c index 65e2354e17..f4779b75c5 100644 --- a/src/joystick/dsu/SDL_dsujoystick.c +++ b/src/joystick/dsu/SDL_dsujoystick.c @@ -88,6 +88,8 @@ typedef int socklen_t; #include #include #include + #include + #include #ifdef HAVE_SYS_IOCTL_H #include #endif