Joystick support
[mercenary-reloaded.git] / src / libsdl / sdl.c
index 99178db..b10efec 100644 (file)
 #include <stdio.h>
 #include <stdint.h>
 #include <errno.h>
+#include "print.h"
+#include "../../include/keycodes.h"
 #include "sdl.h"
-#include "opengl.h"
 
+#include <SDL2/SDL.h>
 #define GL3_PROTOTYPES 1
 #include <GL/glew.h>
 
 static int sdl_initialized = 0;
 static int audio_initialized = 0;
+static const char *device_string = NULL;
+static SDL_AudioDeviceID audio_devid = 0;
 static SDL_Window *gl_window = NULL;
 static SDL_GLContext gl_context = NULL;
-static void (*keyboard_sdl)(int down, SDL_Keycode sym) = NULL;
+static SDL_Joystick *sdl_joystick = NULL;
+static void (*keyboard_sdl)(int down, enum keycode keycode) = NULL;
+static void (*joystick_sdl)(double x, double y, int fire) = NULL;
 static void (*audio_sdl)(float *data, int len) = NULL;
+static void (*resize_window_sdl)(int width, int height) = NULL;
+static int joystick_y_axis_sdl = 1, joystick_x_axis_sdl = 0, joystick_fire_button_sdl = -1;
 
 static void audio_cb(void __attribute__((unused)) *userdata, Uint8 *stream, int len)
 {
        float audio_data[len / sizeof(float)];
 
        SDL_memset(stream, 0, len);
-       audio_sdl(audio_data, len / sizeof(float));
-       SDL_MixAudio(stream, (Uint8 *)audio_data, len, SDL_MIX_MAXVOLUME);
+       audio_sdl(audio_data, len / sizeof(float) / 2);
+       SDL_MixAudioFormat(stream, (Uint8 *)audio_data, AUDIO_F32, len, SDL_MIX_MAXVOLUME / 2.0);
 }
 
-int init_sdl(const char *progname, int width, int height, int sound_samplerate, int sound_chunk, void (*keyboard)(int down, SDL_Keycode sym), void (*audio)(float *data, int len))
+void sdl_list_joysticks(void)
 {
-       int rc;
+       int num, i, rc;
+
+       rc = SDL_Init(SDL_INIT_JOYSTICK);
+       if (rc < 0) {
+               print_error("Failed to init SDL\n");
+               return;
+       }
+
+       num = SDL_NumJoysticks();
+       for (i = 0; i < num; i++)
+               printf("#%d: %s\n", i, SDL_JoystickNameForIndex(i));
+
+       SDL_Quit();
+}
+
+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)
+{
+       int rc = -EINVAL;
 
        keyboard_sdl = keyboard;
+       joystick_sdl = joystick;
        audio_sdl = audio;
+       resize_window_sdl = resize_window;
+       if (joystick_x_axis >= 0)
+               joystick_x_axis_sdl = joystick_x_axis;
+       if (joystick_y_axis >= 0)
+               joystick_y_axis_sdl = joystick_y_axis;
+       if (joystick_fire_button >= 0)
+               joystick_fire_button_sdl = joystick_fire_button;
 
        /* init SDL library */
-       rc = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
+       rc = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK);
        if (rc < 0) {
-               fprintf(stderr, "Failed to init SDL\n");
+               print_error("Failed to init SDL\n");
                goto error;
        }
        sdl_initialized = 1;
 
+       SDL_JoystickEventState(SDL_ENABLE);
+       sdl_joystick = SDL_JoystickOpen((joysticknum < 0) ? 0 : joysticknum);
+       if (!sdl_joystick && joysticknum >= 0) {
+               rc = -EIO;
+               print_error("Failed to open joystick #%d, see help!\n", joysticknum);
+               goto error;
+       }
+
+retry_without_multisampling:
+       if (multisampling > 1) {
+               SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
+               SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
+               SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
+               SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
+               SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
+               SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
+               SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, multisampling);
+       }
+
        /* open window */
        gl_window = SDL_CreateWindow(progname, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
        if (!gl_window) {
-               fprintf(stderr, "Failed to open SDL window: %s\n", SDL_GetError());
+               if (multisampling) {
+                       print_info("Failed to open SDL window: %s (retrying without multisampling)\n", SDL_GetError());
+                       SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
+                       SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
+                       multisampling = 0;
+                       goto retry_without_multisampling;
+               }
+               print_error("Failed to open SDL window: %s\n", SDL_GetError());
+               SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
+               SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, multisampling);
                rc = EIO;
                goto error;
        }
@@ -68,68 +129,97 @@ int init_sdl(const char *progname, int width, int height, int sound_samplerate,
        /* create GL context */
        gl_context = SDL_GL_CreateContext(gl_window);
        if (!gl_context) {
-               fprintf(stderr, "Failed to create SDL's OpenGL context: %s\n", SDL_GetError());
+               print_error("Failed to create SDL's OpenGL context: %s\n", SDL_GetError());
                rc = EIO;
                goto error;
        }
 
        rc = SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
        if (rc < 0) {
-               fprintf(stderr, "Failed to set SDL's OpenGL context profile\n");
+               print_error("Failed to set SDL's OpenGL context profile\n");
                goto error;
        }
 
        rc = SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
        if (rc < 0) {
-               fprintf(stderr, "Failed to set SDL's OpenGL major version\n");
+               print_error("Failed to set SDL's OpenGL major version\n");
                goto error;
        }
        rc = SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
        if (rc < 0) {
-               fprintf(stderr, "Failed to set SDL's OpenGL minor version\n");
+               print_error("Failed to set SDL's OpenGL minor version\n");
                goto error;
        }
 
        rc = SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
        if (rc < 0) {
-               fprintf(stderr, "Failed to set SDL's OpenGL doublebuffer\n");
+               print_error("Failed to set SDL's OpenGL doublebuffer\n");
                goto error;
        }
 
-       rc = SDL_GL_SetSwapInterval(1);
+       rc = SDL_GL_SetSwapInterval((vbl_sync) ? 1 : 0);
        if (rc < 0) {
-               fprintf(stderr, "Failed to set SDL's OpenGL swap interval to VBLANK\n");
-               goto error;
+               print_error("Failed to set SDL's OpenGL swap interval to VBLANK\n");
+               // continue anyway
        }
 
+// seems like a hack to me. do we need this?
 #ifndef __APPLE__
+#if !defined(_WIN32)
        glewExperimental = GL_TRUE;
        if (glewInit() != GLEW_OK) {
-               fprintf(stderr, "Failed to init GLEW\n");
+               print_error("Failed to init GLEW\n");
                goto error;
        }
 #endif
+#endif
 
        /* just in case */
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_CULL_FACE);
+       if (multisampling > 1)
+               glEnable(GL_MULTISAMPLE);
+
+       if (rift) {
+               int i, count;
+               count = SDL_GetNumAudioDevices(0);
+               const char *string;
+               for(i = 0; i < count; i++) {
+                       string = SDL_GetAudioDeviceName(i, 0);
+                       if (strstr(string, "Rift"))
+                               device_string = string;
+               }
+               if (!device_string)
+                       print_error("Oculus Rift headset not found, falling back to default audio device.\n");
+       }
 
        /* open audio */
        SDL_AudioSpec want, have;
        SDL_memset(&want, 0, sizeof(want)); /* or SDL_zero(want) */
        want.freq = sound_samplerate;
        want.format = AUDIO_F32; /* we always use float in this project */
-       want.channels = 1;
+       want.channels = 2;
        want.samples = sound_chunk; /* must be a power of two */
        want.callback = audio_cb;
-       rc = SDL_OpenAudio(&want, &have);
-       if (rc < 0) {
-               fprintf(stderr, "Failed to open audio\n");
+       audio_devid = SDL_OpenAudioDevice(device_string, 0, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
+       if (audio_devid <= 0) {
+               print_error("Failed to open audio! (No speaker connected?)\n");
+               rc = -EIO;
+               goto error;
        } else if (have.format != want.format) {
-               fprintf(stderr, "Failed to open audio with desired audio format\n");
-               SDL_CloseAudio();
+               print_error("Failed to open audio with desired audio format (want %d, got %d)\n", want.format, have.format);
+               rc = -EIO;
+               goto error;
+       } else if (have.freq != want.freq) {
+               print_error("Failed to open audio with desired sample rate (want %d, got %d)\n", want.freq, have.freq);
+               rc = -EIO;
+               goto error;
+       } else if (have.channels != want.channels) {
+               print_error("Failed to open audio with desired number of channels (want %d, got %d)\n", want.channels, have.channels);
+               rc = -EIO;
+               goto error;
        } else {
-               SDL_PauseAudio(0);
+               SDL_PauseAudioDevice(audio_devid, 0);
                audio_initialized = 1;
        }
 
@@ -140,7 +230,113 @@ error:
        return rc;
 }
 
-static int key_ctrl = 0, fullscreen = 0;
+static enum keycode sdl2keycode(SDL_Keycode sym)
+{
+       switch (sym) {
+       case SDLK_LCTRL: return KEYCODE_LCTRL;
+       case SDLK_RCTRL: return KEYCODE_RCTRL;
+       case SDLK_LSHIFT: return KEYCODE_LSHIFT;
+       case SDLK_RSHIFT: return KEYCODE_RSHIFT;
+       case SDLK_LALT: return KEYCODE_LALT;
+       case SDLK_RALT: return KEYCODE_RALT;
+       case SDLK_PAUSE: return KEYCODE_PAUSE;
+       case SDLK_LEFT: return KEYCODE_LEFT;
+       case SDLK_RIGHT: return KEYCODE_RIGHT;
+       case SDLK_UP: return KEYCODE_UP;
+       case SDLK_DOWN: return KEYCODE_DOWN;
+       case SDLK_END: return KEYCODE_END;
+       case SDLK_a: return KEYCODE_a;
+       case SDLK_b: return KEYCODE_b;
+       case SDLK_c: return KEYCODE_c;
+       case SDLK_d: return KEYCODE_d;
+       case SDLK_e: return KEYCODE_e;
+       case SDLK_f: return KEYCODE_f;
+       case SDLK_g: return KEYCODE_g;
+       case SDLK_h: return KEYCODE_h;
+       case SDLK_i: return KEYCODE_i;
+       case SDLK_j: return KEYCODE_j;
+       case SDLK_k: return KEYCODE_k;
+       case SDLK_l: return KEYCODE_l;
+       case SDLK_m: return KEYCODE_m;
+       case SDLK_n: return KEYCODE_n;
+       case SDLK_o: return KEYCODE_o;
+       case SDLK_p: return KEYCODE_p;
+       case SDLK_q: return KEYCODE_q;
+       case SDLK_r: return KEYCODE_r;
+       case SDLK_s: return KEYCODE_s;
+       case SDLK_t: return KEYCODE_t;
+       case SDLK_u: return KEYCODE_u;
+       case SDLK_v: return KEYCODE_v;
+       case SDLK_w: return KEYCODE_w;
+       case SDLK_x: return KEYCODE_x;
+       case SDLK_y: return KEYCODE_y;
+       case SDLK_z: return KEYCODE_z;
+       case SDLK_0: return KEYCODE_0;
+       case SDLK_1: return KEYCODE_1;
+       case SDLK_2: return KEYCODE_2;
+       case SDLK_3: return KEYCODE_3;
+       case SDLK_4: return KEYCODE_4;
+       case SDLK_5: return KEYCODE_5;
+       case SDLK_6: return KEYCODE_6;
+       case SDLK_7: return KEYCODE_7;
+       case SDLK_8: return KEYCODE_8;
+       case SDLK_9: return KEYCODE_9;
+       case SDLK_KP_0: return KEYCODE_KP_0;
+       case SDLK_KP_1: return KEYCODE_KP_1;
+       case SDLK_KP_2: return KEYCODE_KP_2;
+       case SDLK_KP_3: return KEYCODE_KP_3;
+       case SDLK_KP_4: return KEYCODE_KP_4;
+       case SDLK_KP_5: return KEYCODE_KP_5;
+       case SDLK_KP_6: return KEYCODE_KP_6;
+       case SDLK_KP_7: return KEYCODE_KP_7;
+       case SDLK_KP_8: return KEYCODE_KP_8;
+       case SDLK_KP_9: return KEYCODE_KP_9;
+       case SDLK_KP_DIVIDE: return KEYCODE_KP_DIVIDE;
+       case SDLK_KP_MULTIPLY: return KEYCODE_KP_MULTIPLY;
+       case SDLK_KP_MINUS: return KEYCODE_KP_MINUS;
+       case SDLK_KP_PLUS: return KEYCODE_KP_PLUS;
+       case SDLK_KP_PERIOD: return KEYCODE_KP_PERIOD;
+       case SDLK_KP_ENTER: return KEYCODE_KP_ENTER;
+       case SDLK_F1: return KEYCODE_F1;
+       case SDLK_F2: return KEYCODE_F2;
+       case SDLK_F3: return KEYCODE_F3;
+       case SDLK_F4: return KEYCODE_F4;
+       case SDLK_F5: return KEYCODE_F5;
+       case SDLK_F6: return KEYCODE_F6;
+       case SDLK_F7: return KEYCODE_F7;
+       case SDLK_F8: return KEYCODE_F8;
+       case SDLK_F9: return KEYCODE_F9;
+       case SDLK_F10: return KEYCODE_F10;
+       case SDLK_SPACE: return KEYCODE_SPACE;
+       case SDLK_BACKSPACE: return KEYCODE_BACKSPACE;
+       case SDLK_TAB: return KEYCODE_TAB;
+       case SDLK_RETURN: return KEYCODE_RETURN;
+       case SDLK_ESCAPE: return KEYCODE_ESCAPE;
+       case SDLK_DELETE: return KEYCODE_DELETE;
+       case SDLK_INSERT: return KEYCODE_INSERT;
+       case SDLK_COMMA: return KEYCODE_COMMA;
+       case SDLK_PERIOD: return KEYCODE_PERIOD;
+       default: return KEYCODE_UNDEFINED;
+       }
+}
+
+static double joystick_x = 0, joystick_y = 0;
+static int joystick_fire = 0;
+
+static void joystick_event(int axis, int value)
+{
+       double v = (double)value  / 32767.0;
+
+       switch (axis) {
+       case 0: joystick_y = v; break;
+       case 1: joystick_x = v; break;
+       case 2: joystick_fire = value; break;
+       }
+
+       joystick_sdl(joystick_x, joystick_y, joystick_fire);
+}
+
+static int key_ctrl = 0, key_f = 0, fullscreen = 0;
 
 int event_sdl(void)
 {
@@ -148,40 +344,63 @@ int event_sdl(void)
        SDL_Event event;
 
        while (SDL_PollEvent(&event)) {
-               if (event.type == SDL_QUIT)
+               switch (event.type) {
+               case SDL_QUIT:
                        quit = 1;
-               if (event.type == SDL_WINDOWEVENT) {
+                       break;
+               case SDL_WINDOWEVENT:
                        if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
-                               resize_opengl(event.window.data1, event.window.data2);
-               }
-               if (event.type == SDL_KEYDOWN) {
+                               resize_window_sdl(event.window.data1, event.window.data2);
+                       break;
+               case SDL_KEYDOWN:
                        switch (event.key.keysym.sym) {
                        case SDLK_f:
-                               if (key_ctrl) {
+                               if (key_ctrl && !key_f) {
                                        if (fullscreen) {
                                                fullscreen = 0;
                                                SDL_SetWindowFullscreen(gl_window, 0);
+                                               SDL_ShowCursor(SDL_ENABLE);
                                        } else {
                                                SDL_SetWindowFullscreen(gl_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
+                                               SDL_ShowCursor(SDL_DISABLE);
                                                fullscreen = 1;
                                        }
                                }
+                               key_f = 1;
                                break;
                        case SDLK_LCTRL:
                        case SDLK_RCTRL:
                                key_ctrl = 1;
                                break;
                        }
-                       keyboard_sdl(1, event.key.keysym.sym);
-               }
-               if (event.type == SDL_KEYUP) {
+                       keyboard_sdl(1, sdl2keycode(event.key.keysym.sym));
+                       break;
+               case SDL_KEYUP:
                        switch (event.key.keysym.sym) {
                        case SDLK_LCTRL:
                        case SDLK_RCTRL:
                                key_ctrl = 0;
                                break;
+                       case SDLK_f:
+                               key_f = 0;
+                               break;
                        }
-                       keyboard_sdl(0, event.key.keysym.sym);
+                       keyboard_sdl(0, sdl2keycode(event.key.keysym.sym));
+                       break;
+               case SDL_JOYAXISMOTION:
+                       if (event.jaxis.axis == joystick_y_axis_sdl)
+                               joystick_event(0, event.jaxis.value);
+                       if (event.jaxis.axis == joystick_x_axis_sdl)
+                               joystick_event(1, event.jaxis.value);
+                       break;
+               case SDL_JOYBUTTONDOWN:
+                       if (joystick_fire_button_sdl < 0 || event.jbutton.button == joystick_fire_button_sdl)
+                               joystick_event(2, 1);
+                       break;
+               case SDL_JOYBUTTONUP:
+                       if (joystick_fire_button_sdl < 0 || event.jbutton.button == joystick_fire_button_sdl)
+                               joystick_event(2, 0);
+                       break;
                }
        }
 
@@ -195,6 +414,11 @@ void swap_sdl(void)
 
 void exit_sdl(void)
 {
+       /* close joystick */
+       if (sdl_joystick) {
+               SDL_JoystickClose(sdl_joystick);
+               sdl_joystick = NULL;
+       }
        /* close window */
        if (gl_window) {
                SDL_DestroyWindow(gl_window);
@@ -209,13 +433,20 @@ void exit_sdl(void)
 
        /* exit SDL library */
        if (audio_initialized) {
-               SDL_PauseAudio(1);
-               SDL_CloseAudio();
+               SDL_PauseAudioDevice(audio_devid, 1);
                audio_initialized = 0;
        }
+       if (audio_devid > 0) {
+               SDL_CloseAudioDevice(audio_devid);
+               audio_devid = 0;
+       }
        if (sdl_initialized) {
                SDL_Quit();
                sdl_initialized = 0;
        }
 }
 
+uint32_t ticks_sdl(void)
+{
+       return SDL_GetTicks();
+}