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