Joystick support
[mercenary-reloaded.git] / src / libsdl / sdl.c
1 /* SDL handling
2  *
3  * (C) 2018 by Andreas Eversberg <jolly@eversberg.eu>
4  * All Rights Reserved
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <stdio.h>
21 #include <stdint.h>
22 #include <errno.h>
23 #include "print.h"
24 #include "../../include/keycodes.h"
25 #include "sdl.h"
26
27 #include <SDL2/SDL.h>
28 #define GL3_PROTOTYPES 1
29 #include <GL/glew.h>
30
31 static int sdl_initialized = 0;
32 static int audio_initialized = 0;
33 static const char *device_string = NULL;
34 static SDL_AudioDeviceID audio_devid = 0;
35 static SDL_Window *gl_window = NULL;
36 static SDL_GLContext gl_context = NULL;
37 static SDL_Joystick *sdl_joystick = NULL;
38 static void (*keyboard_sdl)(int down, enum keycode keycode) = NULL;
39 static void (*joystick_sdl)(double x, double y, int fire) = NULL;
40 static void (*audio_sdl)(float *data, int len) = NULL;
41 static void (*resize_window_sdl)(int width, int height) = NULL;
42 static int joystick_y_axis_sdl = 1, joystick_x_axis_sdl = 0, joystick_fire_button_sdl = -1;
43
44 static void audio_cb(void __attribute__((unused)) *userdata, Uint8 *stream, int len)
45 {
46         float audio_data[len / sizeof(float)];
47
48         SDL_memset(stream, 0, len);
49         audio_sdl(audio_data, len / sizeof(float) / 2);
50         SDL_MixAudioFormat(stream, (Uint8 *)audio_data, AUDIO_F32, len, SDL_MIX_MAXVOLUME / 2.0);
51 }
52
53 void sdl_list_joysticks(void)
54 {
55         int num, i, rc;
56
57         rc = SDL_Init(SDL_INIT_JOYSTICK);
58         if (rc < 0) {
59                 print_error("Failed to init SDL\n");
60                 return;
61         }
62
63         num = SDL_NumJoysticks();
64         for (i = 0; i < num; i++)
65                 printf("#%d: %s\n", i, SDL_JoystickNameForIndex(i));
66
67         SDL_Quit();
68 }
69
70 int init_sdl(const char *progname, int width, int height, int sound_samplerate, int sound_chunk, void (*keyboard)(int down, enum keycode keycode), int joysticknum, int joystick_x_axis, int joystick_y_axis, int joystick_fire_button, void (*joystick)(double x, double y, int fire), void (*audio)(float *data, int len), void (*resize_window)(int width, int height), int multisampling, int vbl_sync, int rift)
71 {
72         int rc = -EINVAL;
73
74         keyboard_sdl = keyboard;
75         joystick_sdl = joystick;
76         audio_sdl = audio;
77         resize_window_sdl = resize_window;
78         if (joystick_x_axis >= 0)
79                 joystick_x_axis_sdl = joystick_x_axis;
80         if (joystick_y_axis >= 0)
81                 joystick_y_axis_sdl = joystick_y_axis;
82         if (joystick_fire_button >= 0)
83                 joystick_fire_button_sdl = joystick_fire_button;
84
85         /* init SDL library */
86         rc = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK);
87         if (rc < 0) {
88                 print_error("Failed to init SDL\n");
89                 goto error;
90         }
91         sdl_initialized = 1;
92
93         SDL_JoystickEventState(SDL_ENABLE);
94         sdl_joystick = SDL_JoystickOpen((joysticknum < 0) ? 0 : joysticknum);
95         if (!sdl_joystick && joysticknum >= 0) {
96                 rc = -EIO;
97                 print_error("Failed to open joystick #%d, see help!\n", joysticknum);
98                 goto error;
99         }
100
101 retry_without_multisampling:
102         if (multisampling > 1) {
103                 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
104                 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
105                 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
106                 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
107                 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
108                 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
109                 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, multisampling);
110         }
111
112         /* open window */
113         gl_window = SDL_CreateWindow(progname, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
114         if (!gl_window) {
115                 if (multisampling) {
116                         print_info("Failed to open SDL window: %s (retrying without multisampling)\n", SDL_GetError());
117                         SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
118                         SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
119                         multisampling = 0;
120                         goto retry_without_multisampling;
121                 }
122                 print_error("Failed to open SDL window: %s\n", SDL_GetError());
123                 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
124                 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, multisampling);
125                 rc = EIO;
126                 goto error;
127         }
128
129         /* create GL context */
130         gl_context = SDL_GL_CreateContext(gl_window);
131         if (!gl_context) {
132                 print_error("Failed to create SDL's OpenGL context: %s\n", SDL_GetError());
133                 rc = EIO;
134                 goto error;
135         }
136
137         rc = SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
138         if (rc < 0) {
139                 print_error("Failed to set SDL's OpenGL context profile\n");
140                 goto error;
141         }
142
143         rc = SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
144         if (rc < 0) {
145                 print_error("Failed to set SDL's OpenGL major version\n");
146                 goto error;
147         }
148         rc = SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
149         if (rc < 0) {
150                 print_error("Failed to set SDL's OpenGL minor version\n");
151                 goto error;
152         }
153
154         rc = SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
155         if (rc < 0) {
156                 print_error("Failed to set SDL's OpenGL doublebuffer\n");
157                 goto error;
158         }
159
160         rc = SDL_GL_SetSwapInterval((vbl_sync) ? 1 : 0);
161         if (rc < 0) {
162                 print_error("Failed to set SDL's OpenGL swap interval to VBLANK\n");
163                 // continue anyway
164         }
165
166 // seems like a hack to me. do we need this?
167 #ifndef __APPLE__
168 #if !defined(_WIN32)
169         glewExperimental = GL_TRUE;
170         if (glewInit() != GLEW_OK) {
171                 print_error("Failed to init GLEW\n");
172                 goto error;
173         }
174 #endif
175 #endif
176
177         /* just in case */
178         glDisable(GL_DEPTH_TEST);
179         glDisable(GL_CULL_FACE);
180         if (multisampling > 1)
181                 glEnable(GL_MULTISAMPLE);
182
183         if (rift) {
184                 int i, count;
185                 count = SDL_GetNumAudioDevices(0);
186                 const char *string;
187                 for(i = 0; i < count; i++) {
188                         string = SDL_GetAudioDeviceName(i, 0);
189                         if (strstr(string, "Rift"))
190                                 device_string = string;
191                 }
192                 if (!device_string)
193                         print_error("Oculus Rift headset not found, falling back to default audio device.\n");
194         }
195
196         /* open audio */
197         SDL_AudioSpec want, have;
198         SDL_memset(&want, 0, sizeof(want)); /* or SDL_zero(want) */
199         want.freq = sound_samplerate;
200         want.format = AUDIO_F32; /* we always use float in this project */
201         want.channels = 2;
202         want.samples = sound_chunk; /* must be a power of two */
203         want.callback = audio_cb;
204         audio_devid = SDL_OpenAudioDevice(device_string, 0, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
205         if (audio_devid <= 0) {
206                 print_error("Failed to open audio! (No speaker connected?)\n");
207                 rc = -EIO;
208                 goto error;
209         } else if (have.format != want.format) {
210                 print_error("Failed to open audio with desired audio format (want %d, got %d)\n", want.format, have.format);
211                 rc = -EIO;
212                 goto error;
213         } else if (have.freq != want.freq) {
214                 print_error("Failed to open audio with desired sample rate (want %d, got %d)\n", want.freq, have.freq);
215                 rc = -EIO;
216                 goto error;
217         } else if (have.channels != want.channels) {
218                 print_error("Failed to open audio with desired number of channels (want %d, got %d)\n", want.channels, have.channels);
219                 rc = -EIO;
220                 goto error;
221         } else {
222                 SDL_PauseAudioDevice(audio_devid, 0);
223                 audio_initialized = 1;
224         }
225
226         return 0;
227
228 error:
229         exit_sdl();
230         return rc;
231 }
232
233 static enum keycode sdl2keycode(SDL_Keycode sym)
234 {
235         switch (sym) {
236         case SDLK_LCTRL: return KEYCODE_LCTRL;
237         case SDLK_RCTRL: return KEYCODE_RCTRL;
238         case SDLK_LSHIFT: return KEYCODE_LSHIFT;
239         case SDLK_RSHIFT: return KEYCODE_RSHIFT;
240         case SDLK_LALT: return KEYCODE_LALT;
241         case SDLK_RALT: return KEYCODE_RALT;
242         case SDLK_PAUSE: return KEYCODE_PAUSE;
243         case SDLK_LEFT: return KEYCODE_LEFT;
244         case SDLK_RIGHT: return KEYCODE_RIGHT;
245         case SDLK_UP: return KEYCODE_UP;
246         case SDLK_DOWN: return KEYCODE_DOWN;
247         case SDLK_END: return KEYCODE_END;
248         case SDLK_a: return KEYCODE_a;
249         case SDLK_b: return KEYCODE_b;
250         case SDLK_c: return KEYCODE_c;
251         case SDLK_d: return KEYCODE_d;
252         case SDLK_e: return KEYCODE_e;
253         case SDLK_f: return KEYCODE_f;
254         case SDLK_g: return KEYCODE_g;
255         case SDLK_h: return KEYCODE_h;
256         case SDLK_i: return KEYCODE_i;
257         case SDLK_j: return KEYCODE_j;
258         case SDLK_k: return KEYCODE_k;
259         case SDLK_l: return KEYCODE_l;
260         case SDLK_m: return KEYCODE_m;
261         case SDLK_n: return KEYCODE_n;
262         case SDLK_o: return KEYCODE_o;
263         case SDLK_p: return KEYCODE_p;
264         case SDLK_q: return KEYCODE_q;
265         case SDLK_r: return KEYCODE_r;
266         case SDLK_s: return KEYCODE_s;
267         case SDLK_t: return KEYCODE_t;
268         case SDLK_u: return KEYCODE_u;
269         case SDLK_v: return KEYCODE_v;
270         case SDLK_w: return KEYCODE_w;
271         case SDLK_x: return KEYCODE_x;
272         case SDLK_y: return KEYCODE_y;
273         case SDLK_z: return KEYCODE_z;
274         case SDLK_0: return KEYCODE_0;
275         case SDLK_1: return KEYCODE_1;
276         case SDLK_2: return KEYCODE_2;
277         case SDLK_3: return KEYCODE_3;
278         case SDLK_4: return KEYCODE_4;
279         case SDLK_5: return KEYCODE_5;
280         case SDLK_6: return KEYCODE_6;
281         case SDLK_7: return KEYCODE_7;
282         case SDLK_8: return KEYCODE_8;
283         case SDLK_9: return KEYCODE_9;
284         case SDLK_KP_0: return KEYCODE_KP_0;
285         case SDLK_KP_1: return KEYCODE_KP_1;
286         case SDLK_KP_2: return KEYCODE_KP_2;
287         case SDLK_KP_3: return KEYCODE_KP_3;
288         case SDLK_KP_4: return KEYCODE_KP_4;
289         case SDLK_KP_5: return KEYCODE_KP_5;
290         case SDLK_KP_6: return KEYCODE_KP_6;
291         case SDLK_KP_7: return KEYCODE_KP_7;
292         case SDLK_KP_8: return KEYCODE_KP_8;
293         case SDLK_KP_9: return KEYCODE_KP_9;
294         case SDLK_KP_DIVIDE: return KEYCODE_KP_DIVIDE;
295         case SDLK_KP_MULTIPLY: return KEYCODE_KP_MULTIPLY;
296         case SDLK_KP_MINUS: return KEYCODE_KP_MINUS;
297         case SDLK_KP_PLUS: return KEYCODE_KP_PLUS;
298         case SDLK_KP_PERIOD: return KEYCODE_KP_PERIOD;
299         case SDLK_KP_ENTER: return KEYCODE_KP_ENTER;
300         case SDLK_F1: return KEYCODE_F1;
301         case SDLK_F2: return KEYCODE_F2;
302         case SDLK_F3: return KEYCODE_F3;
303         case SDLK_F4: return KEYCODE_F4;
304         case SDLK_F5: return KEYCODE_F5;
305         case SDLK_F6: return KEYCODE_F6;
306         case SDLK_F7: return KEYCODE_F7;
307         case SDLK_F8: return KEYCODE_F8;
308         case SDLK_F9: return KEYCODE_F9;
309         case SDLK_F10: return KEYCODE_F10;
310         case SDLK_SPACE: return KEYCODE_SPACE;
311         case SDLK_BACKSPACE: return KEYCODE_BACKSPACE;
312         case SDLK_TAB: return KEYCODE_TAB;
313         case SDLK_RETURN: return KEYCODE_RETURN;
314         case SDLK_ESCAPE: return KEYCODE_ESCAPE;
315         case SDLK_DELETE: return KEYCODE_DELETE;
316         case SDLK_INSERT: return KEYCODE_INSERT;
317         case SDLK_COMMA: return KEYCODE_COMMA;
318         case SDLK_PERIOD: return KEYCODE_PERIOD;
319         default: return KEYCODE_UNDEFINED;
320         }
321 }
322
323 static double joystick_x = 0, joystick_y = 0;
324 static int joystick_fire = 0;
325
326 static void joystick_event(int axis, int value)
327 {
328         double v = (double)value  / 32767.0;
329
330         switch (axis) {
331         case 0: joystick_y = v; break;
332         case 1: joystick_x = v; break;
333         case 2: joystick_fire = value; break;
334         }
335
336         joystick_sdl(joystick_x, joystick_y, joystick_fire);
337 }
338
339 static int key_ctrl = 0, key_f = 0, fullscreen = 0;
340
341 int event_sdl(void)
342 {
343         int quit = 0;
344         SDL_Event event;
345
346         while (SDL_PollEvent(&event)) {
347                 switch (event.type) {
348                 case SDL_QUIT:
349                         quit = 1;
350                         break;
351                 case SDL_WINDOWEVENT:
352                         if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
353                                 resize_window_sdl(event.window.data1, event.window.data2);
354                         break;
355                 case SDL_KEYDOWN:
356                         switch (event.key.keysym.sym) {
357                         case SDLK_f:
358                                 if (key_ctrl && !key_f) {
359                                         if (fullscreen) {
360                                                 fullscreen = 0;
361                                                 SDL_SetWindowFullscreen(gl_window, 0);
362                                                 SDL_ShowCursor(SDL_ENABLE);
363                                         } else {
364                                                 SDL_SetWindowFullscreen(gl_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
365                                                 SDL_ShowCursor(SDL_DISABLE);
366                                                 fullscreen = 1;
367                                         }
368                                 }
369                                 key_f = 1;
370                                 break;
371                         case SDLK_LCTRL:
372                         case SDLK_RCTRL:
373                                 key_ctrl = 1;
374                                 break;
375                         }
376                         keyboard_sdl(1, sdl2keycode(event.key.keysym.sym));
377                         break;
378                 case SDL_KEYUP:
379                         switch (event.key.keysym.sym) {
380                         case SDLK_LCTRL:
381                         case SDLK_RCTRL:
382                                 key_ctrl = 0;
383                                 break;
384                         case SDLK_f:
385                                 key_f = 0;
386                                 break;
387                         }
388                         keyboard_sdl(0, sdl2keycode(event.key.keysym.sym));
389                         break;
390                 case SDL_JOYAXISMOTION:
391                         if (event.jaxis.axis == joystick_y_axis_sdl)
392                                 joystick_event(0, event.jaxis.value);
393                         if (event.jaxis.axis == joystick_x_axis_sdl)
394                                 joystick_event(1, event.jaxis.value);
395                         break;
396                 case SDL_JOYBUTTONDOWN:
397                         if (joystick_fire_button_sdl < 0 || event.jbutton.button == joystick_fire_button_sdl)
398                                 joystick_event(2, 1);
399                         break;
400                 case SDL_JOYBUTTONUP:
401                         if (joystick_fire_button_sdl < 0 || event.jbutton.button == joystick_fire_button_sdl)
402                                 joystick_event(2, 0);
403                         break;
404                 }
405         }
406
407         return quit;
408 }
409
410 void swap_sdl(void)
411 {
412         SDL_GL_SwapWindow(gl_window);
413 }
414
415 void exit_sdl(void)
416 {
417         /* close joystick */
418         if (sdl_joystick) {
419                 SDL_JoystickClose(sdl_joystick);
420                 sdl_joystick = NULL;
421         }
422         /* close window */
423         if (gl_window) {
424                 SDL_DestroyWindow(gl_window);
425                 gl_window = NULL;
426         }
427
428         /* clear OpenGL context */
429         if (gl_context) {
430                 SDL_GL_DeleteContext(gl_context);
431                 gl_context = NULL;
432         }
433
434         /* exit SDL library */
435         if (audio_initialized) {
436                 SDL_PauseAudioDevice(audio_devid, 1);
437                 audio_initialized = 0;
438         }
439         if (audio_devid > 0) {
440                 SDL_CloseAudioDevice(audio_devid);
441                 audio_devid = 0;
442         }
443         if (sdl_initialized) {
444                 SDL_Quit();
445                 sdl_initialized = 0;
446         }
447 }
448
449 uint32_t ticks_sdl(void)
450 {
451         return SDL_GetTicks();
452 }