shithub: m8c

Download patch

ref: a4e93e180f339a2af400474fdfbf28329e2239f0
parent: 61fe9fb2c3a8b3bfa3c7247978ad60e0a6fa4fdd
author: Jonne Kokkonen <jonne.kokkonen@gmail.com>
date: Fri Apr 18 19:00:15 EDT 2025

Working gamepads

--- a/src/backends/audio_sdl.c
+++ b/src/backends/audio_sdl.c
@@ -25,8 +25,15 @@
     audio_close();
     return;
   }
+  if (bytes_available == 0) { return; }
 
   Uint8 *src_audio_data = SDL_malloc(bytes_available);
+  if (!src_audio_data) { // check allocation
+    SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "Failed to allocate audio buffer");
+    audio_close();
+    return;
+  }
+
   const int bytes_read = SDL_GetAudioStreamData(audio_stream_in, src_audio_data, bytes_available);
   if (bytes_read == bytes_available) {
     SDL_PutAudioStreamData(stream, src_audio_data, bytes_read);
@@ -57,13 +64,15 @@
 
 int audio_initialize(const char *output_device_name, const unsigned int audio_buffer_size) {
 
-  // wait for system to initialize possible new audio devices
-  SDL_Delay(500);
-
   int num_devices_in, num_devices_out;
   SDL_AudioDeviceID m8_device_id = 0;
   SDL_AudioDeviceID output_device_id = 0;
 
+  if (SDL_Init(SDL_INIT_AUDIO) == false) {
+    SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "SDL Audio init failed, SDL Error: %s", SDL_GetError());
+    return 0;
+  }
+
   SDL_AudioDeviceID *devices_in = SDL_GetAudioRecordingDevices(&num_devices_in);
   if (!devices_in) {
     SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "No audio capture devices, SDL Error: %s", SDL_GetError());
@@ -162,6 +171,7 @@
   SDL_Log("Closing audio devices");
   SDL_DestroyAudioStream(audio_stream_in);
   SDL_DestroyAudioStream(audio_stream_out);
+  SDL_QuitSubSystem(SDL_INIT_AUDIO);
   audio_initialized = 0;
 }
 
--- a/src/backends/m8_libserialport.c
+++ b/src/backends/m8_libserialport.c
@@ -46,7 +46,7 @@
 static int disconnect() {
   SDL_Log("Disconnecting M8");
 
-  // wait for serial processing thread to finish
+  // wait for the serial processing thread to finish
   thread_params.should_stop = 1;
   SDL_WaitThread(serial_thread, NULL);
   destroy_queue(&queue);
--- a/src/backends/m8_libusb.c
+++ b/src/backends/m8_libusb.c
@@ -297,7 +297,7 @@
 
   SDL_Delay(5);
   if ()
-  result = m8_reset_display();
+    result = m8_reset_display();
   return result;
 }
 
--- a/src/events.c
+++ b/src/events.c
@@ -1,7 +1,7 @@
 #include "events.h"
 #include "backends/m8.h"
 #include "common.h"
-#include "gamecontrollers.h"
+#include "gamepads.h"
 #include "input.h"
 #include "render.h"
 #include <SDL3/SDL.h>
@@ -50,7 +50,7 @@
   case SDL_EVENT_GAMEPAD_ADDED:
   case SDL_EVENT_GAMEPAD_REMOVED:
     // Reinitialize game controllers on controller add/remove/remap
-    gamecontrollers_initialize();
+    gamepads_initialize();
     break;
 
   case SDL_EVENT_KEY_DOWN:
--- a/src/gamecontrollers.c
+++ /dev/null
@@ -1,90 +1,0 @@
-//
-// Created by jonne on 8/19/24.
-//
-
-#include "gamecontrollers.h"
-#include "backends/m8.h"
-#include "config.h"
-#include "events.h"
-#include "render.h"
-
-#include <SDL3/SDL.h>
-#include <stdio.h>
-
-// Maximum number of game controllers to support
-#define MAX_CONTROLLERS 4
-
-static int num_joysticks = 0;
-SDL_Gamepad *game_controllers[MAX_CONTROLLERS];
-
-/**
- * Initializes available game controllers and loads game controller mappings.
- *
- * This function scans for connected joysticks and attempts to open those that are recognized
- * as game controllers. It also loads the game controller mapping database to improve compatibility
- * with various devices. Any errors during initialization are logged.
- *
- * @return The number of successfully initialized game controllers. Returns -1 if an error occurs
- *         during the initialization process.
- */
-int gamecontrollers_initialize() {
-
-  SDL_GetJoysticks(&num_joysticks);
-  int controller_index = 0;
-
-  SDL_Log("Looking for game controllers");
-  SDL_Delay(10); // Some controllers like XBone wired need a little while to get ready
-
-  // Try to load the game controller database file
-  char db_filename[2048] = {0};
-  if (snprintf(db_filename, sizeof(db_filename), "%sgamecontrollerdb.txt", SDL_GetPrefPath("", "m8c")) >= sizeof(db_filename)) {
-    SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Path too long for buffer");
-    return -1;
-}
-  SDL_Log("Trying to open game controller database from %s", db_filename);
-  SDL_IOStream *db_rw = SDL_IOFromFile(db_filename, "rb");
-  if (db_rw == NULL) {
-    snprintf(db_filename, sizeof(db_filename), "%sgamecontrollerdb.txt", SDL_GetBasePath());
-    SDL_Log("Trying to open game controller database from %s", db_filename);
-    db_rw = SDL_IOFromFile(db_filename, "rb");
-  }
-
-  if (db_rw != NULL) {
-    const int mappings = SDL_AddGamepadMappingsFromIO(db_rw, true);
-    if (mappings != -1) {
-      SDL_Log("Found %d game controller mappings", mappings);
-    } else {
-      SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Error loading game controller mappings.");
-    }
-    SDL_CloseIO(db_rw);
-  } else {
-    SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Unable to open game controller database file.");
-  }
-
-  // Open all available game controllers
-  for (int i = 0; i < num_joysticks; i++) {
-    if (!SDL_IsGamepad(i))
-      continue;
-    if (controller_index >= MAX_CONTROLLERS)
-      break;
-    game_controllers[controller_index] = SDL_OpenGamepad(i);
-    if (game_controllers[controller_index] == NULL) {
-      SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to open gamepad %d: %s", i, SDL_GetError());
-      continue;
-    }
-    SDL_Log("Controller %d: %s", controller_index + 1,
-            SDL_GetGamepadName(game_controllers[controller_index]));
-    controller_index++;
-  }
-
-  return controller_index;
-}
-
-// Closes all open game controllers
-void gamecontrollers_close() {
-
-  for (int i = 0; i < MAX_CONTROLLERS; i++) {
-    if (game_controllers[i])
-      SDL_CloseGamepad(game_controllers[i]);
-  }
-}
--- a/src/gamecontrollers.h
+++ /dev/null
@@ -1,16 +1,0 @@
-//
-// Created by jonne on 8/19/24.
-//
-
-#ifndef GAMECONTROLLERS_H_
-#define GAMECONTROLLERS_H_
-
-#include "config.h"
-#include "input.h"
-
-int gamecontrollers_initialize();
-void gamecontrollers_close();
-int gamecontrollers_handle_buttons(const config_params_s *conf);
-input_msg_s gamecontrollers_handle_special_messages(const config_params_s *conf);
-
-#endif //GAMECONTROLLERS_H_
--- /dev/null
+++ b/src/gamepads.c
@@ -1,0 +1,108 @@
+//
+// Created by jonne on 8/19/24.
+//
+
+#include "gamepads.h"
+#include "backends/m8.h"
+#include "config.h"
+#include "events.h"
+#include "render.h"
+
+#include <SDL3/SDL.h>
+#include <stdio.h>
+
+// Maximum number of game controllers to support
+#define MAX_CONTROLLERS 4
+
+SDL_Gamepad *game_controllers[MAX_CONTROLLERS];
+
+/**
+ * Initializes available game controllers and loads game controller mappings.
+ *
+ * This function scans for connected joysticks and attempts to open those that are recognized
+ * as game controllers. It also loads the game controller mapping database to improve compatibility
+ * with various devices. Any errors during initialization are logged.
+ *
+ * @return The number of successfully initialized game controllers. Returns -1 if an error occurs
+ *         during the initialization process.
+ */
+int gamepads_initialize() {
+
+  int num_joysticks = 0;
+  SDL_JoystickID *joystick_ids = NULL;
+
+  if (SDL_Init(SDL_INIT_GAMEPAD) == false) {
+    SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to initialize SDL_GAMEPAD: %s", SDL_GetError());
+    return -1;
+  }
+
+  int controller_index = 0;
+
+  SDL_Log("Looking for game controllers");
+  SDL_Delay(10); // Some controllers like XBone wired need a little while to get ready
+
+  // Try to load the game controller database file
+  char db_filename[2048] = {0};
+  if (snprintf(db_filename, sizeof(db_filename), "%sgamecontrollerdb.txt",
+               SDL_GetPrefPath("", "m8c")) >= sizeof(db_filename)) {
+    SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Path too long for buffer");
+    return -1;
+  }
+  SDL_Log("Trying to open game controller database from %s", db_filename);
+  SDL_IOStream *db_rw = SDL_IOFromFile(db_filename, "rb");
+  if (db_rw == NULL) {
+    snprintf(db_filename, sizeof(db_filename), "%sgamecontrollerdb.txt", SDL_GetBasePath());
+    SDL_Log("Trying to open game controller database from %s", db_filename);
+    db_rw = SDL_IOFromFile(db_filename, "rb");
+  }
+
+  if (db_rw != NULL) {
+    const int mappings = SDL_AddGamepadMappingsFromIO(db_rw, true);
+    if (mappings != -1) {
+      SDL_Log("Found %d game controller mappings", mappings);
+    } else {
+      SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Error loading game controller mappings.");
+    }
+    SDL_CloseIO(db_rw);
+  } else {
+    SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Unable to open game controller database file.");
+  }
+
+  joystick_ids = SDL_GetGamepads(&num_joysticks);
+  if (joystick_ids == NULL) {
+    SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to get gamepad IDs: %s", SDL_GetError());
+    return -1;
+  }
+
+  // Open all available game controllers
+  SDL_Log("Found %d gamepads", num_joysticks);
+  for (int i = 0; i < num_joysticks; i++) {
+    if (!SDL_IsGamepad(joystick_ids[i]))
+      continue;
+    if (controller_index >= MAX_CONTROLLERS)
+      break;
+    game_controllers[controller_index] = SDL_OpenGamepad(joystick_ids[i]);
+    if (game_controllers[controller_index] == NULL) {
+      SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Failed to open gamepad %d: %s", i, SDL_GetError());
+      continue;
+    }
+    SDL_Log("Controller %d: %s", controller_index + 1,
+            SDL_GetGamepadName(game_controllers[controller_index]));
+    controller_index++;
+  }
+
+  SDL_free(joystick_ids);
+
+  return controller_index;
+}
+
+// Closes all open game controllers
+void gamepads_close() {
+
+  for (int i = 0; i < MAX_CONTROLLERS; i++) {
+    if (game_controllers[i])
+      SDL_CloseGamepad(game_controllers[i]);
+  }
+
+  SDL_QuitSubSystem(SDL_INIT_GAMEPAD);
+}
--- /dev/null
+++ b/src/gamepads.h
@@ -1,0 +1,16 @@
+//
+// Created by jonne on 8/19/24.
+//
+
+#ifndef GAMECONTROLLERS_H_
+#define GAMECONTROLLERS_H_
+
+#include "config.h"
+#include "input.h"
+
+int gamepads_initialize();
+void gamepads_close();
+int gamecontrollers_handle_buttons(const config_params_s *conf);
+input_msg_s gamecontrollers_handle_special_messages(const config_params_s *conf);
+
+#endif //GAMECONTROLLERS_H_
--- a/src/input.c
+++ b/src/input.c
@@ -220,7 +220,7 @@
  * @param ctx Pointer to the app_context structure containing application state and configurations.
  * @param event Pointer to the SDL_Event structure representing the "key up" event.
  */
-void input_handle_key_up_event(struct app_context *ctx, const SDL_Event *event) {
+void input_handle_key_up_event(const struct app_context *ctx, const SDL_Event *event) {
   key = handle_m8_keys(event, &ctx->conf);
   if (keyjazz_enabled) {
     key = handle_keyjazz(event, key.value, &ctx->conf);
@@ -230,12 +230,10 @@
   input_process_and_send(ctx);
 }
 
-// Function to get the current gamepad state
-int get_gamepad_input_state(void) { return gamepad_state.current_buttons; }
-
 void input_handle_gamepad_button(struct app_context *ctx, const SDL_GamepadButton button,
                                  const bool pressed) {
   const config_params_s *conf = &ctx->conf;
+  static int prev_key_value = 0;
   int key_value = 0;
 
   // Handle standard buttons
@@ -256,7 +254,7 @@
     }
   }
 
-  if (pressed) {
+  if (pressed && key_value != prev_key_value) {
     gamepad_state.current_buttons |= key_value;
   } else {
     gamepad_state.current_buttons &= ~key_value;
@@ -273,6 +271,8 @@
     return;
   }
 
+  keycode = gamepad_state.current_buttons;
+
   input_process_and_send(ctx);
 }
 
@@ -281,8 +281,8 @@
  *
  * @param axis_value     The current value of the axis
  * @param threshold      The threshold that determines when a direction is activated
- * @param negative_key   The key to activate when axis value is below negative threshold
- * @param positive_key   The key to activate when axis value is above positive threshold
+ * @param negative_key   The key to activate when the axis value is below a negative threshold
+ * @param positive_key   The key to activate when the axis value is above a positive threshold
  */
 static void update_button_state_from_axis(Sint16 axis_value, int threshold,
                                          int negative_key, int positive_key) {
@@ -295,7 +295,15 @@
   }
 }
 
-
+/**
+ * Processes gamepad axis movements and updates internal state accordingly.
+ * Maps gamepad analog axis input to directional or functional button states
+ * based on the configuration and analog value threshold.
+ *
+ * @param ctx Pointer to the app_context structure containing application configuration and state.
+ * @param axis The gamepad axis being processed, specified as an SDL_GamepadAxis.
+ * @param value The analog value of the axis, typically ranging from -32768 to 32767.
+ */
 void input_handle_gamepad_axis(const struct app_context *ctx, const SDL_GamepadAxis axis,
                              const Sint16 value) {
   const config_params_s *conf = &ctx->conf;
@@ -315,16 +323,24 @@
   } else if (axis == conf->gamepad_analog_axis_edit) {
     update_button_state_from_axis(value, conf->gamepad_analog_threshold, key_edit, key_edit);
   }
-}
 
+  keycode = gamepad_state.current_buttons;
 
+  input_process_and_send(ctx);
+}
+
+/**
+ * Processes the current input message and sends the appropriate data based on its type.
+ *
+ * @param ctx Pointer to the app_context structure containing application configuration and state.
+ * @return An integer indicating the success status of the operation.
+ * Returns 1 for successful processing and sending of messages, or 0 if the device is not connected.
+ */
 int input_process_and_send(const struct app_context *ctx) {
   if (!ctx->device_connected) {
     return 0;
   }
   static unsigned char prev_input = 0;
-
-  keycode |= gamepad_state.current_buttons;
 
   // get current inputs
   const input_msg_s input = (input_msg_s){key.type, keycode, 0};
--- a/src/input.h
+++ b/src/input.h
@@ -48,7 +48,7 @@
 input_msg_s input_get_msg(config_params_s *conf);
 int input_process_and_send(const struct app_context *ctx);
 void input_handle_key_down_event(struct app_context *ctx, const SDL_Event *event);
-void input_handle_key_up_event(struct app_context *ctx, const SDL_Event *event);
+void input_handle_key_up_event(const struct app_context *ctx, const SDL_Event *event);
 void input_handle_gamepad_button(struct app_context *ctx, SDL_GamepadButton button, bool pressed);
 void input_handle_gamepad_axis(const struct app_context *ctx, SDL_GamepadAxis axis, Sint16 value);
 
--- a/src/main.c
+++ b/src/main.c
@@ -17,7 +17,7 @@
 #include "common.h"
 #include "config.h"
 #include "events.h"
-#include "gamecontrollers.h"
+#include "gamepads.h"
 #include "render.h"
 
 static void do_wait_for_device(struct app_context *ctx) {
@@ -108,7 +108,7 @@
  *         Returns 1 if the device is connected successfully, or 0 if not connected.
  */
 static unsigned char handle_m8_connection_init(const unsigned char wait_for_device,
-                                                  const char *preferred_device) {
+                                               const char *preferred_device) {
   const unsigned char device_connected = m8_initialize(1, preferred_device);
   if (!wait_for_device && device_connected == 0) {
     SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Device not detected!");
@@ -136,7 +136,7 @@
     }
     break;
 
-  case RUN:
+  case RUN: {
     const int result = m8_process_data(&ctx->conf);
     if (result == DEVICE_DISCONNECTED) {
       ctx->device_connected = 0;
@@ -147,6 +147,7 @@
     }
     render_screen();
     break;
+  }
 
   case QUIT:
     app_result = SDL_APP_SUCCESS;
@@ -160,6 +161,9 @@
 SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
   char *config_filename = NULL;
 
+  // Process the application's main callback roughly at 120 Hz
+  SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "120");
+
   struct app_context *ctx = SDL_calloc(1, sizeof(struct app_context));
   if (ctx == NULL) {
     SDL_LogCritical(SDL_LOG_CATEGORY_SYSTEM, "SDL_calloc failed: %s", SDL_GetError());
@@ -182,21 +186,22 @@
   // Show debug messages in the application log
   SDL_SetLogPriorities(SDL_LOG_PRIORITY_DEBUG);
   SDL_LogDebug(SDL_LOG_CATEGORY_TEST, "Running a Debug build");
+#else
+  // Show debug messages in the application log
+  SDL_SetLogPriorities(SDL_LOG_PRIORITY_INFO);
 #endif
 
-  if (gamecontrollers_initialize() < 0) {
+  if (gamepads_initialize() < 0) {
     SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Failed to initialize game controllers.");
     return SDL_APP_FAILURE;
   }
 
-  // Process the application's main callback roughly at 120 Hz
-  SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "120");
-
   if (ctx->device_connected && m8_enable_display(0)) {
     if (ctx->conf.audio_enabled) {
       audio_initialize(ctx->conf.audio_device_name, ctx->conf.audio_buffer_size);
     }
     ctx->app_state = RUN;
+    SDL_Delay(100);     // Give the device time to initialize
     m8_reset_display(); // Avoid display glitches.
   } else {
     SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Device not detected.");
@@ -217,7 +222,7 @@
     if (app->conf.audio_enabled) {
       audio_close();
     }
-    gamecontrollers_close();
+    gamepads_close();
     renderer_close();
     inline_font_close();
     if (app->device_connected) {
--- a/src/render.c
+++ b/src/render.c
@@ -63,9 +63,9 @@
   // SDL documentation recommends this
   atexit(SDL_Quit);
 
-  if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_GAMEPAD) == false) {
+  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) == false) {
     SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "SDL_Init: %s", SDL_GetError());
-    return false;
+    return 0;
   }
 
   if (!SDL_CreateWindowAndRenderer("m8c", texture_width * 2, texture_height * 2,
--