1 /*
2 Copyright (C) 1997-2021 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 static SDL_Window *window = NULL;
65 static SDL_Renderer *screen = NULL;
66 static SDL_bool retval = SDL_FALSE;
67 static SDL_bool done = SDL_FALSE;
68 static SDL_bool set_LED = SDL_FALSE;
69 static int trigger_effect = 0;
70 static SDL_Texture *background_front, *background_back, *button, *axis;
71 static SDL_GameController *gamecontroller;
72 static SDL_GameController **gamecontrollers;
73 static int num_controllers = 0;
74
UpdateWindowTitle()75 static void UpdateWindowTitle()
76 {
77 if (!window) {
78 return;
79 }
80
81 if (gamecontroller) {
82 const char *name = SDL_GameControllerName(gamecontroller);
83 const char *serial = SDL_GameControllerGetSerial(gamecontroller);
84 const char *basetitle = "Game Controller Test: ";
85 const size_t titlelen = SDL_strlen(basetitle) + SDL_strlen(name) + (serial ? 3 + SDL_strlen(serial) : 0) + 1;
86 char *title = (char *)SDL_malloc(titlelen);
87
88 retval = SDL_FALSE;
89 done = SDL_FALSE;
90
91 if (title) {
92 SDL_snprintf(title, titlelen, "%s%s", basetitle, name);
93 if (serial) {
94 SDL_strlcat(title, " (", titlelen);
95 SDL_strlcat(title, serial, titlelen);
96 SDL_strlcat(title, ")", titlelen);
97 }
98 SDL_SetWindowTitle(window, title);
99 SDL_free(title);
100 }
101 } else {
102 SDL_SetWindowTitle(window, "Waiting for controller...");
103 }
104 }
105
FindController(SDL_JoystickID controller_id)106 static int FindController(SDL_JoystickID controller_id)
107 {
108 int i;
109
110 for (i = 0; i < num_controllers; ++i) {
111 if (controller_id == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamecontrollers[i]))) {
112 return i;
113 }
114 }
115 return -1;
116 }
117
AddController(int device_index,SDL_bool verbose)118 static void AddController(int device_index, SDL_bool verbose)
119 {
120 SDL_JoystickID controller_id = SDL_JoystickGetDeviceInstanceID(device_index);
121 SDL_GameController *controller;
122 SDL_GameController **controllers;
123
124 controller_id = SDL_JoystickGetDeviceInstanceID(device_index);
125 if (controller_id < 0) {
126 SDL_Log("Couldn't get controller ID: %s\n", SDL_GetError());
127 return;
128 }
129
130 if (FindController(controller_id) >= 0) {
131 /* We already have this controller */
132 return;
133 }
134
135 controller = SDL_GameControllerOpen(device_index);
136 if (!controller) {
137 SDL_Log("Couldn't open controller: %s\n", SDL_GetError());
138 return;
139 }
140
141 controllers = (SDL_GameController **)SDL_realloc(gamecontrollers, (num_controllers + 1) * sizeof(*controllers));
142 if (!controllers) {
143 SDL_GameControllerClose(controller);
144 return;
145 }
146
147 controllers[num_controllers++] = controller;
148 gamecontrollers = controllers;
149 gamecontroller = controller;
150 trigger_effect = 0;
151
152 if (verbose) {
153 const char *name = SDL_GameControllerName(gamecontroller);
154 SDL_Log("Opened game controller %s\n", name);
155 }
156
157 if (SDL_GameControllerHasSensor(gamecontroller, SDL_SENSOR_ACCEL)) {
158 if (verbose) {
159 SDL_Log("Enabling accelerometer at %.2f Hz\n", SDL_GameControllerGetSensorDataRate(gamecontroller, SDL_SENSOR_ACCEL));
160 }
161 SDL_GameControllerSetSensorEnabled(gamecontroller, SDL_SENSOR_ACCEL, SDL_TRUE);
162 }
163
164 if (SDL_GameControllerHasSensor(gamecontroller, SDL_SENSOR_GYRO)) {
165 if (verbose) {
166 SDL_Log("Enabling gyro at %.2f Hz\n", SDL_GameControllerGetSensorDataRate(gamecontroller, SDL_SENSOR_GYRO));
167 }
168 SDL_GameControllerSetSensorEnabled(gamecontroller, SDL_SENSOR_GYRO, SDL_TRUE);
169 }
170
171 if (SDL_GameControllerHasRumble(gamecontroller)) {
172 SDL_Log("Rumble supported");
173 }
174
175 if (SDL_GameControllerHasRumbleTriggers(gamecontroller)) {
176 SDL_Log("Trigger rumble supported");
177 }
178
179 UpdateWindowTitle();
180 }
181
SetController(SDL_JoystickID controller)182 static void SetController(SDL_JoystickID controller)
183 {
184 int i = FindController(controller);
185
186 if (i < 0) {
187 return;
188 }
189
190 if (gamecontroller != gamecontrollers[i]) {
191 gamecontroller = gamecontrollers[i];
192 UpdateWindowTitle();
193 }
194 }
195
DelController(SDL_JoystickID controller)196 static void DelController(SDL_JoystickID controller)
197 {
198 int i = FindController(controller);
199
200 if (i < 0) {
201 return;
202 }
203
204 SDL_GameControllerClose(gamecontrollers[i]);
205
206 --num_controllers;
207 if (i < num_controllers) {
208 SDL_memcpy(&gamecontrollers[i], &gamecontrollers[i+1], (num_controllers - i) * sizeof(*gamecontrollers));
209 }
210
211 if (num_controllers > 0) {
212 gamecontroller = gamecontrollers[0];
213 } else {
214 gamecontroller = NULL;
215 }
216 UpdateWindowTitle();
217 }
218
219 static SDL_Texture *
LoadTexture(SDL_Renderer * renderer,const char * file,SDL_bool transparent)220 LoadTexture(SDL_Renderer *renderer, const char *file, SDL_bool transparent)
221 {
222 SDL_Surface *temp = NULL;
223 SDL_Texture *texture = NULL;
224
225 temp = SDL_LoadBMP(file);
226 if (temp == NULL) {
227 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s", file, SDL_GetError());
228 } else {
229 /* Set transparent pixel as the pixel at (0,0) */
230 if (transparent) {
231 if (temp->format->BytesPerPixel == 1) {
232 SDL_SetColorKey(temp, SDL_TRUE, *(Uint8 *)temp->pixels);
233 }
234 }
235
236 texture = SDL_CreateTextureFromSurface(renderer, temp);
237 if (!texture) {
238 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create texture: %s\n", SDL_GetError());
239 }
240 }
241 if (temp) {
242 SDL_FreeSurface(temp);
243 }
244 return texture;
245 }
246
ConvertAxisToRumble(Sint16 axisval)247 static Uint16 ConvertAxisToRumble(Sint16 axisval)
248 {
249 /* Only start rumbling if the axis is past the halfway point */
250 const Sint16 half_axis = (Sint16)SDL_ceil(SDL_JOYSTICK_AXIS_MAX / 2.0f);
251 if (axisval > half_axis) {
252 return (Uint16)(axisval - half_axis) * 4;
253 } else {
254 return 0;
255 }
256 }
257
258 /* PS5 trigger effect documentation:
259 https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes
260 */
261 typedef struct
262 {
263 Uint8 ucEnableBits1; /* 0 */
264 Uint8 ucEnableBits2; /* 1 */
265 Uint8 ucRumbleRight; /* 2 */
266 Uint8 ucRumbleLeft; /* 3 */
267 Uint8 ucHeadphoneVolume; /* 4 */
268 Uint8 ucSpeakerVolume; /* 5 */
269 Uint8 ucMicrophoneVolume; /* 6 */
270 Uint8 ucAudioEnableBits; /* 7 */
271 Uint8 ucMicLightMode; /* 8 */
272 Uint8 ucAudioMuteBits; /* 9 */
273 Uint8 rgucRightTriggerEffect[11]; /* 10 */
274 Uint8 rgucLeftTriggerEffect[11]; /* 21 */
275 Uint8 rgucUnknown1[6]; /* 32 */
276 Uint8 ucLedFlags; /* 38 */
277 Uint8 rgucUnknown2[2]; /* 39 */
278 Uint8 ucLedAnim; /* 41 */
279 Uint8 ucLedBrightness; /* 42 */
280 Uint8 ucPadLights; /* 43 */
281 Uint8 ucLedRed; /* 44 */
282 Uint8 ucLedGreen; /* 45 */
283 Uint8 ucLedBlue; /* 46 */
284 } DS5EffectsState_t;
285
CyclePS5TriggerEffect()286 static void CyclePS5TriggerEffect()
287 {
288 DS5EffectsState_t state;
289
290 Uint8 effects[3][11] =
291 {
292 /* Clear trigger effect */
293 { 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
294 /* Constant resistance across entire trigger pull */
295 { 0x01, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0 },
296 /* Resistance and vibration when trigger is pulled */
297 { 0x06, 15, 63, 128, 0, 0, 0, 0, 0, 0, 0 },
298 };
299
300 trigger_effect = (trigger_effect + 1) % SDL_arraysize(effects);
301
302 SDL_zero(state);
303 state.ucEnableBits1 |= (0x04 | 0x08); /* Modify right and left trigger effect respectively */
304 SDL_memcpy(state.rgucRightTriggerEffect, effects[trigger_effect], sizeof(effects[trigger_effect]));
305 SDL_memcpy(state.rgucLeftTriggerEffect, effects[trigger_effect], sizeof(effects[trigger_effect]));
306 SDL_GameControllerSendEffect(gamecontroller, &state, sizeof(state));
307 }
308
309 void
loop(void * arg)310 loop(void *arg)
311 {
312 SDL_Event event;
313 int i;
314 SDL_bool showing_front = SDL_TRUE;
315
316 /* Update to get the current event state */
317 SDL_PumpEvents();
318
319 /* Process all currently pending events */
320 while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) == 1) {
321 switch (event.type) {
322 case SDL_CONTROLLERDEVICEADDED:
323 SDL_Log("Game controller device %d added.\n", (int) SDL_JoystickGetDeviceInstanceID(event.cdevice.which));
324 AddController(event.cdevice.which, SDL_TRUE);
325 break;
326
327 case SDL_CONTROLLERDEVICEREMOVED:
328 SDL_Log("Game controller device %d removed.\n", (int) event.cdevice.which);
329 DelController(event.cdevice.which);
330 break;
331
332 case SDL_CONTROLLERTOUCHPADDOWN:
333 case SDL_CONTROLLERTOUCHPADMOTION:
334 case SDL_CONTROLLERTOUCHPADUP:
335 SDL_Log("Controller %d touchpad %d finger %d %s %.2f, %.2f, %.2f\n",
336 event.ctouchpad.which,
337 event.ctouchpad.touchpad,
338 event.ctouchpad.finger,
339 (event.type == SDL_CONTROLLERTOUCHPADDOWN ? "pressed at" :
340 (event.type == SDL_CONTROLLERTOUCHPADUP ? "released at" :
341 "moved to")),
342 event.ctouchpad.x,
343 event.ctouchpad.y,
344 event.ctouchpad.pressure);
345 break;
346
347 #define VERBOSE_SENSORS
348 #ifdef VERBOSE_SENSORS
349 case SDL_CONTROLLERSENSORUPDATE:
350 SDL_Log("Controller %d sensor %s: %.2f, %.2f, %.2f\n",
351 event.csensor.which,
352 event.csensor.sensor == SDL_SENSOR_ACCEL ? "accelerometer" :
353 event.csensor.sensor == SDL_SENSOR_GYRO ? "gyro" : "unknown",
354 event.csensor.data[0],
355 event.csensor.data[1],
356 event.csensor.data[2]);
357 break;
358 #endif /* VERBOSE_SENSORS */
359
360 #define VERBOSE_AXES
361 #ifdef VERBOSE_AXES
362 case SDL_CONTROLLERAXISMOTION:
363 if (event.caxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.caxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
364 SetController(event.caxis.which);
365 }
366 SDL_Log("Controller %d axis %s changed to %d\n", event.caxis.which, SDL_GameControllerGetStringForAxis((SDL_GameControllerAxis)event.caxis.axis), event.caxis.value);
367 break;
368 #endif /* VERBOSE_AXES */
369
370 case SDL_CONTROLLERBUTTONDOWN:
371 case SDL_CONTROLLERBUTTONUP:
372 if (event.type == SDL_CONTROLLERBUTTONDOWN) {
373 SetController(event.cbutton.which);
374 }
375 SDL_Log("Controller %d button %s %s\n", event.cbutton.which, SDL_GameControllerGetStringForButton((SDL_GameControllerButton)event.cbutton.button), event.cbutton.state ? "pressed" : "released");
376
377 /* Cycle PS5 trigger effects when the microphone button is pressed */
378 if (event.type == SDL_CONTROLLERBUTTONDOWN &&
379 event.cbutton.button == SDL_CONTROLLER_BUTTON_MISC1 &&
380 SDL_GameControllerGetType(gamecontroller) == SDL_CONTROLLER_TYPE_PS5) {
381 CyclePS5TriggerEffect();
382 }
383 break;
384
385 case SDL_KEYDOWN:
386 if (event.key.keysym.sym >= SDLK_0 && event.key.keysym.sym <= SDLK_9) {
387 if (gamecontroller) {
388 int player_index = (event.key.keysym.sym - SDLK_0);
389
390 SDL_GameControllerSetPlayerIndex(gamecontroller, player_index);
391 }
392 break;
393 }
394 if (event.key.keysym.sym != SDLK_ESCAPE) {
395 break;
396 }
397 SDL_FALLTHROUGH;
398 case SDL_QUIT:
399 done = SDL_TRUE;
400 break;
401 default:
402 break;
403 }
404 }
405
406 if (gamecontroller) {
407 /* Show the back of the controller if the paddles are being held */
408 for (i = SDL_CONTROLLER_BUTTON_PADDLE1; i <= SDL_CONTROLLER_BUTTON_PADDLE4; ++i) {
409 if (SDL_GameControllerGetButton(gamecontroller, (SDL_GameControllerButton)i) == SDL_PRESSED) {
410 showing_front = SDL_FALSE;
411 break;
412 }
413 }
414 }
415
416 /* blank screen, set up for drawing this frame. */
417 SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE);
418 SDL_RenderClear(screen);
419 SDL_RenderCopy(screen, showing_front ? background_front : background_back, NULL, NULL);
420
421 if (gamecontroller) {
422 /* Update visual controller state */
423 for (i = 0; i < SDL_CONTROLLER_BUTTON_TOUCHPAD; ++i) {
424 if (SDL_GameControllerGetButton(gamecontroller, (SDL_GameControllerButton)i) == SDL_PRESSED) {
425 SDL_bool on_front = (i < SDL_CONTROLLER_BUTTON_PADDLE1 || i > SDL_CONTROLLER_BUTTON_PADDLE4);
426 if (on_front == showing_front) {
427 SDL_Rect dst;
428 dst.x = button_positions[i].x;
429 dst.y = button_positions[i].y;
430 dst.w = 50;
431 dst.h = 50;
432 SDL_RenderCopyEx(screen, button, NULL, &dst, 0, NULL, SDL_FLIP_NONE);
433 }
434 }
435 }
436
437 if (showing_front) {
438 for (i = 0; i < SDL_CONTROLLER_AXIS_MAX; ++i) {
439 const Sint16 deadzone = 8000; /* !!! FIXME: real deadzone */
440 const Sint16 value = SDL_GameControllerGetAxis(gamecontroller, (SDL_GameControllerAxis)(i));
441 if (value < -deadzone) {
442 const double angle = axis_positions[i].angle;
443 SDL_Rect dst;
444 dst.x = axis_positions[i].x;
445 dst.y = axis_positions[i].y;
446 dst.w = 50;
447 dst.h = 50;
448 SDL_RenderCopyEx(screen, axis, NULL, &dst, angle, NULL, SDL_FLIP_NONE);
449 } else if (value > deadzone) {
450 const double angle = axis_positions[i].angle + 180.0;
451 SDL_Rect dst;
452 dst.x = axis_positions[i].x;
453 dst.y = axis_positions[i].y;
454 dst.w = 50;
455 dst.h = 50;
456 SDL_RenderCopyEx(screen, axis, NULL, &dst, angle, NULL, SDL_FLIP_NONE);
457 }
458 }
459 }
460
461 /* Update LED based on left thumbstick position */
462 {
463 Sint16 x = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTX);
464 Sint16 y = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTY);
465
466 if (!set_LED) {
467 set_LED = (x < -8000 || x > 8000 || y > 8000);
468 }
469 if (set_LED) {
470 Uint8 r, g, b;
471
472 if (x < 0) {
473 r = (Uint8)(((int)(~x) * 255) / 32767);
474 b = 0;
475 } else {
476 r = 0;
477 b = (Uint8)(((int)(x) * 255) / 32767);
478 }
479 if (y > 0) {
480 g = (Uint8)(((int)(y) * 255) / 32767);
481 } else {
482 g = 0;
483 }
484
485 SDL_GameControllerSetLED(gamecontroller, r, g, b);
486 }
487 }
488
489 if (trigger_effect == 0) {
490 /* Update rumble based on trigger state */
491 {
492 Sint16 left = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
493 Sint16 right = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
494 Uint16 low_frequency_rumble = ConvertAxisToRumble(left);
495 Uint16 high_frequency_rumble = ConvertAxisToRumble(right);
496 SDL_GameControllerRumble(gamecontroller, low_frequency_rumble, high_frequency_rumble, 250);
497 }
498
499 /* Update trigger rumble based on thumbstick state */
500 {
501 Sint16 left = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTY);
502 Sint16 right = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_RIGHTY);
503 Uint16 left_rumble = ConvertAxisToRumble(~left);
504 Uint16 right_rumble = ConvertAxisToRumble(~right);
505
506 SDL_GameControllerRumbleTriggers(gamecontroller, left_rumble, right_rumble, 250);
507 }
508 }
509 }
510
511 SDL_RenderPresent(screen);
512
513 #ifdef __EMSCRIPTEN__
514 if (done) {
515 emscripten_cancel_main_loop();
516 }
517 #endif
518 }
519
520 int
main(int argc,char * argv[])521 main(int argc, char *argv[])
522 {
523 int i;
524 int controller_count = 0;
525 int controller_index = 0;
526 char guid[64];
527
528 SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
529 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
530 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
531 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
532 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
533 SDL_SetHint(SDL_HINT_LINUX_JOYSTICK_DEADZONES, "1");
534
535 /* Enable standard application logging */
536 SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
537
538 /* Initialize SDL (Note: video is required to start event loop) */
539 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER ) < 0) {
540 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
541 return 1;
542 }
543
544 SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt");
545
546 /* Print information about the mappings */
547 if (argv[1] && SDL_strcmp(argv[1], "--mappings") == 0) {
548 SDL_Log("Supported mappings:\n");
549 for (i = 0; i < SDL_GameControllerNumMappings(); ++i) {
550 char *mapping = SDL_GameControllerMappingForIndex(i);
551 if (mapping) {
552 SDL_Log("\t%s\n", mapping);
553 SDL_free(mapping);
554 }
555 }
556 SDL_Log("\n");
557 }
558
559 /* Print information about the controller */
560 for (i = 0; i < SDL_NumJoysticks(); ++i) {
561 const char *name;
562 const char *description;
563
564 SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(i),
565 guid, sizeof (guid));
566
567 if (SDL_IsGameController(i)) {
568 controller_count++;
569 name = SDL_GameControllerNameForIndex(i);
570 switch (SDL_GameControllerTypeForIndex(i)) {
571 case SDL_CONTROLLER_TYPE_AMAZON_LUNA:
572 description = "Amazon Luna Controller";
573 break;
574 case SDL_CONTROLLER_TYPE_GOOGLE_STADIA:
575 description = "Google Stadia Controller";
576 break;
577 case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO:
578 description = "Nintendo Switch Pro Controller";
579 break;
580 case SDL_CONTROLLER_TYPE_PS3:
581 description = "PS3 Controller";
582 break;
583 case SDL_CONTROLLER_TYPE_PS4:
584 description = "PS4 Controller";
585 break;
586 case SDL_CONTROLLER_TYPE_PS5:
587 description = "PS5 Controller";
588 break;
589 case SDL_CONTROLLER_TYPE_XBOX360:
590 description = "XBox 360 Controller";
591 break;
592 case SDL_CONTROLLER_TYPE_XBOXONE:
593 description = "XBox One Controller";
594 break;
595 case SDL_CONTROLLER_TYPE_VIRTUAL:
596 description = "Virtual Game Controller";
597 break;
598 default:
599 description = "Game Controller";
600 break;
601 }
602 AddController(i, SDL_FALSE);
603 } else {
604 name = SDL_JoystickNameForIndex(i);
605 description = "Joystick";
606 }
607 SDL_Log("%s %d: %s (guid %s, VID 0x%.4x, PID 0x%.4x, player index = %d)\n",
608 description, i, name ? name : "Unknown", guid,
609 SDL_JoystickGetDeviceVendor(i), SDL_JoystickGetDeviceProduct(i), SDL_JoystickGetDevicePlayerIndex(i));
610 }
611 SDL_Log("There are %d game controller(s) attached (%d joystick(s))\n", controller_count, SDL_NumJoysticks());
612
613 /* Create a window to display controller state */
614 window = SDL_CreateWindow("Game Controller Test", SDL_WINDOWPOS_CENTERED,
615 SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH,
616 SCREEN_HEIGHT, 0);
617 if (window == NULL) {
618 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s\n", SDL_GetError());
619 return 2;
620 }
621
622 screen = SDL_CreateRenderer(window, -1, 0);
623 if (screen == NULL) {
624 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError());
625 SDL_DestroyWindow(window);
626 return 2;
627 }
628
629 SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE);
630 SDL_RenderClear(screen);
631 SDL_RenderPresent(screen);
632
633 /* scale for platforms that don't give you the window size you asked for. */
634 SDL_RenderSetLogicalSize(screen, SCREEN_WIDTH, SCREEN_HEIGHT);
635
636 background_front = LoadTexture(screen, "controllermap.bmp", SDL_FALSE);
637 background_back = LoadTexture(screen, "controllermap_back.bmp", SDL_FALSE);
638 button = LoadTexture(screen, "button.bmp", SDL_TRUE);
639 axis = LoadTexture(screen, "axis.bmp", SDL_TRUE);
640
641 if (!background_front || !background_back || !button || !axis) {
642 SDL_DestroyRenderer(screen);
643 SDL_DestroyWindow(window);
644 return 2;
645 }
646 SDL_SetTextureColorMod(button, 10, 255, 21);
647 SDL_SetTextureColorMod(axis, 10, 255, 21);
648
649 /* !!! FIXME: */
650 /*SDL_RenderSetLogicalSize(screen, background->w, background->h);*/
651
652 if (argv[1] && *argv[1] != '-') {
653 controller_index = SDL_atoi(argv[1]);
654 }
655 if (controller_index < num_controllers) {
656 gamecontroller = gamecontrollers[controller_index];
657 } else {
658 gamecontroller = NULL;
659 }
660 UpdateWindowTitle();
661
662 /* Loop, getting controller events! */
663 #ifdef __EMSCRIPTEN__
664 emscripten_set_main_loop_arg(loop, NULL, 0, 1);
665 #else
666 while (!done) {
667 loop(NULL);
668 }
669 #endif
670
671 /* Reset trigger state */
672 if (trigger_effect != 0) {
673 trigger_effect = -1;
674 CyclePS5TriggerEffect();
675 }
676
677 SDL_DestroyRenderer(screen);
678 SDL_DestroyWindow(window);
679 SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
680
681 return 0;
682 }
683
684 #else
685
686 int
main(int argc,char * argv[])687 main(int argc, char *argv[])
688 {
689 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL compiled without Joystick support.\n");
690 return 1;
691 }
692
693 #endif
694
695 /* vi: set ts=4 sw=4 expandtab: */
696