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. */
31static 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. */
55static 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
64SDL_Window *window = NULL;
65SDL_Renderer *screen = NULL;
66SDL_bool retval = SDL_FALSE;
67SDL_bool done = SDL_FALSE;
68SDL_bool set_LED = SDL_FALSE;
69SDL_Texture *background_front, *background_back, *button, *axis;
70SDL_GameController *gamecontroller;
71SDL_GameController **gamecontrollers;
72int num_controllers = 0;
73
74static 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
105static 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
117static 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
172static 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
186static 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
209static SDL_Texture *
210LoadTexture(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
237static 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
248void
249loop(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 %d touchpad %d finger %d %s %.2f, %.2f, %.2f\n",
271 event.ctouchpad.which,
272 event.ctouchpad.touchpad,
273 event.ctouchpad.finger,
274 (event.type == SDL_CONTROLLERTOUCHPADDOWN ? "pressed at" :
275 (event.type == SDL_CONTROLLERTOUCHPADUP ? "released at" :
276 "moved to")),
277 event.ctouchpad.x,
278 event.ctouchpad.y,
279 event.ctouchpad.pressure);
280 break;
281
282 case SDL_CONTROLLERSENSORUPDATE:
283 SDL_Log("Controller %d sensor %s: %.2f, %.2f, %.2f\n",
284 event.csensor.which,
285 event.csensor.sensor == SDL_SENSOR_ACCEL ? "accelerometer" :
286 event.csensor.sensor == SDL_SENSOR_GYRO ? "gyro" : "unknown",
287 event.csensor.data[0],
288 event.csensor.data[1],
289 event.csensor.data[2]);
290 break;
291
292 case SDL_CONTROLLERAXISMOTION:
293 if (event.caxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.caxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
294 SetController(event.caxis.which);
295 }
296 SDL_Log("Controller %d axis %s changed to %d\n", event.caxis.which, SDL_GameControllerGetStringForAxis((SDL_GameControllerAxis)event.caxis.axis), event.caxis.value);
297 break;
298
299 case SDL_CONTROLLERBUTTONDOWN:
300 case SDL_CONTROLLERBUTTONUP:
301 if (event.type == SDL_CONTROLLERBUTTONDOWN) {
302 SetController(event.cbutton.which);
303 }
304 SDL_Log("Controller %d button %s %s\n", event.cbutton.which, SDL_GameControllerGetStringForButton((SDL_GameControllerButton)event.cbutton.button), event.cbutton.state ? "pressed" : "released");
305 break;
306
307 case SDL_KEYDOWN:
308 if (event.key.keysym.sym >= SDLK_0 && event.key.keysym.sym <= SDLK_9) {
309 if (gamecontroller) {
310 int player_index = (event.key.keysym.sym - SDLK_0);
311
312 SDL_GameControllerSetPlayerIndex(gamecontroller, player_index);
313 }
314 break;
315 }
316 if (event.key.keysym.sym != SDLK_ESCAPE) {
317 break;
318 }
319 /* Fall through to signal quit */
320 case SDL_QUIT:
321 done = SDL_TRUE;
322 break;
323 default:
324 break;
325 }
326 }
327
328 if (gamecontroller) {
329 /* Show the back of the controller if the paddles are being held */
330 for (i = SDL_CONTROLLER_BUTTON_PADDLE1; i <= SDL_CONTROLLER_BUTTON_PADDLE4; ++i) {
331 if (SDL_GameControllerGetButton(gamecontroller, (SDL_GameControllerButton)i) == SDL_PRESSED) {
332 showing_front = SDL_FALSE;
333 break;
334 }
335 }
336 }
337
338 /* blank screen, set up for drawing this frame. */
339 SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE);
340 SDL_RenderClear(screen);
341 SDL_RenderCopy(screen, showing_front ? background_front : background_back, NULL, NULL);
342
343 if (gamecontroller) {
344 /* Update visual controller state */
345 for (i = 0; i < SDL_CONTROLLER_BUTTON_TOUCHPAD; ++i) {
346 if (SDL_GameControllerGetButton(gamecontroller, (SDL_GameControllerButton)i) == SDL_PRESSED) {
347 SDL_bool on_front = (i < SDL_CONTROLLER_BUTTON_PADDLE1 || i > SDL_CONTROLLER_BUTTON_PADDLE4);
348 if (on_front == showing_front) {
349 const SDL_Rect dst = { button_positions[i].x, button_positions[i].y, 50, 50 };
350 SDL_RenderCopyEx(screen, button, NULL, &dst, 0, NULL, SDL_FLIP_NONE);
351 }
352 }
353 }
354
355 if (showing_front) {
356 for (i = 0; i < SDL_CONTROLLER_AXIS_MAX; ++i) {
357 const Sint16 deadzone = 8000; /* !!! FIXME: real deadzone */
358 const Sint16 value = SDL_GameControllerGetAxis(gamecontroller, (SDL_GameControllerAxis)(i));
359 if (value < -deadzone) {
360 const SDL_Rect dst = { axis_positions[i].x, axis_positions[i].y, 50, 50 };
361 const double angle = axis_positions[i].angle;
362 SDL_RenderCopyEx(screen, axis, NULL, &dst, angle, NULL, SDL_FLIP_NONE);
363 } else if (value > deadzone) {
364 const SDL_Rect dst = { axis_positions[i].x, axis_positions[i].y, 50, 50 };
365 const double angle = axis_positions[i].angle + 180.0;
366 SDL_RenderCopyEx(screen, axis, NULL, &dst, angle, NULL, SDL_FLIP_NONE);
367 }
368 }
369 }
370
371 /* Update LED based on left thumbstick position */
372 {
373 Sint16 x = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTX);
374 Sint16 y = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTY);
375
376 if (!set_LED) {
377 set_LED = (x < -8000 || x > 8000 || y > 8000);
378 }
379 if (set_LED) {
380 Uint8 r, g, b;
381
382 if (x < 0) {
383 r = (Uint8)(((int)(~x) * 255) / 32767);
384 b = 0;
385 } else {
386 r = 0;
387 b = (Uint8)(((int)(x) * 255) / 32767);
388 }
389 if (y > 0) {
390 g = (Uint8)(((int)(y) * 255) / 32767);
391 } else {
392 g = 0;
393 }
394
395 SDL_GameControllerSetLED(gamecontroller, r, g, b);
396 }
397 }
398
399 /* Update rumble based on trigger state */
400 {
401 Sint16 left = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
402 Sint16 right = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
403 Uint16 low_frequency_rumble = ConvertAxisToRumble(left);
404 Uint16 high_frequency_rumble = ConvertAxisToRumble(right);
405 SDL_GameControllerRumble(gamecontroller, low_frequency_rumble, high_frequency_rumble, 250);
406 }
407
408 /* Update trigger rumble based on thumbstick state */
409 {
410 Sint16 left = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTY);
411 Sint16 right = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_RIGHTY);
412 Uint16 left_rumble = ConvertAxisToRumble(~left);
413 Uint16 right_rumble = ConvertAxisToRumble(~right);
414
415 SDL_GameControllerRumbleTriggers(gamecontroller, left_rumble, right_rumble, 250);
416 }
417 }
418
419 SDL_RenderPresent(screen);
420
421#ifdef __EMSCRIPTEN__
422 if (done) {
423 emscripten_cancel_main_loop();
424 }
425#endif
426}
427
428int
429main(int argc, char *argv[])
430{
431 int i;
432 int controller_count = 0;
433 int controller_index = 0;
434 char guid[64];
435
436 SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
437 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
438 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
439 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
440
441 /* Enable standard application logging */
442 SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
443
444 /* Initialize SDL (Note: video is required to start event loop) */
445 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER ) < 0) {
446 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
447 return 1;
448 }
449
450 SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt");
451
452 /* Print information about the mappings */
453 if (argv[1] && SDL_strcmp(argv[1], "--mappings") == 0) {
454 SDL_Log("Supported mappings:\n");
455 for (i = 0; i < SDL_GameControllerNumMappings(); ++i) {
456 char *mapping = SDL_GameControllerMappingForIndex(i);
457 if (mapping) {
458 SDL_Log("\t%s\n", mapping);
459 SDL_free(mapping);
460 }
461 }
462 SDL_Log("\n");
463 }
464
465 /* Print information about the controller */
466 for (i = 0; i < SDL_NumJoysticks(); ++i) {
467 const char *name;
468 const char *description;
469
470 SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(i),
471 guid, sizeof (guid));
472
473 if (SDL_IsGameController(i)) {
474 controller_count++;
475 name = SDL_GameControllerNameForIndex(i);
476 switch (SDL_GameControllerTypeForIndex(i)) {
477 case SDL_CONTROLLER_TYPE_XBOX360:
478 description = "XBox 360 Controller";
479 break;
480 case SDL_CONTROLLER_TYPE_XBOXONE:
481 description = "XBox One Controller";
482 break;
483 case SDL_CONTROLLER_TYPE_PS3:
484 description = "PS3 Controller";
485 break;
486 case SDL_CONTROLLER_TYPE_PS4:
487 description = "PS4 Controller";
488 break;
489 case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO:
490 description = "Nintendo Switch Pro Controller";
491 break;
492 case SDL_CONTROLLER_TYPE_VIRTUAL:
493 description = "Virtual Game Controller";
494 break;
495 default:
496 description = "Game Controller";
497 break;
498 }
499 AddController(i, SDL_FALSE);
500 } else {
501 name = SDL_JoystickNameForIndex(i);
502 description = "Joystick";
503 }
504 SDL_Log("%s %d: %s (guid %s, VID 0x%.4x, PID 0x%.4x, player index = %d)\n",
505 description, i, name ? name : "Unknown", guid,
506 SDL_JoystickGetDeviceVendor(i), SDL_JoystickGetDeviceProduct(i), SDL_JoystickGetDevicePlayerIndex(i));
507 }
508 SDL_Log("There are %d game controller(s) attached (%d joystick(s))\n", controller_count, SDL_NumJoysticks());
509
510 /* Create a window to display controller state */
511 window = SDL_CreateWindow("Game Controller Test", SDL_WINDOWPOS_CENTERED,
512 SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH,
513 SCREEN_HEIGHT, 0);
514 if (window == NULL) {
515 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s\n", SDL_GetError());
516 return 2;
517 }
518
519 screen = SDL_CreateRenderer(window, -1, 0);
520 if (screen == NULL) {
521 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError());
522 SDL_DestroyWindow(window);
523 return 2;
524 }
525
526 SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE);
527 SDL_RenderClear(screen);
528 SDL_RenderPresent(screen);
529
530 /* scale for platforms that don't give you the window size you asked for. */
531 SDL_RenderSetLogicalSize(screen, SCREEN_WIDTH, SCREEN_HEIGHT);
532
533 background_front = LoadTexture(screen, "controllermap.bmp", SDL_FALSE);
534 background_back = LoadTexture(screen, "controllermap_back.bmp", SDL_FALSE);
535 button = LoadTexture(screen, "button.bmp", SDL_TRUE);
536 axis = LoadTexture(screen, "axis.bmp", SDL_TRUE);
537
538 if (!background_front || !background_back || !button || !axis) {
539 SDL_DestroyRenderer(screen);
540 SDL_DestroyWindow(window);
541 return 2;
542 }
543 SDL_SetTextureColorMod(button, 10, 255, 21);
544 SDL_SetTextureColorMod(axis, 10, 255, 21);
545
546 /* !!! FIXME: */
547 /*SDL_RenderSetLogicalSize(screen, background->w, background->h);*/
548
549 if (argv[1] && *argv[1] != '-') {
550 controller_index = SDL_atoi(argv[1]);
551 }
552 if (controller_index < num_controllers) {
553 gamecontroller = gamecontrollers[controller_index];
554 } else {
555 gamecontroller = NULL;
556 }
557 UpdateWindowTitle();
558
559 /* Loop, getting controller events! */
560#ifdef __EMSCRIPTEN__
561 emscripten_set_main_loop_arg(loop, NULL, 0, 1);
562#else
563 while (!done) {
564 loop(NULL);
565 }
566#endif
567
568 SDL_DestroyRenderer(screen);
569 SDL_DestroyWindow(window);
570 SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
571
572 return 0;
573}
574
575#else
576
577int
578main(int argc, char *argv[])
579{
580 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL compiled without Joystick support.\n");
581 return 1;
582}
583
584#endif
585
586/* vi: set ts=4 sw=4 expandtab: */
587