moved game controller base mappings file to new 'conf' sub-directory
[rocksndiamonds.git] / src / libgame / sdl.c
index 9ed0e49da6bc6367e88ae84be0c781780abd2fe1..3afecaf918c2e7bd991e3ec36e335cad334a65b3 100644 (file)
@@ -37,8 +37,10 @@ static boolean limit_screen_updates = FALSE;
 /* functions from SGE library */
 void sge_Line(SDL_Surface *, Sint16, Sint16, Sint16, Sint16, Uint32);
 
+#if defined(USE_TOUCH_INPUT_OVERLAY)
 /* functions to draw overlay graphics for touch device input */
 static void DrawTouchInputOverlay();
+#endif
 
 void SDLLimitScreenUpdates(boolean enable)
 {
@@ -205,8 +207,11 @@ static void UpdateScreenExt(SDL_Rect *rect, boolean with_frame_delay)
     SDL_RenderCopy(sdl_renderer, sdl_texture_target, src_rect2, dst_rect2);
   }
 
+#if defined(USE_TOUCH_INPUT_OVERLAY)
   // draw overlay graphics for touch device input, if needed
   DrawTouchInputOverlay();
+#endif
+
 #endif
 
   // global synchronization point of the game to align video frame delay
@@ -434,6 +439,9 @@ boolean SDLSetNativeSurface(SDL_Surface **surface)
 #if defined(TARGET_SDL2)
 static SDL_Texture *SDLCreateTextureFromSurface(SDL_Surface *surface)
 {
+  if (program.headless)
+    return NULL;
+
   SDL_Texture *texture = SDL_CreateTextureFromSurface(sdl_renderer, surface);
 
   if (texture == NULL)
@@ -497,8 +505,11 @@ void SDLInitVideoDisplay(void)
 #endif
 }
 
-void SDLInitVideoBuffer(boolean fullscreen)
+inline static void SDLInitVideoBuffer_VideoBuffer(boolean fullscreen)
 {
+  if (program.headless)
+    return;
+
   video.window_scaling_percent = setup.window_scaling_percent;
   video.window_scaling_quality = setup.window_scaling_quality;
 
@@ -526,7 +537,10 @@ void SDLInitVideoBuffer(boolean fullscreen)
 #else
   SDL_WM_SetCaption(program.window_title, program.window_title);
 #endif
+}
 
+inline static void SDLInitVideoBuffer_DrawBuffer()
+{
   /* SDL cannot directly draw to the visible video framebuffer like X11,
      but always uses a backbuffer, which is then blitted to the visible
      video framebuffer with 'SDL_UpdateRect' (or replaced with the current
@@ -542,6 +556,16 @@ void SDLInitVideoBuffer(boolean fullscreen)
 
   /* create additional (symbolic) buffer for double-buffering */
   ReCreateBitmap(&window, video.width, video.height);
+
+  /* create dummy drawing buffer for headless mode, if needed */
+  if (program.headless)
+    ReCreateBitmap(&backbuffer, video.width, video.height);
+}
+
+void SDLInitVideoBuffer(boolean fullscreen)
+{
+  SDLInitVideoBuffer_VideoBuffer(fullscreen);
+  SDLInitVideoBuffer_DrawBuffer();
 }
 
 static boolean SDLCreateScreen(boolean fullscreen)
@@ -565,10 +589,10 @@ static boolean SDLCreateScreen(boolean fullscreen)
      it will crash if flags are *not* set to SDL_RENDERER_SOFTWARE (because
      it will try to use accelerated graphics and apparently fails miserably) */
   int renderer_flags = SDL_RENDERER_SOFTWARE;
-#endif
 #endif
 
   SDLSetScreenSizeAndOffsets(video.width, video.height);
+#endif
 
   int width  = video.width;
   int height = video.height;
@@ -978,6 +1002,9 @@ void SDLRedrawWindow()
 void SDLCreateBitmapContent(Bitmap *bitmap, int width, int height,
                            int depth)
 {
+  if (program.headless)
+    return;
+
   SDL_Surface *surface =
     SDL_CreateRGBSurface(SURFACE_FLAGS, width, height, depth, 0,0,0, 0);
 
@@ -2362,6 +2389,14 @@ Bitmap *SDLLoadImage(char *filename)
   Bitmap *new_bitmap = CreateBitmapStruct();
   SDL_Surface *sdl_image_tmp;
 
+  if (program.headless)
+  {
+    /* prevent sanity check warnings at later stage */
+    new_bitmap->width = new_bitmap->height = 1;
+
+    return new_bitmap;
+  }
+
   print_timestamp_init("SDLLoadImage");
 
   print_timestamp_time(getBaseNamePtr(filename));
@@ -2451,6 +2486,9 @@ void SDLSetMouseCursor(struct MouseCursorInfo *cursor_info)
 
 void SDLOpenAudio(void)
 {
+  if (program.headless)
+    return;
+
 #if !defined(TARGET_SDL2)
   if (!strEqual(setup.system.sdl_audiodriver, ARG_DEFAULT))
     SDL_putenv(getStringCat2("SDL_AUDIODRIVER=", setup.system.sdl_audiodriver));
@@ -2553,31 +2591,83 @@ void SDLHandleWindowManagerEvent(Event *event)
 /* joystick functions                                                        */
 /* ========================================================================= */
 
-static SDL_Joystick *sdl_joystick[MAX_PLAYERS] = { NULL, NULL, NULL, NULL };
-static int sdl_js_axis[MAX_PLAYERS][2]   = { {0, 0}, {0, 0}, {0, 0}, {0, 0} };
-static int sdl_js_button[MAX_PLAYERS][2] = { {0, 0}, {0, 0}, {0, 0}, {0, 0} };
+#if defined(TARGET_SDL2)
+static void *sdl_joystick[MAX_PLAYERS];                // game controller or joystick
+#else
+static SDL_Joystick *sdl_joystick[MAX_PLAYERS];        // only joysticks supported
+#endif
+static int sdl_js_axis_raw[MAX_PLAYERS][2];
+static int sdl_js_axis[MAX_PLAYERS][2];
+static int sdl_js_button[MAX_PLAYERS][2];
+static boolean sdl_is_controller[MAX_PLAYERS];
 
-static boolean SDLOpenJoystick(int nr)
+void SDLClearJoystickState()
 {
-  if (nr < 0 || nr > MAX_PLAYERS)
+  int i, j;
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    for (j = 0; j < 2; j++)
+    {
+      sdl_js_axis_raw[i][j] = -1;
+      sdl_js_axis[i][j] = 0;
+      sdl_js_button[i][j] = 0;
+    }
+  }
+}
+
+boolean SDLOpenJoystick(int nr)
+{
+  if (nr < 0 || nr >= MAX_PLAYERS)
     return FALSE;
 
-  return ((sdl_joystick[nr] = SDL_JoystickOpen(nr)) == NULL ? FALSE : TRUE);
+#if defined(TARGET_SDL2)
+  sdl_is_controller[nr] = SDL_IsGameController(nr);
+#else
+  sdl_is_controller[nr] = FALSE;
+#endif
+
+#if 1
+  Error(ERR_DEBUG, "opening joystick %d (%s)",
+       nr, (sdl_is_controller[nr] ? "game controller" : "joystick"));
+#endif
+
+#if defined(TARGET_SDL2)
+  if (sdl_is_controller[nr])
+    sdl_joystick[nr] = SDL_GameControllerOpen(nr);
+  else
+    sdl_joystick[nr] = SDL_JoystickOpen(nr);
+#else
+  sdl_joystick[nr] = SDL_JoystickOpen(nr);
+#endif
+
+  return (sdl_joystick[nr] != NULL);
 }
 
-static void SDLCloseJoystick(int nr)
+void SDLCloseJoystick(int nr)
 {
-  if (nr < 0 || nr > MAX_PLAYERS)
+  if (nr < 0 || nr >= MAX_PLAYERS)
     return;
 
+#if 1
+  Error(ERR_DEBUG, "closing joystick %d", nr);
+#endif
+
+#if defined(TARGET_SDL2)
+  if (sdl_is_controller[nr])
+    SDL_GameControllerClose(sdl_joystick[nr]);
+  else
+    SDL_JoystickClose(sdl_joystick[nr]);
+#else
   SDL_JoystickClose(sdl_joystick[nr]);
+#endif
 
   sdl_joystick[nr] = NULL;
 }
 
-static boolean SDLCheckJoystickOpened(int nr)
+boolean SDLCheckJoystickOpened(int nr)
 {
-  if (nr < 0 || nr > MAX_PLAYERS)
+  if (nr < 0 || nr >= MAX_PLAYERS)
     return FALSE;
 
 #if defined(TARGET_SDL2)
@@ -2587,23 +2677,164 @@ static boolean SDLCheckJoystickOpened(int nr)
 #endif
 }
 
+static void setJoystickAxis(int nr, int axis_id_raw, int axis_value)
+{
+#if defined(TARGET_SDL2)
+  int axis_id = (axis_id_raw == SDL_CONTROLLER_AXIS_LEFTX ||
+                axis_id_raw == SDL_CONTROLLER_AXIS_RIGHTX ? 0 :
+                axis_id_raw == SDL_CONTROLLER_AXIS_LEFTY ||
+                axis_id_raw == SDL_CONTROLLER_AXIS_RIGHTY ? 1 : -1);
+#else
+  int axis_id = axis_id_raw % 2;
+#endif
+
+  if (nr < 0 || nr >= MAX_PLAYERS)
+    return;
+
+  if (axis_id == -1)
+    return;
+
+  // prevent (slightly jittering, but centered) axis A from resetting axis B
+  if (ABS(axis_value) < JOYSTICK_PERCENT * JOYSTICK_MAX_AXIS_POS / 100 &&
+      axis_id_raw != sdl_js_axis_raw[nr][axis_id])
+    return;
+
+  sdl_js_axis[nr][axis_id] = axis_value;
+  sdl_js_axis_raw[nr][axis_id] = axis_id_raw;
+}
+
+static void setJoystickButton(int nr, int button_id_raw, int button_state)
+{
+#if defined(TARGET_SDL2)
+  int button_id = (button_id_raw == SDL_CONTROLLER_BUTTON_A ||
+                  button_id_raw == SDL_CONTROLLER_BUTTON_X ||
+                  button_id_raw == SDL_CONTROLLER_BUTTON_LEFTSHOULDER ||
+                  button_id_raw == SDL_CONTROLLER_BUTTON_LEFTSTICK ||
+                  button_id_raw == SDL_CONTROLLER_BUTTON_RIGHTSTICK ? 0 :
+                  button_id_raw == SDL_CONTROLLER_BUTTON_B ||
+                  button_id_raw == SDL_CONTROLLER_BUTTON_Y ||
+                  button_id_raw == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER ? 1 :
+                  -1);
+
+  if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
+    sdl_js_axis[nr][0] = button_state * JOYSTICK_XLEFT;
+  else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
+    sdl_js_axis[nr][0] = button_state * JOYSTICK_XRIGHT;
+  else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_UP)
+    sdl_js_axis[nr][1] = button_state * JOYSTICK_YUPPER;
+  else if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+    sdl_js_axis[nr][1] = button_state * JOYSTICK_YLOWER;
+
+  if (button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_LEFT ||
+      button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_RIGHT ||
+      button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_UP ||
+      button_id_raw == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
+    sdl_js_axis_raw[nr][0] = sdl_js_axis_raw[nr][1] = -1;
+#else
+  int button_id = button_id_raw % 2;
+#endif
+
+  if (nr < 0 || nr >= MAX_PLAYERS)
+    return;
+
+  if (button_id == -1)
+    return;
+
+  sdl_js_button[nr][button_id] = button_state;
+}
+
 void HandleJoystickEvent(Event *event)
 {
   switch(event->type)
   {
+#if defined(TARGET_SDL2)
+    case SDL_CONTROLLERDEVICEADDED:
+#if 1
+      Error(ERR_DEBUG, "SDL_CONTROLLERDEVICEADDED: device %d added",
+           event->cdevice.which);
+#endif
+      InitJoysticks();
+      break;
+
+    case SDL_CONTROLLERDEVICEREMOVED:
+#if 1
+      Error(ERR_DEBUG, "SDL_CONTROLLERDEVICEREMOVED: device %d removed",
+           event->cdevice.which);
+#endif
+      InitJoysticks();
+      break;
+
+    case SDL_CONTROLLERAXISMOTION:
+#if 1
+      Error(ERR_DEBUG, "SDL_CONTROLLERAXISMOTION: device %d, axis %d: %d",
+           event->caxis.which, event->caxis.axis, event->caxis.value);
+#endif
+      setJoystickAxis(event->caxis.which,
+                     event->caxis.axis,
+                     event->caxis.value);
+      break;
+
+    case SDL_CONTROLLERBUTTONDOWN:
+#if 1
+      Error(ERR_DEBUG, "SDL_CONTROLLERBUTTONDOWN: device %d, button %d",
+           event->cbutton.which, event->cbutton.button);
+#endif
+      setJoystickButton(event->cbutton.which,
+                       event->cbutton.button,
+                       TRUE);
+      break;
+
+    case SDL_CONTROLLERBUTTONUP:
+#if 1
+      Error(ERR_DEBUG, "SDL_CONTROLLERBUTTONUP: device %d, button %d",
+           event->cbutton.which, event->cbutton.button);
+#endif
+      setJoystickButton(event->cbutton.which,
+                       event->cbutton.button,
+                       FALSE);
+      break;
+#endif
+
     case SDL_JOYAXISMOTION:
-      if (event->jaxis.axis < 2)
-       sdl_js_axis[event->jaxis.which][event->jaxis.axis]= event->jaxis.value;
+      if (sdl_is_controller[event->jaxis.which])
+       break;
+
+#if 1
+      Error(ERR_DEBUG, "SDL_JOYAXISMOTION: device %d, axis %d: %d",
+           event->jaxis.which, event->jaxis.axis, event->jaxis.value);
+#endif
+      if (event->jaxis.axis < 4)
+       setJoystickAxis(event->jaxis.which,
+                       event->jaxis.axis,
+                       event->jaxis.value);
       break;
 
     case SDL_JOYBUTTONDOWN:
-      if (event->jbutton.button < 2)
-       sdl_js_button[event->jbutton.which][event->jbutton.button] = TRUE;
+      if (sdl_is_controller[event->jaxis.which])
+       break;
+
+#if 1
+      Error(ERR_DEBUG, "SDL_JOYBUTTONDOWN: device %d, button %d",
+           event->jbutton.which, event->jbutton.button);
+#endif
+      if (event->jbutton.button < 4)
+       setJoystickButton(event->jbutton.which,
+                         event->jbutton.button,
+                         TRUE);
       break;
 
     case SDL_JOYBUTTONUP:
-      if (event->jbutton.button < 2)
-       sdl_js_button[event->jbutton.which][event->jbutton.button] = FALSE;
+      if (sdl_is_controller[event->jaxis.which])
+       break;
+
+#if 1
+      Error(ERR_DEBUG, "SDL_JOYBUTTONUP: device %d, button %d",
+           event->jbutton.which, event->jbutton.button);
+#endif
+      if (event->jbutton.button < 4)
+       setJoystickButton(event->jbutton.which,
+                         event->jbutton.button,
+                         FALSE);
       break;
 
     default:
@@ -2615,19 +2846,73 @@ void SDLInitJoysticks()
 {
   static boolean sdl_joystick_subsystem_initialized = FALSE;
   boolean print_warning = !sdl_joystick_subsystem_initialized;
+#if defined(TARGET_SDL2)
+  char *mappings_file_base = getPath2(options.conf_directory,
+                                     GAMECONTROLLER_BASENAME);
+  char *mappings_file_user = getPath2(getUserGameDataDir(),
+                                     GAMECONTROLLER_BASENAME);
+  int num_mappings;
+#endif
   int i;
 
   if (!sdl_joystick_subsystem_initialized)
   {
     sdl_joystick_subsystem_initialized = TRUE;
 
+#if defined(TARGET_SDL2)
+    SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
+
+    if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0)
+#else
     if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0)
+#endif
     {
       Error(ERR_EXIT, "SDL_Init() failed: %s", SDL_GetError());
       return;
     }
+
+#if defined(TARGET_SDL2)
+    num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_base);
+
+    if (num_mappings != -1)
+      Error(ERR_INFO, "%d game controller base mapping(s) added", num_mappings);
+    else
+      Error(ERR_WARN, "no game controller base mappings found");
+
+    num_mappings = SDL_GameControllerAddMappingsFromFile(mappings_file_user);
+
+    if (num_mappings != -1)
+      Error(ERR_INFO, "%d game controller user mapping(s) added", num_mappings);
+    else
+      Error(ERR_WARN, "no game controller user mappings found");
+
+    Error(ERR_INFO, "%d joystick(s) found:", SDL_NumJoysticks());
+
+    checked_free(mappings_file_base);
+    checked_free(mappings_file_user);
+
+    for (i = 0; i < SDL_NumJoysticks(); i++)
+    {
+      const char *name, *type;
+
+      if (SDL_IsGameController(i))
+      {
+       name = SDL_GameControllerNameForIndex(i);
+       type = "game controller";
+      }
+      else
+      {
+       name = SDL_JoystickNameForIndex(i);
+       type = "joystick";
+      }
+
+      Error(ERR_INFO, "- joystick %d (%s): '%s'",
+           i, type, (name ? name : "(Unknown)"));
+    }
+#endif
   }
 
+  /* assign joysticks from configured to connected joystick for all players */
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     /* get configured joystick for this player */
@@ -2642,29 +2927,24 @@ void SDLInitJoysticks()
       joystick_nr = -1;
     }
 
-    /* misuse joystick file descriptor variable to store joystick number */
-    joystick.fd[i] = joystick_nr;
-
-    if (joystick_nr == -1)
-      continue;
+    /* store configured joystick number for each player */
+    joystick.nr[i] = joystick_nr;
+  }
 
+  /* now open all connected joysticks (regardless if configured or not) */
+  for (i = 0; i < SDL_NumJoysticks(); i++)
+  {
     /* this allows subsequent calls to 'InitJoysticks' for re-initialization */
-    if (SDLCheckJoystickOpened(joystick_nr))
-      SDLCloseJoystick(joystick_nr);
+    if (SDLCheckJoystickOpened(i))
+      SDLCloseJoystick(i);
 
-    if (!setup.input[i].use_joystick)
-      continue;
-
-    if (!SDLOpenJoystick(joystick_nr))
-    {
-      if (print_warning)
-       Error(ERR_WARN, "cannot open joystick %d", joystick_nr);
-
-      continue;
-    }
-
-    joystick.status = JOYSTICK_ACTIVATED;
+    if (SDLOpenJoystick(i))
+      joystick.status = JOYSTICK_ACTIVATED;
+    else if (print_warning)
+      Error(ERR_WARN, "cannot open joystick %d", i);
   }
+
+  SDLClearJoystickState();
 }
 
 boolean SDLReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
@@ -2685,9 +2965,14 @@ boolean SDLReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
   return TRUE;
 }
 
+
+/* ========================================================================= */
+/* touch input overlay functions                                             */
+/* ========================================================================= */
+
+#if defined(USE_TOUCH_INPUT_OVERLAY)
 static void DrawTouchInputOverlay()
 {
-#if defined(USE_TOUCH_INPUT_OVERLAY)
   static SDL_Texture *texture = NULL;
   static boolean initialized = FALSE;
   static boolean deactivated = TRUE;
@@ -2696,11 +2981,12 @@ static void DrawTouchInputOverlay()
   static int alpha_step = 5;
   static int alpha_last = 0;
   static int alpha = 0;
+  boolean active = (overlay.enabled && overlay.active);
 
-  if (!overlay.active && deactivated)
+  if (!active && deactivated)
     return;
 
-  if (overlay.active)
+  if (active)
   {
     if (alpha < alpha_max)
       alpha = MIN(alpha + alpha_step, alpha_max);
@@ -2753,9 +3039,29 @@ static void DrawTouchInputOverlay()
 
   alpha_last = alpha;
 
+  float ratio_overlay = (float) width / height;
+  float ratio_screen = (float) video.screen_width / video.screen_height;
+  int width_scaled, height_scaled;
+  int xpos, ypos;
+
+  if (ratio_overlay > ratio_screen)
+  {
+    width_scaled = video.screen_width;
+    height_scaled = video.screen_height * ratio_screen / ratio_overlay;
+    xpos = 0;
+    ypos = video.screen_height - height_scaled;
+  }
+  else
+  {
+    width_scaled = video.screen_width * ratio_overlay / ratio_screen;
+    height_scaled = video.screen_height;
+    xpos = (video.screen_width - width_scaled) / 2;
+    ypos = 0;
+  }
+
   SDL_Rect src_rect = { 0, 0, width, height };
-  SDL_Rect dst_rect = { 0, 0, video.screen_width, video.screen_height };
+  SDL_Rect dst_rect = { xpos, ypos, width_scaled, height_scaled };
 
   SDL_RenderCopy(sdl_renderer, texture, &src_rect, &dst_rect);
-#endif
 }
+#endif