Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_rumble.c
9906 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#ifdef SDL_JOYSTICK_HIDAPI2324// Handle rumble on a separate thread so it doesn't block the application2526#include "SDL_hidapijoystick_c.h"27#include "SDL_hidapi_rumble.h"28#include "../../thread/SDL_systhread.h"2930typedef struct SDL_HIDAPI_RumbleRequest31{32SDL_HIDAPI_Device *device;33Uint8 data[2 * USB_PACKET_LENGTH]; // need enough space for the biggest report: dualshock4 is 78 bytes34int size;35SDL_HIDAPI_RumbleSentCallback callback;36void *userdata;37struct SDL_HIDAPI_RumbleRequest *prev;3839} SDL_HIDAPI_RumbleRequest;4041typedef struct SDL_HIDAPI_RumbleContext42{43SDL_AtomicInt initialized;44SDL_AtomicInt running;45SDL_Thread *thread;46SDL_Semaphore *request_sem;47SDL_HIDAPI_RumbleRequest *requests_head;48SDL_HIDAPI_RumbleRequest *requests_tail;49} SDL_HIDAPI_RumbleContext;5051#ifndef SDL_THREAD_SAFETY_ANALYSIS52static53#endif54SDL_Mutex *SDL_HIDAPI_rumble_lock;55static SDL_HIDAPI_RumbleContext rumble_context SDL_GUARDED_BY(SDL_HIDAPI_rumble_lock);5657static int SDLCALL SDL_HIDAPI_RumbleThread(void *data)58{59SDL_HIDAPI_RumbleContext *ctx = (SDL_HIDAPI_RumbleContext *)data;6061SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH);6263while (SDL_GetAtomicInt(&ctx->running)) {64SDL_HIDAPI_RumbleRequest *request = NULL;6566SDL_WaitSemaphore(ctx->request_sem);6768SDL_LockMutex(SDL_HIDAPI_rumble_lock);69request = ctx->requests_tail;70if (request) {71if (request == ctx->requests_head) {72ctx->requests_head = NULL;73}74ctx->requests_tail = request->prev;75}76SDL_UnlockMutex(SDL_HIDAPI_rumble_lock);7778if (request) {79SDL_LockMutex(request->device->dev_lock);80if (request->device->dev) {81#ifdef DEBUG_RUMBLE82HIDAPI_DumpPacket("Rumble packet: size = %d", request->data, request->size);83#endif84SDL_hid_write(request->device->dev, request->data, request->size);85}86SDL_UnlockMutex(request->device->dev_lock);87if (request->callback) {88request->callback(request->userdata);89}90(void)SDL_AtomicDecRef(&request->device->rumble_pending);91SDL_free(request);9293// Make sure we're not starving report reads when there's lots of rumble94SDL_Delay(10);95}96}97return 0;98}99100static void SDL_HIDAPI_StopRumbleThread(SDL_HIDAPI_RumbleContext *ctx)101{102SDL_HIDAPI_RumbleRequest *request;103104SDL_SetAtomicInt(&ctx->running, false);105106if (ctx->thread) {107int result;108109SDL_SignalSemaphore(ctx->request_sem);110SDL_WaitThread(ctx->thread, &result);111ctx->thread = NULL;112}113114SDL_LockMutex(SDL_HIDAPI_rumble_lock);115while (ctx->requests_tail) {116request = ctx->requests_tail;117if (request == ctx->requests_head) {118ctx->requests_head = NULL;119}120ctx->requests_tail = request->prev;121122if (request->callback) {123request->callback(request->userdata);124}125(void)SDL_AtomicDecRef(&request->device->rumble_pending);126SDL_free(request);127}128SDL_UnlockMutex(SDL_HIDAPI_rumble_lock);129130if (ctx->request_sem) {131SDL_DestroySemaphore(ctx->request_sem);132ctx->request_sem = NULL;133}134135if (SDL_HIDAPI_rumble_lock) {136SDL_DestroyMutex(SDL_HIDAPI_rumble_lock);137SDL_HIDAPI_rumble_lock = NULL;138}139140SDL_SetAtomicInt(&ctx->initialized, false);141}142143static bool SDL_HIDAPI_StartRumbleThread(SDL_HIDAPI_RumbleContext *ctx)144{145SDL_HIDAPI_rumble_lock = SDL_CreateMutex();146if (!SDL_HIDAPI_rumble_lock) {147SDL_HIDAPI_StopRumbleThread(ctx);148return false;149}150151ctx->request_sem = SDL_CreateSemaphore(0);152if (!ctx->request_sem) {153SDL_HIDAPI_StopRumbleThread(ctx);154return false;155}156157SDL_SetAtomicInt(&ctx->running, true);158ctx->thread = SDL_CreateThread(SDL_HIDAPI_RumbleThread, "HIDAPI Rumble", ctx);159if (!ctx->thread) {160SDL_HIDAPI_StopRumbleThread(ctx);161return false;162}163return true;164}165166bool SDL_HIDAPI_LockRumble(void)167{168SDL_HIDAPI_RumbleContext *ctx = &rumble_context;169170if (SDL_CompareAndSwapAtomicInt(&ctx->initialized, false, true)) {171if (!SDL_HIDAPI_StartRumbleThread(ctx)) {172return false;173}174}175176SDL_LockMutex(SDL_HIDAPI_rumble_lock);177return true;178}179180bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size)181{182SDL_HIDAPI_RumbleContext *ctx = &rumble_context;183SDL_HIDAPI_RumbleRequest *request, *found;184185found = NULL;186for (request = ctx->requests_tail; request; request = request->prev) {187if (request->device == device) {188found = request;189}190}191if (found) {192*data = found->data;193*size = &found->size;194*maximum_size = sizeof(found->data);195return true;196}197return false;198}199200int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size)201{202return SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device, data, size, NULL, NULL);203}204205int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size, SDL_HIDAPI_RumbleSentCallback callback, void *userdata)206{207SDL_HIDAPI_RumbleContext *ctx = &rumble_context;208SDL_HIDAPI_RumbleRequest *request;209210if (size > sizeof(request->data)) {211SDL_HIDAPI_UnlockRumble();212SDL_SetError("Couldn't send rumble, size %d is greater than %d", size, (int)sizeof(request->data));213return -1;214}215216request = (SDL_HIDAPI_RumbleRequest *)SDL_calloc(1, sizeof(*request));217if (!request) {218SDL_HIDAPI_UnlockRumble();219return -1;220}221request->device = device;222SDL_memcpy(request->data, data, size);223request->size = size;224request->callback = callback;225request->userdata = userdata;226227SDL_AtomicIncRef(&device->rumble_pending);228229if (ctx->requests_head) {230ctx->requests_head->prev = request;231} else {232ctx->requests_tail = request;233}234ctx->requests_head = request;235236// Make sure we unlock before posting the semaphore so the rumble thread can run immediately237SDL_HIDAPI_UnlockRumble();238239SDL_SignalSemaphore(ctx->request_sem);240241return size;242}243244void SDL_HIDAPI_UnlockRumble(void)245{246SDL_UnlockMutex(SDL_HIDAPI_rumble_lock);247}248249int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size)250{251Uint8 *pending_data;252int *pending_size;253int maximum_size;254255if (size <= 0) {256SDL_SetError("Tried to send rumble with invalid size");257return -1;258}259260if (!SDL_HIDAPI_LockRumble()) {261return -1;262}263264// check if there is a pending request for the device and update it265if (SDL_HIDAPI_GetPendingRumbleLocked(device, &pending_data, &pending_size, &maximum_size) &&266size == *pending_size && data[0] == pending_data[0]) {267SDL_memcpy(pending_data, data, size);268SDL_HIDAPI_UnlockRumble();269return size;270}271272return SDL_HIDAPI_SendRumbleAndUnlock(device, data, size);273}274275void SDL_HIDAPI_QuitRumble(void)276{277SDL_HIDAPI_RumbleContext *ctx = &rumble_context;278279if (SDL_GetAtomicInt(&ctx->running)) {280SDL_HIDAPI_StopRumbleThread(ctx);281}282}283284#endif // SDL_JOYSTICK_HIDAPI285286287