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.
This commit is contained in:
danprice142 2025-11-15 23:10:37 +00:00
parent fe8c232ff2
commit bc413bf483
4 changed files with 787 additions and 898 deletions

View file

@ -640,7 +640,6 @@
<ClCompile Include="..\..\src\joystick\SDL_joystick.c" />
<ClCompile Include="..\..\src\joystick\SDL_steam_virtual_gamepad.c" />
<ClCompile Include="..\..\src\joystick\dsu\SDL_dsujoystick.c" />
<ClCompile Include="..\..\src\joystick\dsu\SDL_dsujoystick_driver.c" />
<ClCompile Include="..\..\src\joystick\virtual\SDL_virtualjoystick.c" />
<ClCompile Include="..\..\src\joystick\windows\SDL_dinputjoystick.c" />
<ClCompile Include="..\..\src\joystick\windows\SDL_rawinputjoystick.c" />

View file

@ -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: */

View file

@ -43,7 +43,7 @@ extern SDL_JoystickDriver SDL_DSU_JoystickDriver;
#endif
#include <winsock2.h>
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_ */

View file

@ -1,836 +0,0 @@
/*
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 socket headers before SDL to avoid macro conflicts */
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib")
#endif
#else
/* Unix-like systems including Haiku */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#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 <SDL3/SDL_timer.h>
/* 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: */