SDL_xinputhaptic.c (13305B)
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 #include "SDL_error.h" 24 #include "SDL_haptic.h" 25 #include "../SDL_syshaptic.h" 26 27 #if SDL_HAPTIC_XINPUT 28 29 #include "SDL_hints.h" 30 #include "SDL_timer.h" 31 #include "SDL_windowshaptic_c.h" 32 #include "SDL_xinputhaptic_c.h" 33 #include "../../core/windows/SDL_xinput.h" 34 #include "../../joystick/windows/SDL_windowsjoystick_c.h" 35 #include "../../thread/SDL_systhread.h" 36 37 /* 38 * Internal stuff. 39 */ 40 static SDL_bool loaded_xinput = SDL_FALSE; 41 42 43 int 44 SDL_XINPUT_HapticInit(void) 45 { 46 if (SDL_GetHintBoolean(SDL_HINT_XINPUT_ENABLED, SDL_TRUE)) { 47 loaded_xinput = (WIN_LoadXInputDLL() == 0); 48 } 49 50 if (loaded_xinput) { 51 DWORD i; 52 for (i = 0; i < XUSER_MAX_COUNT; i++) { 53 SDL_XINPUT_MaybeAddDevice(i); 54 } 55 } 56 return 0; 57 } 58 59 int 60 SDL_XINPUT_MaybeAddDevice(const DWORD dwUserid) 61 { 62 const Uint8 userid = (Uint8)dwUserid; 63 SDL_hapticlist_item *item; 64 XINPUT_VIBRATION state; 65 66 if ((!loaded_xinput) || (dwUserid >= XUSER_MAX_COUNT)) { 67 return -1; 68 } 69 70 /* Make sure we don't already have it */ 71 for (item = SDL_hapticlist; item; item = item->next) { 72 if (item->bXInputHaptic && item->userid == userid) { 73 return -1; /* Already added */ 74 } 75 } 76 77 SDL_zero(state); 78 if (XINPUTSETSTATE(dwUserid, &state) != ERROR_SUCCESS) { 79 return -1; /* no force feedback on this device. */ 80 } 81 82 item = (SDL_hapticlist_item *)SDL_malloc(sizeof(SDL_hapticlist_item)); 83 if (item == NULL) { 84 return SDL_OutOfMemory(); 85 } 86 87 SDL_zerop(item); 88 89 /* !!! FIXME: I'm not bothering to query for a real name right now (can we even?) */ 90 { 91 char buf[64]; 92 SDL_snprintf(buf, sizeof(buf), "XInput Controller #%u", (unsigned int)(userid + 1)); 93 item->name = SDL_strdup(buf); 94 } 95 96 if (!item->name) { 97 SDL_free(item); 98 return -1; 99 } 100 101 /* Copy the instance over, useful for creating devices. */ 102 item->bXInputHaptic = SDL_TRUE; 103 item->userid = userid; 104 105 return SDL_SYS_AddHapticDevice(item); 106 } 107 108 int 109 SDL_XINPUT_MaybeRemoveDevice(const DWORD dwUserid) 110 { 111 const Uint8 userid = (Uint8)dwUserid; 112 SDL_hapticlist_item *item; 113 SDL_hapticlist_item *prev = NULL; 114 115 if ((!loaded_xinput) || (dwUserid >= XUSER_MAX_COUNT)) { 116 return -1; 117 } 118 119 for (item = SDL_hapticlist; item != NULL; item = item->next) { 120 if (item->bXInputHaptic && item->userid == userid) { 121 /* found it, remove it. */ 122 return SDL_SYS_RemoveHapticDevice(prev, item); 123 } 124 prev = item; 125 } 126 return -1; 127 } 128 129 /* !!! FIXME: this is a hack, remove this later. */ 130 /* Since XInput doesn't offer a way to vibrate for X time, we hook into 131 * SDL_PumpEvents() to check if it's time to stop vibrating with some 132 * frequency. 133 * In practice, this works for 99% of use cases. But in an ideal world, 134 * we do this in a separate thread so that: 135 * - we aren't bound to when the app chooses to pump the event queue. 136 * - we aren't adding more polling to the event queue 137 * - we can emulate all the haptic effects correctly (start on a delay, 138 * mix multiple effects, etc). 139 * 140 * Mostly, this is here to get rumbling to work, and all the other features 141 * are absent in the XInput path for now. :( 142 */ 143 static int SDLCALL 144 SDL_RunXInputHaptic(void *arg) 145 { 146 struct haptic_hwdata *hwdata = (struct haptic_hwdata *) arg; 147 148 while (!SDL_AtomicGet(&hwdata->stopThread)) { 149 SDL_Delay(50); 150 SDL_LockMutex(hwdata->mutex); 151 /* If we're currently running and need to stop... */ 152 if (hwdata->stopTicks) { 153 if ((hwdata->stopTicks != SDL_HAPTIC_INFINITY) && SDL_TICKS_PASSED(SDL_GetTicks(), hwdata->stopTicks)) { 154 XINPUT_VIBRATION vibration = { 0, 0 }; 155 hwdata->stopTicks = 0; 156 XINPUTSETSTATE(hwdata->userid, &vibration); 157 } 158 } 159 SDL_UnlockMutex(hwdata->mutex); 160 } 161 162 return 0; 163 } 164 165 static int 166 SDL_XINPUT_HapticOpenFromUserIndex(SDL_Haptic *haptic, const Uint8 userid) 167 { 168 char threadName[32]; 169 XINPUT_VIBRATION vibration = { 0, 0 }; /* stop any current vibration */ 170 XINPUTSETSTATE(userid, &vibration); 171 172 haptic->supported = SDL_HAPTIC_LEFTRIGHT; 173 174 haptic->neffects = 1; 175 haptic->nplaying = 1; 176 177 /* Prepare effects memory. */ 178 haptic->effects = (struct haptic_effect *) 179 SDL_malloc(sizeof(struct haptic_effect) * haptic->neffects); 180 if (haptic->effects == NULL) { 181 return SDL_OutOfMemory(); 182 } 183 /* Clear the memory */ 184 SDL_memset(haptic->effects, 0, 185 sizeof(struct haptic_effect) * haptic->neffects); 186 187 haptic->hwdata = (struct haptic_hwdata *) SDL_malloc(sizeof(*haptic->hwdata)); 188 if (haptic->hwdata == NULL) { 189 SDL_free(haptic->effects); 190 haptic->effects = NULL; 191 return SDL_OutOfMemory(); 192 } 193 SDL_memset(haptic->hwdata, 0, sizeof(*haptic->hwdata)); 194 195 haptic->hwdata->bXInputHaptic = 1; 196 haptic->hwdata->userid = userid; 197 198 haptic->hwdata->mutex = SDL_CreateMutex(); 199 if (haptic->hwdata->mutex == NULL) { 200 SDL_free(haptic->effects); 201 SDL_free(haptic->hwdata); 202 haptic->effects = NULL; 203 return SDL_SetError("Couldn't create XInput haptic mutex"); 204 } 205 206 SDL_snprintf(threadName, sizeof(threadName), "SDLXInputDev%d", (int)userid); 207 haptic->hwdata->thread = SDL_CreateThreadInternal(SDL_RunXInputHaptic, threadName, 64 * 1024, haptic->hwdata); 208 209 if (haptic->hwdata->thread == NULL) { 210 SDL_DestroyMutex(haptic->hwdata->mutex); 211 SDL_free(haptic->effects); 212 SDL_free(haptic->hwdata); 213 haptic->effects = NULL; 214 return SDL_SetError("Couldn't create XInput haptic thread"); 215 } 216 217 return 0; 218 } 219 220 int 221 SDL_XINPUT_HapticOpen(SDL_Haptic * haptic, SDL_hapticlist_item *item) 222 { 223 return SDL_XINPUT_HapticOpenFromUserIndex(haptic, item->userid); 224 } 225 226 int 227 SDL_XINPUT_JoystickSameHaptic(SDL_Haptic * haptic, SDL_Joystick * joystick) 228 { 229 return (haptic->hwdata->userid == joystick->hwdata->userid); 230 } 231 232 int 233 SDL_XINPUT_HapticOpenFromJoystick(SDL_Haptic * haptic, SDL_Joystick * joystick) 234 { 235 SDL_hapticlist_item *item; 236 int index = 0; 237 238 /* Since it comes from a joystick we have to try to match it with a haptic device on our haptic list. */ 239 for (item = SDL_hapticlist; item != NULL; item = item->next) { 240 if (item->bXInputHaptic && item->userid == joystick->hwdata->userid) { 241 haptic->index = index; 242 return SDL_XINPUT_HapticOpenFromUserIndex(haptic, joystick->hwdata->userid); 243 } 244 ++index; 245 } 246 247 SDL_SetError("Couldn't find joystick in haptic device list"); 248 return -1; 249 } 250 251 void 252 SDL_XINPUT_HapticClose(SDL_Haptic * haptic) 253 { 254 SDL_AtomicSet(&haptic->hwdata->stopThread, 1); 255 SDL_WaitThread(haptic->hwdata->thread, NULL); 256 SDL_DestroyMutex(haptic->hwdata->mutex); 257 } 258 259 void 260 SDL_XINPUT_HapticQuit(void) 261 { 262 if (loaded_xinput) { 263 WIN_UnloadXInputDLL(); 264 loaded_xinput = SDL_FALSE; 265 } 266 } 267 268 int 269 SDL_XINPUT_HapticNewEffect(SDL_Haptic * haptic, struct haptic_effect *effect, SDL_HapticEffect * base) 270 { 271 SDL_assert(base->type == SDL_HAPTIC_LEFTRIGHT); /* should catch this at higher level */ 272 return SDL_XINPUT_HapticUpdateEffect(haptic, effect, base); 273 } 274 275 int 276 SDL_XINPUT_HapticUpdateEffect(SDL_Haptic * haptic, struct haptic_effect *effect, SDL_HapticEffect * data) 277 { 278 XINPUT_VIBRATION *vib = &effect->hweffect->vibration; 279 SDL_assert(data->type == SDL_HAPTIC_LEFTRIGHT); 280 /* SDL_HapticEffect has max magnitude of 32767, XInput expects 65535 max, so multiply */ 281 vib->wLeftMotorSpeed = data->leftright.large_magnitude * 2; 282 vib->wRightMotorSpeed = data->leftright.small_magnitude * 2; 283 SDL_LockMutex(haptic->hwdata->mutex); 284 if (haptic->hwdata->stopTicks) { /* running right now? Update it. */ 285 XINPUTSETSTATE(haptic->hwdata->userid, vib); 286 } 287 SDL_UnlockMutex(haptic->hwdata->mutex); 288 return 0; 289 } 290 291 int 292 SDL_XINPUT_HapticRunEffect(SDL_Haptic * haptic, struct haptic_effect *effect, Uint32 iterations) 293 { 294 XINPUT_VIBRATION *vib = &effect->hweffect->vibration; 295 SDL_assert(effect->effect.type == SDL_HAPTIC_LEFTRIGHT); /* should catch this at higher level */ 296 SDL_LockMutex(haptic->hwdata->mutex); 297 if (effect->effect.leftright.length == SDL_HAPTIC_INFINITY || iterations == SDL_HAPTIC_INFINITY) { 298 haptic->hwdata->stopTicks = SDL_HAPTIC_INFINITY; 299 } else if ((!effect->effect.leftright.length) || (!iterations)) { 300 /* do nothing. Effect runs for zero milliseconds. */ 301 } else { 302 haptic->hwdata->stopTicks = SDL_GetTicks() + (effect->effect.leftright.length * iterations); 303 if ((haptic->hwdata->stopTicks == SDL_HAPTIC_INFINITY) || (haptic->hwdata->stopTicks == 0)) { 304 haptic->hwdata->stopTicks = 1; /* fix edge cases. */ 305 } 306 } 307 SDL_UnlockMutex(haptic->hwdata->mutex); 308 return (XINPUTSETSTATE(haptic->hwdata->userid, vib) == ERROR_SUCCESS) ? 0 : -1; 309 } 310 311 int 312 SDL_XINPUT_HapticStopEffect(SDL_Haptic * haptic, struct haptic_effect *effect) 313 { 314 XINPUT_VIBRATION vibration = { 0, 0 }; 315 SDL_LockMutex(haptic->hwdata->mutex); 316 haptic->hwdata->stopTicks = 0; 317 SDL_UnlockMutex(haptic->hwdata->mutex); 318 return (XINPUTSETSTATE(haptic->hwdata->userid, &vibration) == ERROR_SUCCESS) ? 0 : -1; 319 } 320 321 void 322 SDL_XINPUT_HapticDestroyEffect(SDL_Haptic * haptic, struct haptic_effect *effect) 323 { 324 SDL_XINPUT_HapticStopEffect(haptic, effect); 325 } 326 327 int 328 SDL_XINPUT_HapticGetEffectStatus(SDL_Haptic * haptic, struct haptic_effect *effect) 329 { 330 return SDL_Unsupported(); 331 } 332 333 int 334 SDL_XINPUT_HapticSetGain(SDL_Haptic * haptic, int gain) 335 { 336 return SDL_Unsupported(); 337 } 338 339 int 340 SDL_XINPUT_HapticSetAutocenter(SDL_Haptic * haptic, int autocenter) 341 { 342 return SDL_Unsupported(); 343 } 344 345 int 346 SDL_XINPUT_HapticPause(SDL_Haptic * haptic) 347 { 348 return SDL_Unsupported(); 349 } 350 351 int 352 SDL_XINPUT_HapticUnpause(SDL_Haptic * haptic) 353 { 354 return SDL_Unsupported(); 355 } 356 357 int 358 SDL_XINPUT_HapticStopAll(SDL_Haptic * haptic) 359 { 360 XINPUT_VIBRATION vibration = { 0, 0 }; 361 SDL_LockMutex(haptic->hwdata->mutex); 362 haptic->hwdata->stopTicks = 0; 363 SDL_UnlockMutex(haptic->hwdata->mutex); 364 return (XINPUTSETSTATE(haptic->hwdata->userid, &vibration) == ERROR_SUCCESS) ? 0 : -1; 365 } 366 367 #else /* !SDL_HAPTIC_XINPUT */ 368 369 #include "../../core/windows/SDL_windows.h" 370 371 typedef struct SDL_hapticlist_item SDL_hapticlist_item; 372 373 int 374 SDL_XINPUT_HapticInit(void) 375 { 376 return 0; 377 } 378 379 int 380 SDL_XINPUT_MaybeAddDevice(const DWORD dwUserid) 381 { 382 return SDL_Unsupported(); 383 } 384 385 int 386 SDL_XINPUT_MaybeRemoveDevice(const DWORD dwUserid) 387 { 388 return SDL_Unsupported(); 389 } 390 391 int 392 SDL_XINPUT_HapticOpen(SDL_Haptic * haptic, SDL_hapticlist_item *item) 393 { 394 return SDL_Unsupported(); 395 } 396 397 int 398 SDL_XINPUT_JoystickSameHaptic(SDL_Haptic * haptic, SDL_Joystick * joystick) 399 { 400 return SDL_Unsupported(); 401 } 402 403 int 404 SDL_XINPUT_HapticOpenFromJoystick(SDL_Haptic * haptic, SDL_Joystick * joystick) 405 { 406 return SDL_Unsupported(); 407 } 408 409 void 410 SDL_XINPUT_HapticClose(SDL_Haptic * haptic) 411 { 412 } 413 414 void 415 SDL_XINPUT_HapticQuit(void) 416 { 417 } 418 419 int 420 SDL_XINPUT_HapticNewEffect(SDL_Haptic * haptic, struct haptic_effect *effect, SDL_HapticEffect * base) 421 { 422 return SDL_Unsupported(); 423 } 424 425 int 426 SDL_XINPUT_HapticUpdateEffect(SDL_Haptic * haptic, struct haptic_effect *effect, SDL_HapticEffect * data) 427 { 428 return SDL_Unsupported(); 429 } 430 431 int 432 SDL_XINPUT_HapticRunEffect(SDL_Haptic * haptic, struct haptic_effect *effect, Uint32 iterations) 433 { 434 return SDL_Unsupported(); 435 } 436 437 int 438 SDL_XINPUT_HapticStopEffect(SDL_Haptic * haptic, struct haptic_effect *effect) 439 { 440 return SDL_Unsupported(); 441 } 442 443 void 444 SDL_XINPUT_HapticDestroyEffect(SDL_Haptic * haptic, struct haptic_effect *effect) 445 { 446 } 447 448 int 449 SDL_XINPUT_HapticGetEffectStatus(SDL_Haptic * haptic, struct haptic_effect *effect) 450 { 451 return SDL_Unsupported(); 452 } 453 454 int 455 SDL_XINPUT_HapticSetGain(SDL_Haptic * haptic, int gain) 456 { 457 return SDL_Unsupported(); 458 } 459 460 int 461 SDL_XINPUT_HapticSetAutocenter(SDL_Haptic * haptic, int autocenter) 462 { 463 return SDL_Unsupported(); 464 } 465 466 int 467 SDL_XINPUT_HapticPause(SDL_Haptic * haptic) 468 { 469 return SDL_Unsupported(); 470 } 471 472 int 473 SDL_XINPUT_HapticUnpause(SDL_Haptic * haptic) 474 { 475 return SDL_Unsupported(); 476 } 477 478 int 479 SDL_XINPUT_HapticStopAll(SDL_Haptic * haptic) 480 { 481 return SDL_Unsupported(); 482 } 483 484 #endif /* SDL_HAPTIC_XINPUT */ 485 486 /* vi: set ts=4 sw=4 expandtab: */