testgamecontroller.c (19779B)
1 /* 2 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org> 3 4 This software is provided 'as-is', without any express or implied 5 warranty. In no event will the authors be held liable for any damages 6 arising from the use of this software. 7 8 Permission is granted to anyone to use this software for any purpose, 9 including commercial applications, and to alter it and redistribute it 10 freely. 11 */ 12 13 /* Simple program to test the SDL game controller routines */ 14 15 #include <stdio.h> 16 #include <stdlib.h> 17 #include <string.h> 18 19 #include "SDL.h" 20 21 #ifdef __EMSCRIPTEN__ 22 #include <emscripten/emscripten.h> 23 #endif 24 25 #ifndef SDL_JOYSTICK_DISABLED 26 27 #define SCREEN_WIDTH 512 28 #define SCREEN_HEIGHT 320 29 30 /* This is indexed by SDL_GameControllerButton. */ 31 static const struct { int x; int y; } button_positions[] = { 32 {387, 167}, /* SDL_CONTROLLER_BUTTON_A */ 33 {431, 132}, /* SDL_CONTROLLER_BUTTON_B */ 34 {342, 132}, /* SDL_CONTROLLER_BUTTON_X */ 35 {389, 101}, /* SDL_CONTROLLER_BUTTON_Y */ 36 {174, 132}, /* SDL_CONTROLLER_BUTTON_BACK */ 37 {232, 128}, /* SDL_CONTROLLER_BUTTON_GUIDE */ 38 {289, 132}, /* SDL_CONTROLLER_BUTTON_START */ 39 {75, 154}, /* SDL_CONTROLLER_BUTTON_LEFTSTICK */ 40 {305, 230}, /* SDL_CONTROLLER_BUTTON_RIGHTSTICK */ 41 {77, 40}, /* SDL_CONTROLLER_BUTTON_LEFTSHOULDER */ 42 {396, 36}, /* SDL_CONTROLLER_BUTTON_RIGHTSHOULDER */ 43 {154, 188}, /* SDL_CONTROLLER_BUTTON_DPAD_UP */ 44 {154, 249}, /* SDL_CONTROLLER_BUTTON_DPAD_DOWN */ 45 {116, 217}, /* SDL_CONTROLLER_BUTTON_DPAD_LEFT */ 46 {186, 217}, /* SDL_CONTROLLER_BUTTON_DPAD_RIGHT */ 47 {232, 174}, /* SDL_CONTROLLER_BUTTON_MISC1 */ 48 {132, 135}, /* SDL_CONTROLLER_BUTTON_PADDLE1 */ 49 {330, 135}, /* SDL_CONTROLLER_BUTTON_PADDLE2 */ 50 {132, 175}, /* SDL_CONTROLLER_BUTTON_PADDLE3 */ 51 {330, 175}, /* SDL_CONTROLLER_BUTTON_PADDLE4 */ 52 }; 53 54 /* This is indexed by SDL_GameControllerAxis. */ 55 static const struct { int x; int y; double angle; } axis_positions[] = { 56 {74, 153, 270.0}, /* LEFTX */ 57 {74, 153, 0.0}, /* LEFTY */ 58 {306, 231, 270.0}, /* RIGHTX */ 59 {306, 231, 0.0}, /* RIGHTY */ 60 {91, -20, 0.0}, /* TRIGGERLEFT */ 61 {375, -20, 0.0}, /* TRIGGERRIGHT */ 62 }; 63 64 SDL_Window *window = NULL; 65 SDL_Renderer *screen = NULL; 66 SDL_bool retval = SDL_FALSE; 67 SDL_bool done = SDL_FALSE; 68 SDL_bool set_LED = SDL_FALSE; 69 SDL_Texture *background_front, *background_back, *button, *axis; 70 SDL_GameController *gamecontroller; 71 SDL_GameController **gamecontrollers; 72 int num_controllers = 0; 73 74 static void UpdateWindowTitle() 75 { 76 if (!window) { 77 return; 78 } 79 80 if (gamecontroller) { 81 const char *name = SDL_GameControllerName(gamecontroller); 82 const char *serial = SDL_GameControllerGetSerial(gamecontroller); 83 const char *basetitle = "Game Controller Test: "; 84 const size_t titlelen = SDL_strlen(basetitle) + SDL_strlen(name) + (serial ? 3 + SDL_strlen(serial) : 0) + 1; 85 char *title = (char *)SDL_malloc(titlelen); 86 87 retval = SDL_FALSE; 88 done = SDL_FALSE; 89 90 if (title) { 91 SDL_snprintf(title, titlelen, "%s%s", basetitle, name); 92 if (serial) { 93 SDL_strlcat(title, " (", titlelen); 94 SDL_strlcat(title, serial, titlelen); 95 SDL_strlcat(title, ")", titlelen); 96 } 97 SDL_SetWindowTitle(window, title); 98 SDL_free(title); 99 } 100 } else { 101 SDL_SetWindowTitle(window, "Waiting for controller..."); 102 } 103 } 104 105 static int FindController(SDL_JoystickID controller_id) 106 { 107 int i; 108 109 for (i = 0; i < num_controllers; ++i) { 110 if (controller_id == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamecontrollers[i]))) { 111 return i; 112 } 113 } 114 return -1; 115 } 116 117 static void AddController(int device_index, SDL_bool verbose) 118 { 119 SDL_JoystickID controller_id = SDL_JoystickGetDeviceInstanceID(device_index); 120 SDL_GameController *controller; 121 SDL_GameController **controllers; 122 123 controller_id = SDL_JoystickGetDeviceInstanceID(device_index); 124 if (controller_id < 0) { 125 SDL_Log("Couldn't get controller ID: %s\n", SDL_GetError()); 126 return; 127 } 128 129 if (FindController(controller_id) >= 0) { 130 /* We already have this controller */ 131 return; 132 } 133 134 controller = SDL_GameControllerOpen(device_index); 135 if (!controller) { 136 SDL_Log("Couldn't open controller: %s\n", SDL_GetError()); 137 return; 138 } 139 140 controllers = (SDL_GameController **)SDL_realloc(gamecontrollers, (num_controllers + 1) * sizeof(*controllers)); 141 if (!controllers) { 142 SDL_GameControllerClose(controller); 143 return; 144 } 145 146 controllers[num_controllers++] = controller; 147 gamecontrollers = controllers; 148 gamecontroller = controller; 149 150 if (verbose) { 151 const char *name = SDL_GameControllerName(gamecontroller); 152 SDL_Log("Opened game controller %s\n", name); 153 } 154 155 if (SDL_GameControllerHasSensor(gamecontroller, SDL_SENSOR_ACCEL)) { 156 if (verbose) { 157 SDL_Log("Enabling accelerometer\n"); 158 } 159 SDL_GameControllerSetSensorEnabled(gamecontroller, SDL_SENSOR_ACCEL, SDL_TRUE); 160 } 161 162 if (SDL_GameControllerHasSensor(gamecontroller, SDL_SENSOR_GYRO)) { 163 if (verbose) { 164 SDL_Log("Enabling gyro\n"); 165 } 166 SDL_GameControllerSetSensorEnabled(gamecontroller, SDL_SENSOR_GYRO, SDL_TRUE); 167 } 168 169 UpdateWindowTitle(); 170 } 171 172 static void SetController(SDL_JoystickID controller) 173 { 174 int i = FindController(controller); 175 176 if (i < 0) { 177 return; 178 } 179 180 if (gamecontroller != gamecontrollers[i]) { 181 gamecontroller = gamecontrollers[i]; 182 UpdateWindowTitle(); 183 } 184 } 185 186 static void DelController(SDL_JoystickID controller) 187 { 188 int i = FindController(controller); 189 190 if (i < 0) { 191 return; 192 } 193 194 SDL_GameControllerClose(gamecontrollers[i]); 195 196 --num_controllers; 197 if (i < num_controllers) { 198 SDL_memcpy(&gamecontrollers[i], &gamecontrollers[i+1], (num_controllers - i) * sizeof(*gamecontrollers)); 199 } 200 201 if (num_controllers > 0) { 202 gamecontroller = gamecontrollers[0]; 203 } else { 204 gamecontroller = NULL; 205 } 206 UpdateWindowTitle(); 207 } 208 209 static SDL_Texture * 210 LoadTexture(SDL_Renderer *renderer, const char *file, SDL_bool transparent) 211 { 212 SDL_Surface *temp = NULL; 213 SDL_Texture *texture = NULL; 214 215 temp = SDL_LoadBMP(file); 216 if (temp == NULL) { 217 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s", file, SDL_GetError()); 218 } else { 219 /* Set transparent pixel as the pixel at (0,0) */ 220 if (transparent) { 221 if (temp->format->BytesPerPixel == 1) { 222 SDL_SetColorKey(temp, SDL_TRUE, *(Uint8 *)temp->pixels); 223 } 224 } 225 226 texture = SDL_CreateTextureFromSurface(renderer, temp); 227 if (!texture) { 228 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create texture: %s\n", SDL_GetError()); 229 } 230 } 231 if (temp) { 232 SDL_FreeSurface(temp); 233 } 234 return texture; 235 } 236 237 static Uint16 ConvertAxisToRumble(Sint16 axis) 238 { 239 /* Only start rumbling if the axis is past the halfway point */ 240 const Sint16 half_axis = (Sint16)SDL_ceil(SDL_JOYSTICK_AXIS_MAX / 2.0f); 241 if (axis > half_axis) { 242 return (Uint16)(axis - half_axis) * 4; 243 } else { 244 return 0; 245 } 246 } 247 248 void 249 loop(void *arg) 250 { 251 SDL_Event event; 252 int i; 253 SDL_bool showing_front = SDL_TRUE; 254 255 while (SDL_PollEvent(&event)) { 256 switch (event.type) { 257 case SDL_CONTROLLERDEVICEADDED: 258 SDL_Log("Game controller device %d added.\n", (int) SDL_JoystickGetDeviceInstanceID(event.cdevice.which)); 259 AddController(event.cdevice.which, SDL_TRUE); 260 break; 261 262 case SDL_CONTROLLERDEVICEREMOVED: 263 SDL_Log("Game controller device %d removed.\n", (int) event.cdevice.which); 264 DelController(event.cdevice.which); 265 break; 266 267 case SDL_CONTROLLERTOUCHPADDOWN: 268 case SDL_CONTROLLERTOUCHPADMOTION: 269 case SDL_CONTROLLERTOUCHPADUP: 270 SDL_Log("Controller touchpad %d finger %d %s %.2f, %.2f, %.2f\n", 271 event.ctouchpad.touchpad, 272 event.ctouchpad.finger, 273 (event.type == SDL_CONTROLLERTOUCHPADDOWN ? "pressed at" : 274 (event.type == SDL_CONTROLLERTOUCHPADUP ? "released at" : 275 "moved to")), 276 event.ctouchpad.x, 277 event.ctouchpad.y, 278 event.ctouchpad.pressure); 279 break; 280 281 case SDL_CONTROLLERSENSORUPDATE: 282 SDL_Log("Controller sensor %s: %.2f, %.2f, %.2f\n", 283 event.csensor.sensor == SDL_SENSOR_ACCEL ? "accelerometer" : 284 event.csensor.sensor == SDL_SENSOR_GYRO ? "gyro" : "unknown", 285 event.csensor.data[0], 286 event.csensor.data[1], 287 event.csensor.data[2]); 288 break; 289 290 case SDL_CONTROLLERAXISMOTION: 291 if (event.caxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.caxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { 292 SetController(event.caxis.which); 293 } 294 SDL_Log("Controller axis %s changed to %d\n", SDL_GameControllerGetStringForAxis((SDL_GameControllerAxis)event.caxis.axis), event.caxis.value); 295 break; 296 297 case SDL_CONTROLLERBUTTONDOWN: 298 case SDL_CONTROLLERBUTTONUP: 299 if (event.type == SDL_CONTROLLERBUTTONDOWN) { 300 SetController(event.cbutton.which); 301 } 302 SDL_Log("Controller button %s %s\n", SDL_GameControllerGetStringForButton((SDL_GameControllerButton)event.cbutton.button), event.cbutton.state ? "pressed" : "released"); 303 break; 304 305 case SDL_KEYDOWN: 306 if (event.key.keysym.sym != SDLK_ESCAPE) { 307 break; 308 } 309 /* Fall through to signal quit */ 310 case SDL_QUIT: 311 done = SDL_TRUE; 312 break; 313 default: 314 break; 315 } 316 } 317 318 if (gamecontroller) { 319 /* Show the back of the controller if the paddles are being held */ 320 for (i = SDL_CONTROLLER_BUTTON_PADDLE1; i <= SDL_CONTROLLER_BUTTON_PADDLE4; ++i) { 321 if (SDL_GameControllerGetButton(gamecontroller, (SDL_GameControllerButton)i) == SDL_PRESSED) { 322 showing_front = SDL_FALSE; 323 break; 324 } 325 } 326 } 327 328 /* blank screen, set up for drawing this frame. */ 329 SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE); 330 SDL_RenderClear(screen); 331 SDL_RenderCopy(screen, showing_front ? background_front : background_back, NULL, NULL); 332 333 if (gamecontroller) { 334 /* Update visual controller state */ 335 for (i = 0; i < SDL_CONTROLLER_BUTTON_TOUCHPAD; ++i) { 336 if (SDL_GameControllerGetButton(gamecontroller, (SDL_GameControllerButton)i) == SDL_PRESSED) { 337 SDL_bool on_front = (i < SDL_CONTROLLER_BUTTON_PADDLE1 || i > SDL_CONTROLLER_BUTTON_PADDLE4); 338 if (on_front == showing_front) { 339 const SDL_Rect dst = { button_positions[i].x, button_positions[i].y, 50, 50 }; 340 SDL_RenderCopyEx(screen, button, NULL, &dst, 0, NULL, SDL_FLIP_NONE); 341 } 342 } 343 } 344 345 if (showing_front) { 346 for (i = 0; i < SDL_CONTROLLER_AXIS_MAX; ++i) { 347 const Sint16 deadzone = 8000; /* !!! FIXME: real deadzone */ 348 const Sint16 value = SDL_GameControllerGetAxis(gamecontroller, (SDL_GameControllerAxis)(i)); 349 if (value < -deadzone) { 350 const SDL_Rect dst = { axis_positions[i].x, axis_positions[i].y, 50, 50 }; 351 const double angle = axis_positions[i].angle; 352 SDL_RenderCopyEx(screen, axis, NULL, &dst, angle, NULL, SDL_FLIP_NONE); 353 } else if (value > deadzone) { 354 const SDL_Rect dst = { axis_positions[i].x, axis_positions[i].y, 50, 50 }; 355 const double angle = axis_positions[i].angle + 180.0; 356 SDL_RenderCopyEx(screen, axis, NULL, &dst, angle, NULL, SDL_FLIP_NONE); 357 } 358 } 359 } 360 361 /* Update LED based on left thumbstick position */ 362 { 363 Sint16 x = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTX); 364 Sint16 y = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTY); 365 366 if (!set_LED) { 367 set_LED = (x < -8000 || x > 8000 || y > 8000); 368 } 369 if (set_LED) { 370 Uint8 r, g, b; 371 372 if (x < 0) { 373 r = (Uint8)(((int)(~x) * 255) / 32767); 374 b = 0; 375 } else { 376 r = 0; 377 b = (Uint8)(((int)(x) * 255) / 32767); 378 } 379 if (y > 0) { 380 g = (Uint8)(((int)(y) * 255) / 32767); 381 } else { 382 g = 0; 383 } 384 385 SDL_GameControllerSetLED(gamecontroller, r, g, b); 386 } 387 } 388 389 /* Update rumble based on trigger state */ 390 { 391 Sint16 left = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERLEFT); 392 Sint16 right = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); 393 Uint16 low_frequency_rumble = ConvertAxisToRumble(left); 394 Uint16 high_frequency_rumble = ConvertAxisToRumble(right); 395 SDL_GameControllerRumble(gamecontroller, low_frequency_rumble, high_frequency_rumble, 250); 396 } 397 398 /* Update trigger rumble based on thumbstick state */ 399 { 400 Sint16 left = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTY); 401 Sint16 right = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_RIGHTY); 402 Uint16 left_rumble = ConvertAxisToRumble(~left); 403 Uint16 right_rumble = ConvertAxisToRumble(~right); 404 405 SDL_GameControllerRumbleTriggers(gamecontroller, left_rumble, right_rumble, 250); 406 } 407 } 408 409 SDL_RenderPresent(screen); 410 411 #ifdef __EMSCRIPTEN__ 412 if (done) { 413 emscripten_cancel_main_loop(); 414 } 415 #endif 416 } 417 418 int 419 main(int argc, char *argv[]) 420 { 421 int i; 422 int controller_count = 0; 423 int controller_index = 0; 424 char guid[64]; 425 426 SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); 427 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); 428 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); 429 430 /* Enable standard application logging */ 431 SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); 432 433 /* Initialize SDL (Note: video is required to start event loop) */ 434 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER ) < 0) { 435 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError()); 436 return 1; 437 } 438 439 SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt"); 440 441 /* Print information about the mappings */ 442 if (argv[1] && SDL_strcmp(argv[1], "--mappings") == 0) { 443 SDL_Log("Supported mappings:\n"); 444 for (i = 0; i < SDL_GameControllerNumMappings(); ++i) { 445 char *mapping = SDL_GameControllerMappingForIndex(i); 446 if (mapping) { 447 SDL_Log("\t%s\n", mapping); 448 SDL_free(mapping); 449 } 450 } 451 SDL_Log("\n"); 452 } 453 454 /* Print information about the controller */ 455 for (i = 0; i < SDL_NumJoysticks(); ++i) { 456 const char *name; 457 const char *description; 458 459 SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(i), 460 guid, sizeof (guid)); 461 462 if (SDL_IsGameController(i)) { 463 controller_count++; 464 name = SDL_GameControllerNameForIndex(i); 465 switch (SDL_GameControllerTypeForIndex(i)) { 466 case SDL_CONTROLLER_TYPE_XBOX360: 467 description = "XBox 360 Controller"; 468 break; 469 case SDL_CONTROLLER_TYPE_XBOXONE: 470 description = "XBox One Controller"; 471 break; 472 case SDL_CONTROLLER_TYPE_PS3: 473 description = "PS3 Controller"; 474 break; 475 case SDL_CONTROLLER_TYPE_PS4: 476 description = "PS4 Controller"; 477 break; 478 case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: 479 description = "Nintendo Switch Pro Controller"; 480 break; 481 case SDL_CONTROLLER_TYPE_VIRTUAL: 482 description = "Virtual Game Controller"; 483 break; 484 default: 485 description = "Game Controller"; 486 break; 487 } 488 AddController(i, SDL_FALSE); 489 } else { 490 name = SDL_JoystickNameForIndex(i); 491 description = "Joystick"; 492 } 493 SDL_Log("%s %d: %s (guid %s, VID 0x%.4x, PID 0x%.4x, player index = %d)\n", 494 description, i, name ? name : "Unknown", guid, 495 SDL_JoystickGetDeviceVendor(i), SDL_JoystickGetDeviceProduct(i), SDL_JoystickGetDevicePlayerIndex(i)); 496 } 497 SDL_Log("There are %d game controller(s) attached (%d joystick(s))\n", controller_count, SDL_NumJoysticks()); 498 499 /* Create a window to display controller state */ 500 window = SDL_CreateWindow("Game Controller Test", SDL_WINDOWPOS_CENTERED, 501 SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, 502 SCREEN_HEIGHT, 0); 503 if (window == NULL) { 504 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s\n", SDL_GetError()); 505 return 2; 506 } 507 508 screen = SDL_CreateRenderer(window, -1, 0); 509 if (screen == NULL) { 510 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError()); 511 SDL_DestroyWindow(window); 512 return 2; 513 } 514 515 SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE); 516 SDL_RenderClear(screen); 517 SDL_RenderPresent(screen); 518 519 /* scale for platforms that don't give you the window size you asked for. */ 520 SDL_RenderSetLogicalSize(screen, SCREEN_WIDTH, SCREEN_HEIGHT); 521 522 background_front = LoadTexture(screen, "controllermap.bmp", SDL_FALSE); 523 background_back = LoadTexture(screen, "controllermap_back.bmp", SDL_FALSE); 524 button = LoadTexture(screen, "button.bmp", SDL_TRUE); 525 axis = LoadTexture(screen, "axis.bmp", SDL_TRUE); 526 527 if (!background_front || !background_back || !button || !axis) { 528 SDL_DestroyRenderer(screen); 529 SDL_DestroyWindow(window); 530 return 2; 531 } 532 SDL_SetTextureColorMod(button, 10, 255, 21); 533 SDL_SetTextureColorMod(axis, 10, 255, 21); 534 535 /* !!! FIXME: */ 536 /*SDL_RenderSetLogicalSize(screen, background->w, background->h);*/ 537 538 if (argv[1] && *argv[1] != '-') { 539 controller_index = SDL_atoi(argv[1]); 540 } 541 if (controller_index < num_controllers) { 542 gamecontroller = gamecontrollers[controller_index]; 543 } else { 544 gamecontroller = NULL; 545 } 546 UpdateWindowTitle(); 547 548 /* Loop, getting controller events! */ 549 #ifdef __EMSCRIPTEN__ 550 emscripten_set_main_loop_arg(loop, NULL, 0, 1); 551 #else 552 while (!done) { 553 loop(NULL); 554 } 555 #endif 556 557 SDL_DestroyRenderer(screen); 558 SDL_DestroyWindow(window); 559 SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); 560 561 return 0; 562 } 563 564 #else 565 566 int 567 main(int argc, char *argv[]) 568 { 569 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL compiled without Joystick support.\n"); 570 return 1; 571 } 572 573 #endif 574 575 /* vi: set ts=4 sw=4 expandtab: */