SDL_hidapi_rumble.c (7444B)
1 /* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20 */ 21 #include "../../SDL_internal.h" 22 23 #ifdef SDL_JOYSTICK_HIDAPI 24 25 /* Handle rumble on a separate thread so it doesn't block the application */ 26 27 #include "SDL_thread.h" 28 #include "SDL_hidapijoystick_c.h" 29 #include "SDL_hidapi_rumble.h" 30 #include "../../thread/SDL_systhread.h" 31 32 33 typedef struct SDL_HIDAPI_RumbleRequest 34 { 35 SDL_HIDAPI_Device *device; 36 Uint8 data[2*USB_PACKET_LENGTH]; /* need enough space for the biggest report: dualshock4 is 78 bytes */ 37 int size; 38 struct SDL_HIDAPI_RumbleRequest *prev; 39 40 } SDL_HIDAPI_RumbleRequest; 41 42 typedef struct SDL_HIDAPI_RumbleContext 43 { 44 SDL_atomic_t initialized; 45 SDL_atomic_t running; 46 SDL_Thread *thread; 47 SDL_mutex *lock; 48 SDL_sem *request_sem; 49 SDL_HIDAPI_RumbleRequest *requests_head; 50 SDL_HIDAPI_RumbleRequest *requests_tail; 51 } SDL_HIDAPI_RumbleContext; 52 53 static SDL_HIDAPI_RumbleContext rumble_context; 54 55 static int SDL_HIDAPI_RumbleThread(void *data) 56 { 57 SDL_HIDAPI_RumbleContext *ctx = (SDL_HIDAPI_RumbleContext *)data; 58 59 SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); 60 61 while (SDL_AtomicGet(&ctx->running)) { 62 SDL_HIDAPI_RumbleRequest *request = NULL; 63 64 SDL_SemWait(ctx->request_sem); 65 66 SDL_LockMutex(ctx->lock); 67 request = ctx->requests_tail; 68 if (request) { 69 if (request == ctx->requests_head) { 70 ctx->requests_head = NULL; 71 } 72 ctx->requests_tail = request->prev; 73 } 74 SDL_UnlockMutex(ctx->lock); 75 76 if (request) { 77 SDL_LockMutex(request->device->dev_lock); 78 if (request->device->dev) { 79 hid_write( request->device->dev, request->data, request->size ); 80 } 81 SDL_UnlockMutex(request->device->dev_lock); 82 (void)SDL_AtomicDecRef(&request->device->rumble_pending); 83 SDL_free(request); 84 } 85 } 86 return 0; 87 } 88 89 static void 90 SDL_HIDAPI_StopRumbleThread(SDL_HIDAPI_RumbleContext *ctx) 91 { 92 SDL_HIDAPI_RumbleRequest *request; 93 94 SDL_AtomicSet(&ctx->running, SDL_FALSE); 95 96 if (ctx->thread) { 97 int result; 98 99 SDL_SemPost(ctx->request_sem); 100 SDL_WaitThread(ctx->thread, &result); 101 ctx->thread = NULL; 102 } 103 104 SDL_LockMutex(ctx->lock); 105 while (ctx->requests_tail) { 106 request = ctx->requests_tail; 107 if (request == ctx->requests_head) { 108 ctx->requests_head = NULL; 109 } 110 ctx->requests_tail = request->prev; 111 112 (void)SDL_AtomicDecRef(&request->device->rumble_pending); 113 SDL_free(request); 114 } 115 SDL_UnlockMutex(ctx->lock); 116 117 if (ctx->request_sem) { 118 SDL_DestroySemaphore(ctx->request_sem); 119 ctx->request_sem = NULL; 120 } 121 122 if (ctx->lock) { 123 SDL_DestroyMutex(ctx->lock); 124 ctx->lock = NULL; 125 } 126 127 SDL_AtomicSet(&ctx->initialized, SDL_FALSE); 128 } 129 130 static int 131 SDL_HIDAPI_StartRumbleThread(SDL_HIDAPI_RumbleContext *ctx) 132 { 133 ctx->lock = SDL_CreateMutex(); 134 if (!ctx->lock) { 135 SDL_HIDAPI_StopRumbleThread(ctx); 136 return -1; 137 } 138 139 ctx->request_sem = SDL_CreateSemaphore(0); 140 if (!ctx->request_sem) { 141 SDL_HIDAPI_StopRumbleThread(ctx); 142 return -1; 143 } 144 145 SDL_AtomicSet(&ctx->running, SDL_TRUE); 146 ctx->thread = SDL_CreateThreadInternal(SDL_HIDAPI_RumbleThread, "HIDAPI Rumble", 0, ctx); 147 if (!ctx->thread) { 148 SDL_HIDAPI_StopRumbleThread(ctx); 149 return -1; 150 } 151 return 0; 152 } 153 154 int SDL_HIDAPI_LockRumble(void) 155 { 156 SDL_HIDAPI_RumbleContext *ctx = &rumble_context; 157 158 if (SDL_AtomicCAS(&ctx->initialized, SDL_FALSE, SDL_TRUE)) { 159 if (SDL_HIDAPI_StartRumbleThread(ctx) < 0) { 160 return -1; 161 } 162 } 163 164 return SDL_LockMutex(ctx->lock); 165 } 166 167 SDL_bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size) 168 { 169 SDL_HIDAPI_RumbleContext *ctx = &rumble_context; 170 SDL_HIDAPI_RumbleRequest *request, *found; 171 172 found = NULL; 173 for (request = ctx->requests_tail; request; request = request->prev) { 174 if (request->device == device) { 175 found = request; 176 } 177 } 178 if (found) { 179 *data = found->data; 180 *size = &found->size; 181 *maximum_size = sizeof(found->data); 182 return SDL_TRUE; 183 } 184 return SDL_FALSE; 185 } 186 187 int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size) 188 { 189 SDL_HIDAPI_RumbleContext *ctx = &rumble_context; 190 SDL_HIDAPI_RumbleRequest *request; 191 192 if (size > sizeof(request->data)) { 193 SDL_HIDAPI_UnlockRumble(); 194 return SDL_SetError("Couldn't send rumble, size %d is greater than %d", size, (int)sizeof(request->data)); 195 } 196 197 request = (SDL_HIDAPI_RumbleRequest *)SDL_calloc(1, sizeof(*request)); 198 if (!request) { 199 SDL_HIDAPI_UnlockRumble(); 200 return SDL_OutOfMemory(); 201 } 202 request->device = device; 203 SDL_memcpy(request->data, data, size); 204 request->size = size; 205 206 SDL_AtomicIncRef(&device->rumble_pending); 207 208 if (ctx->requests_head) { 209 ctx->requests_head->prev = request; 210 } else { 211 ctx->requests_tail = request; 212 } 213 ctx->requests_head = request; 214 215 /* Make sure we unlock before posting the semaphore so the rumble thread can run immediately */ 216 SDL_HIDAPI_UnlockRumble(); 217 218 SDL_SemPost(ctx->request_sem); 219 220 return size; 221 } 222 223 void SDL_HIDAPI_UnlockRumble(void) 224 { 225 SDL_HIDAPI_RumbleContext *ctx = &rumble_context; 226 227 SDL_UnlockMutex(ctx->lock); 228 } 229 230 int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size) 231 { 232 Uint8 *pending_data; 233 int *pending_size; 234 int maximum_size; 235 236 if (SDL_HIDAPI_LockRumble() < 0) { 237 return -1; 238 } 239 240 /* check if there is a pending request for the device and update it */ 241 if (SDL_HIDAPI_GetPendingRumbleLocked(device, &pending_data, &pending_size, &maximum_size)) { 242 if (size > maximum_size) { 243 SDL_HIDAPI_UnlockRumble(); 244 return SDL_SetError("Couldn't send rumble, size %d is greater than %d", size, maximum_size); 245 } 246 247 SDL_memcpy(pending_data, data, size); 248 *pending_size = size; 249 SDL_HIDAPI_UnlockRumble(); 250 return size; 251 } 252 253 return SDL_HIDAPI_SendRumbleAndUnlock(device, data, size); 254 } 255 256 void SDL_HIDAPI_QuitRumble(void) 257 { 258 SDL_HIDAPI_RumbleContext *ctx = &rumble_context; 259 260 if (SDL_AtomicGet(&ctx->running)) { 261 SDL_HIDAPI_StopRumbleThread(ctx); 262 } 263 } 264 265 #endif /* SDL_JOYSTICK_HIDAPI */ 266 267 /* vi: set ts=4 sw=4 expandtab: */