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