This commit is contained in:
danprice142 2026-06-06 00:22:04 +08:00 committed by GitHub
commit 5375567a82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1470 additions and 0 deletions

View file

@ -1256,6 +1256,35 @@ if(SDL_JOYSTICK)
file(GLOB JOYSTICK_VIRTUAL_SOURCES ${SDL2_SOURCE_DIR}/src/joystick/virtual/*.c)
list(APPEND SOURCE_FILES ${JOYSTICK_VIRTUAL_SOURCES})
endif()
# DSU (DualShock UDP) client support
option(SDL_DSU_JOYSTICK "Enable DSU client joystick support" ON)
if(SDL_DSU_JOYSTICK)
set(SDL_JOYSTICK_DSU 1)
file(GLOB JOYSTICK_DSU_SOURCES ${SDL2_SOURCE_DIR}/src/joystick/dsu/*.c)
list(APPEND SOURCE_FILES ${JOYSTICK_DSU_SOURCES})
# DSU requires network libraries on Unix-like systems
if(UNIX AND NOT APPLE AND NOT ANDROID)
if(NOT WIN32)
# Check if we need to link against socket libraries
include(CheckFunctionExists)
check_function_exists(socket HAVE_SOCKET_IN_LIBC)
if(NOT HAVE_SOCKET_IN_LIBC)
# Try to find socket in libsocket (Solaris)
check_library_exists(socket socket "" HAVE_LIBSOCKET)
if(HAVE_LIBSOCKET)
list(APPEND EXTRA_LIBS socket)
endif()
# Try to find inet_addr in libnsl (Solaris)
check_library_exists(nsl inet_addr "" HAVE_LIBNSL)
if(HAVE_LIBNSL)
list(APPEND EXTRA_LIBS nsl)
endif()
endif()
endif()
endif()
endif()
endif()
if(SDL_VIDEO)
@ -1995,6 +2024,10 @@ elseif(WINDOWS)
# Libraries for Win32 native and MinGW
if(NOT WINDOWS_STORE)
list(APPEND EXTRA_LIBS kernel32 user32 gdi32 winmm imm32 ole32 oleaut32 version uuid advapi32 setupapi shell32)
# Add Winsock library for DSU support
if(SDL_DSU_JOYSTICK)
list(APPEND EXTRA_LIBS ws2_32)
endif()
endif()
if(WINDOWS_STORE)
@ -2479,6 +2512,11 @@ elseif(HAIKU)
CheckPTHREAD()
list(APPEND EXTRA_LIBS root be media game device textencoding)
# Add network library for DSU support on Haiku
if(SDL_DSU_JOYSTICK)
list(APPEND EXTRA_LIBS network)
endif()
elseif(RISCOS)
if(SDL_MISC)

View file

@ -351,6 +351,7 @@
#cmakedefine SDL_JOYSTICK_RAWINPUT @SDL_JOYSTICK_RAWINPUT@
#cmakedefine SDL_JOYSTICK_EMSCRIPTEN @SDL_JOYSTICK_EMSCRIPTEN@
#cmakedefine SDL_JOYSTICK_VIRTUAL @SDL_JOYSTICK_VIRTUAL@
#cmakedefine SDL_JOYSTICK_DSU @SDL_JOYSTICK_DSU@
#cmakedefine SDL_JOYSTICK_VITA @SDL_JOYSTICK_VITA@
#cmakedefine SDL_JOYSTICK_PSP @SDL_JOYSTICK_PSP@
#cmakedefine SDL_JOYSTICK_PS2 @SDL_JOYSTICK_PS2@

View file

@ -261,6 +261,7 @@ typedef unsigned int uintptr_t;
#define SDL_JOYSTICK_RAWINPUT 1
#endif
#define SDL_JOYSTICK_VIRTUAL 1
#define SDL_JOYSTICK_DSU 1
#ifdef HAVE_WINDOWS_GAMING_INPUT_H
#define SDL_JOYSTICK_WGI 1
#endif

View file

@ -1247,6 +1247,40 @@ extern "C" {
*/
#define SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED "SDL_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED"
/**
* 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.
*/
#define SDL_HINT_JOYSTICK_DSU "SDL_JOYSTICK_DSU"
/**
* A variable controlling the DSU server address.
*
* The default value is "127.0.0.1"
*/
#define SDL_HINT_DSU_SERVER "SDL_DSU_SERVER"
/**
* A variable controlling the DSU server port.
*
* The default value is "26760"
*/
#define SDL_HINT_DSU_SERVER_PORT "SDL_DSU_SERVER_PORT"
/**
* A variable controlling the DSU client port.
*
* The default value is "0" (auto-select)
*/
#define SDL_HINT_DSU_CLIENT_PORT "SDL_DSU_CLIENT_PORT"
/**
* A variable controlling whether IOKit should be used for controller
* handling.

View file

@ -51,6 +51,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 /* Before WINDOWS_ driver, as WINDOWS wants to check if this driver is handling things */
&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

View file

@ -0,0 +1,465 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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 "SDL_joystick.h"
#include "SDL_endian.h"
#include "SDL_timer.h"
#include "SDL_hints.h"
#include "SDL_thread.h"
#include "SDL_atomic.h"
#include "../SDL_sysjoystick.h"
#include "../SDL_joystick_c.h"
#include "SDL_dsujoystick_c.h"
/* Platform-specific socket includes */
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
typedef int socklen_t;
#define DSU_SOCKET_ERROR SOCKET_ERROR
#define DSU_INVALID_SOCKET INVALID_SOCKET
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define DSU_SOCKET_ERROR -1
#define DSU_INVALID_SOCKET -1
#define closesocket close
#endif
#include <math.h>
/* Constants */
#define SERVER_REREGISTER_INTERVAL 1000 /* ms */
#define SERVER_TIMEOUT_INTERVAL 2000 /* ms */
#define GRAVITY_ACCELERATION 9.80665f /* m/s² */
/* Global DSU context - defined in SDL_dsujoystick_driver.c */
extern DSU_Context *g_dsu_context;
/* 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
}
int DSU_CreateSocket(Uint16 port)
{
int 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(int 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)
{
struct sockaddr_in server;
DSU_Header *header = (DSU_Header *)packet;
/* Set header fields */
SDL_memcpy(header->magic, DSU_MAGIC_CLIENT, 4);
header->version = SDL_SwapLE16(DSU_PROTOCOL_VERSION);
header->length = SDL_SwapLE16((Uint16)(size - sizeof(DSU_Header)));
header->client_id = SDL_SwapLE32(ctx->client_id);
/* Calculate CRC32 */
header->crc32 = 0;
header->crc32 = SDL_SwapLE32(DSU_CalculateCRC32((Uint8*)packet, size));
/* Send to server */
SDL_memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(ctx->server_port);
server.sin_addr.s_addr = inet_addr(ctx->server_address);
return sendto(ctx->socket, (const char*)packet, (int)size, 0,
(struct sockaddr *)&server, sizeof(server));
}
/* 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_SwapLE32(DSU_MSG_PORTS_INFO);
request.flags = 0;
request.slot_id = slot; /* 0xFF for all slots */
/* MAC is zeros for all controllers */
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_SwapLE32(DSU_MSG_DATA);
request.flags = 0; /* Subscribe to data */
request.slot_id = slot;
DSU_SendPacket(ctx, &request, sizeof(request));
}
/* Process incoming controller data */
void DSU_ProcessControllerData(DSU_Context *ctx, DSU_ControllerData *data)
{
DSU_ControllerSlot *slot;
int slot_id;
SDL_bool was_connected;
/* Get slot ID */
slot_id = data->info.slot;
if (slot_id >= DSU_MAX_SLOTS) {
return;
}
SDL_LockMutex(ctx->slots_mutex);
slot = &ctx->slots[slot_id];
/* Update connection state */
was_connected = slot->connected;
slot->connected = (data->info.slot_state == DSU_STATE_CONNECTED);
if (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 = SDL_TRUE;
slot->has_accel = SDL_TRUE;
slot->motion_timestamp = SDL_SwapLE64(data->motion_timestamp);
/* Convert gyro from deg/s to rad/s (handling endianness) */
slot->gyro[0] = SDL_SwapFloatLE(data->gyro_pitch) * (M_PI / 180.0f);
slot->gyro[1] = SDL_SwapFloatLE(data->gyro_yaw) * (M_PI / 180.0f);
slot->gyro[2] = SDL_SwapFloatLE(data->gyro_roll) * (M_PI / 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_GetTicks64();
/* Touch data */
slot->has_touchpad = SDL_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_SwapLE16(data->touch1_x);
slot->touch1_y = SDL_SwapLE16(data->touch1_y);
slot->touch2_x = SDL_SwapLE16(data->touch2_x);
slot->touch2_y = SDL_SwapLE16(data->touch2_y);
/* Update timing */
slot->last_packet_time = SDL_GetTicks64();
slot->packet_number = SDL_SwapLE32(data->packet_number);
}
SDL_UnlockMutex(ctx->slots_mutex);
/* Handle connection state changes */
if (!was_connected && slot->connected) {
Uint16 vendor;
Uint16 product;
/* New controller connected */
slot->instance_id = SDL_GetNextJoystickInstanceID();
/* 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);
/* Subscribe to controller data updates */
DSU_RequestControllerData(ctx, slot_id);
SDL_PrivateJoystickAdded(slot->instance_id);
} else if (was_connected && !slot->connected) {
/* Controller disconnected */
SDL_PrivateJoystickRemoved(slot->instance_id);
slot->instance_id = 0;
}
}
/* Receiver thread implementation */
int 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_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
while (SDL_AtomicGet(&ctx->running)) {
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_SwapLE32(header->crc32);
header->crc32 = 0;
calculated_crc = DSU_CalculateCRC32(buffer, received);
if (received_crc == calculated_crc) {
Uint32 msg_type = SDL_SwapLE32(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 */
/* 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_ProcessControllerData(ctx, (DSU_ControllerData *)buffer);
}
break;
default:
/* Unknown message type */
break;
}
}
}
} else if (received < 0) {
/* Check for real errors (not just EWOULDBLOCK) */
#ifdef _WIN32
int error = WSAGetLastError();
if (error != WSAEWOULDBLOCK && error != WSAEINTR && error != WSAECONNRESET) {
SDL_Delay(100); /* Back off on errors */
}
#else
if (errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) {
SDL_Delay(100);
}
#endif
}
/* Small delay to prevent CPU spinning */
SDL_Delay(1);
}
return 0;
}
#endif /* SDL_JOYSTICK_DSU */
/* vi: set ts=4 sw=4 expandtab: */

View file

@ -0,0 +1,108 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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 "../SDL_sysjoystick.h"
#include "SDL_dsuprotocol.h"
/* DSU Joystick driver */
extern SDL_JoystickDriver SDL_DSU_JoystickDriver;
/* Internal structures */
typedef struct DSU_ControllerSlot {
SDL_bool connected;
SDL_JoystickID instance_id;
SDL_JoystickGUID 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 */
SDL_bool has_gyro;
SDL_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 */
SDL_bool has_touchpad;
SDL_bool touch1_active;
SDL_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;
} DSU_ControllerSlot;
typedef struct DSU_Context {
/* Network */
int socket;
SDL_Thread *receiver_thread;
SDL_atomic_t 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;
/* Socket helpers */
int DSU_InitSockets(void);
void DSU_QuitSockets(void);
int DSU_CreateSocket(Uint16 port);
void DSU_CloseSocket(int socket);
/* CRC32 calculation */
Uint32 DSU_CalculateCRC32(const Uint8 *data, size_t length);
#endif /* SDL_JOYSTICK_DSU */
#endif /* SDL_dsujoystick_c_h_ */
/* vi: set ts=4 sw=4 expandtab: */

View file

@ -0,0 +1,615 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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 Joystick Driver - SDL Driver Interface Implementation */
#include "SDL_joystick.h"
#include "SDL_endian.h"
#include "SDL_timer.h"
#include "SDL_hints.h"
#include "SDL_thread.h"
#include "SDL_atomic.h"
#include "SDL_mutex.h"
#include "SDL_events.h"
#include "../SDL_sysjoystick.h"
#include "../SDL_joystick_c.h"
#include "../../thread/SDL_systhread.h"
#include "SDL_dsujoystick_c.h"
/* Platform-specific socket includes for rumble support */
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
/* Global DSU context (defined in SDL_dsujoystick.c) */
DSU_Context *g_dsu_context = NULL;
/* Forward declarations */
extern int DSU_ReceiverThread(void *data);
extern void DSU_RequestControllerInfo(DSU_Context *ctx, Uint8 slot);
extern void DSU_RequestControllerData(DSU_Context *ctx, Uint8 slot);
/* Driver functions */
static int DSU_JoystickInit(void)
{
const char *enabled;
const char *server;
const char *server_port;
const char *client_port;
/* Check if DSU is enabled */
enabled = SDL_GetHint(SDL_HINT_JOYSTICK_DSU);
if (enabled && SDL_atoi(enabled) == 0) {
return 0; /* DSU disabled */
}
/* Allocate context */
g_dsu_context = (DSU_Context *)SDL_calloc(1, sizeof(DSU_Context));
if (!g_dsu_context) {
return SDL_OutOfMemory();
}
/* Get configuration from hints with fallbacks */
server = SDL_GetHint(SDL_HINT_DSU_SERVER);
if (!server || !*server) {
server = DSU_SERVER_ADDRESS_DEFAULT;
}
SDL_strlcpy(g_dsu_context->server_address, server,
sizeof(g_dsu_context->server_address));
server_port = SDL_GetHint(SDL_HINT_DSU_SERVER_PORT);
if (server_port && *server_port) {
g_dsu_context->server_port = SDL_atoi(server_port);
} else {
g_dsu_context->server_port = DSU_SERVER_PORT_DEFAULT;
}
client_port = SDL_GetHint(SDL_HINT_DSU_CLIENT_PORT);
if (client_port && *client_port) {
g_dsu_context->client_port = SDL_atoi(client_port);
} else {
g_dsu_context->client_port = DSU_CLIENT_PORT_DEFAULT;
}
g_dsu_context->client_id = SDL_GetTicks();
/* Initialize sockets */
if (DSU_InitSockets() != 0) {
SDL_free(g_dsu_context);
g_dsu_context = NULL;
return -1;
}
/* Create UDP socket */
g_dsu_context->socket = DSU_CreateSocket(g_dsu_context->client_port);
if (g_dsu_context->socket == -1) {
DSU_QuitSockets();
SDL_free(g_dsu_context);
g_dsu_context = NULL;
return -1;
}
/* Create mutex */
g_dsu_context->slots_mutex = SDL_CreateMutex();
if (!g_dsu_context->slots_mutex) {
DSU_CloseSocket(g_dsu_context->socket);
DSU_QuitSockets();
SDL_free(g_dsu_context);
g_dsu_context = NULL;
return SDL_OutOfMemory();
}
/* Start receiver thread */
SDL_AtomicSet(&g_dsu_context->running, 1);
g_dsu_context->receiver_thread = SDL_CreateThreadInternal(
DSU_ReceiverThread, "DSU_Receiver", 0, g_dsu_context);
if (!g_dsu_context->receiver_thread) {
SDL_DestroyMutex(g_dsu_context->slots_mutex);
DSU_CloseSocket(g_dsu_context->socket);
DSU_QuitSockets();
SDL_free(g_dsu_context);
g_dsu_context = NULL;
return SDL_SetError("Failed to create DSU receiver thread");
}
/* Request controller info from all slots */
DSU_RequestControllerInfo(g_dsu_context, 0xFF);
return 0;
}
static int DSU_JoystickGetCount(void)
{
int count = 0;
int i;
if (!g_dsu_context) {
return 0;
}
SDL_LockMutex(g_dsu_context->slots_mutex);
for (i = 0; i < DSU_MAX_SLOTS; i++) {
if (g_dsu_context->slots[i].connected) {
count++;
}
}
SDL_UnlockMutex(g_dsu_context->slots_mutex);
return count;
}
static void DSU_JoystickDetect(void)
{
Uint64 now;
int i;
if (!g_dsu_context) {
return;
}
/* Periodically request controller info and re-subscribe to data */
now = SDL_GetTicks64();
if (now - g_dsu_context->last_request_time >= 500) { /* Request more frequently */
DSU_RequestControllerInfo(g_dsu_context, 0xFF);
/* Re-subscribe to data for connected controllers */
for (i = 0; i < DSU_MAX_SLOTS; i++) {
if (g_dsu_context->slots[i].connected) {
DSU_RequestControllerData(g_dsu_context, i);
}
}
g_dsu_context->last_request_time = now;
}
/* Check for timeouts */
SDL_LockMutex(g_dsu_context->slots_mutex);
for (i = 0; i < DSU_MAX_SLOTS; i++) {
if (g_dsu_context->slots[i].connected &&
now - g_dsu_context->slots[i].last_packet_time > 5000) { /* Increased timeout */
/* Controller timed out */
g_dsu_context->slots[i].connected = SDL_FALSE;
SDL_PrivateJoystickRemoved(g_dsu_context->slots[i].instance_id);
g_dsu_context->slots[i].instance_id = 0;
}
}
SDL_UnlockMutex(g_dsu_context->slots_mutex);
}
static const char *DSU_JoystickGetDeviceName(int device_index)
{
int i, count = 0;
if (!g_dsu_context) {
return NULL;
}
SDL_LockMutex(g_dsu_context->slots_mutex);
for (i = 0; i < DSU_MAX_SLOTS; i++) {
if (g_dsu_context->slots[i].connected) {
if (count == device_index) {
SDL_UnlockMutex(g_dsu_context->slots_mutex);
return g_dsu_context->slots[i].name;
}
count++;
}
}
SDL_UnlockMutex(g_dsu_context->slots_mutex);
return NULL;
}
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;
if (!g_dsu_context) {
return -1;
}
SDL_LockMutex(g_dsu_context->slots_mutex);
for (i = 0; i < DSU_MAX_SLOTS; i++) {
if (g_dsu_context->slots[i].connected) {
if (count == device_index) {
SDL_UnlockMutex(g_dsu_context->slots_mutex);
return i; /* Return slot ID as player index */
}
count++;
}
}
SDL_UnlockMutex(g_dsu_context->slots_mutex);
return -1;
}
static void DSU_JoystickSetDevicePlayerIndex(int device_index, int player_index)
{
/* DSU controllers have fixed slots, can't change */
}
static SDL_JoystickGUID DSU_JoystickGetDeviceGUID(int device_index)
{
SDL_JoystickGUID guid;
int i, count = 0;
SDL_zero(guid);
if (!g_dsu_context) {
return guid;
}
SDL_LockMutex(g_dsu_context->slots_mutex);
for (i = 0; i < DSU_MAX_SLOTS; i++) {
if (g_dsu_context->slots[i].connected) {
if (count == device_index) {
guid = g_dsu_context->slots[i].guid;
SDL_UnlockMutex(g_dsu_context->slots_mutex);
return guid;
}
count++;
}
}
SDL_UnlockMutex(g_dsu_context->slots_mutex);
return guid;
}
static SDL_JoystickID DSU_JoystickGetDeviceInstanceID(int device_index)
{
int i, count = 0;
if (!g_dsu_context) {
return -1;
}
SDL_LockMutex(g_dsu_context->slots_mutex);
for (i = 0; i < DSU_MAX_SLOTS; i++) {
if (g_dsu_context->slots[i].connected) {
if (count == device_index) {
SDL_JoystickID id = g_dsu_context->slots[i].instance_id;
SDL_UnlockMutex(g_dsu_context->slots_mutex);
return id;
}
count++;
}
}
SDL_UnlockMutex(g_dsu_context->slots_mutex);
return -1;
}
static int DSU_JoystickOpen(SDL_Joystick *joystick, int device_index)
{
DSU_ControllerSlot *slot = NULL;
int i, count = 0;
if (!g_dsu_context) {
return SDL_SetError("DSU not initialized");
}
/* Find the slot for this device */
SDL_LockMutex(g_dsu_context->slots_mutex);
for (i = 0; i < DSU_MAX_SLOTS; i++) {
if (g_dsu_context->slots[i].connected) {
if (count == device_index) {
slot = &g_dsu_context->slots[i];
break;
}
count++;
}
}
SDL_UnlockMutex(g_dsu_context->slots_mutex);
if (!slot) {
return SDL_SetError("DSU device not found");
}
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 */
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_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
slot->has_gyro = SDL_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);
slot->has_accel = SDL_TRUE;
}
return 0;
}
static int 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;
if (!g_dsu_context || !slot || !slot->connected) {
return SDL_SetError("DSU controller not available");
}
/* Build rumble packet */
SDL_memset(&packet, 0, sizeof(packet));
SDL_memcpy(packet.header.magic, DSU_MAGIC_CLIENT, 4);
packet.header.version = SDL_SwapLE16(DSU_PROTOCOL_VERSION);
packet.header.length = SDL_SwapLE16((Uint16)(sizeof(packet) - sizeof(DSU_Header)));
packet.header.client_id = SDL_SwapLE32(g_dsu_context->client_id);
packet.header.message_type = SDL_SwapLE32(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_SwapLE32(DSU_CalculateCRC32((Uint8*)&packet, sizeof(packet)));
/* Send to server */
SDL_memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(g_dsu_context->server_port);
server.sin_addr.s_addr = inet_addr(g_dsu_context->server_address);
if (sendto(g_dsu_context->socket, (const char*)&packet, sizeof(packet), 0,
(struct sockaddr *)&server, sizeof(server)) < 0) {
return SDL_SetError("Failed to send rumble packet");
}
return 0;
}
static int DSU_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
{
return SDL_Unsupported();
}
static Uint32 DSU_JoystickGetCapabilities(SDL_Joystick *joystick)
{
Uint32 caps = 0;
/* DSU protocol supports rumble through unofficial extensions */
caps |= SDL_JOYCAP_RUMBLE;
/* Note: SDL doesn't have a capability flag for motion sensors yet,
* but they're supported through SDL_JoystickGetSensor* APIs */
return caps;
}
static int DSU_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
{
return SDL_Unsupported();
}
static int DSU_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
{
return SDL_Unsupported();
}
static int DSU_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
{
DSU_ControllerSlot *slot = (DSU_ControllerSlot *)joystick->hwdata;
/* Sensors are always enabled if available */
return (slot->has_gyro || slot->has_accel) ? 0 : SDL_Unsupported();
}
static void DSU_JoystickUpdate(SDL_Joystick *joystick)
{
DSU_ControllerSlot *slot = (DSU_ControllerSlot *)joystick->hwdata;
int i;
if (!slot || !slot->connected) {
return;
}
SDL_LockMutex(g_dsu_context->slots_mutex);
/* Update buttons */
for (i = 0; i < 12; i++) {
SDL_PrivateJoystickButton(joystick, i, (slot->buttons & (1 << i)) ? 1 : 0);
}
/* Update axes */
for (i = 0; i < 6; i++) {
SDL_PrivateJoystickAxis(joystick, i, slot->axes[i]);
}
/* Update hat */
SDL_PrivateJoystickHat(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 */
Uint8 touchpad_state = slot->touch1_active ? SDL_PRESSED : SDL_RELEASED;
float touchpad_x = (float)slot->touch1_x / TOUCHPAD_WIDTH;
float touchpad_y = (float)slot->touch1_y / TOUCHPAD_HEIGHT;
/* 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_PrivateJoystickTouchpad(joystick, 0, 0, touchpad_state,
touchpad_x, touchpad_y,
touchpad_state ? 1.0f : 0.0f);
/* Second touch point */
touchpad_state = slot->touch2_active ? SDL_PRESSED : SDL_RELEASED;
touchpad_x = (float)slot->touch2_x / TOUCHPAD_WIDTH;
touchpad_y = (float)slot->touch2_y / TOUCHPAD_HEIGHT;
/* 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_PrivateJoystickTouchpad(joystick, 0, 1, touchpad_state,
touchpad_x, touchpad_y,
touchpad_state ? 1.0f : 0.0f);
}
/* Update battery level */
switch (slot->battery) {
case DSU_BATTERY_DYING:
case DSU_BATTERY_LOW:
SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_LOW);
break;
case DSU_BATTERY_MEDIUM:
SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_MEDIUM);
break;
case DSU_BATTERY_HIGH:
case DSU_BATTERY_FULL:
SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_FULL);
break;
case DSU_BATTERY_CHARGING:
case DSU_BATTERY_CHARGED:
SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_WIRED);
break;
default:
SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_UNKNOWN);
break;
}
/* Update sensors if available */
if (slot->has_gyro) {
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO,
slot->motion_timestamp, slot->gyro, 3);
}
if (slot->has_accel) {
SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL,
slot->motion_timestamp, slot->accel, 3);
}
SDL_UnlockMutex(g_dsu_context->slots_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)
{
if (!g_dsu_context) {
return;
}
/* Stop receiver thread */
SDL_AtomicSet(&g_dsu_context->running, 0);
if (g_dsu_context->receiver_thread) {
SDL_WaitThread(g_dsu_context->receiver_thread, NULL);
}
/* Close socket */
DSU_CloseSocket(g_dsu_context->socket);
DSU_QuitSockets();
/* Clean up mutex */
if (g_dsu_context->slots_mutex) {
SDL_DestroyMutex(g_dsu_context->slots_mutex);
}
/* Free context */
SDL_free(g_dsu_context);
g_dsu_context = NULL;
}
static SDL_bool DSU_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
{
/* DSU controllers map well to standard gamepad layout */
return SDL_FALSE; /* Use default mapping */
}
/* Export the driver */
SDL_JoystickDriver SDL_DSU_JoystickDriver = {
DSU_JoystickInit,
DSU_JoystickGetCount,
DSU_JoystickDetect,
DSU_JoystickGetDeviceName,
DSU_JoystickGetDevicePath,
NULL, /* GetDeviceSteamVirtualGamepadSlot */
DSU_JoystickGetDevicePlayerIndex,
DSU_JoystickSetDevicePlayerIndex,
DSU_JoystickGetDeviceGUID,
DSU_JoystickGetDeviceInstanceID,
DSU_JoystickOpen,
DSU_JoystickRumble,
DSU_JoystickRumbleTriggers,
DSU_JoystickGetCapabilities,
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: */

View file

@ -0,0 +1,201 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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: */