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 #ifdef __IPHONEOS__
28 #define SCREEN_WIDTH    480
29 #define SCREEN_HEIGHT    320
30 #else
31 #define SCREEN_WIDTH    512
32 #define SCREEN_HEIGHT   320
33 #endif
34 
35 /* This is indexed by SDL_GameControllerButton. */
36 static const struct { int x; int y; } button_positions[] = {
37     {387, 167},  /* A */
38     {431, 132},  /* B */
39     {342, 132},  /* X */
40     {389, 101},  /* Y */
41     {174, 132},  /* BACK */
42     {233, 132},  /* GUIDE */
43     {289, 132},  /* START */
44     {75,  154},  /* LEFTSTICK */
45     {305, 230},  /* RIGHTSTICK */
46     {77,  40},   /* LEFTSHOULDER */
47     {396, 36},   /* RIGHTSHOULDER */
48     {154, 188},  /* DPAD_UP */
49     {154, 249},  /* DPAD_DOWN */
50     {116, 217},  /* DPAD_LEFT */
51     {186, 217},  /* DPAD_RIGHT */
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_Texture *background, *button, *axis;
69 SDL_GameController *gamecontroller;
70 
71 static SDL_Texture *
LoadTexture(SDL_Renderer * renderer,const char * file,SDL_bool transparent)72 LoadTexture(SDL_Renderer *renderer, const char *file, SDL_bool transparent)
73 {
74     SDL_Surface *temp = NULL;
75     SDL_Texture *texture = NULL;
76 
77     temp = SDL_LoadBMP(file);
78     if (temp == NULL) {
79         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s", file, SDL_GetError());
80     } else {
81         /* Set transparent pixel as the pixel at (0,0) */
82         if (transparent) {
83             if (temp->format->BytesPerPixel == 1) {
84                 SDL_SetColorKey(temp, SDL_TRUE, *(Uint8 *)temp->pixels);
85             }
86         }
87 
88         texture = SDL_CreateTextureFromSurface(renderer, temp);
89         if (!texture) {
90             SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create texture: %s\n", SDL_GetError());
91         }
92     }
93     if (temp) {
94         SDL_FreeSurface(temp);
95     }
96     return texture;
97 }
98 
99 static void
UpdateWindowTitle()100 UpdateWindowTitle()
101 {
102     const char *name = SDL_GameControllerName(gamecontroller);
103     const char *basetitle = "Game Controller Test: ";
104     const size_t titlelen = SDL_strlen(basetitle) + SDL_strlen(name) + 1;
105     char *title = (char *)SDL_malloc(titlelen);
106 
107     retval = SDL_FALSE;
108     done = SDL_FALSE;
109 
110     if (title) {
111         SDL_snprintf(title, titlelen, "%s%s", basetitle, name);
112         SDL_SetWindowTitle(window, title);
113         SDL_free(title);
114     }
115 }
116 
117 void
loop(void * arg)118 loop(void *arg)
119 {
120     SDL_Event event;
121     int i;
122 
123     /* blank screen, set up for drawing this frame. */
124     SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE);
125     SDL_RenderClear(screen);
126     SDL_RenderCopy(screen, background, NULL, NULL);
127 
128     while (SDL_PollEvent(&event)) {
129         switch (event.type) {
130         case SDL_CONTROLLERDEVICEADDED:
131             SDL_Log("Game controller device %d added.\n", (int) event.cdevice.which);
132             if (!gamecontroller) {
133                 gamecontroller = SDL_GameControllerOpen(event.cdevice.which);
134                 if (gamecontroller) {
135                     UpdateWindowTitle();
136                 } else {
137                     SDL_Log("Couldn't open controller: %s\n", SDL_GetError());
138                 }
139             }
140             break;
141 
142         case SDL_CONTROLLERDEVICEREMOVED:
143             SDL_Log("Game controller device %d removed.\n", (int) event.cdevice.which);
144             if (gamecontroller && event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamecontroller))) {
145                 SDL_GameControllerClose(gamecontroller);
146                 gamecontroller = SDL_GameControllerOpen(0);
147                 if (gamecontroller) {
148                     UpdateWindowTitle();
149                 }
150             }
151             break;
152 
153         case SDL_CONTROLLERAXISMOTION:
154             SDL_Log("Controller axis %s changed to %d\n", SDL_GameControllerGetStringForAxis((SDL_GameControllerAxis)event.caxis.axis), event.caxis.value);
155             break;
156         case SDL_CONTROLLERBUTTONDOWN:
157         case SDL_CONTROLLERBUTTONUP:
158             SDL_Log("Controller button %s %s\n", SDL_GameControllerGetStringForButton((SDL_GameControllerButton)event.cbutton.button), event.cbutton.state ? "pressed" : "released");
159             break;
160         case SDL_KEYDOWN:
161             if (event.key.keysym.sym != SDLK_ESCAPE) {
162                 break;
163             }
164             /* Fall through to signal quit */
165         case SDL_QUIT:
166             done = SDL_TRUE;
167             break;
168         default:
169             break;
170         }
171     }
172 
173     if (gamecontroller) {
174         /* Update visual controller state */
175         for (i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i) {
176             if (SDL_GameControllerGetButton(gamecontroller, (SDL_GameControllerButton)i) == SDL_PRESSED) {
177                 const SDL_Rect dst = { button_positions[i].x, button_positions[i].y, 50, 50 };
178                 SDL_RenderCopyEx(screen, button, NULL, &dst, 0, NULL, SDL_FLIP_NONE);
179             }
180         }
181 
182         for (i = 0; i < SDL_CONTROLLER_AXIS_MAX; ++i) {
183             const Sint16 deadzone = 8000;  /* !!! FIXME: real deadzone */
184             const Sint16 value = SDL_GameControllerGetAxis(gamecontroller, (SDL_GameControllerAxis)(i));
185             if (value < -deadzone) {
186                 const SDL_Rect dst = { axis_positions[i].x, axis_positions[i].y, 50, 50 };
187                 const double angle = axis_positions[i].angle;
188                 SDL_RenderCopyEx(screen, axis, NULL, &dst, angle, NULL, SDL_FLIP_NONE);
189             } else if (value > deadzone) {
190                 const SDL_Rect dst = { axis_positions[i].x, axis_positions[i].y, 50, 50 };
191                 const double angle = axis_positions[i].angle + 180.0;
192                 SDL_RenderCopyEx(screen, axis, NULL, &dst, angle, NULL, SDL_FLIP_NONE);
193             }
194         }
195 
196         /* Update rumble based on trigger state */
197         {
198             Uint16 low_frequency_rumble = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) * 2;
199             Uint16 high_frequency_rumble = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) * 2;
200             SDL_GameControllerRumble(gamecontroller, low_frequency_rumble, high_frequency_rumble, 250);
201         }
202     }
203 
204     SDL_RenderPresent(screen);
205 
206 #ifdef __EMSCRIPTEN__
207     if (done) {
208         emscripten_cancel_main_loop();
209     }
210 #endif
211 }
212 
213 int
main(int argc,char * argv[])214 main(int argc, char *argv[])
215 {
216     int i;
217     int nController = 0;
218     char guid[64];
219 
220     /* Enable standard application logging */
221     SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
222 
223     /* Initialize SDL (Note: video is required to start event loop) */
224     if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER ) < 0) {
225         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
226         return 1;
227     }
228 
229     SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt");
230 
231     /* Print information about the mappings */
232     if (argv[1] && SDL_strcmp(argv[1], "--mappings") == 0) {
233         SDL_Log("Supported mappings:\n");
234         for (i = 0; i < SDL_GameControllerNumMappings(); ++i) {
235             char *mapping = SDL_GameControllerMappingForIndex(i);
236             if (mapping) {
237                 SDL_Log("\t%s\n", mapping);
238                 SDL_free(mapping);
239             }
240         }
241         SDL_Log("\n");
242     }
243 
244     /* Print information about the controller */
245     for (i = 0; i < SDL_NumJoysticks(); ++i) {
246         const char *name;
247         const char *description;
248 
249         SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(i),
250                                   guid, sizeof (guid));
251 
252         if ( SDL_IsGameController(i) ) {
253             nController++;
254             name = SDL_GameControllerNameForIndex(i);
255             switch (SDL_GameControllerTypeForIndex(i)) {
256             case SDL_CONTROLLER_TYPE_XBOX360:
257                 description = "XBox 360 Controller";
258                 break;
259             case SDL_CONTROLLER_TYPE_XBOXONE:
260                 description = "XBox One Controller";
261                 break;
262             case SDL_CONTROLLER_TYPE_PS3:
263                 description = "PS3 Controller";
264                 break;
265             case SDL_CONTROLLER_TYPE_PS4:
266                 description = "PS4 Controller";
267                 break;
268             case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO:
269                 description = "Nintendo Switch Pro Controller";
270                 break;
271             case SDL_CONTROLLER_TYPE_VIRTUAL:
272                 description = "Virtual Game Controller";
273                 break;
274             default:
275                 description = "Game Controller";
276                 break;
277             }
278         } else {
279             name = SDL_JoystickNameForIndex(i);
280             description = "Joystick";
281         }
282         SDL_Log("%s %d: %s (guid %s, VID 0x%.4x, PID 0x%.4x, player index = %d)\n",
283             description, i, name ? name : "Unknown", guid,
284             SDL_JoystickGetDeviceVendor(i), SDL_JoystickGetDeviceProduct(i), SDL_JoystickGetDevicePlayerIndex(i));
285     }
286     SDL_Log("There are %d game controller(s) attached (%d joystick(s))\n", nController, SDL_NumJoysticks());
287 
288     /* Create a window to display controller state */
289     window = SDL_CreateWindow("Game Controller Test", SDL_WINDOWPOS_CENTERED,
290                               SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH,
291                               SCREEN_HEIGHT, 0);
292     if (window == NULL) {
293         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s\n", SDL_GetError());
294         return 2;
295     }
296 
297     screen = SDL_CreateRenderer(window, -1, 0);
298     if (screen == NULL) {
299         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError());
300         SDL_DestroyWindow(window);
301         return 2;
302     }
303 
304     SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE);
305     SDL_RenderClear(screen);
306     SDL_RenderPresent(screen);
307 
308     /* scale for platforms that don't give you the window size you asked for. */
309     SDL_RenderSetLogicalSize(screen, SCREEN_WIDTH, SCREEN_HEIGHT);
310 
311     background = LoadTexture(screen, "controllermap.bmp", SDL_FALSE);
312     button = LoadTexture(screen, "button.bmp", SDL_TRUE);
313     axis = LoadTexture(screen, "axis.bmp", SDL_TRUE);
314 
315     if (!background || !button || !axis) {
316         SDL_DestroyRenderer(screen);
317         SDL_DestroyWindow(window);
318         return 2;
319     }
320     SDL_SetTextureColorMod(button, 10, 255, 21);
321     SDL_SetTextureColorMod(axis, 10, 255, 21);
322 
323     /* !!! FIXME: */
324     /*SDL_RenderSetLogicalSize(screen, background->w, background->h);*/
325 
326     /* Loop, getting controller events! */
327 #ifdef __EMSCRIPTEN__
328     emscripten_set_main_loop_arg(loop, NULL, 0, 1);
329 #else
330     while (!done) {
331         loop(NULL);
332     }
333 #endif
334 
335     SDL_DestroyRenderer(screen);
336     screen = NULL;
337     background = NULL;
338     button = NULL;
339     axis = NULL;
340     SDL_DestroyWindow(window);
341     SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
342 
343     return 0;
344 }
345 
346 #else
347 
348 int
main(int argc,char * argv[])349 main(int argc, char *argv[])
350 {
351     SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL compiled without Joystick support.\n");
352     return 1;
353 }
354 
355 #endif
356 
357 /* vi: set ts=4 sw=4 expandtab: */
358