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,--
⑨